tapimo 0.12.0 → 0.14.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/dist/App.d.ts +11333 -9563
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +39 -3
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +2 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +17 -0
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.js +2 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +1064 -924
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +80 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +54 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +63 -15
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +2 -1
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +92 -3
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +69 -7
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +7 -7
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +151 -151
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/Zones.d.ts +28 -28
- package/dist/apps/Zones.js +7 -7
- package/dist/apps/data/App.d.ts +2909 -2523
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +26 -21
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +8 -8
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
- package/dist/apps/data/ZoneSelection.js +1 -1
- package/dist/apps/data/ZoneSelection.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +588 -584
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +1561 -1239
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +22 -22
- package/dist/apps/data/routes/balances.d.ts +26 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +43 -16
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +44 -44
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +21 -11
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +3 -3
- package/dist/apps/data/routes/earn.d.ts +201 -25
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +434 -75
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +160 -145
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +180 -114
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +18 -19
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +155 -55
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +28 -10
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +13 -13
- package/dist/apps/data/routes/tokens.d.ts +5 -5
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +51 -20
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +7 -2
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +37 -17
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +13 -13
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +18 -12
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +31 -31
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +12 -7
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +8 -8
- package/dist/apps/data/routes/webhooks.d.ts +816 -113
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +47 -5
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/data/routes/zones.js +1 -1
- package/dist/apps/funding/App.d.ts +466 -44
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +1 -1
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +17 -12
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +650 -57
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
- package/dist/apps/funding/routes/transfers.js +238 -24
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +5120 -4152
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +17 -1
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +632 -81
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +236 -4
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +76 -76
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invitations.d.ts +24 -24
- package/dist/apps/management/routes/invite-links.d.ts +4 -4
- package/dist/apps/management/routes/members.d.ts +16 -16
- package/dist/apps/management/routes/orgs.d.ts +20 -20
- package/dist/apps/management/routes/projects.d.ts +4 -4
- package/dist/apps/management/routes/usage.d.ts +8 -8
- package/dist/apps/management/routes/verified-token-requests.d.ts +17 -17
- package/dist/apps/management/routes/webhooks.d.ts +675 -25
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +21 -21
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +115 -23
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +27 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +34 -1
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +11 -5
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +21 -3
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingCatalog.d.ts +4 -2
- package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
- package/dist/db/tables/fundingCatalog.js +5 -1
- package/dist/db/tables/fundingCatalog.js.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
- package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
- package/dist/db/tables/fundingTransferTransactions.js +44 -3
- package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
- package/dist/db/tables/fundingTransfers.d.ts +17 -0
- package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
- package/dist/db/tables/fundingTransfers.js +21 -0
- package/dist/db/tables/fundingTransfers.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +24 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +27 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -1
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +26 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +146 -13
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +9 -6
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +9 -0
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/internal/Auth.d.ts +3 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -3
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +3 -3
- package/dist/internal/EarnVaults.js +5 -5
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/Fetch.d.ts +23 -0
- package/dist/internal/Fetch.d.ts.map +1 -0
- package/dist/internal/Fetch.js +26 -0
- package/dist/internal/Fetch.js.map +1 -0
- package/dist/internal/Log.d.ts +4 -2
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +16 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +5 -3
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +13 -3
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +6 -3
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +8 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +16 -3
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +82 -16
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +7 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Timeout.d.ts +36 -0
- package/dist/internal/Timeout.d.ts.map +1 -0
- package/dist/internal/Timeout.js +37 -0
- package/dist/internal/Timeout.js.map +1 -0
- package/dist/internal/Timing.d.ts +9 -0
- package/dist/internal/Timing.d.ts.map +1 -1
- package/dist/internal/Timing.js +7 -0
- package/dist/internal/Timing.js.map +1 -1
- package/dist/internal/Viem.d.ts +16 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +3 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/funding/Action.d.ts +9 -0
- package/dist/internal/funding/Action.d.ts.map +1 -1
- package/dist/internal/funding/Action.js +16 -8
- package/dist/internal/funding/Action.js.map +1 -1
- package/dist/internal/funding/Catalog.d.ts +9 -2
- package/dist/internal/funding/Catalog.d.ts.map +1 -1
- package/dist/internal/funding/Catalog.js +19 -8
- package/dist/internal/funding/Catalog.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
- package/dist/internal/funding/DepositWebhook.js +2 -1
- package/dist/internal/funding/DepositWebhook.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +2 -10
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +108 -13
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +34 -8
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
- package/dist/internal/funding/SourceObservation.js +5 -7
- package/dist/internal/funding/SourceObservation.js.map +1 -1
- package/dist/internal/funding/Transfer.d.ts +114 -18
- package/dist/internal/funding/Transfer.d.ts.map +1 -1
- package/dist/internal/funding/Transfer.js +185 -55
- package/dist/internal/funding/Transfer.js.map +1 -1
- package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
- package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
- package/dist/internal/funding/TransferReconciliation.js +192 -0
- package/dist/internal/funding/TransferReconciliation.js.map +1 -0
- package/dist/internal/funding/TransferWebhook.d.ts +21 -0
- package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
- package/dist/internal/funding/TransferWebhook.js +43 -0
- package/dist/internal/funding/TransferWebhook.js.map +1 -0
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +3 -12
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +127 -9
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
- package/dist/internal/funding/providers/stargate.js +548 -112
- package/dist/internal/funding/providers/stargate.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +2 -2
- package/src/App.test.ts +100 -0
- package/src/App.ts +43 -3
- package/src/Client.test-d.ts +129 -2
- package/src/Scope.test.ts +20 -0
- package/src/Scope.ts +16 -0
- package/src/Webhooks.ts +2 -2
- package/src/admin/apps/api-keys.test.ts +75 -0
- package/src/admin/apps/api-keys.ts +64 -0
- package/src/admin/apps/earn-vaults.test.ts +1 -1
- package/src/admin/apps/earn-vaults.ts +2 -1
- package/src/admin/apps/organizations.test.ts +53 -1
- package/src/admin/apps/organizations.ts +90 -5
- package/src/admin/ui/src/lib/api-keys.ts +19 -0
- package/src/admin/ui/src/lib/organizations.test.ts +112 -1
- package/src/admin/ui/src/lib/organizations.ts +55 -0
- package/src/admin/ui/src/routes/api-keys.tsx +89 -2
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
- package/src/admin/ui.generated.ts +189 -151
- package/src/apps/Zones.test.ts +7 -7
- package/src/apps/Zones.ts +7 -7
- package/src/apps/composition.test.ts +2 -1
- package/src/apps/data/App.test.ts +41 -0
- package/src/apps/data/App.ts +30 -21
- package/src/apps/data/ZoneSelection.test.ts +2 -2
- package/src/apps/data/ZoneSelection.ts +8 -8
- package/src/apps/data/routes/activities.test.ts +1526 -12
- package/src/apps/data/routes/activities.ts +1774 -1356
- package/src/apps/data/routes/balances.ts +133 -97
- package/src/apps/data/routes/blocks.test.ts +533 -0
- package/src/apps/data/routes/blocks.ts +35 -16
- package/src/apps/data/routes/earn.test.ts +373 -16
- package/src/apps/data/routes/earn.ts +548 -79
- package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
- package/src/apps/data/routes/exchanges.test.ts +740 -15
- package/src/apps/data/routes/exchanges.ts +954 -753
- package/src/apps/data/routes/fee-amm.ts +17 -18
- package/src/apps/data/routes/receipts.ts +47 -16
- package/src/apps/data/routes/tokens.test.ts +578 -0
- package/src/apps/data/routes/tokens.ts +283 -225
- package/src/apps/data/routes/transactions.ts +68 -29
- package/src/apps/data/routes/transfers.test.ts +421 -0
- package/src/apps/data/routes/transfers.ts +45 -27
- package/src/apps/data/routes/valuation.ts +94 -78
- package/src/apps/data/routes/webhooks.test.ts +21 -5
- package/src/apps/data/routes/webhooks.ts +63 -8
- package/src/apps/data/routes/zones.test.ts +2 -2
- package/src/apps/data/routes/zones.ts +1 -1
- package/src/apps/funding/App.ts +12 -2
- package/src/apps/funding/routes/deposit-addresses.test.ts +29 -7
- package/src/apps/funding/routes/deposit-addresses.ts +19 -14
- package/src/apps/funding/routes/quotes.test.ts +5 -9
- package/src/apps/funding/routes/transfers.test.ts +901 -23
- package/src/apps/funding/routes/transfers.ts +347 -64
- package/src/apps/management/Billing.ts +18 -5
- package/src/apps/management/routes/api-keys.test.ts +220 -0
- package/src/apps/management/routes/api-keys.ts +280 -3
- package/src/apps/management/routes/webhooks.test.ts +3 -3
- package/src/apps/management/routes/webhooks.ts +24 -25
- package/src/apps/relay/App.test.ts +212 -1
- package/src/apps/relay/App.ts +158 -29
- package/src/apps/relay/Sponsorships.ts +24 -1
- package/src/db/Db.ts +48 -1
- package/src/db/Schema.ts +22 -4
- package/src/db/tables/earnVaults.test.ts +9 -9
- package/src/db/tables/fundingCatalog.test.ts +16 -2
- package/src/db/tables/fundingCatalog.ts +9 -3
- package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
- package/src/db/tables/fundingTransferTransactions.ts +79 -3
- package/src/db/tables/fundingTransfers.test.ts +26 -0
- package/src/db/tables/fundingTransfers.ts +36 -0
- package/src/db/tables/organizations.test.ts +41 -0
- package/src/db/tables/organizations.ts +49 -0
- package/src/db/tables/projects.test.ts +55 -0
- package/src/db/tables/projects.ts +46 -1
- package/src/db/tables/sponsoredTransactions.test.ts +338 -0
- package/src/db/tables/sponsoredTransactions.ts +197 -13
- package/src/db/tables/webhookSubscriptions.ts +18 -11
- package/src/handlers/internal/utils.test.ts +53 -0
- package/src/handlers/internal/utils.ts +15 -0
- package/src/internal/Auth.ts +12 -4
- package/src/internal/EarnVaults.capabilities.test.ts +2 -2
- package/src/internal/EarnVaults.test.ts +1 -1
- package/src/internal/EarnVaults.ts +5 -5
- package/src/internal/Fetch.test.ts +46 -0
- package/src/internal/Fetch.ts +56 -0
- package/src/internal/Log.test-d.ts +2 -0
- package/src/internal/Log.test.ts +21 -0
- package/src/internal/Log.ts +22 -5
- package/src/internal/OpenApi.ts +14 -4
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.test.ts +2 -0
- package/src/internal/Schema.ts +12 -2
- package/src/internal/Store.test.ts +178 -0
- package/src/internal/Store.ts +104 -18
- package/src/internal/Tidx.test.ts +2 -1
- package/src/internal/Tidx.ts +7 -2
- package/src/internal/Timeout.test.ts +35 -0
- package/src/internal/Timeout.ts +73 -0
- package/src/internal/Timing.ts +16 -0
- package/src/internal/Viem.ts +39 -3
- package/src/internal/WebhookDestination.ts +3 -0
- package/src/internal/Webhooks.test.ts +45 -0
- package/src/internal/funding/Action.test.ts +41 -1
- package/src/internal/funding/Action.ts +69 -41
- package/src/internal/funding/Catalog.test.ts +34 -0
- package/src/internal/funding/Catalog.ts +28 -11
- package/src/internal/funding/DepositWebhook.ts +2 -1
- package/src/internal/funding/Evidence.ts +2 -13
- package/src/internal/funding/Provider.test.ts +73 -0
- package/src/internal/funding/Provider.ts +125 -28
- package/src/internal/funding/SourceObservation.ts +5 -9
- package/src/internal/funding/Transfer.test.ts +155 -0
- package/src/internal/funding/Transfer.ts +387 -147
- package/src/internal/funding/TransferReconciliation.test.ts +147 -0
- package/src/internal/funding/TransferReconciliation.ts +264 -0
- package/src/internal/funding/TransferWebhook.test.ts +128 -0
- package/src/internal/funding/TransferWebhook.ts +67 -0
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/index.test.ts +4 -167
- package/src/internal/funding/providers/relay.test.ts +2 -2
- package/src/internal/funding/providers/relay.ts +3 -15
- package/src/internal/funding/providers/stargate.test.ts +328 -0
- package/src/internal/funding/providers/stargate.ts +807 -135
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { tempoMainnet } from 'viem/tempo/chains'
|
|
2
|
+
|
|
3
|
+
import * as Anvil from '../../../test/Anvil.js'
|
|
4
|
+
import * as TestApp from '../../../test/App.js'
|
|
5
|
+
import * as TestFunding from '../../../test/Funding.js'
|
|
6
|
+
import * as Catalog from './Catalog.js'
|
|
7
|
+
import * as Transfer from './Transfer.js'
|
|
8
|
+
import * as TransferReconciliation from './TransferReconciliation.js'
|
|
9
|
+
|
|
10
|
+
const db = TestApp.database()
|
|
11
|
+
const destinationTransactionHash =
|
|
12
|
+
'0x3dfaba0a78b8f4bc09e55e05211c8120729e7b6879efe32613883befbca666da'
|
|
13
|
+
const guid = '0x5549d117c8b23f0ecdf05c2604ed989cf0034f378707bd81319e93834f2f85f2'
|
|
14
|
+
const recipient = '0x7212623e4cff9ad010d28aa065fad2bf58578783'
|
|
15
|
+
const sender = '0x52b650a62e384f68880d0a1dc2b84dfed47d5660'
|
|
16
|
+
const sourceTransactionHash = '0xfd71359adf5095f2ab512a19e0a2df6c8cc0db14ac8a52286d61287daabdccd4'
|
|
17
|
+
const routeConfiguration = TestFunding.snapshot.routesByProvider
|
|
18
|
+
.get('stargate')!
|
|
19
|
+
.find((candidate) => candidate.route.source.chain.id === 'eip155:8453')!.configuration!
|
|
20
|
+
|
|
21
|
+
describe.skipIf(!Anvil.available)('createTracker', () => {
|
|
22
|
+
let destination: Anvil.start.ReturnType
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
destination = await Anvil.start({
|
|
26
|
+
chain: tempoMainnet,
|
|
27
|
+
forkUrl: tempoMainnet.rpcUrls.default.http[0]!,
|
|
28
|
+
})
|
|
29
|
+
await Catalog.publish(db, {
|
|
30
|
+
...TestFunding.data,
|
|
31
|
+
chains: TestFunding.data.chains.map((chain) =>
|
|
32
|
+
chain.id === 'eip155:4217' ? { ...chain, rpcUrls: [destination.rpcUrl] } : chain,
|
|
33
|
+
),
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await destination?.stop()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('completes a registered transfer from Tempo evidence', async () => {
|
|
42
|
+
const amount = {
|
|
43
|
+
baseUnits: '200000',
|
|
44
|
+
currency: 'USD',
|
|
45
|
+
decimals: 6,
|
|
46
|
+
formatted: '0.2',
|
|
47
|
+
} as const
|
|
48
|
+
const transfer = await Transfer.create(db, {
|
|
49
|
+
apiKeyId: 'key_test',
|
|
50
|
+
environment: 'production',
|
|
51
|
+
orgId: 'org_test',
|
|
52
|
+
providerState: {
|
|
53
|
+
destinationBlockNumber: (
|
|
54
|
+
await destination.client.getTransactionReceipt({ hash: destinationTransactionHash })
|
|
55
|
+
).blockNumber.toString(),
|
|
56
|
+
routeConfiguration,
|
|
57
|
+
},
|
|
58
|
+
snapshot: TestFunding.transferSnapshot({
|
|
59
|
+
destinationAmount: amount,
|
|
60
|
+
destinationAmountMin: amount,
|
|
61
|
+
provider: { id: 'stargate', name: 'Stargate' },
|
|
62
|
+
recipient,
|
|
63
|
+
sender,
|
|
64
|
+
sourceAmount: amount,
|
|
65
|
+
}),
|
|
66
|
+
})
|
|
67
|
+
await Transfer.registerSourceTransaction(db, {
|
|
68
|
+
chainId: 'eip155:8453',
|
|
69
|
+
id: transfer.id,
|
|
70
|
+
providerState: { stargate: { guid, sourceEid: 30_184 } },
|
|
71
|
+
transactionHash: sourceTransactionHash,
|
|
72
|
+
})
|
|
73
|
+
await Catalog.publish(db, {
|
|
74
|
+
...TestFunding.data,
|
|
75
|
+
chains: TestFunding.data.chains.map((chain) =>
|
|
76
|
+
chain.id === 'eip155:4217' ? { ...chain, rpcUrls: [destination.rpcUrl] } : chain,
|
|
77
|
+
),
|
|
78
|
+
routes: TestFunding.data.routes.filter((route) => route.providerId !== 'stargate'),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const tracker = TransferReconciliation.createTracker({ db })
|
|
82
|
+
await expect(
|
|
83
|
+
tracker.reconcile({ transferId: transfer.id, type: 'funding:transfer:reconcile' }),
|
|
84
|
+
).resolves.toEqual({ type: 'completed' })
|
|
85
|
+
|
|
86
|
+
const completed = await Transfer.transition(db, {
|
|
87
|
+
expectedVersion: 2,
|
|
88
|
+
id: transfer.id,
|
|
89
|
+
status: 'completed',
|
|
90
|
+
})
|
|
91
|
+
expect(completed.type).toBe('stale')
|
|
92
|
+
if (completed.type !== 'stale') return
|
|
93
|
+
expect({
|
|
94
|
+
destinationTransactionHashes: completed.current.snapshot.destinationTransactionHashes,
|
|
95
|
+
status: completed.current.status,
|
|
96
|
+
version: completed.current.version,
|
|
97
|
+
}).toEqual({
|
|
98
|
+
destinationTransactionHashes: [destinationTransactionHash],
|
|
99
|
+
status: 'completed',
|
|
100
|
+
version: 3,
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('tick', () => {
|
|
106
|
+
test('advances through bounded recovery pages', async () => {
|
|
107
|
+
const database = TestApp.database()
|
|
108
|
+
await TestFunding.publish(database)
|
|
109
|
+
for (let index = 0; index < 26; index++) {
|
|
110
|
+
const transfer = await Transfer.create(database, {
|
|
111
|
+
apiKeyId: 'key_test',
|
|
112
|
+
environment: 'production',
|
|
113
|
+
id: Transfer.generateId(new Date(Date.UTC(2026, 0, 1, 0, 0, index))),
|
|
114
|
+
orgId: 'org_test',
|
|
115
|
+
providerState: {},
|
|
116
|
+
snapshot: TestFunding.transferSnapshot({
|
|
117
|
+
provider: { id: 'stargate', name: 'Stargate' },
|
|
118
|
+
}),
|
|
119
|
+
})
|
|
120
|
+
await Transfer.transition(database, {
|
|
121
|
+
expectedVersion: 1,
|
|
122
|
+
id: transfer.id,
|
|
123
|
+
status: 'processing',
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type Dispatch = { delaySeconds: number; message: TransferReconciliation.Message }
|
|
128
|
+
const dispatches: Dispatch[] = []
|
|
129
|
+
let available = false
|
|
130
|
+
const tracker = TransferReconciliation.createTracker({
|
|
131
|
+
db: database,
|
|
132
|
+
dispatch: async (message, options) => {
|
|
133
|
+
dispatches.push({ delaySeconds: options.delaySeconds, message })
|
|
134
|
+
if (!available) throw new Error('queue unavailable')
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
await expect(tracker.tick()).rejects.toThrow('queue unavailable')
|
|
138
|
+
available = true
|
|
139
|
+
await expect(tracker.tick()).resolves.toEqual({ checked: 25 })
|
|
140
|
+
await expect(tracker.tick()).resolves.toEqual({ checked: 1 })
|
|
141
|
+
|
|
142
|
+
expect(dispatches).toHaveLength(27)
|
|
143
|
+
expect(dispatches[0]).toEqual(dispatches[1])
|
|
144
|
+
expect(new Set(dispatches.map(({ delaySeconds }) => delaySeconds))).toEqual(new Set([0]))
|
|
145
|
+
expect(new Set(dispatches.map(({ message }) => message.transferId)).size).toBe(26)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Hash } from 'ox'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import * as Db from '../../db/Db.js'
|
|
5
|
+
import * as FundingTransferTransactions from '../../db/tables/fundingTransferTransactions.js'
|
|
6
|
+
import * as FundingTransfers from '../../db/tables/fundingTransfers.js'
|
|
7
|
+
import type * as Metrics from '../../Metrics.js'
|
|
8
|
+
import * as Schema from '../Schema.js'
|
|
9
|
+
import type * as Webhooks from '../Webhooks.js'
|
|
10
|
+
import * as Catalog from './Catalog.js'
|
|
11
|
+
import type * as Provider from './Provider.js'
|
|
12
|
+
import * as Stargate from './providers/stargate.js'
|
|
13
|
+
import * as Transfer from './Transfer.js'
|
|
14
|
+
|
|
15
|
+
const followUpDelaySeconds = 15
|
|
16
|
+
const maximumFollowUpAttempts = 120
|
|
17
|
+
const sweepLimit = 25
|
|
18
|
+
|
|
19
|
+
const providerState = z.object({
|
|
20
|
+
destinationBlockNumber: z.string().check(z.regex(/^\d+$/)),
|
|
21
|
+
routeConfiguration: Stargate.schema.Configuration,
|
|
22
|
+
stargate: z.object({
|
|
23
|
+
guid: Schema.Hash,
|
|
24
|
+
sourceBlock: z.optional(
|
|
25
|
+
z.object({
|
|
26
|
+
hash: Schema.Hash,
|
|
27
|
+
number: z.string().check(z.regex(/^\d+$/)),
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
sourceEid: z.number().check(z.int(), z.positive()),
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** Queue message that advances one registered funding transfer. */
|
|
35
|
+
export type Message = {
|
|
36
|
+
/** Zero-based destination reconciliation attempt. */
|
|
37
|
+
attempt?: number | undefined
|
|
38
|
+
/** Funding transfer id (`ftr_…`). */
|
|
39
|
+
transferId: string
|
|
40
|
+
/** Funding transfer reconciliation discriminator. */
|
|
41
|
+
type: 'funding:transfer:reconcile'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Creates a tracker that verifies and completes registered funding transfers. */
|
|
45
|
+
export function createTracker(options: createTracker.Options): createTracker.Tracker {
|
|
46
|
+
const fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init))
|
|
47
|
+
let sweepCursor: string | undefined
|
|
48
|
+
const record = (outcome: string, count = 1) => {
|
|
49
|
+
options.metrics?.count('funding_transfer_reconciliation_count', count, {
|
|
50
|
+
outcome,
|
|
51
|
+
provider: 'stargate',
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
async reconcile(message) {
|
|
57
|
+
const db = Db.get(options.db)
|
|
58
|
+
const transfer = await FundingTransfers.get(db, message.transferId)
|
|
59
|
+
if (!transfer || transfer.status === 'completed') {
|
|
60
|
+
record(transfer ? 'already_completed' : 'not_found')
|
|
61
|
+
return { type: 'ignored' }
|
|
62
|
+
}
|
|
63
|
+
if (transfer.status !== 'processing' || transfer.providerId !== 'stargate') {
|
|
64
|
+
record('invalid_state')
|
|
65
|
+
return { type: 'ignored' }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const state = providerState.safeParse(transfer.providerState)
|
|
69
|
+
const transactions = await FundingTransferTransactions.listByTransfer(db, transfer.id)
|
|
70
|
+
const source = transactions.find((transaction) => transaction.role === 'source')
|
|
71
|
+
const catalog = await Catalog.read(db)
|
|
72
|
+
const destinationChain = catalog.chainsByKey.get(transfer.snapshot.destinationChain.id)
|
|
73
|
+
const sourceChain = catalog.chainsByKey.get(transfer.snapshot.sourceChain.id)
|
|
74
|
+
const destinationAmount = transfer.snapshot.destinationAmount
|
|
75
|
+
if (
|
|
76
|
+
!state.success ||
|
|
77
|
+
!source ||
|
|
78
|
+
!Hash.validate(source.transactionRef) ||
|
|
79
|
+
!destinationChain ||
|
|
80
|
+
!destinationAmount ||
|
|
81
|
+
!sourceChain ||
|
|
82
|
+
!transfer.snapshot.sender
|
|
83
|
+
)
|
|
84
|
+
throw new EvidenceError('Registered Stargate transfer evidence is incomplete.')
|
|
85
|
+
|
|
86
|
+
const delivery = await Stargate.verifyDestinationDelivery({
|
|
87
|
+
configuration: state.data.routeConfiguration,
|
|
88
|
+
destinationAmount: destinationAmount.baseUnits,
|
|
89
|
+
destinationBlockNumber: state.data.destinationBlockNumber,
|
|
90
|
+
destinationChain,
|
|
91
|
+
destinationTokenAddress: transfer.snapshot.destinationToken.address,
|
|
92
|
+
fetch,
|
|
93
|
+
guid: state.data.stargate.guid,
|
|
94
|
+
recipient: transfer.snapshot.recipient,
|
|
95
|
+
sourceChainId: transfer.snapshot.sourceChain.id,
|
|
96
|
+
sourceTokenAddress: transfer.snapshot.sourceToken.address,
|
|
97
|
+
})
|
|
98
|
+
if (delivery.type === 'invalid')
|
|
99
|
+
throw new EvidenceError('Tempo delivery conflicts with registered transfer terms.')
|
|
100
|
+
if (delivery.type === 'pending') {
|
|
101
|
+
const verified = await Stargate.verifySourceTransaction({
|
|
102
|
+
configuration: state.data.routeConfiguration,
|
|
103
|
+
destinationTokenAddress: transfer.snapshot.destinationToken.address,
|
|
104
|
+
fetch,
|
|
105
|
+
recipient: transfer.snapshot.recipient,
|
|
106
|
+
sender: transfer.snapshot.sender,
|
|
107
|
+
sourceAmount: transfer.snapshot.sourceAmount.baseUnits,
|
|
108
|
+
sourceBlock: state.data.stargate.sourceBlock
|
|
109
|
+
? {
|
|
110
|
+
hash: state.data.stargate.sourceBlock.hash,
|
|
111
|
+
number: BigInt(state.data.stargate.sourceBlock.number),
|
|
112
|
+
}
|
|
113
|
+
: undefined,
|
|
114
|
+
sourceChain,
|
|
115
|
+
sourceTokenAddress: transfer.snapshot.sourceToken.address,
|
|
116
|
+
transactionHash: source.transactionRef,
|
|
117
|
+
validAfter: transfer.snapshot.quote.sampledAt,
|
|
118
|
+
})
|
|
119
|
+
if (verified.type === 'reorged') {
|
|
120
|
+
const reopened = await Transfer.reopenSourceTransaction(db, {
|
|
121
|
+
chainId: transfer.snapshot.sourceChain.id,
|
|
122
|
+
id: transfer.id,
|
|
123
|
+
providerState: {
|
|
124
|
+
destinationBlockNumber: state.data.destinationBlockNumber,
|
|
125
|
+
routeConfiguration: state.data.routeConfiguration,
|
|
126
|
+
},
|
|
127
|
+
transactionHash: source.transactionRef,
|
|
128
|
+
})
|
|
129
|
+
if (reopened.type !== 'reopened') {
|
|
130
|
+
record('invalid_state')
|
|
131
|
+
return { type: 'ignored' }
|
|
132
|
+
}
|
|
133
|
+
if (options.dispatchTransferUpdates && reopened.references.length > 0)
|
|
134
|
+
await options.dispatchTransferUpdates(reopened.references)
|
|
135
|
+
record('reopened')
|
|
136
|
+
return { type: 'reopened' }
|
|
137
|
+
}
|
|
138
|
+
if (verified.type === 'pending') return followUp(message, options, record)
|
|
139
|
+
if (
|
|
140
|
+
verified.type === 'invalid' ||
|
|
141
|
+
verified.guid.toLowerCase() !== state.data.stargate.guid.toLowerCase() ||
|
|
142
|
+
verified.sourceEid !== state.data.stargate.sourceEid
|
|
143
|
+
)
|
|
144
|
+
throw new EvidenceError('Source transaction conflicts with registered Stargate evidence.')
|
|
145
|
+
return followUp(message, options, record)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const completed = await Transfer.complete(db, {
|
|
149
|
+
chainId: transfer.snapshot.destinationChain.id,
|
|
150
|
+
destinationAmount,
|
|
151
|
+
id: transfer.id,
|
|
152
|
+
transactionHash: delivery.transactionHash,
|
|
153
|
+
})
|
|
154
|
+
if (completed.type === 'invalid')
|
|
155
|
+
throw new EvidenceError('Funding transfer cannot accept destination evidence.')
|
|
156
|
+
if (completed.type === 'not_found') {
|
|
157
|
+
record('not_found')
|
|
158
|
+
return { type: 'ignored' }
|
|
159
|
+
}
|
|
160
|
+
if (
|
|
161
|
+
completed.type === 'completed' &&
|
|
162
|
+
options.dispatchTransferUpdates &&
|
|
163
|
+
completed.references.length > 0
|
|
164
|
+
)
|
|
165
|
+
await options.dispatchTransferUpdates(completed.references)
|
|
166
|
+
record(completed.type)
|
|
167
|
+
return { type: 'completed' }
|
|
168
|
+
},
|
|
169
|
+
async tick() {
|
|
170
|
+
if (!options.dispatch) throw new DispatchError()
|
|
171
|
+
const db = Db.get(options.db)
|
|
172
|
+
let records = await FundingTransfers.listProcessing(db, {
|
|
173
|
+
cursor: sweepCursor,
|
|
174
|
+
limit: sweepLimit,
|
|
175
|
+
providerId: 'stargate',
|
|
176
|
+
})
|
|
177
|
+
if (records.length === 0 && sweepCursor !== undefined) {
|
|
178
|
+
sweepCursor = undefined
|
|
179
|
+
records = await FundingTransfers.listProcessing(db, {
|
|
180
|
+
limit: sweepLimit,
|
|
181
|
+
providerId: 'stargate',
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
let checked = 0
|
|
185
|
+
for (const transfer of records) {
|
|
186
|
+
await options.dispatch(
|
|
187
|
+
{
|
|
188
|
+
transferId: transfer.id,
|
|
189
|
+
type: 'funding:transfer:reconcile',
|
|
190
|
+
},
|
|
191
|
+
{ delaySeconds: 0 },
|
|
192
|
+
)
|
|
193
|
+
sweepCursor = transfer.id
|
|
194
|
+
checked++
|
|
195
|
+
}
|
|
196
|
+
record('swept', checked)
|
|
197
|
+
return { checked }
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export declare namespace createTracker {
|
|
203
|
+
/** Tracker dependencies and queue dispatch. */
|
|
204
|
+
type Options = {
|
|
205
|
+
/** Authoritative database or per-invocation database factory. */
|
|
206
|
+
db: Db.Source
|
|
207
|
+
/** Enqueues one delayed reconciliation follow-up. */
|
|
208
|
+
dispatch?: ((message: Message, options: { delaySeconds: number }) => Promise<void>) | undefined
|
|
209
|
+
/** Enqueues webhook updates staged with transfer mutations. */
|
|
210
|
+
dispatchTransferUpdates?:
|
|
211
|
+
| ((references: readonly Webhooks.QueueReference[]) => Promise<number>)
|
|
212
|
+
| undefined
|
|
213
|
+
/** Fetch implementation override for chain RPC requests. */
|
|
214
|
+
fetch?: Provider.Fetch | undefined
|
|
215
|
+
/** Optional bounded operational metrics. */
|
|
216
|
+
metrics?: Metrics.Metrics | undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Reconciles one registered funding transfer. */
|
|
220
|
+
type Tracker = {
|
|
221
|
+
/** Verifies or defers one destination delivery. */
|
|
222
|
+
reconcile(message: Message): Promise<Result>
|
|
223
|
+
/** Enqueues processing transfers whose initial dispatch may have failed. */
|
|
224
|
+
tick(): Promise<{ checked: number }>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** One reconciliation disposition. */
|
|
228
|
+
type Result =
|
|
229
|
+
| { type: 'completed' }
|
|
230
|
+
| { type: 'ignored' }
|
|
231
|
+
| { type: 'pending' }
|
|
232
|
+
| { type: 'reopened' }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function followUp(
|
|
236
|
+
message: Message,
|
|
237
|
+
options: createTracker.Options,
|
|
238
|
+
record: (outcome: string) => void,
|
|
239
|
+
): Promise<createTracker.Result> {
|
|
240
|
+
const attempt = (message.attempt ?? 0) + 1
|
|
241
|
+
if (attempt > maximumFollowUpAttempts) {
|
|
242
|
+
record('exhausted')
|
|
243
|
+
throw new ExhaustedError()
|
|
244
|
+
}
|
|
245
|
+
if (!options.dispatch) throw new DispatchError()
|
|
246
|
+
await options.dispatch(
|
|
247
|
+
{ attempt, transferId: message.transferId, type: 'funding:transfer:reconcile' },
|
|
248
|
+
{ delaySeconds: followUpDelaySeconds },
|
|
249
|
+
)
|
|
250
|
+
record('pending')
|
|
251
|
+
return { type: 'pending' }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
class DispatchError extends Error {
|
|
255
|
+
override name = 'Funding.TransferReconciliation.DispatchError'
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
class EvidenceError extends Error {
|
|
259
|
+
override name = 'Funding.TransferReconciliation.EvidenceError'
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class ExhaustedError extends Error {
|
|
263
|
+
override name = 'Funding.TransferReconciliation.ExhaustedError'
|
|
264
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as TestApp from '../../../test/App.js'
|
|
2
|
+
import * as TestFunding from '../../../test/Funding.js'
|
|
3
|
+
import * as FundingTransferEvents from '../../db/tables/fundingTransferEvents.js'
|
|
4
|
+
import * as Webhooks from '../Webhooks.js'
|
|
5
|
+
import * as Transfer from './Transfer.js'
|
|
6
|
+
import * as TransferWebhook from './TransferWebhook.js'
|
|
7
|
+
|
|
8
|
+
const db = TestApp.database()
|
|
9
|
+
|
|
10
|
+
describe('stage', () => {
|
|
11
|
+
test('stages owner-scoped public versions with stable event ids', async () => {
|
|
12
|
+
const id = Transfer.generateId()
|
|
13
|
+
const orgId = 'org_funding_transfer_webhook'
|
|
14
|
+
const projectId = 'prj_funding_transfer_webhook'
|
|
15
|
+
const org = await subscription({ filters: { id }, orgId })
|
|
16
|
+
const project = await subscription({ filters: { id, status: 'completed' }, orgId, projectId })
|
|
17
|
+
await subscription({ filters: { id }, orgId, projectId: 'prj_funding_transfer_sibling' })
|
|
18
|
+
await subscription({ environment: 'sandbox', filters: { id }, orgId })
|
|
19
|
+
await subscription({ filters: { id: Transfer.generateId() }, orgId })
|
|
20
|
+
await subscription({ filters: { id }, now: new Date('2099-01-01T00:00:00.000Z'), orgId })
|
|
21
|
+
|
|
22
|
+
const created = await db.transaction((tx) =>
|
|
23
|
+
Transfer.createIn(tx, {
|
|
24
|
+
apiKeyId: 'key_funding_transfer_webhook',
|
|
25
|
+
environment: 'production',
|
|
26
|
+
id,
|
|
27
|
+
orgId,
|
|
28
|
+
projectId,
|
|
29
|
+
providerState: { privateRequestId: 'provider-private' },
|
|
30
|
+
snapshot: TestFunding.transferSnapshot(),
|
|
31
|
+
}),
|
|
32
|
+
)
|
|
33
|
+
const processing = await Transfer.transition(db, {
|
|
34
|
+
expectedVersion: created.record.version,
|
|
35
|
+
id,
|
|
36
|
+
status: 'processing',
|
|
37
|
+
})
|
|
38
|
+
if (processing.type !== 'applied') throw new Error('Expected processing transition.')
|
|
39
|
+
const completed = await Transfer.transition(db, {
|
|
40
|
+
expectedVersion: processing.record.version,
|
|
41
|
+
id,
|
|
42
|
+
status: 'completed',
|
|
43
|
+
})
|
|
44
|
+
if (completed.type !== 'applied') throw new Error('Expected completion transition.')
|
|
45
|
+
|
|
46
|
+
expect({
|
|
47
|
+
completed: completed.references.length,
|
|
48
|
+
created: created.references.length,
|
|
49
|
+
processing: processing.references.length,
|
|
50
|
+
}).toEqual({ completed: 2, created: 1, processing: 1 })
|
|
51
|
+
|
|
52
|
+
const rows = await db.kysely
|
|
53
|
+
.selectFrom('webhook_queue_events')
|
|
54
|
+
.select(['envelope', 'subscriptionId'])
|
|
55
|
+
.where('subscriptionId', 'in', [org.id, project.id])
|
|
56
|
+
.orderBy('subscriptionId')
|
|
57
|
+
.execute()
|
|
58
|
+
expect(
|
|
59
|
+
rows.map(({ envelope, subscriptionId }) => ({
|
|
60
|
+
data: envelope.data,
|
|
61
|
+
eventId: envelope.id,
|
|
62
|
+
subscriptionId,
|
|
63
|
+
type: envelope.type,
|
|
64
|
+
})),
|
|
65
|
+
).toEqual(
|
|
66
|
+
expect.arrayContaining([
|
|
67
|
+
expect.objectContaining({
|
|
68
|
+
data: expect.objectContaining({ id, status: 'awaiting-source', version: 1 }),
|
|
69
|
+
subscriptionId: org.id,
|
|
70
|
+
type: 'funding:transfer.updated',
|
|
71
|
+
}),
|
|
72
|
+
expect.objectContaining({
|
|
73
|
+
data: expect.objectContaining({ id, status: 'processing', version: 2 }),
|
|
74
|
+
subscriptionId: org.id,
|
|
75
|
+
type: 'funding:transfer.updated',
|
|
76
|
+
}),
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
data: expect.objectContaining({ id, status: 'completed', version: 3 }),
|
|
79
|
+
subscriptionId: org.id,
|
|
80
|
+
type: 'funding:transfer.updated',
|
|
81
|
+
}),
|
|
82
|
+
expect.objectContaining({
|
|
83
|
+
data: expect.objectContaining({ id, status: 'completed', version: 3 }),
|
|
84
|
+
subscriptionId: project.id,
|
|
85
|
+
type: 'funding:transfer.updated',
|
|
86
|
+
}),
|
|
87
|
+
]),
|
|
88
|
+
)
|
|
89
|
+
expect(rows).toHaveLength(4)
|
|
90
|
+
expect(rows[0]?.envelope.data).not.toHaveProperty('action')
|
|
91
|
+
expect(rows[0]?.envelope.data).not.toHaveProperty('providerState')
|
|
92
|
+
|
|
93
|
+
const events = await FundingTransferEvents.listByTransfer(db, id)
|
|
94
|
+
const repeated = await TransferWebhook.stage(db, {
|
|
95
|
+
changes: [{ event: events.at(-1)!, record: completed.record }],
|
|
96
|
+
})
|
|
97
|
+
expect(repeated.created).toBe(0)
|
|
98
|
+
expect(new Set(repeated.references.map((reference) => reference.eventId))).toEqual(
|
|
99
|
+
new Set([rows.find(({ subscriptionId }) => subscriptionId === project.id)?.envelope.id]),
|
|
100
|
+
)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
function subscription(options: subscription.Options) {
|
|
105
|
+
return Webhooks.createSubscription(
|
|
106
|
+
db,
|
|
107
|
+
{
|
|
108
|
+
chainId: 4217,
|
|
109
|
+
destination: { type: 'url', url: 'https://example.com/webhooks/tempo' },
|
|
110
|
+
environment: options.environment ?? 'production',
|
|
111
|
+
eventType: 'funding:transfer.updated',
|
|
112
|
+
filters: options.filters,
|
|
113
|
+
owner: { orgId: options.orgId, type: 'api_key' },
|
|
114
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
115
|
+
},
|
|
116
|
+
options.now === undefined ? {} : { now: () => options.now! },
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
declare namespace subscription {
|
|
121
|
+
type Options = {
|
|
122
|
+
environment?: 'production' | 'sandbox' | undefined
|
|
123
|
+
filters: Record<string, unknown>
|
|
124
|
+
now?: Date | undefined
|
|
125
|
+
orgId: string
|
|
126
|
+
projectId?: string | undefined
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type * as Db from '../../db/Db.js'
|
|
2
|
+
import type * as FundingTransferEvents from '../../db/tables/fundingTransferEvents.js'
|
|
3
|
+
import type * as FundingTransfers from '../../db/tables/fundingTransfers.js'
|
|
4
|
+
import * as WebhookSubscriptions from '../../db/tables/webhookSubscriptions.js'
|
|
5
|
+
import * as Webhooks from '../Webhooks.js'
|
|
6
|
+
|
|
7
|
+
/** Stages durable webhook obligations for committed funding transfer versions. */
|
|
8
|
+
export async function stage(
|
|
9
|
+
db: Db.Db,
|
|
10
|
+
options: stage.Options,
|
|
11
|
+
): Promise<Webhooks.ensureQueueEvents.Result> {
|
|
12
|
+
const dispatchables: Webhooks.ensureQueueEvents.Dispatchable[] = []
|
|
13
|
+
const now = new Date().toISOString()
|
|
14
|
+
for (const { event, record } of options.changes) {
|
|
15
|
+
const match = /^eip155:(\d+)$/.exec(event.snapshot.destinationChain.id)
|
|
16
|
+
if (!match?.[1]) continue
|
|
17
|
+
const chainId = Number(match[1])
|
|
18
|
+
if (!Number.isSafeInteger(chainId) || chainId <= 0) continue
|
|
19
|
+
const subscriptions = await WebhookSubscriptions.listActiveForFundingEvent(db, {
|
|
20
|
+
chainId,
|
|
21
|
+
environment: record.environment,
|
|
22
|
+
eventCreatedAt: event.createdAt,
|
|
23
|
+
eventType: 'funding:transfer.updated',
|
|
24
|
+
now,
|
|
25
|
+
orgId: record.orgId,
|
|
26
|
+
...(record.projectId === null ? {} : { projectId: record.projectId }),
|
|
27
|
+
})
|
|
28
|
+
for (const subscription of subscriptions) {
|
|
29
|
+
if (
|
|
30
|
+
typeof subscription.filters['id'] === 'string' &&
|
|
31
|
+
subscription.filters['id'] !== event.transferId
|
|
32
|
+
)
|
|
33
|
+
continue
|
|
34
|
+
if (
|
|
35
|
+
typeof subscription.filters['status'] === 'string' &&
|
|
36
|
+
subscription.filters['status'] !== event.status
|
|
37
|
+
)
|
|
38
|
+
continue
|
|
39
|
+
dispatchables.push({
|
|
40
|
+
envelope: Webhooks.buildKeyedEnvelope({
|
|
41
|
+
createdAt: new Date(event.createdAt),
|
|
42
|
+
data: event.snapshot,
|
|
43
|
+
key: `${event.transferId}:${event.version}`,
|
|
44
|
+
subscription,
|
|
45
|
+
}),
|
|
46
|
+
subscription,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return Webhooks.ensureQueueEvents(db, dispatchables)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export declare namespace stage {
|
|
54
|
+
/** One committed transfer record and its immutable public event. */
|
|
55
|
+
type Change = {
|
|
56
|
+
/** Immutable public transfer version. */
|
|
57
|
+
event: FundingTransferEvents.Record
|
|
58
|
+
/** Transfer ownership and environment record. */
|
|
59
|
+
record: FundingTransfers.Record
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Committed transfer changes eligible for customer webhook delivery. */
|
|
63
|
+
type Options = {
|
|
64
|
+
/** Transfer changes committed with their delivery obligations. */
|
|
65
|
+
changes: readonly Change[]
|
|
66
|
+
}
|
|
67
|
+
}
|