@waiaas/daemon 2.11.0-rc.7 → 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 (430) 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/resolve-asset.d.ts +26 -0
  21. package/dist/api/middleware/resolve-asset.d.ts.map +1 -0
  22. package/dist/api/middleware/resolve-asset.js +81 -0
  23. package/dist/api/middleware/resolve-asset.js.map +1 -0
  24. package/dist/api/middleware/siwe-verify.d.ts +6 -26
  25. package/dist/api/middleware/siwe-verify.d.ts.map +1 -1
  26. package/dist/api/middleware/siwe-verify.js +5 -50
  27. package/dist/api/middleware/siwe-verify.js.map +1 -1
  28. package/dist/api/routes/actions.d.ts +1 -0
  29. package/dist/api/routes/actions.d.ts.map +1 -1
  30. package/dist/api/routes/actions.js +52 -4
  31. package/dist/api/routes/actions.js.map +1 -1
  32. package/dist/api/routes/admin-actions.d.ts +1 -0
  33. package/dist/api/routes/admin-actions.d.ts.map +1 -1
  34. package/dist/api/routes/admin-actions.js +3 -3
  35. package/dist/api/routes/admin-actions.js.map +1 -1
  36. package/dist/api/routes/admin-auth.d.ts.map +1 -1
  37. package/dist/api/routes/admin-auth.js +12 -7
  38. package/dist/api/routes/admin-auth.js.map +1 -1
  39. package/dist/api/routes/admin-credentials.js +2 -2
  40. package/dist/api/routes/admin-credentials.js.map +1 -1
  41. package/dist/api/routes/admin-monitoring.d.ts +10 -0
  42. package/dist/api/routes/admin-monitoring.d.ts.map +1 -1
  43. package/dist/api/routes/admin-monitoring.js +59 -14
  44. package/dist/api/routes/admin-monitoring.js.map +1 -1
  45. package/dist/api/routes/admin-notifications.d.ts.map +1 -1
  46. package/dist/api/routes/admin-notifications.js +2 -15
  47. package/dist/api/routes/admin-notifications.js.map +1 -1
  48. package/dist/api/routes/admin-settings.d.ts.map +1 -1
  49. package/dist/api/routes/admin-settings.js +90 -1
  50. package/dist/api/routes/admin-settings.js.map +1 -1
  51. package/dist/api/routes/admin-wallets.d.ts +16 -1
  52. package/dist/api/routes/admin-wallets.d.ts.map +1 -1
  53. package/dist/api/routes/admin-wallets.js +64 -75
  54. package/dist/api/routes/admin-wallets.js.map +1 -1
  55. package/dist/api/routes/admin.d.ts +1 -0
  56. package/dist/api/routes/admin.d.ts.map +1 -1
  57. package/dist/api/routes/admin.js.map +1 -1
  58. package/dist/api/routes/connect-info.d.ts.map +1 -1
  59. package/dist/api/routes/connect-info.js +2 -1
  60. package/dist/api/routes/connect-info.js.map +1 -1
  61. package/dist/api/routes/credentials.js +2 -2
  62. package/dist/api/routes/credentials.js.map +1 -1
  63. package/dist/api/routes/defi-positions.d.ts.map +1 -1
  64. package/dist/api/routes/defi-positions.js +10 -0
  65. package/dist/api/routes/defi-positions.js.map +1 -1
  66. package/dist/api/routes/incoming.d.ts.map +1 -1
  67. package/dist/api/routes/incoming.js +2 -1
  68. package/dist/api/routes/incoming.js.map +1 -1
  69. package/dist/api/routes/nfts.js +24 -4
  70. package/dist/api/routes/nfts.js.map +1 -1
  71. package/dist/api/routes/openapi-schemas.d.ts +611 -118
  72. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  73. package/dist/api/routes/openapi-schemas.js +53 -5
  74. package/dist/api/routes/openapi-schemas.js.map +1 -1
  75. package/dist/api/routes/policies.d.ts +2 -0
  76. package/dist/api/routes/policies.d.ts.map +1 -1
  77. package/dist/api/routes/policies.js +55 -6
  78. package/dist/api/routes/policies.js.map +1 -1
  79. package/dist/api/routes/rpc-proxy.js.map +1 -1
  80. package/dist/api/routes/sessions.d.ts.map +1 -1
  81. package/dist/api/routes/sessions.js +47 -28
  82. package/dist/api/routes/sessions.js.map +1 -1
  83. package/dist/api/routes/staking.d.ts.map +1 -1
  84. package/dist/api/routes/staking.js +19 -76
  85. package/dist/api/routes/staking.js.map +1 -1
  86. package/dist/api/routes/tokens.d.ts.map +1 -1
  87. package/dist/api/routes/tokens.js +8 -1
  88. package/dist/api/routes/tokens.js.map +1 -1
  89. package/dist/api/routes/transactions.d.ts +3 -0
  90. package/dist/api/routes/transactions.d.ts.map +1 -1
  91. package/dist/api/routes/transactions.js +66 -12
  92. package/dist/api/routes/transactions.js.map +1 -1
  93. package/dist/api/routes/userop.d.ts.map +1 -1
  94. package/dist/api/routes/userop.js +0 -2
  95. package/dist/api/routes/userop.js.map +1 -1
  96. package/dist/api/routes/wallet-apps.d.ts.map +1 -1
  97. package/dist/api/routes/wallet-apps.js +20 -13
  98. package/dist/api/routes/wallet-apps.js.map +1 -1
  99. package/dist/api/routes/wallet.d.ts.map +1 -1
  100. package/dist/api/routes/wallet.js +12 -4
  101. package/dist/api/routes/wallet.js.map +1 -1
  102. package/dist/api/routes/wallets.d.ts.map +1 -1
  103. package/dist/api/routes/wallets.js +3 -0
  104. package/dist/api/routes/wallets.js.map +1 -1
  105. package/dist/api/routes/wc.d.ts.map +1 -1
  106. package/dist/api/routes/wc.js +13 -8
  107. package/dist/api/routes/wc.js.map +1 -1
  108. package/dist/api/routes/x402.d.ts.map +1 -1
  109. package/dist/api/routes/x402.js +1 -2
  110. package/dist/api/routes/x402.js.map +1 -1
  111. package/dist/api/server.d.ts +8 -4
  112. package/dist/api/server.d.ts.map +1 -1
  113. package/dist/api/server.js +47 -5
  114. package/dist/api/server.js.map +1 -1
  115. package/dist/constants.d.ts +1 -1
  116. package/dist/constants.d.ts.map +1 -1
  117. package/dist/constants.js +1 -1
  118. package/dist/constants.js.map +1 -1
  119. package/dist/infrastructure/action/action-provider-registry.d.ts.map +1 -1
  120. package/dist/infrastructure/action/action-provider-registry.js +2 -3
  121. package/dist/infrastructure/action/action-provider-registry.js.map +1 -1
  122. package/dist/infrastructure/action/builtin-metadata.d.ts +22 -0
  123. package/dist/infrastructure/action/builtin-metadata.d.ts.map +1 -0
  124. package/dist/infrastructure/action/builtin-metadata.js +29 -0
  125. package/dist/infrastructure/action/builtin-metadata.js.map +1 -0
  126. package/dist/infrastructure/adapter-pool.d.ts +2 -1
  127. package/dist/infrastructure/adapter-pool.d.ts.map +1 -1
  128. package/dist/infrastructure/adapter-pool.js.map +1 -1
  129. package/dist/infrastructure/auth/address-validation.d.ts +38 -0
  130. package/dist/infrastructure/auth/address-validation.d.ts.map +1 -0
  131. package/dist/infrastructure/auth/address-validation.js +134 -0
  132. package/dist/infrastructure/auth/address-validation.js.map +1 -0
  133. package/dist/infrastructure/auth/siwe-verify.d.ts +34 -0
  134. package/dist/infrastructure/auth/siwe-verify.d.ts.map +1 -0
  135. package/dist/infrastructure/auth/siwe-verify.js +58 -0
  136. package/dist/infrastructure/auth/siwe-verify.js.map +1 -0
  137. package/dist/infrastructure/auth/types.d.ts +12 -0
  138. package/dist/infrastructure/auth/types.d.ts.map +1 -0
  139. package/dist/infrastructure/auth/types.js +8 -0
  140. package/dist/infrastructure/auth/types.js.map +1 -0
  141. package/dist/infrastructure/config/loader.d.ts +1 -10
  142. package/dist/infrastructure/config/loader.d.ts.map +1 -1
  143. package/dist/infrastructure/config/loader.js +0 -2
  144. package/dist/infrastructure/config/loader.js.map +1 -1
  145. package/dist/infrastructure/database/migrate.d.ts +6 -18
  146. package/dist/infrastructure/database/migrate.d.ts.map +1 -1
  147. package/dist/infrastructure/database/migrate.js +25 -2856
  148. package/dist/infrastructure/database/migrate.js.map +1 -1
  149. package/dist/infrastructure/database/migrations/v11-v20.d.ts +17 -0
  150. package/dist/infrastructure/database/migrations/v11-v20.d.ts.map +1 -0
  151. package/dist/infrastructure/database/migrations/v11-v20.js +295 -0
  152. package/dist/infrastructure/database/migrations/v11-v20.js.map +1 -0
  153. package/dist/infrastructure/database/migrations/v2-v10.d.ts +16 -0
  154. package/dist/infrastructure/database/migrations/v2-v10.d.ts.map +1 -0
  155. package/dist/infrastructure/database/migrations/v2-v10.js +539 -0
  156. package/dist/infrastructure/database/migrations/v2-v10.js.map +1 -0
  157. package/dist/infrastructure/database/migrations/v21-v30.d.ts +17 -0
  158. package/dist/infrastructure/database/migrations/v21-v30.d.ts.map +1 -0
  159. package/dist/infrastructure/database/migrations/v21-v30.js +507 -0
  160. package/dist/infrastructure/database/migrations/v21-v30.js.map +1 -0
  161. package/dist/infrastructure/database/migrations/v31-v40.d.ts +17 -0
  162. package/dist/infrastructure/database/migrations/v31-v40.d.ts.map +1 -0
  163. package/dist/infrastructure/database/migrations/v31-v40.js +203 -0
  164. package/dist/infrastructure/database/migrations/v31-v40.js.map +1 -0
  165. package/dist/infrastructure/database/migrations/v41-v50.d.ts +17 -0
  166. package/dist/infrastructure/database/migrations/v41-v50.d.ts.map +1 -0
  167. package/dist/infrastructure/database/migrations/v41-v50.js +188 -0
  168. package/dist/infrastructure/database/migrations/v41-v50.js.map +1 -0
  169. package/dist/infrastructure/database/migrations/v51-v59.d.ts +17 -0
  170. package/dist/infrastructure/database/migrations/v51-v59.d.ts.map +1 -0
  171. package/dist/infrastructure/database/migrations/v51-v59.js +420 -0
  172. package/dist/infrastructure/database/migrations/v51-v59.js.map +1 -0
  173. package/dist/infrastructure/database/schema-ddl.d.ts +24 -0
  174. package/dist/infrastructure/database/schema-ddl.d.ts.map +1 -0
  175. package/dist/infrastructure/database/schema-ddl.js +596 -0
  176. package/dist/infrastructure/database/schema-ddl.js.map +1 -0
  177. package/dist/infrastructure/database/schema.d.ts +38 -0
  178. package/dist/infrastructure/database/schema.d.ts.map +1 -1
  179. package/dist/infrastructure/database/schema.js +2 -0
  180. package/dist/infrastructure/database/schema.js.map +1 -1
  181. package/dist/infrastructure/jwt/jwt-secret-manager.d.ts.map +1 -1
  182. package/dist/infrastructure/jwt/jwt-secret-manager.js +16 -3
  183. package/dist/infrastructure/jwt/jwt-secret-manager.js.map +1 -1
  184. package/dist/infrastructure/nft/alchemy-nft-indexer.d.ts.map +1 -1
  185. package/dist/infrastructure/nft/alchemy-nft-indexer.js +0 -1
  186. package/dist/infrastructure/nft/alchemy-nft-indexer.js.map +1 -1
  187. package/dist/infrastructure/nft/helius-nft-indexer.d.ts.map +1 -1
  188. package/dist/infrastructure/nft/helius-nft-indexer.js +1 -2
  189. package/dist/infrastructure/nft/helius-nft-indexer.js.map +1 -1
  190. package/dist/infrastructure/nft/nft-indexer-client.d.ts.map +1 -1
  191. package/dist/infrastructure/nft/nft-indexer-client.js +0 -2
  192. package/dist/infrastructure/nft/nft-indexer-client.js.map +1 -1
  193. package/dist/infrastructure/security/ssrf-guard.d.ts +33 -0
  194. package/dist/infrastructure/security/ssrf-guard.d.ts.map +1 -0
  195. package/dist/infrastructure/security/ssrf-guard.js +244 -0
  196. package/dist/infrastructure/security/ssrf-guard.js.map +1 -0
  197. package/dist/infrastructure/settings/hot-reload.d.ts +1 -1
  198. package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
  199. package/dist/infrastructure/settings/hot-reload.js +0 -2
  200. package/dist/infrastructure/settings/hot-reload.js.map +1 -1
  201. package/dist/infrastructure/settings/index.d.ts +2 -2
  202. package/dist/infrastructure/settings/index.d.ts.map +1 -1
  203. package/dist/infrastructure/settings/index.js +1 -1
  204. package/dist/infrastructure/settings/index.js.map +1 -1
  205. package/dist/infrastructure/settings/setting-keys.d.ts +14 -0
  206. package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
  207. package/dist/infrastructure/settings/setting-keys.js +296 -214
  208. package/dist/infrastructure/settings/setting-keys.js.map +1 -1
  209. package/dist/infrastructure/settings/settings-service.d.ts +6 -1
  210. package/dist/infrastructure/settings/settings-service.d.ts.map +1 -1
  211. package/dist/infrastructure/settings/settings-service.js +15 -5
  212. package/dist/infrastructure/settings/settings-service.js.map +1 -1
  213. package/dist/infrastructure/telegram/telegram-bot-service.d.ts.map +1 -1
  214. package/dist/infrastructure/telegram/telegram-bot-service.js +3 -2
  215. package/dist/infrastructure/telegram/telegram-bot-service.js.map +1 -1
  216. package/dist/infrastructure/token-registry/builtin-tokens.d.ts.map +1 -1
  217. package/dist/infrastructure/token-registry/builtin-tokens.js +4 -7
  218. package/dist/infrastructure/token-registry/builtin-tokens.js.map +1 -1
  219. package/dist/lifecycle/daemon-pipeline.d.ts +49 -0
  220. package/dist/lifecycle/daemon-pipeline.d.ts.map +1 -0
  221. package/dist/lifecycle/daemon-pipeline.js +281 -0
  222. package/dist/lifecycle/daemon-pipeline.js.map +1 -0
  223. package/dist/lifecycle/daemon-shutdown.d.ts +14 -0
  224. package/dist/lifecycle/daemon-shutdown.d.ts.map +1 -0
  225. package/dist/lifecycle/daemon-shutdown.js +176 -0
  226. package/dist/lifecycle/daemon-shutdown.js.map +1 -0
  227. package/dist/lifecycle/daemon-startup.d.ts +15 -0
  228. package/dist/lifecycle/daemon-startup.d.ts.map +1 -0
  229. package/dist/lifecycle/daemon-startup.js +1527 -0
  230. package/dist/lifecycle/daemon-startup.js.map +1 -0
  231. package/dist/lifecycle/daemon.d.ts +171 -114
  232. package/dist/lifecycle/daemon.d.ts.map +1 -1
  233. package/dist/lifecycle/daemon.js +22 -1904
  234. package/dist/lifecycle/daemon.js.map +1 -1
  235. package/dist/notifications/channels/discord.d.ts.map +1 -1
  236. package/dist/notifications/channels/discord.js +1 -0
  237. package/dist/notifications/channels/discord.js.map +1 -1
  238. package/dist/notifications/channels/slack.d.ts.map +1 -1
  239. package/dist/notifications/channels/slack.js +1 -0
  240. package/dist/notifications/channels/slack.js.map +1 -1
  241. package/dist/notifications/index.d.ts +0 -1
  242. package/dist/notifications/index.d.ts.map +1 -1
  243. package/dist/notifications/index.js +0 -1
  244. package/dist/notifications/index.js.map +1 -1
  245. package/dist/notifications/notification-service.d.ts.map +1 -1
  246. package/dist/notifications/notification-service.js +8 -6
  247. package/dist/notifications/notification-service.js.map +1 -1
  248. package/dist/pipeline/database-policy-engine.d.ts +18 -438
  249. package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
  250. package/dist/pipeline/database-policy-engine.js +154 -1321
  251. package/dist/pipeline/database-policy-engine.js.map +1 -1
  252. package/dist/pipeline/dry-run.d.ts +5 -2
  253. package/dist/pipeline/dry-run.d.ts.map +1 -1
  254. package/dist/pipeline/dry-run.js +102 -8
  255. package/dist/pipeline/dry-run.js.map +1 -1
  256. package/dist/pipeline/evaluators/allowed-tokens.d.ts +28 -0
  257. package/dist/pipeline/evaluators/allowed-tokens.d.ts.map +1 -0
  258. package/dist/pipeline/evaluators/allowed-tokens.js +129 -0
  259. package/dist/pipeline/evaluators/allowed-tokens.js.map +1 -0
  260. package/dist/pipeline/evaluators/approved-spenders.d.ts +26 -0
  261. package/dist/pipeline/evaluators/approved-spenders.d.ts.map +1 -0
  262. package/dist/pipeline/evaluators/approved-spenders.js +115 -0
  263. package/dist/pipeline/evaluators/approved-spenders.js.map +1 -0
  264. package/dist/pipeline/evaluators/contract-whitelist.d.ts +28 -0
  265. package/dist/pipeline/evaluators/contract-whitelist.d.ts.map +1 -0
  266. package/dist/pipeline/evaluators/contract-whitelist.js +168 -0
  267. package/dist/pipeline/evaluators/contract-whitelist.js.map +1 -0
  268. package/dist/pipeline/evaluators/helpers.d.ts +9 -0
  269. package/dist/pipeline/evaluators/helpers.d.ts.map +1 -0
  270. package/dist/pipeline/evaluators/helpers.js +13 -0
  271. package/dist/pipeline/evaluators/helpers.js.map +1 -0
  272. package/dist/pipeline/evaluators/lending-asset-whitelist.d.ts +18 -0
  273. package/dist/pipeline/evaluators/lending-asset-whitelist.d.ts.map +1 -0
  274. package/dist/pipeline/evaluators/lending-asset-whitelist.js +44 -0
  275. package/dist/pipeline/evaluators/lending-asset-whitelist.js.map +1 -0
  276. package/dist/pipeline/evaluators/lending-ltv-limit.d.ts +24 -0
  277. package/dist/pipeline/evaluators/lending-ltv-limit.d.ts.map +1 -0
  278. package/dist/pipeline/evaluators/lending-ltv-limit.js +130 -0
  279. package/dist/pipeline/evaluators/lending-ltv-limit.js.map +1 -0
  280. package/dist/pipeline/evaluators/spending-limit.d.ts +46 -0
  281. package/dist/pipeline/evaluators/spending-limit.d.ts.map +1 -0
  282. package/dist/pipeline/evaluators/spending-limit.js +241 -0
  283. package/dist/pipeline/evaluators/spending-limit.js.map +1 -0
  284. package/dist/pipeline/evaluators/types.d.ts +71 -0
  285. package/dist/pipeline/evaluators/types.d.ts.map +1 -0
  286. package/dist/pipeline/evaluators/types.js +7 -0
  287. package/dist/pipeline/evaluators/types.js.map +1 -0
  288. package/dist/pipeline/external-action-pipeline.js.map +1 -1
  289. package/dist/pipeline/gas-condition-tracker.d.ts +1 -1
  290. package/dist/pipeline/gas-condition-tracker.js +1 -1
  291. package/dist/pipeline/pipeline-helpers.d.ts +146 -0
  292. package/dist/pipeline/pipeline-helpers.d.ts.map +1 -0
  293. package/dist/pipeline/pipeline-helpers.js +260 -0
  294. package/dist/pipeline/pipeline-helpers.js.map +1 -0
  295. package/dist/pipeline/pipeline.d.ts +1 -0
  296. package/dist/pipeline/pipeline.d.ts.map +1 -1
  297. package/dist/pipeline/pipeline.js +3 -2
  298. package/dist/pipeline/pipeline.js.map +1 -1
  299. package/dist/pipeline/resolve-effective-amount-usd.d.ts.map +1 -1
  300. package/dist/pipeline/resolve-effective-amount-usd.js +4 -10
  301. package/dist/pipeline/resolve-effective-amount-usd.js.map +1 -1
  302. package/dist/pipeline/sign-message.js +1 -1
  303. package/dist/pipeline/sign-message.js.map +1 -1
  304. package/dist/pipeline/sleep.d.ts +1 -5
  305. package/dist/pipeline/sleep.d.ts.map +1 -1
  306. package/dist/pipeline/sleep.js +2 -7
  307. package/dist/pipeline/sleep.js.map +1 -1
  308. package/dist/pipeline/stage1-validate.d.ts +8 -0
  309. package/dist/pipeline/stage1-validate.d.ts.map +1 -0
  310. package/dist/pipeline/stage1-validate.js +69 -0
  311. package/dist/pipeline/stage1-validate.js.map +1 -0
  312. package/dist/pipeline/stage2-auth.d.ts +12 -0
  313. package/dist/pipeline/stage2-auth.d.ts.map +1 -0
  314. package/dist/pipeline/stage2-auth.js +18 -0
  315. package/dist/pipeline/stage2-auth.js.map +1 -0
  316. package/dist/pipeline/stage3-policy.d.ts +26 -0
  317. package/dist/pipeline/stage3-policy.d.ts.map +1 -0
  318. package/dist/pipeline/stage3-policy.js +384 -0
  319. package/dist/pipeline/stage3-policy.js.map +1 -0
  320. package/dist/pipeline/stage4-wait.d.ts +8 -0
  321. package/dist/pipeline/stage4-wait.d.ts.map +1 -0
  322. package/dist/pipeline/stage4-wait.js +87 -0
  323. package/dist/pipeline/stage4-wait.js.map +1 -0
  324. package/dist/pipeline/stage5-execute.d.ts +120 -0
  325. package/dist/pipeline/stage5-execute.d.ts.map +1 -0
  326. package/dist/pipeline/stage5-execute.js +1070 -0
  327. package/dist/pipeline/stage5-execute.js.map +1 -0
  328. package/dist/pipeline/stage6-confirm.d.ts +11 -0
  329. package/dist/pipeline/stage6-confirm.d.ts.map +1 -0
  330. package/dist/pipeline/stage6-confirm.js +110 -0
  331. package/dist/pipeline/stage6-confirm.js.map +1 -0
  332. package/dist/pipeline/stages.d.ts +11 -245
  333. package/dist/pipeline/stages.d.ts.map +1 -1
  334. package/dist/pipeline/stages.js +11 -1896
  335. package/dist/pipeline/stages.js.map +1 -1
  336. package/dist/rpc-proxy/sync-pipeline.js +2 -2
  337. package/dist/rpc-proxy/sync-pipeline.js.map +1 -1
  338. package/dist/services/autostop/autostop-service.d.ts +4 -1
  339. package/dist/services/autostop/autostop-service.d.ts.map +1 -1
  340. package/dist/services/autostop/autostop-service.js +27 -7
  341. package/dist/services/autostop/autostop-service.js.map +1 -1
  342. package/dist/services/defi/position-tracker.d.ts +5 -0
  343. package/dist/services/defi/position-tracker.d.ts.map +1 -1
  344. package/dist/services/defi/position-tracker.js +41 -6
  345. package/dist/services/defi/position-tracker.js.map +1 -1
  346. package/dist/services/defi/position-write-queue.d.ts.map +1 -1
  347. package/dist/services/defi/position-write-queue.js +3 -2
  348. package/dist/services/defi/position-write-queue.js.map +1 -1
  349. package/dist/services/incoming/__tests__/integration-wiring.test.js +58 -0
  350. package/dist/services/incoming/__tests__/integration-wiring.test.js.map +1 -1
  351. package/dist/services/incoming/incoming-tx-monitor-service.d.ts.map +1 -1
  352. package/dist/services/incoming/incoming-tx-monitor-service.js +11 -14
  353. package/dist/services/incoming/incoming-tx-monitor-service.js.map +1 -1
  354. package/dist/services/incoming/incoming-tx-workers.d.ts +2 -2
  355. package/dist/services/incoming/incoming-tx-workers.d.ts.map +1 -1
  356. package/dist/services/incoming/incoming-tx-workers.js +1 -1
  357. package/dist/services/incoming/incoming-tx-workers.js.map +1 -1
  358. package/dist/services/incoming/safety-rules.d.ts.map +1 -1
  359. package/dist/services/incoming/safety-rules.js +3 -2
  360. package/dist/services/incoming/safety-rules.js.map +1 -1
  361. package/dist/services/incoming/subscription-multiplexer.d.ts +2 -6
  362. package/dist/services/incoming/subscription-multiplexer.d.ts.map +1 -1
  363. package/dist/services/incoming/subscription-multiplexer.js +1 -3
  364. package/dist/services/incoming/subscription-multiplexer.js.map +1 -1
  365. package/dist/services/monitoring/balance-monitor-service.d.ts.map +1 -1
  366. package/dist/services/monitoring/balance-monitor-service.js +2 -2
  367. package/dist/services/monitoring/balance-monitor-service.js.map +1 -1
  368. package/dist/services/signing-sdk/approval-channel-router.d.ts +7 -7
  369. package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
  370. package/dist/services/signing-sdk/approval-channel-router.js +13 -13
  371. package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
  372. package/dist/services/signing-sdk/channels/index.d.ts +2 -2
  373. package/dist/services/signing-sdk/channels/index.d.ts.map +1 -1
  374. package/dist/services/signing-sdk/channels/index.js +1 -1
  375. package/dist/services/signing-sdk/channels/index.js.map +1 -1
  376. package/dist/services/signing-sdk/channels/push-relay-signing-channel.d.ts +59 -0
  377. package/dist/services/signing-sdk/channels/push-relay-signing-channel.d.ts.map +1 -0
  378. package/dist/services/signing-sdk/channels/push-relay-signing-channel.js +190 -0
  379. package/dist/services/signing-sdk/channels/push-relay-signing-channel.js.map +1 -0
  380. package/dist/services/signing-sdk/channels/telegram-signing-channel.d.ts +1 -1
  381. package/dist/services/signing-sdk/channels/telegram-signing-channel.js +1 -1
  382. package/dist/services/signing-sdk/channels/wallet-notification-channel.d.ts +6 -7
  383. package/dist/services/signing-sdk/channels/wallet-notification-channel.d.ts.map +1 -1
  384. package/dist/services/signing-sdk/channels/wallet-notification-channel.js +38 -55
  385. package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
  386. package/dist/services/signing-sdk/index.d.ts +3 -3
  387. package/dist/services/signing-sdk/index.d.ts.map +1 -1
  388. package/dist/services/signing-sdk/index.js +2 -2
  389. package/dist/services/signing-sdk/index.js.map +1 -1
  390. package/dist/services/signing-sdk/preset-auto-setup.js +2 -2
  391. package/dist/services/signing-sdk/preset-auto-setup.js.map +1 -1
  392. package/dist/services/signing-sdk/sign-request-builder.d.ts +2 -2
  393. package/dist/services/signing-sdk/sign-request-builder.d.ts.map +1 -1
  394. package/dist/services/signing-sdk/sign-request-builder.js +17 -25
  395. package/dist/services/signing-sdk/sign-request-builder.js.map +1 -1
  396. package/dist/services/signing-sdk/wallet-app-service.d.ts +4 -0
  397. package/dist/services/signing-sdk/wallet-app-service.d.ts.map +1 -1
  398. package/dist/services/signing-sdk/wallet-app-service.js +12 -5
  399. package/dist/services/signing-sdk/wallet-app-service.js.map +1 -1
  400. package/dist/services/staking/aggregate-staking-balance.d.ts +24 -0
  401. package/dist/services/staking/aggregate-staking-balance.d.ts.map +1 -0
  402. package/dist/services/staking/aggregate-staking-balance.js +82 -0
  403. package/dist/services/staking/aggregate-staking-balance.js.map +1 -0
  404. package/dist/services/wc-session-service.d.ts.map +1 -1
  405. package/dist/services/wc-session-service.js +2 -1
  406. package/dist/services/wc-session-service.js.map +1 -1
  407. package/dist/services/wc-signing-bridge.js +2 -2
  408. package/dist/services/wc-signing-bridge.js.map +1 -1
  409. package/dist/services/x402/payment-signer.d.ts.map +1 -1
  410. package/dist/services/x402/payment-signer.js +2 -5
  411. package/dist/services/x402/payment-signer.js.map +1 -1
  412. package/dist/services/x402/ssrf-guard.d.ts +4 -23
  413. package/dist/services/x402/ssrf-guard.d.ts.map +1 -1
  414. package/dist/services/x402/ssrf-guard.js +3 -232
  415. package/dist/services/x402/ssrf-guard.js.map +1 -1
  416. package/dist/signing/capabilities/eip712-signer.d.ts.map +1 -1
  417. package/dist/signing/capabilities/eip712-signer.js +2 -0
  418. package/dist/signing/capabilities/eip712-signer.js.map +1 -1
  419. package/package.json +5 -5
  420. package/public/admin/assets/index-CpFF2lCo.js +3 -0
  421. package/public/admin/index.html +1 -1
  422. package/dist/notifications/channels/ntfy.d.ts +0 -13
  423. package/dist/notifications/channels/ntfy.d.ts.map +0 -1
  424. package/dist/notifications/channels/ntfy.js +0 -74
  425. package/dist/notifications/channels/ntfy.js.map +0 -1
  426. package/dist/services/signing-sdk/channels/ntfy-signing-channel.d.ts +0 -66
  427. package/dist/services/signing-sdk/channels/ntfy-signing-channel.d.ts.map +0 -1
  428. package/dist/services/signing-sdk/channels/ntfy-signing-channel.js +0 -270
  429. package/dist/services/signing-sdk/channels/ntfy-signing-channel.js.map +0 -1
  430. package/public/admin/assets/index-CQ3i4P2U.js +0 -3
@@ -1,57 +1,27 @@
1
1
  /**
2
2
  * DaemonLifecycle - orchestrates daemon startup (6 steps) and shutdown (10 steps).
3
3
  *
4
- * Startup sequence (doc 28 section 2):
5
- * 1. Environment validation + config + flock (5s timeout, fail-fast)
6
- * 2. Database initialization (30s timeout, fail-fast)
7
- * 3. Keystore unlock (30s timeout, fail-fast)
8
- * 4. Adapter initialization (10s, fail-soft)
9
- * 4c-6. WalletConnect service (fail-soft)
10
- * 4c-8. Signing SDK lifecycle (fail-soft)
11
- * 4c-9. IncomingTxMonitorService (fail-soft)
12
- * 4c-10. AsyncPollingService (fail-soft)
13
- * 4c-10.5. PositionTracker (fail-soft)
14
- * 4c-11. DeFiMonitorService (fail-soft)
15
- * 4h. EncryptedBackupService (fail-soft)
16
- * 4i. WebhookService (fail-soft)
17
- * 5. HTTP server start (5s, fail-fast)
18
- * 6. Background workers + PID (no timeout, fail-soft)
19
- *
20
- * Shutdown sequence (doc 28 section 3):
21
- * 1. Set isShuttingDown, start force timer, log signal
22
- * 2-4. HTTP server close
23
- * 5. In-flight signing -- STUB (Phase 50-04)
24
- * 6. Pending queue persistence -- STUB (Phase 50-04)
25
- * 6a. Stop PositionTracker, DeFiMonitorService, TelegramBot, WcSessionService, AutoStop, BalanceMonitor, IncomingTxMonitor, WebhookService
26
- * 6b. Remove all EventBus listeners
27
- * 7. workers.stopAll()
28
- * 8. WAL checkpoint(TRUNCATE)
29
- * 9. keyStore.lockAll()
30
- * 10. sqlite.close(), unlink PID, close lockFd, process.exit(0)
4
+ * This file contains the class shell with field declarations, getters, and thin
5
+ * method wrappers. The actual startup/shutdown/pipeline logic is in:
6
+ * - daemon-startup.ts (6-step startup sequence)
7
+ * - daemon-shutdown.ts (10-step shutdown cascade)
8
+ * - daemon-pipeline.ts (pipeline re-entry: stage4, stage5, approval handling)
31
9
  *
32
10
  * @see docs/28-daemon-lifecycle-cli.md
33
11
  */
34
- import { writeFileSync, unlinkSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
35
- import { createRequire } from 'node:module';
36
- import { join, dirname } from 'node:path';
37
- import { WAIaaSError, getSingleNetwork, EventBus, RpcPool, BUILT_IN_RPC_DEFAULTS } from '@waiaas/core';
12
+ import { writeFileSync, existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { WAIaaSError, EventBus, RpcPool } from '@waiaas/core';
15
+ import { DelayQueue } from '../workflow/delay-queue.js';
16
+ import { ApprovalWorkflow } from '../workflow/approval-workflow.js';
17
+ import { JwtSecretManager } from '../infrastructure/jwt/index.js';
38
18
  import { KillSwitchService } from '../services/kill-switch-service.js';
39
19
  import { AutoStopService } from '../services/autostop-service.js';
40
20
  import { InMemoryCounter } from '../infrastructure/metrics/in-memory-counter.js';
41
21
  import { AdminStatsService } from '../services/admin-stats-service.js';
42
- import { resolveRpcUrl, resolveRpcUrlFromPool } from '../infrastructure/adapter-pool.js';
43
- import { createDatabase, pushSchema, checkSchemaCompatibility } from '../infrastructure/database/index.js';
44
- import { loadConfig } from '../infrastructure/config/index.js';
45
- import { BackgroundWorkers } from './workers.js';
46
- import { keyValueStore, transactions as txTable } from '../infrastructure/database/schema.js';
47
- import { eq } from 'drizzle-orm';
48
- import { decrypt } from '../infrastructure/keystore/crypto.js';
49
- import { DelayQueue } from '../workflow/delay-queue.js';
50
- import { ApprovalWorkflow } from '../workflow/approval-workflow.js';
51
- import { DatabasePolicyEngine } from '../pipeline/database-policy-engine.js';
52
- import { JwtSecretManager } from '../infrastructure/jwt/index.js';
53
- import argon2 from 'argon2';
54
- const esmRequire = createRequire(import.meta.url);
22
+ import { startDaemon } from './daemon-startup.js';
23
+ import { shutdownDaemon } from './daemon-shutdown.js';
24
+ import { executeFromStage4 as pipelineExecuteFromStage4, executeFromStage5 as pipelineExecuteFromStage5, handleApprovalApproved as pipelineHandleApprovalApproved, } from './daemon-pipeline.js';
55
25
  let _lockfile = null;
56
26
  async function getLockfile() {
57
27
  if (_lockfile)
@@ -66,7 +36,7 @@ async function getLockfile() {
66
36
  /**
67
37
  * Race a promise against a timeout. Rejects with WAIaaSError on timeout.
68
38
  */
69
- function withTimeout(promise, ms, errorCode) {
39
+ export function withTimeout(promise, ms, errorCode) {
70
40
  return new Promise((resolve, reject) => {
71
41
  const timer = setTimeout(() => {
72
42
  reject(new WAIaaSError('SYSTEM_LOCKED', {
@@ -82,8 +52,6 @@ function withTimeout(promise, ms, errorCode) {
82
52
  });
83
53
  });
84
54
  }
85
- // Export for testing
86
- export { withTimeout };
87
55
  // ---------------------------------------------------------------------------
88
56
  // DaemonLifecycle
89
57
  // ---------------------------------------------------------------------------
@@ -95,7 +63,6 @@ export class DaemonLifecycle {
95
63
  masterPassword = '';
96
64
  passwordRef = null;
97
65
  rpcPool = null;
98
- /** IRpcCaller for Aave V3 on-chain reads, passed to HotReloadOrchestrator */
99
66
  rpcCaller;
100
67
  adapterPool = null;
101
68
  httpServer = null;
@@ -112,7 +79,6 @@ export class DaemonLifecycle {
112
79
  _settingsService = null;
113
80
  priceOracle;
114
81
  actionProviderRegistry = null;
115
- // apiKeyStore removed in v29.5 (#214) -- API keys now managed by SettingsService
116
82
  forexRateService = null;
117
83
  eventBus = new EventBus();
118
84
  killSwitchService = null;
@@ -135,6 +101,7 @@ export class DaemonLifecycle {
135
101
  adminStatsService = null;
136
102
  hyperliquidMarketData = null;
137
103
  polymarketInfra = null;
104
+ contractNameRegistry = null;
138
105
  daemonStartTime = Math.floor(Date.now() / 1000);
139
106
  /** Whether shutdown has been initiated. */
140
107
  get isShuttingDown() {
@@ -172,1880 +139,31 @@ export class DaemonLifecycle {
172
139
  * 6-step startup sequence with per-step timeouts and 90s overall cap.
173
140
  */
174
141
  async start(dataDir, masterPassword) {
175
- // Wrap everything in a 90-second overall timeout
176
- await withTimeout(this._startInternal(dataDir, masterPassword), 90_000, 'STARTUP_TIMEOUT');
177
- }
178
- async _startInternal(dataDir, masterPassword) {
179
- // Store master password for route handlers
180
- this.masterPassword = masterPassword;
181
- // ------------------------------------------------------------------
182
- // Step 1: Environment validation + config + flock (5s, fail-fast)
183
- // ------------------------------------------------------------------
184
- await withTimeout((async () => {
185
- // Ensure data directory exists
186
- if (!existsSync(dataDir)) {
187
- mkdirSync(dataDir, { recursive: true });
188
- }
189
- // Load config
190
- this._config = loadConfig(dataDir);
191
- // Acquire daemon lock (flock-like via proper-lockfile)
192
- await this.acquireDaemonLock(dataDir);
193
- console.debug('Step 1: Config loaded, daemon lock acquired');
194
- })(), 5_000, 'STEP1_CONFIG_LOCK');
195
- // ------------------------------------------------------------------
196
- // Step 2: Database initialization (30s, fail-fast)
197
- // ------------------------------------------------------------------
198
- await withTimeout((async () => {
199
- const dbPath = join(dataDir, this._config.database.path);
200
- // Ensure DB directory exists
201
- const dbDir = dirname(dbPath);
202
- if (!existsSync(dbDir)) {
203
- mkdirSync(dbDir, { recursive: true });
204
- }
205
- const { sqlite, db } = createDatabase(dbPath);
206
- this.sqlite = sqlite;
207
- this._db = db;
208
- // Check schema compatibility before migration
209
- const compatibility = checkSchemaCompatibility(sqlite);
210
- if (compatibility.action === 'reject') {
211
- console.error(`Step 2: Schema incompatible -- ${compatibility.message}`);
212
- throw new WAIaaSError('SCHEMA_INCOMPATIBLE', {
213
- message: compatibility.message,
214
- });
215
- }
216
- if (compatibility.action === 'migrate') {
217
- console.debug('Step 2: Schema migration needed, applying...');
218
- }
219
- // Create all tables + run migrations (idempotent)
220
- pushSchema(sqlite);
221
- // Auto-import config.toml operational settings into DB (first boot only)
222
- const { SettingsService } = await import('../infrastructure/settings/index.js');
223
- this._settingsService = new SettingsService({
224
- db: this._db,
225
- config: this._config,
226
- masterPassword,
227
- passwordRef: this.passwordRef ?? undefined,
228
- });
229
- const importResult = this._settingsService.importFromConfig();
230
- if (importResult.imported > 0) {
231
- console.debug(`Step 2: Settings imported from config.toml (${importResult.imported} keys)`);
232
- }
233
- console.debug('Step 2: Database initialized');
234
- })(), 30_000, 'STEP2_DATABASE');
235
- // ------------------------------------------------------------------
236
- // Step 2b: Master password validation (fail-fast)
237
- // ------------------------------------------------------------------
238
- await withTimeout((async () => {
239
- const existingHash = this._db
240
- .select()
241
- .from(keyValueStore)
242
- .where(eq(keyValueStore.key, 'master_password_hash'))
243
- .get();
244
- if (existingHash) {
245
- // Path A: DB hash exists -> verify against stored hash
246
- const isValid = await argon2.verify(existingHash.value, masterPassword);
247
- if (!isValid) {
248
- console.error('Invalid master password.');
249
- process.exit(1);
250
- }
251
- console.debug('Step 2b: Master password verified (DB hash)');
252
- }
253
- else {
254
- // Path B: No DB hash -> check for existing keystore files
255
- const keystoreDir = join(dataDir, 'keystore');
256
- const keystoreFiles = existsSync(keystoreDir)
257
- ? readdirSync(keystoreDir).filter(f => f.endsWith('.json'))
258
- : [];
259
- if (keystoreFiles.length > 0) {
260
- // Existing user migration: validate by decrypting first keystore
261
- const keystorePath = join(keystoreDir, keystoreFiles[0]);
262
- const content = readFileSync(keystorePath, 'utf-8');
263
- const parsed = JSON.parse(content);
264
- const encrypted = {
265
- iv: Buffer.from(parsed.crypto.cipherparams.iv, 'hex'),
266
- ciphertext: Buffer.from(parsed.crypto.ciphertext, 'hex'),
267
- authTag: Buffer.from(parsed.crypto.authTag, 'hex'),
268
- salt: Buffer.from(parsed.crypto.kdfparams.salt, 'hex'),
269
- kdfparams: parsed.crypto.kdfparams,
270
- };
271
- try {
272
- const plain = await decrypt(encrypted, masterPassword);
273
- plain.fill(0); // zero immediately
274
- }
275
- catch {
276
- console.error('Invalid master password. Cannot decrypt existing wallets.');
277
- process.exit(1);
278
- }
279
- console.debug('Step 2b: Master password verified (keystore migration)');
280
- }
281
- else {
282
- console.debug('Step 2b: First install, no password validation needed');
283
- }
284
- // Store hash in DB for future startups
285
- const hash = await argon2.hash(masterPassword, {
286
- type: argon2.argon2id,
287
- memoryCost: 19456,
288
- timeCost: 2,
289
- parallelism: 1,
290
- });
291
- this._db
292
- .insert(keyValueStore)
293
- .values({
294
- key: 'master_password_hash',
295
- value: hash,
296
- updatedAt: new Date(),
297
- })
298
- .onConflictDoNothing()
299
- .run();
300
- }
301
- })(), 30_000, 'STEP2B_PASSWORD_VALIDATION');
302
- // ------------------------------------------------------------------
303
- // Step 3: Keystore unlock (30s, fail-fast)
304
- // ------------------------------------------------------------------
305
- await withTimeout((async () => {
306
- // Dynamic import to avoid circular dependency issues
307
- const { LocalKeyStore: KeyStoreCls } = await import('../infrastructure/keystore/index.js');
308
- const keystoreDir = join(dataDir, 'keystore');
309
- if (!existsSync(keystoreDir)) {
310
- mkdirSync(keystoreDir, { recursive: true });
311
- }
312
- this.keyStore = new KeyStoreCls(keystoreDir);
313
- // v1.1: just verify keystore infrastructure is accessible
314
- // Full key decryption happens when agents are accessed
315
- if (masterPassword) {
316
- console.debug('Step 3: Keystore infrastructure verified (master password provided)');
317
- }
318
- else {
319
- console.debug('Step 3: Keystore infrastructure verified (no master password)');
320
- }
321
- })(), 30_000, 'STEP3_KEYSTORE');
322
- // ------------------------------------------------------------------
323
- // Step 4: Adapter pool initialization (10s, fail-soft)
324
- // ------------------------------------------------------------------
325
- try {
326
- await withTimeout((async () => {
327
- const { AdapterPool, configKeyToNetwork: configKeyToNet } = await import('../infrastructure/adapter-pool.js');
328
- // 1. Create empty RpcPool with onEvent callback for notifications
329
- this.rpcPool = new RpcPool({
330
- onEvent: (event) => {
331
- // RPC pool health notifications -- use 'system' as walletId
332
- // since these are infrastructure-level alerts, not wallet-specific.
333
- if (this.notificationService) {
334
- const vars = {
335
- network: event.network,
336
- url: event.url,
337
- errorCount: String(event.failureCount),
338
- totalEndpoints: String(event.totalEndpoints),
339
- };
340
- void this.notificationService.notify(event.type, 'system', vars);
341
- }
342
- },
343
- });
344
- // 2. Seed config.toml URLs first (highest priority)
345
- // WAIAAS_RPC_* env vars are already applied to config.rpc by applyEnvOverrides in loader.ts
346
- const rpcConfig = this._config.rpc;
347
- for (const [configKey, url] of Object.entries(rpcConfig)) {
348
- if (typeof url !== 'string' || !url)
349
- continue;
350
- const network = configKeyToNet(configKey);
351
- if (network) {
352
- this.rpcPool.register(network, [url]);
353
- }
354
- }
355
- // 3. Register built-in defaults (lower priority, appended after config URLs)
356
- for (const [network, urls] of Object.entries(BUILT_IN_RPC_DEFAULTS)) {
357
- this.rpcPool.register(network, [...urls]);
358
- }
359
- // 4. Create AdapterPool with RpcPool
360
- this.adapterPool = new AdapterPool(this.rpcPool);
361
- console.debug(`Step 4: AdapterPool created with RpcPool (${this.rpcPool.getNetworks().length} networks seeded)`);
362
- })(), 10_000, 'STEP4_ADAPTER');
363
- }
364
- catch (err) {
365
- // fail-soft: log warning but continue (daemon runs without chain adapter)
366
- console.warn('Step 4 (fail-soft): AdapterPool init warning:', err);
367
- this.adapterPool = null;
368
- this.rpcPool = null;
369
- }
370
- // ------------------------------------------------------------------
371
- // Step 4b: Create workflow instances (DelayQueue + ApprovalWorkflow)
372
- // ------------------------------------------------------------------
373
- if (this._db && this.sqlite && this._config) {
374
- this.delayQueue = new DelayQueue({ db: this._db, sqlite: this.sqlite });
375
- this.approvalWorkflow = new ApprovalWorkflow({
376
- db: this._db,
377
- sqlite: this.sqlite,
378
- config: {
379
- policy_defaults_approval_timeout: this._config.security.policy_defaults_approval_timeout,
380
- },
381
- onApproved: (txId) => this.handleApprovalApproved(txId),
382
- });
383
- console.debug('Step 4b: Workflow instances created (DelayQueue + ApprovalWorkflow)');
384
- }
385
- // ------------------------------------------------------------------
386
- // Step 4c: JWT Secret Manager + master password hash
387
- // ------------------------------------------------------------------
388
- if (this._db) {
389
- this.jwtSecretManager = new JwtSecretManager(this._db);
390
- await this.jwtSecretManager.initialize();
391
- this.masterPasswordHash = await argon2.hash(masterPassword, {
392
- type: argon2.argon2id,
393
- memoryCost: 19456,
394
- timeCost: 2,
395
- parallelism: 1,
396
- });
397
- // Create mutable ref for live password/hash updates (password change API)
398
- this.passwordRef = { password: masterPassword, hash: this.masterPasswordHash };
399
- console.debug('Step 4c: JWT secret manager initialized, master password hashed');
400
- }
401
- // ------------------------------------------------------------------
402
- // Step 4c-2: KillSwitchService initialization
403
- // ------------------------------------------------------------------
404
- if (this.sqlite) {
405
- this.killSwitchService = new KillSwitchService({
406
- sqlite: this.sqlite,
407
- // notificationService will be set after Step 4d
408
- eventBus: this.eventBus,
409
- });
410
- this.killSwitchService.ensureInitialized();
411
- console.debug('Step 4c-2: KillSwitchService initialized');
412
- }
413
- // ------------------------------------------------------------------
414
- // Step 4d: Notification Service initialization (fail-soft)
415
- // ------------------------------------------------------------------
416
- try {
417
- // Always create NotificationService regardless of config.toml enabled value.
418
- // When enabled=false, service starts with 0 channels (no notifications sent).
419
- // Admin UI can dynamically enable via hot-reload at runtime.
420
- const { NotificationService, TelegramChannel, DiscordChannel, SlackChannel } = await import('../notifications/index.js');
421
- // Read notification settings from SettingsService first, fall back to config.toml
422
- const ss = this._settingsService;
423
- const notifLocale = ((ss ? ss.get('notifications.locale') : null)
424
- || this._config.notifications.locale || 'en');
425
- const notifRateLimitRpm = Number((ss ? ss.get('notifications.rate_limit_rpm') : null)
426
- || this._config.notifications.rate_limit_rpm
427
- || 20);
428
- this.notificationService = new NotificationService({
429
- db: this._db ?? undefined,
430
- config: {
431
- locale: notifLocale,
432
- rateLimitRpm: notifRateLimitRpm,
433
- },
434
- });
435
- // Inject SettingsService for category filtering
436
- if (ss) {
437
- this.notificationService.setSettingsService(ss);
438
- }
439
- // Initialize configured channels: SettingsService (DB) takes priority over config.toml
440
- const notifEnabled = ss
441
- ? ss.get('notifications.enabled') === 'true'
442
- : this._config.notifications.enabled;
443
- if (notifEnabled) {
444
- const notifConfig = this._config.notifications;
445
- const tgToken = (ss ? ss.get('notifications.telegram_bot_token') : null)
446
- || notifConfig.telegram_bot_token;
447
- const tgChatId = (ss ? ss.get('notifications.telegram_chat_id') : null)
448
- || notifConfig.telegram_chat_id;
449
- if (tgToken && tgChatId) {
450
- const telegram = new TelegramChannel();
451
- await telegram.initialize({
452
- telegram_bot_token: tgToken,
453
- telegram_chat_id: tgChatId,
454
- });
455
- this.notificationService.addChannel(telegram);
456
- }
457
- const discordUrl = (ss ? ss.get('notifications.discord_webhook_url') : null)
458
- || notifConfig.discord_webhook_url;
459
- if (discordUrl) {
460
- const discord = new DiscordChannel();
461
- await discord.initialize({
462
- discord_webhook_url: discordUrl,
463
- });
464
- this.notificationService.addChannel(discord);
465
- }
466
- // Global NtfyChannel removed in v29.10 -- per-wallet topics now in wallet_apps table.
467
- // Per-wallet ntfy channels are managed by the signing SDK / notification routing layer.
468
- const slackUrl = (ss ? ss.get('notifications.slack_webhook_url') : null)
469
- || notifConfig.slack_webhook_url;
470
- if (slackUrl) {
471
- const slack = new SlackChannel();
472
- await slack.initialize({
473
- slack_webhook_url: slackUrl,
474
- });
475
- this.notificationService.addChannel(slack);
476
- }
477
- }
478
- const channelNames = this.notificationService.getChannelNames();
479
- console.debug(`Step 4d: NotificationService initialized (${channelNames.length} channels: ${channelNames.join(', ') || 'none'})`);
480
- }
481
- catch (err) {
482
- console.warn('Step 4d (fail-soft): NotificationService init warning:', err);
483
- this.notificationService = null;
484
- }
485
- // Wire NotificationService to KillSwitchService (created before Step 4d)
486
- if (this.killSwitchService && this.notificationService) {
487
- // Re-create with notification service attached
488
- this.killSwitchService = new KillSwitchService({
489
- sqlite: this.sqlite,
490
- notificationService: this.notificationService,
491
- eventBus: this.eventBus,
492
- });
493
- this.killSwitchService.ensureInitialized();
494
- }
495
- // ------------------------------------------------------------------
496
- // Step 4c-3: AutoStop Engine (fail-soft)
497
- // ------------------------------------------------------------------
498
- try {
499
- if (this.sqlite && this.killSwitchService && this._settingsService) {
500
- const autoStopConfig = {
501
- consecutiveFailuresThreshold: parseInt(this._settingsService.get('autostop.consecutive_failures_threshold'), 10),
502
- unusualActivityThreshold: parseInt(this._settingsService.get('autostop.unusual_activity_threshold'), 10),
503
- unusualActivityWindowSec: parseInt(this._settingsService.get('autostop.unusual_activity_window_sec'), 10),
504
- idleTimeoutSec: parseInt(this._settingsService.get('autostop.idle_timeout_sec'), 10),
505
- idleCheckIntervalSec: parseInt(this._settingsService.get('autostop.idle_check_interval_sec'), 10),
506
- enabled: this._settingsService.get('autostop.enabled') === 'true',
507
- };
508
- this.autoStopService = new AutoStopService({
509
- sqlite: this.sqlite,
510
- eventBus: this.eventBus,
511
- killSwitchService: this.killSwitchService,
512
- notificationService: this.notificationService ?? undefined,
513
- config: autoStopConfig,
514
- });
515
- if (autoStopConfig.enabled) {
516
- this.autoStopService.start();
517
- console.debug('Step 4c-3: AutoStop engine started');
518
- }
519
- else {
520
- console.debug('Step 4c-3: AutoStop engine disabled');
521
- }
522
- }
523
- }
524
- catch (err) {
525
- console.warn('Step 4c-3 (fail-soft): AutoStop engine init warning:', err);
526
- this.autoStopService = null;
527
- }
528
- // ------------------------------------------------------------------
529
- // Step 4c-3b: InMemoryCounter + AdminStatsService (fail-soft)
530
- // ------------------------------------------------------------------
531
- try {
532
- if (this.sqlite) {
533
- this.metricsCounter = new InMemoryCounter();
534
- // v30.2: inject metricsCounter into AutoStopService (STAT-02)
535
- if (this.autoStopService) {
536
- this.autoStopService.setMetricsCounter(this.metricsCounter);
537
- }
538
- const { version: daemonVersion } = esmRequire('../../package.json');
539
- this.adminStatsService = new AdminStatsService({
540
- sqlite: this.sqlite,
541
- metricsCounter: this.metricsCounter,
542
- autoStopService: this.autoStopService ?? undefined,
543
- startTime: this.daemonStartTime,
544
- version: daemonVersion,
545
- dataDir,
546
- });
547
- console.debug('Step 4c-3b: AdminStatsService created');
548
- }
549
- }
550
- catch (err) {
551
- console.warn('Step 4c-3b (fail-soft): AdminStatsService init warning:', err);
552
- this.adminStatsService = null;
553
- }
554
- // ------------------------------------------------------------------
555
- // Step 4c-4: BalanceMonitorService initialization (fail-soft)
556
- // ------------------------------------------------------------------
557
- try {
558
- if (this.sqlite && this.adapterPool && this._config && this._settingsService) {
559
- const { BalanceMonitorService: BalanceMonitorCls } = await import('../services/monitoring/balance-monitor-service.js');
560
- const monitorConfig = {
561
- checkIntervalSec: parseInt(this._settingsService.get('monitoring.check_interval_sec'), 10),
562
- lowBalanceThresholdSol: parseFloat(this._settingsService.get('monitoring.low_balance_threshold_sol')),
563
- lowBalanceThresholdEth: parseFloat(this._settingsService.get('monitoring.low_balance_threshold_eth')),
564
- cooldownHours: parseInt(this._settingsService.get('monitoring.cooldown_hours'), 10),
565
- enabled: this._settingsService.get('monitoring.enabled') === 'true',
566
- };
567
- this.balanceMonitorService = new BalanceMonitorCls({
568
- sqlite: this.sqlite,
569
- adapterPool: this.adapterPool,
570
- config: this._config,
571
- notificationService: this.notificationService ?? undefined,
572
- monitorConfig,
573
- });
574
- if (monitorConfig.enabled) {
575
- this.balanceMonitorService.start();
576
- console.debug('Step 4c-4: Balance monitor started');
577
- }
578
- else {
579
- console.debug('Step 4c-4: Balance monitor disabled');
580
- }
581
- }
582
- }
583
- catch (err) {
584
- console.warn('Step 4c-4 (fail-soft): Balance monitor init warning:', err);
585
- this.balanceMonitorService = null;
586
- }
587
- // ------------------------------------------------------------------
588
- // Step 4c-5: TelegramBotService initialization (fail-soft)
589
- // ------------------------------------------------------------------
590
- try {
591
- // Read telegram settings from SettingsService (falls back to config.toml)
592
- const ss = this._settingsService;
593
- // Token priority: telegram.bot_token > notifications.telegram_bot_token > config.toml
594
- const botToken = (ss ? (ss.get('telegram.bot_token') || ss.get('notifications.telegram_bot_token')) : null)
595
- || this._config.telegram.bot_token;
596
- if (botToken) {
597
- const { TelegramBotService, TelegramApi } = await import('../infrastructure/telegram/index.js');
598
- const telegramApi = new TelegramApi(botToken);
599
- const telegramLocale = ((ss ? ss.get('telegram.locale') : null)
600
- || this._config.telegram.locale
601
- || this._config.notifications.locale
602
- || 'en');
603
- this.telegramBotService = new TelegramBotService({
604
- sqlite: this.sqlite,
605
- api: telegramApi,
606
- locale: telegramLocale,
607
- killSwitchService: this.killSwitchService ?? undefined,
608
- notificationService: this.notificationService ?? undefined,
609
- settingsService: this._settingsService ?? undefined,
610
- onApproved: (txId) => this.handleApprovalApproved(txId),
611
- });
612
- this.telegramBotService.start();
613
- this.telegramBotRef.current = this.telegramBotService;
614
- console.debug('Step 4c-5: Telegram Bot started');
615
- }
616
- else {
617
- console.debug('Step 4c-5: Telegram Bot disabled');
618
- }
619
- }
620
- catch (err) {
621
- console.warn('Step 4c-5 (fail-soft): Telegram Bot init warning:', err);
622
- this.telegramBotService = null;
623
- this.telegramBotRef.current = null;
624
- }
625
- // ------------------------------------------------------------------
626
- // Step 4c-6: WalletConnect service initialization (fail-soft)
627
- // ------------------------------------------------------------------
628
- try {
629
- const wcProjectId = this._settingsService?.get('walletconnect.project_id');
630
- if (wcProjectId) {
631
- const { WcSessionService } = await import('../services/wc-session-service.js');
632
- this.wcSessionService = new WcSessionService({
633
- sqlite: this.sqlite,
634
- settingsService: this._settingsService,
635
- });
636
- await this.wcSessionService.initialize();
637
- this.wcServiceRef.current = this.wcSessionService;
638
- console.debug('Step 4c-6: WalletConnect service initialized');
639
- }
640
- else {
641
- console.debug('Step 4c-6: WalletConnect disabled (no project_id)');
642
- }
643
- }
644
- catch (err) {
645
- console.warn('Step 4c-6 (fail-soft): WalletConnect init warning:', err);
646
- this.wcSessionService = null;
647
- this.wcServiceRef.current = null;
648
- }
649
- // ------------------------------------------------------------------
650
- // Step 4c-7: WcSigningBridge (fail-soft, requires WcSessionService + ApprovalWorkflow)
651
- // ------------------------------------------------------------------
652
- try {
653
- if (this.wcSessionService && this.approvalWorkflow && this.sqlite) {
654
- const { WcSigningBridge } = await import('../services/wc-signing-bridge.js');
655
- this.wcSigningBridgeRef.current = new WcSigningBridge({
656
- wcServiceRef: this.wcServiceRef,
657
- approvalWorkflow: this.approvalWorkflow,
658
- sqlite: this.sqlite,
659
- notificationService: this.notificationService ?? undefined,
660
- eventBus: this.eventBus,
661
- });
662
- console.debug('Step 4c-7: WcSigningBridge initialized');
663
- }
664
- }
665
- catch (err) {
666
- console.warn('Step 4c-7 (fail-soft): WcSigningBridge init warning:', err);
667
- this.wcSigningBridgeRef.current = null;
668
- }
669
- // ------------------------------------------------------------------
670
- // Step 4c-8: Signing SDK lifecycle (fail-soft)
671
- // ------------------------------------------------------------------
672
- try {
673
- if (this._settingsService?.get('signing_sdk.enabled') === 'true') {
674
- const { SignRequestBuilder, SignResponseHandler, WalletLinkRegistry, NtfySigningChannel, TelegramSigningChannel, ApprovalChannelRouter, WalletNotificationChannel, } = await import('../services/signing-sdk/index.js');
675
- const walletLinkRegistry = new WalletLinkRegistry(this._settingsService);
676
- const signRequestBuilder = new SignRequestBuilder({
677
- settingsService: this._settingsService,
678
- walletLinkRegistry,
679
- sqlite: this.sqlite, // per-wallet topic lookup from wallet_apps table
680
- });
681
- const signResponseHandler = new SignResponseHandler({ sqlite: this.sqlite }, { onApproved: (txId) => this.handleApprovalApproved(txId) });
682
- const ntfyChannel = new NtfySigningChannel({
683
- signRequestBuilder,
684
- signResponseHandler,
685
- settingsService: this._settingsService,
686
- });
687
- // Conditionally create TelegramSigningChannel (only if Telegram bot is running)
688
- let telegramChannel;
689
- if (this.telegramBotService) {
690
- const { TelegramApi } = await import('../infrastructure/telegram/index.js');
691
- const botToken = (this._settingsService
692
- ? this._settingsService.get('telegram.bot_token') ||
693
- this._settingsService.get('notifications.telegram_bot_token')
694
- : null) || this._config.telegram.bot_token;
695
- if (botToken) {
696
- const signingTelegramApi = new TelegramApi(botToken);
697
- telegramChannel = new TelegramSigningChannel({
698
- signRequestBuilder,
699
- signResponseHandler,
700
- settingsService: this._settingsService,
701
- telegramApi: signingTelegramApi,
702
- });
703
- }
704
- }
705
- this.approvalChannelRouter = new ApprovalChannelRouter({
706
- sqlite: this.sqlite,
707
- settingsService: this._settingsService,
708
- ntfyChannel,
709
- telegramChannel,
710
- });
711
- // Inject signResponseHandler into TelegramBotService for /sign_response command (GAP-2: CHAN-04)
712
- if (this.telegramBotService) {
713
- this.telegramBotService.setSignResponseHandler(signResponseHandler);
714
- console.debug('Step 4c-8: signResponseHandler injected into TelegramBotService');
715
- }
716
- // Wallet Notification Side Channel (v2.7)
717
- const walletNotifChannel = new WalletNotificationChannel({
718
- sqlite: this.sqlite,
719
- settingsService: this._settingsService,
720
- });
721
- this.notificationService?.setWalletNotificationChannel(walletNotifChannel);
722
- console.debug('Step 4c-8: WalletNotificationChannel injected into NotificationService');
723
- console.debug('Step 4c-8: Signing SDK initialized (ApprovalChannelRouter + channels)');
724
- }
725
- else {
726
- console.debug('Step 4c-8: Signing SDK disabled');
727
- }
728
- }
729
- catch (err) {
730
- console.warn('Step 4c-8 (fail-soft): Signing SDK init warning:', err);
731
- }
732
- // ------------------------------------------------------------------
733
- // Step 4c-9: IncomingTxMonitorService initialization (fail-soft)
734
- // ------------------------------------------------------------------
735
- // Pre-create BackgroundWorkers so Step 4c-9 (incoming monitor) can register its workers.
736
- // startAll() is still called in Step 6 after all workers are registered.
737
- if (!this.workers) {
738
- this.workers = new BackgroundWorkers();
739
- }
740
- try {
741
- if (this.sqlite && this._settingsService) {
742
- const incoming_enabled = this._settingsService.get('incoming.enabled');
743
- if (incoming_enabled === 'true') {
744
- const { IncomingTxMonitorService: IncomingTxMonitorCls } = await import('../services/incoming/incoming-tx-monitor-service.js');
745
- // Build config from SettingsService
746
- const ss = this._settingsService;
747
- const monitorConfig = {
748
- enabled: true,
749
- pollIntervalSec: parseInt(ss.get('incoming.poll_interval') || '30', 10),
750
- retentionDays: parseInt(ss.get('incoming.retention_days') || '90', 10),
751
- dustThresholdUsd: parseFloat(ss.get('incoming.suspicious_dust_usd') || '0.01'),
752
- amountMultiplier: parseFloat(ss.get('incoming.suspicious_amount_multiplier') || '10'),
753
- cooldownMinutes: parseInt(ss.get('incoming.cooldown_minutes') || '5', 10),
754
- };
755
- // subscriberFactory creates chain-specific subscribers via dynamic import
756
- // URL resolution: prefer RpcPool (multi-endpoint rotation), fallback to SettingsService
757
- const subscriberFactory = async (chain, network) => {
758
- const sSvc = this._settingsService;
759
- // Per-network WSS URL resolution (#193):
760
- // Priority: per-network key → global incoming.wss_url → auto-derive from RPC URL
761
- const resolveWssUrl = (net, rpcUrl) => {
762
- const perNetwork = sSvc.get(`incoming.wss_url.${net}`);
763
- if (perNetwork)
764
- return perNetwork;
765
- const global = sSvc.get('incoming.wss_url');
766
- if (global)
767
- return global;
768
- return rpcUrl.replace(/^https:\/\//, 'wss://');
769
- };
770
- if (chain === 'solana') {
771
- const rpcUrl = resolveRpcUrlFromPool(this.rpcPool, sSvc.get.bind(sSvc), chain, network);
772
- const wssUrl = resolveWssUrl(network, rpcUrl);
773
- const { SolanaIncomingSubscriber } = await import('@waiaas/adapter-solana');
774
- return new SolanaIncomingSubscriber({ rpcUrl, wsUrl: wssUrl });
775
- }
776
- // EVM chains — dynamic URL resolution via RPC Pool (#199)
777
- const rpcPool = this.rpcPool;
778
- const resolveRpcUrl = () => resolveRpcUrlFromPool(rpcPool, sSvc.get.bind(sSvc), chain, network);
779
- const initialRpcUrl = resolveRpcUrl();
780
- const wssUrl = resolveWssUrl(network, initialRpcUrl);
781
- const { EvmIncomingSubscriber } = await import('@waiaas/adapter-evm');
782
- const ns = this.notificationService;
783
- // Token address resolver for getLogs address filter (#203)
784
- const { TokenRegistryService } = await import('../infrastructure/token-registry/index.js');
785
- const tokenRegistry = this._db ? new TokenRegistryService(this._db) : null;
786
- let cachedTokenAddresses = [];
787
- let cacheExpiry = 0;
788
- const resolveTokenAddresses = () => {
789
- const now = Date.now();
790
- if (now < cacheExpiry)
791
- return cachedTokenAddresses;
792
- // Refresh every 60s to pick up runtime token additions
793
- try {
794
- if (tokenRegistry) {
795
- // Synchronous access: getTokensForNetwork is async but uses sync DB
796
- // Use a cached snapshot refreshed periodically
797
- void tokenRegistry.getTokensForNetwork(network).then((tokens) => {
798
- cachedTokenAddresses = tokens
799
- .map((t) => t.address);
800
- cacheExpiry = Date.now() + 60_000;
801
- });
802
- }
803
- }
804
- catch { /* keep previous cache */ }
805
- return cachedTokenAddresses;
806
- };
807
- // Prime the cache immediately
808
- if (tokenRegistry) {
809
- try {
810
- const tokens = await tokenRegistry.getTokensForNetwork(network);
811
- cachedTokenAddresses = tokens.map((t) => t.address);
812
- cacheExpiry = Date.now() + 60_000;
813
- }
814
- catch { /* empty cache is fine — ERC-20 polling will be skipped */ }
815
- }
816
- return new EvmIncomingSubscriber({
817
- resolveRpcUrl,
818
- reportRpcFailure: (url) => rpcPool?.reportFailure(network, url),
819
- reportRpcSuccess: (url) => rpcPool?.reportSuccess(network, url),
820
- wsUrl: wssUrl !== initialRpcUrl.replace(/^https:\/\//, 'wss://') ? wssUrl : undefined,
821
- resolveTokenAddresses,
822
- onRpcAlert: ns ? (alert) => {
823
- ns.notify(alert.type, alert.walletId, {
824
- network: alert.network,
825
- errorCount: String(alert.errorCount),
826
- lastError: alert.lastError,
827
- ...(alert.fromBlock ? { fromBlock: alert.fromBlock } : {}),
828
- ...(alert.toBlock ? { toBlock: alert.toBlock } : {}),
829
- });
830
- } : undefined,
831
- });
832
- };
833
- this.incomingTxMonitorService = new IncomingTxMonitorCls({
834
- sqlite: this.sqlite,
835
- db: this._db,
836
- workers: this.workers ?? new BackgroundWorkers(),
837
- eventBus: this.eventBus,
838
- killSwitchService: this.killSwitchService,
839
- notificationService: this.notificationService,
840
- subscriberFactory,
841
- config: monitorConfig,
842
- });
843
- await this.incomingTxMonitorService.start();
844
- console.debug('Step 4c-9: Incoming TX monitor started');
845
- }
846
- else {
847
- console.debug('Step 4c-9: Incoming TX monitor disabled');
848
- }
849
- }
850
- }
851
- catch (err) {
852
- console.warn('Step 4c-9 (fail-soft): Incoming TX monitor init warning:', err);
853
- this.incomingTxMonitorService = null;
854
- }
855
- // ------------------------------------------------------------------
856
- // Step 4c-10: AsyncPollingService initialization (fail-soft)
857
- // ------------------------------------------------------------------
858
- try {
859
- if (this._db) {
860
- const { AsyncPollingService } = await import('../services/async-polling-service.js');
861
- this._asyncPollingService = new AsyncPollingService(this._db, {
862
- emitNotification: (eventType, walletId, data) => {
863
- if (this.notificationService) {
864
- void this.notificationService.notify(eventType, walletId, undefined, // vars (template interpolation — not needed for bridge events)
865
- data);
866
- }
867
- },
868
- releaseReservation: (txId) => {
869
- // Reset reserved_amount and reserved_amount_usd to 0 for the transaction
870
- this._db
871
- .update(txTable)
872
- .set({ reservedAmount: '0', reservedAmountUsd: null })
873
- .where(eq(txTable.id, txId))
874
- .run();
875
- },
876
- resumePipeline: (txId, walletId) => {
877
- // Gas condition met: re-enter pipeline at stage 4 (execute from stage 4 onward)
878
- void this.executeFromStage4(txId, walletId);
879
- },
880
- });
881
- console.debug('Step 4c-10: AsyncPollingService initialized (with callbacks)');
882
- }
883
- }
884
- catch (err) {
885
- console.warn('Step 4c-10 (fail-soft): AsyncPollingService init warning:', err);
886
- this._asyncPollingService = null;
887
- }
888
- // ------------------------------------------------------------------
889
- // Step 4c-10.5: PositionTracker initialization (fail-soft)
890
- // ------------------------------------------------------------------
891
- try {
892
- if (this.sqlite && this._settingsService) {
893
- const trackerEnabled = this._settingsService.get('position_tracker.enabled');
894
- if (trackerEnabled !== 'false') {
895
- const { PositionTracker } = await import('../services/defi/position-tracker.js');
896
- this.positionTracker = new PositionTracker({
897
- sqlite: this.sqlite,
898
- settingsService: this._settingsService,
899
- });
900
- this.positionTracker.start();
901
- console.debug('Step 4c-10.5: Position tracker started');
902
- }
903
- else {
904
- console.debug('Step 4c-10.5: Position tracker disabled');
905
- }
906
- }
907
- }
908
- catch (err) {
909
- console.warn('Step 4c-10.5 (fail-soft): Position tracker init warning:', err);
910
- this.positionTracker = null;
911
- }
912
- // ------------------------------------------------------------------
913
- // Step 4c-11: DeFiMonitorService initialization (fail-soft)
914
- // ------------------------------------------------------------------
915
- try {
916
- const { DeFiMonitorService } = await import('../services/monitoring/defi-monitor-service.js');
917
- this.defiMonitorService = new DeFiMonitorService();
918
- // Register HealthFactorMonitor
919
- if (this.sqlite) {
920
- const { HealthFactorMonitor } = await import('../services/monitoring/health-factor-monitor.js');
921
- const healthMonitor = new HealthFactorMonitor({
922
- sqlite: this.sqlite,
923
- notificationService: this.notificationService ?? undefined,
924
- positionTracker: this.positionTracker ?? undefined,
925
- });
926
- this.defiMonitorService.register(healthMonitor);
927
- }
928
- // Register MaturityMonitor
929
- if (this.sqlite) {
930
- const { MaturityMonitor } = await import('../services/monitoring/maturity-monitor.js');
931
- const maturityMonitor = new MaturityMonitor({
932
- sqlite: this.sqlite,
933
- eventBus: this.eventBus,
934
- notificationService: this.notificationService ?? undefined,
935
- });
936
- if (this._settingsService) {
937
- maturityMonitor.loadFromSettings(this._settingsService);
938
- }
939
- this.defiMonitorService.register(maturityMonitor);
940
- }
941
- // Register MarginMonitor
942
- if (this.sqlite) {
943
- const { MarginMonitor } = await import('../services/monitoring/margin-monitor.js');
944
- const marginMonitor = new MarginMonitor({
945
- sqlite: this.sqlite,
946
- eventBus: this.eventBus,
947
- notificationService: this.notificationService ?? undefined,
948
- positionTracker: this.positionTracker ?? undefined,
949
- });
950
- if (this._settingsService) {
951
- marginMonitor.loadFromSettings(this._settingsService);
952
- }
953
- this.defiMonitorService.register(marginMonitor);
954
- }
955
- this.defiMonitorService.start();
956
- console.debug('Step 4c-11: DeFi monitor service started with', this.defiMonitorService.monitorCount, 'monitors');
957
- }
958
- catch (err) {
959
- console.warn('Step 4c-11 (fail-soft): DeFi monitor service init warning:', err);
960
- this.defiMonitorService = null;
961
- }
962
- // ------------------------------------------------------------------
963
- // Step 4e: Price Oracle (fail-soft)
964
- // ------------------------------------------------------------------
965
- try {
966
- const { InMemoryPriceCache, PythOracle, CoinGeckoOracle, OracleChain } = await import('../infrastructure/oracle/index.js');
967
- const priceCache = new InMemoryPriceCache();
968
- const pythOracle = new PythOracle();
969
- const coingeckoApiKey = this._settingsService?.get('oracle.coingecko_api_key');
970
- const coingeckoOracle = coingeckoApiKey
971
- ? new CoinGeckoOracle(coingeckoApiKey)
972
- : undefined;
973
- const thresholdStr = this._settingsService?.get('oracle.cross_validation_threshold');
974
- const crossValidationThreshold = thresholdStr ? Number(thresholdStr) : 5;
975
- this.priceOracle = new OracleChain({
976
- primary: pythOracle,
977
- fallback: coingeckoOracle,
978
- cache: priceCache,
979
- crossValidationThreshold,
980
- });
981
- console.debug(`Step 4e: PriceOracle initialized (Pyth primary${coingeckoOracle ? ' + CoinGecko fallback' : ''})`);
982
- }
983
- catch (err) {
984
- console.warn('Step 4e (fail-soft): PriceOracle init warning:', err);
985
- this.priceOracle = undefined;
986
- }
987
- // ------------------------------------------------------------------
988
- // Step 4e-2: ForexRateService (fail-soft)
989
- // ------------------------------------------------------------------
990
- try {
991
- const { CoinGeckoForexProvider, ForexRateService, InMemoryPriceCache } = await import('../infrastructure/oracle/index.js');
992
- const forexCache = new InMemoryPriceCache(30 * 60 * 1000, // TTL: 30 minutes
993
- 2 * 60 * 60 * 1000, // staleMax: 2 hours
994
- 64);
995
- const coingeckoApiKey = this._settingsService?.get('oracle.coingecko_api_key') ?? '';
996
- const forexProvider = new CoinGeckoForexProvider(coingeckoApiKey);
997
- this.forexRateService = new ForexRateService({ forexProvider, cache: forexCache });
998
- console.debug('Step 4e-2: ForexRateService initialized (30min cache)');
999
- }
1000
- catch (err) {
1001
- console.warn('Step 4e-2 (fail-soft): ForexRateService init warning:', err);
1002
- this.forexRateService = null;
1003
- }
1004
- // ------------------------------------------------------------------
1005
- // Step 4f: ActionProviderRegistry (fail-soft)
1006
- // API keys are managed by SettingsService since v29.5 (#214)
1007
- // ------------------------------------------------------------------
1008
- try {
1009
- const { ActionProviderRegistry } = await import('../infrastructure/action/index.js');
1010
- this.actionProviderRegistry = new ActionProviderRegistry();
1011
- // Create IRpcCaller for Aave V3 using RpcPool eth_call.
1012
- // RpcPool.getUrl(network) provides priority-based URL rotation with cooldown.
1013
- const rpcCaller = this.rpcPool ? (() => {
1014
- const pool = this.rpcPool;
1015
- const networkMap = {
1016
- 1: 'ethereum-mainnet',
1017
- 42161: 'arbitrum-mainnet',
1018
- 10: 'optimism-mainnet',
1019
- 137: 'polygon-mainnet',
1020
- 8453: 'base-mainnet',
1021
- };
1022
- return {
1023
- call: async (params) => {
1024
- const network = params.chainId ? (networkMap[params.chainId] ?? 'ethereum-mainnet') : 'ethereum-mainnet';
1025
- const rpcUrl = pool.getUrl(network);
1026
- const resp = await fetch(rpcUrl, {
1027
- method: 'POST',
1028
- headers: { 'Content-Type': 'application/json' },
1029
- body: JSON.stringify({
1030
- jsonrpc: '2.0',
1031
- id: 1,
1032
- method: 'eth_call',
1033
- params: [{ to: params.to, data: params.data }, 'latest'],
1034
- }),
1035
- });
1036
- const json = await resp.json();
1037
- if (json.error)
1038
- throw new Error(json.error.message);
1039
- return json.result ?? '0x';
1040
- },
1041
- };
1042
- })() : undefined;
1043
- // Store rpcCaller for HotReloadOrchestrator use
1044
- this.rpcCaller = rpcCaller;
1045
- // Register built-in action providers from @waiaas/actions (reads from SettingsService)
1046
- const { registerBuiltInProviders } = await import('@waiaas/actions');
1047
- const builtIn = registerBuiltInProviders(this.actionProviderRegistry, this._settingsService, { rpcCaller });
1048
- // Capture HyperliquidMarketData for HTTP routes (Phase 349)
1049
- if (builtIn.hyperliquidMarketData) {
1050
- this.hyperliquidMarketData = builtIn.hyperliquidMarketData;
1051
- }
1052
- // Register Polymarket providers when enabled (Phase 373)
1053
- if (this._settingsService.get('actions.polymarket_enabled') === 'true') {
1054
- try {
1055
- const { createPolymarketInfrastructure } = await import('@waiaas/actions');
1056
- const { encryptSettingValue, decryptSettingValue } = await import('../infrastructure/settings/settings-crypto.js');
1057
- const { polymarketOrders: pmOrdersTable, polymarketPositions: pmPositionsTable, polymarketApiKeys: pmApiKeysTable, } = await import('../infrastructure/database/schema.js');
1058
- const { eq: drizzleEq } = await import('drizzle-orm');
1059
- const { uuidv7 } = await import('uuidv7');
1060
- const mpHash = this.masterPassword || '';
1061
- const db = this.db;
1062
- const now = () => Math.floor(Date.now() / 1000);
1063
- // Thin DB adapters wrapping Drizzle ORM for Polymarket interfaces (snake_case mapping)
1064
- const apiKeyDbAdapter = {
1065
- getApiKeyByWalletId: (walletId) => {
1066
- const row = db.select().from(pmApiKeysTable).where(drizzleEq(pmApiKeysTable.walletId, walletId)).get();
1067
- if (!row)
1068
- return null;
1069
- return { id: row.id, wallet_id: row.walletId, api_key: row.apiKey, api_secret_encrypted: row.apiSecretEncrypted, api_passphrase_encrypted: row.apiPassphraseEncrypted, signature_type: row.signatureType, proxy_address: row.proxyAddress, created_at: row.createdAt };
1070
- },
1071
- insertApiKey: (row) => db.insert(pmApiKeysTable).values({ id: uuidv7(), walletId: row.wallet_id, apiKey: row.api_key, apiSecretEncrypted: row.api_secret_encrypted, apiPassphraseEncrypted: row.api_passphrase_encrypted, signatureType: row.signature_type ?? 0, proxyAddress: row.proxy_address ?? null, createdAt: now() }).run(),
1072
- deleteApiKeyByWalletId: (walletId) => db.delete(pmApiKeysTable).where(drizzleEq(pmApiKeysTable.walletId, walletId)).run(),
1073
- };
1074
- const orderDbAdapter = {
1075
- insertOrder: (row) => db.insert(pmOrdersTable).values(row).run(),
1076
- updateOrderStatus: (id, status, updatedAt) => db.update(pmOrdersTable).set({ status, updatedAt }).where(drizzleEq(pmOrdersTable.id, id)).run(),
1077
- updateOrderStatusByOrderId: (orderId, status, updatedAt) => db.update(pmOrdersTable).set({ status, updatedAt }).where(drizzleEq(pmOrdersTable.orderId, orderId)).run(),
1078
- };
1079
- // PositionDb adapter matching the PositionDb interface
1080
- const positionDbAdapter = {
1081
- getPositions: (walletId) => {
1082
- const rows = db.select().from(pmPositionsTable).where(drizzleEq(pmPositionsTable.walletId, walletId)).all();
1083
- return rows.map(r => ({ id: r.id, wallet_id: r.walletId, condition_id: r.conditionId, token_id: r.tokenId, market_slug: r.marketSlug, outcome: r.outcome, size: r.size, avg_price: r.avgPrice, realized_pnl: r.realizedPnl, market_resolved: r.marketResolved, winning_outcome: r.winningOutcome, is_neg_risk: r.isNegRisk, created_at: r.createdAt, updated_at: r.updatedAt }));
1084
- },
1085
- getPosition: (walletId, tokenId) => {
1086
- const rows = db.select().from(pmPositionsTable).where(drizzleEq(pmPositionsTable.walletId, walletId)).all();
1087
- const row = rows.find(r => r.tokenId === tokenId);
1088
- if (!row)
1089
- return null;
1090
- return { id: row.id, wallet_id: row.walletId, condition_id: row.conditionId, token_id: row.tokenId, market_slug: row.marketSlug, outcome: row.outcome, size: row.size, avg_price: row.avgPrice, realized_pnl: row.realizedPnl, market_resolved: row.marketResolved, winning_outcome: row.winningOutcome, is_neg_risk: row.isNegRisk, created_at: row.createdAt, updated_at: row.updatedAt };
1091
- },
1092
- upsert: (row) => db.insert(pmPositionsTable).values(row)
1093
- .onConflictDoUpdate({ target: [pmPositionsTable.walletId, pmPositionsTable.tokenId], set: row }).run(),
1094
- updateResolution: (conditionId, winningOutcome) => db.update(pmPositionsTable).set({ marketResolved: 1, winningOutcome, updatedAt: now() }).where(drizzleEq(pmPositionsTable.conditionId, conditionId)).run(),
1095
- };
1096
- const encryptFn = (plaintext) => encryptSettingValue(plaintext, mpHash);
1097
- const decryptFn = (ciphertext) => decryptSettingValue(ciphertext, mpHash);
1098
- const pmInfra = createPolymarketInfrastructure({}, { apiKeys: apiKeyDbAdapter, orders: orderDbAdapter, positions: positionDbAdapter }, encryptFn, decryptFn);
1099
- this.actionProviderRegistry.register(pmInfra.orderProvider);
1100
- this.actionProviderRegistry.register(pmInfra.ctfProvider);
1101
- this.polymarketInfra = pmInfra;
1102
- console.debug('Step 4f-pm: Polymarket providers registered (order + ctf)');
1103
- }
1104
- catch (err) {
1105
- console.warn('Step 4f-pm (fail-soft): Polymarket registration failed:', err);
1106
- }
1107
- }
1108
- // Load plugins from ~/.waiaas/actions/ (if exists)
1109
- const actionsDir = join(dataDir, 'actions');
1110
- if (existsSync(actionsDir)) {
1111
- const result = await this.actionProviderRegistry.loadPlugins(actionsDir);
1112
- console.debug(`Step 4f: ActionProviderRegistry initialized (${builtIn.loaded.length} built-in, ${result.loaded.length} plugins loaded, ${result.failed.length} failed)`);
1113
- }
1114
- else {
1115
- console.debug(`Step 4f: ActionProviderRegistry initialized (${builtIn.loaded.length} built-in, no plugins directory)`);
1116
- }
1117
- }
1118
- catch (err) {
1119
- console.warn('Step 4f (fail-soft): ActionProviderRegistry init warning:', err);
1120
- }
1121
- // ------------------------------------------------------------------
1122
- // Step 4f-2: Register bridge status trackers when lifi is enabled
1123
- // ------------------------------------------------------------------
1124
- if (this._asyncPollingService && this._settingsService?.get('actions.lifi_enabled') === 'true') {
1125
- try {
1126
- const { BridgeStatusTracker, BridgeMonitoringTracker } = await import('@waiaas/actions');
1127
- const lifiConfig = {
1128
- enabled: true,
1129
- apiBaseUrl: this._settingsService.get('actions.lifi_api_base_url'),
1130
- apiKey: this._settingsService.get('actions.lifi_api_key'),
1131
- defaultSlippagePct: Number(this._settingsService.get('actions.lifi_default_slippage_pct')),
1132
- maxSlippagePct: Number(this._settingsService.get('actions.lifi_max_slippage_pct')),
1133
- requestTimeoutMs: 15_000,
1134
- };
1135
- this._asyncPollingService.registerTracker(new BridgeStatusTracker(lifiConfig));
1136
- this._asyncPollingService.registerTracker(new BridgeMonitoringTracker(lifiConfig));
1137
- console.debug('Step 4f-2: Bridge status trackers registered (bridge + bridge-monitoring)');
1138
- }
1139
- catch (err) {
1140
- console.warn('Step 4f-2 (fail-soft): Bridge tracker registration failed:', err);
1141
- }
1142
- }
1143
- // ------------------------------------------------------------------
1144
- // Step 4f-2a: Register Across bridge status trackers when across_bridge is enabled
1145
- // ------------------------------------------------------------------
1146
- if (this._asyncPollingService && this._settingsService?.get('actions.across_bridge_enabled') === 'true') {
1147
- try {
1148
- const { AcrossBridgeStatusTracker, AcrossBridgeMonitoringTracker } = await import('@waiaas/actions');
1149
- const acrossConfig = {
1150
- enabled: true,
1151
- apiBaseUrl: this._settingsService.get('actions.across_bridge_api_base_url') || 'https://app.across.to/api',
1152
- integratorId: this._settingsService.get('actions.across_bridge_integrator_id') || '',
1153
- fillDeadlineBufferSec: Number(this._settingsService.get('actions.across_bridge_fill_deadline_buffer_sec')) || 21600,
1154
- defaultSlippagePct: Number(this._settingsService.get('actions.across_bridge_default_slippage_pct')) || 0.01,
1155
- maxSlippagePct: Number(this._settingsService.get('actions.across_bridge_max_slippage_pct')) || 0.03,
1156
- requestTimeoutMs: 10_000,
1157
- };
1158
- this._asyncPollingService.registerTracker(new AcrossBridgeStatusTracker(acrossConfig));
1159
- this._asyncPollingService.registerTracker(new AcrossBridgeMonitoringTracker(acrossConfig));
1160
- console.debug('Step 4f-2a: Across bridge status trackers registered (across-bridge + across-bridge-monitoring)');
1161
- }
1162
- catch (err) {
1163
- console.warn('Step 4f-2a (fail-soft): Across bridge tracker registration failed:', err);
1164
- }
1165
- }
1166
- // ------------------------------------------------------------------
1167
- // Step 4f-3: Register staking status trackers when lido/jito is enabled
1168
- // ------------------------------------------------------------------
1169
- if (this._asyncPollingService) {
1170
- try {
1171
- if (this._settingsService?.get('actions.lido_staking_enabled') === 'true') {
1172
- const { LidoWithdrawalTracker } = await import('@waiaas/actions');
1173
- this._asyncPollingService.registerTracker(new LidoWithdrawalTracker());
1174
- console.debug('Step 4f-3: Lido withdrawal tracker registered');
1175
- }
1176
- if (this._settingsService?.get('actions.jito_staking_enabled') === 'true') {
1177
- const { JitoEpochTracker } = await import('@waiaas/actions');
1178
- this._asyncPollingService.registerTracker(new JitoEpochTracker());
1179
- console.debug('Step 4f-3: Jito epoch tracker registered');
1180
- }
1181
- }
1182
- catch (err) {
1183
- console.warn('Step 4f-3 (fail-soft): Staking tracker registration failed:', err);
1184
- }
1185
- }
1186
- // ------------------------------------------------------------------
1187
- // Step 4f-4: Register GasConditionTracker (gas price condition monitoring)
1188
- // ------------------------------------------------------------------
1189
- if (this._asyncPollingService) {
1190
- try {
1191
- const gasConditionEnabled = this._settingsService?.get('gas_condition.enabled') !== 'false';
1192
- if (gasConditionEnabled) {
1193
- const { GasConditionTracker } = await import('../pipeline/gas-condition-tracker.js');
1194
- this._asyncPollingService.registerTracker(new GasConditionTracker());
1195
- console.debug('Step 4f-4: GasConditionTracker registered');
1196
- }
1197
- else {
1198
- console.debug('Step 4f-4: GasConditionTracker disabled');
1199
- }
1200
- }
1201
- catch (err) {
1202
- console.warn('Step 4f-4 (fail-soft): GasConditionTracker registration failed:', err);
1203
- }
1204
- }
1205
- // ------------------------------------------------------------------
1206
- // Step 4f-6: Register IPositionProvider implementations with PositionTracker
1207
- // ------------------------------------------------------------------
1208
- if (this.positionTracker && this.actionProviderRegistry) {
1209
- try {
1210
- // Check each registered provider for IPositionProvider interface (duck-typing)
1211
- for (const meta of this.actionProviderRegistry.listProviders()) {
1212
- const provider = this.actionProviderRegistry.getProvider(meta.name);
1213
- if (provider && 'getPositions' in provider && 'getSupportedCategories' in provider && 'getProviderName' in provider) {
1214
- this.positionTracker.registerProvider(provider);
1215
- console.debug(`Step 4f-5: Registered ${meta.name} with PositionTracker`);
1216
- }
1217
- }
1218
- console.debug(`Step 4f-5: PositionTracker has ${this.positionTracker.providerCount} providers`);
1219
- // Trigger immediate LENDING sync now that providers are registered
1220
- if (this.positionTracker.providerCount > 0) {
1221
- void this.positionTracker.syncCategory('LENDING');
1222
- }
1223
- }
1224
- catch (err) {
1225
- console.warn('Step 4f-5 (fail-soft): PositionTracker provider registration warning:', err);
1226
- }
1227
- }
1228
- // ------------------------------------------------------------------
1229
- // Step 4g: VersionCheckService (create before Step 5 for Health endpoint)
1230
- // ------------------------------------------------------------------
1231
- if (this.sqlite && this._config.daemon.update_check) {
1232
- const { VersionCheckService } = await import('../infrastructure/version/index.js');
1233
- this._versionCheckService = new VersionCheckService(this.sqlite);
1234
- if (this.notificationService) {
1235
- this._versionCheckService.setNotificationService(this.notificationService);
1236
- }
1237
- console.debug('Step 4g: VersionCheckService created');
1238
- }
1239
- // ------------------------------------------------------------------
1240
- // Step 4h: EncryptedBackupService (fail-soft)
1241
- // ------------------------------------------------------------------
1242
- try {
1243
- if (this.sqlite) {
1244
- const { isAbsolute } = await import('node:path');
1245
- const { EncryptedBackupService } = await import('../infrastructure/backup/encrypted-backup-service.js');
1246
- const backupDir = this._config.backup?.dir ?? 'backups';
1247
- const backupsDir = isAbsolute(backupDir) ? backupDir : join(dataDir, backupDir);
1248
- this._encryptedBackupService = new EncryptedBackupService(dataDir, backupsDir, this.sqlite);
1249
- console.debug('Step 4h: EncryptedBackupService created');
1250
- }
1251
- }
1252
- catch (err) {
1253
- console.warn('Step 4h (fail-soft): EncryptedBackupService init warning:', err);
1254
- }
1255
- // ------------------------------------------------------------------
1256
- // Step 4i: WebhookService (fail-soft)
1257
- // ------------------------------------------------------------------
1258
- try {
1259
- if (this.sqlite && this.eventBus) {
1260
- const { WebhookService } = await import('../services/webhook-service.js');
1261
- this.webhookService = new WebhookService(this.sqlite, this.eventBus, () => this.masterPassword);
1262
- console.debug('Step 4i: WebhookService created');
1263
- }
1264
- }
1265
- catch (err) {
1266
- console.warn('Step 4i (fail-soft): WebhookService init warning:', err);
1267
- }
1268
- // ------------------------------------------------------------------
1269
- // Step 5: HTTP server start (5s, fail-fast)
1270
- // ------------------------------------------------------------------
1271
- await withTimeout((async () => {
1272
- const { createApp } = await import('../api/index.js');
1273
- const { serve } = await import('@hono/node-server');
1274
- const { HotReloadOrchestrator } = await import('../infrastructure/settings/index.js');
1275
- const hotReloader = new HotReloadOrchestrator({
1276
- settingsService: this._settingsService,
1277
- notificationService: this.notificationService,
1278
- adapterPool: this.adapterPool,
1279
- autoStopService: this.autoStopService,
1280
- balanceMonitorService: this.balanceMonitorService,
1281
- wcServiceRef: this.wcServiceRef,
1282
- wcSigningBridgeRef: this.wcSigningBridgeRef,
1283
- approvalWorkflow: this.approvalWorkflow,
1284
- sqlite: this.sqlite,
1285
- telegramBotRef: this.telegramBotRef,
1286
- killSwitchService: this.killSwitchService,
1287
- incomingTxMonitorService: this.incomingTxMonitorService,
1288
- actionProviderRegistryRef: { current: this.actionProviderRegistry },
1289
- rpcCaller: this.rpcCaller ?? undefined,
1290
- });
1291
- // [Phase 320] Create ReputationCacheService for REPUTATION_THRESHOLD policy evaluation
1292
- const { ReputationCacheService } = await import('../services/erc8004/index.js');
1293
- const reputationCacheService = new ReputationCacheService(this._db, this._settingsService ?? undefined);
1294
- // [#272] Create SmartAccountService for ERC-4337 CREATE2 address prediction
1295
- const { SmartAccountService } = await import('../infrastructure/smart-account/smart-account-service.js');
1296
- const smartAccountService = new SmartAccountService();
1297
- // [Phase 390] Bootstrap signer capabilities for external action signing
1298
- const { SignerCapabilityRegistry } = await import('../signing/registry.js');
1299
- const { bootstrapSignerCapabilities } = await import('../signing/bootstrap.js');
1300
- const signerRegistry = new SignerCapabilityRegistry();
1301
- bootstrapSignerCapabilities(signerRegistry);
1302
- const app = createApp({
1303
- db: this._db,
1304
- sqlite: this.sqlite ?? undefined,
1305
- keyStore: this.keyStore,
1306
- masterPassword: this.masterPassword,
1307
- masterPasswordHash: this.masterPasswordHash || undefined,
1308
- passwordRef: this.passwordRef ?? undefined,
1309
- config: this._config,
1310
- adapterPool: this.adapterPool,
1311
- policyEngine: new DatabasePolicyEngine(this._db, this.sqlite ?? undefined, this._settingsService ?? undefined, reputationCacheService),
1312
- reputationCache: reputationCacheService,
1313
- jwtSecretManager: this.jwtSecretManager ?? undefined,
1314
- delayQueue: this.delayQueue ?? undefined,
1315
- approvalWorkflow: this.approvalWorkflow ?? undefined,
1316
- notificationService: this.notificationService ?? undefined,
1317
- settingsService: this._settingsService ?? undefined,
1318
- priceOracle: this.priceOracle,
1319
- actionProviderRegistry: this.actionProviderRegistry ?? undefined,
1320
- smartAccountService,
1321
- // apiKeyStore removed in v29.5 -- API keys via SettingsService
1322
- onSettingsChanged: (changedKeys) => {
1323
- void hotReloader.handleChangedKeys(changedKeys);
1324
- },
1325
- dataDir,
1326
- forexRateService: this.forexRateService ?? undefined,
1327
- eventBus: this.eventBus,
1328
- killSwitchService: this.killSwitchService ?? undefined,
1329
- wcServiceRef: this.wcServiceRef,
1330
- wcSigningBridgeRef: this.wcSigningBridgeRef,
1331
- approvalChannelRouter: this.approvalChannelRouter ?? undefined,
1332
- versionCheckService: this._versionCheckService,
1333
- encryptedBackupService: this._encryptedBackupService ?? undefined,
1334
- adminStatsService: this.adminStatsService ?? undefined,
1335
- autoStopService: this.autoStopService ?? undefined,
1336
- metricsCounter: this.metricsCounter ?? undefined,
1337
- hyperliquidMarketData: this.hyperliquidMarketData ?? undefined,
1338
- polymarketInfra: this.polymarketInfra ?? undefined,
1339
- signerRegistry,
1340
- });
1341
- const hostname = this._config.daemon.hostname;
1342
- const port = this._config.daemon.port;
1343
- const server = serve({
1344
- fetch: app.fetch,
1345
- hostname,
1346
- port,
1347
- });
1348
- this.httpServer = server;
1349
- // v31.14: Long-poll RPC proxy support -- keep connections alive for 10 minutes
1350
- // Default Node.js keepAliveTimeout is 5s, which is too short for DELAY/APPROVAL tier
1351
- // long-poll responses that can take up to 600s.
1352
- server.keepAliveTimeout = 600_000; // 600 seconds in milliseconds
1353
- server.headersTimeout = 605_000; // Must be > keepAliveTimeout (Node.js docs)
1354
- // Wait for server to actually start listening (catches EADDRINUSE)
1355
- await new Promise((resolve, reject) => {
1356
- const onListening = () => {
1357
- server.removeListener('error', onError);
1358
- resolve();
1359
- };
1360
- const onError = (err) => {
1361
- server.removeListener('listening', onListening);
1362
- if (err.code === 'EADDRINUSE') {
1363
- reject(new Error(`Port ${port} is already in use. Try a different port or stop the other process.`));
1364
- }
1365
- else {
1366
- reject(err);
1367
- }
1368
- };
1369
- // @hono/node-server serve() returns a Node.js http.Server
1370
- server.once('listening', onListening);
1371
- server.once('error', onError);
1372
- });
1373
- console.debug(`Step 5: HTTP server listening on ${hostname}:${port}`);
1374
- })(), 5_000, 'STEP5_HTTP_SERVER');
1375
- // ------------------------------------------------------------------
1376
- // Step 6: Background workers + PID (no timeout, fail-soft)
1377
- // ------------------------------------------------------------------
1378
- try {
1379
- // Ensure workers instance exists (should already be created before Step 4c-9)
1380
- if (!this.workers) {
1381
- this.workers = new BackgroundWorkers();
1382
- }
1383
- // Register WAL checkpoint worker (default: 5 min = 300s)
1384
- const walInterval = this._config.database.wal_checkpoint_interval * 1000;
1385
- this.workers.register('wal-checkpoint', {
1386
- interval: walInterval,
1387
- handler: () => {
1388
- if (this.sqlite && !this._isShuttingDown) {
1389
- this.sqlite.pragma('wal_checkpoint(PASSIVE)');
1390
- }
1391
- },
1392
- });
1393
- // Register session cleanup worker (1 min = 60s)
1394
- this.workers.register('session-cleanup', {
1395
- interval: 60_000,
1396
- handler: () => {
1397
- if (this.sqlite && !this._isShuttingDown) {
1398
- // Notify expired sessions before deletion (fire-and-forget)
1399
- if (this.notificationService) {
1400
- try {
1401
- const expired = this.sqlite.prepare("SELECT id, wallet_id FROM sessions WHERE expires_at > 0 AND expires_at < unixepoch() AND revoked_at IS NULL").all();
1402
- for (const session of expired) {
1403
- void this.notificationService.notify('SESSION_EXPIRED', session.wallet_id, {
1404
- sessionId: session.id,
1405
- });
1406
- }
1407
- }
1408
- catch {
1409
- // Fire-and-forget: never block cleanup
1410
- }
1411
- }
1412
- this.sqlite.exec("DELETE FROM sessions WHERE expires_at > 0 AND expires_at < unixepoch() AND revoked_at IS NULL");
1413
- }
1414
- },
1415
- });
1416
- // Register delay-expired worker (every 5s: check for expired DELAY transactions)
1417
- // #327: Process expired items sequentially with concurrency limit to prevent
1418
- // resource exhaustion from concurrent RPC calls when many items expire at once.
1419
- if (this.delayQueue) {
1420
- this.workers.register('delay-expired', {
1421
- interval: 5_000,
1422
- handler: () => {
1423
- if (this._isShuttingDown)
1424
- return;
1425
- const now = Math.floor(Date.now() / 1000);
1426
- const expired = this.delayQueue.processExpired(now);
1427
- if (expired.length === 0)
1428
- return;
1429
- // Process sequentially (one at a time) to avoid concurrent RPC/memory pressure
1430
- void (async () => {
1431
- for (const tx of expired) {
1432
- if (this._isShuttingDown)
1433
- break;
1434
- try {
1435
- await this.executeFromStage5(tx.txId, tx.walletId);
1436
- }
1437
- catch (err) {
1438
- console.error(`[delay-expired] executeFromStage5(${tx.txId}) error:`, err);
1439
- }
1440
- }
1441
- })();
1442
- },
1443
- });
1444
- }
1445
- // Register approval-expired worker (every 30s: expire timed-out approvals)
1446
- if (this.approvalWorkflow) {
1447
- this.workers.register('approval-expired', {
1448
- interval: 30_000,
1449
- handler: () => {
1450
- if (this._isShuttingDown)
1451
- return;
1452
- const now = Math.floor(Date.now() / 1000);
1453
- this.approvalWorkflow.processExpiredApprovals(now);
1454
- },
1455
- });
1456
- }
1457
- // #329: Register submitted-tx-confirm worker (every 60s)
1458
- // Retries confirmation for transactions stuck in SUBMITTED state after Stage 6 timeout.
1459
- // Prevents STO-03 regression where on-chain success is not reflected in DB status.
1460
- this.workers.register('submitted-tx-confirm', {
1461
- interval: 60_000,
1462
- handler: async () => {
1463
- if (this._isShuttingDown || !this._db || !this.adapterPool || !this.sqlite)
1464
- return;
1465
- try {
1466
- const { transactions } = await import('../infrastructure/database/schema.js');
1467
- const { eq, and, isNotNull } = await import('drizzle-orm');
1468
- const { insertAuditLog } = await import('../infrastructure/database/audit-helper.js');
1469
- // Find SUBMITTED transactions with txHash that are older than 60s
1470
- const cutoff = Math.floor(Date.now() / 1000) - 60;
1471
- const stuckTxs = this._db
1472
- .select({
1473
- id: transactions.id,
1474
- txHash: transactions.txHash,
1475
- walletId: transactions.walletId,
1476
- chain: transactions.chain,
1477
- network: transactions.network,
1478
- amount: transactions.amount,
1479
- toAddress: transactions.toAddress,
1480
- type: transactions.type,
1481
- })
1482
- .from(transactions)
1483
- .where(and(eq(transactions.status, 'SUBMITTED'), isNotNull(transactions.txHash)))
1484
- .all()
1485
- .filter((tx) => {
1486
- // Only retry if created before cutoff (avoid racing with Stage 6)
1487
- const meta = this.sqlite.prepare('SELECT created_at FROM transactions WHERE id = ?').get(tx.id);
1488
- return !meta?.created_at || meta.created_at < cutoff;
1489
- });
1490
- for (const tx of stuckTxs) {
1491
- if (this._isShuttingDown || !tx.txHash || !tx.network)
1492
- continue;
1493
- try {
1494
- const rpcUrl = resolveRpcUrl(this._config.rpc, tx.chain, tx.network);
1495
- const adapter = await this.adapterPool.resolve(tx.chain, tx.network, rpcUrl);
1496
- const result = await adapter.waitForConfirmation(tx.txHash, 10_000);
1497
- if (result.status === 'confirmed' || result.status === 'finalized') {
1498
- const executedAt = new Date(Math.floor(Date.now() / 1000) * 1000);
1499
- this._db
1500
- .update(transactions)
1501
- .set({ status: 'CONFIRMED', executedAt })
1502
- .where(eq(transactions.id, tx.id))
1503
- .run();
1504
- insertAuditLog(this.sqlite, {
1505
- eventType: 'TX_CONFIRMED',
1506
- actor: 'system',
1507
- walletId: tx.walletId,
1508
- txId: tx.id,
1509
- details: { txHash: tx.txHash, source: 'submitted-tx-confirm-worker', network: tx.network },
1510
- severity: 'info',
1511
- });
1512
- void this.notificationService?.notify('TX_CONFIRMED', tx.walletId, {
1513
- txId: tx.id,
1514
- txHash: tx.txHash,
1515
- network: tx.network,
1516
- amount: tx.amount ?? '',
1517
- to: tx.toAddress ?? '',
1518
- type: tx.type ?? '',
1519
- }, { txId: tx.id });
1520
- console.info(`[submitted-tx-confirm] ${tx.id} confirmed via background retry`);
1521
- }
1522
- else if (result.status === 'failed') {
1523
- this._db
1524
- .update(transactions)
1525
- .set({ status: 'FAILED', error: 'Transaction reverted on-chain (background check)' })
1526
- .where(eq(transactions.id, tx.id))
1527
- .run();
1528
- console.warn(`[submitted-tx-confirm] ${tx.id} failed on-chain`);
1529
- }
1530
- // status === 'submitted': still pending, retry on next interval
1531
- }
1532
- catch (_err) {
1533
- // Swallow individual tx errors (RPC timeout, rate limit) — will retry next interval
1534
- }
1535
- }
1536
- }
1537
- catch (err) {
1538
- console.error('[submitted-tx-confirm] worker error:', err);
1539
- }
1540
- },
1541
- });
1542
- // Register userop-build-cleanup worker (5 min = 300s)
1543
- // Deletes expired build records from userop_builds table (10-min TTL)
1544
- this.workers.register('userop-build-cleanup', {
1545
- interval: 300_000,
1546
- handler: () => {
1547
- if (this.sqlite && !this._isShuttingDown) {
1548
- const now = Math.floor(Date.now() / 1000);
1549
- this.sqlite.prepare('DELETE FROM userop_builds WHERE expires_at < ?').run(now);
1550
- }
1551
- },
1552
- });
1553
- // Register credential-cleanup worker (5 min = 300s)
1554
- // Deletes expired credentials from wallet_credentials table
1555
- this.workers.register('credential-cleanup', {
1556
- interval: 300_000,
1557
- handler: () => {
1558
- if (this.sqlite && !this._isShuttingDown) {
1559
- const now = Math.floor(Date.now() / 1000);
1560
- const result = this.sqlite.prepare('DELETE FROM wallet_credentials WHERE expires_at IS NOT NULL AND expires_at < ?').run(now);
1561
- if (result.changes > 0) {
1562
- console.log(`[credential-cleanup] Deleted ${result.changes} expired credential(s)`);
1563
- }
1564
- }
1565
- },
1566
- });
1567
- // Register async-status polling worker (30s)
1568
- if (this._asyncPollingService) {
1569
- const pollingService = this._asyncPollingService;
1570
- this.workers.register('async-status', {
1571
- interval: 30_000,
1572
- handler: async () => {
1573
- if (this._isShuttingDown)
1574
- return;
1575
- await pollingService.pollAll();
1576
- },
1577
- });
1578
- }
1579
- // Register version-check worker (uses instance created in Step 4g)
1580
- if (this._versionCheckService) {
1581
- const versionCheckInterval = this._config.daemon.update_check_interval * 1000;
1582
- this.workers.register('version-check', {
1583
- interval: versionCheckInterval,
1584
- runImmediately: true,
1585
- handler: async () => { await this._versionCheckService.check(); },
1586
- });
1587
- console.debug('Step 6: Version check worker registered');
1588
- }
1589
- else {
1590
- console.debug('Step 6: Version check disabled');
1591
- }
1592
- // Register backup worker (auto-backup scheduler)
1593
- if (this._encryptedBackupService && this._config.backup.interval > 0) {
1594
- const backupInterval = this._config.backup.interval * 1000; // seconds -> ms
1595
- const retentionCount = this._config.backup.retention_count;
1596
- const backupService = this._encryptedBackupService;
1597
- const masterPwd = this.masterPassword;
1598
- this.workers.register('backup-worker', {
1599
- interval: backupInterval,
1600
- handler: async () => {
1601
- if (this._isShuttingDown)
1602
- return;
1603
- try {
1604
- const info = await backupService.createBackup(masterPwd);
1605
- console.log(`Auto-backup created: ${info.filename} (${info.size} bytes)`);
1606
- const pruned = backupService.pruneBackups(retentionCount);
1607
- if (pruned > 0) {
1608
- console.log(`Auto-backup: pruned ${pruned} old backup(s), keeping ${retentionCount}`);
1609
- }
1610
- }
1611
- catch (err) {
1612
- console.error('Auto-backup failed:', err);
1613
- }
1614
- },
1615
- });
1616
- console.debug(`Step 6: Backup worker registered (interval=${this._config.backup.interval}s, retention=${retentionCount})`);
1617
- }
1618
- else {
1619
- console.debug('Step 6: Backup worker disabled (interval=0 or no backup service)');
1620
- }
1621
- this.workers.startAll();
1622
- // Write PID file
1623
- this.pidPath = join(dataDir, this._config.daemon.pid_file);
1624
- writeFileSync(this.pidPath, String(process.pid), 'utf-8');
1625
- console.debug(`Step 6: Workers started, PID file written`);
1626
- console.log(`WAIaaS daemon ready on http://${this._config.daemon.hostname}:${this._config.daemon.port} (PID: ${process.pid})\n` +
1627
- ` Admin UI: http://${this._config.daemon.hostname}:${this._config.daemon.port}/admin`);
1628
- }
1629
- catch (err) {
1630
- console.warn('Step 6 (fail-soft): Worker/PID warning:', err);
1631
- }
142
+ await withTimeout(startDaemon(this, dataDir, masterPassword), 90_000, 'STARTUP_TIMEOUT');
1632
143
  }
1633
144
  /**
1634
145
  * 10-step graceful shutdown cascade.
1635
146
  */
1636
147
  async shutdown(signal) {
1637
- // Guard against double shutdown
1638
- if (this._isShuttingDown)
1639
- return;
1640
- this._isShuttingDown = true;
1641
- console.log(`Shutdown initiated by ${signal}`);
1642
- // Start force-exit timer (configurable, default 30s)
1643
- const timeout = this._config?.daemon.shutdown_timeout ?? 30;
1644
- this.forceTimer = setTimeout(() => {
1645
- console.error('Force exit: shutdown timeout exceeded');
1646
- process.exit(1);
1647
- }, timeout * 1000);
1648
- this.forceTimer.unref(); // don't prevent exit
1649
- try {
1650
- // Steps 1: Set flag + log (done above)
1651
- // Steps 2-4: HTTP server close
1652
- if (this.httpServer) {
1653
- this.httpServer.close();
1654
- console.log('Steps 2-4: HTTP server closed');
1655
- }
1656
- // Steps 5: In-flight signing -- STUB (Phase 50-04)
1657
- // Steps 6: Pending queue persistence -- STUB (Phase 50-04)
1658
- // Disconnect all chain adapters
1659
- if (this.adapterPool) {
1660
- try {
1661
- await this.adapterPool.disconnectAll();
1662
- console.log('Adapter pool disconnected');
1663
- }
1664
- catch (err) {
1665
- console.warn('Adapter pool disconnect warning:', err);
1666
- }
1667
- this.adapterPool = null;
1668
- }
1669
- // Stop AutoStop engine (before EventBus cleanup)
1670
- if (this.autoStopService) {
1671
- this.autoStopService.stop();
1672
- this.autoStopService = null;
1673
- }
1674
- // Stop PositionTracker (before BalanceMonitor for cleaner ordering)
1675
- if (this.positionTracker) {
1676
- this.positionTracker.stop();
1677
- this.positionTracker = null;
1678
- }
1679
- // Stop DeFiMonitorService
1680
- if (this.defiMonitorService) {
1681
- this.defiMonitorService.stop();
1682
- this.defiMonitorService = null;
1683
- }
1684
- // Stop BalanceMonitorService (before EventBus cleanup)
1685
- if (this.balanceMonitorService) {
1686
- this.balanceMonitorService.stop();
1687
- this.balanceMonitorService = null;
1688
- }
1689
- // Stop TelegramBotService (before EventBus cleanup)
1690
- if (this.telegramBotService) {
1691
- this.telegramBotService.stop();
1692
- this.telegramBotService = null;
1693
- this.telegramBotRef.current = null;
1694
- }
1695
- // Stop ApprovalChannelRouter (shuts down signing channels)
1696
- if (this.approvalChannelRouter) {
1697
- this.approvalChannelRouter.shutdown();
1698
- this.approvalChannelRouter = null;
1699
- }
1700
- // Stop WcSessionService (before EventBus cleanup)
1701
- if (this.wcSessionService) {
1702
- try {
1703
- await this.wcSessionService.shutdown();
1704
- }
1705
- catch (err) {
1706
- console.warn('WcSessionService shutdown warning:', err);
1707
- }
1708
- this.wcSessionService = null;
1709
- this.wcServiceRef.current = null;
1710
- }
1711
- // Stop IncomingTxMonitorService (final flush + destroy subscribers)
1712
- if (this.incomingTxMonitorService) {
1713
- try {
1714
- await this.incomingTxMonitorService.stop();
1715
- }
1716
- catch (err) {
1717
- console.warn('IncomingTxMonitorService shutdown warning:', err);
1718
- }
1719
- this.incomingTxMonitorService = null;
1720
- }
1721
- // Clear AsyncPollingService reference (workers.stopAll() handles the timer)
1722
- this._asyncPollingService = null;
1723
- // WebhookService destroy (before removing EventBus listeners)
1724
- this.webhookService?.destroy();
1725
- this.webhookService = null;
1726
- // Step 6b: Remove all EventBus listeners
1727
- this.eventBus.removeAllListeners();
1728
- // Step 7: Stop background workers
1729
- if (this.workers) {
1730
- await this.workers.stopAll();
1731
- console.log('Step 7: Workers stopped');
1732
- }
1733
- // Step 8: WAL checkpoint(TRUNCATE)
1734
- if (this.sqlite) {
1735
- try {
1736
- this.sqlite.pragma('wal_checkpoint(TRUNCATE)');
1737
- console.log('Step 8: WAL checkpoint complete');
1738
- }
1739
- catch (err) {
1740
- console.warn('Step 8: WAL checkpoint warning:', err);
1741
- }
1742
- }
1743
- // Step 9: Keystore lock (sodium_memzero all guarded buffers)
1744
- if (this.keyStore) {
1745
- this.keyStore.lockAll();
1746
- console.log('Step 9: Keystore locked');
1747
- }
1748
- // Clear master password and hash from memory
1749
- this.masterPassword = '';
1750
- this.masterPasswordHash = '';
1751
- if (this.passwordRef) {
1752
- this.passwordRef.password = '';
1753
- this.passwordRef.hash = '';
1754
- this.passwordRef = null;
1755
- }
1756
- // Step 10: Close DB, unlink PID, release lock
1757
- if (this.sqlite) {
1758
- try {
1759
- this.sqlite.close();
1760
- console.log('Step 10: Database closed');
1761
- }
1762
- catch (err) {
1763
- console.warn('Step 10: DB close warning:', err);
1764
- }
1765
- this.sqlite = null;
1766
- this._db = null;
1767
- }
1768
- // Delete PID file
1769
- if (this.pidPath) {
1770
- try {
1771
- unlinkSync(this.pidPath);
1772
- }
1773
- catch {
1774
- // Ignore if already deleted
1775
- }
1776
- }
1777
- // Release daemon lock
1778
- if (this.releaseLock) {
1779
- try {
1780
- await this.releaseLock();
1781
- }
1782
- catch {
1783
- // Ignore lock release errors during shutdown
1784
- }
1785
- this.releaseLock = null;
1786
- }
1787
- // Cancel force timer
1788
- if (this.forceTimer) {
1789
- clearTimeout(this.forceTimer);
1790
- this.forceTimer = null;
1791
- }
1792
- console.log('Shutdown complete');
1793
- process.exit(0);
1794
- }
1795
- catch (err) {
1796
- console.error('Shutdown error:', err);
1797
- process.exit(1);
1798
- }
148
+ await shutdownDaemon(this, signal);
1799
149
  }
1800
150
  /**
1801
151
  * Re-enter the pipeline at stage4 for a gas-condition-met transaction.
1802
- *
1803
- * Called by the resumePipeline callback in AsyncPollingService when
1804
- * GasConditionTracker returns COMPLETED. Skips stages 1-3 and 3.5
1805
- * (already evaluated). Runs stage5Execute + stage6Confirm.
1806
- *
1807
- * Gas-condition transactions bypass stage4Wait (policy was already evaluated
1808
- * at Stage 3, and the transaction was only waiting for gas price -- no further
1809
- * delay/approval needed).
1810
- *
1811
- * @param txId - Transaction ID to execute
1812
- * @param walletId - Wallet that owns the transaction
1813
152
  */
1814
153
  async executeFromStage4(txId, walletId) {
1815
- try {
1816
- if (!this._db || !this.adapterPool || !this.keyStore || !this._config) {
1817
- console.warn(`executeFromStage4(${txId}): missing deps, skipping`);
1818
- return;
1819
- }
1820
- // Import stages and schema
1821
- const { stage5Execute, stage6Confirm } = await import('../pipeline/stages.js');
1822
- const { wallets, transactions } = await import('../infrastructure/database/schema.js');
1823
- const { eq } = await import('drizzle-orm');
1824
- // Look up wallet from DB
1825
- const wallet = this._db.select().from(wallets).where(eq(wallets.id, walletId)).get();
1826
- if (!wallet) {
1827
- console.warn(`executeFromStage4(${txId}): wallet ${walletId} not found`);
1828
- return;
1829
- }
1830
- // Look up transaction to get request data
1831
- const tx = this._db.select().from(transactions).where(eq(transactions.id, txId)).get();
1832
- if (!tx) {
1833
- console.warn(`executeFromStage4(${txId}): transaction not found`);
1834
- return;
1835
- }
1836
- // Use network recorded at Stage 1 (NOT re-resolve)
1837
- const resolvedNetwork = tx.network
1838
- ?? getSingleNetwork(wallet.chain, wallet.environment)
1839
- ?? (() => { throw new WAIaaSError('NETWORK_REQUIRED'); })();
1840
- // Resolve adapter from pool using recorded network
1841
- const rpcUrl = resolveRpcUrl(this._config.rpc, wallet.chain, resolvedNetwork);
1842
- const adapter = await this.adapterPool.resolve(wallet.chain, resolvedNetwork, rpcUrl);
1843
- // Restore original request from metadata (#208)
1844
- // DELAY/GAS_WAITING re-entry needs full request to rebuild correct tx type
1845
- const meta = tx.metadata ? JSON.parse(tx.metadata) : {};
1846
- const request = meta.originalRequest ?? {
1847
- to: tx.toAddress ?? '',
1848
- amount: tx.amount ?? '0',
1849
- memo: undefined,
1850
- };
1851
- // Construct PipelineContext for stages 5-6
1852
- // Policy already evaluated at Stage 3 before GAS_WAITING entry
1853
- const ctx = {
1854
- db: this._db,
1855
- adapter,
1856
- keyStore: this.keyStore,
1857
- policyEngine: null, // Not needed for stages 5-6
1858
- masterPassword: this.masterPassword,
1859
- walletId,
1860
- wallet: {
1861
- publicKey: wallet.publicKey,
1862
- chain: wallet.chain,
1863
- environment: wallet.environment,
1864
- // #251: pass AA fields for Smart Account re-entry
1865
- accountType: wallet.accountType,
1866
- aaProvider: wallet.aaProvider,
1867
- aaProviderApiKeyEncrypted: wallet.aaProviderApiKeyEncrypted,
1868
- aaBundlerUrl: wallet.aaBundlerUrl,
1869
- aaPaymasterUrl: wallet.aaPaymasterUrl,
1870
- aaPaymasterPolicyId: wallet.aaPaymasterPolicyId,
1871
- },
1872
- resolvedNetwork,
1873
- resolvedRpcUrl: rpcUrl,
1874
- request,
1875
- txId,
1876
- eventBus: this.eventBus,
1877
- notificationService: this.notificationService ?? undefined,
1878
- };
1879
- // Skip stage4Wait -- gas condition met, proceed directly to execution
1880
- await stage5Execute(ctx);
1881
- await stage6Confirm(ctx);
1882
- }
1883
- catch (error) {
1884
- // Mark as FAILED if stages 5-6 throw
1885
- try {
1886
- if (this._db) {
1887
- const { transactions } = await import('../infrastructure/database/schema.js');
1888
- const { eq } = await import('drizzle-orm');
1889
- const errorMessage = error instanceof Error ? error.message : 'Gas condition pipeline re-entry failed';
1890
- this._db
1891
- .update(transactions)
1892
- .set({ status: 'FAILED', error: errorMessage })
1893
- .where(eq(transactions.id, txId))
1894
- .run();
1895
- }
1896
- }
1897
- catch {
1898
- // Swallow DB update errors in background
1899
- }
1900
- }
154
+ await pipelineExecuteFromStage4(this, txId, walletId);
1901
155
  }
1902
156
  /**
1903
- * Resume pipeline after APPROVAL tier owner sign-off (fix #246).
1904
- *
1905
- * Shared handler for all 4 approval paths:
1906
- * 1. REST API (ApprovalWorkflow.approve)
1907
- * 2. WalletConnect (WcSigningBridge -> ApprovalWorkflow.approve)
1908
- * 3. Signing SDK (SignResponseHandler.handleApprove)
1909
- * 4. Telegram Bot (TelegramBotService.handleApprove)
1910
- *
1911
- * Looks up the transaction's walletId, then delegates to executeFromStage5.
157
+ * Resume pipeline after APPROVAL tier owner sign-off.
1912
158
  */
1913
159
  handleApprovalApproved(txId) {
1914
- try {
1915
- if (!this._db)
1916
- return;
1917
- const tx = this._db.select().from(txTable).where(eq(txTable.id, txId)).get();
1918
- if (tx) {
1919
- void this.executeFromStage5(txId, tx.walletId);
1920
- }
1921
- }
1922
- catch (error) {
1923
- console.warn(`[handleApprovalApproved] Failed for ${txId}:`, error);
1924
- }
160
+ pipelineHandleApprovalApproved(this, txId);
1925
161
  }
1926
162
  /**
1927
163
  * Re-enter the pipeline at stage5 for a delay-expired transaction.
1928
- *
1929
- * Called by the delay-expired BackgroundWorker when processExpired()
1930
- * returns transactions whose cooldown has elapsed.
1931
- * Also called by handleApprovalApproved for APPROVAL tier transactions.
1932
- *
1933
- * @param txId - Transaction ID to execute
1934
- * @param walletId - Wallet that owns the transaction
1935
164
  */
1936
165
  async executeFromStage5(txId, walletId) {
1937
- try {
1938
- if (!this._db || !this.adapterPool || !this.keyStore || !this._config) {
1939
- console.warn(`executeFromStage5(${txId}): missing deps, skipping`);
1940
- return;
1941
- }
1942
- // Import stages and schema
1943
- const { stage5Execute, stage6Confirm } = await import('../pipeline/stages.js');
1944
- const { wallets, transactions } = await import('../infrastructure/database/schema.js');
1945
- const { eq } = await import('drizzle-orm');
1946
- // Look up wallet from DB
1947
- const wallet = this._db.select().from(wallets).where(eq(wallets.id, walletId)).get();
1948
- if (!wallet) {
1949
- console.warn(`executeFromStage5(${txId}): wallet ${walletId} not found`);
1950
- return;
1951
- }
1952
- // Look up transaction to get request data
1953
- const tx = this._db.select().from(transactions).where(eq(transactions.id, txId)).get();
1954
- if (!tx) {
1955
- console.warn(`executeFromStage5(${txId}): transaction not found`);
1956
- return;
1957
- }
1958
- // Use network recorded at Stage 1 (NOT re-resolve)
1959
- const resolvedNetwork = tx.network
1960
- ?? getSingleNetwork(wallet.chain, wallet.environment)
1961
- ?? (() => { throw new WAIaaSError('NETWORK_REQUIRED'); })();
1962
- // Resolve adapter from pool using recorded network
1963
- const rpcUrl = resolveRpcUrl(this._config.rpc, wallet.chain, resolvedNetwork);
1964
- const adapter = await this.adapterPool.resolve(wallet.chain, resolvedNetwork, rpcUrl);
1965
- // Restore original request from metadata (#208)
1966
- const meta = tx.metadata ? JSON.parse(tx.metadata) : {};
1967
- let request = meta.originalRequest ?? {
1968
- to: tx.toAddress ?? '',
1969
- amount: tx.amount ?? '0',
1970
- memo: undefined,
1971
- };
1972
- // Phase 321: Re-encode calldata for EIP-712 approvals (setAgentWallet)
1973
- // The original calldata has a placeholder '0x' signature. On approval,
1974
- // the Owner's real EIP-712 signature is stored in pending_approvals.
1975
- // Re-encode the calldata with the real signature before stage5Execute.
1976
- if (this.sqlite) {
1977
- const approvalRow = this.sqlite.prepare('SELECT approval_type, typed_data_json, owner_signature FROM pending_approvals WHERE tx_id = ?').get(txId);
1978
- if (approvalRow?.approval_type === 'EIP712' && approvalRow.typed_data_json && approvalRow.owner_signature) {
1979
- try {
1980
- const typedData = JSON.parse(approvalRow.typed_data_json);
1981
- const { encodeFunctionData } = await import('viem');
1982
- const { IDENTITY_REGISTRY_ABI } = await import('@waiaas/actions');
1983
- const reEncodedCalldata = encodeFunctionData({
1984
- abi: IDENTITY_REGISTRY_ABI,
1985
- functionName: 'setAgentWallet',
1986
- args: [
1987
- BigInt(typedData.message.agentId),
1988
- typedData.message.newWallet,
1989
- BigInt(typedData.message.deadline),
1990
- approvalRow.owner_signature,
1991
- ],
1992
- });
1993
- // Replace calldata in the request object
1994
- request = { ...request, calldata: reEncodedCalldata };
1995
- }
1996
- catch (err) {
1997
- console.warn(`[executeFromStage5] EIP-712 calldata re-encoding failed for ${txId}:`, err);
1998
- }
1999
- }
2000
- }
2001
- // Construct PipelineContext for stages 5-6
2002
- const ctx = {
2003
- db: this._db,
2004
- adapter,
2005
- keyStore: this.keyStore,
2006
- policyEngine: null, // Not needed for stages 5-6
2007
- masterPassword: this.masterPassword,
2008
- walletId,
2009
- wallet: {
2010
- publicKey: wallet.publicKey,
2011
- chain: wallet.chain,
2012
- environment: wallet.environment,
2013
- // #251: pass AA fields for Smart Account re-entry
2014
- accountType: wallet.accountType,
2015
- aaProvider: wallet.aaProvider,
2016
- aaProviderApiKeyEncrypted: wallet.aaProviderApiKeyEncrypted,
2017
- aaBundlerUrl: wallet.aaBundlerUrl,
2018
- aaPaymasterUrl: wallet.aaPaymasterUrl,
2019
- aaPaymasterPolicyId: wallet.aaPaymasterPolicyId,
2020
- },
2021
- resolvedNetwork,
2022
- resolvedRpcUrl: rpcUrl,
2023
- request,
2024
- txId,
2025
- eventBus: this.eventBus,
2026
- notificationService: this.notificationService ?? undefined,
2027
- };
2028
- await stage5Execute(ctx);
2029
- await stage6Confirm(ctx);
2030
- }
2031
- catch (error) {
2032
- // Mark as FAILED if stages 5-6 throw
2033
- try {
2034
- if (this._db) {
2035
- const { transactions } = await import('../infrastructure/database/schema.js');
2036
- const { eq } = await import('drizzle-orm');
2037
- const errorMessage = error instanceof Error ? error.message : 'Pipeline re-entry failed';
2038
- this._db
2039
- .update(transactions)
2040
- .set({ status: 'FAILED', error: errorMessage })
2041
- .where(eq(transactions.id, txId))
2042
- .run();
2043
- }
2044
- }
2045
- catch {
2046
- // Swallow DB update errors in background
2047
- }
2048
- }
166
+ await pipelineExecuteFromStage5(this, txId, walletId);
2049
167
  }
2050
168
  /**
2051
169
  * Acquire an exclusive daemon lock to prevent multiple instances.