@waiaas/daemon 2.11.0-rc.8 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/README.md +5 -5
  2. package/dist/api/middleware/address-validation.d.ts +6 -33
  3. package/dist/api/middleware/address-validation.d.ts.map +1 -1
  4. package/dist/api/middleware/address-validation.js +5 -129
  5. package/dist/api/middleware/address-validation.js.map +1 -1
  6. package/dist/api/middleware/host-guard.d.ts +1 -1
  7. package/dist/api/middleware/host-guard.js +2 -2
  8. package/dist/api/middleware/host-guard.js.map +1 -1
  9. package/dist/api/middleware/index.d.ts +1 -0
  10. package/dist/api/middleware/index.d.ts.map +1 -1
  11. package/dist/api/middleware/index.js +1 -0
  12. package/dist/api/middleware/index.js.map +1 -1
  13. package/dist/api/middleware/master-auth.d.ts +2 -5
  14. package/dist/api/middleware/master-auth.d.ts.map +1 -1
  15. package/dist/api/middleware/master-auth.js.map +1 -1
  16. package/dist/api/middleware/rate-limiter.d.ts +51 -0
  17. package/dist/api/middleware/rate-limiter.d.ts.map +1 -0
  18. package/dist/api/middleware/rate-limiter.js +146 -0
  19. package/dist/api/middleware/rate-limiter.js.map +1 -0
  20. package/dist/api/middleware/siwe-verify.d.ts +6 -26
  21. package/dist/api/middleware/siwe-verify.d.ts.map +1 -1
  22. package/dist/api/middleware/siwe-verify.js +5 -50
  23. package/dist/api/middleware/siwe-verify.js.map +1 -1
  24. package/dist/api/routes/actions.d.ts +1 -0
  25. package/dist/api/routes/actions.d.ts.map +1 -1
  26. package/dist/api/routes/actions.js +52 -4
  27. package/dist/api/routes/actions.js.map +1 -1
  28. package/dist/api/routes/admin-actions.d.ts +1 -0
  29. package/dist/api/routes/admin-actions.d.ts.map +1 -1
  30. package/dist/api/routes/admin-actions.js +3 -3
  31. package/dist/api/routes/admin-actions.js.map +1 -1
  32. package/dist/api/routes/admin-auth.d.ts.map +1 -1
  33. package/dist/api/routes/admin-auth.js +12 -7
  34. package/dist/api/routes/admin-auth.js.map +1 -1
  35. package/dist/api/routes/admin-credentials.js +2 -2
  36. package/dist/api/routes/admin-credentials.js.map +1 -1
  37. package/dist/api/routes/admin-monitoring.d.ts +10 -0
  38. package/dist/api/routes/admin-monitoring.d.ts.map +1 -1
  39. package/dist/api/routes/admin-monitoring.js +59 -14
  40. package/dist/api/routes/admin-monitoring.js.map +1 -1
  41. package/dist/api/routes/admin-notifications.d.ts.map +1 -1
  42. package/dist/api/routes/admin-notifications.js +2 -15
  43. package/dist/api/routes/admin-notifications.js.map +1 -1
  44. package/dist/api/routes/admin-settings.d.ts.map +1 -1
  45. package/dist/api/routes/admin-settings.js +90 -1
  46. package/dist/api/routes/admin-settings.js.map +1 -1
  47. package/dist/api/routes/admin-wallets.d.ts +16 -1
  48. package/dist/api/routes/admin-wallets.d.ts.map +1 -1
  49. package/dist/api/routes/admin-wallets.js +64 -75
  50. package/dist/api/routes/admin-wallets.js.map +1 -1
  51. package/dist/api/routes/admin.d.ts +1 -0
  52. package/dist/api/routes/admin.d.ts.map +1 -1
  53. package/dist/api/routes/admin.js.map +1 -1
  54. package/dist/api/routes/credentials.js +2 -2
  55. package/dist/api/routes/credentials.js.map +1 -1
  56. package/dist/api/routes/defi-positions.js.map +1 -1
  57. package/dist/api/routes/nfts.js.map +1 -1
  58. package/dist/api/routes/openapi-schemas.d.ts +412 -12
  59. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  60. package/dist/api/routes/openapi-schemas.js +38 -5
  61. package/dist/api/routes/openapi-schemas.js.map +1 -1
  62. package/dist/api/routes/policies.d.ts +2 -0
  63. package/dist/api/routes/policies.d.ts.map +1 -1
  64. package/dist/api/routes/policies.js +55 -6
  65. package/dist/api/routes/policies.js.map +1 -1
  66. package/dist/api/routes/rpc-proxy.js.map +1 -1
  67. package/dist/api/routes/sessions.d.ts.map +1 -1
  68. package/dist/api/routes/sessions.js +47 -28
  69. package/dist/api/routes/sessions.js.map +1 -1
  70. package/dist/api/routes/staking.d.ts.map +1 -1
  71. package/dist/api/routes/staking.js +4 -76
  72. package/dist/api/routes/staking.js.map +1 -1
  73. package/dist/api/routes/tokens.d.ts.map +1 -1
  74. package/dist/api/routes/tokens.js.map +1 -1
  75. package/dist/api/routes/transactions.d.ts +1 -0
  76. package/dist/api/routes/transactions.d.ts.map +1 -1
  77. package/dist/api/routes/transactions.js +8 -2
  78. package/dist/api/routes/transactions.js.map +1 -1
  79. package/dist/api/routes/userop.d.ts.map +1 -1
  80. package/dist/api/routes/userop.js +0 -2
  81. package/dist/api/routes/userop.js.map +1 -1
  82. package/dist/api/routes/wallet-apps.d.ts.map +1 -1
  83. package/dist/api/routes/wallet-apps.js +20 -13
  84. package/dist/api/routes/wallet-apps.js.map +1 -1
  85. package/dist/api/routes/wallet.js.map +1 -1
  86. package/dist/api/routes/wallets.d.ts.map +1 -1
  87. package/dist/api/routes/wallets.js +3 -0
  88. package/dist/api/routes/wallets.js.map +1 -1
  89. package/dist/api/routes/wc.d.ts.map +1 -1
  90. package/dist/api/routes/wc.js +13 -8
  91. package/dist/api/routes/wc.js.map +1 -1
  92. package/dist/api/routes/x402.d.ts.map +1 -1
  93. package/dist/api/routes/x402.js +1 -2
  94. package/dist/api/routes/x402.js.map +1 -1
  95. package/dist/api/server.d.ts +8 -4
  96. package/dist/api/server.d.ts.map +1 -1
  97. package/dist/api/server.js +46 -5
  98. package/dist/api/server.js.map +1 -1
  99. package/dist/constants.d.ts +1 -1
  100. package/dist/constants.d.ts.map +1 -1
  101. package/dist/constants.js +1 -1
  102. package/dist/constants.js.map +1 -1
  103. package/dist/infrastructure/action/action-provider-registry.d.ts.map +1 -1
  104. package/dist/infrastructure/action/action-provider-registry.js +2 -3
  105. package/dist/infrastructure/action/action-provider-registry.js.map +1 -1
  106. package/dist/infrastructure/action/builtin-metadata.d.ts +22 -0
  107. package/dist/infrastructure/action/builtin-metadata.d.ts.map +1 -0
  108. package/dist/infrastructure/action/builtin-metadata.js +29 -0
  109. package/dist/infrastructure/action/builtin-metadata.js.map +1 -0
  110. package/dist/infrastructure/adapter-pool.d.ts +2 -1
  111. package/dist/infrastructure/adapter-pool.d.ts.map +1 -1
  112. package/dist/infrastructure/adapter-pool.js.map +1 -1
  113. package/dist/infrastructure/auth/address-validation.d.ts +38 -0
  114. package/dist/infrastructure/auth/address-validation.d.ts.map +1 -0
  115. package/dist/infrastructure/auth/address-validation.js +134 -0
  116. package/dist/infrastructure/auth/address-validation.js.map +1 -0
  117. package/dist/infrastructure/auth/siwe-verify.d.ts +34 -0
  118. package/dist/infrastructure/auth/siwe-verify.d.ts.map +1 -0
  119. package/dist/infrastructure/auth/siwe-verify.js +58 -0
  120. package/dist/infrastructure/auth/siwe-verify.js.map +1 -0
  121. package/dist/infrastructure/auth/types.d.ts +12 -0
  122. package/dist/infrastructure/auth/types.d.ts.map +1 -0
  123. package/dist/infrastructure/auth/types.js +8 -0
  124. package/dist/infrastructure/auth/types.js.map +1 -0
  125. package/dist/infrastructure/config/loader.d.ts +1 -10
  126. package/dist/infrastructure/config/loader.d.ts.map +1 -1
  127. package/dist/infrastructure/config/loader.js +0 -2
  128. package/dist/infrastructure/config/loader.js.map +1 -1
  129. package/dist/infrastructure/database/migrate.d.ts +6 -18
  130. package/dist/infrastructure/database/migrate.d.ts.map +1 -1
  131. package/dist/infrastructure/database/migrate.js +25 -2856
  132. package/dist/infrastructure/database/migrate.js.map +1 -1
  133. package/dist/infrastructure/database/migrations/v11-v20.d.ts +17 -0
  134. package/dist/infrastructure/database/migrations/v11-v20.d.ts.map +1 -0
  135. package/dist/infrastructure/database/migrations/v11-v20.js +295 -0
  136. package/dist/infrastructure/database/migrations/v11-v20.js.map +1 -0
  137. package/dist/infrastructure/database/migrations/v2-v10.d.ts +16 -0
  138. package/dist/infrastructure/database/migrations/v2-v10.d.ts.map +1 -0
  139. package/dist/infrastructure/database/migrations/v2-v10.js +539 -0
  140. package/dist/infrastructure/database/migrations/v2-v10.js.map +1 -0
  141. package/dist/infrastructure/database/migrations/v21-v30.d.ts +17 -0
  142. package/dist/infrastructure/database/migrations/v21-v30.d.ts.map +1 -0
  143. package/dist/infrastructure/database/migrations/v21-v30.js +507 -0
  144. package/dist/infrastructure/database/migrations/v21-v30.js.map +1 -0
  145. package/dist/infrastructure/database/migrations/v31-v40.d.ts +17 -0
  146. package/dist/infrastructure/database/migrations/v31-v40.d.ts.map +1 -0
  147. package/dist/infrastructure/database/migrations/v31-v40.js +203 -0
  148. package/dist/infrastructure/database/migrations/v31-v40.js.map +1 -0
  149. package/dist/infrastructure/database/migrations/v41-v50.d.ts +17 -0
  150. package/dist/infrastructure/database/migrations/v41-v50.d.ts.map +1 -0
  151. package/dist/infrastructure/database/migrations/v41-v50.js +188 -0
  152. package/dist/infrastructure/database/migrations/v41-v50.js.map +1 -0
  153. package/dist/infrastructure/database/migrations/v51-v59.d.ts +17 -0
  154. package/dist/infrastructure/database/migrations/v51-v59.d.ts.map +1 -0
  155. package/dist/infrastructure/database/migrations/v51-v59.js +420 -0
  156. package/dist/infrastructure/database/migrations/v51-v59.js.map +1 -0
  157. package/dist/infrastructure/database/schema-ddl.d.ts +24 -0
  158. package/dist/infrastructure/database/schema-ddl.d.ts.map +1 -0
  159. package/dist/infrastructure/database/schema-ddl.js +596 -0
  160. package/dist/infrastructure/database/schema-ddl.js.map +1 -0
  161. package/dist/infrastructure/database/schema.d.ts +38 -0
  162. package/dist/infrastructure/database/schema.d.ts.map +1 -1
  163. package/dist/infrastructure/database/schema.js +2 -0
  164. package/dist/infrastructure/database/schema.js.map +1 -1
  165. package/dist/infrastructure/jwt/jwt-secret-manager.d.ts.map +1 -1
  166. package/dist/infrastructure/jwt/jwt-secret-manager.js +16 -3
  167. package/dist/infrastructure/jwt/jwt-secret-manager.js.map +1 -1
  168. package/dist/infrastructure/nft/alchemy-nft-indexer.d.ts.map +1 -1
  169. package/dist/infrastructure/nft/alchemy-nft-indexer.js +0 -1
  170. package/dist/infrastructure/nft/alchemy-nft-indexer.js.map +1 -1
  171. package/dist/infrastructure/nft/helius-nft-indexer.d.ts.map +1 -1
  172. package/dist/infrastructure/nft/helius-nft-indexer.js +1 -2
  173. package/dist/infrastructure/nft/helius-nft-indexer.js.map +1 -1
  174. package/dist/infrastructure/nft/nft-indexer-client.d.ts.map +1 -1
  175. package/dist/infrastructure/nft/nft-indexer-client.js +0 -2
  176. package/dist/infrastructure/nft/nft-indexer-client.js.map +1 -1
  177. package/dist/infrastructure/security/ssrf-guard.d.ts +33 -0
  178. package/dist/infrastructure/security/ssrf-guard.d.ts.map +1 -0
  179. package/dist/infrastructure/security/ssrf-guard.js +244 -0
  180. package/dist/infrastructure/security/ssrf-guard.js.map +1 -0
  181. package/dist/infrastructure/settings/hot-reload.d.ts +1 -1
  182. package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
  183. package/dist/infrastructure/settings/hot-reload.js +0 -2
  184. package/dist/infrastructure/settings/hot-reload.js.map +1 -1
  185. package/dist/infrastructure/settings/index.d.ts +2 -2
  186. package/dist/infrastructure/settings/index.d.ts.map +1 -1
  187. package/dist/infrastructure/settings/index.js +1 -1
  188. package/dist/infrastructure/settings/index.js.map +1 -1
  189. package/dist/infrastructure/settings/setting-keys.d.ts +14 -0
  190. package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
  191. package/dist/infrastructure/settings/setting-keys.js +296 -214
  192. package/dist/infrastructure/settings/setting-keys.js.map +1 -1
  193. package/dist/infrastructure/settings/settings-service.d.ts +6 -1
  194. package/dist/infrastructure/settings/settings-service.d.ts.map +1 -1
  195. package/dist/infrastructure/settings/settings-service.js +15 -5
  196. package/dist/infrastructure/settings/settings-service.js.map +1 -1
  197. package/dist/infrastructure/telegram/telegram-bot-service.d.ts.map +1 -1
  198. package/dist/infrastructure/telegram/telegram-bot-service.js +3 -2
  199. package/dist/infrastructure/telegram/telegram-bot-service.js.map +1 -1
  200. package/dist/infrastructure/token-registry/builtin-tokens.d.ts.map +1 -1
  201. package/dist/infrastructure/token-registry/builtin-tokens.js +4 -7
  202. package/dist/infrastructure/token-registry/builtin-tokens.js.map +1 -1
  203. package/dist/lifecycle/daemon-pipeline.d.ts +49 -0
  204. package/dist/lifecycle/daemon-pipeline.d.ts.map +1 -0
  205. package/dist/lifecycle/daemon-pipeline.js +281 -0
  206. package/dist/lifecycle/daemon-pipeline.js.map +1 -0
  207. package/dist/lifecycle/daemon-shutdown.d.ts +14 -0
  208. package/dist/lifecycle/daemon-shutdown.d.ts.map +1 -0
  209. package/dist/lifecycle/daemon-shutdown.js +176 -0
  210. package/dist/lifecycle/daemon-shutdown.js.map +1 -0
  211. package/dist/lifecycle/daemon-startup.d.ts +15 -0
  212. package/dist/lifecycle/daemon-startup.d.ts.map +1 -0
  213. package/dist/lifecycle/daemon-startup.js +1527 -0
  214. package/dist/lifecycle/daemon-startup.js.map +1 -0
  215. package/dist/lifecycle/daemon.d.ts +171 -114
  216. package/dist/lifecycle/daemon.d.ts.map +1 -1
  217. package/dist/lifecycle/daemon.js +22 -1904
  218. package/dist/lifecycle/daemon.js.map +1 -1
  219. package/dist/notifications/channels/discord.d.ts.map +1 -1
  220. package/dist/notifications/channels/discord.js +1 -0
  221. package/dist/notifications/channels/discord.js.map +1 -1
  222. package/dist/notifications/channels/slack.d.ts.map +1 -1
  223. package/dist/notifications/channels/slack.js +1 -0
  224. package/dist/notifications/channels/slack.js.map +1 -1
  225. package/dist/notifications/index.d.ts +0 -1
  226. package/dist/notifications/index.d.ts.map +1 -1
  227. package/dist/notifications/index.js +0 -1
  228. package/dist/notifications/index.js.map +1 -1
  229. package/dist/notifications/notification-service.d.ts.map +1 -1
  230. package/dist/notifications/notification-service.js +8 -6
  231. package/dist/notifications/notification-service.js.map +1 -1
  232. package/dist/pipeline/database-policy-engine.d.ts +18 -438
  233. package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
  234. package/dist/pipeline/database-policy-engine.js +154 -1321
  235. package/dist/pipeline/database-policy-engine.js.map +1 -1
  236. package/dist/pipeline/dry-run.d.ts +5 -2
  237. package/dist/pipeline/dry-run.d.ts.map +1 -1
  238. package/dist/pipeline/dry-run.js +102 -8
  239. package/dist/pipeline/dry-run.js.map +1 -1
  240. package/dist/pipeline/evaluators/allowed-tokens.d.ts +28 -0
  241. package/dist/pipeline/evaluators/allowed-tokens.d.ts.map +1 -0
  242. package/dist/pipeline/evaluators/allowed-tokens.js +129 -0
  243. package/dist/pipeline/evaluators/allowed-tokens.js.map +1 -0
  244. package/dist/pipeline/evaluators/approved-spenders.d.ts +26 -0
  245. package/dist/pipeline/evaluators/approved-spenders.d.ts.map +1 -0
  246. package/dist/pipeline/evaluators/approved-spenders.js +115 -0
  247. package/dist/pipeline/evaluators/approved-spenders.js.map +1 -0
  248. package/dist/pipeline/evaluators/contract-whitelist.d.ts +28 -0
  249. package/dist/pipeline/evaluators/contract-whitelist.d.ts.map +1 -0
  250. package/dist/pipeline/evaluators/contract-whitelist.js +168 -0
  251. package/dist/pipeline/evaluators/contract-whitelist.js.map +1 -0
  252. package/dist/pipeline/evaluators/helpers.d.ts +9 -0
  253. package/dist/pipeline/evaluators/helpers.d.ts.map +1 -0
  254. package/dist/pipeline/evaluators/helpers.js +13 -0
  255. package/dist/pipeline/evaluators/helpers.js.map +1 -0
  256. package/dist/pipeline/evaluators/lending-asset-whitelist.d.ts +18 -0
  257. package/dist/pipeline/evaluators/lending-asset-whitelist.d.ts.map +1 -0
  258. package/dist/pipeline/evaluators/lending-asset-whitelist.js +44 -0
  259. package/dist/pipeline/evaluators/lending-asset-whitelist.js.map +1 -0
  260. package/dist/pipeline/evaluators/lending-ltv-limit.d.ts +24 -0
  261. package/dist/pipeline/evaluators/lending-ltv-limit.d.ts.map +1 -0
  262. package/dist/pipeline/evaluators/lending-ltv-limit.js +130 -0
  263. package/dist/pipeline/evaluators/lending-ltv-limit.js.map +1 -0
  264. package/dist/pipeline/evaluators/spending-limit.d.ts +46 -0
  265. package/dist/pipeline/evaluators/spending-limit.d.ts.map +1 -0
  266. package/dist/pipeline/evaluators/spending-limit.js +241 -0
  267. package/dist/pipeline/evaluators/spending-limit.js.map +1 -0
  268. package/dist/pipeline/evaluators/types.d.ts +71 -0
  269. package/dist/pipeline/evaluators/types.d.ts.map +1 -0
  270. package/dist/pipeline/evaluators/types.js +7 -0
  271. package/dist/pipeline/evaluators/types.js.map +1 -0
  272. package/dist/pipeline/external-action-pipeline.js.map +1 -1
  273. package/dist/pipeline/gas-condition-tracker.d.ts +1 -1
  274. package/dist/pipeline/gas-condition-tracker.js +1 -1
  275. package/dist/pipeline/pipeline-helpers.d.ts +146 -0
  276. package/dist/pipeline/pipeline-helpers.d.ts.map +1 -0
  277. package/dist/pipeline/pipeline-helpers.js +260 -0
  278. package/dist/pipeline/pipeline-helpers.js.map +1 -0
  279. package/dist/pipeline/pipeline.d.ts +1 -0
  280. package/dist/pipeline/pipeline.d.ts.map +1 -1
  281. package/dist/pipeline/pipeline.js +3 -2
  282. package/dist/pipeline/pipeline.js.map +1 -1
  283. package/dist/pipeline/resolve-effective-amount-usd.d.ts.map +1 -1
  284. package/dist/pipeline/resolve-effective-amount-usd.js +4 -10
  285. package/dist/pipeline/resolve-effective-amount-usd.js.map +1 -1
  286. package/dist/pipeline/sign-message.js +1 -1
  287. package/dist/pipeline/sign-message.js.map +1 -1
  288. package/dist/pipeline/sleep.d.ts +1 -5
  289. package/dist/pipeline/sleep.d.ts.map +1 -1
  290. package/dist/pipeline/sleep.js +2 -7
  291. package/dist/pipeline/sleep.js.map +1 -1
  292. package/dist/pipeline/stage1-validate.d.ts +8 -0
  293. package/dist/pipeline/stage1-validate.d.ts.map +1 -0
  294. package/dist/pipeline/stage1-validate.js +69 -0
  295. package/dist/pipeline/stage1-validate.js.map +1 -0
  296. package/dist/pipeline/stage2-auth.d.ts +12 -0
  297. package/dist/pipeline/stage2-auth.d.ts.map +1 -0
  298. package/dist/pipeline/stage2-auth.js +18 -0
  299. package/dist/pipeline/stage2-auth.js.map +1 -0
  300. package/dist/pipeline/stage3-policy.d.ts +26 -0
  301. package/dist/pipeline/stage3-policy.d.ts.map +1 -0
  302. package/dist/pipeline/stage3-policy.js +384 -0
  303. package/dist/pipeline/stage3-policy.js.map +1 -0
  304. package/dist/pipeline/stage4-wait.d.ts +8 -0
  305. package/dist/pipeline/stage4-wait.d.ts.map +1 -0
  306. package/dist/pipeline/stage4-wait.js +87 -0
  307. package/dist/pipeline/stage4-wait.js.map +1 -0
  308. package/dist/pipeline/stage5-execute.d.ts +120 -0
  309. package/dist/pipeline/stage5-execute.d.ts.map +1 -0
  310. package/dist/pipeline/stage5-execute.js +1070 -0
  311. package/dist/pipeline/stage5-execute.js.map +1 -0
  312. package/dist/pipeline/stage6-confirm.d.ts +11 -0
  313. package/dist/pipeline/stage6-confirm.d.ts.map +1 -0
  314. package/dist/pipeline/stage6-confirm.js +110 -0
  315. package/dist/pipeline/stage6-confirm.js.map +1 -0
  316. package/dist/pipeline/stages.d.ts +11 -245
  317. package/dist/pipeline/stages.d.ts.map +1 -1
  318. package/dist/pipeline/stages.js +11 -1896
  319. package/dist/pipeline/stages.js.map +1 -1
  320. package/dist/rpc-proxy/sync-pipeline.js +2 -2
  321. package/dist/rpc-proxy/sync-pipeline.js.map +1 -1
  322. package/dist/services/autostop/autostop-service.d.ts +4 -1
  323. package/dist/services/autostop/autostop-service.d.ts.map +1 -1
  324. package/dist/services/autostop/autostop-service.js +27 -7
  325. package/dist/services/autostop/autostop-service.js.map +1 -1
  326. package/dist/services/defi/position-tracker.d.ts +5 -0
  327. package/dist/services/defi/position-tracker.d.ts.map +1 -1
  328. package/dist/services/defi/position-tracker.js +41 -6
  329. package/dist/services/defi/position-tracker.js.map +1 -1
  330. package/dist/services/defi/position-write-queue.d.ts.map +1 -1
  331. package/dist/services/defi/position-write-queue.js +3 -2
  332. package/dist/services/defi/position-write-queue.js.map +1 -1
  333. package/dist/services/incoming/__tests__/integration-wiring.test.js +58 -0
  334. package/dist/services/incoming/__tests__/integration-wiring.test.js.map +1 -1
  335. package/dist/services/incoming/incoming-tx-monitor-service.d.ts.map +1 -1
  336. package/dist/services/incoming/incoming-tx-monitor-service.js +11 -14
  337. package/dist/services/incoming/incoming-tx-monitor-service.js.map +1 -1
  338. package/dist/services/incoming/incoming-tx-workers.d.ts +2 -2
  339. package/dist/services/incoming/incoming-tx-workers.d.ts.map +1 -1
  340. package/dist/services/incoming/incoming-tx-workers.js +1 -1
  341. package/dist/services/incoming/incoming-tx-workers.js.map +1 -1
  342. package/dist/services/incoming/safety-rules.d.ts.map +1 -1
  343. package/dist/services/incoming/safety-rules.js +3 -2
  344. package/dist/services/incoming/safety-rules.js.map +1 -1
  345. package/dist/services/incoming/subscription-multiplexer.d.ts +2 -6
  346. package/dist/services/incoming/subscription-multiplexer.d.ts.map +1 -1
  347. package/dist/services/incoming/subscription-multiplexer.js +1 -3
  348. package/dist/services/incoming/subscription-multiplexer.js.map +1 -1
  349. package/dist/services/monitoring/balance-monitor-service.d.ts.map +1 -1
  350. package/dist/services/monitoring/balance-monitor-service.js +2 -2
  351. package/dist/services/monitoring/balance-monitor-service.js.map +1 -1
  352. package/dist/services/signing-sdk/approval-channel-router.d.ts +7 -7
  353. package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
  354. package/dist/services/signing-sdk/approval-channel-router.js +13 -13
  355. package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
  356. package/dist/services/signing-sdk/channels/index.d.ts +2 -2
  357. package/dist/services/signing-sdk/channels/index.d.ts.map +1 -1
  358. package/dist/services/signing-sdk/channels/index.js +1 -1
  359. package/dist/services/signing-sdk/channels/index.js.map +1 -1
  360. package/dist/services/signing-sdk/channels/push-relay-signing-channel.d.ts +59 -0
  361. package/dist/services/signing-sdk/channels/push-relay-signing-channel.d.ts.map +1 -0
  362. package/dist/services/signing-sdk/channels/push-relay-signing-channel.js +190 -0
  363. package/dist/services/signing-sdk/channels/push-relay-signing-channel.js.map +1 -0
  364. package/dist/services/signing-sdk/channels/telegram-signing-channel.d.ts +1 -1
  365. package/dist/services/signing-sdk/channels/telegram-signing-channel.js +1 -1
  366. package/dist/services/signing-sdk/channels/wallet-notification-channel.d.ts +6 -7
  367. package/dist/services/signing-sdk/channels/wallet-notification-channel.d.ts.map +1 -1
  368. package/dist/services/signing-sdk/channels/wallet-notification-channel.js +38 -55
  369. package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
  370. package/dist/services/signing-sdk/index.d.ts +3 -3
  371. package/dist/services/signing-sdk/index.d.ts.map +1 -1
  372. package/dist/services/signing-sdk/index.js +2 -2
  373. package/dist/services/signing-sdk/index.js.map +1 -1
  374. package/dist/services/signing-sdk/preset-auto-setup.js +2 -2
  375. package/dist/services/signing-sdk/preset-auto-setup.js.map +1 -1
  376. package/dist/services/signing-sdk/sign-request-builder.d.ts +2 -2
  377. package/dist/services/signing-sdk/sign-request-builder.d.ts.map +1 -1
  378. package/dist/services/signing-sdk/sign-request-builder.js +17 -25
  379. package/dist/services/signing-sdk/sign-request-builder.js.map +1 -1
  380. package/dist/services/signing-sdk/wallet-app-service.d.ts +4 -0
  381. package/dist/services/signing-sdk/wallet-app-service.d.ts.map +1 -1
  382. package/dist/services/signing-sdk/wallet-app-service.js +12 -5
  383. package/dist/services/signing-sdk/wallet-app-service.js.map +1 -1
  384. package/dist/services/staking/aggregate-staking-balance.d.ts +24 -0
  385. package/dist/services/staking/aggregate-staking-balance.d.ts.map +1 -0
  386. package/dist/services/staking/aggregate-staking-balance.js +82 -0
  387. package/dist/services/staking/aggregate-staking-balance.js.map +1 -0
  388. package/dist/services/wc-session-service.d.ts.map +1 -1
  389. package/dist/services/wc-session-service.js +2 -1
  390. package/dist/services/wc-session-service.js.map +1 -1
  391. package/dist/services/wc-signing-bridge.js +2 -2
  392. package/dist/services/wc-signing-bridge.js.map +1 -1
  393. package/dist/services/x402/payment-signer.d.ts.map +1 -1
  394. package/dist/services/x402/payment-signer.js +2 -5
  395. package/dist/services/x402/payment-signer.js.map +1 -1
  396. package/dist/services/x402/ssrf-guard.d.ts +4 -23
  397. package/dist/services/x402/ssrf-guard.d.ts.map +1 -1
  398. package/dist/services/x402/ssrf-guard.js +3 -232
  399. package/dist/services/x402/ssrf-guard.js.map +1 -1
  400. package/dist/signing/capabilities/eip712-signer.d.ts.map +1 -1
  401. package/dist/signing/capabilities/eip712-signer.js +2 -0
  402. package/dist/signing/capabilities/eip712-signer.js.map +1 -1
  403. package/package.json +5 -5
  404. package/public/admin/assets/index-CpFF2lCo.js +3 -0
  405. package/public/admin/index.html +1 -1
  406. package/dist/notifications/channels/ntfy.d.ts +0 -13
  407. package/dist/notifications/channels/ntfy.d.ts.map +0 -1
  408. package/dist/notifications/channels/ntfy.js +0 -74
  409. package/dist/notifications/channels/ntfy.js.map +0 -1
  410. package/dist/services/signing-sdk/channels/ntfy-signing-channel.d.ts +0 -66
  411. package/dist/services/signing-sdk/channels/ntfy-signing-channel.d.ts.map +0 -1
  412. package/dist/services/signing-sdk/channels/ntfy-signing-channel.js +0 -270
  413. package/dist/services/signing-sdk/channels/ntfy-signing-channel.js.map +0 -1
  414. package/public/admin/assets/index-CQ3i4P2U.js +0 -3
@@ -1,84 +1,31 @@
1
1
  /**
2
2
  * DatabasePolicyEngine - v1.2 DB-backed policy engine with network scoping.
3
3
  *
4
- * Evaluates transactions against policies stored in the policies table.
5
- * Supports SPENDING_LIMIT (4-tier classification), WHITELIST (address filtering),
6
- * ALLOWED_NETWORKS (network whitelist, permissive default),
7
- * ALLOWED_TOKENS (token transfer whitelist, default deny),
8
- * CONTRACT_WHITELIST (contract call whitelist, default deny),
9
- * METHOD_WHITELIST (optional method-level restriction for contract calls),
10
- * APPROVED_SPENDERS (approve spender whitelist, default deny),
11
- * APPROVE_AMOUNT_LIMIT (unlimited approve block + amount cap),
12
- * and APPROVE_TIER_OVERRIDE (forced tier for APPROVE transactions).
13
- *
14
- * Algorithm:
15
- * 1. Load enabled policies for wallet (wallet-specific + global), ORDER BY priority DESC
16
- * 2. If no policies found, return INSTANT passthrough (Phase 7 compat)
17
- * 3. Resolve overrides: 4-level priority (wallet+network > wallet+null > global+network > global+null)
18
- * 4. Evaluate WHITELIST: deny if toAddress not in allowed_addresses
19
- * 4a.5. Evaluate ALLOWED_NETWORKS: deny if network not in allowed list (permissive default)
20
- * 4b. Evaluate ALLOWED_TOKENS: deny TOKEN_TRANSFER if no policy or token not whitelisted
21
- * 4c. Evaluate CONTRACT_WHITELIST: deny CONTRACT_CALL if no policy or contract not whitelisted
22
- * 4d. Evaluate METHOD_WHITELIST: deny CONTRACT_CALL if method selector not whitelisted (optional)
23
- * 4e. Evaluate APPROVED_SPENDERS: deny APPROVE if no policy or spender not approved
24
- * 4f. Evaluate APPROVE_AMOUNT_LIMIT: deny APPROVE if unlimited or exceeds max amount
25
- * 4g. Evaluate APPROVE_TIER_OVERRIDE: force tier for APPROVE (defaults to APPROVAL, skips SPENDING_LIMIT)
26
- * 4h. Evaluate LENDING_ASSET_WHITELIST: deny lending action if asset not whitelisted (default-deny)
27
- * 4h-b. Evaluate LENDING_LTV_LIMIT: deny borrow if projected LTV exceeds maxLtv
28
- * 4i. Evaluate PERP_ALLOWED_MARKETS: deny perp action if market not whitelisted (default-deny)
29
- * 4i-b. Evaluate PERP_MAX_LEVERAGE: deny open/modify if leverage exceeds max
30
- * 4i-c. Evaluate PERP_MAX_POSITION_USD: deny open/modify if position USD exceeds max
31
- * 5. Evaluate SPENDING_LIMIT: classify amount into INSTANT/NOTIFY/DELAY/APPROVAL
32
- * (skip for non-spending lending actions: supply/repay/withdraw)
4
+ * This file contains the orchestration class that dispatches to evaluator modules
5
+ * in the evaluators/ directory. Each policy type has its own evaluator file.
33
6
  *
34
- * TOCTOU Prevention (evaluateAndReserve):
35
- * Uses BEGIN IMMEDIATE to serialize concurrent policy evaluations.
36
- * reserved_amount tracks pending amounts to prevent two requests from both passing
37
- * under the same spending limit.
7
+ * Evaluates transactions against policies stored in the policies table.
8
+ * Supports SPENDING_LIMIT, WHITELIST, ALLOWED_NETWORKS, ALLOWED_TOKENS,
9
+ * CONTRACT_WHITELIST, METHOD_WHITELIST, APPROVED_SPENDERS, APPROVE_AMOUNT_LIMIT,
10
+ * APPROVE_TIER_OVERRIDE, LENDING_ASSET_WHITELIST, LENDING_LTV_LIMIT,
11
+ * PERP_ALLOWED_MARKETS, PERP_MAX_LEVERAGE, PERP_MAX_POSITION_USD,
12
+ * VENUE_WHITELIST, ACTION_CATEGORY_LIMIT, and REPUTATION_THRESHOLD.
38
13
  *
39
14
  * @see docs/33-time-lock-approval-mechanism.md
40
15
  * @see docs/71-policy-engine-network-extension-design.md
41
16
  */
42
- import { parseCaip19 } from '@waiaas/core';
17
+ import { safeJsonParse, WAIaaSError, SpendingLimitRulesSchema, ApproveTierOverrideRulesSchema, ReputationThresholdRulesSchema, } from '@waiaas/core';
18
+ import {} from 'zod';
43
19
  import { eq, or, and, isNull, desc } from 'drizzle-orm';
44
20
  import { policies, wallets, agentIdentities } from '../infrastructure/database/schema.js';
45
- /** Threshold for detecting "unlimited" approve amounts. */
46
- const UNLIMITED_THRESHOLD = (2n ** 256n - 1n) / 2n;
47
- // Phase 236: Native token decimal places for human-readable conversion
48
- const NATIVE_DECIMALS = {
49
- solana: 9,
50
- ethereum: 18,
51
- };
52
- /**
53
- * Parse a human-readable decimal string (e.g. "1.5", "1000") to raw bigint units.
54
- * Multiplies the value by 10^decimals for precise BigInt comparison.
55
- *
56
- * Examples:
57
- * parseDecimalToBigInt("1.5", 9) -> 1500000000n (1.5 SOL in lamports)
58
- * parseDecimalToBigInt("1000", 6) -> 1000000000n (1000 USDC in raw)
59
- */
60
- function parseDecimalToBigInt(value, decimals) {
61
- const parts = value.split('.');
62
- const integerPart = parts[0] ?? '0';
63
- let fractionalPart = parts[1] ?? '';
64
- // Pad or truncate fractional part to exactly `decimals` digits
65
- if (fractionalPart.length > decimals) {
66
- fractionalPart = fractionalPart.slice(0, decimals);
67
- }
68
- else {
69
- fractionalPart = fractionalPart.padEnd(decimals, '0');
70
- }
71
- return BigInt(integerPart + fractionalPart);
72
- }
73
- // ---------------------------------------------------------------------------
74
- // Tier order + maxTier helper (Phase 127)
75
- // ---------------------------------------------------------------------------
76
- const TIER_ORDER = ['INSTANT', 'NOTIFY', 'DELAY', 'APPROVAL'];
77
- function maxTier(a, b) {
78
- const aIdx = TIER_ORDER.indexOf(a);
79
- const bIdx = TIER_ORDER.indexOf(b);
80
- return TIER_ORDER[Math.max(aIdx, bIdx)];
81
- }
21
+ // Evaluator imports
22
+ import { evaluateWhitelist, evaluateAllowedNetworks, evaluateAllowedTokens } from './evaluators/allowed-tokens.js';
23
+ import { evaluateContractWhitelist, evaluateMethodWhitelist, evaluateVenueWhitelist, evaluatePerpAllowedMarkets } from './evaluators/contract-whitelist.js';
24
+ import { evaluateApprovedSpenders, evaluateApproveAmountLimit, evaluateApproveTierOverride } from './evaluators/approved-spenders.js';
25
+ import { evaluateSpendingLimit, evaluateActionCategoryLimit } from './evaluators/spending-limit.js';
26
+ import { evaluateLendingAssetWhitelist } from './evaluators/lending-asset-whitelist.js';
27
+ import { evaluateLendingLtvLimit, evaluatePerpMaxLeverage, evaluatePerpMaxPositionUsd } from './evaluators/lending-ltv-limit.js';
28
+ import { maxTier } from './evaluators/helpers.js';
82
29
  // ---------------------------------------------------------------------------
83
30
  // DatabasePolicyEngine
84
31
  // ---------------------------------------------------------------------------
@@ -89,9 +36,6 @@ function maxTier(a, b) {
89
36
  *
90
37
  * Network scoping: policies can target specific networks via the `network` column.
91
38
  * 4-level override priority: wallet+network > wallet+null > global+network > global+null.
92
- *
93
- * Constructor takes a Drizzle DB instance typed with the full schema,
94
- * and optionally a raw better-sqlite3 Database instance for BEGIN IMMEDIATE transactions.
95
39
  */
96
40
  export class DatabasePolicyEngine {
97
41
  db;
@@ -104,6 +48,24 @@ export class DatabasePolicyEngine {
104
48
  this.settingsService = settingsService ?? null;
105
49
  this.reputationCacheService = reputationCacheService ?? null;
106
50
  }
51
+ /** Evaluator context with parseRules + settingsService access. */
52
+ get ctx() {
53
+ return {
54
+ parseRules: this.parseRules.bind(this),
55
+ settingsService: this.settingsService,
56
+ };
57
+ }
58
+ /**
59
+ * Parse policy rules JSON with Zod validation.
60
+ * Throws POLICY_RULES_CORRUPT on invalid JSON or schema mismatch.
61
+ */
62
+ parseRules(rules, zodSchema, policyType) {
63
+ const result = safeJsonParse(rules, zodSchema);
64
+ if (!result.success) {
65
+ throw new WAIaaSError('POLICY_RULES_CORRUPT', { message: `${policyType} policy rules corrupt: ${result.error.message}` });
66
+ }
67
+ return result.data;
68
+ }
107
69
  /**
108
70
  * Evaluate a transaction against DB-stored policies.
109
71
  */
@@ -119,138 +81,117 @@ export class DatabasePolicyEngine {
119
81
  if (rows.length === 0) {
120
82
  return { allowed: true, tier: 'INSTANT' };
121
83
  }
122
- // Step 3: Resolve overrides (4-level: wallet+network > wallet+null > global+network > global+null)
84
+ // Step 3: Resolve overrides
123
85
  const resolved = this.resolveOverrides(rows, walletId, transaction.network);
86
+ const ctx = this.ctx;
124
87
  // Step 4: Evaluate WHITELIST (deny-first)
125
- const whitelistResult = this.evaluateWhitelist(resolved, transaction.toAddress);
126
- if (whitelistResult !== null) {
88
+ const whitelistResult = evaluateWhitelist(ctx, resolved, transaction.toAddress);
89
+ if (whitelistResult !== null)
127
90
  return whitelistResult;
128
- }
129
- // Step 4a.5: Evaluate ALLOWED_NETWORKS (network whitelist, permissive default)
91
+ // Step 4a.5: Evaluate ALLOWED_NETWORKS
130
92
  if (transaction.network) {
131
- const allowedNetworksResult = this.evaluateAllowedNetworks(resolved, transaction.network);
132
- if (allowedNetworksResult !== null) {
93
+ const allowedNetworksResult = evaluateAllowedNetworks(ctx, resolved, transaction.network);
94
+ if (allowedNetworksResult !== null)
133
95
  return allowedNetworksResult;
134
- }
135
96
  }
136
- // Step 4b: Evaluate ALLOWED_TOKENS (token transfer whitelist)
137
- const allowedTokensResult = this.evaluateAllowedTokens(resolved, transaction);
138
- if (allowedTokensResult !== null) {
97
+ // Step 4b: Evaluate ALLOWED_TOKENS
98
+ const allowedTokensResult = evaluateAllowedTokens(ctx, resolved, transaction);
99
+ if (allowedTokensResult !== null)
139
100
  return allowedTokensResult;
140
- }
141
- // Step 4c: Evaluate CONTRACT_WHITELIST (contract call whitelist)
142
- const contractWhitelistResult = this.evaluateContractWhitelist(resolved, transaction);
143
- if (contractWhitelistResult !== null) {
101
+ // Step 4c: Evaluate CONTRACT_WHITELIST
102
+ const contractWhitelistResult = evaluateContractWhitelist(ctx, resolved, transaction);
103
+ if (contractWhitelistResult !== null)
144
104
  return contractWhitelistResult;
145
- }
146
- // Step 4d: Evaluate METHOD_WHITELIST (method-level restriction)
147
- const methodWhitelistResult = this.evaluateMethodWhitelist(resolved, transaction);
148
- if (methodWhitelistResult !== null) {
105
+ // Step 4d: Evaluate METHOD_WHITELIST
106
+ const methodWhitelistResult = evaluateMethodWhitelist(ctx, resolved, transaction);
107
+ if (methodWhitelistResult !== null)
149
108
  return methodWhitelistResult;
150
- }
151
- // Step 4e: Evaluate APPROVED_SPENDERS (approve spender whitelist)
152
- const approvedSpendersResult = this.evaluateApprovedSpenders(resolved, transaction);
153
- if (approvedSpendersResult !== null) {
109
+ // Step 4e: Evaluate APPROVED_SPENDERS
110
+ const approvedSpendersResult = evaluateApprovedSpenders(ctx, resolved, transaction);
111
+ if (approvedSpendersResult !== null)
154
112
  return approvedSpendersResult;
155
- }
156
- // Step 4e.5: Evaluate REPUTATION_THRESHOLD (counterparty reputation check, Phase 320)
113
+ // Step 4e.5: Evaluate REPUTATION_THRESHOLD (async, kept in this class)
157
114
  const reputationFloorTier = await this.evaluateReputationThreshold(resolved, transaction);
158
- // Step 4f: Evaluate APPROVE_AMOUNT_LIMIT (unlimited approve block + amount cap)
159
- const approveAmountResult = this.evaluateApproveAmountLimit(resolved, transaction);
160
- if (approveAmountResult !== null) {
115
+ // Step 4f: Evaluate APPROVE_AMOUNT_LIMIT
116
+ const approveAmountResult = evaluateApproveAmountLimit(ctx, resolved, transaction);
117
+ if (approveAmountResult !== null)
161
118
  return approveAmountResult;
162
- }
163
- // Step 4g: Evaluate APPROVE_TIER_OVERRIDE (forced tier for APPROVE transactions)
164
- const approveTierResult = this.evaluateApproveTierOverride(resolved, transaction);
165
- if (approveTierResult !== null) {
166
- return approveTierResult; // FINAL result, skips SPENDING_LIMIT (including token_limits)
167
- }
119
+ // Step 4g: Evaluate APPROVE_TIER_OVERRIDE
120
+ const approveTierResult = evaluateApproveTierOverride(ctx, resolved, transaction);
121
+ if (approveTierResult !== null)
122
+ return approveTierResult;
168
123
  // Step 4g.5: NFT_TRANSFER default tier APPROVAL (PLCY-03, v31.0)
169
- // NFT transfers default to APPROVAL tier (owner approval required) unless overridden.
170
124
  if (transaction.type === 'NFT_TRANSFER') {
171
125
  let nftTierOverride = null;
172
126
  try {
173
127
  nftTierOverride = this.settingsService?.get('policy.nft_transfer_default_tier') ?? null;
174
128
  }
175
- catch {
176
- // Setting key not registered yet -- use default
177
- }
129
+ catch { /* Setting key not registered yet */ }
178
130
  const nftTier = (nftTierOverride ?? 'APPROVAL');
179
131
  const finalTier = reputationFloorTier ? maxTier(nftTier, reputationFloorTier) : nftTier;
180
132
  return { allowed: true, tier: finalTier };
181
133
  }
182
134
  // v31.14: CONTRACT_DEPLOY default tier APPROVAL (DEPL-04)
183
- // Contract deployments default to APPROVAL tier (owner approval required) unless overridden.
184
135
  if (transaction.type === 'CONTRACT_DEPLOY') {
185
136
  let deployTierOverride = null;
186
137
  try {
187
138
  deployTierOverride = this.settingsService?.get('rpc_proxy.deploy_default_tier') ?? null;
188
139
  }
189
- catch {
190
- // Setting key not registered yet -- use default
191
- }
140
+ catch { /* Setting key not registered yet */ }
192
141
  const deployTier = (deployTierOverride ?? 'APPROVAL');
193
142
  const finalTier = reputationFloorTier ? maxTier(deployTier, reputationFloorTier) : deployTier;
194
143
  return { allowed: true, tier: finalTier };
195
144
  }
196
- // Step 4h: Evaluate LENDING_ASSET_WHITELIST (default-deny for lending assets)
197
- const lendingAssetResult = this.evaluateLendingAssetWhitelist(resolved, transaction);
198
- if (lendingAssetResult !== null) {
145
+ // Step 4h: Evaluate LENDING_ASSET_WHITELIST
146
+ const lendingAssetResult = evaluateLendingAssetWhitelist(ctx, resolved, transaction);
147
+ if (lendingAssetResult !== null)
199
148
  return lendingAssetResult;
200
- }
201
- // Step 4h-b: Evaluate LENDING_LTV_LIMIT (LTV-based borrow restriction)
202
- const ltvResult = this.evaluateLendingLtvLimit(resolved, transaction, walletId);
203
- if (ltvResult !== null) {
149
+ // Step 4h-b: Evaluate LENDING_LTV_LIMIT
150
+ const ltvResult = evaluateLendingLtvLimit(ctx, resolved, transaction, walletId, this.sqlite);
151
+ if (ltvResult !== null)
204
152
  return ltvResult;
205
- }
206
- // Step 4i: Evaluate PERP_ALLOWED_MARKETS (default-deny for perp markets)
207
- const perpMarketResult = this.evaluatePerpAllowedMarkets(resolved, transaction);
153
+ // Step 4i: Evaluate PERP_ALLOWED_MARKETS
154
+ const perpMarketResult = evaluatePerpAllowedMarkets(ctx, resolved, transaction);
208
155
  if (perpMarketResult !== null)
209
156
  return perpMarketResult;
210
- // Step 4i-b: Evaluate PERP_MAX_LEVERAGE (deny if leverage exceeds max)
211
- const leverageResult = this.evaluatePerpMaxLeverage(resolved, transaction);
157
+ // Step 4i-b: Evaluate PERP_MAX_LEVERAGE
158
+ const leverageResult = evaluatePerpMaxLeverage(ctx, resolved, transaction);
212
159
  if (leverageResult !== null)
213
160
  return leverageResult;
214
- // Step 4i-c: Evaluate PERP_MAX_POSITION_USD (deny if position USD exceeds max)
215
- const positionUsdResult = this.evaluatePerpMaxPositionUsd(resolved, transaction);
161
+ // Step 4i-c: Evaluate PERP_MAX_POSITION_USD
162
+ const positionUsdResult = evaluatePerpMaxPositionUsd(ctx, resolved, transaction);
216
163
  if (positionUsdResult !== null)
217
164
  return positionUsdResult;
218
- // Step 4j: Evaluate VENUE_WHITELIST (Phase 389)
219
- const venueResult = this.evaluateVenueWhitelist(resolved, transaction);
165
+ // Step 4j: Evaluate VENUE_WHITELIST
166
+ const venueResult = evaluateVenueWhitelist(ctx, resolved, transaction);
220
167
  if (venueResult !== null)
221
168
  return venueResult;
222
- // Step 4k: Evaluate ACTION_CATEGORY_LIMIT (Phase 389)
223
- const categoryLimitResult = this.evaluateActionCategoryLimit(resolved, transaction, walletId);
169
+ // Step 4k: Evaluate ACTION_CATEGORY_LIMIT
170
+ const categoryLimitResult = evaluateActionCategoryLimit(ctx, resolved, transaction, walletId, this.sqlite);
224
171
  if (categoryLimitResult !== null) {
225
- // ACTION_CATEGORY_LIMIT returns allowed:true with escalated tier
226
- // Combine with reputation floor tier and return
227
172
  if (reputationFloorTier) {
228
173
  categoryLimitResult.tier = maxTier(categoryLimitResult.tier, reputationFloorTier);
229
174
  }
230
175
  return categoryLimitResult;
231
176
  }
232
- // Step 5: Non-spending classification for lending actions (LEND-08, research flag M6)
233
- // supply/repay/withdraw are non-spending — skip SPENDING_LIMIT entirely
177
+ // Step 5: Non-spending classification for lending actions
234
178
  const NON_SPENDING_ACTIONS = new Set(['supply', 'repay', 'withdraw', 'close_position', 'add_margin']);
235
179
  if (transaction.actionName && NON_SPENDING_ACTIONS.has(transaction.actionName)) {
236
180
  const baseTier = 'INSTANT';
237
181
  const finalTier = reputationFloorTier ? maxTier(baseTier, reputationFloorTier) : baseTier;
238
182
  return { allowed: true, tier: finalTier };
239
183
  }
240
- // Step 5 (continued): Evaluate SPENDING_LIMIT (tier classification)
241
- // Phase 236: Build tokenContext from transaction for token_limits evaluation
184
+ // Step 5 (continued): Evaluate SPENDING_LIMIT
242
185
  const spendingPolicy = resolved.find((p) => p.type === 'SPENDING_LIMIT');
243
186
  const tokenContext = this.buildTokenContext(transaction, spendingPolicy);
244
- const spendingResult = this.evaluateSpendingLimit(resolved, transaction.amount, undefined, tokenContext);
187
+ const spendingResult = evaluateSpendingLimit(ctx, resolved, transaction.amount, undefined, tokenContext);
245
188
  if (spendingResult !== null) {
246
- // [Phase 320] Apply reputation floor tier via maxTier (escalation only)
247
189
  if (reputationFloorTier) {
248
190
  spendingResult.tier = maxTier(spendingResult.tier, reputationFloorTier);
249
191
  }
250
192
  return spendingResult;
251
193
  }
252
- // Default: INSTANT passthrough (no applicable rules)
253
- // [Phase 320] Apply reputation floor tier if set
194
+ // Default: INSTANT passthrough
254
195
  const defaultTier = 'INSTANT';
255
196
  const finalDefaultTier = reputationFloorTier ? maxTier(defaultTier, reputationFloorTier) : defaultTier;
256
197
  return { allowed: true, tier: finalDefaultTier };
@@ -258,23 +199,7 @@ export class DatabasePolicyEngine {
258
199
  // -------------------------------------------------------------------------
259
200
  // Batch evaluation: evaluateBatch
260
201
  // -------------------------------------------------------------------------
261
- /**
262
- * Evaluate a batch of instructions using 2-stage policy evaluation.
263
- *
264
- * Phase A: Evaluate each instruction individually against its applicable policies.
265
- * All-or-Nothing: if any instruction is denied, entire batch is denied.
266
- *
267
- * Phase B: Sum native amounts (TRANSFER.amount) and evaluate
268
- * aggregate against SPENDING_LIMIT. If batch contains APPROVE, apply
269
- * APPROVE_TIER_OVERRIDE and take max(amount tier, approve tier).
270
- *
271
- * @param walletId - Wallet whose policies to evaluate
272
- * @param instructions - Array of instruction parameters (same shape as TransactionParam)
273
- * @returns PolicyEvaluation with final tier or denial with violation details
274
- */
275
202
  async evaluateBatch(walletId, instructions, batchUsdAmount) {
276
- // Step 1: Load policies with network filter
277
- // All instructions in a BATCH share the same network
278
203
  const resolvedNetwork = instructions[0]?.network;
279
204
  const rows = await this.db
280
205
  .select()
@@ -282,16 +207,15 @@ export class DatabasePolicyEngine {
282
207
  .where(and(or(eq(policies.walletId, walletId), isNull(policies.walletId)), or(resolvedNetwork ? eq(policies.network, resolvedNetwork) : isNull(policies.network), isNull(policies.network)), eq(policies.enabled, true)))
283
208
  .orderBy(desc(policies.priority))
284
209
  .all();
285
- if (rows.length === 0) {
210
+ if (rows.length === 0)
286
211
  return { allowed: true, tier: 'INSTANT' };
287
- }
288
212
  const resolved = this.resolveOverrides(rows, walletId, resolvedNetwork);
213
+ const ctx = this.ctx;
289
214
  // ALLOWED_NETWORKS evaluation before Phase A
290
215
  if (resolvedNetwork) {
291
- const allowedNetworksResult = this.evaluateAllowedNetworks(resolved, resolvedNetwork);
292
- if (allowedNetworksResult !== null) {
216
+ const allowedNetworksResult = evaluateAllowedNetworks(ctx, resolved, resolvedNetwork);
217
+ if (allowedNetworksResult !== null)
293
218
  return allowedNetworksResult;
294
- }
295
219
  }
296
220
  // Phase A: Evaluate each instruction individually
297
221
  const violations = [];
@@ -321,94 +245,72 @@ export class DatabasePolicyEngine {
321
245
  if (instr.type === 'TRANSFER') {
322
246
  totalNativeAmount += BigInt(instr.amount);
323
247
  }
324
- // TOKEN_TRANSFER and APPROVE: 0 (no native amount)
325
- // CONTRACT_CALL: Solana has no native value attachment in CPI, so 0
326
248
  }
327
- // Evaluate aggregate against SPENDING_LIMIT (Phase 127: pass batchUsdAmount for USD evaluation)
328
- // BATCH: token_limits not applicable -- aggregate native amount evaluated via raw/USD only (tokenContext intentionally omitted)
329
- const amountTier = this.evaluateSpendingLimit(resolved, totalNativeAmount.toString(), batchUsdAmount);
249
+ const amountTier = evaluateSpendingLimit(ctx, resolved, totalNativeAmount.toString(), batchUsdAmount);
330
250
  let finalTier = amountTier ? amountTier.tier : 'INSTANT';
331
251
  // If batch contains APPROVE, apply APPROVE_TIER_OVERRIDE
332
252
  const hasApprove = instructions.some((i) => i.type === 'APPROVE');
333
253
  if (hasApprove) {
334
- // Get approve tier from APPROVE_TIER_OVERRIDE policy (or default APPROVAL)
335
254
  const approveTierPolicy = resolved.find((p) => p.type === 'APPROVE_TIER_OVERRIDE');
336
255
  let approveTier;
337
256
  if (approveTierPolicy) {
338
- const rules = JSON.parse(approveTierPolicy.rules);
257
+ const rules = this.parseRules(approveTierPolicy.rules, ApproveTierOverrideRulesSchema, 'APPROVE_TIER_OVERRIDE');
339
258
  approveTier = rules.tier;
340
259
  }
341
260
  else {
342
261
  approveTier = 'APPROVAL';
343
262
  }
344
- // Final tier = max(amount tier, approve tier)
345
263
  const tierOrder = ['INSTANT', 'NOTIFY', 'DELAY', 'APPROVAL'];
346
264
  const amountIdx = tierOrder.indexOf(finalTier);
347
265
  const approveIdx = tierOrder.indexOf(approveTier);
348
266
  finalTier = tierOrder[Math.max(amountIdx, approveIdx)];
349
267
  }
350
- return {
351
- allowed: true,
352
- tier: finalTier,
353
- };
268
+ return { allowed: true, tier: finalTier };
354
269
  }
355
270
  // -------------------------------------------------------------------------
356
271
  // Private: Per-instruction policy evaluation (Phase A helper)
357
272
  // -------------------------------------------------------------------------
358
- /**
359
- * Evaluate applicable policies for a single instruction in a batch.
360
- *
361
- * Only evaluates type-specific policies:
362
- * - TRANSFER: WHITELIST
363
- * - TOKEN_TRANSFER: WHITELIST + ALLOWED_TOKENS
364
- * - CONTRACT_CALL: CONTRACT_WHITELIST + METHOD_WHITELIST
365
- * - APPROVE: APPROVED_SPENDERS + APPROVE_AMOUNT_LIMIT
366
- *
367
- * Does NOT evaluate SPENDING_LIMIT (that's Phase B aggregate) or
368
- * APPROVE_TIER_OVERRIDE (that's Phase B).
369
- *
370
- * Returns null if all policies pass, PolicyEvaluation with allowed=false if denied.
371
- */
372
273
  evaluateInstructionPolicies(resolved, instr) {
274
+ const ctx = this.ctx;
373
275
  // WHITELIST applies to TRANSFER and TOKEN_TRANSFER
374
276
  if (instr.type === 'TRANSFER' || instr.type === 'TOKEN_TRANSFER') {
375
- const whitelistResult = this.evaluateWhitelist(resolved, instr.toAddress);
277
+ const whitelistResult = evaluateWhitelist(ctx, resolved, instr.toAddress);
376
278
  if (whitelistResult !== null)
377
279
  return whitelistResult;
378
280
  }
379
281
  // ALLOWED_TOKENS applies to TOKEN_TRANSFER
380
282
  if (instr.type === 'TOKEN_TRANSFER') {
381
- const allowedTokensResult = this.evaluateAllowedTokens(resolved, instr);
283
+ const allowedTokensResult = evaluateAllowedTokens(ctx, resolved, instr);
382
284
  if (allowedTokensResult !== null)
383
285
  return allowedTokensResult;
384
286
  }
385
287
  // CONTRACT_WHITELIST applies to CONTRACT_CALL
386
288
  if (instr.type === 'CONTRACT_CALL') {
387
- const contractResult = this.evaluateContractWhitelist(resolved, instr);
289
+ const contractResult = evaluateContractWhitelist(ctx, resolved, instr);
388
290
  if (contractResult !== null)
389
291
  return contractResult;
390
- const methodResult = this.evaluateMethodWhitelist(resolved, instr);
292
+ const methodResult = evaluateMethodWhitelist(ctx, resolved, instr);
391
293
  if (methodResult !== null)
392
294
  return methodResult;
393
295
  }
394
296
  // APPROVED_SPENDERS + APPROVE_AMOUNT_LIMIT apply to APPROVE
395
297
  if (instr.type === 'APPROVE') {
396
- const spendersResult = this.evaluateApprovedSpenders(resolved, instr);
298
+ const spendersResult = evaluateApprovedSpenders(ctx, resolved, instr);
397
299
  if (spendersResult !== null)
398
300
  return spendersResult;
399
- const amountResult = this.evaluateApproveAmountLimit(resolved, instr);
301
+ const amountResult = evaluateApproveAmountLimit(ctx, resolved, instr);
400
302
  if (amountResult !== null)
401
303
  return amountResult;
402
304
  }
403
- // LENDING_ASSET_WHITELIST applies to lending actions (CONTRACT_CALL with actionName)
305
+ // LENDING_ASSET_WHITELIST applies to lending actions
404
306
  if (instr.type === 'CONTRACT_CALL') {
405
- const lendingAssetResult = this.evaluateLendingAssetWhitelist(resolved, instr);
307
+ const lendingAssetResult = evaluateLendingAssetWhitelist(ctx, resolved, instr);
406
308
  if (lendingAssetResult !== null)
407
309
  return lendingAssetResult;
408
310
  }
409
- // PERP_ALLOWED_MARKETS applies to perp actions (CONTRACT_CALL with actionName)
311
+ // PERP_ALLOWED_MARKETS applies to perp actions
410
312
  if (instr.type === 'CONTRACT_CALL') {
411
- const perpMarketResult = this.evaluatePerpAllowedMarkets(resolved, instr);
313
+ const perpMarketResult = evaluatePerpAllowedMarkets(ctx, resolved, instr);
412
314
  if (perpMarketResult !== null)
413
315
  return perpMarketResult;
414
316
  }
@@ -417,32 +319,13 @@ export class DatabasePolicyEngine {
417
319
  // -------------------------------------------------------------------------
418
320
  // TOCTOU Prevention: evaluateAndReserve
419
321
  // -------------------------------------------------------------------------
420
- /**
421
- * Evaluate transaction and reserve amount atomically using BEGIN IMMEDIATE.
422
- *
423
- * This method:
424
- * 1. Begins an IMMEDIATE transaction (exclusive write lock)
425
- * 2. Loads policies (same as evaluate)
426
- * 3. For SPENDING_LIMIT: computes current reserved total from PENDING/QUEUED txs
427
- * 4. Adds current request amount to reserved total
428
- * 5. Evaluates against limits with reserved total considered
429
- * 6. If allowed: sets reserved_amount on the transaction row
430
- * 7. Commits
431
- *
432
- * @param walletId - The wallet whose policies to evaluate
433
- * @param transaction - Transaction details for evaluation
434
- * @param txId - The transaction ID to update with reserved_amount
435
- * @returns PolicyEvaluation result
436
- * @throws Error if sqlite instance not provided in constructor
437
- */
438
322
  evaluateAndReserve(walletId, transaction, txId, usdAmount, reputationFloorTier) {
439
323
  if (!this.sqlite) {
440
324
  throw new Error('evaluateAndReserve requires raw sqlite instance in constructor');
441
325
  }
442
326
  const sqlite = this.sqlite;
443
- // Use better-sqlite3 transaction().immediate() for BEGIN IMMEDIATE
327
+ const ctx = this.ctx;
444
328
  const txn = sqlite.transaction(() => {
445
- // Step 1: Load enabled policies via raw SQL with network filter (inside IMMEDIATE txn)
446
329
  const policyRows = sqlite
447
330
  .prepare(`SELECT id, wallet_id AS walletId, type, rules, priority, enabled, network
448
331
  FROM policies
@@ -451,103 +334,73 @@ export class DatabasePolicyEngine {
451
334
  AND enabled = 1
452
335
  ORDER BY priority DESC`)
453
336
  .all(walletId, transaction.network ?? null);
454
- // Step 2: No policies -> INSTANT passthrough (apply reputation floor if set)
455
337
  if (policyRows.length === 0) {
456
338
  const baseTier = 'INSTANT';
457
339
  const effectiveTier = reputationFloorTier ? maxTier(baseTier, reputationFloorTier) : baseTier;
458
340
  return { allowed: true, tier: effectiveTier };
459
341
  }
460
- // Step 3: Resolve overrides (4-level with network)
461
342
  const resolved = this.resolveOverrides(policyRows, walletId, transaction.network);
462
- // Step 4: Evaluate WHITELIST (deny-first)
463
- const whitelistResult = this.evaluateWhitelist(resolved, transaction.toAddress);
464
- if (whitelistResult !== null) {
343
+ const whitelistResult = evaluateWhitelist(ctx, resolved, transaction.toAddress);
344
+ if (whitelistResult !== null)
465
345
  return whitelistResult;
466
- }
467
- // Step 4a.5: Evaluate ALLOWED_NETWORKS (network whitelist, permissive default)
468
346
  if (transaction.network) {
469
- const allowedNetworksResult = this.evaluateAllowedNetworks(resolved, transaction.network);
470
- if (allowedNetworksResult !== null) {
347
+ const allowedNetworksResult = evaluateAllowedNetworks(ctx, resolved, transaction.network);
348
+ if (allowedNetworksResult !== null)
471
349
  return allowedNetworksResult;
472
- }
473
350
  }
474
- // Step 4b: Evaluate ALLOWED_TOKENS (token transfer whitelist)
475
- const allowedTokensResult = this.evaluateAllowedTokens(resolved, transaction);
476
- if (allowedTokensResult !== null) {
351
+ const allowedTokensResult = evaluateAllowedTokens(ctx, resolved, transaction);
352
+ if (allowedTokensResult !== null)
477
353
  return allowedTokensResult;
478
- }
479
- // Step 4c: Evaluate CONTRACT_WHITELIST (contract call whitelist)
480
- const contractWhitelistResult = this.evaluateContractWhitelist(resolved, transaction);
481
- if (contractWhitelistResult !== null) {
354
+ const contractWhitelistResult = evaluateContractWhitelist(ctx, resolved, transaction);
355
+ if (contractWhitelistResult !== null)
482
356
  return contractWhitelistResult;
483
- }
484
- // Step 4d: Evaluate METHOD_WHITELIST (method-level restriction)
485
- const methodWhitelistResult = this.evaluateMethodWhitelist(resolved, transaction);
486
- if (methodWhitelistResult !== null) {
357
+ const methodWhitelistResult = evaluateMethodWhitelist(ctx, resolved, transaction);
358
+ if (methodWhitelistResult !== null)
487
359
  return methodWhitelistResult;
488
- }
489
- // Step 4e: Evaluate APPROVED_SPENDERS (approve spender whitelist)
490
- const approvedSpendersResult = this.evaluateApprovedSpenders(resolved, transaction);
491
- if (approvedSpendersResult !== null) {
360
+ const approvedSpendersResult = evaluateApprovedSpenders(ctx, resolved, transaction);
361
+ if (approvedSpendersResult !== null)
492
362
  return approvedSpendersResult;
493
- }
494
- // Step 4f: Evaluate APPROVE_AMOUNT_LIMIT (unlimited approve block + amount cap)
495
- const approveAmountResult = this.evaluateApproveAmountLimit(resolved, transaction);
496
- if (approveAmountResult !== null) {
363
+ const approveAmountResult = evaluateApproveAmountLimit(ctx, resolved, transaction);
364
+ if (approveAmountResult !== null)
497
365
  return approveAmountResult;
498
- }
499
- // Step 4g: Evaluate APPROVE_TIER_OVERRIDE (forced tier for APPROVE transactions)
500
- const approveTierResult = this.evaluateApproveTierOverride(resolved, transaction);
501
- if (approveTierResult !== null) {
502
- return approveTierResult; // FINAL result, skips SPENDING_LIMIT (including token_limits)
503
- }
504
- // Step 4h: Evaluate LENDING_ASSET_WHITELIST (default-deny for lending assets)
505
- const lendingAssetResult = this.evaluateLendingAssetWhitelist(resolved, transaction);
506
- if (lendingAssetResult !== null) {
366
+ const approveTierResult = evaluateApproveTierOverride(ctx, resolved, transaction);
367
+ if (approveTierResult !== null)
368
+ return approveTierResult;
369
+ const lendingAssetResult = evaluateLendingAssetWhitelist(ctx, resolved, transaction);
370
+ if (lendingAssetResult !== null)
507
371
  return lendingAssetResult;
508
- }
509
- // Step 4h-b: Evaluate LENDING_LTV_LIMIT (LTV-based borrow restriction)
510
- const ltvResult = this.evaluateLendingLtvLimit(resolved, transaction, walletId, usdAmount);
511
- if (ltvResult !== null) {
372
+ const ltvResult = evaluateLendingLtvLimit(ctx, resolved, transaction, walletId, sqlite, usdAmount);
373
+ if (ltvResult !== null)
512
374
  return ltvResult;
513
- }
514
- // Step 4i: Evaluate PERP_ALLOWED_MARKETS (default-deny for perp markets)
515
- const perpMarketResult = this.evaluatePerpAllowedMarkets(resolved, transaction);
375
+ const perpMarketResult = evaluatePerpAllowedMarkets(ctx, resolved, transaction);
516
376
  if (perpMarketResult !== null)
517
377
  return perpMarketResult;
518
- // Step 4i-b: Evaluate PERP_MAX_LEVERAGE
519
- const leverageResult = this.evaluatePerpMaxLeverage(resolved, transaction);
378
+ const leverageResult = evaluatePerpMaxLeverage(ctx, resolved, transaction);
520
379
  if (leverageResult !== null)
521
380
  return leverageResult;
522
- // Step 4i-c: Evaluate PERP_MAX_POSITION_USD
523
- const positionUsdResult = this.evaluatePerpMaxPositionUsd(resolved, transaction);
381
+ const positionUsdResult = evaluatePerpMaxPositionUsd(ctx, resolved, transaction);
524
382
  if (positionUsdResult !== null)
525
383
  return positionUsdResult;
526
- // Step 4j: Evaluate VENUE_WHITELIST (Phase 389)
527
- const venueResult = this.evaluateVenueWhitelist(resolved, transaction);
384
+ const venueResult = evaluateVenueWhitelist(ctx, resolved, transaction);
528
385
  if (venueResult !== null)
529
386
  return venueResult;
530
- // Step 4k: Evaluate ACTION_CATEGORY_LIMIT (Phase 389)
531
- const categoryLimitResult = this.evaluateActionCategoryLimit(resolved, transaction, walletId);
387
+ const categoryLimitResult = evaluateActionCategoryLimit(ctx, resolved, transaction, walletId, sqlite);
532
388
  if (categoryLimitResult !== null) {
533
389
  if (reputationFloorTier) {
534
390
  categoryLimitResult.tier = maxTier(categoryLimitResult.tier, reputationFloorTier);
535
391
  }
536
392
  return categoryLimitResult;
537
393
  }
538
- // Step 5: Non-spending classification for lending actions (LEND-08, research flag M6)
539
- // supply/repay/withdraw are non-spending — skip SPENDING_LIMIT entirely
394
+ // Non-spending actions
540
395
  const NON_SPENDING_ACTIONS_R = new Set(['supply', 'repay', 'withdraw', 'close_position', 'add_margin']);
541
396
  if (transaction.actionName && NON_SPENDING_ACTIONS_R.has(transaction.actionName)) {
542
397
  const baseTier = 'INSTANT';
543
398
  const finalTier = reputationFloorTier ? maxTier(baseTier, reputationFloorTier) : baseTier;
544
399
  return { allowed: true, tier: finalTier };
545
400
  }
546
- // Step 5 (continued): Compute reserved total for SPENDING_LIMIT evaluation
401
+ // SPENDING_LIMIT with reservation
547
402
  const spendingPolicy = resolved.find((p) => p.type === 'SPENDING_LIMIT');
548
403
  if (spendingPolicy) {
549
- // Sum of reserved_amount for wallet's PENDING/QUEUED/SIGNED transactions
550
- // SIGNED included for sign-only pipeline reservation (TOCTOU prevention)
551
404
  const reservedRow = sqlite
552
405
  .prepare(`SELECT COALESCE(SUM(CAST(reserved_amount AS INTEGER)), 0) AS total
553
406
  FROM transactions
@@ -558,22 +411,19 @@ export class DatabasePolicyEngine {
558
411
  const reservedTotal = BigInt(reservedRow.total);
559
412
  const requestAmount = BigInt(transaction.amount);
560
413
  const effectiveAmount = reservedTotal + requestAmount;
561
- // Evaluate with effective amount (reserved + current) via unified evaluateSpendingLimit
562
- // Phase 236: Build tokenContext for token_limits evaluation
563
414
  const tokenContext = this.buildTokenContext(transaction, spendingPolicy);
564
- const spendingResult = this.evaluateSpendingLimit(resolved, effectiveAmount.toString(), usdAmount, tokenContext);
565
- // Step 6: Cumulative USD limit evaluation (daily/monthly rolling window)
415
+ const spendingResult = evaluateSpendingLimit(ctx, resolved, effectiveAmount.toString(), usdAmount, tokenContext);
416
+ // Cumulative USD limit evaluation
566
417
  if (usdAmount !== undefined && usdAmount > 0) {
567
- const rules = JSON.parse(spendingPolicy.rules);
418
+ const rules = this.parseRules(spendingPolicy.rules, SpendingLimitRulesSchema, 'SPENDING_LIMIT');
568
419
  const hasCumulativeLimits = rules.daily_limit_usd !== undefined || rules.monthly_limit_usd !== undefined;
569
420
  if (hasCumulativeLimits) {
570
421
  const now = Math.floor(Date.now() / 1000);
571
422
  let cumulativeTier = 'INSTANT';
572
423
  let cumulativeReason;
573
424
  let cumulativeWarning;
574
- // 6a: Daily (24h rolling window)
575
425
  if (rules.daily_limit_usd !== undefined) {
576
- const windowStart = now - 86400; // 24 * 60 * 60
426
+ const windowStart = now - 86400;
577
427
  const spent = this.getCumulativeUsdSpent(sqlite, walletId, windowStart);
578
428
  const totalWithCurrent = spent + usdAmount;
579
429
  if (totalWithCurrent > rules.daily_limit_usd) {
@@ -581,16 +431,14 @@ export class DatabasePolicyEngine {
581
431
  cumulativeReason = 'cumulative_daily';
582
432
  }
583
433
  else {
584
- // 80% warning check
585
434
  const ratio = totalWithCurrent / rules.daily_limit_usd;
586
435
  if (ratio >= 0.8) {
587
436
  cumulativeWarning = { type: 'daily', ratio, spent: totalWithCurrent, limit: rules.daily_limit_usd };
588
437
  }
589
438
  }
590
439
  }
591
- // 6b: Monthly (30-day rolling window) -- only if daily didn't already escalate
592
440
  if (rules.monthly_limit_usd !== undefined && cumulativeReason === undefined) {
593
- const windowStart = now - 2592000; // 30 * 24 * 60 * 60
441
+ const windowStart = now - 2592000;
594
442
  const spent = this.getCumulativeUsdSpent(sqlite, walletId, windowStart);
595
443
  const totalWithCurrent = spent + usdAmount;
596
444
  if (totalWithCurrent > rules.monthly_limit_usd) {
@@ -598,26 +446,21 @@ export class DatabasePolicyEngine {
598
446
  cumulativeReason = 'cumulative_monthly';
599
447
  }
600
448
  else if (!cumulativeWarning) {
601
- // 80% warning check (only if daily warning not already set)
602
449
  const ratio = totalWithCurrent / rules.monthly_limit_usd;
603
450
  if (ratio >= 0.8) {
604
451
  cumulativeWarning = { type: 'monthly', ratio, spent: totalWithCurrent, limit: rules.monthly_limit_usd };
605
452
  }
606
453
  }
607
454
  }
608
- // Step 7: Final tier = max(per-tx tier, cumulative tier)
609
455
  const perTxTier = spendingResult?.tier ?? 'INSTANT';
610
456
  const finalTier = maxTier(perTxTier, cumulativeTier);
611
- // Determine approvalReason
612
457
  let approvalReason;
613
458
  if (finalTier === 'APPROVAL') {
614
459
  approvalReason = cumulativeReason ?? 'per_tx';
615
460
  }
616
- // Record USD amounts + reserved_amount
617
461
  sqlite
618
462
  .prepare(`UPDATE transactions SET reserved_amount = ?, amount_usd = ?, reserved_amount_usd = ? WHERE id = ?`)
619
463
  .run(transaction.amount, usdAmount, usdAmount, txId);
620
- // [Phase 320] Apply reputation floor tier via maxTier (escalation only)
621
464
  const repFinalTier = reputationFloorTier ? maxTier(finalTier, reputationFloorTier) : finalTier;
622
465
  return {
623
466
  allowed: true,
@@ -628,8 +471,7 @@ export class DatabasePolicyEngine {
628
471
  };
629
472
  }
630
473
  }
631
- // No cumulative limits or usdAmount not available -- use per-tx result
632
- // Set reserved_amount on the transaction row + USD amounts if available
474
+ // No cumulative limits
633
475
  if (usdAmount !== undefined) {
634
476
  sqlite
635
477
  .prepare(`UPDATE transactions SET reserved_amount = ?, amount_usd = ?, reserved_amount_usd = ? WHERE id = ?`)
@@ -640,30 +482,21 @@ export class DatabasePolicyEngine {
640
482
  .prepare(`UPDATE transactions SET reserved_amount = ? WHERE id = ?`)
641
483
  .run(transaction.amount, txId);
642
484
  }
643
- // [Phase 320] Apply reputation floor tier via maxTier (escalation only)
644
485
  const baseResult = spendingResult ?? { allowed: true, tier: 'INSTANT' };
645
486
  if (reputationFloorTier && baseResult.allowed) {
646
487
  baseResult.tier = maxTier(baseResult.tier, reputationFloorTier);
647
488
  }
648
489
  return baseResult;
649
490
  }
650
- // No SPENDING_LIMIT -> INSTANT passthrough (whitelist already passed)
651
- // [Phase 320] Apply reputation floor tier if set
491
+ // No SPENDING_LIMIT -> INSTANT passthrough
652
492
  const noSpendingTier = reputationFloorTier ?? 'INSTANT';
653
493
  return { allowed: true, tier: noSpendingTier };
654
494
  });
655
- // Execute with IMMEDIATE isolation
656
495
  return txn.immediate();
657
496
  }
658
497
  // -------------------------------------------------------------------------
659
498
  // releaseReservation
660
499
  // -------------------------------------------------------------------------
661
- /**
662
- * Release a reserved amount on a transaction.
663
- * Called when transaction reaches FAILED/CANCELLED/EXPIRED state.
664
- *
665
- * @param txId - The transaction ID to clear reservation for
666
- */
667
500
  releaseReservation(txId) {
668
501
  if (!this.sqlite) {
669
502
  throw new Error('releaseReservation requires raw sqlite instance in constructor');
@@ -675,23 +508,13 @@ export class DatabasePolicyEngine {
675
508
  // -------------------------------------------------------------------------
676
509
  // Private: Cumulative USD spending aggregation
677
510
  // -------------------------------------------------------------------------
678
- /**
679
- * Get cumulative USD spent by wallet within a time window.
680
- * Includes both confirmed amounts (amount_usd) and pending reservations (reserved_amount_usd).
681
- *
682
- * CONFIRMED/SIGNED: counted via amount_usd (confirmed or about to be broadcasted).
683
- * PENDING/QUEUED: counted via reserved_amount_usd (awaiting processing, not yet confirmed).
684
- * Deduplication: SIGNED is in the first query only (amount_usd). PENDING/QUEUED in second only.
685
- */
686
511
  getCumulativeUsdSpent(sqlite, walletId, windowStart) {
687
- // 1. Confirmed transactions (CONFIRMED/SIGNED) amount_usd within window
688
512
  const confirmedRow = sqlite
689
513
  .prepare(`SELECT COALESCE(SUM(amount_usd), 0) AS total
690
514
  FROM transactions
691
515
  WHERE wallet_id = ? AND status IN ('CONFIRMED', 'SIGNED')
692
516
  AND created_at >= ? AND amount_usd IS NOT NULL`)
693
517
  .get(walletId, windowStart);
694
- // 2. Pending transactions (PENDING/QUEUED) reserved_amount_usd (no window filter -- all pending count)
695
518
  const pendingRow = sqlite
696
519
  .prepare(`SELECT COALESCE(SUM(reserved_amount_usd), 0) AS total
697
520
  FROM transactions
@@ -703,20 +526,6 @@ export class DatabasePolicyEngine {
703
526
  // -------------------------------------------------------------------------
704
527
  // Private: Override resolution
705
528
  // -------------------------------------------------------------------------
706
- /**
707
- * Resolve policy overrides with 4-level priority:
708
- * 1. wallet-specific + network-specific (highest)
709
- * 2. wallet-specific + all-networks
710
- * 3. global + network-specific
711
- * 4. global + all-networks (lowest)
712
- *
713
- * For each policy type, one policy is selected.
714
- * Lower priority entries are inserted first, higher priority entries overwrite.
715
- * Key: typeMap[row.type] (same as current -- no composite key needed, PLCY-D03).
716
- *
717
- * Backward compat: when all policies have network=NULL,
718
- * phases 2+4 collapse into current 2-level (wallet > global) behavior.
719
- */
720
529
  resolveOverrides(rows, walletId, resolvedNetwork) {
721
530
  const typeMap = new Map();
722
531
  // Phase 1: global + all-networks (4th priority, lowest)
@@ -750,868 +559,35 @@ export class DatabasePolicyEngine {
750
559
  return Array.from(typeMap.values());
751
560
  }
752
561
  // -------------------------------------------------------------------------
753
- // Private: ALLOWED_NETWORKS evaluation
754
- // -------------------------------------------------------------------------
755
- /**
756
- * Evaluate ALLOWED_NETWORKS policy.
757
- *
758
- * Logic:
759
- * - Applies to ALL 5 transaction types (TRANSFER, TOKEN_TRANSFER, CONTRACT_CALL, APPROVE, BATCH)
760
- * - If no ALLOWED_NETWORKS policy exists: return null (permissive default -- all networks allowed)
761
- * - If policy exists: check if resolvedNetwork is in rules.networks[].network
762
- * -> If found: return null (continue to next evaluation)
763
- * -> If not found: deny with reason 'Network not in allowed list'
764
- * - Comparison: case-insensitive (toLowerCase)
765
- * - Tier: INSTANT (immediate denial)
766
- *
767
- * Returns PolicyEvaluation if denied, null if allowed (or no policy).
768
- */
769
- evaluateAllowedNetworks(resolved, resolvedNetwork) {
770
- const policy = resolved.find((p) => p.type === 'ALLOWED_NETWORKS');
771
- // No ALLOWED_NETWORKS policy -> permissive default (all networks allowed)
772
- if (!policy)
773
- return null;
774
- const rules = JSON.parse(policy.rules);
775
- // Case-insensitive comparison
776
- const isAllowed = rules.networks.some((n) => n.network.toLowerCase() === resolvedNetwork.toLowerCase());
777
- if (!isAllowed) {
778
- const allowedList = rules.networks.map((n) => n.network).join(', ');
779
- return {
780
- allowed: false,
781
- tier: 'INSTANT',
782
- reason: `Network '${resolvedNetwork}' not in allowed networks list. Allowed: ${allowedList}`,
783
- };
784
- }
785
- return null; // Network allowed, continue evaluation
786
- }
787
- // -------------------------------------------------------------------------
788
- // Private: WHITELIST evaluation
789
- // -------------------------------------------------------------------------
790
- /**
791
- * Evaluate WHITELIST policy.
792
- * Returns PolicyEvaluation if denied, null if allowed (or no whitelist).
793
- */
794
- evaluateWhitelist(resolved, toAddress) {
795
- const whitelist = resolved.find((p) => p.type === 'WHITELIST');
796
- if (!whitelist)
797
- return null;
798
- const rules = JSON.parse(whitelist.rules);
799
- // Empty allowed_addresses = whitelist inactive
800
- if (!rules.allowed_addresses || rules.allowed_addresses.length === 0) {
801
- return null;
802
- }
803
- // Case-insensitive comparison (EVM compat)
804
- const normalizedTo = toAddress.toLowerCase();
805
- const isWhitelisted = rules.allowed_addresses.some((addr) => addr.toLowerCase() === normalizedTo);
806
- if (!isWhitelisted) {
807
- return {
808
- allowed: false,
809
- tier: 'INSTANT',
810
- reason: `Address ${toAddress} not in whitelist`,
811
- };
812
- }
813
- return null;
814
- }
815
- // -------------------------------------------------------------------------
816
- // Private: ALLOWED_TOKENS evaluation
817
- // -------------------------------------------------------------------------
818
- /**
819
- * Evaluate ALLOWED_TOKENS policy with 4-scenario matching matrix (PLCY-03).
820
- *
821
- * Logic:
822
- * - Only applies to TOKEN_TRANSFER transaction type
823
- * - If transaction type is TOKEN_TRANSFER and no ALLOWED_TOKENS policy exists:
824
- * -> deny with reason 'Token transfer not allowed: no ALLOWED_TOKENS policy configured'
825
- * - If ALLOWED_TOKENS policy exists, match using 4-scenario matrix:
826
- * Scenario 1: Policy assetId + TX assetId -> exact CAIP-19 string match
827
- * Scenario 2: Policy assetId + TX address only -> extract address from policy assetId, compare lowercase
828
- * Scenario 3: Policy address only + TX assetId -> extract address from TX assetId, compare lowercase
829
- * Scenario 4: Policy address only + TX address only -> current behavior (case-insensitive)
830
- * - EVM addresses normalized to lowercase for comparison (PLCY-04)
831
- *
832
- * Returns PolicyEvaluation if denied, null if allowed (or not applicable).
833
- */
834
- evaluateAllowedTokens(resolved, transaction) {
835
- // Only evaluate for TOKEN_TRANSFER transactions
836
- if (transaction.type !== 'TOKEN_TRANSFER')
837
- return null;
838
- const allowedTokensPolicy = resolved.find((p) => p.type === 'ALLOWED_TOKENS');
839
- // No ALLOWED_TOKENS policy -> check toggle, then deny (default deny)
840
- if (!allowedTokensPolicy) {
841
- if (this.settingsService?.get('policy.default_deny_tokens') === 'false') {
842
- return null; // default-allow mode: skip token whitelist check
843
- }
844
- return {
845
- allowed: false,
846
- tier: 'INSTANT',
847
- reason: 'Token transfer not allowed: no ALLOWED_TOKENS policy configured',
848
- };
849
- }
850
- // Parse rules.tokens array
851
- const rules = JSON.parse(allowedTokensPolicy.rules);
852
- const txTokenAddress = transaction.tokenAddress;
853
- const txAssetId = transaction.assetId;
854
- if (!txTokenAddress && !txAssetId) {
855
- return {
856
- allowed: false,
857
- tier: 'INSTANT',
858
- reason: 'Token transfer missing token address',
859
- };
860
- }
861
- // 4-scenario matching matrix (PLCY-03)
862
- const isAllowed = rules.tokens.some((policyToken) => {
863
- // Scenario 1: Both have assetId -> exact CAIP-19 string match
864
- // CAIP-19 strings are already normalized (EVM lowercase by tokenAssetId, Solana preserved)
865
- if (policyToken.assetId && txAssetId) {
866
- return policyToken.assetId === txAssetId;
867
- }
868
- // Scenario 2: Policy has assetId, TX has address only
869
- if (policyToken.assetId && txTokenAddress) {
870
- try {
871
- const policyAddr = parseCaip19(policyToken.assetId).assetReference;
872
- return policyAddr.toLowerCase() === txTokenAddress.toLowerCase();
873
- }
874
- catch {
875
- return false; // Invalid policy assetId -> no match
876
- }
877
- }
878
- // Scenario 3: Policy has address only, TX has assetId
879
- if (!policyToken.assetId && txAssetId) {
880
- try {
881
- const txAddr = parseCaip19(txAssetId).assetReference;
882
- return policyToken.address.toLowerCase() === txAddr.toLowerCase();
883
- }
884
- catch {
885
- return false; // Invalid TX assetId -> no match
886
- }
887
- }
888
- // Scenario 4: Both address only -> current behavior (case-insensitive)
889
- return policyToken.address.toLowerCase() === (txTokenAddress ?? '').toLowerCase();
890
- });
891
- if (!isAllowed) {
892
- return {
893
- allowed: false,
894
- tier: 'INSTANT',
895
- reason: `Token not in allowed list: ${txAssetId ?? txTokenAddress}`,
896
- };
897
- }
898
- return null; // Token is allowed, continue evaluation
899
- }
900
- // -------------------------------------------------------------------------
901
- // Private: CONTRACT_WHITELIST evaluation
902
- // -------------------------------------------------------------------------
903
- /**
904
- * Evaluate CONTRACT_WHITELIST policy.
905
- *
906
- * Logic:
907
- * - Only applies to CONTRACT_CALL transaction type
908
- * - Provider-trust: if transaction has actionProvider and the provider is enabled
909
- * in SettingsService, skip CONTRACT_WHITELIST entirely (trusted provider bypass)
910
- * - If transaction type is CONTRACT_CALL and no CONTRACT_WHITELIST policy exists:
911
- * -> deny with reason 'Contract calls disabled: no CONTRACT_WHITELIST policy configured'
912
- * - If CONTRACT_WHITELIST policy exists, check if contract address is in rules.contracts[].address:
913
- * -> If found: return null (continue to next evaluation)
914
- * -> If not found: deny with reason 'Contract not whitelisted: {address}'
915
- * - For non-CONTRACT_CALL types: return null (not applicable)
916
- *
917
- * Returns PolicyEvaluation if denied, null if allowed (or not applicable).
918
- */
919
- evaluateContractWhitelist(resolved, transaction) {
920
- // Evaluate for CONTRACT_CALL and NFT_TRANSFER transactions (v31.0)
921
- if (transaction.type !== 'CONTRACT_CALL' && transaction.type !== 'NFT_TRANSFER')
922
- return null;
923
- // Provider-trust: skip CONTRACT_WHITELIST for trusted action providers
924
- if (transaction.actionProvider && this.settingsService) {
925
- const enabledKey = `actions.${transaction.actionProvider}_enabled`;
926
- try {
927
- const enabled = this.settingsService.get(enabledKey);
928
- if (enabled === 'true') {
929
- return null; // Skip CONTRACT_WHITELIST -- provider is trusted
930
- }
931
- }
932
- catch {
933
- // Unknown setting key means provider is not registered -- fall through to normal check
934
- }
935
- }
936
- const contractWhitelistPolicy = resolved.find((p) => p.type === 'CONTRACT_WHITELIST');
937
- // No CONTRACT_WHITELIST policy -> check toggle, then deny (default deny)
938
- if (!contractWhitelistPolicy) {
939
- if (this.settingsService?.get('policy.default_deny_contracts') === 'false') {
940
- return null; // default-allow mode: skip contract whitelist check
941
- }
942
- return {
943
- allowed: false,
944
- tier: 'INSTANT',
945
- reason: 'Contract calls disabled: no CONTRACT_WHITELIST policy configured',
946
- };
947
- }
948
- // Parse rules.contracts array
949
- const rules = JSON.parse(contractWhitelistPolicy.rules);
950
- const contractAddress = transaction.contractAddress ?? transaction.toAddress;
951
- // Check if contract is in whitelist (case-insensitive comparison for EVM addresses)
952
- const isWhitelisted = rules.contracts.some((c) => c.address.toLowerCase() === contractAddress.toLowerCase());
953
- if (!isWhitelisted) {
954
- return {
955
- allowed: false,
956
- tier: 'INSTANT',
957
- reason: `Contract not whitelisted: ${contractAddress}`,
958
- };
959
- }
960
- return null; // Contract is whitelisted, continue evaluation
961
- }
962
- // -------------------------------------------------------------------------
963
- // Private: METHOD_WHITELIST evaluation
964
- // -------------------------------------------------------------------------
965
- /**
966
- * Evaluate METHOD_WHITELIST policy.
967
- *
968
- * Logic:
969
- * - Only applies to CONTRACT_CALL transaction type
970
- * - If no METHOD_WHITELIST policy exists: return null (method restriction is optional)
971
- * - If METHOD_WHITELIST policy exists, find matching entry for transaction's contract address:
972
- * -> If no entry for this contract: return null (no method restriction for this contract)
973
- * -> If entry found, check if transaction's selector is in entry.selectors:
974
- * -> If found: return null (method allowed)
975
- * -> If not found: deny with reason 'Method not whitelisted: {selector} on contract {address}'
976
- *
977
- * Returns PolicyEvaluation if denied, null if allowed (or not applicable).
978
- */
979
- evaluateMethodWhitelist(resolved, transaction) {
980
- // Only evaluate for CONTRACT_CALL transactions
981
- if (transaction.type !== 'CONTRACT_CALL')
982
- return null;
983
- const methodWhitelistPolicy = resolved.find((p) => p.type === 'METHOD_WHITELIST');
984
- // No METHOD_WHITELIST policy -> no method restriction (optional policy)
985
- if (!methodWhitelistPolicy)
986
- return null;
987
- // Parse rules.methods array
988
- const rules = JSON.parse(methodWhitelistPolicy.rules);
989
- const contractAddress = transaction.contractAddress ?? transaction.toAddress;
990
- const selector = transaction.selector;
991
- // Find matching entry for this contract (case-insensitive)
992
- const entry = rules.methods.find((m) => m.contractAddress.toLowerCase() === contractAddress.toLowerCase());
993
- // No entry for this contract -> no method restriction for this specific contract
994
- if (!entry)
995
- return null;
996
- // Check if selector is in the allowed list (case-insensitive)
997
- if (!selector) {
998
- return {
999
- allowed: false,
1000
- tier: 'INSTANT',
1001
- reason: `Method not whitelisted: missing selector on contract ${contractAddress}`,
1002
- };
1003
- }
1004
- const isAllowed = entry.selectors.some((s) => s.toLowerCase() === selector.toLowerCase());
1005
- if (!isAllowed) {
1006
- return {
1007
- allowed: false,
1008
- tier: 'INSTANT',
1009
- reason: `Method not whitelisted: ${selector} on contract ${contractAddress}`,
1010
- };
1011
- }
1012
- return null; // Method is whitelisted, continue evaluation
1013
- }
1014
- // -------------------------------------------------------------------------
1015
- // Private: APPROVED_SPENDERS evaluation
1016
- // -------------------------------------------------------------------------
1017
- /**
1018
- * Evaluate APPROVED_SPENDERS policy.
1019
- *
1020
- * Logic:
1021
- * - Only applies to APPROVE transaction type
1022
- * - If transaction type is APPROVE and no APPROVED_SPENDERS policy exists:
1023
- * -> deny with reason 'Token approvals disabled: no APPROVED_SPENDERS policy configured'
1024
- * - If APPROVED_SPENDERS policy exists, check if transaction's spenderAddress is in rules.spenders[]:
1025
- * -> If found: return null (continue evaluation)
1026
- * -> If not found: deny with reason 'Spender not in approved list: {address}'
1027
- * - Case-insensitive comparison (EVM addresses)
1028
- *
1029
- * Returns PolicyEvaluation if denied, null if allowed (or not applicable).
1030
- */
1031
- evaluateApprovedSpenders(resolved, transaction) {
1032
- // Only evaluate for APPROVE transactions
1033
- if (transaction.type !== 'APPROVE')
1034
- return null;
1035
- const approvedSpendersPolicy = resolved.find((p) => p.type === 'APPROVED_SPENDERS');
1036
- // No APPROVED_SPENDERS policy -> check toggle, then deny (default deny)
1037
- if (!approvedSpendersPolicy) {
1038
- if (this.settingsService?.get('policy.default_deny_spenders') === 'false') {
1039
- return null; // default-allow mode: skip approved spenders check
1040
- }
1041
- return {
1042
- allowed: false,
1043
- tier: 'INSTANT',
1044
- reason: 'Token approvals disabled: no APPROVED_SPENDERS policy configured',
1045
- };
1046
- }
1047
- // Parse rules.spenders array
1048
- const rules = JSON.parse(approvedSpendersPolicy.rules);
1049
- const spenderAddress = transaction.spenderAddress;
1050
- if (!spenderAddress) {
1051
- return {
1052
- allowed: false,
1053
- tier: 'INSTANT',
1054
- reason: 'Approve missing spender address',
1055
- };
1056
- }
1057
- // Check if spender is in approved list (case-insensitive for EVM addresses)
1058
- const isApproved = rules.spenders.some((s) => s.address.toLowerCase() === spenderAddress.toLowerCase());
1059
- if (!isApproved) {
1060
- return {
1061
- allowed: false,
1062
- tier: 'INSTANT',
1063
- reason: `Spender not in approved list: ${spenderAddress}`,
1064
- };
1065
- }
1066
- return null; // Spender is approved, continue evaluation
1067
- }
1068
- // -------------------------------------------------------------------------
1069
- // Private: APPROVE_AMOUNT_LIMIT evaluation
1070
- // -------------------------------------------------------------------------
1071
- /**
1072
- * Evaluate APPROVE_AMOUNT_LIMIT policy.
1073
- *
1074
- * Logic:
1075
- * - Only applies to APPROVE transaction type
1076
- * - Checks for unlimited approve amounts (>= UNLIMITED_THRESHOLD)
1077
- * - Checks for amount cap (maxAmount)
1078
- * - If no policy exists: default block_unlimited=true (block unlimited approvals)
1079
- *
1080
- * Returns PolicyEvaluation if denied, null if allowed (or not applicable).
1081
- */
1082
- evaluateApproveAmountLimit(resolved, transaction) {
1083
- // Only evaluate for APPROVE transactions
1084
- if (transaction.type !== 'APPROVE')
1085
- return null;
1086
- const approveAmount = transaction.approveAmount;
1087
- if (!approveAmount)
1088
- return null; // No amount to check
1089
- const amount = BigInt(approveAmount);
1090
- const approveAmountPolicy = resolved.find((p) => p.type === 'APPROVE_AMOUNT_LIMIT');
1091
- if (!approveAmountPolicy) {
1092
- // No policy: default block unlimited
1093
- if (amount >= UNLIMITED_THRESHOLD) {
1094
- return {
1095
- allowed: false,
1096
- tier: 'INSTANT',
1097
- reason: 'Unlimited token approval is blocked',
1098
- };
1099
- }
1100
- return null;
1101
- }
1102
- // Parse rules
1103
- const rules = JSON.parse(approveAmountPolicy.rules);
1104
- // Check unlimited block
1105
- if (rules.blockUnlimited && amount >= UNLIMITED_THRESHOLD) {
1106
- return {
1107
- allowed: false,
1108
- tier: 'INSTANT',
1109
- reason: 'Unlimited token approval is blocked',
1110
- };
1111
- }
1112
- // Check maxAmount cap
1113
- if (rules.maxAmount && amount > BigInt(rules.maxAmount)) {
1114
- return {
1115
- allowed: false,
1116
- tier: 'INSTANT',
1117
- reason: 'Approve amount exceeds limit',
1118
- };
1119
- }
1120
- return null; // Amount within limits, continue evaluation
1121
- }
1122
- // -------------------------------------------------------------------------
1123
- // Private: APPROVE_TIER_OVERRIDE evaluation
1124
- // -------------------------------------------------------------------------
1125
- /**
1126
- * Evaluate APPROVE_TIER_OVERRIDE policy.
1127
- *
1128
- * Logic:
1129
- * - Only applies to APPROVE transaction type
1130
- * - If APPROVE_TIER_OVERRIDE policy exists: return configured tier (FINAL, skips SPENDING_LIMIT)
1131
- * - If no APPROVE_TIER_OVERRIDE policy exists: return null (Phase 236: fall through to SPENDING_LIMIT
1132
- * for token_limits evaluation; if no SPENDING_LIMIT either, INSTANT passthrough)
1133
- *
1134
- * Phase 236 change: Previously defaulted to APPROVAL when no override policy existed.
1135
- * Now falls through to SPENDING_LIMIT to allow token_limits evaluation for APPROVE transactions.
1136
- *
1137
- * Returns PolicyEvaluation if override policy exists, null otherwise.
1138
- */
1139
- evaluateApproveTierOverride(resolved, transaction) {
1140
- // Only evaluate for APPROVE transactions
1141
- if (transaction.type !== 'APPROVE')
1142
- return null;
1143
- const approveTierPolicy = resolved.find((p) => p.type === 'APPROVE_TIER_OVERRIDE');
1144
- if (!approveTierPolicy) {
1145
- // Phase 236: No override -> fall through to SPENDING_LIMIT for token_limits evaluation
1146
- return null;
1147
- }
1148
- // Parse rules
1149
- const rules = JSON.parse(approveTierPolicy.rules);
1150
- return { allowed: true, tier: rules.tier };
1151
- }
1152
- // -------------------------------------------------------------------------
1153
- // Private: SPENDING_LIMIT evaluation
1154
- // -------------------------------------------------------------------------
1155
- /**
1156
- * Evaluate SPENDING_LIMIT policy.
1157
- * Returns PolicyEvaluation with tier classification, or null if no spending limit.
1158
- *
1159
- * Phase 127: usdAmount가 전달되고 rules에 USD 임계값이 설정되어 있으면,
1160
- * 네이티브 티어와 USD 티어 중 더 보수적인(높은) 티어를 채택한다.
1161
- *
1162
- * Phase 236: tokenContext가 전달되고 rules에 token_limits가 설정되어 있으면,
1163
- * evaluateTokenTier()를 사용하여 토큰별 human-readable 한도를 평가한다.
1164
- */
1165
- evaluateSpendingLimit(resolved, amount, usdAmount, tokenContext) {
1166
- const spending = resolved.find((p) => p.type === 'SPENDING_LIMIT');
1167
- if (!spending)
1168
- return null;
1169
- const rules = JSON.parse(spending.rules);
1170
- // 1. Token-specific tier (Phase 236)
1171
- let tokenTier = 'INSTANT';
1172
- if (tokenContext && rules.token_limits) {
1173
- const tokenResult = this.evaluateTokenTier(BigInt(amount), rules, tokenContext);
1174
- if (tokenResult !== null) {
1175
- tokenTier = tokenResult;
1176
- }
1177
- else {
1178
- // No token_limits match -> fall back to raw fields
1179
- tokenTier = this.evaluateNativeTier(BigInt(amount), rules);
1180
- }
1181
- }
1182
- else {
1183
- // No tokenContext or no token_limits -> use raw fields (existing behavior)
1184
- tokenTier = this.evaluateNativeTier(BigInt(amount), rules);
1185
- }
1186
- // 2. USD 기준 티어 (Phase 127)
1187
- let finalTier = tokenTier;
1188
- if (usdAmount !== undefined && usdAmount > 0 && this.hasUsdThresholds(rules)) {
1189
- const usdTier = this.evaluateUsdTier(usdAmount, rules);
1190
- finalTier = maxTier(tokenTier, usdTier);
1191
- }
1192
- // delaySeconds는 최종 tier가 DELAY일 때만 포함
1193
- const delaySeconds = finalTier === 'DELAY' ? rules.delay_seconds : undefined;
1194
- return {
1195
- allowed: true,
1196
- tier: finalTier,
1197
- ...(delaySeconds !== undefined ? { delaySeconds } : {}),
1198
- ...(finalTier === 'APPROVAL' ? { approvalReason: 'per_tx' } : {}),
1199
- };
1200
- }
1201
- /**
1202
- * Evaluate token-specific tier using token_limits with CAIP-19 key matching.
1203
- * Returns PolicyTier if a matching token limit is found, null otherwise (-> raw fallback).
1204
- *
1205
- * Matching priority:
1206
- * 1. Exact CAIP-19 asset ID match (TOKEN_TRANSFER, APPROVE)
1207
- * 2. "native:{chain}" match (TRANSFER)
1208
- * 3. "native" shorthand match (TRANSFER, only when policy has network set)
1209
- * 4. No match -> return null (caller falls back to raw fields)
1210
- */
1211
- evaluateTokenTier(amountBig, rules, tokenContext) {
1212
- if (!rules.token_limits)
1213
- return null;
1214
- // Skip for CONTRACT_CALL and BATCH (they don't use token_limits)
1215
- if (tokenContext.type === 'CONTRACT_CALL' || tokenContext.type === 'BATCH') {
1216
- return null;
1217
- }
1218
- let matchedLimit;
1219
- let decimals;
1220
- if (tokenContext.type === 'TOKEN_TRANSFER' || tokenContext.type === 'APPROVE') {
1221
- // Try exact CAIP-19 asset ID match
1222
- if (tokenContext.assetId && rules.token_limits[tokenContext.assetId]) {
1223
- matchedLimit = rules.token_limits[tokenContext.assetId];
1224
- decimals = tokenContext.tokenDecimals;
1225
- }
1226
- }
1227
- else if (tokenContext.type === 'TRANSFER') {
1228
- // Try "native:{chain}" match
1229
- const nativeChainKey = tokenContext.chain ? `native:${tokenContext.chain}` : undefined;
1230
- if (nativeChainKey && rules.token_limits[nativeChainKey]) {
1231
- matchedLimit = rules.token_limits[nativeChainKey];
1232
- decimals = tokenContext.chain ? NATIVE_DECIMALS[tokenContext.chain] : undefined;
1233
- }
1234
- // Fallback: try "native" shorthand (only when policy has a network set)
1235
- if (!matchedLimit && tokenContext.policyNetwork && rules.token_limits['native']) {
1236
- matchedLimit = rules.token_limits['native'];
1237
- decimals = tokenContext.chain ? NATIVE_DECIMALS[tokenContext.chain] : undefined;
1238
- }
1239
- }
1240
- if (!matchedLimit || decimals === undefined) {
1241
- return null; // No match -> caller falls back to raw fields
1242
- }
1243
- // Use fixed-point comparison: multiply limit by divisor (avoids precision loss)
1244
- const instantMaxRaw = parseDecimalToBigInt(matchedLimit.instant_max, decimals);
1245
- const notifyMaxRaw = parseDecimalToBigInt(matchedLimit.notify_max, decimals);
1246
- const delayMaxRaw = parseDecimalToBigInt(matchedLimit.delay_max, decimals);
1247
- if (amountBig <= instantMaxRaw)
1248
- return 'INSTANT';
1249
- if (amountBig <= notifyMaxRaw)
1250
- return 'NOTIFY';
1251
- if (amountBig <= delayMaxRaw)
1252
- return 'DELAY';
1253
- return 'APPROVAL';
1254
- }
1255
- /**
1256
- * Evaluate native amount tier (extracted from evaluateSpendingLimit).
1257
- * Phase 236: proper undefined guards for optional raw fields.
1258
- */
1259
- evaluateNativeTier(amountBig, rules) {
1260
- // Phase 236: raw fields are now optional -- skip native tier if all undefined
1261
- if (rules.instant_max === undefined && rules.notify_max === undefined && rules.delay_max === undefined) {
1262
- return 'INSTANT'; // No raw thresholds -> permissive (USD/token_limits will handle)
1263
- }
1264
- const instantMax = rules.instant_max !== undefined ? BigInt(rules.instant_max) : 0n;
1265
- const notifyMax = rules.notify_max !== undefined ? BigInt(rules.notify_max) : 0n;
1266
- const delayMax = rules.delay_max !== undefined ? BigInt(rules.delay_max) : 0n;
1267
- if (amountBig <= instantMax)
1268
- return 'INSTANT';
1269
- if (amountBig <= notifyMax)
1270
- return 'NOTIFY';
1271
- if (amountBig <= delayMax)
1272
- return 'DELAY';
1273
- return 'APPROVAL';
1274
- }
1275
- /**
1276
- * Check if rules have any USD thresholds configured.
1277
- */
1278
- hasUsdThresholds(rules) {
1279
- return rules.instant_max_usd !== undefined
1280
- || rules.notify_max_usd !== undefined
1281
- || rules.delay_max_usd !== undefined;
1282
- }
1283
- /**
1284
- * Evaluate USD amount tier.
1285
- */
1286
- evaluateUsdTier(usdAmount, rules) {
1287
- if (rules.instant_max_usd !== undefined && usdAmount <= rules.instant_max_usd) {
1288
- return 'INSTANT';
1289
- }
1290
- if (rules.notify_max_usd !== undefined && usdAmount <= rules.notify_max_usd) {
1291
- return 'NOTIFY';
1292
- }
1293
- if (rules.delay_max_usd !== undefined && usdAmount <= rules.delay_max_usd) {
1294
- return 'DELAY';
1295
- }
1296
- return 'APPROVAL';
1297
- }
1298
- /**
1299
- * Build tokenContext from TransactionParam for evaluateTokenTier().
1300
- * Phase 236: Extracts relevant fields and attaches the policy's network.
1301
- */
1302
- buildTokenContext(transaction, spendingPolicy) {
1303
- return {
1304
- type: transaction.type,
1305
- tokenAddress: transaction.tokenAddress,
1306
- tokenDecimals: transaction.tokenDecimals,
1307
- chain: transaction.chain,
1308
- assetId: transaction.assetId,
1309
- policyNetwork: spendingPolicy?.network ?? undefined,
1310
- };
1311
- }
1312
- // -------------------------------------------------------------------------
1313
- // Private: LENDING_ASSET_WHITELIST evaluation (Step 4h)
1314
- // -------------------------------------------------------------------------
1315
- /**
1316
- * Evaluate LENDING_ASSET_WHITELIST policy.
1317
- *
1318
- * Logic:
1319
- * - Only applies to lending actions (supply/borrow/repay/withdraw)
1320
- * - If no LENDING_ASSET_WHITELIST policy exists: deny (default-deny per CLAUDE.md)
1321
- * - If policy exists: check if target contract address is in rules.assets[].address
1322
- *
1323
- * Returns PolicyEvaluation if denied, null if allowed (or not applicable).
1324
- */
1325
- evaluateLendingAssetWhitelist(resolved, transaction) {
1326
- // Only applies to lending actions
1327
- const LENDING_ACTIONS = new Set(['supply', 'borrow', 'repay', 'withdraw']);
1328
- if (!transaction.actionName || !LENDING_ACTIONS.has(transaction.actionName)) {
1329
- return null;
1330
- }
1331
- const assetPolicy = resolved.find((p) => p.type === 'LENDING_ASSET_WHITELIST');
1332
- if (!assetPolicy) {
1333
- // Default-deny (CLAUDE.md compliance): no whitelist -> deny lending
1334
- return {
1335
- allowed: false,
1336
- tier: 'INSTANT',
1337
- reason: 'No LENDING_ASSET_WHITELIST policy configured. Lending assets require explicit whitelist.',
1338
- };
1339
- }
1340
- const rules = JSON.parse(assetPolicy.rules);
1341
- const targetAddress = transaction.contractAddress ?? transaction.toAddress;
1342
- const isWhitelisted = rules.assets.some((a) => a.address.toLowerCase() === targetAddress.toLowerCase());
1343
- if (!isWhitelisted) {
1344
- return {
1345
- allowed: false,
1346
- tier: 'INSTANT',
1347
- reason: `Asset ${targetAddress} not in lending asset whitelist`,
1348
- };
1349
- }
1350
- return null; // pass through
1351
- }
1352
- // -------------------------------------------------------------------------
1353
- // Private: LENDING_LTV_LIMIT evaluation (Step 4h-b)
1354
- // -------------------------------------------------------------------------
1355
- /**
1356
- * Evaluate LENDING_LTV_LIMIT policy for borrow actions.
1357
- *
1358
- * Logic:
1359
- * - Only applies to borrow actions
1360
- * - Reads cached LENDING positions from defi_positions table
1361
- * - Calculates projected LTV = (currentDebtUsd + newBorrowUsd) / totalCollateralUsd
1362
- * - Denies if projected LTV > maxLtv
1363
- * - Returns DELAY tier if projected LTV > warningLtv
1364
- *
1365
- * @param usdAmount - USD value of the new borrow (from pipeline IPriceOracle, LEND-09)
1366
- * Returns PolicyEvaluation if denied/escalated, null if allowed (or not applicable).
1367
- */
1368
- evaluateLendingLtvLimit(resolved, transaction, walletId, usdAmount) {
1369
- // Only applies to borrow actions (matches 'borrow', 'aave_borrow', 'kamino_borrow', etc.)
1370
- if (!transaction.actionName?.endsWith('borrow'))
1371
- return null;
1372
- const ltvPolicy = resolved.find((p) => p.type === 'LENDING_LTV_LIMIT');
1373
- if (!ltvPolicy)
1374
- return null; // No LTV policy -> pass through
1375
- const rules = JSON.parse(ltvPolicy.rules);
1376
- // Read cached position data from defi_positions
1377
- if (!this.sqlite)
1378
- return null;
1379
- const positions = this.sqlite.prepare("SELECT amount_usd, metadata, status FROM defi_positions WHERE wallet_id = ? AND category = 'LENDING' AND status = 'ACTIVE'").all(walletId);
1380
- // Aggregate collateral and debt from positions
1381
- let totalCollateralUsd = 0;
1382
- let totalDebtUsd = 0;
1383
- for (const pos of positions) {
1384
- if (!pos.metadata)
1385
- continue;
1386
- try {
1387
- const meta = JSON.parse(pos.metadata);
1388
- const posType = meta.positionType;
1389
- const usd = pos.amount_usd ?? 0;
1390
- if (posType === 'SUPPLY')
1391
- totalCollateralUsd += usd;
1392
- else if (posType === 'BORROW')
1393
- totalDebtUsd += usd;
1394
- }
1395
- catch { /* ignore parse errors */ }
1396
- }
1397
- // Calculate projected LTV including new borrow amount (LEND-09)
1398
- const newBorrowUsd = usdAmount ?? 0;
1399
- const projectedLtv = totalCollateralUsd > 0
1400
- ? (totalDebtUsd + newBorrowUsd) / totalCollateralUsd
1401
- : (totalDebtUsd > 0 || newBorrowUsd > 0 ? Infinity : 0);
1402
- if (projectedLtv > rules.maxLtv) {
1403
- return {
1404
- allowed: false,
1405
- tier: 'INSTANT',
1406
- reason: `Borrow would exceed max LTV (projected: ${(projectedLtv * 100).toFixed(1)}%, limit: ${(rules.maxLtv * 100).toFixed(1)}%)`,
1407
- };
1408
- }
1409
- if (projectedLtv > rules.warningLtv) {
1410
- return {
1411
- allowed: true,
1412
- tier: 'DELAY',
1413
- reason: `LTV approaching limit (projected: ${(projectedLtv * 100).toFixed(1)}%)`,
1414
- };
1415
- }
1416
- return null; // pass through
1417
- }
1418
- // -------------------------------------------------------------------------
1419
- // Private: PERP_ALLOWED_MARKETS evaluation (Step 4i)
1420
- // -------------------------------------------------------------------------
1421
- /**
1422
- * Evaluate PERP_ALLOWED_MARKETS policy.
1423
- *
1424
- * Logic:
1425
- * - Only applies to perp actions (suffix matching: open_position, close_position,
1426
- * modify_position, add_margin, withdraw_margin)
1427
- * - If no PERP_ALLOWED_MARKETS policy exists: deny (default-deny per CLAUDE.md)
1428
- * - If policy exists: check if transaction's market (from actionName prefix or params)
1429
- * is in rules.markets[].market (case-insensitive)
1430
- *
1431
- * Market identification: TransactionParam.contractAddress is used as the market
1432
- * identifier for perp actions (the protocol program/contract address).
1433
- */
1434
- evaluatePerpAllowedMarkets(resolved, transaction) {
1435
- const PERP_ACTIONS = new Set([
1436
- 'open_position', 'close_position', 'modify_position',
1437
- 'add_margin', 'withdraw_margin',
1438
- ]);
1439
- // Suffix matching for prefixed actions (drift_open_position -> open_position)
1440
- const actionSuffix = transaction.actionName
1441
- ? [...PERP_ACTIONS].find((a) => transaction.actionName.endsWith(a))
1442
- : undefined;
1443
- if (!actionSuffix)
1444
- return null; // Not a perp action
1445
- const marketPolicy = resolved.find((p) => p.type === 'PERP_ALLOWED_MARKETS');
1446
- if (!marketPolicy) {
1447
- return {
1448
- allowed: false,
1449
- tier: 'INSTANT',
1450
- reason: 'No PERP_ALLOWED_MARKETS policy configured. Perp markets require explicit whitelist.',
1451
- };
1452
- }
1453
- const rules = JSON.parse(marketPolicy.rules);
1454
- const targetMarket = transaction.contractAddress ?? transaction.toAddress;
1455
- const isAllowed = rules.markets.some((m) => m.market.toLowerCase() === targetMarket.toLowerCase());
1456
- if (!isAllowed) {
1457
- return {
1458
- allowed: false,
1459
- tier: 'INSTANT',
1460
- reason: `Market ${targetMarket} not in perp allowed markets whitelist`,
1461
- };
1462
- }
1463
- return null;
1464
- }
1465
- // -------------------------------------------------------------------------
1466
- // Private: PERP_MAX_LEVERAGE evaluation (Step 4i-b)
1467
- // -------------------------------------------------------------------------
1468
- /**
1469
- * Evaluate PERP_MAX_LEVERAGE policy.
1470
- *
1471
- * Logic:
1472
- * - Only applies to open_position and modify_position (suffix matching)
1473
- * - Reads perpLeverage from TransactionParam
1474
- * - Denies if perpLeverage > rules.maxLeverage
1475
- * - Returns DELAY tier if perpLeverage > rules.warningLeverage (optional)
1476
- */
1477
- evaluatePerpMaxLeverage(resolved, transaction) {
1478
- if (!transaction.actionName)
1479
- return null;
1480
- const isLeverageAction = transaction.actionName.endsWith('open_position') ||
1481
- transaction.actionName.endsWith('modify_position');
1482
- if (!isLeverageAction)
1483
- return null;
1484
- const leveragePolicy = resolved.find((p) => p.type === 'PERP_MAX_LEVERAGE');
1485
- if (!leveragePolicy)
1486
- return null; // No leverage policy -> pass through
1487
- const rules = JSON.parse(leveragePolicy.rules);
1488
- const leverage = transaction.perpLeverage;
1489
- if (typeof leverage !== 'number')
1490
- return null; // No leverage info -> pass through
1491
- if (leverage > rules.maxLeverage) {
1492
- return {
1493
- allowed: false,
1494
- tier: 'INSTANT',
1495
- reason: `Leverage ${leverage}x exceeds max allowed (${rules.maxLeverage}x)`,
1496
- };
1497
- }
1498
- if (rules.warningLeverage && leverage > rules.warningLeverage) {
1499
- return {
1500
- allowed: true,
1501
- tier: 'DELAY',
1502
- reason: `Leverage ${leverage}x approaching limit (warning: ${rules.warningLeverage}x, max: ${rules.maxLeverage}x)`,
1503
- };
1504
- }
1505
- return null;
1506
- }
1507
- // -------------------------------------------------------------------------
1508
- // Private: PERP_MAX_POSITION_USD evaluation (Step 4i-c)
1509
- // -------------------------------------------------------------------------
1510
- /**
1511
- * Evaluate PERP_MAX_POSITION_USD policy.
1512
- *
1513
- * Logic:
1514
- * - Only applies to open_position and modify_position (suffix matching)
1515
- * - Reads perpSizeUsd from TransactionParam
1516
- * - Denies if perpSizeUsd > rules.maxPositionUsd
1517
- * - Returns DELAY tier if perpSizeUsd > rules.warningPositionUsd (optional)
1518
- */
1519
- evaluatePerpMaxPositionUsd(resolved, transaction) {
1520
- if (!transaction.actionName)
1521
- return null;
1522
- const isSizeAction = transaction.actionName.endsWith('open_position') ||
1523
- transaction.actionName.endsWith('modify_position');
1524
- if (!isSizeAction)
1525
- return null;
1526
- const sizePolicy = resolved.find((p) => p.type === 'PERP_MAX_POSITION_USD');
1527
- if (!sizePolicy)
1528
- return null; // No size policy -> pass through
1529
- const rules = JSON.parse(sizePolicy.rules);
1530
- const sizeUsd = transaction.perpSizeUsd;
1531
- if (typeof sizeUsd !== 'number')
1532
- return null; // No size info -> pass through
1533
- if (sizeUsd > rules.maxPositionUsd) {
1534
- return {
1535
- allowed: false,
1536
- tier: 'INSTANT',
1537
- reason: `Position size $${sizeUsd.toLocaleString()} exceeds max allowed ($${rules.maxPositionUsd.toLocaleString()})`,
1538
- };
1539
- }
1540
- if (rules.warningPositionUsd && sizeUsd > rules.warningPositionUsd) {
1541
- return {
1542
- allowed: true,
1543
- tier: 'DELAY',
1544
- reason: `Position size $${sizeUsd.toLocaleString()} approaching limit (warning: $${rules.warningPositionUsd.toLocaleString()}, max: $${rules.maxPositionUsd.toLocaleString()})`,
1545
- };
1546
- }
1547
- return null;
1548
- }
1549
- // -------------------------------------------------------------------------
1550
562
  // Private: REPUTATION_THRESHOLD evaluation (Phase 320)
1551
563
  // -------------------------------------------------------------------------
1552
- /**
1553
- * Evaluate REPUTATION_THRESHOLD policy.
1554
- *
1555
- * Logic:
1556
- * - Find REPUTATION_THRESHOLD policy in resolved list
1557
- * - If not found or check_counterparty=false, return null (skip)
1558
- * - Resolve counterparty agentId from toAddress via agent_identities table
1559
- * - If no agentId found, treat as unrated
1560
- * - If no reputationCacheService, treat as unrated
1561
- * - Lookup reputation score via cache
1562
- * - If null (unrated/RPC failure), return unrated_tier
1563
- * - If score < min_score, return below_threshold_tier
1564
- * - If score >= min_score, return null (pass, continue evaluation)
1565
- *
1566
- * Returns the reputation floor tier (PolicyTier) or null if no escalation needed.
1567
- * The caller applies maxTier to the final result.
1568
- */
1569
564
  async evaluateReputationThreshold(resolved, transaction) {
1570
565
  const policy = resolved.find((p) => p.type === 'REPUTATION_THRESHOLD');
1571
566
  if (!policy)
1572
567
  return null;
1573
- const rules = JSON.parse(policy.rules);
1574
- // If check_counterparty is disabled, skip entirely
568
+ const rules = this.parseRules(policy.rules, ReputationThresholdRulesSchema, 'REPUTATION_THRESHOLD');
1575
569
  if (!rules.check_counterparty)
1576
570
  return null;
1577
- // Resolve counterparty agentId from toAddress
1578
571
  const agentId = this.resolveAgentIdFromAddress(transaction.toAddress);
1579
- // No agent identity found -> treat as unrated
1580
572
  if (!agentId) {
1581
573
  return (rules.unrated_tier ?? 'APPROVAL');
1582
574
  }
1583
- // No reputation cache service -> treat as unrated
1584
575
  if (!this.reputationCacheService) {
1585
576
  return (rules.unrated_tier ?? 'APPROVAL');
1586
577
  }
1587
- // Lookup reputation
1588
578
  const reputation = await this.reputationCacheService.getReputation(agentId, rules.tag1 ?? '', rules.tag2 ?? '');
1589
- // Unrated (no data or RPC failure)
1590
579
  if (!reputation) {
1591
580
  return (rules.unrated_tier ?? 'APPROVAL');
1592
581
  }
1593
- // Score below threshold -> escalate
1594
582
  if (reputation.score < rules.min_score) {
1595
583
  return (rules.below_threshold_tier ?? 'APPROVAL');
1596
584
  }
1597
- // Score meets threshold -> no escalation
1598
585
  return null;
1599
586
  }
1600
- /**
1601
- * Resolve ERC-8004 agentId from a counterparty address.
1602
- *
1603
- * Looks up agent_identities table via wallet publicKey join to find
1604
- * the chain_agent_id for the counterparty. Case-insensitive for EVM addresses.
1605
- *
1606
- * @returns chain_agent_id string if found, null otherwise
1607
- */
1608
587
  resolveAgentIdFromAddress(toAddress) {
1609
588
  if (!toAddress)
1610
589
  return null;
1611
590
  const normalizedAddress = toAddress.toLowerCase();
1612
- // Join agent_identities + wallets to find chain_agent_id for the address.
1613
- // Case-insensitive comparison (EVM address case varies).
1614
- // Only consider REGISTERED or WALLET_LINKED identities.
1615
591
  const rows = this.db
1616
592
  .select({
1617
593
  chainAgentId: agentIdentities.chainAgentId,
@@ -1625,16 +601,7 @@ export class DatabasePolicyEngine {
1625
601
  (r.status === 'REGISTERED' || r.status === 'WALLET_LINKED'));
1626
602
  return match?.chainAgentId ?? null;
1627
603
  }
1628
- /**
1629
- * Pre-fetch reputation floor tier for use in evaluateAndReserve (synchronous context).
1630
- *
1631
- * Called from stage3Policy before entering the IMMEDIATE transaction, since
1632
- * evaluateReputationThreshold is async (RPC call) but evaluateAndReserve is sync.
1633
- *
1634
- * @returns Object with tier and notification context if escalation needed, undefined otherwise
1635
- */
1636
604
  async prefetchReputationTier(walletId, transaction, reputationCache) {
1637
- // Load policies to check for REPUTATION_THRESHOLD
1638
605
  const rows = await this.db
1639
606
  .select()
1640
607
  .from(policies)
@@ -1647,15 +614,13 @@ export class DatabasePolicyEngine {
1647
614
  const policy = resolved.find((p) => p.type === 'REPUTATION_THRESHOLD');
1648
615
  if (!policy)
1649
616
  return undefined;
1650
- const rules = JSON.parse(policy.rules);
617
+ const rules = this.parseRules(policy.rules, ReputationThresholdRulesSchema, 'REPUTATION_THRESHOLD');
1651
618
  if (!rules.check_counterparty)
1652
619
  return undefined;
1653
- // Resolve counterparty agentId
1654
620
  const agentId = this.resolveAgentIdFromAddress(transaction.toAddress);
1655
621
  if (!agentId) {
1656
622
  return { tier: (rules.unrated_tier ?? 'APPROVAL') };
1657
623
  }
1658
- // Lookup reputation via the provided cache
1659
624
  const reputation = await reputationCache.getReputation(agentId, rules.tag1 ?? '', rules.tag2 ?? '');
1660
625
  if (!reputation) {
1661
626
  return { tier: (rules.unrated_tier ?? 'APPROVAL') };
@@ -1667,152 +632,20 @@ export class DatabasePolicyEngine {
1667
632
  threshold: String(rules.min_score),
1668
633
  };
1669
634
  }
1670
- return undefined; // Score meets threshold
1671
- }
1672
- // ---------------------------------------------------------------------------
1673
- // Phase 389: VENUE_WHITELIST evaluation
1674
- // ---------------------------------------------------------------------------
1675
- /**
1676
- * Evaluate VENUE_WHITELIST policy (default-deny when enabled).
1677
- *
1678
- * Logic:
1679
- * - If transaction has no venue (contractCall) -> return null (skip)
1680
- * - If venue_whitelist_enabled setting is not 'true' -> return null (disabled)
1681
- * - Find VENUE_WHITELIST policy in resolved list
1682
- * - If no policy found + venue present -> DENY (default-deny)
1683
- * - If policy found + venue in whitelist -> return null (allowed)
1684
- * - If policy found + venue not in whitelist -> DENY
1685
- */
1686
- evaluateVenueWhitelist(resolved, transaction) {
1687
- // No venue (contractCall) -> skip
1688
- if (!transaction.venue)
1689
- return null;
1690
- // Check if venue whitelist is enabled via Admin Settings
1691
- let enabled = false;
1692
- try {
1693
- enabled = this.settingsService?.get('venue_whitelist_enabled') === 'true';
1694
- }
1695
- catch {
1696
- // Setting key not registered -- disabled by default
1697
- }
1698
- if (!enabled)
1699
- return null;
1700
- const policy = resolved.find((p) => p.type === 'VENUE_WHITELIST');
1701
- const venueNorm = transaction.venue.toLowerCase();
1702
- if (!policy) {
1703
- // Default-deny: venue present but no whitelist policy
1704
- return {
1705
- allowed: false,
1706
- tier: 'INSTANT',
1707
- reason: `VENUE_NOT_ALLOWED: venue '${transaction.venue}' is not whitelisted (no VENUE_WHITELIST policy)`,
1708
- };
1709
- }
1710
- const rules = JSON.parse(policy.rules);
1711
- const isAllowed = rules.venues.some((v) => v.id.toLowerCase() === venueNorm);
1712
- if (!isAllowed) {
1713
- return {
1714
- allowed: false,
1715
- tier: 'INSTANT',
1716
- reason: `VENUE_NOT_ALLOWED: venue '${transaction.venue}' is not in the whitelist`,
1717
- };
1718
- }
1719
- return null; // Venue allowed
635
+ return undefined;
1720
636
  }
1721
- // ---------------------------------------------------------------------------
1722
- // Phase 389: ACTION_CATEGORY_LIMIT evaluation
1723
- // ---------------------------------------------------------------------------
1724
- /**
1725
- * Evaluate ACTION_CATEGORY_LIMIT policy (per-action, daily, monthly USD limits).
1726
- *
1727
- * Logic:
1728
- * - If transaction has no actionCategory or notionalUsd -> return null (skip)
1729
- * - Find ACTION_CATEGORY_LIMIT policies matching transaction.actionCategory
1730
- * - Check per_action_limit_usd: deny if notionalUsd exceeds
1731
- * - Check daily_limit_usd: query cumulative notionalUsd for category today
1732
- * - Check monthly_limit_usd: query cumulative notionalUsd for category this month
1733
- * - On exceed: return tier_on_exceed (default 'DELAY')
1734
- */
1735
- evaluateActionCategoryLimit(resolved, transaction, walletId) {
1736
- if (!transaction.actionCategory || transaction.notionalUsd === undefined)
1737
- return null;
1738
- // Find matching ACTION_CATEGORY_LIMIT policies
1739
- const categoryPolicies = resolved.filter((p) => p.type === 'ACTION_CATEGORY_LIMIT');
1740
- if (categoryPolicies.length === 0)
1741
- return null;
1742
- for (const policy of categoryPolicies) {
1743
- const rules = JSON.parse(policy.rules);
1744
- if (rules.category !== transaction.actionCategory)
1745
- continue;
1746
- const tierOnExceed = (rules.tier_on_exceed ?? 'DELAY');
1747
- // Per-action limit
1748
- if (rules.per_action_limit_usd !== undefined && transaction.notionalUsd > rules.per_action_limit_usd) {
1749
- return {
1750
- allowed: true,
1751
- tier: tierOnExceed,
1752
- reason: `ACTION_CATEGORY_LIMIT: per-action $${transaction.notionalUsd} exceeds ${rules.category} limit $${rules.per_action_limit_usd}`,
1753
- };
1754
- }
1755
- // Daily limit (cumulative query)
1756
- if (rules.daily_limit_usd !== undefined && this.sqlite) {
1757
- const todayStart = new Date();
1758
- todayStart.setUTCHours(0, 0, 0, 0);
1759
- const todayStartSec = Math.floor(todayStart.getTime() / 1000);
1760
- const row = this.sqlite.prepare(`
1761
- SELECT COALESCE(SUM(
1762
- CASE
1763
- WHEN json_extract(metadata, '$.notionalUsd') IS NOT NULL
1764
- THEN CAST(json_extract(metadata, '$.notionalUsd') AS REAL)
1765
- ELSE 0
1766
- END
1767
- ), 0) AS total
1768
- FROM transactions
1769
- WHERE wallet_id = ?
1770
- AND action_kind IN ('signedData', 'signedHttp')
1771
- AND json_extract(metadata, '$.actionCategory') = ?
1772
- AND created_at >= ?
1773
- AND status != 'FAILED'
1774
- `).get(walletId ?? resolved[0]?.walletId ?? null, rules.category, todayStartSec);
1775
- const cumulative = (row?.total ?? 0) + transaction.notionalUsd;
1776
- if (cumulative > rules.daily_limit_usd) {
1777
- return {
1778
- allowed: true,
1779
- tier: tierOnExceed,
1780
- reason: `ACTION_CATEGORY_LIMIT: daily cumulative $${cumulative.toFixed(2)} exceeds ${rules.category} limit $${rules.daily_limit_usd}`,
1781
- };
1782
- }
1783
- }
1784
- // Monthly limit (cumulative query)
1785
- if (rules.monthly_limit_usd !== undefined && this.sqlite) {
1786
- const monthStart = new Date();
1787
- monthStart.setUTCDate(1);
1788
- monthStart.setUTCHours(0, 0, 0, 0);
1789
- const monthStartSec = Math.floor(monthStart.getTime() / 1000);
1790
- const row = this.sqlite.prepare(`
1791
- SELECT COALESCE(SUM(
1792
- CASE
1793
- WHEN json_extract(metadata, '$.notionalUsd') IS NOT NULL
1794
- THEN CAST(json_extract(metadata, '$.notionalUsd') AS REAL)
1795
- ELSE 0
1796
- END
1797
- ), 0) AS total
1798
- FROM transactions
1799
- WHERE wallet_id = ?
1800
- AND action_kind IN ('signedData', 'signedHttp')
1801
- AND json_extract(metadata, '$.actionCategory') = ?
1802
- AND created_at >= ?
1803
- AND status != 'FAILED'
1804
- `).get(walletId ?? resolved[0]?.walletId ?? null, rules.category, monthStartSec);
1805
- const cumulative = (row?.total ?? 0) + transaction.notionalUsd;
1806
- if (cumulative > rules.monthly_limit_usd) {
1807
- return {
1808
- allowed: true,
1809
- tier: tierOnExceed,
1810
- reason: `ACTION_CATEGORY_LIMIT: monthly cumulative $${cumulative.toFixed(2)} exceeds ${rules.category} limit $${rules.monthly_limit_usd}`,
1811
- };
1812
- }
1813
- }
1814
- }
1815
- return null; // Within limits
637
+ // -------------------------------------------------------------------------
638
+ // Private: buildTokenContext helper
639
+ // -------------------------------------------------------------------------
640
+ buildTokenContext(transaction, spendingPolicy) {
641
+ return {
642
+ type: transaction.type,
643
+ tokenAddress: transaction.tokenAddress,
644
+ tokenDecimals: transaction.tokenDecimals,
645
+ chain: transaction.chain,
646
+ assetId: transaction.assetId,
647
+ policyNetwork: spendingPolicy?.network ?? undefined,
648
+ };
1816
649
  }
1817
650
  }
1818
651
  //# sourceMappingURL=database-policy-engine.js.map