@waiaas/daemon 2.0.0-rc.1
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.
- package/dist/api/error-hints.d.ts +15 -0
- package/dist/api/error-hints.d.ts.map +1 -0
- package/dist/api/error-hints.js +71 -0
- package/dist/api/error-hints.js.map +1 -0
- package/dist/api/index.d.ts +11 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +14 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/middleware/address-validation.d.ts +38 -0
- package/dist/api/middleware/address-validation.d.ts.map +1 -0
- package/dist/api/middleware/address-validation.js +134 -0
- package/dist/api/middleware/address-validation.js.map +1 -0
- package/dist/api/middleware/csp.d.ts +17 -0
- package/dist/api/middleware/csp.d.ts.map +1 -0
- package/dist/api/middleware/csp.js +31 -0
- package/dist/api/middleware/csp.js.map +1 -0
- package/dist/api/middleware/error-handler.d.ts +16 -0
- package/dist/api/middleware/error-handler.d.ts.map +1 -0
- package/dist/api/middleware/error-handler.js +46 -0
- package/dist/api/middleware/error-handler.js.map +1 -0
- package/dist/api/middleware/host-guard.d.ts +11 -0
- package/dist/api/middleware/host-guard.d.ts.map +1 -0
- package/dist/api/middleware/host-guard.js +25 -0
- package/dist/api/middleware/host-guard.js.map +1 -0
- package/dist/api/middleware/index.d.ts +13 -0
- package/dist/api/middleware/index.d.ts.map +1 -0
- package/dist/api/middleware/index.js +13 -0
- package/dist/api/middleware/index.js.map +1 -0
- package/dist/api/middleware/kill-switch-guard.d.ts +19 -0
- package/dist/api/middleware/kill-switch-guard.d.ts.map +1 -0
- package/dist/api/middleware/kill-switch-guard.js +49 -0
- package/dist/api/middleware/kill-switch-guard.js.map +1 -0
- package/dist/api/middleware/master-auth.d.ts +15 -0
- package/dist/api/middleware/master-auth.d.ts.map +1 -0
- package/dist/api/middleware/master-auth.js +35 -0
- package/dist/api/middleware/master-auth.js.map +1 -0
- package/dist/api/middleware/owner-auth.d.ts +30 -0
- package/dist/api/middleware/owner-auth.d.ts.map +1 -0
- package/dist/api/middleware/owner-auth.js +133 -0
- package/dist/api/middleware/owner-auth.js.map +1 -0
- package/dist/api/middleware/request-id.d.ts +10 -0
- package/dist/api/middleware/request-id.d.ts.map +1 -0
- package/dist/api/middleware/request-id.js +18 -0
- package/dist/api/middleware/request-id.js.map +1 -0
- package/dist/api/middleware/request-logger.d.ts +9 -0
- package/dist/api/middleware/request-logger.d.ts.map +1 -0
- package/dist/api/middleware/request-logger.js +18 -0
- package/dist/api/middleware/request-logger.js.map +1 -0
- package/dist/api/middleware/session-auth.d.ts +21 -0
- package/dist/api/middleware/session-auth.d.ts.map +1 -0
- package/dist/api/middleware/session-auth.js +51 -0
- package/dist/api/middleware/session-auth.js.map +1 -0
- package/dist/api/middleware/siwe-verify.d.ts +31 -0
- package/dist/api/middleware/siwe-verify.d.ts.map +1 -0
- package/dist/api/middleware/siwe-verify.js +55 -0
- package/dist/api/middleware/siwe-verify.js.map +1 -0
- package/dist/api/routes/actions.d.ts +56 -0
- package/dist/api/routes/actions.d.ts.map +1 -0
- package/dist/api/routes/actions.js +291 -0
- package/dist/api/routes/actions.js.map +1 -0
- package/dist/api/routes/admin.d.ts +99 -0
- package/dist/api/routes/admin.d.ts.map +1 -0
- package/dist/api/routes/admin.js +1304 -0
- package/dist/api/routes/admin.js.map +1 -0
- package/dist/api/routes/display-currency-helper.d.ts +26 -0
- package/dist/api/routes/display-currency-helper.d.ts.map +1 -0
- package/dist/api/routes/display-currency-helper.js +47 -0
- package/dist/api/routes/display-currency-helper.js.map +1 -0
- package/dist/api/routes/health.d.ts +14 -0
- package/dist/api/routes/health.d.ts.map +1 -0
- package/dist/api/routes/health.js +47 -0
- package/dist/api/routes/health.js.map +1 -0
- package/dist/api/routes/index.d.ts +15 -0
- package/dist/api/routes/index.d.ts.map +1 -0
- package/dist/api/routes/index.js +15 -0
- package/dist/api/routes/index.js.map +1 -0
- package/dist/api/routes/mcp.d.ts +30 -0
- package/dist/api/routes/mcp.d.ts.map +1 -0
- package/dist/api/routes/mcp.js +156 -0
- package/dist/api/routes/mcp.js.map +1 -0
- package/dist/api/routes/nonce.d.ts +20 -0
- package/dist/api/routes/nonce.d.ts.map +1 -0
- package/dist/api/routes/nonce.js +48 -0
- package/dist/api/routes/nonce.js.map +1 -0
- package/dist/api/routes/openapi-schemas.d.ts +2281 -0
- package/dist/api/routes/openapi-schemas.d.ts.map +1 -0
- package/dist/api/routes/openapi-schemas.js +770 -0
- package/dist/api/routes/openapi-schemas.js.map +1 -0
- package/dist/api/routes/policies.d.ts +29 -0
- package/dist/api/routes/policies.d.ts.map +1 -0
- package/dist/api/routes/policies.js +332 -0
- package/dist/api/routes/policies.js.map +1 -0
- package/dist/api/routes/sessions.d.ts +35 -0
- package/dist/api/routes/sessions.d.ts.map +1 -0
- package/dist/api/routes/sessions.js +347 -0
- package/dist/api/routes/sessions.js.map +1 -0
- package/dist/api/routes/skills.d.ts +9 -0
- package/dist/api/routes/skills.d.ts.map +1 -0
- package/dist/api/routes/skills.js +59 -0
- package/dist/api/routes/skills.js.map +1 -0
- package/dist/api/routes/tokens.d.ts +25 -0
- package/dist/api/routes/tokens.d.ts.map +1 -0
- package/dist/api/routes/tokens.js +161 -0
- package/dist/api/routes/tokens.js.map +1 -0
- package/dist/api/routes/transactions.d.ts +68 -0
- package/dist/api/routes/transactions.d.ts.map +1 -0
- package/dist/api/routes/transactions.js +576 -0
- package/dist/api/routes/transactions.js.map +1 -0
- package/dist/api/routes/utils.d.ts +9 -0
- package/dist/api/routes/utils.d.ts.map +1 -0
- package/dist/api/routes/utils.js +52 -0
- package/dist/api/routes/utils.js.map +1 -0
- package/dist/api/routes/wallet.d.ts +36 -0
- package/dist/api/routes/wallet.d.ts.map +1 -0
- package/dist/api/routes/wallet.js +358 -0
- package/dist/api/routes/wallet.js.map +1 -0
- package/dist/api/routes/wallets.d.ts +43 -0
- package/dist/api/routes/wallets.d.ts.map +1 -0
- package/dist/api/routes/wallets.js +630 -0
- package/dist/api/routes/wallets.js.map +1 -0
- package/dist/api/routes/wc.d.ts +46 -0
- package/dist/api/routes/wc.d.ts.map +1 -0
- package/dist/api/routes/wc.js +354 -0
- package/dist/api/routes/wc.js.map +1 -0
- package/dist/api/routes/x402.d.ts +61 -0
- package/dist/api/routes/x402.d.ts.map +1 -0
- package/dist/api/routes/x402.js +493 -0
- package/dist/api/routes/x402.js.map +1 -0
- package/dist/api/server.d.ts +81 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +406 -0
- package/dist/api/server.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/action/action-provider-registry.d.ts +77 -0
- package/dist/infrastructure/action/action-provider-registry.d.ts.map +1 -0
- package/dist/infrastructure/action/action-provider-registry.js +239 -0
- package/dist/infrastructure/action/action-provider-registry.js.map +1 -0
- package/dist/infrastructure/action/api-key-store.d.ts +60 -0
- package/dist/infrastructure/action/api-key-store.d.ts.map +1 -0
- package/dist/infrastructure/action/api-key-store.js +130 -0
- package/dist/infrastructure/action/api-key-store.js.map +1 -0
- package/dist/infrastructure/action/index.d.ts +10 -0
- package/dist/infrastructure/action/index.d.ts.map +1 -0
- package/dist/infrastructure/action/index.js +9 -0
- package/dist/infrastructure/action/index.js.map +1 -0
- package/dist/infrastructure/adapter-pool.d.ts +50 -0
- package/dist/infrastructure/adapter-pool.d.ts.map +1 -0
- package/dist/infrastructure/adapter-pool.js +110 -0
- package/dist/infrastructure/adapter-pool.js.map +1 -0
- package/dist/infrastructure/backup/backup-service.d.ts +53 -0
- package/dist/infrastructure/backup/backup-service.d.ts.map +1 -0
- package/dist/infrastructure/backup/backup-service.js +158 -0
- package/dist/infrastructure/backup/backup-service.js.map +1 -0
- package/dist/infrastructure/backup/index.d.ts +2 -0
- package/dist/infrastructure/backup/index.d.ts.map +1 -0
- package/dist/infrastructure/backup/index.js +2 -0
- package/dist/infrastructure/backup/index.js.map +1 -0
- package/dist/infrastructure/config/index.d.ts +8 -0
- package/dist/infrastructure/config/index.d.ts.map +1 -0
- package/dist/infrastructure/config/index.js +7 -0
- package/dist/infrastructure/config/index.js.map +1 -0
- package/dist/infrastructure/config/loader.d.ts +555 -0
- package/dist/infrastructure/config/loader.d.ts.map +1 -0
- package/dist/infrastructure/config/loader.js +311 -0
- package/dist/infrastructure/config/loader.js.map +1 -0
- package/dist/infrastructure/database/checks.d.ts +19 -0
- package/dist/infrastructure/database/checks.d.ts.map +1 -0
- package/dist/infrastructure/database/checks.js +27 -0
- package/dist/infrastructure/database/checks.js.map +1 -0
- package/dist/infrastructure/database/compatibility.d.ts +36 -0
- package/dist/infrastructure/database/compatibility.d.ts.map +1 -0
- package/dist/infrastructure/database/compatibility.js +75 -0
- package/dist/infrastructure/database/compatibility.js.map +1 -0
- package/dist/infrastructure/database/connection.d.ts +36 -0
- package/dist/infrastructure/database/connection.d.ts.map +1 -0
- package/dist/infrastructure/database/connection.js +47 -0
- package/dist/infrastructure/database/connection.js.map +1 -0
- package/dist/infrastructure/database/id.d.ts +17 -0
- package/dist/infrastructure/database/id.d.ts.map +1 -0
- package/dist/infrastructure/database/id.js +20 -0
- package/dist/infrastructure/database/id.js.map +1 -0
- package/dist/infrastructure/database/index.d.ts +15 -0
- package/dist/infrastructure/database/index.d.ts.map +1 -0
- package/dist/infrastructure/database/index.js +12 -0
- package/dist/infrastructure/database/index.js.map +1 -0
- package/dist/infrastructure/database/migrate.d.ts +76 -0
- package/dist/infrastructure/database/migrate.d.ts.map +1 -0
- package/dist/infrastructure/database/migrate.js +1214 -0
- package/dist/infrastructure/database/migrate.js.map +1 -0
- package/dist/infrastructure/database/schema.d.ts +2352 -0
- package/dist/infrastructure/database/schema.d.ts.map +1 -0
- package/dist/infrastructure/database/schema.js +288 -0
- package/dist/infrastructure/database/schema.js.map +1 -0
- package/dist/infrastructure/jwt/index.d.ts +2 -0
- package/dist/infrastructure/jwt/index.d.ts.map +1 -0
- package/dist/infrastructure/jwt/index.js +2 -0
- package/dist/infrastructure/jwt/index.js.map +1 -0
- package/dist/infrastructure/jwt/jwt-secret-manager.d.ts +58 -0
- package/dist/infrastructure/jwt/jwt-secret-manager.d.ts.map +1 -0
- package/dist/infrastructure/jwt/jwt-secret-manager.js +222 -0
- package/dist/infrastructure/jwt/jwt-secret-manager.js.map +1 -0
- package/dist/infrastructure/keystore/crypto.d.ts +62 -0
- package/dist/infrastructure/keystore/crypto.d.ts.map +1 -0
- package/dist/infrastructure/keystore/crypto.js +89 -0
- package/dist/infrastructure/keystore/crypto.js.map +1 -0
- package/dist/infrastructure/keystore/index.d.ts +4 -0
- package/dist/infrastructure/keystore/index.d.ts.map +1 -0
- package/dist/infrastructure/keystore/index.js +5 -0
- package/dist/infrastructure/keystore/index.js.map +1 -0
- package/dist/infrastructure/keystore/keystore.d.ts +115 -0
- package/dist/infrastructure/keystore/keystore.d.ts.map +1 -0
- package/dist/infrastructure/keystore/keystore.js +327 -0
- package/dist/infrastructure/keystore/keystore.js.map +1 -0
- package/dist/infrastructure/keystore/memory.d.ts +45 -0
- package/dist/infrastructure/keystore/memory.d.ts.map +1 -0
- package/dist/infrastructure/keystore/memory.js +105 -0
- package/dist/infrastructure/keystore/memory.js.map +1 -0
- package/dist/infrastructure/oracle/coingecko-forex.d.ts +35 -0
- package/dist/infrastructure/oracle/coingecko-forex.d.ts.map +1 -0
- package/dist/infrastructure/oracle/coingecko-forex.js +69 -0
- package/dist/infrastructure/oracle/coingecko-forex.js.map +1 -0
- package/dist/infrastructure/oracle/coingecko-oracle.d.ts +73 -0
- package/dist/infrastructure/oracle/coingecko-oracle.d.ts.map +1 -0
- package/dist/infrastructure/oracle/coingecko-oracle.js +199 -0
- package/dist/infrastructure/oracle/coingecko-oracle.js.map +1 -0
- package/dist/infrastructure/oracle/coingecko-platform-ids.d.ts +32 -0
- package/dist/infrastructure/oracle/coingecko-platform-ids.d.ts.map +1 -0
- package/dist/infrastructure/oracle/coingecko-platform-ids.js +30 -0
- package/dist/infrastructure/oracle/coingecko-platform-ids.js.map +1 -0
- package/dist/infrastructure/oracle/forex-currencies.d.ts +36 -0
- package/dist/infrastructure/oracle/forex-currencies.d.ts.map +1 -0
- package/dist/infrastructure/oracle/forex-currencies.js +71 -0
- package/dist/infrastructure/oracle/forex-currencies.js.map +1 -0
- package/dist/infrastructure/oracle/forex-rate-service.d.ts +51 -0
- package/dist/infrastructure/oracle/forex-rate-service.d.ts.map +1 -0
- package/dist/infrastructure/oracle/forex-rate-service.js +149 -0
- package/dist/infrastructure/oracle/forex-rate-service.js.map +1 -0
- package/dist/infrastructure/oracle/index.d.ts +18 -0
- package/dist/infrastructure/oracle/index.d.ts.map +1 -0
- package/dist/infrastructure/oracle/index.js +19 -0
- package/dist/infrastructure/oracle/index.js.map +1 -0
- package/dist/infrastructure/oracle/oracle-chain.d.ts +101 -0
- package/dist/infrastructure/oracle/oracle-chain.d.ts.map +1 -0
- package/dist/infrastructure/oracle/oracle-chain.js +163 -0
- package/dist/infrastructure/oracle/oracle-chain.js.map +1 -0
- package/dist/infrastructure/oracle/oracle-errors.d.ts +42 -0
- package/dist/infrastructure/oracle/oracle-errors.d.ts.map +1 -0
- package/dist/infrastructure/oracle/oracle-errors.js +53 -0
- package/dist/infrastructure/oracle/oracle-errors.js.map +1 -0
- package/dist/infrastructure/oracle/price-age.d.ts +38 -0
- package/dist/infrastructure/oracle/price-age.d.ts.map +1 -0
- package/dist/infrastructure/oracle/price-age.js +44 -0
- package/dist/infrastructure/oracle/price-age.js.map +1 -0
- package/dist/infrastructure/oracle/price-cache.d.ts +99 -0
- package/dist/infrastructure/oracle/price-cache.d.ts.map +1 -0
- package/dist/infrastructure/oracle/price-cache.js +173 -0
- package/dist/infrastructure/oracle/price-cache.js.map +1 -0
- package/dist/infrastructure/oracle/pyth-feed-ids.d.ts +31 -0
- package/dist/infrastructure/oracle/pyth-feed-ids.d.ts.map +1 -0
- package/dist/infrastructure/oracle/pyth-feed-ids.js +44 -0
- package/dist/infrastructure/oracle/pyth-feed-ids.js.map +1 -0
- package/dist/infrastructure/oracle/pyth-oracle.d.ts +69 -0
- package/dist/infrastructure/oracle/pyth-oracle.d.ts.map +1 -0
- package/dist/infrastructure/oracle/pyth-oracle.js +149 -0
- package/dist/infrastructure/oracle/pyth-oracle.js.map +1 -0
- package/dist/infrastructure/settings/hot-reload.d.ts +71 -0
- package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -0
- package/dist/infrastructure/settings/hot-reload.js +315 -0
- package/dist/infrastructure/settings/hot-reload.js.map +1 -0
- package/dist/infrastructure/settings/index.d.ts +13 -0
- package/dist/infrastructure/settings/index.d.ts.map +1 -0
- package/dist/infrastructure/settings/index.js +10 -0
- package/dist/infrastructure/settings/index.js.map +1 -0
- package/dist/infrastructure/settings/setting-keys.d.ts +28 -0
- package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -0
- package/dist/infrastructure/settings/setting-keys.js +105 -0
- package/dist/infrastructure/settings/setting-keys.js.map +1 -0
- package/dist/infrastructure/settings/settings-crypto.d.ts +39 -0
- package/dist/infrastructure/settings/settings-crypto.d.ts.map +1 -0
- package/dist/infrastructure/settings/settings-crypto.js +73 -0
- package/dist/infrastructure/settings/settings-crypto.js.map +1 -0
- package/dist/infrastructure/settings/settings-service.d.ts +82 -0
- package/dist/infrastructure/settings/settings-service.d.ts.map +1 -0
- package/dist/infrastructure/settings/settings-service.js +267 -0
- package/dist/infrastructure/settings/settings-service.js.map +1 -0
- package/dist/infrastructure/telegram/index.d.ts +6 -0
- package/dist/infrastructure/telegram/index.d.ts.map +1 -0
- package/dist/infrastructure/telegram/index.js +5 -0
- package/dist/infrastructure/telegram/index.js.map +1 -0
- package/dist/infrastructure/telegram/telegram-api.d.ts +35 -0
- package/dist/infrastructure/telegram/telegram-api.d.ts.map +1 -0
- package/dist/infrastructure/telegram/telegram-api.js +82 -0
- package/dist/infrastructure/telegram/telegram-api.js.map +1 -0
- package/dist/infrastructure/telegram/telegram-auth.d.ts +57 -0
- package/dist/infrastructure/telegram/telegram-auth.d.ts.map +1 -0
- package/dist/infrastructure/telegram/telegram-auth.js +88 -0
- package/dist/infrastructure/telegram/telegram-auth.js.map +1 -0
- package/dist/infrastructure/telegram/telegram-bot-service.d.ts +95 -0
- package/dist/infrastructure/telegram/telegram-bot-service.d.ts.map +1 -0
- package/dist/infrastructure/telegram/telegram-bot-service.js +564 -0
- package/dist/infrastructure/telegram/telegram-bot-service.js.map +1 -0
- package/dist/infrastructure/telegram/telegram-keyboard.d.ts +27 -0
- package/dist/infrastructure/telegram/telegram-keyboard.d.ts.map +1 -0
- package/dist/infrastructure/telegram/telegram-keyboard.js +52 -0
- package/dist/infrastructure/telegram/telegram-keyboard.js.map +1 -0
- package/dist/infrastructure/telegram/telegram-types.d.ts +43 -0
- package/dist/infrastructure/telegram/telegram-types.d.ts.map +1 -0
- package/dist/infrastructure/telegram/telegram-types.js +8 -0
- package/dist/infrastructure/telegram/telegram-types.js.map +1 -0
- package/dist/infrastructure/token-registry/builtin-tokens.d.ts +39 -0
- package/dist/infrastructure/token-registry/builtin-tokens.d.ts.map +1 -0
- package/dist/infrastructure/token-registry/builtin-tokens.js +135 -0
- package/dist/infrastructure/token-registry/builtin-tokens.js.map +1 -0
- package/dist/infrastructure/token-registry/index.d.ts +8 -0
- package/dist/infrastructure/token-registry/index.d.ts.map +1 -0
- package/dist/infrastructure/token-registry/index.js +8 -0
- package/dist/infrastructure/token-registry/index.js.map +1 -0
- package/dist/infrastructure/token-registry/token-registry-service.d.ts +49 -0
- package/dist/infrastructure/token-registry/token-registry-service.d.ts.map +1 -0
- package/dist/infrastructure/token-registry/token-registry-service.js +93 -0
- package/dist/infrastructure/token-registry/token-registry-service.js.map +1 -0
- package/dist/infrastructure/version/index.d.ts +5 -0
- package/dist/infrastructure/version/index.d.ts.map +1 -0
- package/dist/infrastructure/version/index.js +5 -0
- package/dist/infrastructure/version/index.js.map +1 -0
- package/dist/infrastructure/version/version-check-service.d.ts +35 -0
- package/dist/infrastructure/version/version-check-service.d.ts.map +1 -0
- package/dist/infrastructure/version/version-check-service.js +92 -0
- package/dist/infrastructure/version/version-check-service.js.map +1 -0
- package/dist/lifecycle/daemon.d.ts +103 -0
- package/dist/lifecycle/daemon.d.ts.map +1 -0
- package/dist/lifecycle/daemon.js +934 -0
- package/dist/lifecycle/daemon.js.map +1 -0
- package/dist/lifecycle/index.d.ts +9 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +9 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle/signal-handler.d.ts +18 -0
- package/dist/lifecycle/signal-handler.d.ts.map +1 -0
- package/dist/lifecycle/signal-handler.js +37 -0
- package/dist/lifecycle/signal-handler.js.map +1 -0
- package/dist/lifecycle/workers.d.ts +46 -0
- package/dist/lifecycle/workers.d.ts.map +1 -0
- package/dist/lifecycle/workers.js +101 -0
- package/dist/lifecycle/workers.js.map +1 -0
- package/dist/notifications/channels/discord.d.ts +10 -0
- package/dist/notifications/channels/discord.d.ts.map +1 -0
- package/dist/notifications/channels/discord.js +54 -0
- package/dist/notifications/channels/discord.js.map +1 -0
- package/dist/notifications/channels/ntfy.d.ts +13 -0
- package/dist/notifications/channels/ntfy.d.ts.map +1 -0
- package/dist/notifications/channels/ntfy.js +58 -0
- package/dist/notifications/channels/ntfy.js.map +1 -0
- package/dist/notifications/channels/slack.d.ts +10 -0
- package/dist/notifications/channels/slack.d.ts.map +1 -0
- package/dist/notifications/channels/slack.js +55 -0
- package/dist/notifications/channels/slack.js.map +1 -0
- package/dist/notifications/channels/telegram.d.ts +10 -0
- package/dist/notifications/channels/telegram.d.ts.map +1 -0
- package/dist/notifications/channels/telegram.js +40 -0
- package/dist/notifications/channels/telegram.js.map +1 -0
- package/dist/notifications/index.d.ts +9 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +7 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/notification-service.d.ts +75 -0
- package/dist/notifications/notification-service.d.ts.map +1 -0
- package/dist/notifications/notification-service.js +213 -0
- package/dist/notifications/notification-service.js.map +1 -0
- package/dist/notifications/templates/message-templates.d.ts +12 -0
- package/dist/notifications/templates/message-templates.d.ts.map +1 -0
- package/dist/notifications/templates/message-templates.js +22 -0
- package/dist/notifications/templates/message-templates.js.map +1 -0
- package/dist/pipeline/database-policy-engine.d.ts +286 -0
- package/dist/pipeline/database-policy-engine.d.ts.map +1 -0
- package/dist/pipeline/database-policy-engine.js +992 -0
- package/dist/pipeline/database-policy-engine.js.map +1 -0
- package/dist/pipeline/default-policy-engine.d.ts +26 -0
- package/dist/pipeline/default-policy-engine.d.ts.map +1 -0
- package/dist/pipeline/default-policy-engine.js +25 -0
- package/dist/pipeline/default-policy-engine.js.map +1 -0
- package/dist/pipeline/index.d.ts +9 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +9 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/network-resolver.d.ts +22 -0
- package/dist/pipeline/network-resolver.d.ts.map +1 -0
- package/dist/pipeline/network-resolver.js +32 -0
- package/dist/pipeline/network-resolver.js.map +1 -0
- package/dist/pipeline/pipeline.d.ts +72 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +87 -0
- package/dist/pipeline/pipeline.js.map +1 -0
- package/dist/pipeline/resolve-effective-amount-usd.d.ts +41 -0
- package/dist/pipeline/resolve-effective-amount-usd.d.ts.map +1 -0
- package/dist/pipeline/resolve-effective-amount-usd.js +208 -0
- package/dist/pipeline/resolve-effective-amount-usd.js.map +1 -0
- package/dist/pipeline/sign-only.d.ts +99 -0
- package/dist/pipeline/sign-only.d.ts.map +1 -0
- package/dist/pipeline/sign-only.js +267 -0
- package/dist/pipeline/sign-only.js.map +1 -0
- package/dist/pipeline/sleep.d.ts +6 -0
- package/dist/pipeline/sleep.d.ts.map +1 -0
- package/dist/pipeline/sleep.js +8 -0
- package/dist/pipeline/sleep.js.map +1 -0
- package/dist/pipeline/stages.d.ts +82 -0
- package/dist/pipeline/stages.d.ts.map +1 -0
- package/dist/pipeline/stages.js +784 -0
- package/dist/pipeline/stages.js.map +1 -0
- package/dist/services/autostop-rules.d.ts +79 -0
- package/dist/services/autostop-rules.d.ts.map +1 -0
- package/dist/services/autostop-rules.js +174 -0
- package/dist/services/autostop-rules.js.map +1 -0
- package/dist/services/autostop-service.d.ts +82 -0
- package/dist/services/autostop-service.d.ts.map +1 -0
- package/dist/services/autostop-service.js +223 -0
- package/dist/services/autostop-service.js.map +1 -0
- package/dist/services/kill-switch-service.d.ts +118 -0
- package/dist/services/kill-switch-service.d.ts.map +1 -0
- package/dist/services/kill-switch-service.js +291 -0
- package/dist/services/kill-switch-service.js.map +1 -0
- package/dist/services/monitoring/balance-monitor-service.d.ts +65 -0
- package/dist/services/monitoring/balance-monitor-service.d.ts.map +1 -0
- package/dist/services/monitoring/balance-monitor-service.js +207 -0
- package/dist/services/monitoring/balance-monitor-service.js.map +1 -0
- package/dist/services/wc-session-service.d.ts +123 -0
- package/dist/services/wc-session-service.d.ts.map +1 -0
- package/dist/services/wc-session-service.js +363 -0
- package/dist/services/wc-session-service.js.map +1 -0
- package/dist/services/wc-signing-bridge.d.ts +60 -0
- package/dist/services/wc-signing-bridge.d.ts.map +1 -0
- package/dist/services/wc-signing-bridge.js +334 -0
- package/dist/services/wc-signing-bridge.js.map +1 -0
- package/dist/services/wc-storage.d.ts +32 -0
- package/dist/services/wc-storage.d.ts.map +1 -0
- package/dist/services/wc-storage.js +64 -0
- package/dist/services/wc-storage.js.map +1 -0
- package/dist/services/x402/payment-signer.d.ts +88 -0
- package/dist/services/x402/payment-signer.d.ts.map +1 -0
- package/dist/services/x402/payment-signer.js +311 -0
- package/dist/services/x402/payment-signer.js.map +1 -0
- package/dist/services/x402/ssrf-guard.d.ts +27 -0
- package/dist/services/x402/ssrf-guard.d.ts.map +1 -0
- package/dist/services/x402/ssrf-guard.js +236 -0
- package/dist/services/x402/ssrf-guard.js.map +1 -0
- package/dist/services/x402/x402-domain-policy.d.ts +50 -0
- package/dist/services/x402/x402-domain-policy.d.ts.map +1 -0
- package/dist/services/x402/x402-domain-policy.js +78 -0
- package/dist/services/x402/x402-domain-policy.js.map +1 -0
- package/dist/services/x402/x402-handler.d.ts +71 -0
- package/dist/services/x402/x402-handler.d.ts.map +1 -0
- package/dist/services/x402/x402-handler.js +195 -0
- package/dist/services/x402/x402-handler.js.map +1 -0
- package/dist/services/x402/x402-usd-resolver.d.ts +26 -0
- package/dist/services/x402/x402-usd-resolver.d.ts.map +1 -0
- package/dist/services/x402/x402-usd-resolver.js +79 -0
- package/dist/services/x402/x402-usd-resolver.js.map +1 -0
- package/dist/workflow/approval-workflow.d.ts +103 -0
- package/dist/workflow/approval-workflow.d.ts.map +1 -0
- package/dist/workflow/approval-workflow.js +202 -0
- package/dist/workflow/approval-workflow.js.map +1 -0
- package/dist/workflow/delay-queue.d.ts +78 -0
- package/dist/workflow/delay-queue.d.ts.map +1 -0
- package/dist/workflow/delay-queue.js +174 -0
- package/dist/workflow/delay-queue.js.map +1 -0
- package/dist/workflow/index.d.ts +11 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +9 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/owner-state.d.ts +97 -0
- package/dist/workflow/owner-state.d.ts.map +1 -0
- package/dist/workflow/owner-state.js +168 -0
- package/dist/workflow/owner-state.js.map +1 -0
- package/package.json +71 -0
- package/public/admin/assets/index-BPoUSH8W.css +1 -0
- package/public/admin/assets/index-CDi1qoXB.js +1 -0
- package/public/admin/index.html +13 -0
|
@@ -0,0 +1,992 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatabasePolicyEngine - v1.2 DB-backed policy engine with network scoping.
|
|
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
|
+
* 5. Evaluate SPENDING_LIMIT: classify amount into INSTANT/NOTIFY/DELAY/APPROVAL
|
|
27
|
+
*
|
|
28
|
+
* TOCTOU Prevention (evaluateAndReserve):
|
|
29
|
+
* Uses BEGIN IMMEDIATE to serialize concurrent policy evaluations.
|
|
30
|
+
* reserved_amount tracks pending amounts to prevent two requests from both passing
|
|
31
|
+
* under the same spending limit.
|
|
32
|
+
*
|
|
33
|
+
* @see docs/33-time-lock-approval-mechanism.md
|
|
34
|
+
* @see docs/71-policy-engine-network-extension-design.md
|
|
35
|
+
*/
|
|
36
|
+
import { eq, or, and, isNull, desc } from 'drizzle-orm';
|
|
37
|
+
import { policies } from '../infrastructure/database/schema.js';
|
|
38
|
+
/** Threshold for detecting "unlimited" approve amounts. */
|
|
39
|
+
const UNLIMITED_THRESHOLD = (2n ** 256n - 1n) / 2n;
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Tier order + maxTier helper (Phase 127)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
const TIER_ORDER = ['INSTANT', 'NOTIFY', 'DELAY', 'APPROVAL'];
|
|
44
|
+
function maxTier(a, b) {
|
|
45
|
+
const aIdx = TIER_ORDER.indexOf(a);
|
|
46
|
+
const bIdx = TIER_ORDER.indexOf(b);
|
|
47
|
+
return TIER_ORDER[Math.max(aIdx, bIdx)];
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// DatabasePolicyEngine
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* DB-backed policy engine with SPENDING_LIMIT 4-tier, WHITELIST, ALLOWED_NETWORKS,
|
|
54
|
+
* ALLOWED_TOKENS, CONTRACT_WHITELIST, METHOD_WHITELIST, APPROVED_SPENDERS,
|
|
55
|
+
* APPROVE_AMOUNT_LIMIT, and APPROVE_TIER_OVERRIDE evaluation.
|
|
56
|
+
*
|
|
57
|
+
* Network scoping: policies can target specific networks via the `network` column.
|
|
58
|
+
* 4-level override priority: wallet+network > wallet+null > global+network > global+null.
|
|
59
|
+
*
|
|
60
|
+
* Constructor takes a Drizzle DB instance typed with the full schema,
|
|
61
|
+
* and optionally a raw better-sqlite3 Database instance for BEGIN IMMEDIATE transactions.
|
|
62
|
+
*/
|
|
63
|
+
export class DatabasePolicyEngine {
|
|
64
|
+
db;
|
|
65
|
+
sqlite;
|
|
66
|
+
settingsService;
|
|
67
|
+
constructor(db, sqlite, settingsService) {
|
|
68
|
+
this.db = db;
|
|
69
|
+
this.sqlite = sqlite ?? null;
|
|
70
|
+
this.settingsService = settingsService ?? null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Evaluate a transaction against DB-stored policies.
|
|
74
|
+
*/
|
|
75
|
+
async evaluate(walletId, transaction) {
|
|
76
|
+
// Step 1: Load enabled policies (wallet-specific + global, with network filter)
|
|
77
|
+
const rows = await this.db
|
|
78
|
+
.select()
|
|
79
|
+
.from(policies)
|
|
80
|
+
.where(and(or(eq(policies.walletId, walletId), isNull(policies.walletId)), or(transaction.network ? eq(policies.network, transaction.network) : isNull(policies.network), isNull(policies.network)), eq(policies.enabled, true)))
|
|
81
|
+
.orderBy(desc(policies.priority))
|
|
82
|
+
.all();
|
|
83
|
+
// Step 2: No policies -> INSTANT passthrough
|
|
84
|
+
if (rows.length === 0) {
|
|
85
|
+
return { allowed: true, tier: 'INSTANT' };
|
|
86
|
+
}
|
|
87
|
+
// Step 3: Resolve overrides (4-level: wallet+network > wallet+null > global+network > global+null)
|
|
88
|
+
const resolved = this.resolveOverrides(rows, walletId, transaction.network);
|
|
89
|
+
// Step 4: Evaluate WHITELIST (deny-first)
|
|
90
|
+
const whitelistResult = this.evaluateWhitelist(resolved, transaction.toAddress);
|
|
91
|
+
if (whitelistResult !== null) {
|
|
92
|
+
return whitelistResult;
|
|
93
|
+
}
|
|
94
|
+
// Step 4a.5: Evaluate ALLOWED_NETWORKS (network whitelist, permissive default)
|
|
95
|
+
if (transaction.network) {
|
|
96
|
+
const allowedNetworksResult = this.evaluateAllowedNetworks(resolved, transaction.network);
|
|
97
|
+
if (allowedNetworksResult !== null) {
|
|
98
|
+
return allowedNetworksResult;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Step 4b: Evaluate ALLOWED_TOKENS (token transfer whitelist)
|
|
102
|
+
const allowedTokensResult = this.evaluateAllowedTokens(resolved, transaction);
|
|
103
|
+
if (allowedTokensResult !== null) {
|
|
104
|
+
return allowedTokensResult;
|
|
105
|
+
}
|
|
106
|
+
// Step 4c: Evaluate CONTRACT_WHITELIST (contract call whitelist)
|
|
107
|
+
const contractWhitelistResult = this.evaluateContractWhitelist(resolved, transaction);
|
|
108
|
+
if (contractWhitelistResult !== null) {
|
|
109
|
+
return contractWhitelistResult;
|
|
110
|
+
}
|
|
111
|
+
// Step 4d: Evaluate METHOD_WHITELIST (method-level restriction)
|
|
112
|
+
const methodWhitelistResult = this.evaluateMethodWhitelist(resolved, transaction);
|
|
113
|
+
if (methodWhitelistResult !== null) {
|
|
114
|
+
return methodWhitelistResult;
|
|
115
|
+
}
|
|
116
|
+
// Step 4e: Evaluate APPROVED_SPENDERS (approve spender whitelist)
|
|
117
|
+
const approvedSpendersResult = this.evaluateApprovedSpenders(resolved, transaction);
|
|
118
|
+
if (approvedSpendersResult !== null) {
|
|
119
|
+
return approvedSpendersResult;
|
|
120
|
+
}
|
|
121
|
+
// Step 4f: Evaluate APPROVE_AMOUNT_LIMIT (unlimited approve block + amount cap)
|
|
122
|
+
const approveAmountResult = this.evaluateApproveAmountLimit(resolved, transaction);
|
|
123
|
+
if (approveAmountResult !== null) {
|
|
124
|
+
return approveAmountResult;
|
|
125
|
+
}
|
|
126
|
+
// Step 4g: Evaluate APPROVE_TIER_OVERRIDE (forced tier for APPROVE transactions)
|
|
127
|
+
const approveTierResult = this.evaluateApproveTierOverride(resolved, transaction);
|
|
128
|
+
if (approveTierResult !== null) {
|
|
129
|
+
return approveTierResult; // FINAL result, skips SPENDING_LIMIT
|
|
130
|
+
}
|
|
131
|
+
// Step 5: Evaluate SPENDING_LIMIT (tier classification)
|
|
132
|
+
const spendingResult = this.evaluateSpendingLimit(resolved, transaction.amount);
|
|
133
|
+
if (spendingResult !== null) {
|
|
134
|
+
return spendingResult;
|
|
135
|
+
}
|
|
136
|
+
// Default: INSTANT passthrough (no applicable rules)
|
|
137
|
+
return { allowed: true, tier: 'INSTANT' };
|
|
138
|
+
}
|
|
139
|
+
// -------------------------------------------------------------------------
|
|
140
|
+
// Batch evaluation: evaluateBatch
|
|
141
|
+
// -------------------------------------------------------------------------
|
|
142
|
+
/**
|
|
143
|
+
* Evaluate a batch of instructions using 2-stage policy evaluation.
|
|
144
|
+
*
|
|
145
|
+
* Phase A: Evaluate each instruction individually against its applicable policies.
|
|
146
|
+
* All-or-Nothing: if any instruction is denied, entire batch is denied.
|
|
147
|
+
*
|
|
148
|
+
* Phase B: Sum native amounts (TRANSFER.amount) and evaluate
|
|
149
|
+
* aggregate against SPENDING_LIMIT. If batch contains APPROVE, apply
|
|
150
|
+
* APPROVE_TIER_OVERRIDE and take max(amount tier, approve tier).
|
|
151
|
+
*
|
|
152
|
+
* @param walletId - Wallet whose policies to evaluate
|
|
153
|
+
* @param instructions - Array of instruction parameters (same shape as TransactionParam)
|
|
154
|
+
* @returns PolicyEvaluation with final tier or denial with violation details
|
|
155
|
+
*/
|
|
156
|
+
async evaluateBatch(walletId, instructions, batchUsdAmount) {
|
|
157
|
+
// Step 1: Load policies with network filter
|
|
158
|
+
// All instructions in a BATCH share the same network
|
|
159
|
+
const resolvedNetwork = instructions[0]?.network;
|
|
160
|
+
const rows = await this.db
|
|
161
|
+
.select()
|
|
162
|
+
.from(policies)
|
|
163
|
+
.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)))
|
|
164
|
+
.orderBy(desc(policies.priority))
|
|
165
|
+
.all();
|
|
166
|
+
if (rows.length === 0) {
|
|
167
|
+
return { allowed: true, tier: 'INSTANT' };
|
|
168
|
+
}
|
|
169
|
+
const resolved = this.resolveOverrides(rows, walletId, resolvedNetwork);
|
|
170
|
+
// ALLOWED_NETWORKS evaluation before Phase A
|
|
171
|
+
if (resolvedNetwork) {
|
|
172
|
+
const allowedNetworksResult = this.evaluateAllowedNetworks(resolved, resolvedNetwork);
|
|
173
|
+
if (allowedNetworksResult !== null) {
|
|
174
|
+
return allowedNetworksResult;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Phase A: Evaluate each instruction individually
|
|
178
|
+
const violations = [];
|
|
179
|
+
for (let i = 0; i < instructions.length; i++) {
|
|
180
|
+
const instr = instructions[i];
|
|
181
|
+
const result = this.evaluateInstructionPolicies(resolved, instr);
|
|
182
|
+
if (result !== null && !result.allowed) {
|
|
183
|
+
violations.push({
|
|
184
|
+
index: i,
|
|
185
|
+
type: instr.type,
|
|
186
|
+
reason: result.reason ?? 'Policy violation',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// All-or-Nothing: 1 violation = entire batch denied
|
|
191
|
+
if (violations.length > 0) {
|
|
192
|
+
return {
|
|
193
|
+
allowed: false,
|
|
194
|
+
tier: 'INSTANT',
|
|
195
|
+
reason: `Batch policy violation: ${violations.length} instruction(s) denied. ` +
|
|
196
|
+
violations.map((v) => `[${v.index}] ${v.type}: ${v.reason}`).join('; '),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Phase B: Aggregate amount for SPENDING_LIMIT
|
|
200
|
+
let totalNativeAmount = 0n;
|
|
201
|
+
for (const instr of instructions) {
|
|
202
|
+
if (instr.type === 'TRANSFER') {
|
|
203
|
+
totalNativeAmount += BigInt(instr.amount);
|
|
204
|
+
}
|
|
205
|
+
// TOKEN_TRANSFER and APPROVE: 0 (no native amount)
|
|
206
|
+
// CONTRACT_CALL: Solana has no native value attachment in CPI, so 0
|
|
207
|
+
}
|
|
208
|
+
// Evaluate aggregate against SPENDING_LIMIT (Phase 127: pass batchUsdAmount for USD evaluation)
|
|
209
|
+
const amountTier = this.evaluateSpendingLimit(resolved, totalNativeAmount.toString(), batchUsdAmount);
|
|
210
|
+
let finalTier = amountTier ? amountTier.tier : 'INSTANT';
|
|
211
|
+
// If batch contains APPROVE, apply APPROVE_TIER_OVERRIDE
|
|
212
|
+
const hasApprove = instructions.some((i) => i.type === 'APPROVE');
|
|
213
|
+
if (hasApprove) {
|
|
214
|
+
// Get approve tier from APPROVE_TIER_OVERRIDE policy (or default APPROVAL)
|
|
215
|
+
const approveTierPolicy = resolved.find((p) => p.type === 'APPROVE_TIER_OVERRIDE');
|
|
216
|
+
let approveTier;
|
|
217
|
+
if (approveTierPolicy) {
|
|
218
|
+
const rules = JSON.parse(approveTierPolicy.rules);
|
|
219
|
+
approveTier = rules.tier;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
approveTier = 'APPROVAL';
|
|
223
|
+
}
|
|
224
|
+
// Final tier = max(amount tier, approve tier)
|
|
225
|
+
const tierOrder = ['INSTANT', 'NOTIFY', 'DELAY', 'APPROVAL'];
|
|
226
|
+
const amountIdx = tierOrder.indexOf(finalTier);
|
|
227
|
+
const approveIdx = tierOrder.indexOf(approveTier);
|
|
228
|
+
finalTier = tierOrder[Math.max(amountIdx, approveIdx)];
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
allowed: true,
|
|
232
|
+
tier: finalTier,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
// -------------------------------------------------------------------------
|
|
236
|
+
// Private: Per-instruction policy evaluation (Phase A helper)
|
|
237
|
+
// -------------------------------------------------------------------------
|
|
238
|
+
/**
|
|
239
|
+
* Evaluate applicable policies for a single instruction in a batch.
|
|
240
|
+
*
|
|
241
|
+
* Only evaluates type-specific policies:
|
|
242
|
+
* - TRANSFER: WHITELIST
|
|
243
|
+
* - TOKEN_TRANSFER: WHITELIST + ALLOWED_TOKENS
|
|
244
|
+
* - CONTRACT_CALL: CONTRACT_WHITELIST + METHOD_WHITELIST
|
|
245
|
+
* - APPROVE: APPROVED_SPENDERS + APPROVE_AMOUNT_LIMIT
|
|
246
|
+
*
|
|
247
|
+
* Does NOT evaluate SPENDING_LIMIT (that's Phase B aggregate) or
|
|
248
|
+
* APPROVE_TIER_OVERRIDE (that's Phase B).
|
|
249
|
+
*
|
|
250
|
+
* Returns null if all policies pass, PolicyEvaluation with allowed=false if denied.
|
|
251
|
+
*/
|
|
252
|
+
evaluateInstructionPolicies(resolved, instr) {
|
|
253
|
+
// WHITELIST applies to TRANSFER and TOKEN_TRANSFER
|
|
254
|
+
if (instr.type === 'TRANSFER' || instr.type === 'TOKEN_TRANSFER') {
|
|
255
|
+
const whitelistResult = this.evaluateWhitelist(resolved, instr.toAddress);
|
|
256
|
+
if (whitelistResult !== null)
|
|
257
|
+
return whitelistResult;
|
|
258
|
+
}
|
|
259
|
+
// ALLOWED_TOKENS applies to TOKEN_TRANSFER
|
|
260
|
+
if (instr.type === 'TOKEN_TRANSFER') {
|
|
261
|
+
const allowedTokensResult = this.evaluateAllowedTokens(resolved, instr);
|
|
262
|
+
if (allowedTokensResult !== null)
|
|
263
|
+
return allowedTokensResult;
|
|
264
|
+
}
|
|
265
|
+
// CONTRACT_WHITELIST applies to CONTRACT_CALL
|
|
266
|
+
if (instr.type === 'CONTRACT_CALL') {
|
|
267
|
+
const contractResult = this.evaluateContractWhitelist(resolved, instr);
|
|
268
|
+
if (contractResult !== null)
|
|
269
|
+
return contractResult;
|
|
270
|
+
const methodResult = this.evaluateMethodWhitelist(resolved, instr);
|
|
271
|
+
if (methodResult !== null)
|
|
272
|
+
return methodResult;
|
|
273
|
+
}
|
|
274
|
+
// APPROVED_SPENDERS + APPROVE_AMOUNT_LIMIT apply to APPROVE
|
|
275
|
+
if (instr.type === 'APPROVE') {
|
|
276
|
+
const spendersResult = this.evaluateApprovedSpenders(resolved, instr);
|
|
277
|
+
if (spendersResult !== null)
|
|
278
|
+
return spendersResult;
|
|
279
|
+
const amountResult = this.evaluateApproveAmountLimit(resolved, instr);
|
|
280
|
+
if (amountResult !== null)
|
|
281
|
+
return amountResult;
|
|
282
|
+
}
|
|
283
|
+
return null; // All applicable policies passed
|
|
284
|
+
}
|
|
285
|
+
// -------------------------------------------------------------------------
|
|
286
|
+
// TOCTOU Prevention: evaluateAndReserve
|
|
287
|
+
// -------------------------------------------------------------------------
|
|
288
|
+
/**
|
|
289
|
+
* Evaluate transaction and reserve amount atomically using BEGIN IMMEDIATE.
|
|
290
|
+
*
|
|
291
|
+
* This method:
|
|
292
|
+
* 1. Begins an IMMEDIATE transaction (exclusive write lock)
|
|
293
|
+
* 2. Loads policies (same as evaluate)
|
|
294
|
+
* 3. For SPENDING_LIMIT: computes current reserved total from PENDING/QUEUED txs
|
|
295
|
+
* 4. Adds current request amount to reserved total
|
|
296
|
+
* 5. Evaluates against limits with reserved total considered
|
|
297
|
+
* 6. If allowed: sets reserved_amount on the transaction row
|
|
298
|
+
* 7. Commits
|
|
299
|
+
*
|
|
300
|
+
* @param walletId - The wallet whose policies to evaluate
|
|
301
|
+
* @param transaction - Transaction details for evaluation
|
|
302
|
+
* @param txId - The transaction ID to update with reserved_amount
|
|
303
|
+
* @returns PolicyEvaluation result
|
|
304
|
+
* @throws Error if sqlite instance not provided in constructor
|
|
305
|
+
*/
|
|
306
|
+
evaluateAndReserve(walletId, transaction, txId, usdAmount) {
|
|
307
|
+
if (!this.sqlite) {
|
|
308
|
+
throw new Error('evaluateAndReserve requires raw sqlite instance in constructor');
|
|
309
|
+
}
|
|
310
|
+
const sqlite = this.sqlite;
|
|
311
|
+
// Use better-sqlite3 transaction().immediate() for BEGIN IMMEDIATE
|
|
312
|
+
const txn = sqlite.transaction(() => {
|
|
313
|
+
// Step 1: Load enabled policies via raw SQL with network filter (inside IMMEDIATE txn)
|
|
314
|
+
const policyRows = sqlite
|
|
315
|
+
.prepare(`SELECT id, wallet_id AS walletId, type, rules, priority, enabled, network
|
|
316
|
+
FROM policies
|
|
317
|
+
WHERE (wallet_id = ? OR wallet_id IS NULL)
|
|
318
|
+
AND (network = ? OR network IS NULL)
|
|
319
|
+
AND enabled = 1
|
|
320
|
+
ORDER BY priority DESC`)
|
|
321
|
+
.all(walletId, transaction.network ?? null);
|
|
322
|
+
// Step 2: No policies -> INSTANT passthrough
|
|
323
|
+
if (policyRows.length === 0) {
|
|
324
|
+
return { allowed: true, tier: 'INSTANT' };
|
|
325
|
+
}
|
|
326
|
+
// Step 3: Resolve overrides (4-level with network)
|
|
327
|
+
const resolved = this.resolveOverrides(policyRows, walletId, transaction.network);
|
|
328
|
+
// Step 4: Evaluate WHITELIST (deny-first)
|
|
329
|
+
const whitelistResult = this.evaluateWhitelist(resolved, transaction.toAddress);
|
|
330
|
+
if (whitelistResult !== null) {
|
|
331
|
+
return whitelistResult;
|
|
332
|
+
}
|
|
333
|
+
// Step 4a.5: Evaluate ALLOWED_NETWORKS (network whitelist, permissive default)
|
|
334
|
+
if (transaction.network) {
|
|
335
|
+
const allowedNetworksResult = this.evaluateAllowedNetworks(resolved, transaction.network);
|
|
336
|
+
if (allowedNetworksResult !== null) {
|
|
337
|
+
return allowedNetworksResult;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Step 4b: Evaluate ALLOWED_TOKENS (token transfer whitelist)
|
|
341
|
+
const allowedTokensResult = this.evaluateAllowedTokens(resolved, transaction);
|
|
342
|
+
if (allowedTokensResult !== null) {
|
|
343
|
+
return allowedTokensResult;
|
|
344
|
+
}
|
|
345
|
+
// Step 4c: Evaluate CONTRACT_WHITELIST (contract call whitelist)
|
|
346
|
+
const contractWhitelistResult = this.evaluateContractWhitelist(resolved, transaction);
|
|
347
|
+
if (contractWhitelistResult !== null) {
|
|
348
|
+
return contractWhitelistResult;
|
|
349
|
+
}
|
|
350
|
+
// Step 4d: Evaluate METHOD_WHITELIST (method-level restriction)
|
|
351
|
+
const methodWhitelistResult = this.evaluateMethodWhitelist(resolved, transaction);
|
|
352
|
+
if (methodWhitelistResult !== null) {
|
|
353
|
+
return methodWhitelistResult;
|
|
354
|
+
}
|
|
355
|
+
// Step 4e: Evaluate APPROVED_SPENDERS (approve spender whitelist)
|
|
356
|
+
const approvedSpendersResult = this.evaluateApprovedSpenders(resolved, transaction);
|
|
357
|
+
if (approvedSpendersResult !== null) {
|
|
358
|
+
return approvedSpendersResult;
|
|
359
|
+
}
|
|
360
|
+
// Step 4f: Evaluate APPROVE_AMOUNT_LIMIT (unlimited approve block + amount cap)
|
|
361
|
+
const approveAmountResult = this.evaluateApproveAmountLimit(resolved, transaction);
|
|
362
|
+
if (approveAmountResult !== null) {
|
|
363
|
+
return approveAmountResult;
|
|
364
|
+
}
|
|
365
|
+
// Step 4g: Evaluate APPROVE_TIER_OVERRIDE (forced tier for APPROVE transactions)
|
|
366
|
+
const approveTierResult = this.evaluateApproveTierOverride(resolved, transaction);
|
|
367
|
+
if (approveTierResult !== null) {
|
|
368
|
+
return approveTierResult; // FINAL result, skips SPENDING_LIMIT
|
|
369
|
+
}
|
|
370
|
+
// Step 5: Compute reserved total for SPENDING_LIMIT evaluation
|
|
371
|
+
const spendingPolicy = resolved.find((p) => p.type === 'SPENDING_LIMIT');
|
|
372
|
+
if (spendingPolicy) {
|
|
373
|
+
// Sum of reserved_amount for wallet's PENDING/QUEUED/SIGNED transactions
|
|
374
|
+
// SIGNED included for sign-only pipeline reservation (TOCTOU prevention)
|
|
375
|
+
const reservedRow = sqlite
|
|
376
|
+
.prepare(`SELECT COALESCE(SUM(CAST(reserved_amount AS INTEGER)), 0) AS total
|
|
377
|
+
FROM transactions
|
|
378
|
+
WHERE wallet_id = ?
|
|
379
|
+
AND status IN ('PENDING', 'QUEUED', 'SIGNED')
|
|
380
|
+
AND reserved_amount IS NOT NULL`)
|
|
381
|
+
.get(walletId);
|
|
382
|
+
const reservedTotal = BigInt(reservedRow.total);
|
|
383
|
+
const requestAmount = BigInt(transaction.amount);
|
|
384
|
+
const effectiveAmount = reservedTotal + requestAmount;
|
|
385
|
+
// Evaluate with effective amount (reserved + current) via unified evaluateSpendingLimit
|
|
386
|
+
const spendingResult = this.evaluateSpendingLimit(resolved, effectiveAmount.toString(), usdAmount);
|
|
387
|
+
// Step 6: Cumulative USD limit evaluation (daily/monthly rolling window)
|
|
388
|
+
if (usdAmount !== undefined && usdAmount > 0) {
|
|
389
|
+
const rules = JSON.parse(spendingPolicy.rules);
|
|
390
|
+
const hasCumulativeLimits = rules.daily_limit_usd !== undefined || rules.monthly_limit_usd !== undefined;
|
|
391
|
+
if (hasCumulativeLimits) {
|
|
392
|
+
const now = Math.floor(Date.now() / 1000);
|
|
393
|
+
let cumulativeTier = 'INSTANT';
|
|
394
|
+
let cumulativeReason;
|
|
395
|
+
let cumulativeWarning;
|
|
396
|
+
// 6a: Daily (24h rolling window)
|
|
397
|
+
if (rules.daily_limit_usd !== undefined) {
|
|
398
|
+
const windowStart = now - 86400; // 24 * 60 * 60
|
|
399
|
+
const spent = this.getCumulativeUsdSpent(sqlite, walletId, windowStart);
|
|
400
|
+
const totalWithCurrent = spent + usdAmount;
|
|
401
|
+
if (totalWithCurrent > rules.daily_limit_usd) {
|
|
402
|
+
cumulativeTier = 'APPROVAL';
|
|
403
|
+
cumulativeReason = 'cumulative_daily';
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
// 80% warning check
|
|
407
|
+
const ratio = totalWithCurrent / rules.daily_limit_usd;
|
|
408
|
+
if (ratio >= 0.8) {
|
|
409
|
+
cumulativeWarning = { type: 'daily', ratio, spent: totalWithCurrent, limit: rules.daily_limit_usd };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// 6b: Monthly (30-day rolling window) -- only if daily didn't already escalate
|
|
414
|
+
if (rules.monthly_limit_usd !== undefined && cumulativeReason === undefined) {
|
|
415
|
+
const windowStart = now - 2592000; // 30 * 24 * 60 * 60
|
|
416
|
+
const spent = this.getCumulativeUsdSpent(sqlite, walletId, windowStart);
|
|
417
|
+
const totalWithCurrent = spent + usdAmount;
|
|
418
|
+
if (totalWithCurrent > rules.monthly_limit_usd) {
|
|
419
|
+
cumulativeTier = 'APPROVAL';
|
|
420
|
+
cumulativeReason = 'cumulative_monthly';
|
|
421
|
+
}
|
|
422
|
+
else if (!cumulativeWarning) {
|
|
423
|
+
// 80% warning check (only if daily warning not already set)
|
|
424
|
+
const ratio = totalWithCurrent / rules.monthly_limit_usd;
|
|
425
|
+
if (ratio >= 0.8) {
|
|
426
|
+
cumulativeWarning = { type: 'monthly', ratio, spent: totalWithCurrent, limit: rules.monthly_limit_usd };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Step 7: Final tier = max(per-tx tier, cumulative tier)
|
|
431
|
+
const perTxTier = spendingResult?.tier ?? 'INSTANT';
|
|
432
|
+
const finalTier = maxTier(perTxTier, cumulativeTier);
|
|
433
|
+
// Determine approvalReason
|
|
434
|
+
let approvalReason;
|
|
435
|
+
if (finalTier === 'APPROVAL') {
|
|
436
|
+
approvalReason = cumulativeReason ?? 'per_tx';
|
|
437
|
+
}
|
|
438
|
+
// Record USD amounts + reserved_amount
|
|
439
|
+
sqlite
|
|
440
|
+
.prepare(`UPDATE transactions SET reserved_amount = ?, amount_usd = ?, reserved_amount_usd = ? WHERE id = ?`)
|
|
441
|
+
.run(transaction.amount, usdAmount, usdAmount, txId);
|
|
442
|
+
return {
|
|
443
|
+
allowed: true,
|
|
444
|
+
tier: finalTier,
|
|
445
|
+
...(spendingResult?.delaySeconds !== undefined && finalTier === 'DELAY' ? { delaySeconds: spendingResult.delaySeconds } : {}),
|
|
446
|
+
...(approvalReason ? { approvalReason } : {}),
|
|
447
|
+
...(cumulativeWarning ? { cumulativeWarning } : {}),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// No cumulative limits or usdAmount not available -- use per-tx result
|
|
452
|
+
// Set reserved_amount on the transaction row + USD amounts if available
|
|
453
|
+
if (usdAmount !== undefined) {
|
|
454
|
+
sqlite
|
|
455
|
+
.prepare(`UPDATE transactions SET reserved_amount = ?, amount_usd = ?, reserved_amount_usd = ? WHERE id = ?`)
|
|
456
|
+
.run(transaction.amount, usdAmount, usdAmount, txId);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
sqlite
|
|
460
|
+
.prepare(`UPDATE transactions SET reserved_amount = ? WHERE id = ?`)
|
|
461
|
+
.run(transaction.amount, txId);
|
|
462
|
+
}
|
|
463
|
+
return spendingResult ?? { allowed: true, tier: 'INSTANT' };
|
|
464
|
+
}
|
|
465
|
+
// No SPENDING_LIMIT -> INSTANT passthrough (whitelist already passed)
|
|
466
|
+
return { allowed: true, tier: 'INSTANT' };
|
|
467
|
+
});
|
|
468
|
+
// Execute with IMMEDIATE isolation
|
|
469
|
+
return txn.immediate();
|
|
470
|
+
}
|
|
471
|
+
// -------------------------------------------------------------------------
|
|
472
|
+
// releaseReservation
|
|
473
|
+
// -------------------------------------------------------------------------
|
|
474
|
+
/**
|
|
475
|
+
* Release a reserved amount on a transaction.
|
|
476
|
+
* Called when transaction reaches FAILED/CANCELLED/EXPIRED state.
|
|
477
|
+
*
|
|
478
|
+
* @param txId - The transaction ID to clear reservation for
|
|
479
|
+
*/
|
|
480
|
+
releaseReservation(txId) {
|
|
481
|
+
if (!this.sqlite) {
|
|
482
|
+
throw new Error('releaseReservation requires raw sqlite instance in constructor');
|
|
483
|
+
}
|
|
484
|
+
this.sqlite
|
|
485
|
+
.prepare('UPDATE transactions SET reserved_amount = NULL, reserved_amount_usd = NULL WHERE id = ?')
|
|
486
|
+
.run(txId);
|
|
487
|
+
}
|
|
488
|
+
// -------------------------------------------------------------------------
|
|
489
|
+
// Private: Cumulative USD spending aggregation
|
|
490
|
+
// -------------------------------------------------------------------------
|
|
491
|
+
/**
|
|
492
|
+
* Get cumulative USD spent by wallet within a time window.
|
|
493
|
+
* Includes both confirmed amounts (amount_usd) and pending reservations (reserved_amount_usd).
|
|
494
|
+
*
|
|
495
|
+
* CONFIRMED/SIGNED: counted via amount_usd (confirmed or about to be broadcasted).
|
|
496
|
+
* PENDING/QUEUED: counted via reserved_amount_usd (awaiting processing, not yet confirmed).
|
|
497
|
+
* Deduplication: SIGNED is in the first query only (amount_usd). PENDING/QUEUED in second only.
|
|
498
|
+
*/
|
|
499
|
+
getCumulativeUsdSpent(sqlite, walletId, windowStart) {
|
|
500
|
+
// 1. Confirmed transactions (CONFIRMED/SIGNED) amount_usd within window
|
|
501
|
+
const confirmedRow = sqlite
|
|
502
|
+
.prepare(`SELECT COALESCE(SUM(amount_usd), 0) AS total
|
|
503
|
+
FROM transactions
|
|
504
|
+
WHERE wallet_id = ? AND status IN ('CONFIRMED', 'SIGNED')
|
|
505
|
+
AND created_at >= ? AND amount_usd IS NOT NULL`)
|
|
506
|
+
.get(walletId, windowStart);
|
|
507
|
+
// 2. Pending transactions (PENDING/QUEUED) reserved_amount_usd (no window filter -- all pending count)
|
|
508
|
+
const pendingRow = sqlite
|
|
509
|
+
.prepare(`SELECT COALESCE(SUM(reserved_amount_usd), 0) AS total
|
|
510
|
+
FROM transactions
|
|
511
|
+
WHERE wallet_id = ? AND status IN ('PENDING', 'QUEUED')
|
|
512
|
+
AND reserved_amount_usd IS NOT NULL`)
|
|
513
|
+
.get(walletId);
|
|
514
|
+
return confirmedRow.total + pendingRow.total;
|
|
515
|
+
}
|
|
516
|
+
// -------------------------------------------------------------------------
|
|
517
|
+
// Private: Override resolution
|
|
518
|
+
// -------------------------------------------------------------------------
|
|
519
|
+
/**
|
|
520
|
+
* Resolve policy overrides with 4-level priority:
|
|
521
|
+
* 1. wallet-specific + network-specific (highest)
|
|
522
|
+
* 2. wallet-specific + all-networks
|
|
523
|
+
* 3. global + network-specific
|
|
524
|
+
* 4. global + all-networks (lowest)
|
|
525
|
+
*
|
|
526
|
+
* For each policy type, one policy is selected.
|
|
527
|
+
* Lower priority entries are inserted first, higher priority entries overwrite.
|
|
528
|
+
* Key: typeMap[row.type] (same as current -- no composite key needed, PLCY-D03).
|
|
529
|
+
*
|
|
530
|
+
* Backward compat: when all policies have network=NULL,
|
|
531
|
+
* phases 2+4 collapse into current 2-level (wallet > global) behavior.
|
|
532
|
+
*/
|
|
533
|
+
resolveOverrides(rows, walletId, resolvedNetwork) {
|
|
534
|
+
const typeMap = new Map();
|
|
535
|
+
// Phase 1: global + all-networks (4th priority, lowest)
|
|
536
|
+
for (const row of rows) {
|
|
537
|
+
if (row.walletId === null && row.network === null) {
|
|
538
|
+
typeMap.set(row.type, row);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Phase 2: global + network-specific (3rd priority)
|
|
542
|
+
if (resolvedNetwork) {
|
|
543
|
+
for (const row of rows) {
|
|
544
|
+
if (row.walletId === null && row.network === resolvedNetwork) {
|
|
545
|
+
typeMap.set(row.type, row);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Phase 3: wallet-specific + all-networks (2nd priority)
|
|
550
|
+
for (const row of rows) {
|
|
551
|
+
if (row.walletId === walletId && row.network === null) {
|
|
552
|
+
typeMap.set(row.type, row);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Phase 4: wallet-specific + network-specific (1st priority, highest)
|
|
556
|
+
if (resolvedNetwork) {
|
|
557
|
+
for (const row of rows) {
|
|
558
|
+
if (row.walletId === walletId && row.network === resolvedNetwork) {
|
|
559
|
+
typeMap.set(row.type, row);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return Array.from(typeMap.values());
|
|
564
|
+
}
|
|
565
|
+
// -------------------------------------------------------------------------
|
|
566
|
+
// Private: ALLOWED_NETWORKS evaluation
|
|
567
|
+
// -------------------------------------------------------------------------
|
|
568
|
+
/**
|
|
569
|
+
* Evaluate ALLOWED_NETWORKS policy.
|
|
570
|
+
*
|
|
571
|
+
* Logic:
|
|
572
|
+
* - Applies to ALL 5 transaction types (TRANSFER, TOKEN_TRANSFER, CONTRACT_CALL, APPROVE, BATCH)
|
|
573
|
+
* - If no ALLOWED_NETWORKS policy exists: return null (permissive default -- all networks allowed)
|
|
574
|
+
* - If policy exists: check if resolvedNetwork is in rules.networks[].network
|
|
575
|
+
* -> If found: return null (continue to next evaluation)
|
|
576
|
+
* -> If not found: deny with reason 'Network not in allowed list'
|
|
577
|
+
* - Comparison: case-insensitive (toLowerCase)
|
|
578
|
+
* - Tier: INSTANT (immediate denial)
|
|
579
|
+
*
|
|
580
|
+
* Returns PolicyEvaluation if denied, null if allowed (or no policy).
|
|
581
|
+
*/
|
|
582
|
+
evaluateAllowedNetworks(resolved, resolvedNetwork) {
|
|
583
|
+
const policy = resolved.find((p) => p.type === 'ALLOWED_NETWORKS');
|
|
584
|
+
// No ALLOWED_NETWORKS policy -> permissive default (all networks allowed)
|
|
585
|
+
if (!policy)
|
|
586
|
+
return null;
|
|
587
|
+
const rules = JSON.parse(policy.rules);
|
|
588
|
+
// Case-insensitive comparison
|
|
589
|
+
const isAllowed = rules.networks.some((n) => n.network.toLowerCase() === resolvedNetwork.toLowerCase());
|
|
590
|
+
if (!isAllowed) {
|
|
591
|
+
const allowedList = rules.networks.map((n) => n.network).join(', ');
|
|
592
|
+
return {
|
|
593
|
+
allowed: false,
|
|
594
|
+
tier: 'INSTANT',
|
|
595
|
+
reason: `Network '${resolvedNetwork}' not in allowed networks list. Allowed: ${allowedList}`,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
return null; // Network allowed, continue evaluation
|
|
599
|
+
}
|
|
600
|
+
// -------------------------------------------------------------------------
|
|
601
|
+
// Private: WHITELIST evaluation
|
|
602
|
+
// -------------------------------------------------------------------------
|
|
603
|
+
/**
|
|
604
|
+
* Evaluate WHITELIST policy.
|
|
605
|
+
* Returns PolicyEvaluation if denied, null if allowed (or no whitelist).
|
|
606
|
+
*/
|
|
607
|
+
evaluateWhitelist(resolved, toAddress) {
|
|
608
|
+
const whitelist = resolved.find((p) => p.type === 'WHITELIST');
|
|
609
|
+
if (!whitelist)
|
|
610
|
+
return null;
|
|
611
|
+
const rules = JSON.parse(whitelist.rules);
|
|
612
|
+
// Empty allowed_addresses = whitelist inactive
|
|
613
|
+
if (!rules.allowed_addresses || rules.allowed_addresses.length === 0) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
// Case-insensitive comparison (EVM compat)
|
|
617
|
+
const normalizedTo = toAddress.toLowerCase();
|
|
618
|
+
const isWhitelisted = rules.allowed_addresses.some((addr) => addr.toLowerCase() === normalizedTo);
|
|
619
|
+
if (!isWhitelisted) {
|
|
620
|
+
return {
|
|
621
|
+
allowed: false,
|
|
622
|
+
tier: 'INSTANT',
|
|
623
|
+
reason: `Address ${toAddress} not in whitelist`,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
// -------------------------------------------------------------------------
|
|
629
|
+
// Private: ALLOWED_TOKENS evaluation
|
|
630
|
+
// -------------------------------------------------------------------------
|
|
631
|
+
/**
|
|
632
|
+
* Evaluate ALLOWED_TOKENS policy.
|
|
633
|
+
*
|
|
634
|
+
* Logic:
|
|
635
|
+
* - Only applies to TOKEN_TRANSFER transaction type
|
|
636
|
+
* - If transaction type is TOKEN_TRANSFER and no ALLOWED_TOKENS policy exists:
|
|
637
|
+
* -> deny with reason 'Token transfer not allowed: no ALLOWED_TOKENS policy configured'
|
|
638
|
+
* - If ALLOWED_TOKENS policy exists, check if transaction's token address is in rules.tokens[].address:
|
|
639
|
+
* -> If found: return null (continue to next evaluation)
|
|
640
|
+
* -> If not found: deny with reason 'Token not in allowed list: {tokenAddress}'
|
|
641
|
+
* - For non-TOKEN_TRANSFER types: return null (not applicable)
|
|
642
|
+
*
|
|
643
|
+
* Returns PolicyEvaluation if denied, null if allowed (or not applicable).
|
|
644
|
+
*/
|
|
645
|
+
evaluateAllowedTokens(resolved, transaction) {
|
|
646
|
+
// Only evaluate for TOKEN_TRANSFER transactions
|
|
647
|
+
if (transaction.type !== 'TOKEN_TRANSFER')
|
|
648
|
+
return null;
|
|
649
|
+
const allowedTokensPolicy = resolved.find((p) => p.type === 'ALLOWED_TOKENS');
|
|
650
|
+
// No ALLOWED_TOKENS policy -> check toggle, then deny (default deny)
|
|
651
|
+
if (!allowedTokensPolicy) {
|
|
652
|
+
if (this.settingsService?.get('policy.default_deny_tokens') === 'false') {
|
|
653
|
+
return null; // default-allow mode: skip token whitelist check
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
allowed: false,
|
|
657
|
+
tier: 'INSTANT',
|
|
658
|
+
reason: 'Token transfer not allowed: no ALLOWED_TOKENS policy configured',
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
// Parse rules.tokens array
|
|
662
|
+
const rules = JSON.parse(allowedTokensPolicy.rules);
|
|
663
|
+
const tokenAddress = transaction.tokenAddress;
|
|
664
|
+
if (!tokenAddress) {
|
|
665
|
+
return {
|
|
666
|
+
allowed: false,
|
|
667
|
+
tier: 'INSTANT',
|
|
668
|
+
reason: 'Token transfer missing token address',
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
// Check if token is in allowed list (case-insensitive comparison for EVM addresses)
|
|
672
|
+
const isAllowed = rules.tokens.some((t) => t.address.toLowerCase() === tokenAddress.toLowerCase());
|
|
673
|
+
if (!isAllowed) {
|
|
674
|
+
return {
|
|
675
|
+
allowed: false,
|
|
676
|
+
tier: 'INSTANT',
|
|
677
|
+
reason: `Token not in allowed list: ${tokenAddress}`,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return null; // Token is allowed, continue evaluation
|
|
681
|
+
}
|
|
682
|
+
// -------------------------------------------------------------------------
|
|
683
|
+
// Private: CONTRACT_WHITELIST evaluation
|
|
684
|
+
// -------------------------------------------------------------------------
|
|
685
|
+
/**
|
|
686
|
+
* Evaluate CONTRACT_WHITELIST policy.
|
|
687
|
+
*
|
|
688
|
+
* Logic:
|
|
689
|
+
* - Only applies to CONTRACT_CALL transaction type
|
|
690
|
+
* - If transaction type is CONTRACT_CALL and no CONTRACT_WHITELIST policy exists:
|
|
691
|
+
* -> deny with reason 'Contract calls disabled: no CONTRACT_WHITELIST policy configured'
|
|
692
|
+
* - If CONTRACT_WHITELIST policy exists, check if contract address is in rules.contracts[].address:
|
|
693
|
+
* -> If found: return null (continue to next evaluation)
|
|
694
|
+
* -> If not found: deny with reason 'Contract not whitelisted: {address}'
|
|
695
|
+
* - For non-CONTRACT_CALL types: return null (not applicable)
|
|
696
|
+
*
|
|
697
|
+
* Returns PolicyEvaluation if denied, null if allowed (or not applicable).
|
|
698
|
+
*/
|
|
699
|
+
evaluateContractWhitelist(resolved, transaction) {
|
|
700
|
+
// Only evaluate for CONTRACT_CALL transactions
|
|
701
|
+
if (transaction.type !== 'CONTRACT_CALL')
|
|
702
|
+
return null;
|
|
703
|
+
const contractWhitelistPolicy = resolved.find((p) => p.type === 'CONTRACT_WHITELIST');
|
|
704
|
+
// No CONTRACT_WHITELIST policy -> check toggle, then deny (default deny)
|
|
705
|
+
if (!contractWhitelistPolicy) {
|
|
706
|
+
if (this.settingsService?.get('policy.default_deny_contracts') === 'false') {
|
|
707
|
+
return null; // default-allow mode: skip contract whitelist check
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
allowed: false,
|
|
711
|
+
tier: 'INSTANT',
|
|
712
|
+
reason: 'Contract calls disabled: no CONTRACT_WHITELIST policy configured',
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
// Parse rules.contracts array
|
|
716
|
+
const rules = JSON.parse(contractWhitelistPolicy.rules);
|
|
717
|
+
const contractAddress = transaction.contractAddress ?? transaction.toAddress;
|
|
718
|
+
// Check if contract is in whitelist (case-insensitive comparison for EVM addresses)
|
|
719
|
+
const isWhitelisted = rules.contracts.some((c) => c.address.toLowerCase() === contractAddress.toLowerCase());
|
|
720
|
+
if (!isWhitelisted) {
|
|
721
|
+
return {
|
|
722
|
+
allowed: false,
|
|
723
|
+
tier: 'INSTANT',
|
|
724
|
+
reason: `Contract not whitelisted: ${contractAddress}`,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return null; // Contract is whitelisted, continue evaluation
|
|
728
|
+
}
|
|
729
|
+
// -------------------------------------------------------------------------
|
|
730
|
+
// Private: METHOD_WHITELIST evaluation
|
|
731
|
+
// -------------------------------------------------------------------------
|
|
732
|
+
/**
|
|
733
|
+
* Evaluate METHOD_WHITELIST policy.
|
|
734
|
+
*
|
|
735
|
+
* Logic:
|
|
736
|
+
* - Only applies to CONTRACT_CALL transaction type
|
|
737
|
+
* - If no METHOD_WHITELIST policy exists: return null (method restriction is optional)
|
|
738
|
+
* - If METHOD_WHITELIST policy exists, find matching entry for transaction's contract address:
|
|
739
|
+
* -> If no entry for this contract: return null (no method restriction for this contract)
|
|
740
|
+
* -> If entry found, check if transaction's selector is in entry.selectors:
|
|
741
|
+
* -> If found: return null (method allowed)
|
|
742
|
+
* -> If not found: deny with reason 'Method not whitelisted: {selector} on contract {address}'
|
|
743
|
+
*
|
|
744
|
+
* Returns PolicyEvaluation if denied, null if allowed (or not applicable).
|
|
745
|
+
*/
|
|
746
|
+
evaluateMethodWhitelist(resolved, transaction) {
|
|
747
|
+
// Only evaluate for CONTRACT_CALL transactions
|
|
748
|
+
if (transaction.type !== 'CONTRACT_CALL')
|
|
749
|
+
return null;
|
|
750
|
+
const methodWhitelistPolicy = resolved.find((p) => p.type === 'METHOD_WHITELIST');
|
|
751
|
+
// No METHOD_WHITELIST policy -> no method restriction (optional policy)
|
|
752
|
+
if (!methodWhitelistPolicy)
|
|
753
|
+
return null;
|
|
754
|
+
// Parse rules.methods array
|
|
755
|
+
const rules = JSON.parse(methodWhitelistPolicy.rules);
|
|
756
|
+
const contractAddress = transaction.contractAddress ?? transaction.toAddress;
|
|
757
|
+
const selector = transaction.selector;
|
|
758
|
+
// Find matching entry for this contract (case-insensitive)
|
|
759
|
+
const entry = rules.methods.find((m) => m.contractAddress.toLowerCase() === contractAddress.toLowerCase());
|
|
760
|
+
// No entry for this contract -> no method restriction for this specific contract
|
|
761
|
+
if (!entry)
|
|
762
|
+
return null;
|
|
763
|
+
// Check if selector is in the allowed list (case-insensitive)
|
|
764
|
+
if (!selector) {
|
|
765
|
+
return {
|
|
766
|
+
allowed: false,
|
|
767
|
+
tier: 'INSTANT',
|
|
768
|
+
reason: `Method not whitelisted: missing selector on contract ${contractAddress}`,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
const isAllowed = entry.selectors.some((s) => s.toLowerCase() === selector.toLowerCase());
|
|
772
|
+
if (!isAllowed) {
|
|
773
|
+
return {
|
|
774
|
+
allowed: false,
|
|
775
|
+
tier: 'INSTANT',
|
|
776
|
+
reason: `Method not whitelisted: ${selector} on contract ${contractAddress}`,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
return null; // Method is whitelisted, continue evaluation
|
|
780
|
+
}
|
|
781
|
+
// -------------------------------------------------------------------------
|
|
782
|
+
// Private: APPROVED_SPENDERS evaluation
|
|
783
|
+
// -------------------------------------------------------------------------
|
|
784
|
+
/**
|
|
785
|
+
* Evaluate APPROVED_SPENDERS policy.
|
|
786
|
+
*
|
|
787
|
+
* Logic:
|
|
788
|
+
* - Only applies to APPROVE transaction type
|
|
789
|
+
* - If transaction type is APPROVE and no APPROVED_SPENDERS policy exists:
|
|
790
|
+
* -> deny with reason 'Token approvals disabled: no APPROVED_SPENDERS policy configured'
|
|
791
|
+
* - If APPROVED_SPENDERS policy exists, check if transaction's spenderAddress is in rules.spenders[]:
|
|
792
|
+
* -> If found: return null (continue evaluation)
|
|
793
|
+
* -> If not found: deny with reason 'Spender not in approved list: {address}'
|
|
794
|
+
* - Case-insensitive comparison (EVM addresses)
|
|
795
|
+
*
|
|
796
|
+
* Returns PolicyEvaluation if denied, null if allowed (or not applicable).
|
|
797
|
+
*/
|
|
798
|
+
evaluateApprovedSpenders(resolved, transaction) {
|
|
799
|
+
// Only evaluate for APPROVE transactions
|
|
800
|
+
if (transaction.type !== 'APPROVE')
|
|
801
|
+
return null;
|
|
802
|
+
const approvedSpendersPolicy = resolved.find((p) => p.type === 'APPROVED_SPENDERS');
|
|
803
|
+
// No APPROVED_SPENDERS policy -> check toggle, then deny (default deny)
|
|
804
|
+
if (!approvedSpendersPolicy) {
|
|
805
|
+
if (this.settingsService?.get('policy.default_deny_spenders') === 'false') {
|
|
806
|
+
return null; // default-allow mode: skip approved spenders check
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
allowed: false,
|
|
810
|
+
tier: 'INSTANT',
|
|
811
|
+
reason: 'Token approvals disabled: no APPROVED_SPENDERS policy configured',
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
// Parse rules.spenders array
|
|
815
|
+
const rules = JSON.parse(approvedSpendersPolicy.rules);
|
|
816
|
+
const spenderAddress = transaction.spenderAddress;
|
|
817
|
+
if (!spenderAddress) {
|
|
818
|
+
return {
|
|
819
|
+
allowed: false,
|
|
820
|
+
tier: 'INSTANT',
|
|
821
|
+
reason: 'Approve missing spender address',
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
// Check if spender is in approved list (case-insensitive for EVM addresses)
|
|
825
|
+
const isApproved = rules.spenders.some((s) => s.address.toLowerCase() === spenderAddress.toLowerCase());
|
|
826
|
+
if (!isApproved) {
|
|
827
|
+
return {
|
|
828
|
+
allowed: false,
|
|
829
|
+
tier: 'INSTANT',
|
|
830
|
+
reason: `Spender not in approved list: ${spenderAddress}`,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
return null; // Spender is approved, continue evaluation
|
|
834
|
+
}
|
|
835
|
+
// -------------------------------------------------------------------------
|
|
836
|
+
// Private: APPROVE_AMOUNT_LIMIT evaluation
|
|
837
|
+
// -------------------------------------------------------------------------
|
|
838
|
+
/**
|
|
839
|
+
* Evaluate APPROVE_AMOUNT_LIMIT policy.
|
|
840
|
+
*
|
|
841
|
+
* Logic:
|
|
842
|
+
* - Only applies to APPROVE transaction type
|
|
843
|
+
* - Checks for unlimited approve amounts (>= UNLIMITED_THRESHOLD)
|
|
844
|
+
* - Checks for amount cap (maxAmount)
|
|
845
|
+
* - If no policy exists: default block_unlimited=true (block unlimited approvals)
|
|
846
|
+
*
|
|
847
|
+
* Returns PolicyEvaluation if denied, null if allowed (or not applicable).
|
|
848
|
+
*/
|
|
849
|
+
evaluateApproveAmountLimit(resolved, transaction) {
|
|
850
|
+
// Only evaluate for APPROVE transactions
|
|
851
|
+
if (transaction.type !== 'APPROVE')
|
|
852
|
+
return null;
|
|
853
|
+
const approveAmount = transaction.approveAmount;
|
|
854
|
+
if (!approveAmount)
|
|
855
|
+
return null; // No amount to check
|
|
856
|
+
const amount = BigInt(approveAmount);
|
|
857
|
+
const approveAmountPolicy = resolved.find((p) => p.type === 'APPROVE_AMOUNT_LIMIT');
|
|
858
|
+
if (!approveAmountPolicy) {
|
|
859
|
+
// No policy: default block unlimited
|
|
860
|
+
if (amount >= UNLIMITED_THRESHOLD) {
|
|
861
|
+
return {
|
|
862
|
+
allowed: false,
|
|
863
|
+
tier: 'INSTANT',
|
|
864
|
+
reason: 'Unlimited token approval is blocked',
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
// Parse rules
|
|
870
|
+
const rules = JSON.parse(approveAmountPolicy.rules);
|
|
871
|
+
// Check unlimited block
|
|
872
|
+
if (rules.blockUnlimited && amount >= UNLIMITED_THRESHOLD) {
|
|
873
|
+
return {
|
|
874
|
+
allowed: false,
|
|
875
|
+
tier: 'INSTANT',
|
|
876
|
+
reason: 'Unlimited token approval is blocked',
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
// Check maxAmount cap
|
|
880
|
+
if (rules.maxAmount && amount > BigInt(rules.maxAmount)) {
|
|
881
|
+
return {
|
|
882
|
+
allowed: false,
|
|
883
|
+
tier: 'INSTANT',
|
|
884
|
+
reason: 'Approve amount exceeds limit',
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return null; // Amount within limits, continue evaluation
|
|
888
|
+
}
|
|
889
|
+
// -------------------------------------------------------------------------
|
|
890
|
+
// Private: APPROVE_TIER_OVERRIDE evaluation
|
|
891
|
+
// -------------------------------------------------------------------------
|
|
892
|
+
/**
|
|
893
|
+
* Evaluate APPROVE_TIER_OVERRIDE policy.
|
|
894
|
+
*
|
|
895
|
+
* Logic:
|
|
896
|
+
* - Only applies to APPROVE transaction type
|
|
897
|
+
* - If no APPROVE_TIER_OVERRIDE policy exists: return APPROVAL tier (default: Owner approval required)
|
|
898
|
+
* - If policy exists: return configured tier
|
|
899
|
+
* - This is a FINAL result -- skips SPENDING_LIMIT entirely for APPROVE transactions
|
|
900
|
+
*
|
|
901
|
+
* Returns PolicyEvaluation (always returns result for APPROVE type, null for others).
|
|
902
|
+
*/
|
|
903
|
+
evaluateApproveTierOverride(resolved, transaction) {
|
|
904
|
+
// Only evaluate for APPROVE transactions
|
|
905
|
+
if (transaction.type !== 'APPROVE')
|
|
906
|
+
return null;
|
|
907
|
+
const approveTierPolicy = resolved.find((p) => p.type === 'APPROVE_TIER_OVERRIDE');
|
|
908
|
+
if (!approveTierPolicy) {
|
|
909
|
+
// Default: APPROVAL tier (Owner approval required for approvals)
|
|
910
|
+
return { allowed: true, tier: 'APPROVAL' };
|
|
911
|
+
}
|
|
912
|
+
// Parse rules
|
|
913
|
+
const rules = JSON.parse(approveTierPolicy.rules);
|
|
914
|
+
return { allowed: true, tier: rules.tier };
|
|
915
|
+
}
|
|
916
|
+
// -------------------------------------------------------------------------
|
|
917
|
+
// Private: SPENDING_LIMIT evaluation
|
|
918
|
+
// -------------------------------------------------------------------------
|
|
919
|
+
/**
|
|
920
|
+
* Evaluate SPENDING_LIMIT policy.
|
|
921
|
+
* Returns PolicyEvaluation with tier classification, or null if no spending limit.
|
|
922
|
+
*
|
|
923
|
+
* Phase 127: usdAmount가 전달되고 rules에 USD 임계값이 설정되어 있으면,
|
|
924
|
+
* 네이티브 티어와 USD 티어 중 더 보수적인(높은) 티어를 채택한다.
|
|
925
|
+
*/
|
|
926
|
+
evaluateSpendingLimit(resolved, amount, usdAmount) {
|
|
927
|
+
const spending = resolved.find((p) => p.type === 'SPENDING_LIMIT');
|
|
928
|
+
if (!spending)
|
|
929
|
+
return null;
|
|
930
|
+
const rules = JSON.parse(spending.rules);
|
|
931
|
+
// 1. 네이티브 기준 티어 (기존 로직)
|
|
932
|
+
const nativeTier = this.evaluateNativeTier(BigInt(amount), rules);
|
|
933
|
+
// 2. USD 기준 티어 (Phase 127)
|
|
934
|
+
let finalTier = nativeTier;
|
|
935
|
+
if (usdAmount !== undefined && usdAmount > 0 && this.hasUsdThresholds(rules)) {
|
|
936
|
+
const usdTier = this.evaluateUsdTier(usdAmount, rules);
|
|
937
|
+
finalTier = maxTier(nativeTier, usdTier);
|
|
938
|
+
}
|
|
939
|
+
// delaySeconds는 최종 tier가 DELAY일 때만 포함
|
|
940
|
+
const delaySeconds = finalTier === 'DELAY' ? rules.delay_seconds : undefined;
|
|
941
|
+
return {
|
|
942
|
+
allowed: true,
|
|
943
|
+
tier: finalTier,
|
|
944
|
+
...(delaySeconds !== undefined ? { delaySeconds } : {}),
|
|
945
|
+
...(finalTier === 'APPROVAL' ? { approvalReason: 'per_tx' } : {}),
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Evaluate native amount tier (extracted from evaluateSpendingLimit).
|
|
950
|
+
*/
|
|
951
|
+
evaluateNativeTier(amountBig, rules) {
|
|
952
|
+
const instantMax = BigInt(rules.instant_max);
|
|
953
|
+
const notifyMax = BigInt(rules.notify_max);
|
|
954
|
+
const delayMax = BigInt(rules.delay_max);
|
|
955
|
+
if (amountBig <= instantMax) {
|
|
956
|
+
return 'INSTANT';
|
|
957
|
+
}
|
|
958
|
+
else if (amountBig <= notifyMax) {
|
|
959
|
+
return 'NOTIFY';
|
|
960
|
+
}
|
|
961
|
+
else if (amountBig <= delayMax) {
|
|
962
|
+
return 'DELAY';
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
return 'APPROVAL';
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Check if rules have any USD thresholds configured.
|
|
970
|
+
*/
|
|
971
|
+
hasUsdThresholds(rules) {
|
|
972
|
+
return rules.instant_max_usd !== undefined
|
|
973
|
+
|| rules.notify_max_usd !== undefined
|
|
974
|
+
|| rules.delay_max_usd !== undefined;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Evaluate USD amount tier.
|
|
978
|
+
*/
|
|
979
|
+
evaluateUsdTier(usdAmount, rules) {
|
|
980
|
+
if (rules.instant_max_usd !== undefined && usdAmount <= rules.instant_max_usd) {
|
|
981
|
+
return 'INSTANT';
|
|
982
|
+
}
|
|
983
|
+
if (rules.notify_max_usd !== undefined && usdAmount <= rules.notify_max_usd) {
|
|
984
|
+
return 'NOTIFY';
|
|
985
|
+
}
|
|
986
|
+
if (rules.delay_max_usd !== undefined && usdAmount <= rules.delay_max_usd) {
|
|
987
|
+
return 'DELAY';
|
|
988
|
+
}
|
|
989
|
+
return 'APPROVAL';
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
//# sourceMappingURL=database-policy-engine.js.map
|