@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,1898 +1,13 @@
1
1
  /**
2
- * 6-stage transaction pipeline stages.
3
- *
4
- * Stage 1: Validate request + INSERT PENDING transaction (with sessionId audit trail)
5
- * Stage 2: Auth (sessionId passthrough from route handler)
6
- * Stage 3: Policy evaluation (evaluateAndReserve TOCTOU-safe + downgradeIfNoOwner)
7
- * Stage 4: Wait (v1.1 passthrough, INSTANT tier only)
8
- * Stage 5: On-chain execution (build -> simulate -> sign -> submit)
9
- * Stage 6: Confirmation wait
10
- *
11
- * @see docs/32-pipeline-design.md
12
- */
13
- import { eq, sql } from 'drizzle-orm';
14
- import { WAIaaSError, ChainError, SendTransactionRequestSchema, TransactionRequestSchema, } from '@waiaas/core';
15
- import { wallets, transactions } from '../infrastructure/database/schema.js';
16
- import { generateId } from '../infrastructure/database/id.js';
17
- import { insertAuditLog } from '../infrastructure/database/audit-helper.js';
18
- import { DatabasePolicyEngine } from './database-policy-engine.js';
19
- import { downgradeIfNoOwner } from '../workflow/owner-state.js';
20
- import { GAS_SAFETY_NUMERATOR, GAS_SAFETY_DENOMINATOR } from '../constants.js';
21
- import { formatDisplayCurrency, formatAmount } from '@waiaas/core';
22
- import { resolveEffectiveAmountUsd } from './resolve-effective-amount-usd.js';
23
- import { sleep } from './sleep.js';
24
- import { rpcConfigKey } from '../infrastructure/adapter-pool.js';
25
- // v30.6: ERC-4337 smart account imports
26
- import { privateKeyToAccount } from 'viem/accounts';
27
- import { createPublicClient, http, encodeFunctionData, toHex } from 'viem';
28
- import { SmartAccountService, SOLADY_FACTORY_ADDRESS } from '../infrastructure/smart-account/smart-account-service.js';
29
- import { createSmartAccountBundlerClient } from '../infrastructure/smart-account/smart-account-clients.js';
30
- import { decryptProviderApiKey } from '../infrastructure/smart-account/aa-provider-crypto.js';
31
- // v1.5: CoinGecko 키 안내 힌트 최초 1회 추적 (데몬 재시작 시 리셋 OK)
32
- const hintedTokens = new Set();
33
- // Exported for test cleanup
34
- export { hintedTokens };
35
- // ---------------------------------------------------------------------------
36
- // [Phase 331] Action tier override resolution
37
- // ---------------------------------------------------------------------------
38
- /**
39
- * Resolve the effective tier for an action.
40
- * Priority: Settings override > provider hardcoded defaultTier.
41
- *
42
- * @param providerKey - Provider key, e.g. 'jupiter_swap'
43
- * @param actionName - Action name, e.g. 'swap'
44
- * @param actionDefaultTier - Provider hardcoded default tier
45
- * @param settingsService - SettingsService (optional, undefined = use default)
46
- * @returns The effective tier for this action
47
- */
48
- export function resolveActionTier(providerKey, actionName, actionDefaultTier, settingsService) {
49
- if (!settingsService)
50
- return actionDefaultTier;
51
- const tierKey = `actions.${providerKey}_${actionName}_tier`;
52
- try {
53
- const override = settingsService.get(tierKey);
54
- if (override && override !== '')
55
- return override;
56
- }
57
- catch { /* key not found -- use default */ }
58
- return actionDefaultTier;
59
- }
60
- // ---------------------------------------------------------------------------
61
- // Helper: safe request field accessors for union type
62
- // ---------------------------------------------------------------------------
63
- /** Safely extract `amount` from SendTransactionRequest | TransactionRequest. */
64
- export function getRequestAmount(req) {
65
- if ('amount' in req && typeof req.amount === 'string')
66
- return req.amount;
67
- return '0';
68
- }
69
- /** Safely extract `to` from SendTransactionRequest | TransactionRequest. */
70
- export function getRequestTo(req) {
71
- if ('to' in req && typeof req.to === 'string')
72
- return req.to;
73
- return '';
74
- }
75
- /** Safely extract `memo` from SendTransactionRequest | TransactionRequest. */
76
- export function getRequestMemo(req) {
77
- if ('memo' in req && typeof req.memo === 'string')
78
- return req.memo;
79
- return undefined;
80
- }
81
- // ---------------------------------------------------------------------------
82
- // Helper: format notification amount with token symbol
83
- // ---------------------------------------------------------------------------
84
- const NATIVE_DECIMALS = { solana: 9, ethereum: 18 };
85
- const NATIVE_SYMBOLS = { solana: 'SOL', ethereum: 'ETH' };
86
- /**
87
- * Format raw blockchain amount to human-readable string with token symbol.
88
- * e.g. "1000000000000000000" → "1 ETH", "100000000" → "100 USDC"
89
- */
90
- function formatNotificationAmount(req, chain) {
91
- const raw = getRequestAmount(req);
92
- if (raw === '0' || raw === '')
93
- return '0';
94
- try {
95
- if ('type' in req && req.type === 'TOKEN_TRANSFER') {
96
- const r = req;
97
- const decimals = r.token.decimals;
98
- const symbol = r.token.symbol ?? r.token.address.slice(0, 8);
99
- return `${formatAmount(BigInt(raw), decimals)} ${symbol}`;
100
- }
101
- if ('type' in req && req.type === 'APPROVE') {
102
- const r = req;
103
- const decimals = r.token.decimals;
104
- const symbol = r.token.symbol ?? r.token.address.slice(0, 8);
105
- return `${formatAmount(BigInt(raw), decimals)} ${symbol}`;
106
- }
107
- if ('type' in req && req.type === 'NFT_TRANSFER') {
108
- const r = req;
109
- return `${r.amount ?? '1'} NFT (${r.token.standard})`;
110
- }
111
- // Native transfer / CONTRACT_CALL with value
112
- const decimals = NATIVE_DECIMALS[chain] ?? 18;
113
- const symbol = NATIVE_SYMBOLS[chain] ?? chain.toUpperCase();
114
- return `${formatAmount(BigInt(raw), decimals)} ${symbol}`;
115
- }
116
- catch {
117
- return raw;
118
- }
119
- }
120
- // ---------------------------------------------------------------------------
121
- // Helper: resolve display amount for notification messages
122
- // ---------------------------------------------------------------------------
123
- /**
124
- * Convert amountUsd to display currency string for notification variables.
125
- * Returns empty string on failure (graceful fallback -- no display_amount in message).
126
- */
127
- async function resolveDisplayAmount(amountUsd, settingsService, forexRateService) {
128
- if (!amountUsd || !settingsService || !forexRateService)
129
- return '';
130
- try {
131
- const currency = settingsService.get('display.currency') ?? 'USD';
132
- if (currency === 'USD')
133
- return `($${amountUsd.toFixed(2)})`;
134
- const rate = await forexRateService.getRate(currency);
135
- if (!rate)
136
- return `($${amountUsd.toFixed(2)})`;
137
- return `(${formatDisplayCurrency(amountUsd, currency, rate.rate)})`;
138
- }
139
- catch {
140
- return '';
141
- }
142
- }
143
- // ---------------------------------------------------------------------------
144
- // Helper: extract policy type from evaluation reason string
145
- // ---------------------------------------------------------------------------
146
- function extractPolicyType(reason) {
147
- if (!reason)
148
- return '';
149
- if (reason.includes('not in allowed list') || reason.includes('Token transfer not allowed'))
150
- return 'ALLOWED_TOKENS';
151
- if (reason.includes('not whitelisted') || reason.includes('Contract calls disabled'))
152
- return 'CONTRACT_WHITELIST';
153
- if (reason.includes('Method not whitelisted'))
154
- return 'METHOD_WHITELIST';
155
- if (reason.includes('not in approved list') || reason.includes('Token approvals disabled'))
156
- return 'APPROVED_SPENDERS';
157
- if (reason.includes('not in whitelist') || reason.includes('not in allowed addresses'))
158
- return 'WHITELIST';
159
- if (reason.includes('not in allowed networks'))
160
- return 'ALLOWED_NETWORKS';
161
- if (reason.includes('exceeds limit') || reason.includes('Unlimited token approval'))
162
- return 'APPROVE_AMOUNT_LIMIT';
163
- if (reason.includes('Spending limit'))
164
- return 'SPENDING_LIMIT';
165
- return '';
166
- }
167
- export function buildTransactionParam(req, txType, chain) {
168
- switch (txType) {
169
- case 'TOKEN_TRANSFER': {
170
- const r = req;
171
- return {
172
- type: 'TOKEN_TRANSFER',
173
- amount: r.amount,
174
- toAddress: r.to,
175
- chain,
176
- tokenAddress: r.token.address,
177
- assetId: r.token.assetId,
178
- tokenDecimals: r.token.decimals,
179
- };
180
- }
181
- case 'CONTRACT_CALL': {
182
- const r = req;
183
- return {
184
- type: 'CONTRACT_CALL',
185
- amount: r.value ?? '0',
186
- toAddress: r.to,
187
- chain,
188
- contractAddress: r.to,
189
- selector: r.calldata?.slice(0, 10),
190
- // Pass through actionProvider for provider-trust policy bypass
191
- ...(r.actionProvider ? { actionProvider: r.actionProvider } : {}),
192
- };
193
- }
194
- case 'APPROVE': {
195
- const r = req;
196
- return {
197
- type: 'APPROVE',
198
- amount: r.amount,
199
- toAddress: r.spender,
200
- chain,
201
- tokenAddress: r.token.address,
202
- assetId: r.token.assetId,
203
- spenderAddress: r.spender,
204
- approveAmount: r.amount,
205
- tokenDecimals: r.token.decimals,
206
- };
207
- }
208
- case 'NFT_TRANSFER': {
209
- const r = req;
210
- return {
211
- type: 'NFT_TRANSFER',
212
- amount: r.amount ?? '1',
213
- toAddress: r.to,
214
- chain,
215
- contractAddress: r.token.address,
216
- assetId: r.token.assetId,
217
- };
218
- }
219
- case 'CONTRACT_DEPLOY': {
220
- const r = req;
221
- return {
222
- type: 'CONTRACT_DEPLOY',
223
- amount: r.value ?? '0',
224
- toAddress: '', // no recipient for contract deployment
225
- chain,
226
- contractAddress: '', // will be populated after deployment
227
- };
228
- }
229
- case 'TRANSFER':
230
- default: {
231
- const r = req;
232
- return {
233
- type: 'TRANSFER',
234
- amount: r.amount,
235
- toAddress: r.to,
236
- chain,
237
- };
238
- }
239
- }
240
- }
241
- // ---------------------------------------------------------------------------
242
- // Stage 1: Validate + DB INSERT
243
- // ---------------------------------------------------------------------------
244
- export async function stage1Validate(ctx) {
245
- // Validate request with appropriate Zod schema
246
- // If request has a `type` field, use discriminatedUnion schema (5-type)
247
- // Otherwise, use legacy SendTransactionRequestSchema (backward compat)
248
- const req = ctx.request;
249
- if ('type' in req && req.type) {
250
- TransactionRequestSchema.parse(req);
251
- }
252
- else {
253
- SendTransactionRequestSchema.parse(req);
254
- }
255
- // Determine transaction type from request
256
- const txType = ('type' in req && req.type) ? req.type : 'TRANSFER';
257
- // Generate transaction ID
258
- ctx.txId = generateId();
259
- // INSERT PENDING transaction into DB
260
- const now = new Date(Math.floor(Date.now() / 1000) * 1000);
261
- // Extract common and type-specific fields for DB INSERT
262
- let amount = 'amount' in req ? req.amount : undefined;
263
- // CONTRACT_CALL uses 'value' for native token amount (e.g. ETH sent with contract call)
264
- if (!amount && 'value' in req) {
265
- amount = req.value;
266
- }
267
- const toAddress = 'to' in req ? req.to : undefined;
268
- // Serialize original request for pipeline re-entry (#208)
269
- // DELAY/GAS_WAITING re-entry needs the full request to rebuild the correct tx type
270
- const metadata = JSON.stringify({ originalRequest: req });
271
- await ctx.db.insert(transactions).values({
272
- id: ctx.txId,
273
- walletId: ctx.walletId,
274
- chain: ctx.wallet.chain,
275
- network: ctx.resolvedNetwork,
276
- type: txType,
277
- status: 'PENDING',
278
- amount: amount ?? null,
279
- toAddress: toAddress ?? null,
280
- sessionId: ctx.sessionId ?? null,
281
- createdAt: now,
282
- metadata,
283
- });
284
- // Fire-and-forget: notify TX_REQUESTED (never blocks pipeline)
285
- // display_amount is empty at Stage 1 -- amountUsd not yet computed
286
- void ctx.notificationService?.notify('TX_REQUESTED', ctx.walletId, {
287
- amount: formatNotificationAmount(ctx.request, ctx.wallet.chain),
288
- to: toAddress ?? '',
289
- type: txType,
290
- display_amount: '',
291
- }, { txId: ctx.txId });
292
- // v1.6: emit wallet:activity TX_REQUESTED event
293
- ctx.eventBus?.emit('wallet:activity', {
294
- walletId: ctx.walletId,
295
- activity: 'TX_REQUESTED',
296
- details: { txId: ctx.txId },
297
- timestamp: Math.floor(Date.now() / 1000),
298
- });
299
- }
300
- // ---------------------------------------------------------------------------
301
- // Stage 2: Auth (v1.1 passthrough)
302
- // ---------------------------------------------------------------------------
303
- export async function stage2Auth(_ctx) {
304
- // sessionId is set on PipelineContext by the route handler from Hono c.get('sessionId').
305
- // In v1.2 this stage validates session is still active.
306
- // For now, the sessionAuth middleware already validated the JWT and set sessionId.
307
- }
308
- // ---------------------------------------------------------------------------
309
- // Stage 3: Policy evaluation
310
- // ---------------------------------------------------------------------------
311
- export async function stage3Policy(ctx) {
312
- let evaluation;
313
- // Determine transaction type from request
314
- const req = ctx.request;
315
- const txType = ('type' in req && req.type) ? req.type : 'TRANSFER';
316
- // Build type-specific TransactionParam (hoisted for notification use)
317
- const txParam = buildTransactionParam(req, txType, ctx.wallet.chain);
318
- txParam.network = ctx.resolvedNetwork;
319
- // [Phase 127] Oracle HTTP 호출 (evaluateAndReserve 진입 전 완료)
320
- let priceResult;
321
- if (ctx.priceOracle) {
322
- priceResult = await resolveEffectiveAmountUsd(req, txType, ctx.wallet.chain, ctx.priceOracle, ctx.resolvedNetwork);
323
- }
324
- // BATCH type uses evaluateBatch (2-stage policy evaluation)
325
- if (txType === 'BATCH' && ctx.policyEngine instanceof DatabasePolicyEngine) {
326
- const batchReq = req;
327
- // Classify each instruction and build TransactionParam array
328
- const params = batchReq.instructions.map((instr) => {
329
- let instrType = 'TRANSFER';
330
- if ('spender' in instr)
331
- instrType = 'APPROVE';
332
- else if ('token' in instr)
333
- instrType = 'TOKEN_TRANSFER';
334
- else if ('programId' in instr || 'calldata' in instr)
335
- instrType = 'CONTRACT_CALL';
336
- return {
337
- type: instrType,
338
- amount: 'amount' in instr ? instr.amount ?? '0' : '0',
339
- toAddress: 'to' in instr ? instr.to ?? '' : '',
340
- chain: ctx.wallet.chain,
341
- network: ctx.resolvedNetwork,
342
- tokenAddress: 'token' in instr ? instr.token?.address : undefined,
343
- assetId: 'token' in instr ? instr.token?.assetId : undefined,
344
- contractAddress: instrType === 'CONTRACT_CALL' ? ('to' in instr ? instr.to : undefined) : undefined,
345
- selector: 'calldata' in instr ? instr.calldata?.slice(0, 10) : undefined,
346
- spenderAddress: 'spender' in instr ? instr.spender : undefined,
347
- approveAmount: instrType === 'APPROVE' && 'amount' in instr ? instr.amount : undefined,
348
- };
349
- });
350
- // evaluateBatch에 batchUsdAmount 전달 (Phase 127)
351
- const batchUsdAmount = priceResult?.type === 'success' ? priceResult.usdAmount : undefined;
352
- evaluation = await ctx.policyEngine.evaluateBatch(ctx.walletId, params, batchUsdAmount);
353
- }
354
- else {
355
- // evaluateAndReserve에 usdAmount 전달 (Phase 127)
356
- const usdAmount = priceResult?.type === 'success' ? priceResult.usdAmount : undefined;
357
- // [Phase 320] Pre-fetch reputation floor tier (async, before IMMEDIATE txn)
358
- let reputationFloorTier;
359
- if (ctx.reputationCache && ctx.policyEngine instanceof DatabasePolicyEngine) {
360
- const prefetchResult = await ctx.policyEngine.prefetchReputationTier(ctx.walletId, txParam, ctx.reputationCache);
361
- reputationFloorTier = prefetchResult?.tier;
362
- // INT-01 fix: Emit REPUTATION_THRESHOLD_TRIGGERED when tier is escalated.
363
- // This fires before the evaluation result is used, so the notification goes out
364
- // regardless of whether the transaction is ultimately allowed or denied.
365
- if (prefetchResult) {
366
- void ctx.notificationService?.notify('REPUTATION_THRESHOLD_TRIGGERED', ctx.walletId, {
367
- tier: prefetchResult.tier,
368
- score: prefetchResult.score ?? '',
369
- threshold: prefetchResult.threshold ?? '',
370
- }, { txId: ctx.txId });
371
- }
372
- }
373
- // Use evaluateAndReserve for TOCTOU-safe evaluation when DatabasePolicyEngine + sqlite available
374
- if (ctx.policyEngine instanceof DatabasePolicyEngine && ctx.sqlite) {
375
- evaluation = ctx.policyEngine.evaluateAndReserve(ctx.walletId, txParam, ctx.txId, usdAmount, reputationFloorTier);
376
- }
377
- else {
378
- evaluation = await ctx.policyEngine.evaluate(ctx.walletId, txParam);
379
- }
380
- }
381
- if (!evaluation.allowed) {
382
- // Update tx status to CANCELLED (REJECTED not in TRANSACTION_STATUSES enum)
383
- await ctx.db
384
- .update(transactions)
385
- .set({ status: 'CANCELLED', error: evaluation.reason ?? 'Policy denied' })
386
- .where(eq(transactions.id, ctx.txId));
387
- // Fire-and-forget: notify POLICY_VIOLATION (never blocks pipeline)
388
- void ctx.notificationService?.notify('POLICY_VIOLATION', ctx.walletId, {
389
- reason: evaluation.reason ?? 'Policy denied',
390
- amount: formatNotificationAmount(ctx.request, ctx.wallet.chain),
391
- to: getRequestTo(ctx.request),
392
- policyType: extractPolicyType(evaluation.reason),
393
- tokenAddress: txParam.tokenAddress ?? '',
394
- contractAddress: txParam.contractAddress ?? '',
395
- adminLink: '/admin/policies',
396
- }, { txId: ctx.txId });
397
- // Audit log: POLICY_DENIED
398
- if (ctx.sqlite) {
399
- insertAuditLog(ctx.sqlite, {
400
- eventType: 'POLICY_DENIED',
401
- actor: ctx.sessionId ?? 'system',
402
- walletId: ctx.walletId,
403
- txId: ctx.txId,
404
- details: {
405
- reason: evaluation.reason,
406
- requestedAmount: getRequestAmount(ctx.request),
407
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
408
- },
409
- severity: 'warning',
410
- });
411
- }
412
- throw new WAIaaSError('POLICY_DENIED', {
413
- message: evaluation.reason ?? 'Transaction denied by policy',
414
- });
415
- }
416
- let tier = evaluation.tier;
417
- let downgraded = false;
418
- // [Phase 331] Action tier override: Settings > provider default > no floor
419
- if (ctx.actionProviderKey && ctx.actionName && ctx.actionDefaultTier) {
420
- const actionTier = resolveActionTier(ctx.actionProviderKey, ctx.actionName, ctx.actionDefaultTier, ctx.settingsService);
421
- // Action tier acts as a FLOOR -- escalate but never downgrade
422
- const TIER_ORDER_331 = ['INSTANT', 'NOTIFY', 'DELAY', 'APPROVAL'];
423
- if (TIER_ORDER_331.indexOf(actionTier) > TIER_ORDER_331.indexOf(tier)) {
424
- tier = actionTier;
425
- }
426
- }
427
- // [Phase 127] PriceResult에 따른 후처리
428
- if (priceResult?.type === 'notListed') {
429
- // Audit log: UNLISTED_TOKEN_TRANSFER (refactored to insertAuditLog)
430
- if (ctx.sqlite) {
431
- insertAuditLog(ctx.sqlite, {
432
- eventType: 'UNLISTED_TOKEN_TRANSFER',
433
- actor: ctx.sessionId ?? 'system',
434
- walletId: ctx.walletId,
435
- txId: ctx.txId,
436
- details: {
437
- tokenAddress: priceResult.tokenAddress,
438
- chain: priceResult.chain,
439
- failedCount: priceResult.failedCount,
440
- },
441
- severity: 'warning',
442
- });
443
- }
444
- // 최소 NOTIFY 격상 (evaluation tier와 NOTIFY 중 보수적)
445
- const TIER_ORDER = ['INSTANT', 'NOTIFY', 'DELAY', 'APPROVAL'];
446
- const currentIdx = TIER_ORDER.indexOf(tier);
447
- const notifyIdx = TIER_ORDER.indexOf('NOTIFY');
448
- if (currentIdx < notifyIdx) {
449
- tier = 'NOTIFY';
450
- }
451
- // CoinGecko 키 미설정 + 최초 1회 힌트
452
- const cacheKey = `${priceResult.chain}:${priceResult.tokenAddress}`;
453
- const coingeckoKey = ctx.settingsService?.get('oracle.coingecko_api_key');
454
- const shouldShowHint = !coingeckoKey && !hintedTokens.has(cacheKey);
455
- if (shouldShowHint) {
456
- hintedTokens.add(cacheKey);
457
- }
458
- // 가격 불명 토큰 알림 발송 (allowed=true인 상태에서도 notListed는 발생)
459
- const hint = shouldShowHint
460
- ? 'CoinGecko API 키를 설정하면 이 토큰의 USD 가격을 조회할 수 있습니다. Admin Settings > Oracle에서 설정하세요.'
461
- : undefined;
462
- const notifyVars = {
463
- amount: formatNotificationAmount(ctx.request, ctx.wallet.chain),
464
- to: getRequestTo(ctx.request),
465
- reason: `가격 불명 토큰 (${priceResult.tokenAddress}) -- 최소 NOTIFY 격상`,
466
- policyType: 'SPENDING_LIMIT',
467
- };
468
- if (hint)
469
- notifyVars.hint = hint;
470
- void ctx.notificationService?.notify('POLICY_VIOLATION', ctx.walletId, notifyVars, { txId: ctx.txId });
471
- }
472
- // oracleDown: 아무것도 하지 않음 -- 네이티브 금액 기준 tier가 이미 설정됨
473
- // Check for APPROVAL -> DELAY downgrade when no owner registered
474
- if (tier === 'APPROVAL') {
475
- const walletRow = await ctx.db.select().from(wallets).where(eq(wallets.id, ctx.walletId)).get();
476
- if (walletRow) {
477
- const result = downgradeIfNoOwner({
478
- ownerAddress: walletRow.ownerAddress ?? null,
479
- ownerVerified: walletRow.ownerVerified ?? false,
480
- }, tier);
481
- tier = result.tier;
482
- downgraded = result.downgraded;
483
- }
484
- }
485
- // Set tier and metadata on context
486
- ctx.tier = tier;
487
- ctx.downgraded = downgraded;
488
- if (evaluation.delaySeconds !== undefined) {
489
- ctx.delaySeconds = evaluation.delaySeconds;
490
- }
491
- // [Phase 139] Cache amountUsd on context for Stage 5/6 display_amount
492
- const stageAmountUsd = priceResult?.type === 'success' ? priceResult.usdAmount : undefined;
493
- ctx.amountUsd = stageAmountUsd;
494
- // [Phase 139] Resolve display amount for notifications
495
- const displayAmount = await resolveDisplayAmount(stageAmountUsd ?? null, ctx.settingsService, ctx.forexRateService);
496
- // [Phase 136] Cumulative spending warning notification (80% threshold)
497
- if (evaluation.cumulativeWarning) {
498
- const w = evaluation.cumulativeWarning;
499
- void ctx.notificationService?.notify('CUMULATIVE_LIMIT_WARNING', ctx.walletId, {
500
- type: w.type,
501
- spent: String(w.spent.toFixed(2)),
502
- limit: String(w.limit.toFixed(2)),
503
- ratio: String(Math.round(w.ratio * 100)),
504
- display_amount: displayAmount,
505
- }, { txId: ctx.txId });
506
- }
507
- // [Phase 136] APPROVAL tier notification with reason
508
- if (tier === 'APPROVAL' && !downgraded) {
509
- const reason = evaluation.approvalReason ?? 'per_tx';
510
- void ctx.notificationService?.notify('TX_APPROVAL_REQUIRED', ctx.walletId, {
511
- txId: ctx.txId,
512
- amount: formatNotificationAmount(ctx.request, ctx.wallet.chain),
513
- to: getRequestTo(ctx.request),
514
- reason,
515
- display_amount: displayAmount,
516
- }, { txId: ctx.txId });
517
- }
518
- // Update DB with tier
519
- await ctx.db
520
- .update(transactions)
521
- .set({ tier })
522
- .where(eq(transactions.id, ctx.txId));
523
- }
524
- // ---------------------------------------------------------------------------
525
- // Stage 3.5: Gas condition check (between policy and wait)
526
- // ---------------------------------------------------------------------------
527
- /**
528
- * Stage 3.5: Gas condition check.
529
- *
530
- * If the request includes `gasCondition` and the `gas_condition.enabled` setting
531
- * is true (defaults to true when settings key is not yet registered):
532
- * 1. Check max_pending_count limit against current GAS_WAITING transactions
533
- * 2. Set status='GAS_WAITING', store gasCondition metadata in bridgeMetadata
534
- * 3. Emit TX_GAS_WAITING notification
535
- * 4. Throw PIPELINE_HALTED (transaction will be picked up by GasConditionTracker)
536
- *
537
- * If gasCondition is absent: no-op (backward compat -- proceed to stage4Wait).
538
- *
539
- * @see 258-CONTEXT.md for architecture details
540
- */
541
- export async function stage3_5GasCondition(ctx) {
542
- const req = ctx.request;
543
- // Check if request has gasCondition (present on all 5 discriminatedUnion types)
544
- const gasCondition = ('gasCondition' in req && req.gasCondition)
545
- ? req.gasCondition
546
- : undefined;
547
- if (!gasCondition) {
548
- // No gas condition specified: proceed normally (backward compat)
549
- return;
550
- }
551
- // Check if gas_condition feature is enabled via settings
552
- // Default to true if the setting key is not yet registered (258-02 adds it)
553
- let gasConditionEnabled = true;
554
- if (ctx.settingsService) {
555
- try {
556
- const enabledValue = ctx.settingsService.get('gas_condition.enabled');
557
- gasConditionEnabled = enabledValue !== 'false';
558
- }
559
- catch {
560
- // Setting key not yet registered (will be added in 258-02) -- default to true
561
- gasConditionEnabled = true;
562
- }
563
- }
564
- if (!gasConditionEnabled) {
565
- // Feature disabled: proceed normally, ignore gasCondition
566
- return;
567
- }
568
- // Check max_pending_count limit
569
- let maxPendingCount = 100;
570
- if (ctx.settingsService) {
571
- try {
572
- const maxValue = ctx.settingsService.get('gas_condition.max_pending_count');
573
- const parsed = parseInt(maxValue, 10);
574
- if (!isNaN(parsed) && parsed > 0) {
575
- maxPendingCount = parsed;
576
- }
577
- }
578
- catch {
579
- // Setting key not yet registered -- use default
580
- }
581
- }
582
- // Count current GAS_WAITING transactions
583
- const countResult = ctx.db
584
- .select({ count: sql `count(*)` })
585
- .from(transactions)
586
- .where(eq(transactions.status, 'GAS_WAITING'))
587
- .get();
588
- const currentWaiting = countResult?.count ?? 0;
589
- if (currentWaiting >= maxPendingCount) {
590
- throw new WAIaaSError('ACTION_VALIDATION_FAILED', {
591
- message: `Gas condition pending limit reached (${currentWaiting}/${maxPendingCount}). Try again later.`,
592
- });
593
- }
594
- // Resolve timeout: request.timeout > settings default > hardcoded 3600
595
- let timeout = gasCondition.timeout;
596
- if (!timeout) {
597
- if (ctx.settingsService) {
598
- try {
599
- const defaultTimeout = ctx.settingsService.get('gas_condition.default_timeout_sec');
600
- const parsed = parseInt(defaultTimeout, 10);
601
- if (!isNaN(parsed) && parsed >= 60 && parsed <= 86400) {
602
- timeout = parsed;
603
- }
604
- }
605
- catch {
606
- // Setting key not yet registered
607
- }
608
- }
609
- if (!timeout)
610
- timeout = 3600;
611
- }
612
- // Clamp timeout to max_timeout_sec
613
- let maxTimeout = 86400;
614
- if (ctx.settingsService) {
615
- try {
616
- const maxValue = ctx.settingsService.get('gas_condition.max_timeout_sec');
617
- const parsed = parseInt(maxValue, 10);
618
- if (!isNaN(parsed) && parsed >= 60) {
619
- maxTimeout = parsed;
620
- }
621
- }
622
- catch {
623
- // Setting key not yet registered
624
- }
625
- }
626
- if (timeout > maxTimeout) {
627
- timeout = maxTimeout;
628
- }
629
- // Resolve RPC URL for GasConditionTracker (raw fetch, no adapter dependency)
630
- let rpcUrl = '';
631
- if (ctx.settingsService) {
632
- try {
633
- rpcUrl = ctx.settingsService.get(`rpc.${rpcConfigKey(ctx.wallet.chain, ctx.resolvedNetwork)}`);
634
- }
635
- catch {
636
- // Setting key not found -- tracker will skip this TX
637
- }
638
- }
639
- // Store gas condition metadata in bridgeMetadata for GasConditionTracker (258-02)
640
- const gasConditionMeta = {
641
- tracker: 'gas-condition',
642
- gasCondition: {
643
- maxGasPrice: gasCondition.maxGasPrice,
644
- maxPriorityFee: gasCondition.maxPriorityFee,
645
- timeout,
646
- },
647
- chain: ctx.wallet.chain,
648
- network: ctx.resolvedNetwork,
649
- rpcUrl,
650
- gasConditionCreatedAt: Date.now(),
651
- };
652
- // Set status='GAS_WAITING', store bridgeMetadata
653
- await ctx.db
654
- .update(transactions)
655
- .set({
656
- status: 'GAS_WAITING',
657
- bridgeMetadata: JSON.stringify(gasConditionMeta),
658
- })
659
- .where(eq(transactions.id, ctx.txId));
660
- // Fire-and-forget: notify TX_GAS_WAITING
661
- void ctx.notificationService?.notify('TX_GAS_WAITING', ctx.walletId, {
662
- txId: ctx.txId,
663
- maxGasPrice: gasCondition.maxGasPrice ?? '',
664
- maxPriorityFee: gasCondition.maxPriorityFee ?? '',
665
- timeout: String(timeout),
666
- chain: ctx.wallet.chain,
667
- network: ctx.resolvedNetwork,
668
- }, { txId: ctx.txId });
669
- // Halt pipeline -- transaction will be picked up by GasConditionTracker
670
- throw new WAIaaSError('PIPELINE_HALTED', {
671
- message: `Transaction ${ctx.txId} waiting for gas condition (timeout: ${timeout}s)`,
672
- });
673
- }
674
- // ---------------------------------------------------------------------------
675
- // Stage 4: Wait (DELAY/APPROVAL branching, INSTANT/NOTIFY passthrough)
676
- // ---------------------------------------------------------------------------
677
- export async function stage4Wait(ctx) {
678
- const tier = ctx.tier;
679
- // INSTANT and NOTIFY: pass through to stage5
680
- if (tier === 'INSTANT' || tier === 'NOTIFY') {
681
- return;
682
- }
683
- // DELAY: queue with cooldown, halt pipeline
684
- if (tier === 'DELAY') {
685
- if (!ctx.delayQueue) {
686
- // Fallback: if no DelayQueue, treat as INSTANT (backward compat)
687
- return;
688
- }
689
- const delaySeconds = ctx.delaySeconds
690
- ?? ctx.config?.policy_defaults_delay_seconds
691
- ?? 60;
692
- ctx.delayQueue.queueDelay(ctx.txId, delaySeconds);
693
- // Fire-and-forget: notify TX_QUEUED with cancel keyboard data
694
- void ctx.notificationService?.notify('TX_QUEUED', ctx.walletId, {
695
- txId: ctx.txId,
696
- amount: formatNotificationAmount(ctx.request, ctx.wallet.chain),
697
- to: getRequestTo(ctx.request),
698
- delaySeconds: String(delaySeconds),
699
- }, { txId: ctx.txId });
700
- // Halt pipeline -- transaction will be picked up by processExpired worker
701
- throw new WAIaaSError('PIPELINE_HALTED', {
702
- message: `Transaction ${ctx.txId} queued for ${delaySeconds}s delay`,
703
- });
704
- }
705
- // APPROVAL: create pending approval, halt pipeline
706
- if (tier === 'APPROVAL') {
707
- if (!ctx.approvalWorkflow) {
708
- // Fallback: if no ApprovalWorkflow, treat as INSTANT (backward compat)
709
- return;
710
- }
711
- // Pass EIP-712 metadata to requestApproval if present (Phase 321)
712
- ctx.approvalWorkflow.requestApproval(ctx.txId, ctx.eip712Metadata ? {
713
- approvalType: ctx.eip712Metadata.approvalType,
714
- typedDataJson: ctx.eip712Metadata.typedDataJson,
715
- } : undefined);
716
- // Route approval to the correct signing channel
717
- if (ctx.approvalChannelRouter) {
718
- // v2.6.1+: use ApprovalChannelRouter to determine the correct channel
719
- void (async () => {
720
- try {
721
- const result = await ctx.approvalChannelRouter.route(ctx.walletId, {
722
- walletId: ctx.walletId,
723
- txId: ctx.txId,
724
- chain: ctx.wallet.chain,
725
- network: ctx.resolvedNetwork,
726
- type: ctx.request.type ?? 'TRANSFER',
727
- from: ctx.wallet.publicKey,
728
- to: getRequestTo(ctx.request),
729
- amount: getRequestAmount(ctx.request),
730
- policyTier: 'APPROVAL',
731
- approvalType: ctx.eip712Metadata?.approvalType,
732
- });
733
- // Only invoke WC bridge when the router selects walletconnect
734
- if (result.method === 'walletconnect' && ctx.wcSigningBridge) {
735
- void ctx.wcSigningBridge.requestSignature(ctx.walletId, ctx.txId, ctx.wallet.chain);
736
- }
737
- }
738
- catch {
739
- // Channel routing errors are non-fatal; pipeline already halted
740
- }
741
- })();
742
- }
743
- else if (ctx.wcSigningBridge) {
744
- // Legacy: no router available, fall back to direct WC bridge
745
- void ctx.wcSigningBridge.requestSignature(ctx.walletId, ctx.txId, ctx.wallet.chain);
746
- }
747
- // Halt pipeline -- transaction will be picked up by approve/reject/expire
748
- throw new WAIaaSError('PIPELINE_HALTED', {
749
- message: `Transaction ${ctx.txId} queued for owner approval`,
750
- });
751
- }
752
- }
753
- // ---------------------------------------------------------------------------
754
- // Helper: buildByType -- route to correct adapter method based on request.type
755
- // ---------------------------------------------------------------------------
756
- /**
757
- * Build unsigned transaction by dispatching to the correct IChainAdapter method
758
- * based on request.type (TRANSFER/TOKEN_TRANSFER/CONTRACT_CALL/APPROVE/BATCH).
759
- */
760
- export async function buildByType(adapter, request, walletPublicKey) {
761
- const type = ('type' in request && request.type) || 'TRANSFER';
762
- switch (type) {
763
- case 'TRANSFER': {
764
- return adapter.buildTransaction({
765
- from: walletPublicKey,
766
- to: getRequestTo(request),
767
- amount: BigInt(getRequestAmount(request)),
768
- memo: getRequestMemo(request),
769
- });
770
- }
771
- case 'TOKEN_TRANSFER': {
772
- const req = request;
773
- return adapter.buildTokenTransfer({
774
- from: walletPublicKey,
775
- to: req.to,
776
- amount: BigInt(req.amount),
777
- token: req.token,
778
- memo: req.memo,
779
- });
780
- }
781
- case 'CONTRACT_CALL': {
782
- const req = request;
783
- return adapter.buildContractCall({
784
- from: walletPublicKey,
785
- to: req.to,
786
- calldata: req.calldata,
787
- abi: req.abi,
788
- value: req.value ? BigInt(req.value) : undefined,
789
- programId: req.programId,
790
- instructionData: req.instructionData
791
- ? Buffer.from(req.instructionData, 'base64')
792
- : undefined,
793
- accounts: req.accounts,
794
- // Pass through preInstructions for Solana (e.g., ATA creation for Jito staking)
795
- preInstructions: req.preInstructions?.map((pre) => ({
796
- programId: pre.programId,
797
- data: Buffer.from(pre.data, 'base64'),
798
- accounts: pre.accounts,
799
- })),
800
- });
801
- }
802
- case 'APPROVE': {
803
- const req = request;
804
- // v31.0: NFT approval routing
805
- if (req.nft) {
806
- const approvalType = req.amount === '0' ? 'single' : 'all';
807
- return adapter.approveNft({
808
- from: walletPublicKey,
809
- spender: req.spender,
810
- token: {
811
- address: req.token.address,
812
- tokenId: req.nft.tokenId,
813
- standard: req.nft.standard,
814
- },
815
- approvalType,
816
- });
817
- }
818
- return adapter.buildApprove({
819
- from: walletPublicKey,
820
- spender: req.spender,
821
- token: req.token,
822
- amount: BigInt(req.amount),
823
- });
824
- }
825
- case 'NFT_TRANSFER': {
826
- const req = request;
827
- return adapter.buildNftTransferTx({
828
- from: walletPublicKey,
829
- to: req.to,
830
- token: {
831
- address: req.token.address,
832
- tokenId: req.token.tokenId,
833
- standard: req.token.standard,
834
- },
835
- amount: BigInt(req.amount ?? '1'),
836
- });
837
- }
838
- case 'CONTRACT_DEPLOY': {
839
- const req = request;
840
- // Contract deployment: to=undefined, data=bytecode(+constructorArgs)
841
- const deployData = req.constructorArgs
842
- ? req.bytecode + req.constructorArgs.replace(/^0x/, '')
843
- : req.bytecode;
844
- return adapter.buildContractCall({
845
- from: walletPublicKey,
846
- to: '', // adapter handles to='' as to=undefined for deploy
847
- calldata: deployData,
848
- value: req.value ? BigInt(req.value) : undefined,
849
- });
850
- }
851
- case 'BATCH': {
852
- const req = request;
853
- return adapter.buildBatch({
854
- from: walletPublicKey,
855
- instructions: req.instructions.map((instr) => {
856
- // Classify by field presence (same logic as classifyInstruction in Phase 80)
857
- if ('spender' in instr) {
858
- const a = instr;
859
- return {
860
- from: walletPublicKey,
861
- spender: a.spender,
862
- token: a.token,
863
- amount: BigInt(a.amount),
864
- };
865
- }
866
- if ('token' in instr) {
867
- const t = instr;
868
- return {
869
- from: walletPublicKey,
870
- to: t.to,
871
- amount: BigInt(t.amount),
872
- token: t.token,
873
- memo: t.memo,
874
- };
875
- }
876
- if ('programId' in instr || 'calldata' in instr) {
877
- const c = instr;
878
- return {
879
- from: walletPublicKey,
880
- to: c.to,
881
- calldata: c.calldata,
882
- programId: c.programId,
883
- instructionData: c.instructionData
884
- ? Buffer.from(c.instructionData, 'base64')
885
- : undefined,
886
- accounts: c.accounts,
887
- value: c.value ? BigInt(c.value) : undefined,
888
- };
889
- }
890
- // Default: TRANSFER instruction
891
- const tr = instr;
892
- return {
893
- from: walletPublicKey,
894
- to: tr.to,
895
- amount: BigInt(tr.amount),
896
- memo: tr.memo,
897
- };
898
- }),
899
- });
900
- }
901
- default:
902
- throw new WAIaaSError('CHAIN_ERROR', {
903
- message: `Unknown transaction type: ${type}`,
904
- });
905
- }
906
- }
907
- // ---------------------------------------------------------------------------
908
- // Stage 5: Smart account ERC-4337 UserOperation helpers
909
- // ---------------------------------------------------------------------------
910
- /**
911
- * Minimal ERC-20 ABI for transfer/approve encoding in UserOperation calls.
912
- * Inline to avoid cross-package import from @waiaas/adapters-evm.
913
- */
914
- const ERC20_USEROP_ABI = [
915
- { type: 'function', name: 'transfer', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
916
- { type: 'function', name: 'approve', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
917
- ];
918
- /** ERC-721 ABI for NFT UserOp calls (safeTransferFrom, approve, setApprovalForAll). */
919
- export const ERC721_USEROP_ABI = [
920
- { type: 'function', name: 'safeTransferFrom', inputs: [
921
- { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'tokenId', type: 'uint256' },
922
- ], outputs: [] },
923
- { type: 'function', name: 'approve', inputs: [
924
- { name: 'to', type: 'address' }, { name: 'tokenId', type: 'uint256' },
925
- ], outputs: [] },
926
- { type: 'function', name: 'setApprovalForAll', inputs: [
927
- { name: 'operator', type: 'address' }, { name: 'approved', type: 'bool' },
928
- ], outputs: [] },
929
- ];
930
- /** ERC-1155 ABI for NFT UserOp calls (safeTransferFrom, setApprovalForAll). */
931
- export const ERC1155_USEROP_ABI = [
932
- { type: 'function', name: 'safeTransferFrom', inputs: [
933
- { name: 'from', type: 'address' }, { name: 'to', type: 'address' },
934
- { name: 'id', type: 'uint256' }, { name: 'amount', type: 'uint256' }, { name: 'data', type: 'bytes' },
935
- ], outputs: [] },
936
- { type: 'function', name: 'setApprovalForAll', inputs: [
937
- { name: 'operator', type: 'address' }, { name: 'approved', type: 'bool' },
938
- ], outputs: [] },
939
- ];
940
- /**
941
- * Convert a TransactionRequest to viem's calls[] format for UserOperation submission.
942
- * Each call is { to, value, data } for the smart account to execute.
943
- *
944
- * @param walletAddress - Smart account address (required for NFT_TRANSFER safeTransferFrom 'from' param)
945
- */
946
- export function buildUserOpCalls(request, walletAddress) {
947
- const type = ('type' in request && request.type) || 'TRANSFER';
948
- switch (type) {
949
- case 'TRANSFER': {
950
- return [{
951
- to: getRequestTo(request),
952
- value: BigInt(getRequestAmount(request)),
953
- data: '0x',
954
- }];
955
- }
956
- case 'TOKEN_TRANSFER': {
957
- const req = request;
958
- return [{
959
- to: req.token.address,
960
- value: 0n,
961
- data: encodeFunctionData({
962
- abi: ERC20_USEROP_ABI,
963
- functionName: 'transfer',
964
- args: [req.to, BigInt(req.amount)],
965
- }),
966
- }];
967
- }
968
- case 'CONTRACT_CALL': {
969
- const req = request;
970
- return [{
971
- to: req.to,
972
- value: BigInt(req.value ?? '0'),
973
- data: (req.calldata || '0x'),
974
- }];
975
- }
976
- case 'APPROVE': {
977
- const req = request;
978
- // v31.0: NFT approval routing for Smart Account
979
- if (req.nft) {
980
- const approvalType = req.amount === '0' ? 'single' : 'all';
981
- if (req.nft.standard === 'METAPLEX') {
982
- throw new WAIaaSError('CHAIN_ERROR', {
983
- message: 'Smart Account (ERC-4337) does not support Solana METAPLEX NFT approvals',
984
- });
985
- }
986
- if (approvalType === 'single' && req.nft.standard === 'ERC-721') {
987
- return [{
988
- to: req.token.address,
989
- value: 0n,
990
- data: encodeFunctionData({
991
- abi: ERC721_USEROP_ABI,
992
- functionName: 'approve',
993
- args: [req.spender, BigInt(req.nft.tokenId)],
994
- }),
995
- }];
996
- }
997
- // setApprovalForAll (ERC-721 all / ERC-1155 all)
998
- const nftAbi = req.nft.standard === 'ERC-721' ? ERC721_USEROP_ABI : ERC1155_USEROP_ABI;
999
- return [{
1000
- to: req.token.address,
1001
- value: 0n,
1002
- data: encodeFunctionData({
1003
- abi: nftAbi,
1004
- functionName: 'setApprovalForAll',
1005
- args: [req.spender, true],
1006
- }),
1007
- }];
1008
- }
1009
- return [{
1010
- to: req.token.address,
1011
- value: 0n,
1012
- data: encodeFunctionData({
1013
- abi: ERC20_USEROP_ABI,
1014
- functionName: 'approve',
1015
- args: [req.spender, BigInt(req.amount)],
1016
- }),
1017
- }];
1018
- }
1019
- case 'BATCH': {
1020
- const req = request;
1021
- return req.instructions.map((instr) => {
1022
- if ('spender' in instr) {
1023
- // APPROVE instruction
1024
- const a = instr;
1025
- return {
1026
- to: a.token.address,
1027
- value: 0n,
1028
- data: encodeFunctionData({
1029
- abi: ERC20_USEROP_ABI,
1030
- functionName: 'approve',
1031
- args: [a.spender, BigInt(a.amount)],
1032
- }),
1033
- };
1034
- }
1035
- if ('token' in instr) {
1036
- // TOKEN_TRANSFER instruction
1037
- const t = instr;
1038
- return {
1039
- to: t.token.address,
1040
- value: 0n,
1041
- data: encodeFunctionData({
1042
- abi: ERC20_USEROP_ABI,
1043
- functionName: 'transfer',
1044
- args: [t.to, BigInt(t.amount)],
1045
- }),
1046
- };
1047
- }
1048
- if ('calldata' in instr) {
1049
- // CONTRACT_CALL instruction (also used by ActionProvider resolve() output)
1050
- const c = instr;
1051
- return {
1052
- to: c.to,
1053
- value: BigInt(c.value ?? '0'),
1054
- data: (c.calldata || '0x'),
1055
- };
1056
- }
1057
- // TRANSFER instruction (native transfer)
1058
- const t = instr;
1059
- return {
1060
- to: t.to,
1061
- value: BigInt(t.amount),
1062
- data: '0x',
1063
- };
1064
- });
1065
- }
1066
- case 'NFT_TRANSFER': {
1067
- const req = request;
1068
- if (req.token.standard === 'METAPLEX') {
1069
- throw new WAIaaSError('CHAIN_ERROR', {
1070
- message: 'Smart Account (ERC-4337) does not support Solana METAPLEX NFT transfers',
1071
- });
1072
- }
1073
- const from = (walletAddress ?? '0x0000000000000000000000000000000000000000');
1074
- if (req.token.standard === 'ERC-721') {
1075
- return [{
1076
- to: req.token.address,
1077
- value: 0n,
1078
- data: encodeFunctionData({
1079
- abi: ERC721_USEROP_ABI,
1080
- functionName: 'safeTransferFrom',
1081
- args: [from, req.to, BigInt(req.token.tokenId)],
1082
- }),
1083
- }];
1084
- }
1085
- // ERC-1155
1086
- return [{
1087
- to: req.token.address,
1088
- value: 0n,
1089
- data: encodeFunctionData({
1090
- abi: ERC1155_USEROP_ABI,
1091
- functionName: 'safeTransferFrom',
1092
- args: [from, req.to, BigInt(req.token.tokenId), BigInt(req.amount ?? '1'), '0x'],
1093
- }),
1094
- }];
1095
- }
1096
- case 'CONTRACT_DEPLOY': {
1097
- const req = request;
1098
- const deployData = req.constructorArgs
1099
- ? req.bytecode + req.constructorArgs.replace(/^0x/, '')
1100
- : req.bytecode;
1101
- // Smart account contract deployment via CREATE2-like pattern
1102
- // to is empty (factory handles deployment), data is full bytecode+args
1103
- return [{
1104
- to: '0x', // will be interpreted as factory/self call
1105
- value: BigInt(req.value ?? '0'),
1106
- data: deployData,
1107
- }];
1108
- }
1109
- default:
1110
- throw new WAIaaSError('CHAIN_ERROR', {
1111
- message: `Unknown transaction type for UserOp: ${type}`,
1112
- });
1113
- }
1114
- }
1115
- /**
1116
- * Stage 5 smart account path: execute via UserOperation through BundlerClient.
1117
- *
1118
- * Flow:
1119
- * 1. Decrypt signer key -> create LocalAccount via viem's privateKeyToAccount
1120
- * 2. Create SmartAccount instance via SmartAccountService
1121
- * 3. Create BundlerClient via createSmartAccountBundlerClient
1122
- * 4. Build calls[] from request via buildUserOpCalls
1123
- * 5. prepareUserOperation -> apply 120% gas safety margin
1124
- * 6. sendUserOperation -> waitForUserOperationReceipt
1125
- * 7. Update DB: SUBMITTED -> CONFIRMED, update deployed status if needed
1126
- *
1127
- * Error mapping:
1128
- * - Paymaster rejection (message contains 'paymaster'/'PM_') -> PAYMASTER_REJECTED
1129
- * - UserOperationReverted -> TRANSACTION_REVERTED
1130
- * - Receipt timeout -> TRANSACTION_TIMEOUT
1131
- * - Other -> CHAIN_ERROR
1132
- */
1133
- async function stage5ExecuteSmartAccount(ctx) {
1134
- // Check for deprecated Solady factory before proceeding
1135
- if (ctx.wallet.factoryAddress?.toLowerCase() === SOLADY_FACTORY_ADDRESS.toLowerCase()) {
1136
- throw new WAIaaSError('DEPRECATED_SMART_ACCOUNT');
1137
- }
1138
- const reqAmount = formatNotificationAmount(ctx.request, ctx.wallet.chain);
1139
- const reqTo = getRequestTo(ctx.request);
1140
- const displayAmount = await resolveDisplayAmount(ctx.amountUsd ?? null, ctx.settingsService, ctx.forexRateService);
1141
- // Build calls[] from request (pass wallet address for NFT safeTransferFrom 'from' param)
1142
- const calls = buildUserOpCalls(ctx.request, ctx.wallet.publicKey);
1143
- // CRITICAL: key MUST be released in finally block
1144
- let privateKey = null;
1145
- try {
1146
- // Step 1: Decrypt signer key
1147
- privateKey = await ctx.keyStore.decryptPrivateKey(ctx.walletId, ctx.masterPassword);
1148
- const hexKey = toHex(privateKey);
1149
- const localAccount = privateKeyToAccount(hexKey);
1150
- // Step 2: Create SmartAccount via SmartAccountService
1151
- const smartAccountService = new SmartAccountService();
1152
- // #251: Resolve viem Chain from EVM_CHAIN_MAP using network ID
1153
- const { EVM_CHAIN_MAP } = await import('@waiaas/adapter-evm');
1154
- const chainEntry = EVM_CHAIN_MAP[ctx.resolvedNetwork];
1155
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1156
- const publicClient = createPublicClient({
1157
- chain: chainEntry?.viemChain,
1158
- transport: http(ctx.resolvedRpcUrl),
1159
- });
1160
- const smartAccountInfo = await smartAccountService.createSmartAccount({
1161
- owner: localAccount,
1162
- client: publicClient,
1163
- });
1164
- // Step 3: Create BundlerClient from wallet's provider data (v30.9)
1165
- const decryptedApiKey = ctx.wallet.aaProviderApiKeyEncrypted
1166
- ? decryptProviderApiKey(ctx.wallet.aaProviderApiKeyEncrypted, ctx.masterPassword)
1167
- : null;
1168
- const walletProvider = {
1169
- aaProvider: ctx.wallet.aaProvider ?? null,
1170
- aaProviderApiKey: decryptedApiKey,
1171
- aaBundlerUrl: ctx.wallet.aaBundlerUrl ?? null,
1172
- aaPaymasterUrl: ctx.wallet.aaPaymasterUrl ?? null,
1173
- aaPaymasterPolicyId: ctx.wallet.aaPaymasterPolicyId ?? null,
1174
- };
1175
- const bundlerClient = createSmartAccountBundlerClient({
1176
- client: publicClient,
1177
- account: smartAccountInfo.account,
1178
- networkId: ctx.resolvedNetwork,
1179
- walletProvider,
1180
- settingsService: ctx.settingsService,
1181
- });
1182
- // Step 4: Prepare UserOperation to get gas estimates
1183
- const prepared = await bundlerClient.prepareUserOperation({ calls });
1184
- // Step 5: Apply 120% gas safety margin per CLAUDE.md rule
1185
- const safeCallGasLimit = (BigInt(prepared.callGasLimit) * GAS_SAFETY_NUMERATOR) / GAS_SAFETY_DENOMINATOR;
1186
- const safeVerificationGasLimit = (BigInt(prepared.verificationGasLimit) * GAS_SAFETY_NUMERATOR) / GAS_SAFETY_DENOMINATOR;
1187
- const safePreVerificationGas = (BigInt(prepared.preVerificationGas) * GAS_SAFETY_NUMERATOR) / GAS_SAFETY_DENOMINATOR;
1188
- // Step 6: Submit UserOperation with overridden gas limits
1189
- ctx.metricsCounter?.increment('rpc.calls', { network: ctx.resolvedNetwork });
1190
- const userOpHash = await bundlerClient.sendUserOperation({
1191
- calls,
1192
- userOperation: {
1193
- callGasLimit: safeCallGasLimit,
1194
- verificationGasLimit: safeVerificationGasLimit,
1195
- preVerificationGas: safePreVerificationGas,
1196
- },
1197
- });
1198
- ctx.metricsCounter?.increment('tx.submitted', { network: ctx.resolvedNetwork });
1199
- // Update DB: SUBMITTED with userOpHash
1200
- await ctx.db
1201
- .update(transactions)
1202
- .set({ status: 'SUBMITTED', txHash: userOpHash })
1203
- .where(eq(transactions.id, ctx.txId));
1204
- // Audit log: TX_SUBMITTED
1205
- if (ctx.sqlite) {
1206
- insertAuditLog(ctx.sqlite, {
1207
- eventType: 'TX_SUBMITTED',
1208
- actor: ctx.sessionId ?? 'system',
1209
- walletId: ctx.walletId,
1210
- txId: ctx.txId,
1211
- details: {
1212
- txHash: userOpHash,
1213
- chain: ctx.wallet.chain,
1214
- network: ctx.resolvedNetwork,
1215
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1216
- accountType: 'smart',
1217
- },
1218
- severity: 'info',
1219
- });
1220
- }
1221
- // Notify TX_SUBMITTED
1222
- void ctx.notificationService?.notify('TX_SUBMITTED', ctx.walletId, {
1223
- txId: ctx.txId,
1224
- txHash: userOpHash,
1225
- amount: reqAmount,
1226
- to: reqTo,
1227
- display_amount: displayAmount,
1228
- network: ctx.resolvedNetwork,
1229
- }, { txId: ctx.txId });
1230
- // Emit wallet:activity
1231
- ctx.eventBus?.emit('wallet:activity', {
1232
- walletId: ctx.walletId,
1233
- activity: 'TX_SUBMITTED',
1234
- details: { txId: ctx.txId, txHash: userOpHash },
1235
- timestamp: Math.floor(Date.now() / 1000),
1236
- });
1237
- // Step 7: Wait for UserOperation receipt (120s timeout)
1238
- const receipt = await bundlerClient.waitForUserOperationReceipt({
1239
- hash: userOpHash,
1240
- timeout: 120_000,
1241
- });
1242
- const txHash = receipt?.receipt?.transactionHash ?? userOpHash;
1243
- // Update DB: CONFIRMED with actual txHash
1244
- await ctx.db
1245
- .update(transactions)
1246
- .set({ status: 'CONFIRMED', txHash })
1247
- .where(eq(transactions.id, ctx.txId));
1248
- // Update deployed status if this was first UserOp (lazy deployment)
1249
- const walletRow = ctx.db.select().from(wallets).where(eq(wallets.id, ctx.walletId)).get();
1250
- if (walletRow && !walletRow.deployed) {
1251
- ctx.db.update(wallets).set({ deployed: true }).where(eq(wallets.id, ctx.walletId)).run();
1252
- }
1253
- ctx.metricsCounter?.increment('tx.confirmed', { network: ctx.resolvedNetwork });
1254
- // Store submitResult for Stage 6
1255
- ctx.submitResult = { txHash, status: 'confirmed' };
1256
- // Audit log: TX_CONFIRMED
1257
- if (ctx.sqlite) {
1258
- insertAuditLog(ctx.sqlite, {
1259
- eventType: 'TX_CONFIRMED',
1260
- actor: ctx.sessionId ?? 'system',
1261
- walletId: ctx.walletId,
1262
- txId: ctx.txId,
1263
- details: {
1264
- txHash,
1265
- chain: ctx.wallet.chain,
1266
- network: ctx.resolvedNetwork,
1267
- accountType: 'smart',
1268
- },
1269
- severity: 'info',
1270
- });
1271
- }
1272
- // Notify TX_CONFIRMED
1273
- void ctx.notificationService?.notify('TX_CONFIRMED', ctx.walletId, {
1274
- txId: ctx.txId,
1275
- txHash,
1276
- amount: reqAmount,
1277
- to: reqTo,
1278
- display_amount: displayAmount,
1279
- network: ctx.resolvedNetwork,
1280
- }, { txId: ctx.txId });
1281
- // Emit transaction:completed event
1282
- ctx.eventBus?.emit('transaction:completed', {
1283
- walletId: ctx.walletId,
1284
- txId: ctx.txId,
1285
- txHash,
1286
- network: ctx.resolvedNetwork,
1287
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1288
- timestamp: Math.floor(Date.now() / 1000),
1289
- });
1290
- }
1291
- catch (err) {
1292
- const errMsg = err instanceof Error ? err.message : String(err);
1293
- const errName = err instanceof Error ? err.name : '';
1294
- // Already a WAIaaSError? (e.g., CHAIN_ERROR from bundler URL not configured)
1295
- if (err instanceof WAIaaSError) {
1296
- // Update DB to FAILED
1297
- await ctx.db
1298
- .update(transactions)
1299
- .set({ status: 'FAILED', error: errMsg })
1300
- .where(eq(transactions.id, ctx.txId));
1301
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1302
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1303
- txId: ctx.txId,
1304
- error: errMsg,
1305
- amount: reqAmount,
1306
- display_amount: displayAmount,
1307
- network: ctx.resolvedNetwork,
1308
- }, { txId: ctx.txId });
1309
- ctx.eventBus?.emit('transaction:failed', {
1310
- walletId: ctx.walletId,
1311
- txId: ctx.txId,
1312
- error: errMsg,
1313
- network: ctx.resolvedNetwork,
1314
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1315
- timestamp: Math.floor(Date.now() / 1000),
1316
- });
1317
- throw err;
1318
- }
1319
- // Paymaster rejection detection
1320
- if (errMsg.toLowerCase().includes('paymaster') ||
1321
- errMsg.includes('PM_') ||
1322
- errName.includes('Paymaster')) {
1323
- await ctx.db
1324
- .update(transactions)
1325
- .set({ status: 'FAILED', error: `Paymaster rejected: ${errMsg}` })
1326
- .where(eq(transactions.id, ctx.txId));
1327
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1328
- if (ctx.sqlite) {
1329
- insertAuditLog(ctx.sqlite, {
1330
- eventType: 'TX_FAILED',
1331
- actor: ctx.sessionId ?? 'system',
1332
- walletId: ctx.walletId,
1333
- txId: ctx.txId,
1334
- details: { error: errMsg, stage: 5, reason: 'paymaster_rejected' },
1335
- severity: 'warning',
1336
- });
1337
- }
1338
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1339
- txId: ctx.txId,
1340
- error: `Paymaster rejected: ${errMsg}`,
1341
- amount: reqAmount,
1342
- display_amount: displayAmount,
1343
- network: ctx.resolvedNetwork,
1344
- }, { txId: ctx.txId });
1345
- ctx.eventBus?.emit('transaction:failed', {
1346
- walletId: ctx.walletId,
1347
- txId: ctx.txId,
1348
- error: `Paymaster rejected: ${errMsg}`,
1349
- network: ctx.resolvedNetwork,
1350
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1351
- timestamp: Math.floor(Date.now() / 1000),
1352
- });
1353
- throw new WAIaaSError('PAYMASTER_REJECTED', {
1354
- message: `Paymaster rejected the UserOperation: ${errMsg}`,
1355
- });
1356
- }
1357
- // UserOperationReverted
1358
- if (errName === 'UserOperationReverted' || errMsg.includes('UserOperation reverted')) {
1359
- await ctx.db
1360
- .update(transactions)
1361
- .set({ status: 'FAILED', error: errMsg })
1362
- .where(eq(transactions.id, ctx.txId));
1363
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1364
- if (ctx.sqlite) {
1365
- insertAuditLog(ctx.sqlite, {
1366
- eventType: 'TX_FAILED',
1367
- actor: ctx.sessionId ?? 'system',
1368
- walletId: ctx.walletId,
1369
- txId: ctx.txId,
1370
- details: { error: errMsg, stage: 5, reason: 'user_op_reverted' },
1371
- severity: 'warning',
1372
- });
1373
- }
1374
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1375
- txId: ctx.txId,
1376
- error: errMsg,
1377
- amount: reqAmount,
1378
- display_amount: displayAmount,
1379
- network: ctx.resolvedNetwork,
1380
- }, { txId: ctx.txId });
1381
- ctx.eventBus?.emit('transaction:failed', {
1382
- walletId: ctx.walletId,
1383
- txId: ctx.txId,
1384
- error: errMsg,
1385
- network: ctx.resolvedNetwork,
1386
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1387
- timestamp: Math.floor(Date.now() / 1000),
1388
- });
1389
- throw new WAIaaSError('TRANSACTION_REVERTED', {
1390
- message: errMsg,
1391
- });
1392
- }
1393
- // Receipt timeout
1394
- if (errName === 'WaitForUserOperationReceiptTimeoutError' || errMsg.includes('timed out')) {
1395
- await ctx.db
1396
- .update(transactions)
1397
- .set({ status: 'FAILED', error: `UserOp receipt timeout: ${errMsg}` })
1398
- .where(eq(transactions.id, ctx.txId));
1399
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1400
- if (ctx.sqlite) {
1401
- insertAuditLog(ctx.sqlite, {
1402
- eventType: 'TX_FAILED',
1403
- actor: ctx.sessionId ?? 'system',
1404
- walletId: ctx.walletId,
1405
- txId: ctx.txId,
1406
- details: { error: errMsg, stage: 5, reason: 'user_op_timeout' },
1407
- severity: 'warning',
1408
- });
1409
- }
1410
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1411
- txId: ctx.txId,
1412
- error: `Receipt timeout: ${errMsg}`,
1413
- amount: reqAmount,
1414
- display_amount: displayAmount,
1415
- network: ctx.resolvedNetwork,
1416
- }, { txId: ctx.txId });
1417
- ctx.eventBus?.emit('transaction:failed', {
1418
- walletId: ctx.walletId,
1419
- txId: ctx.txId,
1420
- error: `Receipt timeout: ${errMsg}`,
1421
- network: ctx.resolvedNetwork,
1422
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1423
- timestamp: Math.floor(Date.now() / 1000),
1424
- });
1425
- throw new WAIaaSError('TRANSACTION_TIMEOUT', {
1426
- message: `UserOperation receipt timed out: ${errMsg}`,
1427
- });
1428
- }
1429
- // Generic fallback
1430
- await ctx.db
1431
- .update(transactions)
1432
- .set({ status: 'FAILED', error: errMsg })
1433
- .where(eq(transactions.id, ctx.txId));
1434
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1435
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1436
- txId: ctx.txId,
1437
- error: errMsg,
1438
- amount: reqAmount,
1439
- display_amount: displayAmount,
1440
- network: ctx.resolvedNetwork,
1441
- }, { txId: ctx.txId });
1442
- ctx.eventBus?.emit('transaction:failed', {
1443
- walletId: ctx.walletId,
1444
- txId: ctx.txId,
1445
- error: errMsg,
1446
- network: ctx.resolvedNetwork,
1447
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1448
- timestamp: Math.floor(Date.now() / 1000),
1449
- });
1450
- throw new WAIaaSError('CHAIN_ERROR', { message: errMsg });
1451
- }
1452
- finally {
1453
- if (privateKey) {
1454
- ctx.keyStore.releaseKey(privateKey);
1455
- }
1456
- }
1457
- }
1458
- // ---------------------------------------------------------------------------
1459
- // Stage 5: On-chain execution (CONC-01 retry loop)
1460
- // ---------------------------------------------------------------------------
1461
- /**
1462
- * Stage 5: Build -> Simulate -> Sign -> Submit with CONC-01 retry logic.
1463
- *
1464
- * For smart accounts (accountType === 'smart'), delegates to stage5ExecuteSmartAccount
1465
- * which uses the UserOperation pipeline (BundlerClient + PaymasterClient).
1466
- *
1467
- * For EOA accounts, uses the existing buildByType -> simulate -> sign -> submit path.
1468
- *
1469
- * ChainError category-based retry:
1470
- * - PERMANENT: immediate FAILED, no retry
1471
- * - TRANSIENT: exponential backoff (1s, 2s, 4s), max 3 retries (retryCount >= 3 guard)
1472
- * - STALE: rebuild from Stage 5a, max 1 (retryCount >= 1 guard)
1473
- *
1474
- * retryCount is shared between TRANSIENT and STALE to limit total retry count.
1475
- * Total attempts: initial 1 + up to 3 retries = 4 max.
1476
- */
1477
- export async function stage5Execute(ctx) {
1478
- // Smart account UserOperation path
1479
- if (ctx.wallet.accountType === 'smart') {
1480
- await stage5ExecuteSmartAccount(ctx);
1481
- return;
1482
- }
1483
- // v31.4: ApiDirectResult path -- skip on-chain execution entirely (HDESIGN-01)
1484
- if (ctx.actionResult) {
1485
- const result = ctx.actionResult;
1486
- // Update transaction status to CONFIRMED with API direct result metadata
1487
- await ctx.db
1488
- .update(transactions)
1489
- .set({
1490
- status: 'CONFIRMED',
1491
- metadata: JSON.stringify({
1492
- apiDirect: true,
1493
- provider: result.provider,
1494
- action: result.action,
1495
- externalId: result.externalId,
1496
- resultStatus: result.status,
1497
- data: result.data,
1498
- ...(result.metadata ?? {}),
1499
- }),
1500
- })
1501
- .where(eq(transactions.id, ctx.txId));
1502
- // Audit log: TX_CONFIRMED (API direct)
1503
- if (ctx.sqlite) {
1504
- insertAuditLog(ctx.sqlite, {
1505
- eventType: 'TX_CONFIRMED',
1506
- actor: ctx.sessionId ?? 'system',
1507
- walletId: ctx.walletId,
1508
- txId: ctx.txId,
1509
- details: {
1510
- provider: result.provider,
1511
- action: result.action,
1512
- externalId: result.externalId,
1513
- apiDirect: true,
1514
- chain: ctx.wallet.chain,
1515
- network: ctx.resolvedNetwork,
1516
- },
1517
- severity: 'info',
1518
- });
1519
- }
1520
- // Fire-and-forget: notify TX_CONFIRMED
1521
- const apiDirectAmount = formatNotificationAmount(ctx.request, ctx.wallet.chain);
1522
- const apiDirectTo = getRequestTo(ctx.request);
1523
- const apiDirectDisplayAmount = await resolveDisplayAmount(ctx.amountUsd ?? null, ctx.settingsService, ctx.forexRateService);
1524
- void ctx.notificationService?.notify('TX_CONFIRMED', ctx.walletId, {
1525
- txId: ctx.txId,
1526
- provider: result.provider,
1527
- action: result.action,
1528
- externalId: result.externalId,
1529
- network: ctx.resolvedNetwork,
1530
- amount: apiDirectAmount,
1531
- to: apiDirectTo,
1532
- display_amount: apiDirectDisplayAmount,
1533
- }, { txId: ctx.txId });
1534
- // Emit transaction:completed event (txHash = externalId for API direct)
1535
- ctx.eventBus?.emit('transaction:completed', {
1536
- walletId: ctx.walletId,
1537
- txId: ctx.txId,
1538
- txHash: result.externalId,
1539
- network: ctx.resolvedNetwork,
1540
- type: 'CONTRACT_CALL',
1541
- timestamp: Math.floor(Date.now() / 1000),
1542
- });
1543
- // Increment metrics
1544
- ctx.metricsCounter?.increment('tx.completed', { network: ctx.resolvedNetwork });
1545
- return; // Skip on-chain execution
1546
- }
1547
- // --- EOA execution path (unchanged) ---
1548
- const reqAmount = formatNotificationAmount(ctx.request, ctx.wallet.chain);
1549
- const reqTo = getRequestTo(ctx.request);
1550
- // [Phase 139] Resolve display amount once for all Stage 5 notifications
1551
- const displayAmount = await resolveDisplayAmount(ctx.amountUsd ?? null, ctx.settingsService, ctx.forexRateService);
1552
- let retryCount = 0;
1553
- // Outer buildLoop: STALE errors return here to rebuild from Stage 5a
1554
- buildLoop: while (true) {
1555
- try {
1556
- // Stage 5a: Build unsigned transaction (type-routed)
1557
- ctx.unsignedTx = await buildByType(ctx.adapter, ctx.request, ctx.wallet.publicKey);
1558
- // Stage 5b: Simulate (with RPC metrics)
1559
- const simStart = Date.now();
1560
- ctx.metricsCounter?.increment('rpc.calls', { network: ctx.resolvedNetwork });
1561
- const simResult = await ctx.adapter.simulateTransaction(ctx.unsignedTx);
1562
- ctx.metricsCounter?.recordLatency('rpc.latency', Date.now() - simStart, { network: ctx.resolvedNetwork });
1563
- if (!simResult.success) {
1564
- ctx.metricsCounter?.increment('rpc.errors', { network: ctx.resolvedNetwork });
1565
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1566
- await ctx.db
1567
- .update(transactions)
1568
- .set({ status: 'FAILED', error: simResult.error ?? 'Simulation failed' })
1569
- .where(eq(transactions.id, ctx.txId));
1570
- // Audit log: TX_FAILED (simulation failure)
1571
- if (ctx.sqlite) {
1572
- insertAuditLog(ctx.sqlite, {
1573
- eventType: 'TX_FAILED',
1574
- actor: ctx.sessionId ?? 'system',
1575
- walletId: ctx.walletId,
1576
- txId: ctx.txId,
1577
- details: { error: simResult.error ?? 'Simulation failed', stage: 5, chain: ctx.wallet.chain, network: ctx.resolvedNetwork },
1578
- severity: 'warning',
1579
- });
1580
- }
1581
- // Fire-and-forget: notify TX_FAILED on simulation failure
1582
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1583
- txId: ctx.txId,
1584
- error: simResult.error ?? 'Simulation failed',
1585
- amount: reqAmount,
1586
- display_amount: displayAmount,
1587
- network: ctx.resolvedNetwork,
1588
- }, { txId: ctx.txId });
1589
- // v1.6: emit transaction:failed event (simulation failure)
1590
- ctx.eventBus?.emit('transaction:failed', {
1591
- walletId: ctx.walletId,
1592
- txId: ctx.txId,
1593
- error: simResult.error ?? 'Simulation failed',
1594
- network: ctx.resolvedNetwork,
1595
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1596
- timestamp: Math.floor(Date.now() / 1000),
1597
- });
1598
- throw new WAIaaSError('SIMULATION_FAILED', {
1599
- message: simResult.error ?? 'Transaction simulation failed',
1600
- });
1601
- }
1602
- // Stage 5c: Decrypt private key, sign
1603
- // CRITICAL: key MUST be released in finally block
1604
- let privateKey = null;
1605
- try {
1606
- privateKey = await ctx.keyStore.decryptPrivateKey(ctx.walletId, ctx.masterPassword);
1607
- ctx.signedTx = await ctx.adapter.signTransaction(ctx.unsignedTx, privateKey);
1608
- }
1609
- finally {
1610
- if (privateKey) {
1611
- ctx.keyStore.releaseKey(privateKey);
1612
- }
1613
- }
1614
- // Stage 5d: Submit (with RPC metrics)
1615
- const submitStart = Date.now();
1616
- ctx.metricsCounter?.increment('rpc.calls', { network: ctx.resolvedNetwork });
1617
- ctx.submitResult = await ctx.adapter.submitTransaction(ctx.signedTx);
1618
- ctx.metricsCounter?.recordLatency('rpc.latency', Date.now() - submitStart, { network: ctx.resolvedNetwork });
1619
- // Success: increment tx.submitted counter
1620
- ctx.metricsCounter?.increment('tx.submitted', { network: ctx.resolvedNetwork });
1621
- // Success: Update DB SUBMITTED + txHash
1622
- await ctx.db
1623
- .update(transactions)
1624
- .set({ status: 'SUBMITTED', txHash: ctx.submitResult.txHash })
1625
- .where(eq(transactions.id, ctx.txId));
1626
- // Audit log: TX_SUBMITTED
1627
- if (ctx.sqlite) {
1628
- const txType = ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER';
1629
- const auditDetails = {
1630
- txHash: ctx.submitResult.txHash,
1631
- chain: ctx.wallet.chain,
1632
- network: ctx.resolvedNetwork,
1633
- type: txType,
1634
- };
1635
- // v31.14 DEPL-06: log keccak256(bytecode) for CONTRACT_DEPLOY audit trail
1636
- if (txType === 'CONTRACT_DEPLOY' && 'bytecode' in ctx.request) {
1637
- const { keccak256, toBytes } = await import('viem');
1638
- auditDetails.bytecodeHash = keccak256(toBytes(ctx.request.bytecode));
1639
- }
1640
- insertAuditLog(ctx.sqlite, {
1641
- eventType: 'TX_SUBMITTED',
1642
- actor: ctx.sessionId ?? 'system',
1643
- walletId: ctx.walletId,
1644
- txId: ctx.txId,
1645
- details: auditDetails,
1646
- severity: 'info',
1647
- });
1648
- }
1649
- // Fire-and-forget: notify TX_SUBMITTED
1650
- void ctx.notificationService?.notify('TX_SUBMITTED', ctx.walletId, {
1651
- txId: ctx.txId,
1652
- txHash: ctx.submitResult.txHash,
1653
- amount: reqAmount,
1654
- to: reqTo,
1655
- display_amount: displayAmount,
1656
- network: ctx.resolvedNetwork,
1657
- }, { txId: ctx.txId });
1658
- // v1.6: emit wallet:activity TX_SUBMITTED event
1659
- ctx.eventBus?.emit('wallet:activity', {
1660
- walletId: ctx.walletId,
1661
- activity: 'TX_SUBMITTED',
1662
- details: { txId: ctx.txId, txHash: ctx.submitResult.txHash },
1663
- timestamp: Math.floor(Date.now() / 1000),
1664
- });
1665
- return; // Success -- exit the loop
1666
- }
1667
- catch (err) {
1668
- // Non-ChainError: rethrow as-is (WAIaaSError, validation errors, etc.)
1669
- if (!(err instanceof ChainError)) {
1670
- throw err;
1671
- }
1672
- // ChainError: category-based retry logic
1673
- switch (err.category) {
1674
- case 'PERMANENT': {
1675
- // Immediate failure, no retry
1676
- ctx.metricsCounter?.increment('rpc.errors', { network: ctx.resolvedNetwork });
1677
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1678
- await ctx.db
1679
- .update(transactions)
1680
- .set({ status: 'FAILED', error: err.message })
1681
- .where(eq(transactions.id, ctx.txId));
1682
- // Audit log: TX_FAILED (permanent chain error)
1683
- if (ctx.sqlite) {
1684
- insertAuditLog(ctx.sqlite, {
1685
- eventType: 'TX_FAILED',
1686
- actor: ctx.sessionId ?? 'system',
1687
- walletId: ctx.walletId,
1688
- txId: ctx.txId,
1689
- details: { error: err.message, stage: 5, chain: ctx.wallet.chain, network: ctx.resolvedNetwork },
1690
- severity: 'warning',
1691
- });
1692
- }
1693
- // Fire-and-forget: notify TX_FAILED
1694
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1695
- txId: ctx.txId,
1696
- error: err.message,
1697
- amount: reqAmount,
1698
- display_amount: displayAmount,
1699
- network: ctx.resolvedNetwork,
1700
- }, { txId: ctx.txId });
1701
- // v1.6: emit transaction:failed event (permanent chain error)
1702
- ctx.eventBus?.emit('transaction:failed', {
1703
- walletId: ctx.walletId,
1704
- txId: ctx.txId,
1705
- error: err.message,
1706
- network: ctx.resolvedNetwork,
1707
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1708
- timestamp: Math.floor(Date.now() / 1000),
1709
- });
1710
- throw new WAIaaSError('CHAIN_ERROR', {
1711
- message: err.message,
1712
- cause: err,
1713
- });
1714
- }
1715
- case 'TRANSIENT': {
1716
- ctx.metricsCounter?.increment('rpc.errors', { network: ctx.resolvedNetwork });
1717
- if (retryCount >= 3) {
1718
- // Max retries exhausted
1719
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1720
- await ctx.db
1721
- .update(transactions)
1722
- .set({ status: 'FAILED', error: `${err.code} (max retries exceeded)` })
1723
- .where(eq(transactions.id, ctx.txId));
1724
- // Fire-and-forget: notify TX_FAILED
1725
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1726
- txId: ctx.txId,
1727
- error: `${err.code} (max retries exceeded)`,
1728
- amount: reqAmount,
1729
- display_amount: displayAmount,
1730
- network: ctx.resolvedNetwork,
1731
- }, { txId: ctx.txId });
1732
- // v1.6: emit transaction:failed event (transient max retries)
1733
- ctx.eventBus?.emit('transaction:failed', {
1734
- walletId: ctx.walletId,
1735
- txId: ctx.txId,
1736
- error: `${err.code} (max retries exceeded)`,
1737
- network: ctx.resolvedNetwork,
1738
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1739
- timestamp: Math.floor(Date.now() / 1000),
1740
- });
1741
- throw new WAIaaSError('CHAIN_ERROR', {
1742
- message: `${err.message} (max retries exceeded)`,
1743
- cause: err,
1744
- });
1745
- }
1746
- // Exponential backoff: 1s, 2s, 4s
1747
- await sleep(1000 * Math.pow(2, retryCount));
1748
- retryCount++;
1749
- continue buildLoop; // Retry from Stage 5a (rebuild)
1750
- }
1751
- case 'STALE': {
1752
- ctx.metricsCounter?.increment('rpc.errors', { network: ctx.resolvedNetwork });
1753
- if (retryCount >= 1) {
1754
- // Stale retry exhausted (shared retryCount)
1755
- ctx.metricsCounter?.increment('tx.failed', { network: ctx.resolvedNetwork });
1756
- await ctx.db
1757
- .update(transactions)
1758
- .set({ status: 'FAILED', error: `${err.code} (stale retry exhausted)` })
1759
- .where(eq(transactions.id, ctx.txId));
1760
- // Fire-and-forget: notify TX_FAILED
1761
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1762
- txId: ctx.txId,
1763
- error: `${err.code} (stale retry exhausted)`,
1764
- amount: reqAmount,
1765
- display_amount: displayAmount,
1766
- network: ctx.resolvedNetwork,
1767
- }, { txId: ctx.txId });
1768
- // v1.6: emit transaction:failed event (stale retry exhausted)
1769
- ctx.eventBus?.emit('transaction:failed', {
1770
- walletId: ctx.walletId,
1771
- txId: ctx.txId,
1772
- error: `${err.code} (stale retry exhausted)`,
1773
- network: ctx.resolvedNetwork,
1774
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1775
- timestamp: Math.floor(Date.now() / 1000),
1776
- });
1777
- throw new WAIaaSError('CHAIN_ERROR', {
1778
- message: `${err.message} (stale retry exhausted)`,
1779
- cause: err,
1780
- });
1781
- }
1782
- // Rebuild from Stage 5a with new blockhash/nonce
1783
- retryCount++;
1784
- continue buildLoop;
1785
- }
1786
- default: {
1787
- // Unknown category: treat as permanent
1788
- await ctx.db
1789
- .update(transactions)
1790
- .set({ status: 'FAILED', error: err.message })
1791
- .where(eq(transactions.id, ctx.txId));
1792
- throw new WAIaaSError('CHAIN_ERROR', {
1793
- message: err.message,
1794
- cause: err,
1795
- });
1796
- }
1797
- }
1798
- }
1799
- }
1800
- }
1801
- // ---------------------------------------------------------------------------
1802
- // Stage 6: Confirmation wait
1803
- // ---------------------------------------------------------------------------
1804
- export async function stage6Confirm(ctx) {
1805
- const reqAmount = formatNotificationAmount(ctx.request, ctx.wallet.chain);
1806
- const reqTo = getRequestTo(ctx.request);
1807
- // [Phase 139] Resolve display amount for Stage 6 notifications
1808
- const displayAmount = await resolveDisplayAmount(ctx.amountUsd ?? null, ctx.settingsService, ctx.forexRateService);
1809
- const result = await ctx.adapter.waitForConfirmation(ctx.submitResult.txHash, 30_000);
1810
- if (result.status === 'confirmed' || result.status === 'finalized') {
1811
- // On-chain confirmed
1812
- const executedAt = new Date(Math.floor(Date.now() / 1000) * 1000);
1813
- await ctx.db
1814
- .update(transactions)
1815
- .set({ status: 'CONFIRMED', executedAt })
1816
- .where(eq(transactions.id, ctx.txId));
1817
- // Audit log: TX_CONFIRMED
1818
- if (ctx.sqlite) {
1819
- insertAuditLog(ctx.sqlite, {
1820
- eventType: 'TX_CONFIRMED',
1821
- actor: ctx.sessionId ?? 'system',
1822
- walletId: ctx.walletId,
1823
- txId: ctx.txId,
1824
- details: {
1825
- txHash: ctx.submitResult.txHash,
1826
- chain: ctx.wallet.chain,
1827
- network: ctx.resolvedNetwork,
1828
- executedAt: Math.floor(Date.now() / 1000),
1829
- },
1830
- severity: 'info',
1831
- });
1832
- }
1833
- // Fire-and-forget: notify TX_CONFIRMED (never blocks pipeline)
1834
- void ctx.notificationService?.notify('TX_CONFIRMED', ctx.walletId, {
1835
- txId: ctx.txId,
1836
- txHash: ctx.submitResult.txHash,
1837
- amount: reqAmount,
1838
- to: reqTo,
1839
- display_amount: displayAmount,
1840
- network: ctx.resolvedNetwork,
1841
- }, { txId: ctx.txId });
1842
- // v1.6: emit transaction:completed event
1843
- ctx.eventBus?.emit('transaction:completed', {
1844
- walletId: ctx.walletId,
1845
- txId: ctx.txId,
1846
- txHash: ctx.submitResult.txHash,
1847
- amount: getRequestAmount(ctx.request),
1848
- network: ctx.resolvedNetwork,
1849
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1850
- timestamp: Math.floor(Date.now() / 1000),
1851
- });
1852
- }
1853
- else if (result.status === 'failed') {
1854
- // On-chain revert
1855
- await ctx.db
1856
- .update(transactions)
1857
- .set({ status: 'FAILED', error: 'Transaction reverted on-chain' })
1858
- .where(eq(transactions.id, ctx.txId));
1859
- // Audit log: TX_FAILED (on-chain revert)
1860
- if (ctx.sqlite) {
1861
- insertAuditLog(ctx.sqlite, {
1862
- eventType: 'TX_FAILED',
1863
- actor: ctx.sessionId ?? 'system',
1864
- walletId: ctx.walletId,
1865
- txId: ctx.txId,
1866
- details: { error: 'Transaction reverted on-chain', stage: 6, chain: ctx.wallet.chain, network: ctx.resolvedNetwork },
1867
- severity: 'warning',
1868
- });
1869
- }
1870
- // Fire-and-forget: notify TX_FAILED on on-chain revert (never blocks pipeline)
1871
- void ctx.notificationService?.notify('TX_FAILED', ctx.walletId, {
1872
- txId: ctx.txId,
1873
- error: 'Transaction reverted on-chain',
1874
- amount: reqAmount,
1875
- display_amount: displayAmount,
1876
- network: ctx.resolvedNetwork,
1877
- }, { txId: ctx.txId });
1878
- // v1.6: emit transaction:failed event (on-chain revert)
1879
- ctx.eventBus?.emit('transaction:failed', {
1880
- walletId: ctx.walletId,
1881
- txId: ctx.txId,
1882
- error: 'Transaction reverted on-chain',
1883
- network: ctx.resolvedNetwork,
1884
- type: ('type' in ctx.request && ctx.request.type) ? ctx.request.type : 'TRANSFER',
1885
- timestamp: Math.floor(Date.now() / 1000),
1886
- });
1887
- throw new WAIaaSError('CHAIN_ERROR', {
1888
- message: 'Transaction reverted on-chain',
1889
- });
1890
- }
1891
- else {
1892
- // status === 'submitted': still pending, NOT failed
1893
- // Keep SUBMITTED status (already set by Stage 5)
1894
- // Do NOT overwrite to FAILED -- tx may confirm later
1895
- // No notification: no state change occurred
1896
- }
1897
- }
2
+ * Pipeline stages -- barrel re-export.
3
+ * Individual stage modules: stage1-validate.ts through stage6-confirm.ts
4
+ * Shared types/helpers: pipeline-helpers.ts
5
+ */
6
+ export * from './pipeline-helpers.js';
7
+ export * from './stage1-validate.js';
8
+ export * from './stage2-auth.js';
9
+ export * from './stage3-policy.js';
10
+ export * from './stage4-wait.js';
11
+ export * from './stage5-execute.js';
12
+ export * from './stage6-confirm.js';
1898
13
  //# sourceMappingURL=stages.js.map