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