queries.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import { getDb } from './index'
  2. export type ReferrerMode = 'self' | 'follow_target'
  3. export interface WatchedAddress {
  4. id: number
  5. address: string
  6. label: string | null
  7. enabled: number
  8. copy_multiplier: number | null
  9. copy_max_usd: number | null
  10. referrer_mode: ReferrerMode
  11. created_at: string
  12. }
  13. export interface PositionMapping {
  14. id: number
  15. target_address: string
  16. target_nft_mint: string
  17. target_personal_position: string | null
  18. our_nft_mint: string | null
  19. pool_id: string
  20. tick_lower: number
  21. tick_upper: number
  22. status: string
  23. pnl_usd: string | null
  24. pnl_percent: string | null
  25. apr: string | null
  26. bonus_usd: string | null
  27. earned_usd: string | null
  28. liquidity_usd: string | null
  29. created_at: string
  30. updated_at: string
  31. }
  32. export interface CopyHistoryRow {
  33. id: number
  34. target_address: string
  35. target_tx_sig: string
  36. operation: string
  37. target_nft_mint: string | null
  38. our_nft_mint: string | null
  39. our_tx_sig: string | null
  40. pool_id: string | null
  41. target_amount_a: string | null
  42. target_amount_b: string | null
  43. our_amount_a: string | null
  44. our_amount_b: string | null
  45. status: string
  46. error_message: string | null
  47. swap_tx_sig: string | null
  48. created_at: string
  49. }
  50. // Watched Addresses
  51. export function getWatchedAddresses(): WatchedAddress[] {
  52. return getDb()
  53. .prepare('SELECT * FROM watched_addresses ORDER BY created_at DESC')
  54. .all() as WatchedAddress[]
  55. }
  56. export function getEnabledWatchedAddresses(): WatchedAddress[] {
  57. return getDb()
  58. .prepare('SELECT * FROM watched_addresses WHERE enabled = 1')
  59. .all() as WatchedAddress[]
  60. }
  61. export function addWatchedAddress(address: string, label?: string) {
  62. return getDb()
  63. .prepare('INSERT OR IGNORE INTO watched_addresses (address, label) VALUES (?, ?)')
  64. .run(address, label || null)
  65. }
  66. export function removeWatchedAddress(id: number) {
  67. return getDb().prepare('DELETE FROM watched_addresses WHERE id = ?').run(id)
  68. }
  69. export function toggleWatchedAddress(id: number, enabled: boolean) {
  70. return getDb()
  71. .prepare('UPDATE watched_addresses SET enabled = ? WHERE id = ?')
  72. .run(enabled ? 1 : 0, id)
  73. }
  74. export function getWatchedAddressByAddress(address: string): WatchedAddress | undefined {
  75. return getDb().prepare('SELECT * FROM watched_addresses WHERE address = ?').get(address) as
  76. | WatchedAddress
  77. | undefined
  78. }
  79. export function updateWatchedAddressSettings(
  80. id: number,
  81. settings: {
  82. copyMultiplier?: number | null
  83. copyMaxUsd?: number | null
  84. referrerMode?: ReferrerMode
  85. },
  86. ) {
  87. const sets: string[] = []
  88. const values: unknown[] = []
  89. if (settings.copyMultiplier !== undefined) {
  90. sets.push('copy_multiplier = ?')
  91. values.push(settings.copyMultiplier)
  92. }
  93. if (settings.copyMaxUsd !== undefined) {
  94. sets.push('copy_max_usd = ?')
  95. values.push(settings.copyMaxUsd)
  96. }
  97. if (settings.referrerMode !== undefined) {
  98. sets.push('referrer_mode = ?')
  99. values.push(settings.referrerMode)
  100. }
  101. if (sets.length === 0) return
  102. values.push(id)
  103. return getDb()
  104. .prepare(`UPDATE watched_addresses SET ${sets.join(', ')} WHERE id = ?`)
  105. .run(...values)
  106. }
  107. // Position Mappings
  108. export function getPositionMappings(status?: string): PositionMapping[] {
  109. if (status) {
  110. return getDb()
  111. .prepare('SELECT * FROM position_mappings WHERE status = ? ORDER BY created_at DESC')
  112. .all(status) as PositionMapping[]
  113. }
  114. return getDb()
  115. .prepare('SELECT * FROM position_mappings ORDER BY created_at DESC')
  116. .all() as PositionMapping[]
  117. }
  118. export function getPositionMappingByTargetNft(targetNftMint: string): PositionMapping | undefined {
  119. return getDb()
  120. .prepare('SELECT * FROM position_mappings WHERE target_nft_mint = ?')
  121. .get(targetNftMint) as PositionMapping | undefined
  122. }
  123. export function getPositionMappingByTargetPosition(
  124. targetPersonalPosition: string,
  125. ): PositionMapping | undefined {
  126. return getDb()
  127. .prepare('SELECT * FROM position_mappings WHERE target_personal_position = ?')
  128. .get(targetPersonalPosition) as PositionMapping | undefined
  129. }
  130. export function upsertPositionMapping(params: {
  131. targetAddress: string
  132. targetNftMint: string
  133. targetPersonalPosition?: string
  134. ourNftMint?: string
  135. poolId: string
  136. tickLower: number
  137. tickUpper: number
  138. status?: string
  139. }) {
  140. return getDb()
  141. .prepare(
  142. `INSERT INTO position_mappings (target_address, target_nft_mint, target_personal_position, our_nft_mint, pool_id, tick_lower, tick_upper, status)
  143. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  144. ON CONFLICT(target_nft_mint) DO UPDATE SET
  145. our_nft_mint = COALESCE(excluded.our_nft_mint, our_nft_mint),
  146. status = COALESCE(excluded.status, status),
  147. updated_at = datetime('now')`,
  148. )
  149. .run(
  150. params.targetAddress,
  151. params.targetNftMint,
  152. params.targetPersonalPosition || null,
  153. params.ourNftMint || null,
  154. params.poolId,
  155. params.tickLower,
  156. params.tickUpper,
  157. params.status || 'active',
  158. )
  159. }
  160. export function updatePositionMappingStatus(targetNftMint: string, status: string) {
  161. return getDb()
  162. .prepare(
  163. "UPDATE position_mappings SET status = ?, updated_at = datetime('now') WHERE target_nft_mint = ?",
  164. )
  165. .run(status, targetNftMint)
  166. }
  167. export function getPositionMappingById(id: number): PositionMapping | undefined {
  168. return getDb().prepare('SELECT * FROM position_mappings WHERE id = ?').get(id) as
  169. | PositionMapping
  170. | undefined
  171. }
  172. export function deletePositionMapping(id: number) {
  173. return getDb().prepare('DELETE FROM position_mappings WHERE id = ?').run(id)
  174. }
  175. export function updatePositionMappingSyncData(
  176. ourNftMint: string,
  177. data: {
  178. pnlUsd?: string
  179. pnlPercent?: string
  180. apr?: string
  181. bonusUsd?: string
  182. earnedUsd?: string
  183. liquidityUsd?: string
  184. },
  185. ) {
  186. return getDb()
  187. .prepare(
  188. `UPDATE position_mappings SET
  189. pnl_usd = ?, pnl_percent = ?, apr = ?, bonus_usd = ?, earned_usd = ?, liquidity_usd = ?,
  190. updated_at = datetime('now')
  191. WHERE our_nft_mint = ?`,
  192. )
  193. .run(
  194. data.pnlUsd || null,
  195. data.pnlPercent || null,
  196. data.apr || null,
  197. data.bonusUsd || null,
  198. data.earnedUsd || null,
  199. data.liquidityUsd || null,
  200. ourNftMint,
  201. )
  202. }
  203. export function getActivePositionNftMints(): string[] {
  204. const rows = getDb()
  205. .prepare("SELECT our_nft_mint FROM position_mappings WHERE status = 'active' AND our_nft_mint IS NOT NULL")
  206. .all() as { our_nft_mint: string }[]
  207. return rows.map((r) => r.our_nft_mint)
  208. }
  209. // Copy History
  210. export function addCopyHistory(params: {
  211. targetAddress: string
  212. targetTxSig: string
  213. operation: string
  214. targetNftMint?: string
  215. poolId?: string
  216. targetAmountA?: string
  217. targetAmountB?: string
  218. status?: string
  219. }): number {
  220. const result = getDb()
  221. .prepare(
  222. `INSERT INTO copy_history (target_address, target_tx_sig, operation, target_nft_mint, pool_id, target_amount_a, target_amount_b, status)
  223. VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
  224. )
  225. .run(
  226. params.targetAddress,
  227. params.targetTxSig,
  228. params.operation,
  229. params.targetNftMint || null,
  230. params.poolId || null,
  231. params.targetAmountA || null,
  232. params.targetAmountB || null,
  233. params.status || 'pending',
  234. )
  235. return result.lastInsertRowid as number
  236. }
  237. export function updateCopyHistory(
  238. id: number,
  239. updates: {
  240. ourNftMint?: string
  241. ourTxSig?: string
  242. ourAmountA?: string
  243. ourAmountB?: string
  244. status?: string
  245. errorMessage?: string
  246. swapTxSig?: string
  247. },
  248. ) {
  249. const sets: string[] = []
  250. const values: unknown[] = []
  251. if (updates.ourNftMint !== undefined) {
  252. sets.push('our_nft_mint = ?')
  253. values.push(updates.ourNftMint)
  254. }
  255. if (updates.ourTxSig !== undefined) {
  256. sets.push('our_tx_sig = ?')
  257. values.push(updates.ourTxSig)
  258. }
  259. if (updates.ourAmountA !== undefined) {
  260. sets.push('our_amount_a = ?')
  261. values.push(updates.ourAmountA)
  262. }
  263. if (updates.ourAmountB !== undefined) {
  264. sets.push('our_amount_b = ?')
  265. values.push(updates.ourAmountB)
  266. }
  267. if (updates.status !== undefined) {
  268. sets.push('status = ?')
  269. values.push(updates.status)
  270. }
  271. if (updates.errorMessage !== undefined) {
  272. sets.push('error_message = ?')
  273. values.push(updates.errorMessage)
  274. }
  275. if (updates.swapTxSig !== undefined) {
  276. sets.push('swap_tx_sig = ?')
  277. values.push(updates.swapTxSig)
  278. }
  279. if (sets.length === 0) return
  280. values.push(id)
  281. return getDb()
  282. .prepare(`UPDATE copy_history SET ${sets.join(', ')} WHERE id = ?`)
  283. .run(...values)
  284. }
  285. export function getCopyHistory(limit = 50, offset = 0): CopyHistoryRow[] {
  286. return getDb()
  287. .prepare('SELECT * FROM copy_history ORDER BY created_at DESC LIMIT ? OFFSET ?')
  288. .all(limit, offset) as CopyHistoryRow[]
  289. }
  290. // Settings
  291. export function getSetting(key: string): string | undefined {
  292. const row = getDb().prepare('SELECT value FROM settings WHERE key = ?').get(key) as
  293. | { value: string }
  294. | undefined
  295. return row?.value
  296. }
  297. export function setSetting(key: string, value: string) {
  298. return getDb()
  299. .prepare(
  300. `INSERT INTO settings (key, value) VALUES (?, ?)
  301. ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = datetime('now')`,
  302. )
  303. .run(key, value)
  304. }
  305. export function getAllSettings(): Record<string, string> {
  306. const rows = getDb().prepare('SELECT key, value FROM settings').all() as {
  307. key: string
  308. value: string
  309. }[]
  310. return Object.fromEntries(rows.map((r) => [r.key, r.value]))
  311. }
  312. // Check if TX already processed
  313. export function isTxProcessed(txSig: string): boolean {
  314. const row = getDb().prepare('SELECT id FROM copy_history WHERE target_tx_sig = ?').get(txSig) as
  315. | { id: number }
  316. | undefined
  317. return !!row
  318. }