@waiaas/daemon 2.11.0-rc.8 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/README.md +5 -5
  2. package/dist/api/middleware/address-validation.d.ts +6 -33
  3. package/dist/api/middleware/address-validation.d.ts.map +1 -1
  4. package/dist/api/middleware/address-validation.js +5 -129
  5. package/dist/api/middleware/address-validation.js.map +1 -1
  6. package/dist/api/middleware/host-guard.d.ts +1 -1
  7. package/dist/api/middleware/host-guard.js +2 -2
  8. package/dist/api/middleware/host-guard.js.map +1 -1
  9. package/dist/api/middleware/index.d.ts +1 -0
  10. package/dist/api/middleware/index.d.ts.map +1 -1
  11. package/dist/api/middleware/index.js +1 -0
  12. package/dist/api/middleware/index.js.map +1 -1
  13. package/dist/api/middleware/master-auth.d.ts +2 -5
  14. package/dist/api/middleware/master-auth.d.ts.map +1 -1
  15. package/dist/api/middleware/master-auth.js.map +1 -1
  16. package/dist/api/middleware/rate-limiter.d.ts +51 -0
  17. package/dist/api/middleware/rate-limiter.d.ts.map +1 -0
  18. package/dist/api/middleware/rate-limiter.js +146 -0
  19. package/dist/api/middleware/rate-limiter.js.map +1 -0
  20. package/dist/api/middleware/siwe-verify.d.ts +6 -26
  21. package/dist/api/middleware/siwe-verify.d.ts.map +1 -1
  22. package/dist/api/middleware/siwe-verify.js +5 -50
  23. package/dist/api/middleware/siwe-verify.js.map +1 -1
  24. package/dist/api/routes/actions.d.ts +1 -0
  25. package/dist/api/routes/actions.d.ts.map +1 -1
  26. package/dist/api/routes/actions.js +52 -4
  27. package/dist/api/routes/actions.js.map +1 -1
  28. package/dist/api/routes/admin-actions.d.ts +1 -0
  29. package/dist/api/routes/admin-actions.d.ts.map +1 -1
  30. package/dist/api/routes/admin-actions.js +3 -3
  31. package/dist/api/routes/admin-actions.js.map +1 -1
  32. package/dist/api/routes/admin-auth.d.ts.map +1 -1
  33. package/dist/api/routes/admin-auth.js +12 -7
  34. package/dist/api/routes/admin-auth.js.map +1 -1
  35. package/dist/api/routes/admin-credentials.js +2 -2
  36. package/dist/api/routes/admin-credentials.js.map +1 -1
  37. package/dist/api/routes/admin-monitoring.d.ts +10 -0
  38. package/dist/api/routes/admin-monitoring.d.ts.map +1 -1
  39. package/dist/api/routes/admin-monitoring.js +59 -14
  40. package/dist/api/routes/admin-monitoring.js.map +1 -1
  41. package/dist/api/routes/admin-notifications.d.ts.map +1 -1
  42. package/dist/api/routes/admin-notifications.js +2 -15
  43. package/dist/api/routes/admin-notifications.js.map +1 -1
  44. package/dist/api/routes/admin-settings.d.ts.map +1 -1
  45. package/dist/api/routes/admin-settings.js +90 -1
  46. package/dist/api/routes/admin-settings.js.map +1 -1
  47. package/dist/api/routes/admin-wallets.d.ts +16 -1
  48. package/dist/api/routes/admin-wallets.d.ts.map +1 -1
  49. package/dist/api/routes/admin-wallets.js +64 -75
  50. package/dist/api/routes/admin-wallets.js.map +1 -1
  51. package/dist/api/routes/admin.d.ts +1 -0
  52. package/dist/api/routes/admin.d.ts.map +1 -1
  53. package/dist/api/routes/admin.js.map +1 -1
  54. package/dist/api/routes/credentials.js +2 -2
  55. package/dist/api/routes/credentials.js.map +1 -1
  56. package/dist/api/routes/defi-positions.js.map +1 -1
  57. package/dist/api/routes/nfts.js.map +1 -1
  58. package/dist/api/routes/openapi-schemas.d.ts +412 -12
  59. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  60. package/dist/api/routes/openapi-schemas.js +38 -5
  61. package/dist/api/routes/openapi-schemas.js.map +1 -1
  62. package/dist/api/routes/policies.d.ts +2 -0
  63. package/dist/api/routes/policies.d.ts.map +1 -1
  64. package/dist/api/routes/policies.js +55 -6
  65. package/dist/api/routes/policies.js.map +1 -1
  66. package/dist/api/routes/rpc-proxy.js.map +1 -1
  67. package/dist/api/routes/sessions.d.ts.map +1 -1
  68. package/dist/api/routes/sessions.js +47 -28
  69. package/dist/api/routes/sessions.js.map +1 -1
  70. package/dist/api/routes/staking.d.ts.map +1 -1
  71. package/dist/api/routes/staking.js +4 -76
  72. package/dist/api/routes/staking.js.map +1 -1
  73. package/dist/api/routes/tokens.d.ts.map +1 -1
  74. package/dist/api/routes/tokens.js.map +1 -1
  75. package/dist/api/routes/transactions.d.ts +1 -0
  76. package/dist/api/routes/transactions.d.ts.map +1 -1
  77. package/dist/api/routes/transactions.js +8 -2
  78. package/dist/api/routes/transactions.js.map +1 -1
  79. package/dist/api/routes/userop.d.ts.map +1 -1
  80. package/dist/api/routes/userop.js +0 -2
  81. package/dist/api/routes/userop.js.map +1 -1
  82. package/dist/api/routes/wallet-apps.d.ts.map +1 -1
  83. package/dist/api/routes/wallet-apps.js +20 -13
  84. package/dist/api/routes/wallet-apps.js.map +1 -1
  85. package/dist/api/routes/wallet.js.map +1 -1
  86. package/dist/api/routes/wallets.d.ts.map +1 -1
  87. package/dist/api/routes/wallets.js +3 -0
  88. package/dist/api/routes/wallets.js.map +1 -1
  89. package/dist/api/routes/wc.d.ts.map +1 -1
  90. package/dist/api/routes/wc.js +13 -8
  91. package/dist/api/routes/wc.js.map +1 -1
  92. package/dist/api/routes/x402.d.ts.map +1 -1
  93. package/dist/api/routes/x402.js +1 -2
  94. package/dist/api/routes/x402.js.map +1 -1
  95. package/dist/api/server.d.ts +8 -4
  96. package/dist/api/server.d.ts.map +1 -1
  97. package/dist/api/server.js +46 -5
  98. package/dist/api/server.js.map +1 -1
  99. package/dist/constants.d.ts +1 -1
  100. package/dist/constants.d.ts.map +1 -1
  101. package/dist/constants.js +1 -1
  102. package/dist/constants.js.map +1 -1
  103. package/dist/infrastructure/action/action-provider-registry.d.ts.map +1 -1
  104. package/dist/infrastructure/action/action-provider-registry.js +2 -3
  105. package/dist/infrastructure/action/action-provider-registry.js.map +1 -1
  106. package/dist/infrastructure/action/builtin-metadata.d.ts +22 -0
  107. package/dist/infrastructure/action/builtin-metadata.d.ts.map +1 -0
  108. package/dist/infrastructure/action/builtin-metadata.js +29 -0
  109. package/dist/infrastructure/action/builtin-metadata.js.map +1 -0
  110. package/dist/infrastructure/adapter-pool.d.ts +2 -1
  111. package/dist/infrastructure/adapter-pool.d.ts.map +1 -1
  112. package/dist/infrastructure/adapter-pool.js.map +1 -1
  113. package/dist/infrastructure/auth/address-validation.d.ts +38 -0
  114. package/dist/infrastructure/auth/address-validation.d.ts.map +1 -0
  115. package/dist/infrastructure/auth/address-validation.js +134 -0
  116. package/dist/infrastructure/auth/address-validation.js.map +1 -0
  117. package/dist/infrastructure/auth/siwe-verify.d.ts +34 -0
  118. package/dist/infrastructure/auth/siwe-verify.d.ts.map +1 -0
  119. package/dist/infrastructure/auth/siwe-verify.js +58 -0
  120. package/dist/infrastructure/auth/siwe-verify.js.map +1 -0
  121. package/dist/infrastructure/auth/types.d.ts +12 -0
  122. package/dist/infrastructure/auth/types.d.ts.map +1 -0
  123. package/dist/infrastructure/auth/types.js +8 -0
  124. package/dist/infrastructure/auth/types.js.map +1 -0
  125. package/dist/infrastructure/config/loader.d.ts +1 -10
  126. package/dist/infrastructure/config/loader.d.ts.map +1 -1
  127. package/dist/infrastructure/config/loader.js +0 -2
  128. package/dist/infrastructure/config/loader.js.map +1 -1
  129. package/dist/infrastructure/database/migrate.d.ts +6 -18
  130. package/dist/infrastructure/database/migrate.d.ts.map +1 -1
  131. package/dist/infrastructure/database/migrate.js +25 -2856
  132. package/dist/infrastructure/database/migrate.js.map +1 -1
  133. package/dist/infrastructure/database/migrations/v11-v20.d.ts +17 -0
  134. package/dist/infrastructure/database/migrations/v11-v20.d.ts.map +1 -0
  135. package/dist/infrastructure/database/migrations/v11-v20.js +295 -0
  136. package/dist/infrastructure/database/migrations/v11-v20.js.map +1 -0
  137. package/dist/infrastructure/database/migrations/v2-v10.d.ts +16 -0
  138. package/dist/infrastructure/database/migrations/v2-v10.d.ts.map +1 -0
  139. package/dist/infrastructure/database/migrations/v2-v10.js +539 -0
  140. package/dist/infrastructure/database/migrations/v2-v10.js.map +1 -0
  141. package/dist/infrastructure/database/migrations/v21-v30.d.ts +17 -0
  142. package/dist/infrastructure/database/migrations/v21-v30.d.ts.map +1 -0
  143. package/dist/infrastructure/database/migrations/v21-v30.js +507 -0
  144. package/dist/infrastructure/database/migrations/v21-v30.js.map +1 -0
  145. package/dist/infrastructure/database/migrations/v31-v40.d.ts +17 -0
  146. package/dist/infrastructure/database/migrations/v31-v40.d.ts.map +1 -0
  147. package/dist/infrastructure/database/migrations/v31-v40.js +203 -0
  148. package/dist/infrastructure/database/migrations/v31-v40.js.map +1 -0
  149. package/dist/infrastructure/database/migrations/v41-v50.d.ts +17 -0
  150. package/dist/infrastructure/database/migrations/v41-v50.d.ts.map +1 -0
  151. package/dist/infrastructure/database/migrations/v41-v50.js +188 -0
  152. package/dist/infrastructure/database/migrations/v41-v50.js.map +1 -0
  153. package/dist/infrastructure/database/migrations/v51-v59.d.ts +17 -0
  154. package/dist/infrastructure/database/migrations/v51-v59.d.ts.map +1 -0
  155. package/dist/infrastructure/database/migrations/v51-v59.js +420 -0
  156. package/dist/infrastructure/database/migrations/v51-v59.js.map +1 -0
  157. package/dist/infrastructure/database/schema-ddl.d.ts +24 -0
  158. package/dist/infrastructure/database/schema-ddl.d.ts.map +1 -0
  159. package/dist/infrastructure/database/schema-ddl.js +596 -0
  160. package/dist/infrastructure/database/schema-ddl.js.map +1 -0
  161. package/dist/infrastructure/database/schema.d.ts +38 -0
  162. package/dist/infrastructure/database/schema.d.ts.map +1 -1
  163. package/dist/infrastructure/database/schema.js +2 -0
  164. package/dist/infrastructure/database/schema.js.map +1 -1
  165. package/dist/infrastructure/jwt/jwt-secret-manager.d.ts.map +1 -1
  166. package/dist/infrastructure/jwt/jwt-secret-manager.js +16 -3
  167. package/dist/infrastructure/jwt/jwt-secret-manager.js.map +1 -1
  168. package/dist/infrastructure/nft/alchemy-nft-indexer.d.ts.map +1 -1
  169. package/dist/infrastructure/nft/alchemy-nft-indexer.js +0 -1
  170. package/dist/infrastructure/nft/alchemy-nft-indexer.js.map +1 -1
  171. package/dist/infrastructure/nft/helius-nft-indexer.d.ts.map +1 -1
  172. package/dist/infrastructure/nft/helius-nft-indexer.js +1 -2
  173. package/dist/infrastructure/nft/helius-nft-indexer.js.map +1 -1
  174. package/dist/infrastructure/nft/nft-indexer-client.d.ts.map +1 -1
  175. package/dist/infrastructure/nft/nft-indexer-client.js +0 -2
  176. package/dist/infrastructure/nft/nft-indexer-client.js.map +1 -1
  177. package/dist/infrastructure/security/ssrf-guard.d.ts +33 -0
  178. package/dist/infrastructure/security/ssrf-guard.d.ts.map +1 -0
  179. package/dist/infrastructure/security/ssrf-guard.js +244 -0
  180. package/dist/infrastructure/security/ssrf-guard.js.map +1 -0
  181. package/dist/infrastructure/settings/hot-reload.d.ts +1 -1
  182. package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
  183. package/dist/infrastructure/settings/hot-reload.js +0 -2
  184. package/dist/infrastructure/settings/hot-reload.js.map +1 -1
  185. package/dist/infrastructure/settings/index.d.ts +2 -2
  186. package/dist/infrastructure/settings/index.d.ts.map +1 -1
  187. package/dist/infrastructure/settings/index.js +1 -1
  188. package/dist/infrastructure/settings/index.js.map +1 -1
  189. package/dist/infrastructure/settings/setting-keys.d.ts +14 -0
  190. package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
  191. package/dist/infrastructure/settings/setting-keys.js +296 -214
  192. package/dist/infrastructure/settings/setting-keys.js.map +1 -1
  193. package/dist/infrastructure/settings/settings-service.d.ts +6 -1
  194. package/dist/infrastructure/settings/settings-service.d.ts.map +1 -1
  195. package/dist/infrastructure/settings/settings-service.js +15 -5
  196. package/dist/infrastructure/settings/settings-service.js.map +1 -1
  197. package/dist/infrastructure/telegram/telegram-bot-service.d.ts.map +1 -1
  198. package/dist/infrastructure/telegram/telegram-bot-service.js +3 -2
  199. package/dist/infrastructure/telegram/telegram-bot-service.js.map +1 -1
  200. package/dist/infrastructure/token-registry/builtin-tokens.d.ts.map +1 -1
  201. package/dist/infrastructure/token-registry/builtin-tokens.js +4 -7
  202. package/dist/infrastructure/token-registry/builtin-tokens.js.map +1 -1
  203. package/dist/lifecycle/daemon-pipeline.d.ts +49 -0
  204. package/dist/lifecycle/daemon-pipeline.d.ts.map +1 -0
  205. package/dist/lifecycle/daemon-pipeline.js +281 -0
  206. package/dist/lifecycle/daemon-pipeline.js.map +1 -0
  207. package/dist/lifecycle/daemon-shutdown.d.ts +14 -0
  208. package/dist/lifecycle/daemon-shutdown.d.ts.map +1 -0
  209. package/dist/lifecycle/daemon-shutdown.js +176 -0
  210. package/dist/lifecycle/daemon-shutdown.js.map +1 -0
  211. package/dist/lifecycle/daemon-startup.d.ts +15 -0
  212. package/dist/lifecycle/daemon-startup.d.ts.map +1 -0
  213. package/dist/lifecycle/daemon-startup.js +1527 -0
  214. package/dist/lifecycle/daemon-startup.js.map +1 -0
  215. package/dist/lifecycle/daemon.d.ts +171 -114
  216. package/dist/lifecycle/daemon.d.ts.map +1 -1
  217. package/dist/lifecycle/daemon.js +22 -1904
  218. package/dist/lifecycle/daemon.js.map +1 -1
  219. package/dist/notifications/channels/discord.d.ts.map +1 -1
  220. package/dist/notifications/channels/discord.js +1 -0
  221. package/dist/notifications/channels/discord.js.map +1 -1
  222. package/dist/notifications/channels/slack.d.ts.map +1 -1
  223. package/dist/notifications/channels/slack.js +1 -0
  224. package/dist/notifications/channels/slack.js.map +1 -1
  225. package/dist/notifications/index.d.ts +0 -1
  226. package/dist/notifications/index.d.ts.map +1 -1
  227. package/dist/notifications/index.js +0 -1
  228. package/dist/notifications/index.js.map +1 -1
  229. package/dist/notifications/notification-service.d.ts.map +1 -1
  230. package/dist/notifications/notification-service.js +8 -6
  231. package/dist/notifications/notification-service.js.map +1 -1
  232. package/dist/pipeline/database-policy-engine.d.ts +18 -438
  233. package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
  234. package/dist/pipeline/database-policy-engine.js +154 -1321
  235. package/dist/pipeline/database-policy-engine.js.map +1 -1
  236. package/dist/pipeline/dry-run.d.ts +5 -2
  237. package/dist/pipeline/dry-run.d.ts.map +1 -1
  238. package/dist/pipeline/dry-run.js +102 -8
  239. package/dist/pipeline/dry-run.js.map +1 -1
  240. package/dist/pipeline/evaluators/allowed-tokens.d.ts +28 -0
  241. package/dist/pipeline/evaluators/allowed-tokens.d.ts.map +1 -0
  242. package/dist/pipeline/evaluators/allowed-tokens.js +129 -0
  243. package/dist/pipeline/evaluators/allowed-tokens.js.map +1 -0
  244. package/dist/pipeline/evaluators/approved-spenders.d.ts +26 -0
  245. package/dist/pipeline/evaluators/approved-spenders.d.ts.map +1 -0
  246. package/dist/pipeline/evaluators/approved-spenders.js +115 -0
  247. package/dist/pipeline/evaluators/approved-spenders.js.map +1 -0
  248. package/dist/pipeline/evaluators/contract-whitelist.d.ts +28 -0
  249. package/dist/pipeline/evaluators/contract-whitelist.d.ts.map +1 -0
  250. package/dist/pipeline/evaluators/contract-whitelist.js +168 -0
  251. package/dist/pipeline/evaluators/contract-whitelist.js.map +1 -0
  252. package/dist/pipeline/evaluators/helpers.d.ts +9 -0
  253. package/dist/pipeline/evaluators/helpers.d.ts.map +1 -0
  254. package/dist/pipeline/evaluators/helpers.js +13 -0
  255. package/dist/pipeline/evaluators/helpers.js.map +1 -0
  256. package/dist/pipeline/evaluators/lending-asset-whitelist.d.ts +18 -0
  257. package/dist/pipeline/evaluators/lending-asset-whitelist.d.ts.map +1 -0
  258. package/dist/pipeline/evaluators/lending-asset-whitelist.js +44 -0
  259. package/dist/pipeline/evaluators/lending-asset-whitelist.js.map +1 -0
  260. package/dist/pipeline/evaluators/lending-ltv-limit.d.ts +24 -0
  261. package/dist/pipeline/evaluators/lending-ltv-limit.d.ts.map +1 -0
  262. package/dist/pipeline/evaluators/lending-ltv-limit.js +130 -0
  263. package/dist/pipeline/evaluators/lending-ltv-limit.js.map +1 -0
  264. package/dist/pipeline/evaluators/spending-limit.d.ts +46 -0
  265. package/dist/pipeline/evaluators/spending-limit.d.ts.map +1 -0
  266. package/dist/pipeline/evaluators/spending-limit.js +241 -0
  267. package/dist/pipeline/evaluators/spending-limit.js.map +1 -0
  268. package/dist/pipeline/evaluators/types.d.ts +71 -0
  269. package/dist/pipeline/evaluators/types.d.ts.map +1 -0
  270. package/dist/pipeline/evaluators/types.js +7 -0
  271. package/dist/pipeline/evaluators/types.js.map +1 -0
  272. package/dist/pipeline/external-action-pipeline.js.map +1 -1
  273. package/dist/pipeline/gas-condition-tracker.d.ts +1 -1
  274. package/dist/pipeline/gas-condition-tracker.js +1 -1
  275. package/dist/pipeline/pipeline-helpers.d.ts +146 -0
  276. package/dist/pipeline/pipeline-helpers.d.ts.map +1 -0
  277. package/dist/pipeline/pipeline-helpers.js +260 -0
  278. package/dist/pipeline/pipeline-helpers.js.map +1 -0
  279. package/dist/pipeline/pipeline.d.ts +1 -0
  280. package/dist/pipeline/pipeline.d.ts.map +1 -1
  281. package/dist/pipeline/pipeline.js +3 -2
  282. package/dist/pipeline/pipeline.js.map +1 -1
  283. package/dist/pipeline/resolve-effective-amount-usd.d.ts.map +1 -1
  284. package/dist/pipeline/resolve-effective-amount-usd.js +4 -10
  285. package/dist/pipeline/resolve-effective-amount-usd.js.map +1 -1
  286. package/dist/pipeline/sign-message.js +1 -1
  287. package/dist/pipeline/sign-message.js.map +1 -1
  288. package/dist/pipeline/sleep.d.ts +1 -5
  289. package/dist/pipeline/sleep.d.ts.map +1 -1
  290. package/dist/pipeline/sleep.js +2 -7
  291. package/dist/pipeline/sleep.js.map +1 -1
  292. package/dist/pipeline/stage1-validate.d.ts +8 -0
  293. package/dist/pipeline/stage1-validate.d.ts.map +1 -0
  294. package/dist/pipeline/stage1-validate.js +69 -0
  295. package/dist/pipeline/stage1-validate.js.map +1 -0
  296. package/dist/pipeline/stage2-auth.d.ts +12 -0
  297. package/dist/pipeline/stage2-auth.d.ts.map +1 -0
  298. package/dist/pipeline/stage2-auth.js +18 -0
  299. package/dist/pipeline/stage2-auth.js.map +1 -0
  300. package/dist/pipeline/stage3-policy.d.ts +26 -0
  301. package/dist/pipeline/stage3-policy.d.ts.map +1 -0
  302. package/dist/pipeline/stage3-policy.js +384 -0
  303. package/dist/pipeline/stage3-policy.js.map +1 -0
  304. package/dist/pipeline/stage4-wait.d.ts +8 -0
  305. package/dist/pipeline/stage4-wait.d.ts.map +1 -0
  306. package/dist/pipeline/stage4-wait.js +87 -0
  307. package/dist/pipeline/stage4-wait.js.map +1 -0
  308. package/dist/pipeline/stage5-execute.d.ts +120 -0
  309. package/dist/pipeline/stage5-execute.d.ts.map +1 -0
  310. package/dist/pipeline/stage5-execute.js +1070 -0
  311. package/dist/pipeline/stage5-execute.js.map +1 -0
  312. package/dist/pipeline/stage6-confirm.d.ts +11 -0
  313. package/dist/pipeline/stage6-confirm.d.ts.map +1 -0
  314. package/dist/pipeline/stage6-confirm.js +110 -0
  315. package/dist/pipeline/stage6-confirm.js.map +1 -0
  316. package/dist/pipeline/stages.d.ts +11 -245
  317. package/dist/pipeline/stages.d.ts.map +1 -1
  318. package/dist/pipeline/stages.js +11 -1896
  319. package/dist/pipeline/stages.js.map +1 -1
  320. package/dist/rpc-proxy/sync-pipeline.js +2 -2
  321. package/dist/rpc-proxy/sync-pipeline.js.map +1 -1
  322. package/dist/services/autostop/autostop-service.d.ts +4 -1
  323. package/dist/services/autostop/autostop-service.d.ts.map +1 -1
  324. package/dist/services/autostop/autostop-service.js +27 -7
  325. package/dist/services/autostop/autostop-service.js.map +1 -1
  326. package/dist/services/defi/position-tracker.d.ts +5 -0
  327. package/dist/services/defi/position-tracker.d.ts.map +1 -1
  328. package/dist/services/defi/position-tracker.js +41 -6
  329. package/dist/services/defi/position-tracker.js.map +1 -1
  330. package/dist/services/defi/position-write-queue.d.ts.map +1 -1
  331. package/dist/services/defi/position-write-queue.js +3 -2
  332. package/dist/services/defi/position-write-queue.js.map +1 -1
  333. package/dist/services/incoming/__tests__/integration-wiring.test.js +58 -0
  334. package/dist/services/incoming/__tests__/integration-wiring.test.js.map +1 -1
  335. package/dist/services/incoming/incoming-tx-monitor-service.d.ts.map +1 -1
  336. package/dist/services/incoming/incoming-tx-monitor-service.js +11 -14
  337. package/dist/services/incoming/incoming-tx-monitor-service.js.map +1 -1
  338. package/dist/services/incoming/incoming-tx-workers.d.ts +2 -2
  339. package/dist/services/incoming/incoming-tx-workers.d.ts.map +1 -1
  340. package/dist/services/incoming/incoming-tx-workers.js +1 -1
  341. package/dist/services/incoming/incoming-tx-workers.js.map +1 -1
  342. package/dist/services/incoming/safety-rules.d.ts.map +1 -1
  343. package/dist/services/incoming/safety-rules.js +3 -2
  344. package/dist/services/incoming/safety-rules.js.map +1 -1
  345. package/dist/services/incoming/subscription-multiplexer.d.ts +2 -6
  346. package/dist/services/incoming/subscription-multiplexer.d.ts.map +1 -1
  347. package/dist/services/incoming/subscription-multiplexer.js +1 -3
  348. package/dist/services/incoming/subscription-multiplexer.js.map +1 -1
  349. package/dist/services/monitoring/balance-monitor-service.d.ts.map +1 -1
  350. package/dist/services/monitoring/balance-monitor-service.js +2 -2
  351. package/dist/services/monitoring/balance-monitor-service.js.map +1 -1
  352. package/dist/services/signing-sdk/approval-channel-router.d.ts +7 -7
  353. package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
  354. package/dist/services/signing-sdk/approval-channel-router.js +13 -13
  355. package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
  356. package/dist/services/signing-sdk/channels/index.d.ts +2 -2
  357. package/dist/services/signing-sdk/channels/index.d.ts.map +1 -1
  358. package/dist/services/signing-sdk/channels/index.js +1 -1
  359. package/dist/services/signing-sdk/channels/index.js.map +1 -1
  360. package/dist/services/signing-sdk/channels/push-relay-signing-channel.d.ts +59 -0
  361. package/dist/services/signing-sdk/channels/push-relay-signing-channel.d.ts.map +1 -0
  362. package/dist/services/signing-sdk/channels/push-relay-signing-channel.js +190 -0
  363. package/dist/services/signing-sdk/channels/push-relay-signing-channel.js.map +1 -0
  364. package/dist/services/signing-sdk/channels/telegram-signing-channel.d.ts +1 -1
  365. package/dist/services/signing-sdk/channels/telegram-signing-channel.js +1 -1
  366. package/dist/services/signing-sdk/channels/wallet-notification-channel.d.ts +6 -7
  367. package/dist/services/signing-sdk/channels/wallet-notification-channel.d.ts.map +1 -1
  368. package/dist/services/signing-sdk/channels/wallet-notification-channel.js +38 -55
  369. package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
  370. package/dist/services/signing-sdk/index.d.ts +3 -3
  371. package/dist/services/signing-sdk/index.d.ts.map +1 -1
  372. package/dist/services/signing-sdk/index.js +2 -2
  373. package/dist/services/signing-sdk/index.js.map +1 -1
  374. package/dist/services/signing-sdk/preset-auto-setup.js +2 -2
  375. package/dist/services/signing-sdk/preset-auto-setup.js.map +1 -1
  376. package/dist/services/signing-sdk/sign-request-builder.d.ts +2 -2
  377. package/dist/services/signing-sdk/sign-request-builder.d.ts.map +1 -1
  378. package/dist/services/signing-sdk/sign-request-builder.js +17 -25
  379. package/dist/services/signing-sdk/sign-request-builder.js.map +1 -1
  380. package/dist/services/signing-sdk/wallet-app-service.d.ts +4 -0
  381. package/dist/services/signing-sdk/wallet-app-service.d.ts.map +1 -1
  382. package/dist/services/signing-sdk/wallet-app-service.js +12 -5
  383. package/dist/services/signing-sdk/wallet-app-service.js.map +1 -1
  384. package/dist/services/staking/aggregate-staking-balance.d.ts +24 -0
  385. package/dist/services/staking/aggregate-staking-balance.d.ts.map +1 -0
  386. package/dist/services/staking/aggregate-staking-balance.js +82 -0
  387. package/dist/services/staking/aggregate-staking-balance.js.map +1 -0
  388. package/dist/services/wc-session-service.d.ts.map +1 -1
  389. package/dist/services/wc-session-service.js +2 -1
  390. package/dist/services/wc-session-service.js.map +1 -1
  391. package/dist/services/wc-signing-bridge.js +2 -2
  392. package/dist/services/wc-signing-bridge.js.map +1 -1
  393. package/dist/services/x402/payment-signer.d.ts.map +1 -1
  394. package/dist/services/x402/payment-signer.js +2 -5
  395. package/dist/services/x402/payment-signer.js.map +1 -1
  396. package/dist/services/x402/ssrf-guard.d.ts +4 -23
  397. package/dist/services/x402/ssrf-guard.d.ts.map +1 -1
  398. package/dist/services/x402/ssrf-guard.js +3 -232
  399. package/dist/services/x402/ssrf-guard.js.map +1 -1
  400. package/dist/signing/capabilities/eip712-signer.d.ts.map +1 -1
  401. package/dist/signing/capabilities/eip712-signer.js +2 -0
  402. package/dist/signing/capabilities/eip712-signer.js.map +1 -1
  403. package/package.json +5 -5
  404. package/public/admin/assets/index-CpFF2lCo.js +3 -0
  405. package/public/admin/index.html +1 -1
  406. package/dist/notifications/channels/ntfy.d.ts +0 -13
  407. package/dist/notifications/channels/ntfy.d.ts.map +0 -1
  408. package/dist/notifications/channels/ntfy.js +0 -74
  409. package/dist/notifications/channels/ntfy.js.map +0 -1
  410. package/dist/services/signing-sdk/channels/ntfy-signing-channel.d.ts +0 -66
  411. package/dist/services/signing-sdk/channels/ntfy-signing-channel.d.ts.map +0 -1
  412. package/dist/services/signing-sdk/channels/ntfy-signing-channel.js +0 -270
  413. package/dist/services/signing-sdk/channels/ntfy-signing-channel.js.map +0 -1
  414. package/public/admin/assets/index-CQ3i4P2U.js +0 -3
@@ -1,2878 +1,47 @@
1
1
  /**
2
2
  * Schema push + incremental migration runner for daemon SQLite database.
3
3
  *
4
- * Creates all 19 tables with indexes, foreign keys, and CHECK constraints
4
+ * Creates all tables with indexes, foreign keys, and CHECK constraints
5
5
  * using CREATE TABLE IF NOT EXISTS statements. After initial schema creation,
6
6
  * runs incremental migrations via runMigrations() for ALTER TABLE changes.
7
7
  *
8
8
  * v1.4+: DB schema changes MUST use ALTER TABLE incremental migrations (MIG-01~06).
9
9
  * DB deletion and recreation is prohibited.
10
10
  *
11
- * v1.4.2: agents table renamed to wallets (v3 migration). DDL uses latest
12
- * names (wallets, wallet_id). pushSchema records LATEST_SCHEMA_VERSION so
13
- * migrations are only needed for existing (pre-v3) databases.
14
- *
15
- * v1.4.6: Environment model migration:
16
- * v6a (version 6): Add network column to transactions with backfill from wallets
17
- * v6b (version 7): Replace wallets.network with environment + default_network (12-step)
18
- * v8 (version 8): Add network column to policies (12-step)
11
+ * DDL statements and migration definitions are split into submodules:
12
+ * - schema-ddl.ts: getCreateTableStatements(), getCreateIndexStatements(), LATEST_SCHEMA_VERSION
13
+ * - migrations/v2-v10.ts through migrations/v51-v59.ts: individual migration definitions
19
14
  *
20
15
  * @see docs/25-sqlite-schema.md
21
16
  * @see docs/65-migration-strategy.md
22
- * @see docs/69-db-migration-v6-design.md
23
- * @see docs/71-policy-engine-network-extension-design.md
24
- */
25
- import { WALLET_STATUSES, CHAIN_TYPES, NETWORK_TYPES, ENVIRONMENT_TYPES, TRANSACTION_STATUSES, TRANSACTION_TYPES, POLICY_TYPES, POLICY_TIERS, NOTIFICATION_LOG_STATUSES, INCOMING_TX_STATUSES, POSITION_CATEGORIES, POSITION_STATUSES, NETWORK_TO_CAIP2, tokenAssetId, ACCOUNT_TYPES, } from '@waiaas/core';
26
- // ---------------------------------------------------------------------------
27
- // Utility: build CHECK IN clause from SSoT arrays
28
- // ---------------------------------------------------------------------------
29
- const inList = (values) => values.map((v) => `'${v}'`).join(', ');
30
- /**
31
- * NETWORK_TYPES_WITH_LEGACY includes both old ('mainnet', 'devnet', 'testnet') and new
32
- * ('solana-mainnet', 'solana-devnet', 'solana-testnet') Solana network names.
33
- * Used in pre-v29 migrations so CHECK constraints accept data in either format
34
- * during the migration chain. Migration v29 converts old -> new, and post-v29
35
- * tables use NETWORK_TYPES (new names only).
36
- */
37
- const LEGACY_SOLANA_NETWORKS = ['mainnet', 'devnet', 'testnet'];
38
- const NETWORK_TYPES_WITH_LEGACY = [...new Set([...NETWORK_TYPES, ...LEGACY_SOLANA_NETWORKS])];
39
- /**
40
- * Map legacy bare Solana network names to their prefixed form.
41
- * Used by pre-v29 migrations (e.g., v22 asset_id backfill) that need to look up
42
- * NETWORK_TO_CAIP2 with potentially old-format data.
43
- */
44
- const LEGACY_NETWORK_NORMALIZE = {
45
- mainnet: 'solana-mainnet',
46
- devnet: 'solana-devnet',
47
- testnet: 'solana-testnet',
48
- };
49
- // ---------------------------------------------------------------------------
50
- // DDL statements for all 31 tables (latest schema: wallets + wallet_id + session_wallets + token_registry + settings + telegram_users + wc_sessions + wc_store + incoming_transactions + incoming_tx_cursors + defi_positions + wallet_apps + webhooks + webhook_logs + agent_identities + reputation_cache + nft_metadata_cache + userop_builds + hyperliquid_orders + hyperliquid_sub_accounts + polymarket_orders + polymarket_positions + polymarket_api_keys + wallet_credentials)
51
- // ---------------------------------------------------------------------------
52
- /**
53
- * The latest schema version that getCreateTableStatements() represents.
54
- * pushSchema() records this version for fresh databases so migrations are skipped.
55
- * Increment this whenever DDL statements are updated to match a new migration.
56
17
  */
57
- export const LATEST_SCHEMA_VERSION = 58;
58
- function getCreateTableStatements() {
59
- return [
60
- // Table 1: wallets (renamed from agents in v3, environment model in v6b, v29.3: default_network removed)
61
- `CREATE TABLE IF NOT EXISTS wallets (
62
- id TEXT PRIMARY KEY,
63
- name TEXT NOT NULL,
64
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
65
- environment TEXT NOT NULL CHECK (environment IN (${inList(ENVIRONMENT_TYPES)})),
66
- public_key TEXT NOT NULL,
67
- status TEXT NOT NULL DEFAULT 'CREATING' CHECK (status IN (${inList(WALLET_STATUSES)})),
68
- owner_address TEXT,
69
- owner_verified INTEGER NOT NULL DEFAULT 0 CHECK (owner_verified IN (0, 1)),
70
- created_at INTEGER NOT NULL,
71
- updated_at INTEGER NOT NULL,
72
- suspended_at INTEGER,
73
- suspension_reason TEXT,
74
- monitor_incoming INTEGER NOT NULL DEFAULT 0,
75
- owner_approval_method TEXT CHECK (owner_approval_method IS NULL OR owner_approval_method IN ('sdk_ntfy', 'sdk_telegram', 'walletconnect', 'telegram_bot', 'rest')),
76
- wallet_type TEXT,
77
- account_type TEXT NOT NULL DEFAULT 'eoa' CHECK (account_type IN (${inList(ACCOUNT_TYPES)})),
78
- signer_key TEXT,
79
- deployed INTEGER NOT NULL DEFAULT 1,
80
- entry_point TEXT,
81
- aa_provider TEXT CHECK (aa_provider IS NULL OR aa_provider IN ('pimlico', 'alchemy', 'custom')),
82
- aa_provider_api_key_encrypted TEXT,
83
- aa_bundler_url TEXT,
84
- aa_paymaster_url TEXT,
85
- aa_paymaster_policy_id TEXT,
86
- factory_address TEXT
87
- )`,
88
- // Table 2: sessions (v26.4: wallet_id removed, v26.5: token_issued_count added)
89
- `CREATE TABLE IF NOT EXISTS sessions (
90
- id TEXT PRIMARY KEY,
91
- token_hash TEXT NOT NULL,
92
- expires_at INTEGER NOT NULL,
93
- constraints TEXT,
94
- usage_stats TEXT,
95
- revoked_at INTEGER,
96
- renewal_count INTEGER NOT NULL DEFAULT 0,
97
- max_renewals INTEGER NOT NULL DEFAULT 0,
98
- last_renewed_at INTEGER,
99
- absolute_expires_at INTEGER NOT NULL,
100
- created_at INTEGER NOT NULL,
101
- source TEXT NOT NULL DEFAULT 'api',
102
- token_issued_count INTEGER NOT NULL DEFAULT 1
103
- )`,
104
- // Table 2b: session_wallets (v26.4: session-wallet junction for 1:N model, v29.3: is_default removed)
105
- `CREATE TABLE IF NOT EXISTS session_wallets (
106
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
107
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
108
- created_at INTEGER NOT NULL,
109
- PRIMARY KEY (session_id, wallet_id)
110
- )`,
111
- // Table 3: transactions (bridge_status + bridge_metadata added in v23)
112
- `CREATE TABLE IF NOT EXISTS transactions (
113
- id TEXT PRIMARY KEY,
114
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
115
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
116
- chain TEXT NOT NULL,
117
- tx_hash TEXT,
118
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
119
- amount TEXT,
120
- to_address TEXT,
121
- token_mint TEXT,
122
- contract_address TEXT,
123
- method_signature TEXT,
124
- spender_address TEXT,
125
- approved_amount TEXT,
126
- parent_id TEXT REFERENCES transactions(id) ON DELETE CASCADE,
127
- batch_index INTEGER,
128
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
129
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
130
- queued_at INTEGER,
131
- executed_at INTEGER,
132
- created_at INTEGER NOT NULL,
133
- reserved_amount TEXT,
134
- amount_usd REAL,
135
- reserved_amount_usd REAL,
136
- error TEXT,
137
- metadata TEXT,
138
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
139
- bridge_status TEXT CHECK (bridge_status IS NULL OR bridge_status IN ('PENDING', 'COMPLETED', 'FAILED', 'BRIDGE_MONITORING', 'TIMEOUT', 'REFUNDED', 'PARTIALLY_FILLED', 'FILLED', 'CANCELED', 'SETTLED', 'EXPIRED')),
140
- bridge_metadata TEXT,
141
- action_kind TEXT NOT NULL DEFAULT 'contractCall',
142
- venue TEXT,
143
- operation TEXT,
144
- external_id TEXT
145
- )`,
146
- // Table 4: policies (network column added in v8)
147
- `CREATE TABLE IF NOT EXISTS policies (
148
- id TEXT PRIMARY KEY,
149
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
150
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
151
- rules TEXT NOT NULL,
152
- priority INTEGER NOT NULL DEFAULT 0,
153
- enabled INTEGER NOT NULL DEFAULT 1,
154
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
155
- created_at INTEGER NOT NULL,
156
- updated_at INTEGER NOT NULL
157
- )`,
158
- // Table 5: pending_approvals (approval_channel added in v16, approval_type added in v39, typed_data_json added in v40)
159
- `CREATE TABLE IF NOT EXISTS pending_approvals (
160
- id TEXT PRIMARY KEY,
161
- tx_id TEXT NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
162
- required_by INTEGER NOT NULL,
163
- expires_at INTEGER NOT NULL,
164
- approved_at INTEGER,
165
- rejected_at INTEGER,
166
- owner_signature TEXT,
167
- approval_channel TEXT DEFAULT 'rest_api',
168
- approval_type TEXT NOT NULL DEFAULT 'SIWE' CHECK (approval_type IN ('SIWE', 'EIP712')),
169
- typed_data_json TEXT,
170
- created_at INTEGER NOT NULL
171
- )`,
172
- // Table 6: audit_log
173
- `CREATE TABLE IF NOT EXISTS audit_log (
174
- id INTEGER PRIMARY KEY AUTOINCREMENT,
175
- timestamp INTEGER NOT NULL,
176
- event_type TEXT NOT NULL,
177
- actor TEXT NOT NULL,
178
- wallet_id TEXT,
179
- session_id TEXT,
180
- tx_id TEXT,
181
- details TEXT NOT NULL,
182
- severity TEXT NOT NULL DEFAULT 'info' CHECK (severity IN ('info', 'warning', 'critical')),
183
- ip_address TEXT
184
- )`,
185
- // Table 7: key_value_store
186
- `CREATE TABLE IF NOT EXISTS key_value_store (
187
- key TEXT PRIMARY KEY,
188
- value TEXT NOT NULL,
189
- updated_at INTEGER NOT NULL
190
- )`,
191
- // Table 8: notification_logs
192
- `CREATE TABLE IF NOT EXISTS notification_logs (
193
- id TEXT PRIMARY KEY,
194
- event_type TEXT NOT NULL,
195
- wallet_id TEXT,
196
- channel TEXT NOT NULL,
197
- status TEXT NOT NULL CHECK (status IN (${inList(NOTIFICATION_LOG_STATUSES)})),
198
- error TEXT,
199
- message TEXT,
200
- created_at INTEGER NOT NULL
201
- )`,
202
- // Table 9: token_registry (asset_id added in v22)
203
- `CREATE TABLE IF NOT EXISTS token_registry (
204
- id TEXT PRIMARY KEY,
205
- network TEXT NOT NULL,
206
- address TEXT NOT NULL,
207
- symbol TEXT NOT NULL,
208
- name TEXT NOT NULL,
209
- decimals INTEGER NOT NULL,
210
- source TEXT NOT NULL DEFAULT 'custom' CHECK (source IN ('builtin', 'custom')),
211
- asset_id TEXT,
212
- created_at INTEGER NOT NULL
213
- )`,
214
- // Table 10: settings
215
- `CREATE TABLE IF NOT EXISTS settings (
216
- key TEXT PRIMARY KEY,
217
- value TEXT NOT NULL,
218
- encrypted INTEGER NOT NULL DEFAULT 0 CHECK (encrypted IN (0, 1)),
219
- category TEXT NOT NULL,
220
- updated_at INTEGER NOT NULL
221
- )`,
222
- // Table 11: schema_version
223
- `CREATE TABLE IF NOT EXISTS schema_version (
224
- version INTEGER PRIMARY KEY,
225
- applied_at INTEGER NOT NULL,
226
- description TEXT NOT NULL
227
- )`,
228
- // Table 13: telegram_users (Telegram Bot user management, v1.6)
229
- `CREATE TABLE IF NOT EXISTS telegram_users (
230
- chat_id INTEGER PRIMARY KEY,
231
- username TEXT,
232
- role TEXT NOT NULL DEFAULT 'PENDING' CHECK (role IN ('PENDING', 'ADMIN', 'READONLY')),
233
- registered_at INTEGER NOT NULL,
234
- approved_at INTEGER
235
- )`,
236
- // Table 14: wc_sessions (WalletConnect session metadata, v1.6.1)
237
- `CREATE TABLE IF NOT EXISTS wc_sessions (
238
- wallet_id TEXT PRIMARY KEY REFERENCES wallets(id) ON DELETE CASCADE,
239
- topic TEXT NOT NULL UNIQUE,
240
- peer_meta TEXT,
241
- chain_id TEXT NOT NULL,
242
- owner_address TEXT NOT NULL,
243
- namespaces TEXT,
244
- expiry INTEGER NOT NULL,
245
- created_at INTEGER NOT NULL
246
- )`,
247
- // Table 15: wc_store (WalletConnect IKeyValueStorage, v1.6.1)
248
- `CREATE TABLE IF NOT EXISTS wc_store (
249
- key TEXT PRIMARY KEY,
250
- value TEXT NOT NULL
251
- )`,
252
- // Table 16: incoming_transactions (detected incoming transfers to monitored wallets, v27.1)
253
- `CREATE TABLE IF NOT EXISTS incoming_transactions (
254
- id TEXT PRIMARY KEY,
255
- tx_hash TEXT NOT NULL,
256
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
257
- from_address TEXT NOT NULL,
258
- amount TEXT NOT NULL,
259
- token_address TEXT,
260
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
261
- network TEXT NOT NULL,
262
- status TEXT NOT NULL DEFAULT 'DETECTED' CHECK (status IN (${inList(INCOMING_TX_STATUSES)})),
263
- block_number INTEGER,
264
- detected_at INTEGER NOT NULL,
265
- confirmed_at INTEGER,
266
- is_suspicious INTEGER NOT NULL DEFAULT 0,
267
- UNIQUE(tx_hash, wallet_id)
268
- )`,
269
- // Table 17: incoming_tx_cursors (per-wallet cursor for gap recovery, v27.1)
270
- `CREATE TABLE IF NOT EXISTS incoming_tx_cursors (
271
- wallet_id TEXT PRIMARY KEY REFERENCES wallets(id) ON DELETE CASCADE,
272
- chain TEXT NOT NULL,
273
- network TEXT NOT NULL,
274
- last_signature TEXT,
275
- last_block_number INTEGER,
276
- updated_at INTEGER NOT NULL
277
- )`,
278
- // Table 18: defi_positions (DeFi position tracking, v29.2)
279
- `CREATE TABLE IF NOT EXISTS defi_positions (
280
- id TEXT PRIMARY KEY,
281
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
282
- category TEXT NOT NULL CHECK(category IN (${inList(POSITION_CATEGORIES)})),
283
- provider TEXT NOT NULL,
284
- chain TEXT NOT NULL CHECK(chain IN (${inList(CHAIN_TYPES)})),
285
- network TEXT CHECK(network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
286
- asset_id TEXT,
287
- amount TEXT NOT NULL,
288
- amount_usd REAL,
289
- metadata TEXT,
290
- status TEXT NOT NULL DEFAULT 'ACTIVE' CHECK(status IN (${inList(POSITION_STATUSES)})),
291
- opened_at INTEGER NOT NULL,
292
- closed_at INTEGER,
293
- last_synced_at INTEGER NOT NULL,
294
- created_at INTEGER NOT NULL,
295
- updated_at INTEGER NOT NULL
296
- )`,
297
- // Table 19: wallet_apps (Human Wallet Apps registry, v29.7, v29.10: sign_topic/notify_topic, v34: wallet_type, v35: subscription_token)
298
- `CREATE TABLE IF NOT EXISTS wallet_apps (
299
- id TEXT PRIMARY KEY,
300
- name TEXT NOT NULL UNIQUE,
301
- display_name TEXT NOT NULL,
302
- wallet_type TEXT NOT NULL DEFAULT '',
303
- signing_enabled INTEGER NOT NULL DEFAULT 1,
304
- alerts_enabled INTEGER NOT NULL DEFAULT 1,
305
- sign_topic TEXT,
306
- notify_topic TEXT,
307
- subscription_token TEXT,
308
- created_at INTEGER NOT NULL,
309
- updated_at INTEGER NOT NULL
310
- )`,
311
- // Table 20: webhooks (webhook outbound subscriptions, v37 OPS-04)
312
- `CREATE TABLE IF NOT EXISTS webhooks (
313
- id TEXT PRIMARY KEY,
314
- url TEXT NOT NULL,
315
- secret_hash TEXT NOT NULL,
316
- secret_encrypted TEXT NOT NULL,
317
- events TEXT NOT NULL DEFAULT '[]',
318
- description TEXT,
319
- enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
320
- created_at INTEGER NOT NULL,
321
- updated_at INTEGER NOT NULL
322
- )`,
323
- // Table 21: webhook_logs (webhook delivery attempt history, v37 OPS-04)
324
- `CREATE TABLE IF NOT EXISTS webhook_logs (
325
- id TEXT PRIMARY KEY,
326
- webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
327
- event_type TEXT NOT NULL,
328
- status TEXT NOT NULL CHECK (status IN ('success', 'failed')),
329
- http_status INTEGER,
330
- attempt INTEGER NOT NULL DEFAULT 1,
331
- error TEXT,
332
- request_duration INTEGER,
333
- created_at INTEGER NOT NULL
334
- )`,
335
- // Table 22: agent_identities (ERC-8004 agent identity tracking, v39)
336
- `CREATE TABLE IF NOT EXISTS agent_identities (
337
- id TEXT PRIMARY KEY,
338
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
339
- chain_agent_id TEXT NOT NULL,
340
- registry_address TEXT NOT NULL,
341
- chain_id INTEGER NOT NULL,
342
- agent_uri TEXT,
343
- registration_file_url TEXT,
344
- status TEXT NOT NULL DEFAULT 'PENDING'
345
- CHECK (status IN ('PENDING', 'REGISTERED', 'WALLET_LINKED', 'DEREGISTERED')),
346
- created_at INTEGER NOT NULL,
347
- updated_at INTEGER NOT NULL
348
- )`,
349
- // Table 23: reputation_cache (ERC-8004 reputation score cache, v39)
350
- `CREATE TABLE IF NOT EXISTS reputation_cache (
351
- agent_id TEXT NOT NULL,
352
- registry_address TEXT NOT NULL,
353
- tag1 TEXT NOT NULL DEFAULT '',
354
- tag2 TEXT NOT NULL DEFAULT '',
355
- score INTEGER NOT NULL,
356
- score_decimals INTEGER NOT NULL DEFAULT 0,
357
- feedback_count INTEGER NOT NULL DEFAULT 0,
358
- cached_at INTEGER NOT NULL,
359
- PRIMARY KEY (agent_id, registry_address, tag1, tag2)
360
- )`,
361
- // Table 24: nft_metadata_cache (NFT metadata caching with TTL, v44)
362
- `CREATE TABLE IF NOT EXISTS nft_metadata_cache (
363
- id TEXT PRIMARY KEY,
364
- contract_address TEXT NOT NULL,
365
- token_id TEXT NOT NULL,
366
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
367
- network TEXT NOT NULL CHECK (network IN (${inList(NETWORK_TYPES)})),
368
- metadata_json TEXT NOT NULL,
369
- cached_at INTEGER NOT NULL,
370
- expires_at INTEGER NOT NULL
371
- )`,
372
- // Table 25: userop_builds (UserOp Build/Sign API data, v45, network added v50)
373
- `CREATE TABLE IF NOT EXISTS userop_builds (
374
- id TEXT PRIMARY KEY,
375
- wallet_id TEXT NOT NULL,
376
- call_data TEXT NOT NULL,
377
- sender TEXT NOT NULL,
378
- nonce TEXT NOT NULL,
379
- entry_point TEXT NOT NULL,
380
- network TEXT,
381
- created_at INTEGER NOT NULL,
382
- expires_at INTEGER NOT NULL,
383
- used INTEGER NOT NULL DEFAULT 0 CHECK (used IN (0, 1))
384
- )`,
385
- // Table 26: hyperliquid_orders (Hyperliquid DEX order history, v51)
386
- `CREATE TABLE IF NOT EXISTS hyperliquid_orders (
387
- id TEXT PRIMARY KEY,
388
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
389
- sub_account_address TEXT,
390
- oid INTEGER,
391
- cloid TEXT,
392
- transaction_id TEXT REFERENCES transactions(id),
393
- market TEXT NOT NULL,
394
- asset_index INTEGER NOT NULL,
395
- side TEXT NOT NULL CHECK(side IN ('BUY', 'SELL')),
396
- order_type TEXT NOT NULL CHECK(order_type IN ('MARKET', 'LIMIT', 'STOP_MARKET', 'STOP_LIMIT', 'TAKE_PROFIT')),
397
- size TEXT NOT NULL,
398
- price TEXT,
399
- trigger_price TEXT,
400
- tif TEXT CHECK(tif IN ('GTC', 'IOC', 'ALO')),
401
- reduce_only INTEGER NOT NULL DEFAULT 0,
402
- status TEXT NOT NULL CHECK(status IN ('PENDING', 'RESTING', 'FILLED', 'PARTIALLY_FILLED', 'CANCELLED', 'REJECTED', 'TRIGGERED')),
403
- filled_size TEXT,
404
- avg_fill_price TEXT,
405
- is_spot INTEGER NOT NULL DEFAULT 0,
406
- leverage INTEGER,
407
- margin_mode TEXT CHECK(margin_mode IN ('CROSS', 'ISOLATED')),
408
- response_data TEXT,
409
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
410
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
411
- )`,
412
- // Table 27: hyperliquid_sub_accounts (Hyperliquid Sub-account mapping, v52)
413
- `CREATE TABLE IF NOT EXISTS hyperliquid_sub_accounts (
414
- id TEXT PRIMARY KEY,
415
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
416
- sub_account_address TEXT NOT NULL,
417
- name TEXT,
418
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
419
- UNIQUE(wallet_id, sub_account_address)
420
- )`,
421
- // Table 28: polymarket_orders (Polymarket CLOB order history, v53)
422
- `CREATE TABLE IF NOT EXISTS polymarket_orders (
423
- id TEXT PRIMARY KEY,
424
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
425
- transaction_id TEXT REFERENCES transactions(id),
426
- condition_id TEXT NOT NULL,
427
- token_id TEXT NOT NULL,
428
- market_slug TEXT,
429
- outcome TEXT NOT NULL,
430
- order_id TEXT,
431
- side TEXT NOT NULL CHECK (side IN ('BUY', 'SELL')),
432
- order_type TEXT NOT NULL CHECK (order_type IN ('GTC', 'GTD', 'FOK', 'IOC')),
433
- price TEXT NOT NULL,
434
- size TEXT NOT NULL,
435
- status TEXT NOT NULL CHECK (status IN ('PENDING', 'LIVE', 'MATCHED', 'PARTIALLY_FILLED', 'CANCELLED', 'EXPIRED')),
436
- filled_size TEXT,
437
- avg_fill_price TEXT,
438
- salt TEXT,
439
- maker_amount TEXT,
440
- taker_amount TEXT,
441
- signature_type INTEGER NOT NULL DEFAULT 0,
442
- fee_rate_bps INTEGER,
443
- expiration INTEGER,
444
- nonce TEXT,
445
- is_neg_risk INTEGER NOT NULL DEFAULT 0,
446
- response_data TEXT,
447
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
448
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
449
- )`,
450
- // Table 29: polymarket_positions (Polymarket position tracking, v54)
451
- `CREATE TABLE IF NOT EXISTS polymarket_positions (
452
- id TEXT PRIMARY KEY,
453
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
454
- condition_id TEXT NOT NULL,
455
- token_id TEXT NOT NULL,
456
- market_slug TEXT,
457
- outcome TEXT NOT NULL CHECK (outcome IN ('YES', 'NO')),
458
- size TEXT NOT NULL DEFAULT '0',
459
- avg_price TEXT,
460
- realized_pnl TEXT DEFAULT '0',
461
- market_resolved INTEGER NOT NULL DEFAULT 0,
462
- winning_outcome TEXT,
463
- is_neg_risk INTEGER NOT NULL DEFAULT 0,
464
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
465
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
466
- UNIQUE(wallet_id, token_id)
467
- )`,
468
- // Table 30: polymarket_api_keys (Polymarket CLOB API credentials, v54)
469
- `CREATE TABLE IF NOT EXISTS polymarket_api_keys (
470
- id TEXT PRIMARY KEY,
471
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
472
- api_key TEXT NOT NULL,
473
- api_secret_encrypted TEXT NOT NULL,
474
- api_passphrase_encrypted TEXT NOT NULL,
475
- signature_type INTEGER NOT NULL DEFAULT 0,
476
- proxy_address TEXT,
477
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
478
- UNIQUE(wallet_id)
479
- )`,
480
- // Table 31: wallet_credentials (External Action credential vault, v55)
481
- `CREATE TABLE IF NOT EXISTS wallet_credentials (
482
- id TEXT NOT NULL PRIMARY KEY,
483
- wallet_id TEXT,
484
- type TEXT NOT NULL CHECK (type IN ('api-key','hmac-secret','rsa-private-key','session-token','custom')),
485
- name TEXT NOT NULL,
486
- encrypted_value BLOB NOT NULL,
487
- iv BLOB NOT NULL,
488
- auth_tag BLOB NOT NULL,
489
- metadata TEXT NOT NULL DEFAULT '{}',
490
- expires_at INTEGER,
491
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
492
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
493
- FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE
494
- )`,
495
- ];
496
- }
497
- // ---------------------------------------------------------------------------
498
- // Index creation statements
18
+ import { getCreateTableStatements, getCreateIndexStatements, } from './schema-ddl.js';
19
+ import { migrations as v2to10 } from './migrations/v2-v10.js';
20
+ import { migrations as v11to20 } from './migrations/v11-v20.js';
21
+ import { migrations as v21to30 } from './migrations/v21-v30.js';
22
+ import { migrations as v31to40 } from './migrations/v31-v40.js';
23
+ import { migrations as v41to50 } from './migrations/v41-v50.js';
24
+ import { migrations as v51to59 } from './migrations/v51-v59.js';
25
+ // Re-export LATEST_SCHEMA_VERSION from schema-ddl
26
+ export { LATEST_SCHEMA_VERSION } from './schema-ddl.js';
27
+ // ---------------------------------------------------------------------------
28
+ // Global migration registry (assembled from submodules)
499
29
  // ---------------------------------------------------------------------------
500
- function getCreateIndexStatements() {
501
- return [
502
- // wallets indexes (renamed from agents in v3)
503
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_wallets_public_key ON wallets(public_key)',
504
- 'CREATE INDEX IF NOT EXISTS idx_wallets_status ON wallets(status)',
505
- 'CREATE INDEX IF NOT EXISTS idx_wallets_chain_environment ON wallets(chain, environment)',
506
- 'CREATE INDEX IF NOT EXISTS idx_wallets_owner_address ON wallets(owner_address)',
507
- // sessions indexes (v26.4: idx_sessions_wallet_id removed, wallet_id moved to session_wallets)
508
- 'CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at)',
509
- 'CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash)',
510
- // session_wallets indexes (v26.4)
511
- 'CREATE INDEX IF NOT EXISTS idx_session_wallets_session ON session_wallets(session_id)',
512
- 'CREATE INDEX IF NOT EXISTS idx_session_wallets_wallet ON session_wallets(wallet_id)',
513
- // transactions indexes
514
- 'CREATE INDEX IF NOT EXISTS idx_transactions_wallet_status ON transactions(wallet_id, status)',
515
- 'CREATE INDEX IF NOT EXISTS idx_transactions_session_id ON transactions(session_id)',
516
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_tx_hash ON transactions(tx_hash)',
517
- 'CREATE INDEX IF NOT EXISTS idx_transactions_queued_at ON transactions(queued_at)',
518
- 'CREATE INDEX IF NOT EXISTS idx_transactions_created_at ON transactions(created_at)',
519
- 'CREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(type)',
520
- 'CREATE INDEX IF NOT EXISTS idx_transactions_contract_address ON transactions(contract_address)',
521
- 'CREATE INDEX IF NOT EXISTS idx_transactions_parent_id ON transactions(parent_id)',
522
- // policies indexes
523
- 'CREATE INDEX IF NOT EXISTS idx_policies_wallet_enabled ON policies(wallet_id, enabled)',
524
- 'CREATE INDEX IF NOT EXISTS idx_policies_type ON policies(type)',
525
- 'CREATE INDEX IF NOT EXISTS idx_policies_network ON policies(network)',
526
- // pending_approvals indexes
527
- 'CREATE INDEX IF NOT EXISTS idx_pending_approvals_tx_id ON pending_approvals(tx_id)',
528
- 'CREATE INDEX IF NOT EXISTS idx_pending_approvals_expires_at ON pending_approvals(expires_at)',
529
- // audit_log indexes
530
- 'CREATE INDEX IF NOT EXISTS idx_audit_log_timestamp ON audit_log(timestamp)',
531
- 'CREATE INDEX IF NOT EXISTS idx_audit_log_event_type ON audit_log(event_type)',
532
- 'CREATE INDEX IF NOT EXISTS idx_audit_log_wallet_id ON audit_log(wallet_id)',
533
- 'CREATE INDEX IF NOT EXISTS idx_audit_log_severity ON audit_log(severity)',
534
- 'CREATE INDEX IF NOT EXISTS idx_audit_log_wallet_timestamp ON audit_log(wallet_id, timestamp)',
535
- 'CREATE INDEX IF NOT EXISTS idx_audit_log_tx_id ON audit_log(tx_id)',
536
- // notification_logs indexes
537
- 'CREATE INDEX IF NOT EXISTS idx_notification_logs_event_type ON notification_logs(event_type)',
538
- 'CREATE INDEX IF NOT EXISTS idx_notification_logs_wallet_id ON notification_logs(wallet_id)',
539
- 'CREATE INDEX IF NOT EXISTS idx_notification_logs_status ON notification_logs(status)',
540
- 'CREATE INDEX IF NOT EXISTS idx_notification_logs_created_at ON notification_logs(created_at)',
541
- // token_registry indexes
542
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_token_registry_network_address ON token_registry(network, address)',
543
- 'CREATE INDEX IF NOT EXISTS idx_token_registry_network ON token_registry(network)',
544
- // settings indexes
545
- 'CREATE INDEX IF NOT EXISTS idx_settings_category ON settings(category)',
546
- // telegram_users indexes
547
- 'CREATE INDEX IF NOT EXISTS idx_telegram_users_role ON telegram_users(role)',
548
- // wc_sessions indexes
549
- 'CREATE INDEX IF NOT EXISTS idx_wc_sessions_topic ON wc_sessions(topic)',
550
- // incoming_transactions indexes
551
- 'CREATE INDEX IF NOT EXISTS idx_incoming_tx_wallet_detected ON incoming_transactions(wallet_id, detected_at DESC)',
552
- 'CREATE INDEX IF NOT EXISTS idx_incoming_tx_detected_at ON incoming_transactions(detected_at)',
553
- 'CREATE INDEX IF NOT EXISTS idx_incoming_tx_chain_network ON incoming_transactions(chain, network)',
554
- "CREATE INDEX IF NOT EXISTS idx_incoming_tx_status ON incoming_transactions(status) WHERE status = 'DETECTED'",
555
- // v28.3: DeFi async tracking partial indexes on transactions
556
- 'CREATE INDEX IF NOT EXISTS idx_transactions_bridge_status ON transactions(bridge_status) WHERE bridge_status IS NOT NULL',
557
- "CREATE INDEX IF NOT EXISTS idx_transactions_gas_waiting ON transactions(status) WHERE status = 'GAS_WAITING'",
558
- // v29.2: defi_positions indexes
559
- 'CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_category ON defi_positions(wallet_id, category)',
560
- 'CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_provider ON defi_positions(wallet_id, provider)',
561
- 'CREATE INDEX IF NOT EXISTS idx_defi_positions_status ON defi_positions(status)',
562
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_defi_positions_unique ON defi_positions(wallet_id, provider, asset_id, category)',
563
- // v34: wallet_apps.wallet_type index
564
- 'CREATE INDEX IF NOT EXISTS idx_wallet_apps_wallet_type ON wallet_apps(wallet_type)',
565
- // v37: webhooks + webhook_logs indexes (OPS-04)
566
- 'CREATE INDEX IF NOT EXISTS idx_webhooks_enabled ON webhooks(enabled)',
567
- 'CREATE INDEX IF NOT EXISTS idx_webhook_logs_webhook_id ON webhook_logs(webhook_id)',
568
- 'CREATE INDEX IF NOT EXISTS idx_webhook_logs_event_type ON webhook_logs(event_type)',
569
- 'CREATE INDEX IF NOT EXISTS idx_webhook_logs_status ON webhook_logs(status)',
570
- 'CREATE INDEX IF NOT EXISTS idx_webhook_logs_created_at ON webhook_logs(created_at)',
571
- // v39: agent_identities indexes (ERC-8004)
572
- 'CREATE INDEX IF NOT EXISTS idx_agent_identities_wallet ON agent_identities(wallet_id)',
573
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_identities_chain ON agent_identities(registry_address, chain_agent_id)',
574
- // v44: nft_metadata_cache indexes
575
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_nft_cache_unique ON nft_metadata_cache(contract_address, token_id, chain, network)',
576
- 'CREATE INDEX IF NOT EXISTS idx_nft_cache_expires ON nft_metadata_cache(expires_at)',
577
- // v45: userop_builds indexes
578
- 'CREATE INDEX IF NOT EXISTS idx_userop_builds_wallet_id ON userop_builds(wallet_id)',
579
- 'CREATE INDEX IF NOT EXISTS idx_userop_builds_expires ON userop_builds(expires_at)',
580
- // v51: hyperliquid_orders indexes
581
- 'CREATE INDEX IF NOT EXISTS idx_hl_orders_wallet ON hyperliquid_orders(wallet_id)',
582
- 'CREATE INDEX IF NOT EXISTS idx_hl_orders_oid ON hyperliquid_orders(oid)',
583
- 'CREATE INDEX IF NOT EXISTS idx_hl_orders_market ON hyperliquid_orders(market)',
584
- 'CREATE INDEX IF NOT EXISTS idx_hl_orders_status ON hyperliquid_orders(status)',
585
- 'CREATE INDEX IF NOT EXISTS idx_hl_orders_created ON hyperliquid_orders(created_at)',
586
- // v52: hyperliquid_sub_accounts index
587
- 'CREATE INDEX IF NOT EXISTS idx_hl_sub_wallet ON hyperliquid_sub_accounts(wallet_id)',
588
- // v53: polymarket_orders indexes
589
- 'CREATE INDEX IF NOT EXISTS idx_pm_orders_wallet ON polymarket_orders(wallet_id)',
590
- 'CREATE INDEX IF NOT EXISTS idx_pm_orders_order_id ON polymarket_orders(order_id)',
591
- 'CREATE INDEX IF NOT EXISTS idx_pm_orders_condition ON polymarket_orders(condition_id)',
592
- 'CREATE INDEX IF NOT EXISTS idx_pm_orders_status ON polymarket_orders(status)',
593
- 'CREATE INDEX IF NOT EXISTS idx_pm_orders_created ON polymarket_orders(created_at)',
594
- // v54: polymarket_positions indexes
595
- 'CREATE INDEX IF NOT EXISTS idx_pm_positions_wallet ON polymarket_positions(wallet_id)',
596
- 'CREATE INDEX IF NOT EXISTS idx_pm_positions_condition ON polymarket_positions(condition_id)',
597
- 'CREATE INDEX IF NOT EXISTS idx_pm_positions_resolved ON polymarket_positions(market_resolved)',
598
- // v54: polymarket_api_keys indexes (UNIQUE on wallet_id is inline)
599
- // v55: wallet_credentials indexes
600
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_wallet_credentials_wallet_name ON wallet_credentials(wallet_id, name)',
601
- 'CREATE INDEX IF NOT EXISTS idx_wallet_credentials_global_name ON wallet_credentials(name) WHERE wallet_id IS NULL',
602
- 'CREATE INDEX IF NOT EXISTS idx_wallet_credentials_wallet_id ON wallet_credentials(wallet_id)',
603
- 'CREATE INDEX IF NOT EXISTS idx_wallet_credentials_expires_at ON wallet_credentials(expires_at) WHERE expires_at IS NOT NULL',
604
- // v56: transactions action tracking indexes
605
- 'CREATE INDEX IF NOT EXISTS idx_transactions_action_kind ON transactions(action_kind)',
606
- 'CREATE INDEX IF NOT EXISTS idx_transactions_venue ON transactions(venue) WHERE venue IS NOT NULL',
607
- 'CREATE INDEX IF NOT EXISTS idx_transactions_external_id ON transactions(external_id) WHERE external_id IS NOT NULL',
608
- // v57: composite index for external action tracking queries
609
- 'CREATE INDEX IF NOT EXISTS idx_transactions_action_kind_bridge_status ON transactions(action_kind, bridge_status) WHERE bridge_status IS NOT NULL',
610
- ];
611
- }
612
30
  /**
613
- * Global migration registry. v1.4 migrations will be added here in subsequent phases.
31
+ * Complete migration registry. Built by spreading all 6 version-range arrays.
614
32
  * Each migration's version must be unique and greater than 1.
615
33
  */
616
- export const MIGRATIONS = [];
617
- // ---------------------------------------------------------------------------
618
- // v2: Expand agents.network CHECK to include EVM networks
619
- // ---------------------------------------------------------------------------
620
- // SQLite cannot ALTER CHECK constraints, so we use 12-step table recreation.
621
- // This requires PRAGMA foreign_keys=OFF (handled by managesOwnTransaction).
622
- MIGRATIONS.push({
623
- version: 2,
624
- description: 'Expand agents network CHECK to include EVM networks',
625
- managesOwnTransaction: true,
626
- up: (sqlite) => {
627
- // Step 1: Begin transaction (foreign_keys already OFF via runner)
628
- sqlite.exec('BEGIN');
629
- try {
630
- // Step 2: Create new agents table with expanded CHECK (uses SSoT arrays)
631
- sqlite.exec(`CREATE TABLE agents_new (
632
- id TEXT PRIMARY KEY,
633
- name TEXT NOT NULL,
634
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
635
- network TEXT NOT NULL CHECK (network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
636
- public_key TEXT NOT NULL,
637
- status TEXT NOT NULL DEFAULT 'CREATING' CHECK (status IN (${inList(WALLET_STATUSES)})),
638
- owner_address TEXT,
639
- owner_verified INTEGER NOT NULL DEFAULT 0 CHECK (owner_verified IN (0, 1)),
640
- created_at INTEGER NOT NULL,
641
- updated_at INTEGER NOT NULL,
642
- suspended_at INTEGER,
643
- suspension_reason TEXT
644
- )`);
645
- // Step 3: Copy existing data
646
- sqlite.exec('INSERT INTO agents_new SELECT * FROM agents');
647
- // Step 4: Drop old table
648
- sqlite.exec('DROP TABLE agents');
649
- // Step 5: Rename new table
650
- sqlite.exec('ALTER TABLE agents_new RENAME TO agents');
651
- // Step 6: Recreate indexes
652
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_agents_public_key ON agents(public_key)');
653
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status)');
654
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_agents_chain_network ON agents(chain, network)');
655
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_agents_owner_address ON agents(owner_address)');
656
- // Step 7: Commit transaction
657
- sqlite.exec('COMMIT');
658
- }
659
- catch (err) {
660
- sqlite.exec('ROLLBACK');
661
- throw err;
662
- }
663
- // Step 8: Re-enable foreign keys to run integrity check
664
- sqlite.pragma('foreign_keys = ON');
665
- // Step 9: Verify FK integrity
666
- const fkErrors = sqlite.pragma('foreign_key_check');
667
- if (fkErrors.length > 0) {
668
- throw new Error(`FK integrity check failed after v2 migration: ${JSON.stringify(fkErrors)}`);
669
- }
670
- // Note: Runner will also set foreign_keys = ON after we return,
671
- // but we set it here to run the integrity check with FK enabled.
672
- },
673
- });
674
- // ---------------------------------------------------------------------------
675
- // v3: Rename agents to wallets (table, FK columns, indexes, enum data)
676
- // ---------------------------------------------------------------------------
677
- // Renames agents table to wallets, agent_id columns to wallet_id in 5 tables,
678
- // recreates all affected indexes, and updates AGENT_* enum data to WALLET_*.
679
- // Requires PRAGMA foreign_keys=OFF for table recreation (managesOwnTransaction).
680
- MIGRATIONS.push({
681
- version: 3,
682
- description: 'Rename agents to wallets (table, FK columns, indexes, enum data)',
683
- managesOwnTransaction: true,
684
- up: (sqlite) => {
685
- sqlite.exec('BEGIN');
686
- try {
687
- // Step 1: Rename agents table to wallets
688
- sqlite.exec('ALTER TABLE agents RENAME TO wallets');
689
- // Step 1b: Drop old agents indexes (ALTER TABLE RENAME doesn't rename indexes)
690
- sqlite.exec('DROP INDEX IF EXISTS idx_agents_public_key');
691
- sqlite.exec('DROP INDEX IF EXISTS idx_agents_status');
692
- sqlite.exec('DROP INDEX IF EXISTS idx_agents_chain_network');
693
- sqlite.exec('DROP INDEX IF EXISTS idx_agents_owner_address');
694
- // Step 2: Recreate sessions with wallet_id instead of agent_id
695
- sqlite.exec(`CREATE TABLE sessions_new (
696
- id TEXT PRIMARY KEY,
697
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
698
- token_hash TEXT NOT NULL,
699
- expires_at INTEGER NOT NULL,
700
- constraints TEXT,
701
- usage_stats TEXT,
702
- revoked_at INTEGER,
703
- renewal_count INTEGER NOT NULL DEFAULT 0,
704
- max_renewals INTEGER NOT NULL DEFAULT 30,
705
- last_renewed_at INTEGER,
706
- absolute_expires_at INTEGER NOT NULL,
707
- created_at INTEGER NOT NULL
708
- )`);
709
- sqlite.exec(`INSERT INTO sessions_new (id, wallet_id, token_hash, expires_at, constraints, usage_stats, revoked_at, renewal_count, max_renewals, last_renewed_at, absolute_expires_at, created_at)
710
- SELECT id, agent_id, token_hash, expires_at, constraints, usage_stats, revoked_at, renewal_count, max_renewals, last_renewed_at, absolute_expires_at, created_at FROM sessions`);
711
- sqlite.exec('DROP TABLE sessions');
712
- sqlite.exec('ALTER TABLE sessions_new RENAME TO sessions');
713
- // Step 3: Recreate transactions with wallet_id instead of agent_id
714
- sqlite.exec(`CREATE TABLE transactions_new (
715
- id TEXT PRIMARY KEY,
716
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
717
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
718
- chain TEXT NOT NULL,
719
- tx_hash TEXT,
720
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
721
- amount TEXT,
722
- to_address TEXT,
723
- token_mint TEXT,
724
- contract_address TEXT,
725
- method_signature TEXT,
726
- spender_address TEXT,
727
- approved_amount TEXT,
728
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
729
- batch_index INTEGER,
730
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
731
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
732
- queued_at INTEGER,
733
- executed_at INTEGER,
734
- created_at INTEGER NOT NULL,
735
- reserved_amount TEXT,
736
- error TEXT,
737
- metadata TEXT
738
- )`);
739
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata)
740
- SELECT id, agent_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata FROM transactions`);
741
- sqlite.exec('DROP TABLE transactions');
742
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
743
- // Step 4: Recreate policies with wallet_id instead of agent_id
744
- sqlite.exec(`CREATE TABLE policies_new (
745
- id TEXT PRIMARY KEY,
746
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
747
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
748
- rules TEXT NOT NULL,
749
- priority INTEGER NOT NULL DEFAULT 0,
750
- enabled INTEGER NOT NULL DEFAULT 1,
751
- created_at INTEGER NOT NULL,
752
- updated_at INTEGER NOT NULL
753
- )`);
754
- sqlite.exec(`INSERT INTO policies_new (id, wallet_id, type, rules, priority, enabled, created_at, updated_at)
755
- SELECT id, agent_id, type, rules, priority, enabled, created_at, updated_at FROM policies`);
756
- sqlite.exec('DROP TABLE policies');
757
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
758
- // Step 5: Recreate audit_log with wallet_id instead of agent_id (no FK constraint)
759
- sqlite.exec(`CREATE TABLE audit_log_new (
760
- id INTEGER PRIMARY KEY AUTOINCREMENT,
761
- timestamp INTEGER NOT NULL,
762
- event_type TEXT NOT NULL,
763
- actor TEXT NOT NULL,
764
- wallet_id TEXT,
765
- session_id TEXT,
766
- tx_id TEXT,
767
- details TEXT NOT NULL,
768
- severity TEXT NOT NULL DEFAULT 'info' CHECK (severity IN ('info', 'warning', 'critical')),
769
- ip_address TEXT
770
- )`);
771
- sqlite.exec(`INSERT INTO audit_log_new (id, timestamp, event_type, actor, wallet_id, session_id, tx_id, details, severity, ip_address)
772
- SELECT id, timestamp, event_type, actor, agent_id, session_id, tx_id, details, severity, ip_address FROM audit_log`);
773
- sqlite.exec('DROP TABLE audit_log');
774
- sqlite.exec('ALTER TABLE audit_log_new RENAME TO audit_log');
775
- // Step 6: Recreate notification_logs with wallet_id instead of agent_id (no FK constraint)
776
- sqlite.exec(`CREATE TABLE notification_logs_new (
777
- id TEXT PRIMARY KEY,
778
- event_type TEXT NOT NULL,
779
- wallet_id TEXT,
780
- channel TEXT NOT NULL,
781
- status TEXT NOT NULL CHECK (status IN (${inList(NOTIFICATION_LOG_STATUSES)})),
782
- error TEXT,
783
- created_at INTEGER NOT NULL
784
- )`);
785
- sqlite.exec(`INSERT INTO notification_logs_new (id, event_type, wallet_id, channel, status, error, created_at)
786
- SELECT id, event_type, agent_id, channel, status, error, created_at FROM notification_logs`);
787
- sqlite.exec('DROP TABLE notification_logs');
788
- sqlite.exec('ALTER TABLE notification_logs_new RENAME TO notification_logs');
789
- // Step 7: Recreate all indexes with wallet naming
790
- // wallets table indexes
791
- sqlite.exec('CREATE UNIQUE INDEX idx_wallets_public_key ON wallets(public_key)');
792
- sqlite.exec('CREATE INDEX idx_wallets_status ON wallets(status)');
793
- sqlite.exec('CREATE INDEX idx_wallets_chain_network ON wallets(chain, network)');
794
- sqlite.exec('CREATE INDEX idx_wallets_owner_address ON wallets(owner_address)');
795
- // sessions indexes (all recreated because table was dropped/recreated)
796
- sqlite.exec('CREATE INDEX idx_sessions_wallet_id ON sessions(wallet_id)');
797
- sqlite.exec('CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)');
798
- sqlite.exec('CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)');
799
- // transactions indexes (all recreated because table was dropped/recreated)
800
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
801
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
802
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
803
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
804
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
805
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
806
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
807
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
808
- // policies indexes (all recreated because table was dropped/recreated)
809
- sqlite.exec('CREATE INDEX idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
810
- sqlite.exec('CREATE INDEX idx_policies_type ON policies(type)');
811
- // audit_log indexes (all recreated because table was dropped/recreated)
812
- sqlite.exec('CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp)');
813
- sqlite.exec('CREATE INDEX idx_audit_log_event_type ON audit_log(event_type)');
814
- sqlite.exec('CREATE INDEX idx_audit_log_wallet_id ON audit_log(wallet_id)');
815
- sqlite.exec('CREATE INDEX idx_audit_log_severity ON audit_log(severity)');
816
- sqlite.exec('CREATE INDEX idx_audit_log_wallet_timestamp ON audit_log(wallet_id, timestamp)');
817
- // notification_logs indexes (all recreated because table was dropped/recreated)
818
- sqlite.exec('CREATE INDEX idx_notification_logs_event_type ON notification_logs(event_type)');
819
- sqlite.exec('CREATE INDEX idx_notification_logs_wallet_id ON notification_logs(wallet_id)');
820
- sqlite.exec('CREATE INDEX idx_notification_logs_status ON notification_logs(status)');
821
- sqlite.exec('CREATE INDEX idx_notification_logs_created_at ON notification_logs(created_at)');
822
- // Step 8: Update audit_log.event_type AGENT_* values to WALLET_*
823
- sqlite.exec("UPDATE audit_log SET event_type = 'WALLET_CREATED' WHERE event_type = 'AGENT_CREATED'");
824
- sqlite.exec("UPDATE audit_log SET event_type = 'WALLET_ACTIVATED' WHERE event_type = 'AGENT_ACTIVATED'");
825
- sqlite.exec("UPDATE audit_log SET event_type = 'WALLET_SUSPENDED' WHERE event_type = 'AGENT_SUSPENDED'");
826
- sqlite.exec("UPDATE audit_log SET event_type = 'WALLET_TERMINATED' WHERE event_type = 'AGENT_TERMINATED'");
827
- // Step 9: Update notification_logs.event_type AGENT_SUSPENDED to WALLET_SUSPENDED
828
- sqlite.exec("UPDATE notification_logs SET event_type = 'WALLET_SUSPENDED' WHERE event_type = 'AGENT_SUSPENDED'");
829
- sqlite.exec('COMMIT');
830
- }
831
- catch (err) {
832
- sqlite.exec('ROLLBACK');
833
- throw err;
834
- }
835
- // Re-enable FK and verify integrity
836
- sqlite.pragma('foreign_keys = ON');
837
- const fkErrors = sqlite.pragma('foreign_key_check');
838
- if (fkErrors.length > 0) {
839
- throw new Error(`FK integrity check failed after v3 migration: ${JSON.stringify(fkErrors)}`);
840
- }
841
- },
842
- });
843
- // ---------------------------------------------------------------------------
844
- // v4: Create token_registry table for EVM token management
845
- // ---------------------------------------------------------------------------
846
- MIGRATIONS.push({
847
- version: 4,
848
- description: 'Create token_registry table for EVM token management',
849
- up: (sqlite) => {
850
- sqlite.exec(`CREATE TABLE IF NOT EXISTS token_registry (
851
- id TEXT PRIMARY KEY,
852
- network TEXT NOT NULL,
853
- address TEXT NOT NULL,
854
- symbol TEXT NOT NULL,
855
- name TEXT NOT NULL,
856
- decimals INTEGER NOT NULL,
857
- source TEXT NOT NULL DEFAULT 'custom' CHECK (source IN ('builtin', 'custom')),
858
- created_at INTEGER NOT NULL
859
- )`);
860
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_token_registry_network_address ON token_registry(network, address)');
861
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_token_registry_network ON token_registry(network)');
862
- },
863
- });
864
- // ---------------------------------------------------------------------------
865
- // v5: Create settings table for operational config DB storage
866
- // ---------------------------------------------------------------------------
867
- MIGRATIONS.push({
868
- version: 5,
869
- description: 'Create settings table for operational config DB storage',
870
- up: (sqlite) => {
871
- sqlite.exec(`CREATE TABLE IF NOT EXISTS settings (
872
- key TEXT PRIMARY KEY,
873
- value TEXT NOT NULL,
874
- encrypted INTEGER NOT NULL DEFAULT 0 CHECK (encrypted IN (0, 1)),
875
- category TEXT NOT NULL,
876
- updated_at INTEGER NOT NULL
877
- )`);
878
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_settings_category ON settings(category)');
879
- },
880
- });
881
- // ---------------------------------------------------------------------------
882
- // v6a: Add network column to transactions with backfill from wallets
883
- // ---------------------------------------------------------------------------
884
- // Standard migration (managesOwnTransaction: false). Adds nullable network
885
- // column to transactions and backfills from wallets.network via FK relationship.
886
- // Must run BEFORE v6b which removes wallets.network.
887
- MIGRATIONS.push({
888
- version: 6,
889
- description: 'Add network column to transactions with backfill from wallets',
890
- managesOwnTransaction: false,
891
- up: (sqlite) => {
892
- // SQL 1: Add nullable network column
893
- sqlite.exec('ALTER TABLE transactions ADD COLUMN network TEXT');
894
- // SQL 2: Backfill from wallets.network via FK relationship
895
- sqlite.exec(`UPDATE transactions SET network = (
896
- SELECT w.network FROM wallets w WHERE w.id = transactions.wallet_id
897
- )`);
898
- },
899
- });
900
- // ---------------------------------------------------------------------------
901
- // v6b: Replace wallets.network with environment + default_network (12-step)
902
- // ---------------------------------------------------------------------------
903
- // 12-step table recreation. Converts wallets.network to environment + default_network.
904
- // Recreates FK dependent tables (sessions, transactions, policies, audit_log).
905
- // Requires PRAGMA foreign_keys=OFF (handled by managesOwnTransaction).
906
- // @see docs/69-db-migration-v6-design.md section 3
907
- MIGRATIONS.push({
908
- version: 7,
909
- description: 'Replace wallets.network with environment + default_network (12-step recreation)',
910
- managesOwnTransaction: true,
911
- up: (sqlite) => {
912
- // Step 1: Begin transaction
913
- sqlite.exec('BEGIN');
914
- try {
915
- // Step 2: Create wallets_new with environment + default_network
916
- sqlite.exec(`CREATE TABLE wallets_new (
917
- id TEXT PRIMARY KEY,
918
- name TEXT NOT NULL,
919
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
920
- environment TEXT NOT NULL CHECK (environment IN (${inList(ENVIRONMENT_TYPES)})),
921
- default_network TEXT CHECK (default_network IS NULL OR default_network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
922
- public_key TEXT NOT NULL,
923
- status TEXT NOT NULL DEFAULT 'CREATING' CHECK (status IN (${inList(WALLET_STATUSES)})),
924
- owner_address TEXT,
925
- owner_verified INTEGER NOT NULL DEFAULT 0 CHECK (owner_verified IN (0, 1)),
926
- created_at INTEGER NOT NULL,
927
- updated_at INTEGER NOT NULL,
928
- suspended_at INTEGER,
929
- suspension_reason TEXT
930
- )`);
931
- // Step 3: Data transformation INSERT with 13 CASE WHEN branches
932
- // Maps network -> environment using deriveEnvironment() logic (docs/68 section 3.3)
933
- // Preserves original network as default_network
934
- sqlite.exec(`INSERT INTO wallets_new (
935
- id, name, chain, environment, default_network,
936
- public_key, status, owner_address, owner_verified,
937
- created_at, updated_at, suspended_at, suspension_reason
938
- )
939
- SELECT
940
- id, name, chain,
941
- CASE
942
- WHEN network = 'mainnet' THEN 'mainnet'
943
- WHEN network = 'devnet' THEN 'testnet'
944
- WHEN network = 'testnet' THEN 'testnet'
945
- WHEN network = 'ethereum-mainnet' THEN 'mainnet'
946
- WHEN network = 'polygon-mainnet' THEN 'mainnet'
947
- WHEN network = 'arbitrum-mainnet' THEN 'mainnet'
948
- WHEN network = 'optimism-mainnet' THEN 'mainnet'
949
- WHEN network = 'base-mainnet' THEN 'mainnet'
950
- WHEN network = 'ethereum-sepolia' THEN 'testnet'
951
- WHEN network = 'polygon-amoy' THEN 'testnet'
952
- WHEN network = 'arbitrum-sepolia' THEN 'testnet'
953
- WHEN network = 'optimism-sepolia' THEN 'testnet'
954
- WHEN network = 'base-sepolia' THEN 'testnet'
955
- ELSE 'testnet'
956
- END AS environment,
957
- network AS default_network,
958
- public_key, status, owner_address, owner_verified,
959
- created_at, updated_at, suspended_at, suspension_reason
960
- FROM wallets`);
961
- // Step 4: Drop old wallets table
962
- sqlite.exec('DROP TABLE wallets');
963
- // Step 5: Rename new table
964
- sqlite.exec('ALTER TABLE wallets_new RENAME TO wallets');
965
- // Step 6: Recreate wallets indexes
966
- sqlite.exec('DROP INDEX IF EXISTS idx_wallets_chain_network');
967
- sqlite.exec('CREATE UNIQUE INDEX idx_wallets_public_key ON wallets(public_key)');
968
- sqlite.exec('CREATE INDEX idx_wallets_status ON wallets(status)');
969
- sqlite.exec('CREATE INDEX idx_wallets_chain_environment ON wallets(chain, environment)');
970
- sqlite.exec('CREATE INDEX idx_wallets_owner_address ON wallets(owner_address)');
971
- // Step 7: Recreate sessions (FK reconnection, no schema change)
972
- sqlite.exec(`CREATE TABLE sessions_new (
973
- id TEXT PRIMARY KEY,
974
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
975
- token_hash TEXT NOT NULL,
976
- expires_at INTEGER NOT NULL,
977
- constraints TEXT,
978
- usage_stats TEXT,
979
- revoked_at INTEGER,
980
- renewal_count INTEGER NOT NULL DEFAULT 0,
981
- max_renewals INTEGER NOT NULL DEFAULT 30,
982
- last_renewed_at INTEGER,
983
- absolute_expires_at INTEGER NOT NULL,
984
- created_at INTEGER NOT NULL
985
- )`);
986
- sqlite.exec(`INSERT INTO sessions_new (id, wallet_id, token_hash, expires_at, constraints, usage_stats, revoked_at, renewal_count, max_renewals, last_renewed_at, absolute_expires_at, created_at)
987
- SELECT id, wallet_id, token_hash, expires_at, constraints, usage_stats, revoked_at, renewal_count, max_renewals, last_renewed_at, absolute_expires_at, created_at FROM sessions`);
988
- sqlite.exec('DROP TABLE sessions');
989
- sqlite.exec('ALTER TABLE sessions_new RENAME TO sessions');
990
- sqlite.exec('CREATE INDEX idx_sessions_wallet_id ON sessions(wallet_id)');
991
- sqlite.exec('CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)');
992
- sqlite.exec('CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)');
993
- // Step 8: Recreate transactions (network column with CHECK, FK reconnection)
994
- sqlite.exec(`CREATE TABLE transactions_new (
995
- id TEXT PRIMARY KEY,
996
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
997
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
998
- chain TEXT NOT NULL,
999
- tx_hash TEXT,
1000
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
1001
- amount TEXT,
1002
- to_address TEXT,
1003
- token_mint TEXT,
1004
- contract_address TEXT,
1005
- method_signature TEXT,
1006
- spender_address TEXT,
1007
- approved_amount TEXT,
1008
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
1009
- batch_index INTEGER,
1010
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
1011
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
1012
- queued_at INTEGER,
1013
- executed_at INTEGER,
1014
- created_at INTEGER NOT NULL,
1015
- reserved_amount TEXT,
1016
- error TEXT,
1017
- metadata TEXT,
1018
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)}))
1019
- )`);
1020
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata, network)
1021
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata, network FROM transactions`);
1022
- sqlite.exec('DROP TABLE transactions');
1023
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
1024
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
1025
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
1026
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
1027
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
1028
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
1029
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
1030
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
1031
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
1032
- // Step 9: Recreate policies (FK reconnection, no schema change -- v8 adds network)
1033
- sqlite.exec(`CREATE TABLE policies_new (
1034
- id TEXT PRIMARY KEY,
1035
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
1036
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
1037
- rules TEXT NOT NULL,
1038
- priority INTEGER NOT NULL DEFAULT 0,
1039
- enabled INTEGER NOT NULL DEFAULT 1,
1040
- created_at INTEGER NOT NULL,
1041
- updated_at INTEGER NOT NULL
1042
- )`);
1043
- sqlite.exec(`INSERT INTO policies_new (id, wallet_id, type, rules, priority, enabled, created_at, updated_at)
1044
- SELECT id, wallet_id, type, rules, priority, enabled, created_at, updated_at FROM policies`);
1045
- sqlite.exec('DROP TABLE policies');
1046
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
1047
- sqlite.exec('CREATE INDEX idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
1048
- sqlite.exec('CREATE INDEX idx_policies_type ON policies(type)');
1049
- // Step 10: Recreate audit_log (consistency with v3 pattern)
1050
- sqlite.exec(`CREATE TABLE audit_log_new (
1051
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1052
- timestamp INTEGER NOT NULL,
1053
- event_type TEXT NOT NULL,
1054
- actor TEXT NOT NULL,
1055
- wallet_id TEXT,
1056
- session_id TEXT,
1057
- tx_id TEXT,
1058
- details TEXT NOT NULL,
1059
- severity TEXT NOT NULL DEFAULT 'info' CHECK (severity IN ('info', 'warning', 'critical')),
1060
- ip_address TEXT
1061
- )`);
1062
- sqlite.exec(`INSERT INTO audit_log_new (id, timestamp, event_type, actor, wallet_id, session_id, tx_id, details, severity, ip_address)
1063
- SELECT id, timestamp, event_type, actor, wallet_id, session_id, tx_id, details, severity, ip_address FROM audit_log`);
1064
- sqlite.exec('DROP TABLE audit_log');
1065
- sqlite.exec('ALTER TABLE audit_log_new RENAME TO audit_log');
1066
- sqlite.exec('CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp)');
1067
- sqlite.exec('CREATE INDEX idx_audit_log_event_type ON audit_log(event_type)');
1068
- sqlite.exec('CREATE INDEX idx_audit_log_wallet_id ON audit_log(wallet_id)');
1069
- sqlite.exec('CREATE INDEX idx_audit_log_severity ON audit_log(severity)');
1070
- sqlite.exec('CREATE INDEX idx_audit_log_wallet_timestamp ON audit_log(wallet_id, timestamp)');
1071
- // Step 11: Commit transaction
1072
- sqlite.exec('COMMIT');
1073
- }
1074
- catch (err) {
1075
- sqlite.exec('ROLLBACK');
1076
- throw err;
1077
- }
1078
- // Step 12: Re-enable foreign keys and verify integrity
1079
- sqlite.pragma('foreign_keys = ON');
1080
- const fkErrors = sqlite.pragma('foreign_key_check');
1081
- if (fkErrors.length > 0) {
1082
- throw new Error(`FK integrity violation after v6b: ${JSON.stringify(fkErrors)}`);
1083
- }
1084
- },
1085
- });
1086
- // ---------------------------------------------------------------------------
1087
- // v8: Add network column to policies (12-step recreation)
34
+ export const MIGRATIONS = [
35
+ ...v2to10,
36
+ ...v11to20,
37
+ ...v21to30,
38
+ ...v31to40,
39
+ ...v41to50,
40
+ ...v51to59,
41
+ ];
1088
42
  // ---------------------------------------------------------------------------
1089
- // Adds nullable network column and updates type CHECK to include ALLOWED_NETWORKS.
1090
- // Requires 12-step recreation because SQLite cannot ALTER CHECK constraints.
1091
- // @see docs/71-policy-engine-network-extension-design.md section 6
1092
- MIGRATIONS.push({
1093
- version: 8,
1094
- description: 'Add network column to policies and ALLOWED_NETWORKS type support',
1095
- managesOwnTransaction: true,
1096
- up: (sqlite) => {
1097
- // Step 1: Begin transaction
1098
- sqlite.exec('BEGIN');
1099
- try {
1100
- // Step 2: Create policies_new with network column + updated CHECK
1101
- sqlite.exec(`CREATE TABLE policies_new (
1102
- id TEXT PRIMARY KEY,
1103
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
1104
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
1105
- rules TEXT NOT NULL,
1106
- priority INTEGER NOT NULL DEFAULT 0,
1107
- enabled INTEGER NOT NULL DEFAULT 1,
1108
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
1109
- created_at INTEGER NOT NULL,
1110
- updated_at INTEGER NOT NULL
1111
- )`);
1112
- // Step 3: Copy existing policies with network=NULL
1113
- sqlite.exec(`INSERT INTO policies_new (
1114
- id, wallet_id, type, rules, priority, enabled, network, created_at, updated_at
1115
- )
1116
- SELECT
1117
- id, wallet_id, type, rules, priority, enabled, NULL, created_at, updated_at
1118
- FROM policies`);
1119
- // Step 4: Drop old table
1120
- sqlite.exec('DROP TABLE policies');
1121
- // Step 5: Rename new table
1122
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
1123
- // Step 6: Recreate existing indexes
1124
- sqlite.exec('CREATE INDEX idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
1125
- sqlite.exec('CREATE INDEX idx_policies_type ON policies(type)');
1126
- // Step 7: Create new network index
1127
- sqlite.exec('CREATE INDEX idx_policies_network ON policies(network)');
1128
- // Step 8: Commit
1129
- sqlite.exec('COMMIT');
1130
- }
1131
- catch (err) {
1132
- sqlite.exec('ROLLBACK');
1133
- throw err;
1134
- }
1135
- // Step 9: Re-enable foreign keys and verify integrity
1136
- sqlite.pragma('foreign_keys = ON');
1137
- const fkErrors = sqlite.pragma('foreign_key_check');
1138
- if (fkErrors.length > 0) {
1139
- throw new Error(`FK integrity violation after v8: ${JSON.stringify(fkErrors)}`);
1140
- }
1141
- },
1142
- });
1143
- // ---------------------------------------------------------------------------
1144
- // v9: Add SIGNED status and SIGN type to transactions CHECK constraints
1145
- // ---------------------------------------------------------------------------
1146
- // SSoT arrays TRANSACTION_STATUSES/TRANSACTION_TYPES에 새 값이 추가되었으므로
1147
- // transactions 테이블의 CHECK 제약을 갱신해야 한다. SQLite는 ALTER CHECK 불가 -> 12-step 재생성.
1148
- // 이 마이그레이션은 transactions 테이블만 재생성한다 (다른 테이블에 영향 없음).
1149
- MIGRATIONS.push({
1150
- version: 9,
1151
- description: 'Add SIGNED status and SIGN type to transactions CHECK constraints',
1152
- managesOwnTransaction: true,
1153
- up: (sqlite) => {
1154
- // Step 1: Begin transaction
1155
- sqlite.exec('BEGIN');
1156
- try {
1157
- // Step 2: Create transactions_new with updated CHECK constraints
1158
- sqlite.exec(`CREATE TABLE transactions_new (
1159
- id TEXT PRIMARY KEY,
1160
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
1161
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
1162
- chain TEXT NOT NULL,
1163
- tx_hash TEXT,
1164
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
1165
- amount TEXT,
1166
- to_address TEXT,
1167
- token_mint TEXT,
1168
- contract_address TEXT,
1169
- method_signature TEXT,
1170
- spender_address TEXT,
1171
- approved_amount TEXT,
1172
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
1173
- batch_index INTEGER,
1174
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
1175
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
1176
- queued_at INTEGER,
1177
- executed_at INTEGER,
1178
- created_at INTEGER NOT NULL,
1179
- reserved_amount TEXT,
1180
- error TEXT,
1181
- metadata TEXT,
1182
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)}))
1183
- )`);
1184
- // Step 3: Copy existing data
1185
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata, network)
1186
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata, network FROM transactions`);
1187
- // Step 4: Drop old table
1188
- sqlite.exec('DROP TABLE transactions');
1189
- // Step 5: Rename new table
1190
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
1191
- // Step 6: Recreate all 8 existing indexes
1192
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
1193
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
1194
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
1195
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
1196
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
1197
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
1198
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
1199
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
1200
- // Step 7: Commit
1201
- sqlite.exec('COMMIT');
1202
- }
1203
- catch (err) {
1204
- sqlite.exec('ROLLBACK');
1205
- throw err;
1206
- }
1207
- // Step 8: Re-enable foreign keys and verify integrity
1208
- sqlite.pragma('foreign_keys = ON');
1209
- const fkErrors = sqlite.pragma('foreign_key_check');
1210
- if (fkErrors.length > 0) {
1211
- throw new Error(`FK integrity violation after v9: ${JSON.stringify(fkErrors)}`);
1212
- }
1213
- },
1214
- });
43
+ // Migration runner
1215
44
  // ---------------------------------------------------------------------------
1216
- // v10: Add message column to notification_logs
1217
- // ---------------------------------------------------------------------------
1218
- // Simple ALTER TABLE ADD COLUMN -- no CHECK constraint changes, no table recreation needed.
1219
- MIGRATIONS.push({
1220
- version: 10,
1221
- description: 'Add message column to notification_logs',
1222
- up: (sqlite) => {
1223
- sqlite.exec('ALTER TABLE notification_logs ADD COLUMN message TEXT');
1224
- },
1225
- });
1226
- // ---------------------------------------------------------------------------
1227
- // v11: Create api_keys table for Action Provider API key storage
1228
- // ---------------------------------------------------------------------------
1229
- MIGRATIONS.push({
1230
- version: 11,
1231
- description: 'Add api_keys table for Action Provider API key storage',
1232
- up: (sqlite) => {
1233
- sqlite.exec(`CREATE TABLE IF NOT EXISTS api_keys (
1234
- provider_name TEXT PRIMARY KEY,
1235
- encrypted_key TEXT NOT NULL,
1236
- created_at INTEGER NOT NULL,
1237
- updated_at INTEGER NOT NULL
1238
- )`);
1239
- },
1240
- });
1241
- // ---------------------------------------------------------------------------
1242
- // v12: Add X402_PAYMENT to transactions and X402_ALLOWED_DOMAINS to policies CHECK constraints
1243
- // ---------------------------------------------------------------------------
1244
- // x402 결제 지원을 위해 transactions.type에 X402_PAYMENT,
1245
- // policies.type에 X402_ALLOWED_DOMAINS가 CHECK 제약에 포함되어야 한다.
1246
- // SQLite는 ALTER CHECK 불가 -> transactions와 policies 모두 12-step 재생성.
1247
- MIGRATIONS.push({
1248
- version: 12,
1249
- description: 'Add X402_PAYMENT to transactions and X402_ALLOWED_DOMAINS to policies CHECK constraints',
1250
- managesOwnTransaction: true,
1251
- up: (sqlite) => {
1252
- sqlite.exec('BEGIN');
1253
- try {
1254
- // ── Part 1: transactions 테이블 재생성 ──
1255
- // v9과 동일 DDL. TRANSACTION_TYPES SSoT에 X402_PAYMENT이 이미 포함됨.
1256
- sqlite.exec(`CREATE TABLE transactions_new (
1257
- id TEXT PRIMARY KEY,
1258
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
1259
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
1260
- chain TEXT NOT NULL,
1261
- tx_hash TEXT,
1262
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
1263
- amount TEXT,
1264
- to_address TEXT,
1265
- token_mint TEXT,
1266
- contract_address TEXT,
1267
- method_signature TEXT,
1268
- spender_address TEXT,
1269
- approved_amount TEXT,
1270
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
1271
- batch_index INTEGER,
1272
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
1273
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
1274
- queued_at INTEGER,
1275
- executed_at INTEGER,
1276
- created_at INTEGER NOT NULL,
1277
- reserved_amount TEXT,
1278
- error TEXT,
1279
- metadata TEXT,
1280
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)}))
1281
- )`);
1282
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata, network)
1283
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, error, metadata, network FROM transactions`);
1284
- sqlite.exec('DROP TABLE transactions');
1285
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
1286
- // Recreate all 8 indexes (same as v9)
1287
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
1288
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
1289
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
1290
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
1291
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
1292
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
1293
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
1294
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
1295
- // ── Part 2: policies 테이블 재생성 ──
1296
- // v8과 동일 DDL. POLICY_TYPES SSoT에 X402_ALLOWED_DOMAINS가 이미 포함됨.
1297
- sqlite.exec(`CREATE TABLE policies_new (
1298
- id TEXT PRIMARY KEY,
1299
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
1300
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
1301
- rules TEXT NOT NULL,
1302
- priority INTEGER NOT NULL DEFAULT 0,
1303
- enabled INTEGER NOT NULL DEFAULT 1,
1304
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
1305
- created_at INTEGER NOT NULL,
1306
- updated_at INTEGER NOT NULL
1307
- )`);
1308
- sqlite.exec(`INSERT INTO policies_new (id, wallet_id, type, rules, priority, enabled, network, created_at, updated_at)
1309
- SELECT id, wallet_id, type, rules, priority, enabled, network, created_at, updated_at FROM policies`);
1310
- sqlite.exec('DROP TABLE policies');
1311
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
1312
- // Recreate 3 indexes (same as v8)
1313
- sqlite.exec('CREATE INDEX idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
1314
- sqlite.exec('CREATE INDEX idx_policies_type ON policies(type)');
1315
- sqlite.exec('CREATE INDEX idx_policies_network ON policies(network)');
1316
- sqlite.exec('COMMIT');
1317
- }
1318
- catch (err) {
1319
- sqlite.exec('ROLLBACK');
1320
- throw err;
1321
- }
1322
- // Re-enable foreign keys and verify integrity
1323
- sqlite.pragma('foreign_keys = ON');
1324
- const fkErrors = sqlite.pragma('foreign_key_check');
1325
- if (fkErrors.length > 0) {
1326
- throw new Error(`FK integrity violation after v12: ${JSON.stringify(fkErrors)}`);
1327
- }
1328
- },
1329
- });
1330
- // ---------------------------------------------------------------------------
1331
- // v13: Add amount_usd and reserved_amount_usd columns to transactions
1332
- // ---------------------------------------------------------------------------
1333
- // Simple ALTER TABLE ADD COLUMN -- no CHECK constraint changes, no table recreation needed.
1334
- // These nullable REAL columns store USD-denominated amounts for cumulative spending limit evaluation.
1335
- // amount_usd: confirmed USD amount (persists after CONFIRMED), reserved_amount_usd: cleared on release.
1336
- MIGRATIONS.push({
1337
- version: 13,
1338
- description: 'Add amount_usd and reserved_amount_usd columns to transactions',
1339
- up: (sqlite) => {
1340
- sqlite.exec('ALTER TABLE transactions ADD COLUMN amount_usd REAL');
1341
- sqlite.exec('ALTER TABLE transactions ADD COLUMN reserved_amount_usd REAL');
1342
- },
1343
- });
1344
- // ---------------------------------------------------------------------------
1345
- // v14: Migrate kill_switch_state values: NORMAL->ACTIVE, ACTIVATED->SUSPENDED
1346
- // ---------------------------------------------------------------------------
1347
- // Kill Switch 3-state machine migration.
1348
- // Old 2-state values (NORMAL, ACTIVATED, RECOVERING) are converted to
1349
- // new 3-state values (ACTIVE, SUSPENDED, LOCKED).
1350
- // Simple UPDATE on key_value_store -- no schema changes, no table recreation.
1351
- MIGRATIONS.push({
1352
- version: 14,
1353
- description: 'Migrate kill_switch_state values: NORMAL->ACTIVE, ACTIVATED->SUSPENDED',
1354
- up: (sqlite) => {
1355
- const now = Math.floor(Date.now() / 1000);
1356
- // NORMAL -> ACTIVE
1357
- sqlite
1358
- .prepare("UPDATE key_value_store SET value = 'ACTIVE', updated_at = ? WHERE key = 'kill_switch_state' AND value = 'NORMAL'")
1359
- .run(now);
1360
- // ACTIVATED -> SUSPENDED
1361
- sqlite
1362
- .prepare("UPDATE key_value_store SET value = 'SUSPENDED', updated_at = ? WHERE key = 'kill_switch_state' AND value = 'ACTIVATED'")
1363
- .run(now);
1364
- // RECOVERING -> ACTIVE (v0.10 design removed RECOVERING state)
1365
- sqlite
1366
- .prepare("UPDATE key_value_store SET value = 'ACTIVE', updated_at = ? WHERE key = 'kill_switch_state' AND value = 'RECOVERING'")
1367
- .run(now);
1368
- },
1369
- });
1370
- // ---------------------------------------------------------------------------
1371
- // v15: Create telegram_users table for Telegram Bot user management
1372
- // ---------------------------------------------------------------------------
1373
- MIGRATIONS.push({
1374
- version: 15,
1375
- description: 'Create telegram_users table for Telegram Bot user management',
1376
- up: (sqlite) => {
1377
- sqlite.exec(`CREATE TABLE IF NOT EXISTS telegram_users (
1378
- chat_id INTEGER PRIMARY KEY,
1379
- username TEXT,
1380
- role TEXT NOT NULL DEFAULT 'PENDING' CHECK (role IN ('PENDING', 'ADMIN', 'READONLY')),
1381
- registered_at INTEGER NOT NULL,
1382
- approved_at INTEGER
1383
- )`);
1384
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_telegram_users_role ON telegram_users(role)');
1385
- },
1386
- });
1387
- // ---------------------------------------------------------------------------
1388
- // v16: Add WC infra: wc_sessions table, wc_store table, pending_approvals.approval_channel
1389
- // ---------------------------------------------------------------------------
1390
- MIGRATIONS.push({
1391
- version: 16,
1392
- description: 'Add WC infra: wc_sessions table, wc_store table, pending_approvals.approval_channel',
1393
- up: (sqlite) => {
1394
- // 1. pending_approvals.approval_channel 추가
1395
- sqlite.exec("ALTER TABLE pending_approvals ADD COLUMN approval_channel TEXT DEFAULT 'rest_api'");
1396
- // 2. wc_sessions 테이블 생성
1397
- sqlite.exec(`CREATE TABLE IF NOT EXISTS wc_sessions (
1398
- wallet_id TEXT PRIMARY KEY REFERENCES wallets(id) ON DELETE CASCADE,
1399
- topic TEXT NOT NULL UNIQUE,
1400
- peer_meta TEXT,
1401
- chain_id TEXT NOT NULL,
1402
- owner_address TEXT NOT NULL,
1403
- namespaces TEXT,
1404
- expiry INTEGER NOT NULL,
1405
- created_at INTEGER NOT NULL
1406
- )`);
1407
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_wc_sessions_topic ON wc_sessions(topic)');
1408
- // 3. wc_store 테이블 생성 (IKeyValueStorage용)
1409
- sqlite.exec(`CREATE TABLE IF NOT EXISTS wc_store (
1410
- key TEXT PRIMARY KEY,
1411
- value TEXT NOT NULL
1412
- )`);
1413
- },
1414
- });
1415
- // ---------------------------------------------------------------------------
1416
- // Migration v17: Add source column to sessions table
1417
- // ---------------------------------------------------------------------------
1418
- MIGRATIONS.push({
1419
- version: 17,
1420
- description: 'Add source column to sessions table (api/mcp)',
1421
- up: (sqlite) => {
1422
- sqlite.exec("ALTER TABLE sessions ADD COLUMN source TEXT NOT NULL DEFAULT 'api'");
1423
- },
1424
- });
1425
- // ---------------------------------------------------------------------------
1426
- // Migration v18: Add owner_approval_method column to wallets table
1427
- // ---------------------------------------------------------------------------
1428
- // Simple ALTER TABLE ADD COLUMN -- nullable, no CHECK via ALTER (SQLite limitation).
1429
- // CHECK is enforced at application level via Zod ApprovalMethodSchema.
1430
- // Fresh DB DDL includes CHECK constraint directly.
1431
- MIGRATIONS.push({
1432
- version: 18,
1433
- description: 'Add owner_approval_method column to wallets table',
1434
- up: (sqlite) => {
1435
- sqlite.exec('ALTER TABLE wallets ADD COLUMN owner_approval_method TEXT');
1436
- },
1437
- });
1438
- // ---------------------------------------------------------------------------
1439
- // Migration v19: Create session_wallets junction table, migrate sessions.wallet_id, drop wallet_id column
1440
- // ---------------------------------------------------------------------------
1441
- // 12-step table recreation for sessions (wallet_id column removal).
1442
- // Creates session_wallets junction table, migrates existing 1:1 data as is_default=1,
1443
- // then recreates sessions without wallet_id and reconnects FK-dependent transactions.
1444
- // wallet_id IS NULL sessions are skipped (no crash).
1445
- MIGRATIONS.push({
1446
- version: 19,
1447
- description: 'Create session_wallets junction table, migrate sessions.wallet_id, drop wallet_id column',
1448
- managesOwnTransaction: true,
1449
- up: (sqlite) => {
1450
- sqlite.exec('BEGIN');
1451
- try {
1452
- // Step 1: Create session_wallets table
1453
- sqlite.exec(`CREATE TABLE session_wallets (
1454
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
1455
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
1456
- is_default INTEGER NOT NULL DEFAULT 0,
1457
- created_at INTEGER NOT NULL,
1458
- PRIMARY KEY (session_id, wallet_id)
1459
- )`);
1460
- sqlite.exec('CREATE INDEX idx_session_wallets_session ON session_wallets(session_id)');
1461
- sqlite.exec('CREATE INDEX idx_session_wallets_wallet ON session_wallets(wallet_id)');
1462
- // Step 2: Migrate existing sessions.wallet_id -> session_wallets (is_default = 1)
1463
- // wallet_id가 NULL인 비정상 세션은 스킵 (WHERE wallet_id IS NOT NULL)
1464
- sqlite.exec(`INSERT INTO session_wallets (session_id, wallet_id, is_default, created_at)
1465
- SELECT id, wallet_id, 1, CAST(strftime('%s', 'now') AS INTEGER)
1466
- FROM sessions
1467
- WHERE wallet_id IS NOT NULL`);
1468
- // Step 3: Recreate sessions table without wallet_id column (12-step)
1469
- sqlite.exec(`CREATE TABLE sessions_new (
1470
- id TEXT PRIMARY KEY,
1471
- token_hash TEXT NOT NULL,
1472
- expires_at INTEGER NOT NULL,
1473
- constraints TEXT,
1474
- usage_stats TEXT,
1475
- revoked_at INTEGER,
1476
- renewal_count INTEGER NOT NULL DEFAULT 0,
1477
- max_renewals INTEGER NOT NULL DEFAULT 30,
1478
- last_renewed_at INTEGER,
1479
- absolute_expires_at INTEGER NOT NULL,
1480
- created_at INTEGER NOT NULL,
1481
- source TEXT NOT NULL DEFAULT 'api'
1482
- )`);
1483
- // Step 4: Copy data (excluding wallet_id)
1484
- sqlite.exec(`INSERT INTO sessions_new (id, token_hash, expires_at, constraints, usage_stats, revoked_at, renewal_count, max_renewals, last_renewed_at, absolute_expires_at, created_at, source)
1485
- SELECT id, token_hash, expires_at, constraints, usage_stats, revoked_at, renewal_count, max_renewals, last_renewed_at, absolute_expires_at, created_at, source
1486
- FROM sessions`);
1487
- // Step 5: Drop old sessions table
1488
- sqlite.exec('DROP TABLE sessions');
1489
- // Step 6: Rename new table
1490
- sqlite.exec('ALTER TABLE sessions_new RENAME TO sessions');
1491
- // Step 7: Recreate sessions indexes (without wallet_id index)
1492
- sqlite.exec('CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)');
1493
- sqlite.exec('CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)');
1494
- // Step 8: Recreate transactions table to fix FK reference to sessions
1495
- // (sessions was dropped+renamed, need FK reconnection)
1496
- // transactions references sessions(id) ON DELETE SET NULL
1497
- sqlite.exec(`CREATE TABLE transactions_new (
1498
- id TEXT PRIMARY KEY,
1499
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
1500
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
1501
- chain TEXT NOT NULL,
1502
- tx_hash TEXT,
1503
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
1504
- amount TEXT,
1505
- to_address TEXT,
1506
- token_mint TEXT,
1507
- contract_address TEXT,
1508
- method_signature TEXT,
1509
- spender_address TEXT,
1510
- approved_amount TEXT,
1511
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
1512
- batch_index INTEGER,
1513
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
1514
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
1515
- queued_at INTEGER,
1516
- executed_at INTEGER,
1517
- created_at INTEGER NOT NULL,
1518
- reserved_amount TEXT,
1519
- amount_usd REAL,
1520
- reserved_amount_usd REAL,
1521
- error TEXT,
1522
- metadata TEXT,
1523
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)}))
1524
- )`);
1525
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd, error, metadata, network)
1526
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd, error, metadata, network FROM transactions`);
1527
- sqlite.exec('DROP TABLE transactions');
1528
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
1529
- // Recreate transactions indexes
1530
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
1531
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
1532
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
1533
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
1534
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
1535
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
1536
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
1537
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
1538
- sqlite.exec('COMMIT');
1539
- }
1540
- catch (err) {
1541
- sqlite.exec('ROLLBACK');
1542
- throw err;
1543
- }
1544
- // Re-enable foreign keys and verify integrity
1545
- sqlite.pragma('foreign_keys = ON');
1546
- const fkErrors = sqlite.pragma('foreign_key_check');
1547
- if (fkErrors.length > 0) {
1548
- throw new Error(`FK integrity violation after v19: ${JSON.stringify(fkErrors)}`);
1549
- }
1550
- },
1551
- });
1552
- // ---------------------------------------------------------------------------
1553
- // Migration v20: Add token_issued_count to sessions (v26.5)
1554
- // ---------------------------------------------------------------------------
1555
- MIGRATIONS.push({
1556
- version: 20,
1557
- description: 'Add token_issued_count column to sessions table',
1558
- up: (sqlite) => {
1559
- sqlite.exec('ALTER TABLE sessions ADD COLUMN token_issued_count INTEGER NOT NULL DEFAULT 1');
1560
- },
1561
- });
1562
- // ---------------------------------------------------------------------------
1563
- // Migration v21: Add incoming transaction monitoring tables and wallet opt-in column
1564
- // ---------------------------------------------------------------------------
1565
- // Creates incoming_transactions (13 columns + UNIQUE constraint), incoming_tx_cursors (6 columns),
1566
- // and adds wallets.monitor_incoming opt-in column.
1567
- // Simple ALTER TABLE + CREATE TABLE -- no CHECK constraint changes on existing tables,
1568
- // no table recreation needed.
1569
- MIGRATIONS.push({
1570
- version: 21,
1571
- description: 'Add incoming transaction monitoring tables and wallet opt-in column',
1572
- up: (sqlite) => {
1573
- // 1. wallets.monitor_incoming opt-in column
1574
- sqlite.exec('ALTER TABLE wallets ADD COLUMN monitor_incoming INTEGER NOT NULL DEFAULT 0');
1575
- // 2. incoming_transactions table (13 columns + UNIQUE constraint)
1576
- sqlite.exec(`CREATE TABLE IF NOT EXISTS incoming_transactions (
1577
- id TEXT PRIMARY KEY,
1578
- tx_hash TEXT NOT NULL,
1579
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
1580
- from_address TEXT NOT NULL,
1581
- amount TEXT NOT NULL,
1582
- token_address TEXT,
1583
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
1584
- network TEXT NOT NULL,
1585
- status TEXT NOT NULL DEFAULT 'DETECTED' CHECK (status IN (${inList(INCOMING_TX_STATUSES)})),
1586
- block_number INTEGER,
1587
- detected_at INTEGER NOT NULL,
1588
- confirmed_at INTEGER,
1589
- is_suspicious INTEGER NOT NULL DEFAULT 0,
1590
- UNIQUE(tx_hash, wallet_id)
1591
- )`);
1592
- // 3. incoming_tx_cursors table (6 columns)
1593
- sqlite.exec(`CREATE TABLE IF NOT EXISTS incoming_tx_cursors (
1594
- wallet_id TEXT PRIMARY KEY REFERENCES wallets(id) ON DELETE CASCADE,
1595
- chain TEXT NOT NULL,
1596
- network TEXT NOT NULL,
1597
- last_signature TEXT,
1598
- last_block_number INTEGER,
1599
- updated_at INTEGER NOT NULL
1600
- )`);
1601
- // 4. Indexes on incoming_transactions
1602
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_incoming_tx_wallet_detected ON incoming_transactions(wallet_id, detected_at DESC)');
1603
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_incoming_tx_detected_at ON incoming_transactions(detected_at)');
1604
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_incoming_tx_chain_network ON incoming_transactions(chain, network)');
1605
- sqlite.exec("CREATE INDEX IF NOT EXISTS idx_incoming_tx_status ON incoming_transactions(status) WHERE status = 'DETECTED'");
1606
- },
1607
- });
1608
- // ---------------------------------------------------------------------------
1609
- // Migration v22: Add asset_id column to token_registry with CAIP-19 backfill
1610
- // ---------------------------------------------------------------------------
1611
- // Simple ALTER TABLE ADD COLUMN + application-level backfill.
1612
- // Generates CAIP-19 asset_id for each known-network token using tokenAssetId().
1613
- // Unknown networks are skipped (asset_id remains NULL).
1614
- MIGRATIONS.push({
1615
- version: 22,
1616
- description: 'Add asset_id column to token_registry with CAIP-19 backfill',
1617
- managesOwnTransaction: false,
1618
- up: (sqlite) => {
1619
- // Step 1: Add nullable column (skip if already exists from fresh DDL)
1620
- const columns = sqlite
1621
- .prepare("PRAGMA table_info('token_registry')")
1622
- .all();
1623
- const hasAssetId = columns.some((c) => c.name === 'asset_id');
1624
- if (!hasAssetId) {
1625
- sqlite.exec('ALTER TABLE token_registry ADD COLUMN asset_id TEXT');
1626
- }
1627
- // Step 2: Application-level backfill using tokenAssetId()
1628
- const rows = sqlite
1629
- .prepare('SELECT id, network, address FROM token_registry')
1630
- .all();
1631
- const updateStmt = sqlite.prepare('UPDATE token_registry SET asset_id = ? WHERE id = ?');
1632
- for (const row of rows) {
1633
- // Normalize legacy Solana network names (mainnet -> solana-mainnet, etc.)
1634
- const normalizedNetwork = LEGACY_NETWORK_NORMALIZE[row.network] ?? row.network;
1635
- // Guard: only backfill for known networks (Pitfall 4)
1636
- if (!(normalizedNetwork in NETWORK_TO_CAIP2))
1637
- continue;
1638
- try {
1639
- const assetId = tokenAssetId(normalizedNetwork, row.address);
1640
- updateStmt.run(assetId, row.id);
1641
- }
1642
- catch {
1643
- // Skip on error -- rows with unknown networks get asset_id = NULL
1644
- }
1645
- }
1646
- },
1647
- });
1648
- // ---------------------------------------------------------------------------
1649
- // Migration v23: DeFi async tracking — bridge_status + bridge_metadata + GAS_WAITING
1650
- // ---------------------------------------------------------------------------
1651
- // 12-step table recreation required because:
1652
- // 1. TRANSACTION_STATUSES now includes GAS_WAITING (11 entries), must update status CHECK
1653
- // 2. New bridge_status column with 6-value CHECK constraint
1654
- // 3. New bridge_metadata TEXT column
1655
- // 4. 2 new partial indexes (idx_transactions_bridge_status, idx_transactions_gas_waiting)
1656
- // @see internal/objectives/m28-00-defi-basic-protocol-design.md (DEFI-04 ASNC-01)
1657
- MIGRATIONS.push({
1658
- version: 23,
1659
- description: 'DeFi async tracking: bridge_status + bridge_metadata + GAS_WAITING state',
1660
- managesOwnTransaction: true,
1661
- up: (sqlite) => {
1662
- sqlite.exec('BEGIN');
1663
- try {
1664
- // Step 1: Create transactions_new with updated CHECK + new columns
1665
- sqlite.exec(`CREATE TABLE transactions_new (
1666
- id TEXT PRIMARY KEY,
1667
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
1668
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
1669
- chain TEXT NOT NULL,
1670
- tx_hash TEXT,
1671
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
1672
- amount TEXT,
1673
- to_address TEXT,
1674
- token_mint TEXT,
1675
- contract_address TEXT,
1676
- method_signature TEXT,
1677
- spender_address TEXT,
1678
- approved_amount TEXT,
1679
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
1680
- batch_index INTEGER,
1681
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
1682
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
1683
- queued_at INTEGER,
1684
- executed_at INTEGER,
1685
- created_at INTEGER NOT NULL,
1686
- reserved_amount TEXT,
1687
- amount_usd REAL,
1688
- reserved_amount_usd REAL,
1689
- error TEXT,
1690
- metadata TEXT,
1691
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
1692
- bridge_status TEXT CHECK (bridge_status IS NULL OR bridge_status IN ('PENDING', 'COMPLETED', 'FAILED', 'BRIDGE_MONITORING', 'TIMEOUT', 'REFUNDED', 'PARTIALLY_FILLED', 'FILLED', 'CANCELED', 'SETTLED', 'EXPIRED')),
1693
- bridge_metadata TEXT
1694
- )`);
1695
- // Step 2: Copy existing data (bridge_status and bridge_metadata default to NULL)
1696
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd, error, metadata, network)
1697
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd, error, metadata, network FROM transactions`);
1698
- // Step 3: Drop old table
1699
- sqlite.exec('DROP TABLE transactions');
1700
- // Step 4: Rename new table
1701
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
1702
- // Step 5: Recreate all 8 existing indexes
1703
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
1704
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
1705
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
1706
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
1707
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
1708
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
1709
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
1710
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
1711
- // Step 6: Create 2 new partial indexes
1712
- sqlite.exec('CREATE INDEX idx_transactions_bridge_status ON transactions(bridge_status) WHERE bridge_status IS NOT NULL');
1713
- sqlite.exec("CREATE INDEX idx_transactions_gas_waiting ON transactions(status) WHERE status = 'GAS_WAITING'");
1714
- // Step 7: Commit
1715
- sqlite.exec('COMMIT');
1716
- }
1717
- catch (err) {
1718
- sqlite.exec('ROLLBACK');
1719
- throw err;
1720
- }
1721
- // Step 8: Re-enable foreign keys and verify integrity
1722
- sqlite.pragma('foreign_keys = ON');
1723
- const fkErrors = sqlite.pragma('foreign_key_check');
1724
- if (fkErrors.length > 0) {
1725
- throw new Error(`FK integrity violation after v23: ${JSON.stringify(fkErrors)}`);
1726
- }
1727
- },
1728
- });
1729
- // ---------------------------------------------------------------------------
1730
- // Migration v24: Add wallet_type column to wallets table for preset auto-setup
1731
- // ---------------------------------------------------------------------------
1732
- // Simple ALTER TABLE ADD COLUMN -- nullable TEXT, no CHECK constraint (validated at app level via Zod).
1733
- // Supports wallet preset auto-setup (v28.8): stores the preset type identifier.
1734
- MIGRATIONS.push({
1735
- version: 24,
1736
- description: 'Add wallet_type column to wallets table for preset auto-setup',
1737
- up: (sqlite) => {
1738
- sqlite.exec('ALTER TABLE wallets ADD COLUMN wallet_type TEXT');
1739
- },
1740
- });
1741
- // ---------------------------------------------------------------------------
1742
- // v29.2 Migration 25: Add defi_positions table for DeFi position tracking
1743
- // ---------------------------------------------------------------------------
1744
- MIGRATIONS.push({
1745
- version: 25,
1746
- description: 'Add defi_positions table for DeFi position tracking',
1747
- up: (sqlite) => {
1748
- sqlite.exec(`
1749
- CREATE TABLE IF NOT EXISTS defi_positions (
1750
- id TEXT PRIMARY KEY,
1751
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
1752
- category TEXT NOT NULL CHECK(category IN (${inList(POSITION_CATEGORIES)})),
1753
- provider TEXT NOT NULL,
1754
- chain TEXT NOT NULL CHECK(chain IN (${inList(CHAIN_TYPES)})),
1755
- network TEXT CHECK(network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
1756
- asset_id TEXT,
1757
- amount TEXT NOT NULL,
1758
- amount_usd REAL,
1759
- metadata TEXT,
1760
- status TEXT NOT NULL DEFAULT 'ACTIVE' CHECK(status IN (${inList(POSITION_STATUSES)})),
1761
- opened_at INTEGER NOT NULL,
1762
- closed_at INTEGER,
1763
- last_synced_at INTEGER NOT NULL,
1764
- created_at INTEGER NOT NULL,
1765
- updated_at INTEGER NOT NULL
1766
- )
1767
- `);
1768
- // Indexes for the new table
1769
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_category ON defi_positions(wallet_id, category)');
1770
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_provider ON defi_positions(wallet_id, provider)');
1771
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_status ON defi_positions(status)');
1772
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_defi_positions_unique ON defi_positions(wallet_id, provider, asset_id, category)');
1773
- },
1774
- });
1775
- // ---------------------------------------------------------------------------
1776
- // v26: Add LENDING_LTV_LIMIT and LENDING_ASSET_WHITELIST to policies table CHECK constraint
1777
- // ---------------------------------------------------------------------------
1778
- // POLICY_TYPES SSoT array now includes LENDING_LTV_LIMIT and LENDING_ASSET_WHITELIST (14 total).
1779
- // SQLite cannot ALTER CHECK constraints, so we recreate the policies table (12-step pattern).
1780
- MIGRATIONS.push({
1781
- version: 26,
1782
- description: 'Add lending policy types to policies table CHECK constraint',
1783
- managesOwnTransaction: true,
1784
- up: (sqlite) => {
1785
- // Step 1: Begin transaction (foreign_keys already OFF via runner)
1786
- sqlite.exec('BEGIN');
1787
- try {
1788
- // Step 2: Create policies_new with updated CHECK (uses SSoT POLICY_TYPES array)
1789
- sqlite.exec(`CREATE TABLE policies_new (
1790
- id TEXT PRIMARY KEY,
1791
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
1792
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
1793
- rules TEXT NOT NULL,
1794
- priority INTEGER NOT NULL DEFAULT 0,
1795
- enabled INTEGER NOT NULL DEFAULT 1,
1796
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES_WITH_LEGACY)})),
1797
- created_at INTEGER NOT NULL,
1798
- updated_at INTEGER NOT NULL
1799
- )`);
1800
- // Step 3: Copy existing policies
1801
- sqlite.exec('INSERT INTO policies_new SELECT * FROM policies');
1802
- // Step 4: Drop old table
1803
- sqlite.exec('DROP TABLE policies');
1804
- // Step 5: Rename new table
1805
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
1806
- // Step 6: Recreate indexes
1807
- sqlite.exec('CREATE INDEX idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
1808
- sqlite.exec('CREATE INDEX idx_policies_type ON policies(type)');
1809
- sqlite.exec('CREATE INDEX idx_policies_network ON policies(network)');
1810
- // Step 7: Commit
1811
- sqlite.exec('COMMIT');
1812
- }
1813
- catch (err) {
1814
- sqlite.exec('ROLLBACK');
1815
- throw err;
1816
- }
1817
- // Step 8: Re-enable foreign keys and verify integrity
1818
- sqlite.pragma('foreign_keys = ON');
1819
- const fkErrors = sqlite.pragma('foreign_key_check');
1820
- if (fkErrors.length > 0) {
1821
- throw new Error(`FK integrity violation after v26: ${JSON.stringify(fkErrors)}`);
1822
- }
1823
- },
1824
- });
1825
- // ---------------------------------------------------------------------------
1826
- // Migration v27: Remove is_default from session_wallets, default_network from wallets
1827
- // v29.3: Default wallet/default network concept removed
1828
- // ---------------------------------------------------------------------------
1829
- MIGRATIONS.push({
1830
- version: 27,
1831
- description: 'Remove is_default from session_wallets and default_network from wallets (v29.3)',
1832
- managesOwnTransaction: true,
1833
- up: (sqlite) => {
1834
- sqlite.exec('BEGIN');
1835
- try {
1836
- // ── Part 1: Remove is_default from session_wallets ──
1837
- // Step 2: Create session_wallets_new without is_default
1838
- sqlite.exec(`CREATE TABLE session_wallets_new (
1839
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
1840
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
1841
- created_at INTEGER NOT NULL,
1842
- PRIMARY KEY (session_id, wallet_id)
1843
- )`);
1844
- // Step 3: Copy data (is_default column discarded, no data loss)
1845
- sqlite.exec('INSERT INTO session_wallets_new (session_id, wallet_id, created_at) SELECT session_id, wallet_id, created_at FROM session_wallets');
1846
- // Step 4: Drop old table
1847
- sqlite.exec('DROP TABLE session_wallets');
1848
- // Step 5: Rename
1849
- sqlite.exec('ALTER TABLE session_wallets_new RENAME TO session_wallets');
1850
- // Step 6: Recreate indexes
1851
- sqlite.exec('CREATE INDEX idx_session_wallets_session ON session_wallets(session_id)');
1852
- sqlite.exec('CREATE INDEX idx_session_wallets_wallet ON session_wallets(wallet_id)');
1853
- // ── Part 2: Remove default_network from wallets ──
1854
- // Step 7: Create wallets_new without default_network
1855
- sqlite.exec(`CREATE TABLE wallets_new (
1856
- id TEXT PRIMARY KEY,
1857
- name TEXT NOT NULL,
1858
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
1859
- environment TEXT NOT NULL CHECK (environment IN (${inList(ENVIRONMENT_TYPES)})),
1860
- public_key TEXT NOT NULL,
1861
- status TEXT NOT NULL DEFAULT 'CREATING' CHECK (status IN (${inList(WALLET_STATUSES)})),
1862
- owner_address TEXT,
1863
- owner_verified INTEGER NOT NULL DEFAULT 0 CHECK (owner_verified IN (0, 1)),
1864
- created_at INTEGER NOT NULL,
1865
- updated_at INTEGER NOT NULL,
1866
- suspended_at INTEGER,
1867
- suspension_reason TEXT,
1868
- monitor_incoming INTEGER NOT NULL DEFAULT 0,
1869
- owner_approval_method TEXT CHECK (owner_approval_method IS NULL OR owner_approval_method IN ('sdk_ntfy', 'sdk_telegram', 'walletconnect', 'telegram_bot', 'rest')),
1870
- wallet_type TEXT
1871
- )`);
1872
- // Step 8: Copy data (default_network intentionally excluded)
1873
- sqlite.exec('INSERT INTO wallets_new (id, name, chain, environment, public_key, status, owner_address, owner_verified, created_at, updated_at, suspended_at, suspension_reason, monitor_incoming, owner_approval_method, wallet_type) SELECT id, name, chain, environment, public_key, status, owner_address, owner_verified, created_at, updated_at, suspended_at, suspension_reason, monitor_incoming, owner_approval_method, wallet_type FROM wallets');
1874
- // Step 9: Drop old table
1875
- sqlite.exec('DROP TABLE wallets');
1876
- // Step 10: Rename
1877
- sqlite.exec('ALTER TABLE wallets_new RENAME TO wallets');
1878
- // Step 11: Recreate indexes
1879
- sqlite.exec('CREATE UNIQUE INDEX idx_wallets_public_key ON wallets(public_key)');
1880
- sqlite.exec('CREATE INDEX idx_wallets_status ON wallets(status)');
1881
- sqlite.exec('CREATE INDEX idx_wallets_chain_environment ON wallets(chain, environment)');
1882
- sqlite.exec('CREATE INDEX idx_wallets_owner_address ON wallets(owner_address)');
1883
- // Step 12: Commit
1884
- sqlite.exec('COMMIT');
1885
- }
1886
- catch (err) {
1887
- sqlite.exec('ROLLBACK');
1888
- throw err;
1889
- }
1890
- // Re-enable foreign keys and verify integrity
1891
- sqlite.pragma('foreign_keys = ON');
1892
- const fkErrors = sqlite.pragma('foreign_key_check');
1893
- if (fkErrors.length > 0) {
1894
- throw new Error(`FK integrity violation after v27: ${JSON.stringify(fkErrors)}`);
1895
- }
1896
- },
1897
- });
1898
- // ---------------------------------------------------------------------------
1899
- // v28: Migrate api_keys to settings table and drop api_keys (v29.5 #214)
1900
- // ---------------------------------------------------------------------------
1901
- MIGRATIONS.push({
1902
- version: 28,
1903
- description: 'Migrate api_keys to settings table and drop api_keys (v29.5 #214)',
1904
- managesOwnTransaction: true,
1905
- up: (sqlite) => {
1906
- sqlite.exec('BEGIN');
1907
- try {
1908
- // 1. Check if api_keys table exists (may not exist on very old DBs that skipped v11)
1909
- const tableExists = sqlite
1910
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='api_keys'")
1911
- .get();
1912
- if (tableExists) {
1913
- // 2. Read all api_keys rows
1914
- const rows = sqlite
1915
- .prepare('SELECT provider_name, encrypted_key, updated_at FROM api_keys')
1916
- .all();
1917
- // 3. For each row, insert into settings with key = 'actions.{provider_name}_api_key'
1918
- // Only insert if the setting doesn't already exist (avoid overwriting manual settings)
1919
- // encrypted_key values use the SAME encryption format as settings.value
1920
- // (both use encryptSettingValue from settings-crypto.ts), so copy directly.
1921
- const insertStmt = sqlite.prepare(`INSERT OR IGNORE INTO settings (key, value, encrypted, category, updated_at)
1922
- VALUES (?, ?, 1, 'actions', ?)`);
1923
- for (const row of rows) {
1924
- const settingKey = `actions.${row.provider_name}_api_key`;
1925
- insertStmt.run(settingKey, row.encrypted_key, row.updated_at);
1926
- }
1927
- // 4. Drop api_keys table
1928
- sqlite.exec('DROP TABLE IF EXISTS api_keys');
1929
- }
1930
- sqlite.exec('COMMIT');
1931
- }
1932
- catch (err) {
1933
- sqlite.exec('ROLLBACK');
1934
- throw err;
1935
- }
1936
- },
1937
- });
1938
- // ---------------------------------------------------------------------------
1939
- // v29: Rename Solana network IDs to solana-{network} format (v29.5 #211)
1940
- // ---------------------------------------------------------------------------
1941
- // Converts mainnet->solana-mainnet, devnet->solana-devnet, testnet->solana-testnet
1942
- // in all tables with Solana network references.
1943
- // Tables with CHECK constraints (transactions, policies, defi_positions) require
1944
- // 12-step table recreation. Tables without CHECK (incoming_transactions,
1945
- // incoming_tx_cursors, token_registry) use simple UPDATE.
1946
- MIGRATIONS.push({
1947
- version: 29,
1948
- description: 'Rename Solana network IDs to solana-{network} format (v29.5 #211)',
1949
- managesOwnTransaction: true,
1950
- up: (sqlite) => {
1951
- sqlite.exec('BEGIN');
1952
- try {
1953
- // Helper: Solana network CASE WHEN clause for SELECT
1954
- const solanaCase = (col) => `CASE WHEN chain = 'solana' AND ${col} = 'mainnet' THEN 'solana-mainnet'` +
1955
- ` WHEN chain = 'solana' AND ${col} = 'devnet' THEN 'solana-devnet'` +
1956
- ` WHEN chain = 'solana' AND ${col} = 'testnet' THEN 'solana-testnet'` +
1957
- ` ELSE ${col} END`;
1958
- // ── 1. transactions: 12-step recreation (has CHECK on network) ──
1959
- sqlite.exec(`CREATE TABLE transactions_new (
1960
- id TEXT PRIMARY KEY,
1961
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
1962
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
1963
- chain TEXT NOT NULL,
1964
- tx_hash TEXT,
1965
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
1966
- amount TEXT,
1967
- to_address TEXT,
1968
- token_mint TEXT,
1969
- contract_address TEXT,
1970
- method_signature TEXT,
1971
- spender_address TEXT,
1972
- approved_amount TEXT,
1973
- parent_id TEXT REFERENCES transactions(id) ON DELETE CASCADE,
1974
- batch_index INTEGER,
1975
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
1976
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
1977
- queued_at INTEGER,
1978
- executed_at INTEGER,
1979
- created_at INTEGER NOT NULL,
1980
- reserved_amount TEXT,
1981
- amount_usd REAL,
1982
- reserved_amount_usd REAL,
1983
- error TEXT,
1984
- metadata TEXT,
1985
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
1986
- bridge_status TEXT CHECK (bridge_status IS NULL OR bridge_status IN ('PENDING', 'COMPLETED', 'FAILED', 'BRIDGE_MONITORING', 'TIMEOUT', 'REFUNDED', 'PARTIALLY_FILLED', 'FILLED', 'CANCELED', 'SETTLED', 'EXPIRED')),
1987
- bridge_metadata TEXT
1988
- )`);
1989
- sqlite.exec(`INSERT INTO transactions_new
1990
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address,
1991
- token_mint, contract_address, method_signature, spender_address,
1992
- approved_amount, parent_id, batch_index, status, tier, queued_at,
1993
- executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd,
1994
- error, metadata, ${solanaCase('network')}, bridge_status, bridge_metadata
1995
- FROM transactions`);
1996
- sqlite.exec('DROP TABLE transactions');
1997
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
1998
- // Recreate transactions indexes
1999
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_wallet_status ON transactions(wallet_id, status)');
2000
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_session_id ON transactions(session_id)');
2001
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_tx_hash ON transactions(tx_hash)');
2002
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_queued_at ON transactions(queued_at)');
2003
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_created_at ON transactions(created_at)');
2004
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(type)');
2005
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_contract_address ON transactions(contract_address)');
2006
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_parent_id ON transactions(parent_id)');
2007
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_bridge_status ON transactions(bridge_status) WHERE bridge_status IS NOT NULL');
2008
- sqlite.exec("CREATE INDEX IF NOT EXISTS idx_transactions_gas_waiting ON transactions(status) WHERE status = 'GAS_WAITING'");
2009
- // ── 2. policies: 12-step recreation (has CHECK on network) ──
2010
- sqlite.exec(`CREATE TABLE policies_new (
2011
- id TEXT PRIMARY KEY,
2012
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
2013
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
2014
- rules TEXT NOT NULL,
2015
- priority INTEGER NOT NULL DEFAULT 0,
2016
- enabled INTEGER NOT NULL DEFAULT 1,
2017
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
2018
- created_at INTEGER NOT NULL,
2019
- updated_at INTEGER NOT NULL
2020
- )`);
2021
- // policies doesn't have a chain column, so we check if network is one of the bare Solana names
2022
- sqlite.exec(`INSERT INTO policies_new
2023
- SELECT id, wallet_id, type, rules, priority, enabled,
2024
- CASE WHEN network = 'mainnet' THEN 'solana-mainnet'
2025
- WHEN network = 'devnet' THEN 'solana-devnet'
2026
- WHEN network = 'testnet' THEN 'solana-testnet'
2027
- ELSE network END,
2028
- created_at, updated_at
2029
- FROM policies`);
2030
- sqlite.exec('DROP TABLE policies');
2031
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
2032
- // Recreate policies indexes
2033
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
2034
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_policies_type ON policies(type)');
2035
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_policies_network ON policies(network)');
2036
- // ── 3. defi_positions: 12-step recreation (has CHECK on network) ──
2037
- sqlite.exec(`CREATE TABLE defi_positions_new (
2038
- id TEXT PRIMARY KEY,
2039
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
2040
- category TEXT NOT NULL CHECK(category IN (${inList(POSITION_CATEGORIES)})),
2041
- provider TEXT NOT NULL,
2042
- chain TEXT NOT NULL CHECK(chain IN (${inList(CHAIN_TYPES)})),
2043
- network TEXT CHECK(network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
2044
- asset_id TEXT,
2045
- amount TEXT NOT NULL,
2046
- amount_usd REAL,
2047
- metadata TEXT,
2048
- status TEXT NOT NULL DEFAULT 'ACTIVE' CHECK(status IN (${inList(POSITION_STATUSES)})),
2049
- opened_at INTEGER NOT NULL,
2050
- closed_at INTEGER,
2051
- last_synced_at INTEGER NOT NULL,
2052
- created_at INTEGER NOT NULL,
2053
- updated_at INTEGER NOT NULL
2054
- )`);
2055
- sqlite.exec(`INSERT INTO defi_positions_new
2056
- SELECT id, wallet_id, category, provider, chain, ${solanaCase('network')},
2057
- asset_id, amount, amount_usd, metadata, status, opened_at, closed_at,
2058
- last_synced_at, created_at, updated_at
2059
- FROM defi_positions`);
2060
- sqlite.exec('DROP TABLE defi_positions');
2061
- sqlite.exec('ALTER TABLE defi_positions_new RENAME TO defi_positions');
2062
- // Recreate defi_positions indexes
2063
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_category ON defi_positions(wallet_id, category)');
2064
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_provider ON defi_positions(wallet_id, provider)');
2065
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_status ON defi_positions(status)');
2066
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_defi_positions_unique ON defi_positions(wallet_id, provider, asset_id, category)');
2067
- // ── 4. incoming_transactions: UPDATE only (no CHECK on network) ──
2068
- sqlite.exec(`UPDATE incoming_transactions SET network = 'solana-mainnet' WHERE chain = 'solana' AND network = 'mainnet'`);
2069
- sqlite.exec(`UPDATE incoming_transactions SET network = 'solana-devnet' WHERE chain = 'solana' AND network = 'devnet'`);
2070
- sqlite.exec(`UPDATE incoming_transactions SET network = 'solana-testnet' WHERE chain = 'solana' AND network = 'testnet'`);
2071
- // ── 5. incoming_tx_cursors: UPDATE only (no CHECK on network) ──
2072
- sqlite.exec(`UPDATE incoming_tx_cursors SET network = 'solana-mainnet' WHERE chain = 'solana' AND network = 'mainnet'`);
2073
- sqlite.exec(`UPDATE incoming_tx_cursors SET network = 'solana-devnet' WHERE chain = 'solana' AND network = 'devnet'`);
2074
- sqlite.exec(`UPDATE incoming_tx_cursors SET network = 'solana-testnet' WHERE chain = 'solana' AND network = 'testnet'`);
2075
- // ── 6. token_registry: UPDATE only (no CHECK on network, no chain column) ──
2076
- sqlite.exec(`UPDATE token_registry SET network = 'solana-mainnet' WHERE network = 'mainnet'`);
2077
- sqlite.exec(`UPDATE token_registry SET network = 'solana-devnet' WHERE network = 'devnet'`);
2078
- sqlite.exec(`UPDATE token_registry SET network = 'solana-testnet' WHERE network = 'testnet'`);
2079
- sqlite.exec('COMMIT');
2080
- }
2081
- catch (err) {
2082
- sqlite.exec('ROLLBACK');
2083
- throw err;
2084
- }
2085
- // Re-enable foreign keys and verify integrity
2086
- sqlite.pragma('foreign_keys = ON');
2087
- const fkErrors = sqlite.pragma('foreign_key_check');
2088
- if (fkErrors.length > 0) {
2089
- throw new Error(`FK integrity check failed after v29 migration: ${JSON.stringify(fkErrors)}`);
2090
- }
2091
- },
2092
- });
2093
- // ---------------------------------------------------------------------------
2094
- // Migration v30: Add MATURED status to defi_positions CHECK constraint (v29.6)
2095
- // ---------------------------------------------------------------------------
2096
- MIGRATIONS.push({
2097
- version: 30,
2098
- description: 'Add MATURED position status to defi_positions CHECK constraint (v29.6 Yield)',
2099
- managesOwnTransaction: true,
2100
- up: (sqlite) => {
2101
- sqlite.exec('BEGIN');
2102
- try {
2103
- // 12-step table recreation for defi_positions (status CHECK constraint update)
2104
- sqlite.exec(`CREATE TABLE defi_positions_new (
2105
- id TEXT PRIMARY KEY,
2106
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
2107
- category TEXT NOT NULL CHECK(category IN (${inList(POSITION_CATEGORIES)})),
2108
- provider TEXT NOT NULL,
2109
- chain TEXT NOT NULL CHECK(chain IN (${inList(CHAIN_TYPES)})),
2110
- network TEXT CHECK(network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
2111
- asset_id TEXT,
2112
- amount TEXT NOT NULL,
2113
- amount_usd REAL,
2114
- metadata TEXT,
2115
- status TEXT NOT NULL DEFAULT 'ACTIVE' CHECK(status IN (${inList(POSITION_STATUSES)})),
2116
- opened_at INTEGER NOT NULL,
2117
- closed_at INTEGER,
2118
- last_synced_at INTEGER NOT NULL,
2119
- created_at INTEGER NOT NULL,
2120
- updated_at INTEGER NOT NULL
2121
- )`);
2122
- sqlite.exec(`INSERT INTO defi_positions_new
2123
- SELECT id, wallet_id, category, provider, chain, network,
2124
- asset_id, amount, amount_usd, metadata, status, opened_at, closed_at,
2125
- last_synced_at, created_at, updated_at
2126
- FROM defi_positions`);
2127
- sqlite.exec('DROP TABLE defi_positions');
2128
- sqlite.exec('ALTER TABLE defi_positions_new RENAME TO defi_positions');
2129
- // Recreate indexes
2130
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_category ON defi_positions(wallet_id, category)');
2131
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_wallet_provider ON defi_positions(wallet_id, provider)');
2132
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_defi_positions_status ON defi_positions(status)');
2133
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_defi_positions_unique ON defi_positions(wallet_id, provider, asset_id, category)');
2134
- sqlite.exec('COMMIT');
2135
- }
2136
- catch (err) {
2137
- sqlite.exec('ROLLBACK');
2138
- throw err;
2139
- }
2140
- // Re-enable foreign keys and verify integrity
2141
- sqlite.pragma('foreign_keys = ON');
2142
- const fkErrors = sqlite.pragma('foreign_key_check');
2143
- if (fkErrors.length > 0) {
2144
- throw new Error(`FK integrity check failed after v30 migration: ${JSON.stringify(fkErrors)}`);
2145
- }
2146
- },
2147
- });
2148
- // ---------------------------------------------------------------------------
2149
- // Migration v31: Create wallet_apps table for Human Wallet Apps registry (v29.7)
2150
- // ---------------------------------------------------------------------------
2151
- MIGRATIONS.push({
2152
- version: 31,
2153
- description: 'Create wallet_apps table for Human Wallet Apps registry',
2154
- up: (sqlite) => {
2155
- sqlite.exec(`CREATE TABLE IF NOT EXISTS wallet_apps (
2156
- id TEXT PRIMARY KEY,
2157
- name TEXT NOT NULL UNIQUE,
2158
- display_name TEXT NOT NULL,
2159
- signing_enabled INTEGER NOT NULL DEFAULT 1,
2160
- alerts_enabled INTEGER NOT NULL DEFAULT 1,
2161
- created_at INTEGER NOT NULL,
2162
- updated_at INTEGER NOT NULL
2163
- )`);
2164
- },
2165
- });
2166
- // v32: Change sessions DDL default max_renewals from 30 to 0 (unlimited).
2167
- // This is a no-op migration: existing rows keep their values, and the Drizzle
2168
- // schema (.default(0)) already handles the app-level default for new inserts.
2169
- // The DDL default change only affects fresh databases via getCreateTableStatements().
2170
- MIGRATIONS.push({
2171
- version: 32,
2172
- description: 'Session progressive security: default max_renewals 30 -> 0 (unlimited)',
2173
- up: (_sqlite) => {
2174
- // No DDL needed -- SQLite cannot ALTER TABLE ... ALTER COLUMN DEFAULT.
2175
- // Fresh databases use getCreateTableStatements() which already has DEFAULT 0.
2176
- // Existing sessions retain their max_renewals values unchanged.
2177
- },
2178
- });
2179
- // ---------------------------------------------------------------------------
2180
- // Migration v33: Add sign_topic and notify_topic to wallet_apps for per-wallet ntfy topic routing (v29.10)
2181
- // ---------------------------------------------------------------------------
2182
- MIGRATIONS.push({
2183
- version: 33,
2184
- description: 'Add sign_topic and notify_topic columns to wallet_apps for per-wallet ntfy topic routing',
2185
- up: (sqlite) => {
2186
- const cols = sqlite.prepare("PRAGMA table_info('wallet_apps')").all().map(c => c.name);
2187
- if (!cols.includes('sign_topic'))
2188
- sqlite.exec(`ALTER TABLE wallet_apps ADD COLUMN sign_topic TEXT`);
2189
- if (!cols.includes('notify_topic'))
2190
- sqlite.exec(`ALTER TABLE wallet_apps ADD COLUMN notify_topic TEXT`);
2191
- // Backfill existing rows with prefix+appName defaults
2192
- const prefix = 'waiaas-sign';
2193
- const notifyPrefix = 'waiaas-notify';
2194
- const rows = sqlite.prepare('SELECT id, name FROM wallet_apps').all();
2195
- const stmt = sqlite.prepare('UPDATE wallet_apps SET sign_topic = ?, notify_topic = ? WHERE id = ?');
2196
- for (const row of rows) {
2197
- stmt.run(`${prefix}-${row.name}`, `${notifyPrefix}-${row.name}`, row.id);
2198
- }
2199
- },
2200
- });
2201
- // ---------------------------------------------------------------------------
2202
- // v34: Add wallet_type column to wallet_apps (multi-device per wallet type)
2203
- // ---------------------------------------------------------------------------
2204
- MIGRATIONS.push({
2205
- version: 34,
2206
- description: 'Add wallet_type column to wallet_apps for multi-device per wallet type',
2207
- up: (sqlite) => {
2208
- const cols = sqlite.prepare("PRAGMA table_info('wallet_apps')").all().map(c => c.name);
2209
- if (!cols.includes('wallet_type')) {
2210
- sqlite.exec(`ALTER TABLE wallet_apps ADD COLUMN wallet_type TEXT NOT NULL DEFAULT ''`);
2211
- // Backfill: set wallet_type = name for existing rows
2212
- sqlite.exec(`UPDATE wallet_apps SET wallet_type = name WHERE wallet_type = ''`);
2213
- }
2214
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_wallet_apps_wallet_type ON wallet_apps(wallet_type)');
2215
- },
2216
- });
2217
- // ---------------------------------------------------------------------------
2218
- // v35: Add subscription_token column to wallet_apps (token-based ntfy topic routing)
2219
- // ---------------------------------------------------------------------------
2220
- MIGRATIONS.push({
2221
- version: 35,
2222
- description: 'Add subscription_token column to wallet_apps for token-based ntfy topic routing',
2223
- up: (sqlite) => {
2224
- const cols = sqlite.prepare("PRAGMA table_info('wallet_apps')").all().map(c => c.name);
2225
- if (!cols.includes('subscription_token')) {
2226
- sqlite.exec(`ALTER TABLE wallet_apps ADD COLUMN subscription_token TEXT`);
2227
- }
2228
- },
2229
- });
2230
- // ---------------------------------------------------------------------------
2231
- // v36: Add idx_audit_log_tx_id index for audit log tx_id filter queries (OPS-02)
2232
- // ---------------------------------------------------------------------------
2233
- MIGRATIONS.push({
2234
- version: 36,
2235
- description: 'Add idx_audit_log_tx_id index for audit log tx_id filter queries',
2236
- up: (sqlite) => {
2237
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_audit_log_tx_id ON audit_log(tx_id)');
2238
- },
2239
- });
2240
- // ---------------------------------------------------------------------------
2241
- // v37: Create webhooks + webhook_logs tables for webhook outbound (OPS-04)
2242
- // ---------------------------------------------------------------------------
2243
- MIGRATIONS.push({
2244
- version: 37,
2245
- description: 'Create webhooks and webhook_logs tables for webhook outbound (OPS-04)',
2246
- up: (sqlite) => {
2247
- // Enable foreign keys for CASCADE support
2248
- sqlite.exec('PRAGMA foreign_keys = ON');
2249
- // Table 20: webhooks -- webhook subscription registry
2250
- sqlite.exec(`CREATE TABLE IF NOT EXISTS webhooks (
2251
- id TEXT PRIMARY KEY,
2252
- url TEXT NOT NULL,
2253
- secret_hash TEXT NOT NULL,
2254
- secret_encrypted TEXT NOT NULL,
2255
- events TEXT NOT NULL DEFAULT '[]',
2256
- description TEXT,
2257
- enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
2258
- created_at INTEGER NOT NULL,
2259
- updated_at INTEGER NOT NULL
2260
- )`);
2261
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_webhooks_enabled ON webhooks(enabled)');
2262
- // Table 21: webhook_logs -- webhook delivery attempt history
2263
- sqlite.exec(`CREATE TABLE IF NOT EXISTS webhook_logs (
2264
- id TEXT PRIMARY KEY,
2265
- webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
2266
- event_type TEXT NOT NULL,
2267
- status TEXT NOT NULL CHECK (status IN ('success', 'failed')),
2268
- http_status INTEGER,
2269
- attempt INTEGER NOT NULL DEFAULT 1,
2270
- error TEXT,
2271
- request_duration INTEGER,
2272
- created_at INTEGER NOT NULL
2273
- )`);
2274
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_webhook_logs_webhook_id ON webhook_logs(webhook_id)');
2275
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_webhook_logs_event_type ON webhook_logs(event_type)');
2276
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_webhook_logs_status ON webhook_logs(status)');
2277
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_webhook_logs_created_at ON webhook_logs(created_at)');
2278
- },
2279
- });
2280
- // ---------------------------------------------------------------------------
2281
- // v38: Add smart account columns to wallets table (ERC-4337 Account Abstraction)
2282
- // ---------------------------------------------------------------------------
2283
- MIGRATIONS.push({
2284
- version: 38,
2285
- description: 'Add smart account columns to wallets table (account_type, signer_key, deployed, entry_point)',
2286
- up: (sqlite) => {
2287
- sqlite.exec(`ALTER TABLE wallets ADD COLUMN account_type TEXT NOT NULL DEFAULT 'eoa'`);
2288
- sqlite.exec(`ALTER TABLE wallets ADD COLUMN signer_key TEXT`);
2289
- sqlite.exec(`ALTER TABLE wallets ADD COLUMN deployed INTEGER NOT NULL DEFAULT 1`);
2290
- sqlite.exec(`ALTER TABLE wallets ADD COLUMN entry_point TEXT`);
2291
- },
2292
- });
2293
- // v39: ERC-8004 Trustless Agents Foundation
2294
- // Creates agent_identities + reputation_cache tables, adds pending_approvals.approval_type,
2295
- // recreates policies table with REPUTATION_THRESHOLD in CHECK constraint.
2296
- MIGRATIONS.push({
2297
- version: 39,
2298
- description: 'ERC-8004: agent_identities + reputation_cache + pending_approvals.approval_type + policies CHECK update',
2299
- managesOwnTransaction: true,
2300
- up: (sqlite) => {
2301
- sqlite.exec('BEGIN');
2302
- // Step 1: Create agent_identities table
2303
- sqlite.exec(`CREATE TABLE IF NOT EXISTS agent_identities (
2304
- id TEXT PRIMARY KEY,
2305
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE CASCADE,
2306
- chain_agent_id TEXT NOT NULL,
2307
- registry_address TEXT NOT NULL,
2308
- chain_id INTEGER NOT NULL,
2309
- agent_uri TEXT,
2310
- registration_file_url TEXT,
2311
- status TEXT NOT NULL DEFAULT 'PENDING'
2312
- CHECK (status IN ('PENDING', 'REGISTERED', 'WALLET_LINKED', 'DEREGISTERED')),
2313
- created_at INTEGER NOT NULL,
2314
- updated_at INTEGER NOT NULL
2315
- )`);
2316
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_agent_identities_wallet ON agent_identities(wallet_id)');
2317
- sqlite.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_identities_chain ON agent_identities(registry_address, chain_agent_id)');
2318
- // Step 2: Create reputation_cache table
2319
- sqlite.exec(`CREATE TABLE IF NOT EXISTS reputation_cache (
2320
- agent_id TEXT NOT NULL,
2321
- registry_address TEXT NOT NULL,
2322
- tag1 TEXT NOT NULL DEFAULT '',
2323
- tag2 TEXT NOT NULL DEFAULT '',
2324
- score INTEGER NOT NULL,
2325
- score_decimals INTEGER NOT NULL DEFAULT 0,
2326
- feedback_count INTEGER NOT NULL DEFAULT 0,
2327
- cached_at INTEGER NOT NULL,
2328
- PRIMARY KEY (agent_id, registry_address, tag1, tag2)
2329
- )`);
2330
- // Step 3: Add approval_type to pending_approvals
2331
- sqlite.exec("ALTER TABLE pending_approvals ADD COLUMN approval_type TEXT NOT NULL DEFAULT 'SIWE' CHECK (approval_type IN ('SIWE', 'EIP712'))");
2332
- // Step 4: Recreate policies table with REPUTATION_THRESHOLD in CHECK constraint
2333
- // Uses same pattern as v11, v20, v27, v33 (INSERT → DROP → RENAME)
2334
- sqlite.exec(`CREATE TABLE policies_new (
2335
- id TEXT PRIMARY KEY,
2336
- wallet_id TEXT REFERENCES wallets(id) ON DELETE CASCADE,
2337
- type TEXT NOT NULL CHECK (type IN (${inList(POLICY_TYPES)})),
2338
- rules TEXT NOT NULL,
2339
- priority INTEGER NOT NULL DEFAULT 0,
2340
- enabled INTEGER NOT NULL DEFAULT 1,
2341
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
2342
- created_at INTEGER NOT NULL,
2343
- updated_at INTEGER NOT NULL
2344
- )`);
2345
- sqlite.exec('INSERT INTO policies_new SELECT * FROM policies');
2346
- sqlite.exec('DROP TABLE policies');
2347
- sqlite.exec('ALTER TABLE policies_new RENAME TO policies');
2348
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_policies_wallet_enabled ON policies(wallet_id, enabled)');
2349
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_policies_type ON policies(type)');
2350
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_policies_network ON policies(network)');
2351
- sqlite.exec('COMMIT');
2352
- },
2353
- });
2354
- // ---------------------------------------------------------------------------
2355
- // v40: Add typed_data_json column to pending_approvals for EIP-712 approval flow
2356
- // ---------------------------------------------------------------------------
2357
- MIGRATIONS.push({
2358
- version: 40,
2359
- description: 'ERC-8004: pending_approvals.typed_data_json for EIP-712 approval payloads',
2360
- up: (sqlite) => {
2361
- sqlite.exec("ALTER TABLE pending_approvals ADD COLUMN typed_data_json TEXT");
2362
- },
2363
- });
2364
- // ---------------------------------------------------------------------------
2365
- // v41: Smart Account per-wallet provider columns (v30.9)
2366
- // ---------------------------------------------------------------------------
2367
- MIGRATIONS.push({
2368
- version: 41,
2369
- description: 'Smart Account per-wallet provider: aa_provider, aa_provider_api_key_encrypted, aa_bundler_url, aa_paymaster_url',
2370
- up: (sqlite) => {
2371
- // Skip if columns already exist (e.g. fresh DDL via pushSchema includes them)
2372
- const columns = sqlite
2373
- .prepare("PRAGMA table_info('wallets')")
2374
- .all();
2375
- const has = (name) => columns.some((c) => c.name === name);
2376
- if (!has('aa_provider')) {
2377
- sqlite.exec("ALTER TABLE wallets ADD COLUMN aa_provider TEXT CHECK (aa_provider IS NULL OR aa_provider IN ('pimlico', 'alchemy', 'custom'))");
2378
- }
2379
- if (!has('aa_provider_api_key_encrypted')) {
2380
- sqlite.exec("ALTER TABLE wallets ADD COLUMN aa_provider_api_key_encrypted TEXT");
2381
- }
2382
- if (!has('aa_bundler_url')) {
2383
- sqlite.exec("ALTER TABLE wallets ADD COLUMN aa_bundler_url TEXT");
2384
- }
2385
- if (!has('aa_paymaster_url')) {
2386
- sqlite.exec("ALTER TABLE wallets ADD COLUMN aa_paymaster_url TEXT");
2387
- }
2388
- },
2389
- });
2390
- // ---------------------------------------------------------------------------
2391
- // v42: Seed all 10 action provider _enabled defaults to true
2392
- // ---------------------------------------------------------------------------
2393
- MIGRATIONS.push({
2394
- version: 42,
2395
- description: 'Seed all 10 action provider _enabled defaults to true (INSERT OR IGNORE preserves existing)',
2396
- up: (sqlite) => {
2397
- const keys = [
2398
- 'actions.jupiter_swap_enabled',
2399
- 'actions.zerox_swap_enabled',
2400
- 'actions.lifi_enabled',
2401
- 'actions.lido_staking_enabled',
2402
- 'actions.jito_staking_enabled',
2403
- 'actions.aave_v3_enabled',
2404
- 'actions.kamino_enabled',
2405
- 'actions.pendle_yield_enabled',
2406
- 'actions.drift_enabled',
2407
- 'actions.erc8004_agent_enabled',
2408
- ];
2409
- const now = Math.floor(Date.now() / 1000);
2410
- const stmt = sqlite.prepare("INSERT OR IGNORE INTO settings (key, value, encrypted, category, updated_at) VALUES (?, 'true', 0, 'actions', ?)");
2411
- for (const key of keys) {
2412
- stmt.run(key, now);
2413
- }
2414
- },
2415
- });
2416
- // ---------------------------------------------------------------------------
2417
- // #252: Paymaster Policy ID column
2418
- // ---------------------------------------------------------------------------
2419
- MIGRATIONS.push({
2420
- version: 43,
2421
- description: 'Add aa_paymaster_policy_id column to wallets for paymaster context (sponsorshipPolicyId)',
2422
- up: (sqlite) => {
2423
- const cols = sqlite.pragma('table_info(wallets)');
2424
- const has = (n) => cols.some((c) => c.name === n);
2425
- if (!has('aa_paymaster_policy_id')) {
2426
- sqlite.exec('ALTER TABLE wallets ADD COLUMN aa_paymaster_policy_id TEXT');
2427
- }
2428
- },
2429
- });
2430
- // v44: Create nft_metadata_cache table for NFT metadata caching (24h TTL)
2431
- MIGRATIONS.push({
2432
- version: 44,
2433
- description: 'Create nft_metadata_cache table for NFT metadata caching (24h TTL)',
2434
- up: (sqlite) => {
2435
- sqlite.exec(`
2436
- CREATE TABLE IF NOT EXISTS nft_metadata_cache (
2437
- id TEXT PRIMARY KEY,
2438
- contract_address TEXT NOT NULL,
2439
- token_id TEXT NOT NULL,
2440
- chain TEXT NOT NULL CHECK (chain IN (${inList(CHAIN_TYPES)})),
2441
- network TEXT NOT NULL CHECK (network IN (${inList(NETWORK_TYPES)})),
2442
- metadata_json TEXT NOT NULL,
2443
- cached_at INTEGER NOT NULL,
2444
- expires_at INTEGER NOT NULL
2445
- )
2446
- `);
2447
- sqlite.exec(`
2448
- CREATE UNIQUE INDEX IF NOT EXISTS idx_nft_cache_unique
2449
- ON nft_metadata_cache (contract_address, token_id, chain, network)
2450
- `);
2451
- sqlite.exec(`
2452
- CREATE INDEX IF NOT EXISTS idx_nft_cache_expires
2453
- ON nft_metadata_cache (expires_at)
2454
- `);
2455
- },
2456
- });
2457
- // v45: Create userop_builds table for UserOp Build/Sign API (v31.2)
2458
- MIGRATIONS.push({
2459
- version: 45,
2460
- description: 'Create userop_builds table for UserOp Build/Sign API',
2461
- up: (sqlite) => {
2462
- sqlite.exec(`
2463
- CREATE TABLE IF NOT EXISTS userop_builds (
2464
- id TEXT PRIMARY KEY,
2465
- wallet_id TEXT NOT NULL,
2466
- call_data TEXT NOT NULL,
2467
- sender TEXT NOT NULL,
2468
- nonce TEXT NOT NULL,
2469
- entry_point TEXT NOT NULL,
2470
- created_at INTEGER NOT NULL,
2471
- expires_at INTEGER NOT NULL,
2472
- used INTEGER NOT NULL DEFAULT 0 CHECK (used IN (0, 1))
2473
- )
2474
- `);
2475
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_userop_builds_wallet_id ON userop_builds(wallet_id)');
2476
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_userop_builds_expires ON userop_builds(expires_at)');
2477
- },
2478
- });
2479
- // ── v46: Backfill CONTRACT_CALL amount from metadata (#260) ──────────
2480
- MIGRATIONS.push({
2481
- version: 46,
2482
- description: 'Backfill CONTRACT_CALL amount from metadata.originalRequest.value',
2483
- up: (sqlite) => {
2484
- sqlite.exec(`
2485
- UPDATE transactions
2486
- SET amount = json_extract(metadata, '$.originalRequest.value')
2487
- WHERE type = 'CONTRACT_CALL'
2488
- AND amount IS NULL
2489
- AND json_extract(metadata, '$.originalRequest.value') IS NOT NULL
2490
- `);
2491
- },
2492
- });
2493
- // ── v47: Add factory_address column to wallets (#256) ────────────────
2494
- MIGRATIONS.push({
2495
- version: 47,
2496
- description: 'Add factory_address column to wallets for multichain Smart Account factory tracking',
2497
- up: (sqlite) => {
2498
- // Add nullable factory_address column
2499
- sqlite.exec(`ALTER TABLE wallets ADD COLUMN factory_address TEXT`);
2500
- // Backfill: existing smart accounts used the Solady factory
2501
- sqlite.exec(`
2502
- UPDATE wallets
2503
- SET factory_address = '0x5d82735936c6Cd5DE57cC3c1A799f6B2E6F933Df'
2504
- WHERE account_type = 'smart' AND factory_address IS NULL
2505
- `);
2506
- },
2507
- });
2508
- // ── v48: Purge mock defi_positions data from Kamino/Drift (#263/#269) ──
2509
- MIGRATIONS.push({
2510
- version: 48,
2511
- description: 'Purge mock defi_positions data from Kamino/Drift (#263)',
2512
- up: (sqlite) => {
2513
- sqlite.exec(`DELETE FROM defi_positions WHERE provider IN ('kamino', 'drift_perp')`);
2514
- },
2515
- });
2516
- // ── v49: Fix bugged smart account wallets — convert to EOA (#272) ─────
2517
- MIGRATIONS.push({
2518
- version: 49,
2519
- description: 'Convert bugged smart account wallets (missing signerKey) to EOA (#272)',
2520
- up: (sqlite) => {
2521
- // Smart account wallets created while smartAccountService was not injected
2522
- // have accountType='smart' but signerKey=NULL (signer_key column).
2523
- // Their publicKey is already the EOA address, so convert them to EOA type.
2524
- sqlite.exec(`
2525
- UPDATE wallets
2526
- SET account_type = 'eoa',
2527
- deployed = 1,
2528
- entry_point = NULL,
2529
- factory_address = NULL
2530
- WHERE account_type = 'smart' AND signer_key IS NULL
2531
- `);
2532
- },
2533
- });
2534
- // ── v50: Add network column to userop_builds (#279) ──────────────────
2535
- MIGRATIONS.push({
2536
- version: 50,
2537
- description: 'Add network column to userop_builds for Sign route RPC resolve (#279)',
2538
- up: (sqlite) => {
2539
- const cols = sqlite.prepare("PRAGMA table_info('userop_builds')").all();
2540
- if (!cols.some((c) => c.name === 'network')) {
2541
- sqlite.exec(`ALTER TABLE userop_builds ADD COLUMN network TEXT`);
2542
- }
2543
- },
2544
- });
2545
- // ── v51: Hyperliquid order history table ─────────────────────────────
2546
- MIGRATIONS.push({
2547
- version: 51,
2548
- description: 'Create hyperliquid_orders table for Hyperliquid DEX integration',
2549
- up: (sqlite) => {
2550
- // Idempotent check
2551
- const tables = sqlite
2552
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='hyperliquid_orders'")
2553
- .all();
2554
- if (tables.length > 0)
2555
- return;
2556
- sqlite.exec(`
2557
- CREATE TABLE hyperliquid_orders (
2558
- id TEXT PRIMARY KEY,
2559
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
2560
- sub_account_address TEXT,
2561
- oid INTEGER,
2562
- cloid TEXT,
2563
- transaction_id TEXT REFERENCES transactions(id),
2564
- market TEXT NOT NULL,
2565
- asset_index INTEGER NOT NULL,
2566
- side TEXT NOT NULL CHECK(side IN ('BUY', 'SELL')),
2567
- order_type TEXT NOT NULL CHECK(order_type IN ('MARKET', 'LIMIT', 'STOP_MARKET', 'STOP_LIMIT', 'TAKE_PROFIT')),
2568
- size TEXT NOT NULL,
2569
- price TEXT,
2570
- trigger_price TEXT,
2571
- tif TEXT CHECK(tif IN ('GTC', 'IOC', 'ALO')),
2572
- reduce_only INTEGER NOT NULL DEFAULT 0,
2573
- status TEXT NOT NULL CHECK(status IN ('PENDING', 'RESTING', 'FILLED', 'PARTIALLY_FILLED', 'CANCELLED', 'REJECTED', 'TRIGGERED')),
2574
- filled_size TEXT,
2575
- avg_fill_price TEXT,
2576
- is_spot INTEGER NOT NULL DEFAULT 0,
2577
- leverage INTEGER,
2578
- margin_mode TEXT CHECK(margin_mode IN ('CROSS', 'ISOLATED')),
2579
- response_data TEXT,
2580
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
2581
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
2582
- );
2583
- CREATE INDEX idx_hl_orders_wallet ON hyperliquid_orders(wallet_id);
2584
- CREATE INDEX idx_hl_orders_oid ON hyperliquid_orders(oid);
2585
- CREATE INDEX idx_hl_orders_market ON hyperliquid_orders(market);
2586
- CREATE INDEX idx_hl_orders_status ON hyperliquid_orders(status);
2587
- CREATE INDEX idx_hl_orders_created ON hyperliquid_orders(created_at);
2588
- `);
2589
- },
2590
- });
2591
- // ── v52: Hyperliquid sub-accounts table ──────────────────────────────
2592
- MIGRATIONS.push({
2593
- version: 52,
2594
- description: 'Create hyperliquid_sub_accounts table for Hyperliquid Sub-account management',
2595
- up: (sqlite) => {
2596
- // Idempotent check
2597
- const tables = sqlite
2598
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='hyperliquid_sub_accounts'")
2599
- .all();
2600
- if (tables.length > 0)
2601
- return;
2602
- sqlite.exec(`
2603
- CREATE TABLE hyperliquid_sub_accounts (
2604
- id TEXT PRIMARY KEY,
2605
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
2606
- sub_account_address TEXT NOT NULL,
2607
- name TEXT,
2608
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
2609
- UNIQUE(wallet_id, sub_account_address)
2610
- );
2611
- CREATE INDEX idx_hl_sub_wallet ON hyperliquid_sub_accounts(wallet_id);
2612
- `);
2613
- },
2614
- });
2615
- // ---------------------------------------------------------------------------
2616
- // v53: polymarket_orders table
2617
- // ---------------------------------------------------------------------------
2618
- MIGRATIONS.push({
2619
- version: 53,
2620
- description: 'Create polymarket_orders table for Polymarket CLOB order tracking',
2621
- up: (sqlite) => {
2622
- // Idempotent check
2623
- const tables = sqlite
2624
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='polymarket_orders'")
2625
- .all();
2626
- if (tables.length > 0)
2627
- return;
2628
- sqlite.exec(`
2629
- CREATE TABLE polymarket_orders (
2630
- id TEXT PRIMARY KEY,
2631
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
2632
- transaction_id TEXT REFERENCES transactions(id),
2633
- condition_id TEXT NOT NULL,
2634
- token_id TEXT NOT NULL,
2635
- market_slug TEXT,
2636
- outcome TEXT NOT NULL,
2637
- order_id TEXT,
2638
- side TEXT NOT NULL CHECK (side IN ('BUY', 'SELL')),
2639
- order_type TEXT NOT NULL CHECK (order_type IN ('GTC', 'GTD', 'FOK', 'IOC')),
2640
- price TEXT NOT NULL,
2641
- size TEXT NOT NULL,
2642
- status TEXT NOT NULL CHECK (status IN ('PENDING', 'LIVE', 'MATCHED', 'PARTIALLY_FILLED', 'CANCELLED', 'EXPIRED')),
2643
- filled_size TEXT,
2644
- avg_fill_price TEXT,
2645
- salt TEXT,
2646
- maker_amount TEXT,
2647
- taker_amount TEXT,
2648
- signature_type INTEGER NOT NULL DEFAULT 0,
2649
- fee_rate_bps INTEGER,
2650
- expiration INTEGER,
2651
- nonce TEXT,
2652
- is_neg_risk INTEGER NOT NULL DEFAULT 0,
2653
- response_data TEXT,
2654
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
2655
- updated_at INTEGER NOT NULL DEFAULT (unixepoch())
2656
- );
2657
- CREATE INDEX idx_pm_orders_wallet ON polymarket_orders(wallet_id);
2658
- CREATE INDEX idx_pm_orders_order_id ON polymarket_orders(order_id);
2659
- CREATE INDEX idx_pm_orders_condition ON polymarket_orders(condition_id);
2660
- CREATE INDEX idx_pm_orders_status ON polymarket_orders(status);
2661
- CREATE INDEX idx_pm_orders_created ON polymarket_orders(created_at);
2662
- `);
2663
- },
2664
- });
2665
- // ---------------------------------------------------------------------------
2666
- // v54: polymarket_positions + polymarket_api_keys tables
2667
- // ---------------------------------------------------------------------------
2668
- MIGRATIONS.push({
2669
- version: 54,
2670
- description: 'Create polymarket_positions and polymarket_api_keys tables',
2671
- up: (sqlite) => {
2672
- // polymarket_positions
2673
- const posTables = sqlite
2674
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='polymarket_positions'")
2675
- .all();
2676
- if (posTables.length === 0) {
2677
- sqlite.exec(`
2678
- CREATE TABLE polymarket_positions (
2679
- id TEXT PRIMARY KEY,
2680
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
2681
- condition_id TEXT NOT NULL,
2682
- token_id TEXT NOT NULL,
2683
- market_slug TEXT,
2684
- outcome TEXT NOT NULL CHECK (outcome IN ('YES', 'NO')),
2685
- size TEXT NOT NULL DEFAULT '0',
2686
- avg_price TEXT,
2687
- realized_pnl TEXT DEFAULT '0',
2688
- market_resolved INTEGER NOT NULL DEFAULT 0,
2689
- winning_outcome TEXT,
2690
- is_neg_risk INTEGER NOT NULL DEFAULT 0,
2691
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
2692
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
2693
- UNIQUE(wallet_id, token_id)
2694
- );
2695
- CREATE INDEX idx_pm_positions_wallet ON polymarket_positions(wallet_id);
2696
- CREATE INDEX idx_pm_positions_condition ON polymarket_positions(condition_id);
2697
- CREATE INDEX idx_pm_positions_resolved ON polymarket_positions(market_resolved);
2698
- `);
2699
- }
2700
- // polymarket_api_keys
2701
- const keyTables = sqlite
2702
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='polymarket_api_keys'")
2703
- .all();
2704
- if (keyTables.length === 0) {
2705
- sqlite.exec(`
2706
- CREATE TABLE polymarket_api_keys (
2707
- id TEXT PRIMARY KEY,
2708
- wallet_id TEXT NOT NULL REFERENCES wallets(id),
2709
- api_key TEXT NOT NULL,
2710
- api_secret_encrypted TEXT NOT NULL,
2711
- api_passphrase_encrypted TEXT NOT NULL,
2712
- signature_type INTEGER NOT NULL DEFAULT 0,
2713
- proxy_address TEXT,
2714
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
2715
- UNIQUE(wallet_id)
2716
- );
2717
- `);
2718
- }
2719
- },
2720
- });
2721
- // ---------------------------------------------------------------------------
2722
- // v55: wallet_credentials table for External Action credential vault
2723
- // ---------------------------------------------------------------------------
2724
- MIGRATIONS.push({
2725
- version: 55,
2726
- description: 'Create wallet_credentials table for External Action credential vault',
2727
- up: (sqlite) => {
2728
- // Idempotent check
2729
- const tables = sqlite
2730
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='wallet_credentials'")
2731
- .all();
2732
- if (tables.length > 0)
2733
- return;
2734
- sqlite.exec(`
2735
- CREATE TABLE wallet_credentials (
2736
- id TEXT NOT NULL PRIMARY KEY,
2737
- wallet_id TEXT,
2738
- type TEXT NOT NULL CHECK (type IN ('api-key','hmac-secret','rsa-private-key','session-token','custom')),
2739
- name TEXT NOT NULL,
2740
- encrypted_value BLOB NOT NULL,
2741
- iv BLOB NOT NULL,
2742
- auth_tag BLOB NOT NULL,
2743
- metadata TEXT NOT NULL DEFAULT '{}',
2744
- expires_at INTEGER,
2745
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
2746
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
2747
- FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE
2748
- );
2749
- CREATE UNIQUE INDEX idx_wallet_credentials_wallet_name ON wallet_credentials(wallet_id, name);
2750
- CREATE INDEX idx_wallet_credentials_global_name ON wallet_credentials(name) WHERE wallet_id IS NULL;
2751
- CREATE INDEX idx_wallet_credentials_wallet_id ON wallet_credentials(wallet_id);
2752
- CREATE INDEX idx_wallet_credentials_expires_at ON wallet_credentials(expires_at) WHERE expires_at IS NOT NULL;
2753
- `);
2754
- },
2755
- });
2756
- // ---------------------------------------------------------------------------
2757
- // v56: transactions table action tracking columns
2758
- // ---------------------------------------------------------------------------
2759
- MIGRATIONS.push({
2760
- version: 56,
2761
- description: 'Add action_kind, venue, operation, external_id columns to transactions',
2762
- up: (sqlite) => {
2763
- // Check if columns already exist (pushSchema may have already added them)
2764
- const cols = sqlite.prepare('PRAGMA table_info(transactions)').all()
2765
- .map(c => c.name);
2766
- if (!cols.includes('action_kind')) {
2767
- sqlite.exec("ALTER TABLE transactions ADD COLUMN action_kind TEXT NOT NULL DEFAULT 'contractCall'");
2768
- }
2769
- if (!cols.includes('venue')) {
2770
- sqlite.exec('ALTER TABLE transactions ADD COLUMN venue TEXT');
2771
- }
2772
- if (!cols.includes('operation')) {
2773
- sqlite.exec('ALTER TABLE transactions ADD COLUMN operation TEXT');
2774
- }
2775
- if (!cols.includes('external_id')) {
2776
- sqlite.exec('ALTER TABLE transactions ADD COLUMN external_id TEXT');
2777
- }
2778
- // Create indexes (idempotent with IF NOT EXISTS)
2779
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_action_kind ON transactions(action_kind)');
2780
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_venue ON transactions(venue) WHERE venue IS NOT NULL');
2781
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_external_id ON transactions(external_id) WHERE external_id IS NOT NULL');
2782
- },
2783
- });
2784
- // ---------------------------------------------------------------------------
2785
- // v57: composite index for external action tracking queries
2786
- // ---------------------------------------------------------------------------
2787
- MIGRATIONS.push({
2788
- version: 57,
2789
- description: 'Add composite index idx_transactions_action_kind_bridge_status',
2790
- up: (sqlite) => {
2791
- sqlite.exec('CREATE INDEX IF NOT EXISTS idx_transactions_action_kind_bridge_status ON transactions(action_kind, bridge_status) WHERE bridge_status IS NOT NULL');
2792
- },
2793
- });
2794
- // ---------------------------------------------------------------------------
2795
- // v58: Update transactions type CHECK constraint to include CONTRACT_DEPLOY
2796
- // ---------------------------------------------------------------------------
2797
- MIGRATIONS.push({
2798
- version: 58,
2799
- description: 'Add CONTRACT_DEPLOY to transactions type CHECK constraint (12-step table recreation)',
2800
- managesOwnTransaction: true,
2801
- up: (sqlite) => {
2802
- sqlite.exec('BEGIN');
2803
- try {
2804
- // Step 1: Create transactions_new with updated CHECK constraints (CONTRACT_DEPLOY in TRANSACTION_TYPES)
2805
- sqlite.exec(`CREATE TABLE transactions_new (
2806
- id TEXT PRIMARY KEY,
2807
- wallet_id TEXT NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
2808
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
2809
- chain TEXT NOT NULL,
2810
- tx_hash TEXT,
2811
- type TEXT NOT NULL CHECK (type IN (${inList(TRANSACTION_TYPES)})),
2812
- amount TEXT,
2813
- to_address TEXT,
2814
- token_mint TEXT,
2815
- contract_address TEXT,
2816
- method_signature TEXT,
2817
- spender_address TEXT,
2818
- approved_amount TEXT,
2819
- parent_id TEXT REFERENCES transactions_new(id) ON DELETE CASCADE,
2820
- batch_index INTEGER,
2821
- status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN (${inList(TRANSACTION_STATUSES)})),
2822
- tier TEXT CHECK (tier IS NULL OR tier IN (${inList(POLICY_TIERS)})),
2823
- queued_at INTEGER,
2824
- executed_at INTEGER,
2825
- created_at INTEGER NOT NULL,
2826
- reserved_amount TEXT,
2827
- amount_usd REAL,
2828
- reserved_amount_usd REAL,
2829
- error TEXT,
2830
- metadata TEXT,
2831
- network TEXT CHECK (network IS NULL OR network IN (${inList(NETWORK_TYPES)})),
2832
- bridge_status TEXT CHECK (bridge_status IS NULL OR bridge_status IN ('PENDING', 'COMPLETED', 'FAILED', 'BRIDGE_MONITORING', 'TIMEOUT', 'REFUNDED', 'PARTIALLY_FILLED', 'FILLED', 'CANCELED', 'SETTLED', 'EXPIRED')),
2833
- bridge_metadata TEXT,
2834
- action_kind TEXT NOT NULL DEFAULT 'contractCall',
2835
- venue TEXT,
2836
- operation TEXT,
2837
- external_id TEXT
2838
- )`);
2839
- // Step 2: Copy existing data
2840
- sqlite.exec(`INSERT INTO transactions_new (id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd, error, metadata, network, bridge_status, bridge_metadata, action_kind, venue, operation, external_id)
2841
- SELECT id, wallet_id, session_id, chain, tx_hash, type, amount, to_address, token_mint, contract_address, method_signature, spender_address, approved_amount, parent_id, batch_index, status, tier, queued_at, executed_at, created_at, reserved_amount, amount_usd, reserved_amount_usd, error, metadata, network, bridge_status, bridge_metadata, action_kind, venue, operation, external_id FROM transactions`);
2842
- // Step 3: Drop old table
2843
- sqlite.exec('DROP TABLE transactions');
2844
- // Step 4: Rename new table
2845
- sqlite.exec('ALTER TABLE transactions_new RENAME TO transactions');
2846
- // Step 5: Recreate all 14 indexes
2847
- sqlite.exec('CREATE INDEX idx_transactions_wallet_status ON transactions(wallet_id, status)');
2848
- sqlite.exec('CREATE INDEX idx_transactions_session_id ON transactions(session_id)');
2849
- sqlite.exec('CREATE UNIQUE INDEX idx_transactions_tx_hash ON transactions(tx_hash)');
2850
- sqlite.exec('CREATE INDEX idx_transactions_queued_at ON transactions(queued_at)');
2851
- sqlite.exec('CREATE INDEX idx_transactions_created_at ON transactions(created_at)');
2852
- sqlite.exec('CREATE INDEX idx_transactions_type ON transactions(type)');
2853
- sqlite.exec('CREATE INDEX idx_transactions_contract_address ON transactions(contract_address)');
2854
- sqlite.exec('CREATE INDEX idx_transactions_parent_id ON transactions(parent_id)');
2855
- sqlite.exec("CREATE INDEX idx_transactions_bridge_status ON transactions(bridge_status) WHERE bridge_status IS NOT NULL");
2856
- sqlite.exec("CREATE INDEX idx_transactions_gas_waiting ON transactions(status) WHERE status = 'GAS_WAITING'");
2857
- sqlite.exec('CREATE INDEX idx_transactions_action_kind ON transactions(action_kind)');
2858
- sqlite.exec("CREATE INDEX idx_transactions_venue ON transactions(venue) WHERE venue IS NOT NULL");
2859
- sqlite.exec("CREATE INDEX idx_transactions_external_id ON transactions(external_id) WHERE external_id IS NOT NULL");
2860
- sqlite.exec('CREATE INDEX idx_transactions_action_kind_bridge_status ON transactions(action_kind, bridge_status) WHERE bridge_status IS NOT NULL');
2861
- // Step 6: Commit
2862
- sqlite.exec('COMMIT');
2863
- }
2864
- catch (err) {
2865
- sqlite.exec('ROLLBACK');
2866
- throw err;
2867
- }
2868
- // Step 7: Re-enable foreign keys and verify integrity
2869
- sqlite.pragma('foreign_keys = ON');
2870
- const fkErrors = sqlite.pragma('foreign_key_check');
2871
- if (fkErrors.length > 0) {
2872
- throw new Error(`FK integrity violation after v58: ${JSON.stringify(fkErrors)}`);
2873
- }
2874
- },
2875
- });
2876
45
  /**
2877
46
  * Run incremental migrations against the database.
2878
47
  *