tapimo 0.10.2 → 0.12.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 +4896 -2166
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +2 -2
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +15 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +7 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Webhooks.d.ts +0 -75
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +1 -143
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +127 -78
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +115 -1
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +59 -6
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +3 -3
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +4 -2
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +83 -83
- 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/data/App.d.ts +5924 -4310
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +61 -9
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +62 -0
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
- package/dist/apps/data/ZoneSelection.js +44 -0
- package/dist/apps/data/ZoneSelection.js.map +1 -0
- package/dist/apps/data/routes/activities.d.ts +2368 -5
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +423 -114
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +131 -0
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +25 -25
- package/dist/apps/data/routes/balances.js +1 -1
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +1 -1
- package/dist/apps/data/routes/blocks.js +1 -1
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/earn.d.ts +135 -10
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +157 -25
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +829 -123
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +16 -13
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +26 -26
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +9 -11
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +54 -54
- package/dist/apps/data/routes/receipts.js +1 -1
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.js +24 -10
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +16 -15
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +17 -3
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +34 -15
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +1 -1
- package/dist/apps/data/routes/transactions.js +1 -1
- 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 +7 -9
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +1011 -151
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +126 -739
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +15 -15
- package/dist/apps/funding/App.d.ts +27 -27
- package/dist/apps/funding/routes/chains.d.ts.map +1 -1
- package/dist/apps/funding/routes/chains.js +5 -4
- package/dist/apps/funding/routes/chains.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +18 -18
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +8 -8
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/providers.d.ts.map +1 -1
- package/dist/apps/funding/routes/providers.js +3 -2
- package/dist/apps/funding/routes/providers.js.map +1 -1
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
- package/dist/apps/funding/routes/quotes.js +2 -2
- package/dist/apps/funding/routes/quotes.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +33 -33
- package/dist/apps/management/App.d.ts +1433 -322
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +19 -6
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +154 -94
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +76 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +103 -30
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +91 -32
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +12 -12
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +23 -12
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +46 -13
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +79 -29
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +128 -83
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +100 -41
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +5 -5
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +14 -10
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +68 -47
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +37 -18
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +48 -8
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +48 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +130 -68
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +72 -21
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +8 -5
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +38 -38
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +41 -31
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +34 -6
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.js +36 -17
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +1186 -197
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +106 -38
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +12 -0
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +61 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +30 -2
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +28 -3
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earnVaults.d.ts +5 -1
- package/dist/db/tables/earnVaults.d.ts.map +1 -1
- package/dist/db/tables/earnVaults.js +5 -0
- package/dist/db/tables/earnVaults.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +10 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
- package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
- package/dist/db/tables/webhookQueueCompletions.js +32 -0
- package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
- package/dist/db/tables/webhookQueueEvents.d.ts +16 -20
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +68 -43
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +47 -72
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +92 -105
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +8 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +13 -3
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +11 -7
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +41 -2
- package/dist/internal/EarnVaults.d.ts.map +1 -1
- package/dist/internal/EarnVaults.js +167 -0
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +35 -125
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +50 -121
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +26 -22
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +128 -39
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +13 -8
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +16 -5
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +8 -8
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +10 -9
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +15 -6
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +45 -15
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts +2 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +16 -4
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +1 -55
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +1 -239
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +112 -56
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +124 -93
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +2 -2
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +9 -8
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/DepositAddress.d.ts +4 -4
- package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
- package/dist/internal/funding/DepositAddress.js +9 -8
- package/dist/internal/funding/DepositAddress.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts +25 -0
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
- package/dist/internal/funding/DepositWebhook.js +74 -0
- package/dist/internal/funding/DepositWebhook.js.map +1 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +13 -12
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts +6 -0
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +42 -10
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- 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/stargate.d.ts +19 -0
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
- package/dist/internal/funding/providers/stargate.js +143 -0
- package/dist/internal/funding/providers/stargate.js.map +1 -0
- 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/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -0
- package/dist/providers/index.js.map +1 -1
- package/package.json +2 -2
- package/src/App.test.ts +48 -0
- package/src/App.ts +7 -2
- package/src/Assets.test.ts +18 -0
- package/src/Assets.ts +22 -0
- package/src/Client.test-d.ts +287 -17
- package/src/Webhooks.ts +1 -220
- package/src/admin/apps/earn-vaults.test.ts +143 -0
- package/src/admin/apps/earn-vaults.ts +83 -6
- package/src/admin/apps/organizations.ts +6 -2
- package/src/admin/ui/src/routes/api-keys.tsx +36 -0
- package/src/admin/ui.generated.ts +151 -189
- package/src/apps/data/App.test.ts +31 -0
- package/src/apps/data/App.ts +64 -10
- package/src/apps/data/ZoneSelection.test.ts +196 -0
- package/src/apps/data/ZoneSelection.ts +108 -0
- package/src/apps/data/routes/activities.test.ts +435 -12
- package/src/apps/data/routes/activities.ts +562 -125
- package/src/apps/data/routes/balances.ts +1 -1
- package/src/apps/data/routes/blocks.ts +1 -1
- package/src/apps/data/routes/earn.test.ts +183 -23
- package/src/apps/data/routes/earn.ts +225 -29
- package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
- package/src/apps/data/routes/exchanges.test.ts +31 -1
- package/src/apps/data/routes/exchanges.ts +19 -15
- package/src/apps/data/routes/fee-amm.ts +18 -11
- package/src/apps/data/routes/receipts.ts +1 -1
- package/src/apps/data/routes/rpc.test.ts +75 -0
- package/src/apps/data/routes/rpc.ts +23 -9
- package/src/apps/data/routes/tokenlist.ts +21 -18
- package/src/apps/data/routes/tokens.test.ts +29 -0
- package/src/apps/data/routes/tokens.ts +38 -15
- package/src/apps/data/routes/transactions.ts +1 -1
- package/src/apps/data/routes/transfers.test.ts +3 -3
- package/src/apps/data/routes/transfers.ts +12 -9
- package/src/apps/data/routes/webhooks.test.ts +179 -1497
- package/src/apps/data/routes/webhooks.ts +156 -976
- package/src/apps/data/routes/zones.test.ts +19 -13
- package/src/apps/funding/routes/chains.test.ts +211 -7
- package/src/apps/funding/routes/chains.ts +19 -12
- package/src/apps/funding/routes/deposit-addresses.test.ts +394 -0
- package/src/apps/funding/routes/deposit-addresses.ts +84 -72
- package/src/apps/funding/routes/providers.ts +9 -5
- package/src/apps/funding/routes/quotes.test.ts +46 -0
- package/src/apps/funding/routes/quotes.ts +8 -5
- package/src/apps/funding/routes/transfers.test.ts +35 -32
- package/src/apps/management/Billing.test.ts +26 -0
- package/src/apps/management/Billing.ts +24 -7
- package/src/apps/management/routes/api-keys.test.ts +178 -0
- package/src/apps/management/routes/api-keys.ts +203 -144
- package/src/apps/management/routes/billing.test.ts +209 -0
- package/src/apps/management/routes/billing.ts +254 -177
- package/src/apps/management/routes/faucet.test.ts +39 -0
- package/src/apps/management/routes/faucet.ts +75 -41
- package/src/apps/management/routes/invitations.test.ts +216 -0
- package/src/apps/management/routes/invitations.ts +186 -109
- package/src/apps/management/routes/invite-links.test.ts +263 -1
- package/src/apps/management/routes/invite-links.ts +295 -214
- package/src/apps/management/routes/me.test.ts +49 -0
- package/src/apps/management/routes/me.ts +42 -32
- package/src/apps/management/routes/members.test.ts +124 -1
- package/src/apps/management/routes/members.ts +81 -51
- package/src/apps/management/routes/orgs.test.ts +176 -0
- package/src/apps/management/routes/orgs.ts +101 -59
- package/src/apps/management/routes/projects.test.ts +183 -0
- package/src/apps/management/routes/projects.ts +124 -60
- package/src/apps/management/routes/scopes.test.ts +44 -0
- package/src/apps/management/routes/scopes.ts +37 -28
- package/src/apps/management/routes/usage.test.ts +92 -0
- package/src/apps/management/routes/usage.ts +186 -151
- package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
- package/src/apps/management/routes/verified-token-requests.ts +166 -133
- package/src/apps/management/routes/webhooks.test.ts +313 -1
- package/src/apps/management/routes/webhooks.ts +220 -71
- package/src/apps/mpp/internal/Adapter.ts +13 -1
- package/src/db/Db.test.ts +42 -5
- package/src/db/Db.ts +85 -0
- package/src/db/Schema.ts +41 -3
- package/src/db/tables/earnVaults.test.ts +22 -0
- package/src/db/tables/earnVaults.ts +11 -1
- package/src/db/tables/organizations.test.ts +5 -2
- package/src/db/tables/webhookDeliveries.ts +13 -0
- package/src/db/tables/webhookQueueCompletions.ts +55 -0
- package/src/db/tables/webhookQueueEvents.ts +97 -57
- package/src/db/tables/webhookSubscriptions.ts +134 -144
- package/src/internal/Auth.ts +23 -5
- package/src/internal/Cache.ts +14 -7
- package/src/internal/EarnVaults.capabilities.test.ts +33 -0
- package/src/internal/EarnVaults.test.ts +79 -0
- package/src/internal/EarnVaults.ts +277 -0
- package/src/internal/MetricSink.test.ts +41 -373
- package/src/internal/MetricSink.ts +92 -273
- package/src/internal/OpenApi.ts +165 -49
- package/src/internal/Response.test.ts +95 -0
- package/src/internal/Response.ts +40 -2
- package/src/internal/Schema.test.ts +34 -0
- package/src/internal/Schema.ts +20 -9
- package/src/internal/Store.test.ts +186 -0
- package/src/internal/Store.ts +57 -17
- package/src/internal/Tidx.test.ts +19 -0
- package/src/internal/Tidx.ts +16 -4
- package/src/internal/WebhookTransfer.ts +1 -351
- package/src/internal/Webhooks.test.ts +222 -145
- package/src/internal/Webhooks.ts +234 -135
- package/src/internal/funding/Deposit.ts +71 -64
- package/src/internal/funding/DepositAddress.ts +50 -43
- package/src/internal/funding/DepositWebhook.test.ts +131 -0
- package/src/internal/funding/DepositWebhook.ts +101 -0
- package/src/internal/funding/Provider.ts +164 -142
- package/src/internal/funding/Reconciliation.test.ts +14 -0
- package/src/internal/funding/Reconciliation.ts +161 -86
- 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 +185 -0
- package/src/internal/funding/providers/stargate.ts +184 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
- package/src/providers/index.test.ts +1 -0
- package/src/providers/index.ts +9 -0
- package/src/Webhooks.bench.ts +0 -714
- package/src/Webhooks.memory.bench.ts +0 -250
- package/src/Webhooks.test.ts +0 -146
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { decodeEventLog, encodeEventTopics, parseAbiItem } from 'viem';
|
|
3
3
|
import * as z from 'zod/mini';
|
|
4
|
+
import * as Scope from '../../../Scope.js';
|
|
4
5
|
import * as Auth from '../../../internal/Auth.js';
|
|
5
6
|
import * as Cache from '../../../internal/Cache.js';
|
|
6
|
-
import * as Cursor from '../../../internal/Cursor.js';
|
|
7
7
|
import * as Db from '../../../db/Db.js';
|
|
8
8
|
import * as OpenApi from '../../../internal/OpenApi.js';
|
|
9
9
|
import * as Response from '../../../internal/Response.js';
|
|
@@ -14,6 +14,7 @@ import * as Value from '../../../internal/Value.js';
|
|
|
14
14
|
import * as WebhookDestination from '../../../internal/WebhookDestination.js';
|
|
15
15
|
import * as WebhookTransfer from '../../../internal/WebhookTransfer.js';
|
|
16
16
|
import * as Webhooks from '../../../internal/Webhooks.js';
|
|
17
|
+
import * as FundingDeposit from '../../../internal/funding/Deposit.js';
|
|
17
18
|
import * as Transactions from './transactions.js';
|
|
18
19
|
import * as Transfers from './transfers.js';
|
|
19
20
|
/** Default cap on live subscriptions per owner, overridable via `webhook.maxPerOwner`. */
|
|
@@ -33,6 +34,7 @@ const redactedSlackUrl = 'https://hooks.slack.com/…';
|
|
|
33
34
|
*/
|
|
34
35
|
export const eventTypes = [
|
|
35
36
|
'block:created',
|
|
37
|
+
'funding:deposit.updated',
|
|
36
38
|
'log:emitted',
|
|
37
39
|
'token:transfer',
|
|
38
40
|
'transaction:included',
|
|
@@ -43,6 +45,10 @@ const eventTypeDescriptions = [
|
|
|
43
45
|
description: 'A new block was added to the chain. Filter by producer, gas usage, or block number, or omit filters for a per-block heartbeat.',
|
|
44
46
|
type: 'block:created',
|
|
45
47
|
},
|
|
48
|
+
{
|
|
49
|
+
description: 'A funding deposit materially changed after verified reconciliation, filterable by deposit address, recipient, or status.',
|
|
50
|
+
type: 'funding:deposit.updated',
|
|
51
|
+
},
|
|
46
52
|
{
|
|
47
53
|
description: 'Any contract event log, filtered by emitting address, event signature/topics, or decoded arguments.',
|
|
48
54
|
type: 'log:emitted',
|
|
@@ -268,6 +274,14 @@ export var schema;
|
|
|
268
274
|
timestamp: z.optional(schema.compare.check(z.describe('Only match blocks whose unix timestamp (seconds) is within these bounds.'))),
|
|
269
275
|
})
|
|
270
276
|
.check(z.describe('Filters that narrow which new blocks trigger this webhook.'), z.meta({ title: 'Block filters' }));
|
|
277
|
+
/** `funding:deposit.updated` subscription filters. All are optional and AND-combined. */
|
|
278
|
+
schema.FundingDepositFilters = z
|
|
279
|
+
.strictObject({
|
|
280
|
+
depositAddressId: z.optional(FundingDeposit.schema.Snapshot.shape.depositAddressId),
|
|
281
|
+
recipient: z.optional(Schema.Address),
|
|
282
|
+
status: z.optional(FundingDeposit.schema.Status),
|
|
283
|
+
})
|
|
284
|
+
.check(z.describe('Filters that narrow which funding deposit changes trigger this webhook.'), z.meta({ title: 'Funding deposit filters' }));
|
|
271
285
|
/**
|
|
272
286
|
* Optional human context describing what a subscription is for. Surfaced by
|
|
273
287
|
* destinations (Slack header + description, Better Stack log fields) and echoed
|
|
@@ -300,20 +314,23 @@ export var schema;
|
|
|
300
314
|
.datetime()
|
|
301
315
|
.check(z.describe('When the subscription was created, as an ISO 8601 timestamp.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
302
316
|
destination: PublicDestination,
|
|
317
|
+
environment: z
|
|
318
|
+
.optional(z.enum(['production', 'sandbox']))
|
|
319
|
+
.check(z.describe('API-key environment for private resource events, when applicable.'), z.meta({ examples: ['production'] })),
|
|
303
320
|
eventType: schema.EventType.check(z.meta({ examples: ['token:transfer'] })),
|
|
304
321
|
expiresAt: z
|
|
305
322
|
.optional(z.iso.datetime())
|
|
306
323
|
.check(z.describe('When the subscription expires, as an ISO 8601 timestamp. Only MPP-paid subscriptions expire.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
307
324
|
failureCount: z
|
|
308
325
|
.number()
|
|
309
|
-
.check(z.int(), z.nonnegative(), z.describe('Number of delivery failures in a row for this subscription.'), z.meta({ examples: [0] })),
|
|
326
|
+
.check(z.int(), z.nonnegative(), z.describe('Number of delivery failures in a row for this subscription, capped at the auto-disable threshold.'), z.meta({ examples: [0] })),
|
|
310
327
|
filters: Filters.check(z.describe('Filters applied to this event type.'), z.meta({ examples: [{ address: '0x20c0000000000000000000008f5425160ebe5525' }] })),
|
|
311
328
|
id: z
|
|
312
329
|
.string()
|
|
313
330
|
.check(z.describe('Webhook subscription ID (`wh_…`).'), z.meta({ examples: ['wh_001718668800000_2ZPE2gvateYEQ0dQslgvkhjx'] })),
|
|
314
331
|
lastDeliveryAt: z
|
|
315
332
|
.optional(z.iso.datetime())
|
|
316
|
-
.check(z.describe('When Tempo last delivered an event successfully, as an ISO 8601 timestamp.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
333
|
+
.check(z.describe('When Tempo last delivered an event successfully, as an ISO 8601 timestamp. Refreshed at most once per minute during sustained delivery.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
317
334
|
status: schema.Status.check(z.meta({ examples: ['active'] })),
|
|
318
335
|
updatedAt: z.iso
|
|
319
336
|
.datetime()
|
|
@@ -386,7 +403,7 @@ export var schema;
|
|
|
386
403
|
.string()
|
|
387
404
|
.check(z.describe('Token symbol (e.g. `USDC`).'), z.meta({ examples: ['USDC'] })),
|
|
388
405
|
})
|
|
389
|
-
.check(z.describe('Best-effort token metadata (symbol/decimals) resolved
|
|
406
|
+
.check(z.describe('Best-effort token metadata (symbol/decimals) resolved before delivery so `amount` can render as a human amount. Omitted when the lookup is unavailable.'))),
|
|
390
407
|
transactionHash: Schema.Hash.check(z.describe('Transaction hash that contains this transfer.'), z.meta({
|
|
391
408
|
examples: ['0x3d24a706cc2f6f4c96620bef1f61ddb23040ff77c22c8db42918c7c424bbf9d3'],
|
|
392
409
|
})),
|
|
@@ -489,6 +506,8 @@ export var schema;
|
|
|
489
506
|
.check(z.describe('When the block was produced, as an ISO 8601 timestamp.'), z.meta({ examples: ['2026-01-14T18:38:03.685Z'] })),
|
|
490
507
|
})
|
|
491
508
|
.check(z.describe('Payload for a `block:created` event (a new block header).'));
|
|
509
|
+
/** Public funding deposit after a committed material change. */
|
|
510
|
+
schema.FundingDepositEvent = FundingDeposit.schema.FundingDeposit;
|
|
492
511
|
/** A `ping` (synthetic test-delivery) payload, sent by `POST /webhooks/:id/ping`. */
|
|
493
512
|
schema.PingEvent = z
|
|
494
513
|
.object({
|
|
@@ -536,6 +555,13 @@ export var schema;
|
|
|
536
555
|
z
|
|
537
556
|
.object({ ...envelopeBase, data: schema.BlockEvent, type: z.literal('block:created') })
|
|
538
557
|
.check(z.meta({ title: 'Block event' })),
|
|
558
|
+
z
|
|
559
|
+
.object({
|
|
560
|
+
...envelopeBase,
|
|
561
|
+
data: schema.FundingDepositEvent,
|
|
562
|
+
type: z.literal('funding:deposit.updated'),
|
|
563
|
+
})
|
|
564
|
+
.check(z.meta({ title: 'Funding deposit updated event' })),
|
|
539
565
|
z
|
|
540
566
|
.object({ ...envelopeBase, data: schema.PingEvent, type: z.literal('ping') })
|
|
541
567
|
.check(z.meta({ title: 'Ping (test delivery)' })),
|
|
@@ -612,6 +638,17 @@ export var schema;
|
|
|
612
638
|
filters: z.optional(schema.BlockFilters),
|
|
613
639
|
})
|
|
614
640
|
.check(z.meta({ title: 'Block subscription' })),
|
|
641
|
+
z
|
|
642
|
+
.object({
|
|
643
|
+
chainId: z.optional(Schema.ChainId).check(z.meta({ examples: [4217] })),
|
|
644
|
+
context: z.optional(schema.Context),
|
|
645
|
+
destination: Destination,
|
|
646
|
+
eventType: z
|
|
647
|
+
.literal('funding:deposit.updated')
|
|
648
|
+
.check(z.describe('Event type for committed funding deposit changes.'), z.meta({ examples: ['funding:deposit.updated'] })),
|
|
649
|
+
filters: z.optional(schema.FundingDepositFilters),
|
|
650
|
+
})
|
|
651
|
+
.check(z.meta({ title: 'Funding deposit subscription' })),
|
|
615
652
|
])
|
|
616
653
|
.check(z.describe('Details for creating a webhook subscription.'));
|
|
617
654
|
createWebhook.Response = z
|
|
@@ -762,6 +799,9 @@ export var schema;
|
|
|
762
799
|
*/
|
|
763
800
|
export function webhooks(options = {}) {
|
|
764
801
|
const hidden = !options.enabled;
|
|
802
|
+
const availableEventTypes = eventTypeDescriptions.filter((eventType) => eventType.type !== 'funding:deposit.updated' ||
|
|
803
|
+
options.applicationEventTypes?.includes(eventType.type));
|
|
804
|
+
const availableEventTypeSet = new Set(availableEventTypes.map((eventType) => eventType.type));
|
|
765
805
|
// Gate the whole resource on a configured state store. Without it the
|
|
766
806
|
// outbound-HTTP surface is disabled (read-only deployment).
|
|
767
807
|
const gate = async (c, next) => {
|
|
@@ -804,7 +844,7 @@ export function webhooks(options = {}) {
|
|
|
804
844
|
if (!c.get('webhook'))
|
|
805
845
|
return notEnabled(c);
|
|
806
846
|
return c.json(Response.validated(schema.getWebhookEventTypes.Response, {
|
|
807
|
-
data:
|
|
847
|
+
data: availableEventTypes.map((eventType) => ({ ...eventType })),
|
|
808
848
|
}), 200);
|
|
809
849
|
})
|
|
810
850
|
.post('/v1/webhooks', Auth.policy({ apiKey: { scopes: ['webhooks:write'] } }), OpenApi.validate('json', schema.createWebhook.Body, {
|
|
@@ -817,12 +857,18 @@ export function webhooks(options = {}) {
|
|
|
817
857
|
responses: OpenApi.responses({
|
|
818
858
|
errors: {
|
|
819
859
|
400: {
|
|
820
|
-
codes: [
|
|
860
|
+
codes: [
|
|
861
|
+
'body_invalid',
|
|
862
|
+
'chain_id_unsupported',
|
|
863
|
+
'event_type_unsupported',
|
|
864
|
+
'filters_invalid',
|
|
865
|
+
'url_invalid',
|
|
866
|
+
],
|
|
821
867
|
description: 'The request, destination, filters, or chain are invalid.',
|
|
822
868
|
},
|
|
823
869
|
403: {
|
|
824
|
-
codes: ['limit_exceeded'],
|
|
825
|
-
description: '
|
|
870
|
+
codes: ['api_key_forbidden', 'limit_exceeded'],
|
|
871
|
+
description: 'The API key lacks access or the subscription limit was reached.',
|
|
826
872
|
},
|
|
827
873
|
},
|
|
828
874
|
success: {
|
|
@@ -844,10 +890,27 @@ export function webhooks(options = {}) {
|
|
|
844
890
|
});
|
|
845
891
|
const webhook = c.get('webhook');
|
|
846
892
|
const db = Db.get(c.get('db'));
|
|
893
|
+
const principal = Auth.getPrincipal(c);
|
|
847
894
|
const owner = ownerFor(c);
|
|
848
895
|
if (!owner)
|
|
849
896
|
return unauthorized(c);
|
|
850
897
|
const body = c.req.valid('json');
|
|
898
|
+
if (!availableEventTypeSet.has(body.eventType))
|
|
899
|
+
return Response.error(c, {
|
|
900
|
+
code: 'event_type_unsupported',
|
|
901
|
+
message: 'Webhook event type is not available in this deployment',
|
|
902
|
+
status: 400,
|
|
903
|
+
});
|
|
904
|
+
if (body.eventType === 'funding:deposit.updated') {
|
|
905
|
+
if (principal?.type !== 'api_key')
|
|
906
|
+
return unauthorized(c);
|
|
907
|
+
if (!principal.apiKey.scopes.some((scope) => scope === Scope.wildcard || scope === 'funding:read'))
|
|
908
|
+
return Response.error(c, {
|
|
909
|
+
code: 'api_key_forbidden',
|
|
910
|
+
message: 'API key missing required scope',
|
|
911
|
+
status: 403,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
851
914
|
const chainId = body.chainId ?? c.get('chainId');
|
|
852
915
|
const supportedChainIds = new Set(webhook.supportedChainIds);
|
|
853
916
|
if (!supportedChainIds.has(chainId))
|
|
@@ -858,18 +921,28 @@ export function webhooks(options = {}) {
|
|
|
858
921
|
const maxPerOwner = webhook.maxPerOwner ?? defaultMaxPerOwner;
|
|
859
922
|
if ((await Webhooks.countSubscriptions(db, owner)) >= maxPerOwner)
|
|
860
923
|
throw new Webhooks.LimitExceededError(maxPerOwner);
|
|
861
|
-
const startBlockNumber =
|
|
924
|
+
const startBlockNumber = body.eventType === 'funding:deposit.updated'
|
|
925
|
+
? undefined
|
|
926
|
+
: await getRpcHead(c.get('getClient')(chainId));
|
|
862
927
|
const subscription = await Webhooks.createSubscription(db, {
|
|
863
928
|
chainId,
|
|
864
929
|
...(body.context === undefined ? {} : { context: body.context }),
|
|
865
930
|
destination: body.destination,
|
|
931
|
+
...(principal?.type === 'api_key'
|
|
932
|
+
? {
|
|
933
|
+
environment: principal.environment,
|
|
934
|
+
...(principal.projectId === undefined
|
|
935
|
+
? {}
|
|
936
|
+
: { projectId: principal.projectId }),
|
|
937
|
+
}
|
|
938
|
+
: {}),
|
|
866
939
|
eventType: body.eventType,
|
|
867
940
|
filters: body.filters,
|
|
868
941
|
owner,
|
|
869
942
|
...(owner.type === 'mpp' ? { ttl: mppTtlMs } : {}),
|
|
870
943
|
}, {
|
|
871
944
|
maxPerOwner,
|
|
872
|
-
startBlockNumber,
|
|
945
|
+
...(startBlockNumber === undefined ? {} : { startBlockNumber }),
|
|
873
946
|
});
|
|
874
947
|
return c.json(Response.validated(schema.createWebhook.Response, toCreated(subscription)), 200);
|
|
875
948
|
}
|
|
@@ -906,17 +979,19 @@ export function webhooks(options = {}) {
|
|
|
906
979
|
const owner = ownerFor(c);
|
|
907
980
|
if (!owner)
|
|
908
981
|
return unauthorized(c);
|
|
982
|
+
const access = accessFor(c);
|
|
909
983
|
const { cursor, include, limit, page } = c.req.valid('query');
|
|
910
984
|
try {
|
|
911
985
|
// `totalCount` is opt-in and exact (the per-owner set is bounded);
|
|
912
986
|
// run it concurrently with the page list. Best-effort: a failure
|
|
913
987
|
// omits `meta` rather than failing the page.
|
|
914
988
|
const countPromise = include.includes('totalCount')
|
|
915
|
-
? Webhooks.countSubscriptions(db, owner).catch(() => undefined)
|
|
989
|
+
? Webhooks.countSubscriptions(db, owner, { access }).catch(() => undefined)
|
|
916
990
|
: undefined;
|
|
917
991
|
// Fetch one extra to detect a further page without a second round-trip.
|
|
918
992
|
// `page` translates to a positional slice of `(page - 1) * limit` rows.
|
|
919
993
|
const rows = await Webhooks.listSubscriptions(db, owner, {
|
|
994
|
+
access,
|
|
920
995
|
cursor,
|
|
921
996
|
limit: limit + 1,
|
|
922
997
|
offset: page !== undefined && page > 1 ? (page - 1) * limit : undefined,
|
|
@@ -976,9 +1051,10 @@ export function webhooks(options = {}) {
|
|
|
976
1051
|
const owner = ownerFor(c);
|
|
977
1052
|
if (!owner)
|
|
978
1053
|
return unauthorized(c);
|
|
1054
|
+
const access = accessFor(c);
|
|
979
1055
|
const { id } = c.req.valid('param');
|
|
980
1056
|
try {
|
|
981
|
-
const subscription = await Webhooks.getSubscription(db, owner, id);
|
|
1057
|
+
const subscription = await Webhooks.getSubscription(db, owner, id, { access });
|
|
982
1058
|
if (!subscription)
|
|
983
1059
|
return notFound(c);
|
|
984
1060
|
return c.json(Response.validated(schema.getWebhook.Response, toPublic(subscription)), 200);
|
|
@@ -1030,12 +1106,13 @@ export function webhooks(options = {}) {
|
|
|
1030
1106
|
const owner = ownerFor(c);
|
|
1031
1107
|
if (!owner)
|
|
1032
1108
|
return unauthorized(c);
|
|
1109
|
+
const access = accessFor(c);
|
|
1033
1110
|
const { id } = c.req.valid('param');
|
|
1034
1111
|
const { cursor, include, limit, page } = c.req.valid('query');
|
|
1035
1112
|
try {
|
|
1036
1113
|
// Owner-scope first: resolving the subscription under the caller's owner
|
|
1037
1114
|
// key 404s ids they don't own (indistinguishable from "doesn't exist").
|
|
1038
|
-
const subscription = await Webhooks.getSubscription(db, owner, id);
|
|
1115
|
+
const subscription = await Webhooks.getSubscription(db, owner, id, { access });
|
|
1039
1116
|
if (!subscription)
|
|
1040
1117
|
return notFound(c);
|
|
1041
1118
|
// `totalCount` is opt-in and exact (the delivery log is TTL-bounded);
|
|
@@ -1102,10 +1179,11 @@ export function webhooks(options = {}) {
|
|
|
1102
1179
|
const owner = ownerFor(c);
|
|
1103
1180
|
if (!owner)
|
|
1104
1181
|
return unauthorized(c);
|
|
1182
|
+
const access = accessFor(c);
|
|
1105
1183
|
const { id } = c.req.valid('param');
|
|
1106
1184
|
try {
|
|
1107
1185
|
// Owner-scope first so unowned/unknown ids are indistinguishable (404).
|
|
1108
|
-
const subscription = await Webhooks.getSubscription(db, owner, id);
|
|
1186
|
+
const subscription = await Webhooks.getSubscription(db, owner, id, { access });
|
|
1109
1187
|
if (!subscription)
|
|
1110
1188
|
return notFound(c);
|
|
1111
1189
|
// Inline delivery (not the queue) so the caller gets an immediate
|
|
@@ -1159,10 +1237,11 @@ export function webhooks(options = {}) {
|
|
|
1159
1237
|
const owner = ownerFor(c);
|
|
1160
1238
|
if (!owner)
|
|
1161
1239
|
return unauthorized(c);
|
|
1240
|
+
const access = accessFor(c);
|
|
1162
1241
|
const { deliveryId, id } = c.req.valid('param');
|
|
1163
1242
|
try {
|
|
1164
1243
|
// Owner-scope the subscription first so unowned ids 404 like elsewhere.
|
|
1165
|
-
const subscription = await Webhooks.getSubscription(db, owner, id);
|
|
1244
|
+
const subscription = await Webhooks.getSubscription(db, owner, id, { access });
|
|
1166
1245
|
if (!subscription)
|
|
1167
1246
|
return notFound(c);
|
|
1168
1247
|
const delivery = await Webhooks.getDelivery(db, id, deliveryId);
|
|
@@ -1240,6 +1319,7 @@ export function webhooks(options = {}) {
|
|
|
1240
1319
|
const owner = ownerFor(c);
|
|
1241
1320
|
if (!owner)
|
|
1242
1321
|
return unauthorized(c);
|
|
1322
|
+
const access = accessFor(c);
|
|
1243
1323
|
const { id } = c.req.valid('param');
|
|
1244
1324
|
const patch = c.req.valid('json');
|
|
1245
1325
|
try {
|
|
@@ -1249,12 +1329,12 @@ export function webhooks(options = {}) {
|
|
|
1249
1329
|
// persisting — otherwise a bad filter would silently widen the match
|
|
1250
1330
|
// set at scan time.
|
|
1251
1331
|
if (patch.filters !== undefined) {
|
|
1252
|
-
const existing = await Webhooks.getSubscription(db, owner, id);
|
|
1332
|
+
const existing = await Webhooks.getSubscription(db, owner, id, { access });
|
|
1253
1333
|
if (!existing)
|
|
1254
1334
|
return notFound(c);
|
|
1255
1335
|
parseFilters(existing.eventType, patch.filters);
|
|
1256
1336
|
}
|
|
1257
|
-
const subscription = await Webhooks.updateSubscription(db, owner, id, patch);
|
|
1337
|
+
const subscription = await Webhooks.updateSubscription(db, owner, id, patch, { access });
|
|
1258
1338
|
if (!subscription)
|
|
1259
1339
|
return notFound(c);
|
|
1260
1340
|
return c.json(Response.validated(schema.updateWebhook.Response, toPublic(subscription)), 200);
|
|
@@ -1298,9 +1378,10 @@ export function webhooks(options = {}) {
|
|
|
1298
1378
|
const owner = ownerFor(c);
|
|
1299
1379
|
if (!owner)
|
|
1300
1380
|
return unauthorized(c);
|
|
1381
|
+
const access = accessFor(c);
|
|
1301
1382
|
const { id } = c.req.valid('param');
|
|
1302
1383
|
try {
|
|
1303
|
-
const deleted = await Webhooks.deleteSubscription(db, owner, id);
|
|
1384
|
+
const deleted = await Webhooks.deleteSubscription(db, owner, id, { access });
|
|
1304
1385
|
if (!deleted)
|
|
1305
1386
|
return notFound(c);
|
|
1306
1387
|
return c.json(Response.validated(schema.deleteWebhook.Response, { id }), 200);
|
|
@@ -1310,459 +1391,6 @@ export function webhooks(options = {}) {
|
|
|
1310
1391
|
}
|
|
1311
1392
|
}));
|
|
1312
1393
|
}
|
|
1313
|
-
/**
|
|
1314
|
-
* Largest representable in-block index, used as an end-of-block cursor sentinel
|
|
1315
|
-
* so a fully-scanned block advances the cursor strictly past it (no row index
|
|
1316
|
-
* can equal it).
|
|
1317
|
-
*/
|
|
1318
|
-
const endOfBlockIndex = 2_147_483_647;
|
|
1319
|
-
const scanPageSize = 100;
|
|
1320
|
-
/** Concurrent per-subscription scans and cursor writes per poll round; caps RPC and DB fan-out. */
|
|
1321
|
-
const pollConcurrency = 8;
|
|
1322
|
-
/** Event types scanned together when their combined state fits one existing scan round. */
|
|
1323
|
-
const pollEventTypeConcurrency = 2;
|
|
1324
|
-
/** Maximum subscription states retained by concurrent event-type scan batches. */
|
|
1325
|
-
const pollStateLimit = WebhookTransfer.scanCohortSize * pollConcurrency;
|
|
1326
|
-
/** Schedules asynchronous work through one shared concurrency bound. */
|
|
1327
|
-
function createLimiter(limit) {
|
|
1328
|
-
let active = 0;
|
|
1329
|
-
const waiting = [];
|
|
1330
|
-
return async (fn) => {
|
|
1331
|
-
if (active >= limit)
|
|
1332
|
-
await new Promise((resolve) => waiting.push(resolve));
|
|
1333
|
-
else
|
|
1334
|
-
active++;
|
|
1335
|
-
try {
|
|
1336
|
-
return await fn();
|
|
1337
|
-
}
|
|
1338
|
-
finally {
|
|
1339
|
-
const next = waiting.shift();
|
|
1340
|
-
if (next)
|
|
1341
|
-
next();
|
|
1342
|
-
else
|
|
1343
|
-
active--;
|
|
1344
|
-
}
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
/** Runs `fn` over `items` with at most `limit` in flight, preserving order. */
|
|
1348
|
-
async function mapWithLimit(items, limit, fn) {
|
|
1349
|
-
const results = [];
|
|
1350
|
-
let next = 0;
|
|
1351
|
-
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
1352
|
-
while (next < items.length) {
|
|
1353
|
-
const index = next++;
|
|
1354
|
-
try {
|
|
1355
|
-
results[index] = { status: 'fulfilled', value: await fn(items[index]) };
|
|
1356
|
-
}
|
|
1357
|
-
catch (reason) {
|
|
1358
|
-
results[index] = { reason, status: 'rejected' };
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
}));
|
|
1362
|
-
return results;
|
|
1363
|
-
}
|
|
1364
|
-
/** Returns `candidate` only when it is lexicographically after `current`. */
|
|
1365
|
-
function laterCursor(current, candidate) {
|
|
1366
|
-
const before = Cursor.decode(current, ['int', 'int']);
|
|
1367
|
-
const after = Cursor.decode(candidate, ['int', 'int']);
|
|
1368
|
-
if (!after)
|
|
1369
|
-
return current;
|
|
1370
|
-
if (!before)
|
|
1371
|
-
return candidate;
|
|
1372
|
-
const [beforeBlock, beforeIndex] = before;
|
|
1373
|
-
const [afterBlock, afterIndex] = after;
|
|
1374
|
-
return afterBlock > beforeBlock || (afterBlock === beforeBlock && afterIndex > beforeIndex)
|
|
1375
|
-
? candidate
|
|
1376
|
-
: current;
|
|
1377
|
-
}
|
|
1378
|
-
/**
|
|
1379
|
-
* Polls the chain over RPC for new events and dispatches a signed envelope per
|
|
1380
|
-
* matching row, advancing each active subscription's `(block, index)` keyset
|
|
1381
|
-
* cursor.
|
|
1382
|
-
*
|
|
1383
|
-
* Every event type scans a **narrow confirmed window** `[cursor, head -
|
|
1384
|
-
* confirmations]` directly over JSON-RPC (`eth_getLogs`,
|
|
1385
|
-
* `eth_getBlockByNumber`) so webhook delivery does not wait for an indexer.
|
|
1386
|
-
*
|
|
1387
|
-
* The chain head is injectable (tests pin a fixed historical window); only
|
|
1388
|
-
* blocks at least `confirmations` behind head are emitted, giving at-least-once,
|
|
1389
|
-
* finalized-ish semantics (re-delivery overlap is harmless because the receiver
|
|
1390
|
-
* dedupes on the stable `event.id`).
|
|
1391
|
-
*
|
|
1392
|
-
* Cursor advances only past successfully dispatched rows, so a dispatch failure
|
|
1393
|
-
* pauses that subscription at the last good position and the next tick resumes.
|
|
1394
|
-
* Scan failures leave that subscription unchanged; the first error is rethrown
|
|
1395
|
-
* after the remaining subscriptions run.
|
|
1396
|
-
*/
|
|
1397
|
-
export async function poll(options) {
|
|
1398
|
-
const { batchSize = 200, cache: cache_metadata = Store.memory(), chainId, client, confirmations = 1, db, eventTypes: eventTypes_input = [
|
|
1399
|
-
'token:transfer',
|
|
1400
|
-
'transaction:included',
|
|
1401
|
-
'log:emitted',
|
|
1402
|
-
'block:created',
|
|
1403
|
-
], maxBlocks = 1_000, } = options;
|
|
1404
|
-
const now = options.now ?? (() => new Date());
|
|
1405
|
-
options.signal?.throwIfAborted();
|
|
1406
|
-
// Default (self-host) transport: deliver inline and record the outcome so a
|
|
1407
|
-
// sustained-failure subscription auto-disables. On Cloudflare, callers pass a
|
|
1408
|
-
// Queue producer instead and the consumer runs `deliverAndRecord`.
|
|
1409
|
-
const dispatch = options.dispatch ??
|
|
1410
|
-
(({ envelope, subscription }) => Webhooks.deliverAndRecord(db, subscription, envelope));
|
|
1411
|
-
const dispatchBatch = options.dispatchBatch;
|
|
1412
|
-
// A pinned `headBlockNumber` lets a head subscription supply the exact live
|
|
1413
|
-
// boundary and keeps historical tests deterministic.
|
|
1414
|
-
const override = options.headBlockNumber;
|
|
1415
|
-
let rpcHead = override === undefined ? undefined : Number(override);
|
|
1416
|
-
const resolveRpcHead = async () => (rpcHead ??= await getRpcHead(client));
|
|
1417
|
-
const headStartedAt = Date.now();
|
|
1418
|
-
const rpcSafeBlock = await (async () => {
|
|
1419
|
-
try {
|
|
1420
|
-
const safeBlock = Math.max(0, (await resolveRpcHead()) - confirmations);
|
|
1421
|
-
options.onStage?.({
|
|
1422
|
-
durationMs: Date.now() - headStartedAt,
|
|
1423
|
-
outcome: 'ok',
|
|
1424
|
-
stage: 'rpc_head',
|
|
1425
|
-
});
|
|
1426
|
-
return safeBlock;
|
|
1427
|
-
}
|
|
1428
|
-
catch (error) {
|
|
1429
|
-
options.onStage?.({
|
|
1430
|
-
durationMs: Date.now() - headStartedAt,
|
|
1431
|
-
outcome: 'error',
|
|
1432
|
-
stage: 'rpc_head',
|
|
1433
|
-
});
|
|
1434
|
-
throw error;
|
|
1435
|
-
}
|
|
1436
|
-
})();
|
|
1437
|
-
const result = {
|
|
1438
|
-
chainId,
|
|
1439
|
-
dispatched: 0,
|
|
1440
|
-
failed: 0,
|
|
1441
|
-
safeBlock: rpcSafeBlock,
|
|
1442
|
-
scanned: 0,
|
|
1443
|
-
subscriptions: 0,
|
|
1444
|
-
};
|
|
1445
|
-
let cursorLagBlocks = 0;
|
|
1446
|
-
const pollErrors = [];
|
|
1447
|
-
const eventTypes = [...new Set(eventTypes_input)];
|
|
1448
|
-
const subscriptionsStartedAt = Date.now();
|
|
1449
|
-
const pairs = await Webhooks.listActiveForChain(db, { chainId, eventTypes });
|
|
1450
|
-
options.signal?.throwIfAborted();
|
|
1451
|
-
options.onSubscriptions?.(pairs.length);
|
|
1452
|
-
options.onStage?.({
|
|
1453
|
-
durationMs: Date.now() - subscriptionsStartedAt,
|
|
1454
|
-
outcome: 'ok',
|
|
1455
|
-
stage: 'subscription_read',
|
|
1456
|
-
});
|
|
1457
|
-
const pairsByEventType = Map.groupBy(pairs, (pair) => pair.subscription.eventType);
|
|
1458
|
-
const eventTypes_active = eventTypes.filter((eventType) => (pairsByEventType.get(eventType)?.length ?? 0) > 0);
|
|
1459
|
-
const eventTypeConcurrency = pairs.length <= pollStateLimit
|
|
1460
|
-
? Math.min(pollEventTypeConcurrency, eventTypes_active.length)
|
|
1461
|
-
: Math.min(1, eventTypes_active.length);
|
|
1462
|
-
const scanRoundSize = Math.floor(pollStateLimit / Math.max(1, eventTypeConcurrency));
|
|
1463
|
-
const scheduleScan = createLimiter(pollConcurrency);
|
|
1464
|
-
const eventTypeResults = await mapWithLimit(eventTypes_active, eventTypeConcurrency, async (eventType) => {
|
|
1465
|
-
options.signal?.throwIfAborted();
|
|
1466
|
-
const safeBlock = rpcSafeBlock;
|
|
1467
|
-
const pairs = pairsByEventType.get(eventType) ?? [];
|
|
1468
|
-
const durations = {
|
|
1469
|
-
cursor_write: 0,
|
|
1470
|
-
enrich: 0,
|
|
1471
|
-
scan: 0,
|
|
1472
|
-
};
|
|
1473
|
-
const observed = new Set();
|
|
1474
|
-
let cursorWriteOutcome = 'ok';
|
|
1475
|
-
let scanOutcome = 'ok';
|
|
1476
|
-
const writeCursor = async (parameters) => {
|
|
1477
|
-
const startedAt = Date.now();
|
|
1478
|
-
observed.add('cursor_write');
|
|
1479
|
-
try {
|
|
1480
|
-
return await Webhooks.compareAndSetCursor(db, parameters);
|
|
1481
|
-
}
|
|
1482
|
-
catch (error) {
|
|
1483
|
-
cursorWriteOutcome = 'error';
|
|
1484
|
-
pollErrors.push(error);
|
|
1485
|
-
return false;
|
|
1486
|
-
}
|
|
1487
|
-
finally {
|
|
1488
|
-
durations.cursor_write += Date.now() - startedAt;
|
|
1489
|
-
}
|
|
1490
|
-
};
|
|
1491
|
-
let states = [];
|
|
1492
|
-
for (const pair of pairs) {
|
|
1493
|
-
const { subscription } = pair;
|
|
1494
|
-
result.subscriptions++;
|
|
1495
|
-
let filters;
|
|
1496
|
-
try {
|
|
1497
|
-
filters = parseFilters(subscription.eventType, subscription.filters);
|
|
1498
|
-
}
|
|
1499
|
-
catch (error) {
|
|
1500
|
-
if (!(error instanceof Webhooks.InvalidFilterError))
|
|
1501
|
-
throw error;
|
|
1502
|
-
options.signal?.throwIfAborted();
|
|
1503
|
-
// A breaking filter-schema removal must not poison every chain poll.
|
|
1504
|
-
// Disable the legacy subscription without widening its match set.
|
|
1505
|
-
await Webhooks.updateSubscription(db, subscription.owner, subscription.id, {
|
|
1506
|
-
status: 'disabled',
|
|
1507
|
-
});
|
|
1508
|
-
continue;
|
|
1509
|
-
}
|
|
1510
|
-
let cursor = pair.cursor;
|
|
1511
|
-
if (cursor === null) {
|
|
1512
|
-
// Legacy subscriptions without a creation checkpoint anchor at the
|
|
1513
|
-
// current confirmed head, avoiding a genesis backfill.
|
|
1514
|
-
await writeCursor({
|
|
1515
|
-
cursor: Cursor.encode([safeBlock, endOfBlockIndex]),
|
|
1516
|
-
expectedCursor: null,
|
|
1517
|
-
subscriptionId: subscription.id,
|
|
1518
|
-
});
|
|
1519
|
-
continue;
|
|
1520
|
-
}
|
|
1521
|
-
const checkpoint = Cursor.decode(cursor, ['int']);
|
|
1522
|
-
if (checkpoint) {
|
|
1523
|
-
// Creation checkpoints record the live head without finalizing it. Rebase
|
|
1524
|
-
// to this poller's confirmed boundary so replacement blocks stay eligible.
|
|
1525
|
-
const confirmedCursor = Cursor.encode([
|
|
1526
|
-
Math.max(0, checkpoint[0] - confirmations),
|
|
1527
|
-
endOfBlockIndex,
|
|
1528
|
-
]);
|
|
1529
|
-
const committed = await writeCursor({
|
|
1530
|
-
cursor: confirmedCursor,
|
|
1531
|
-
expectedCursor: cursor,
|
|
1532
|
-
subscriptionId: subscription.id,
|
|
1533
|
-
});
|
|
1534
|
-
if (!committed)
|
|
1535
|
-
continue;
|
|
1536
|
-
cursor = confirmedCursor;
|
|
1537
|
-
}
|
|
1538
|
-
const position = Cursor.decode(cursor, ['int', 'int']);
|
|
1539
|
-
if (position)
|
|
1540
|
-
cursorLagBlocks = Math.max(cursorLagBlocks, safeBlock - position[0]);
|
|
1541
|
-
const window = scanWindow(cursor, safeBlock, maxBlocks);
|
|
1542
|
-
if (window && window.to < safeBlock)
|
|
1543
|
-
result.hasMore = true;
|
|
1544
|
-
states.push({
|
|
1545
|
-
cursor,
|
|
1546
|
-
filters,
|
|
1547
|
-
remaining: batchSize,
|
|
1548
|
-
safeBlock: window?.to ?? safeBlock,
|
|
1549
|
-
subscription,
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
// Every subscription gets one bounded page before any subscription gets a
|
|
1553
|
-
// continuation page, preserving fairness while allowing catch-up.
|
|
1554
|
-
while (states.length > 0) {
|
|
1555
|
-
options.signal?.throwIfAborted();
|
|
1556
|
-
const continuations = [];
|
|
1557
|
-
const round = states;
|
|
1558
|
-
for (let offset = 0; offset < round.length; offset += scanRoundSize) {
|
|
1559
|
-
const batch = round.slice(offset, offset + scanRoundSize);
|
|
1560
|
-
const windows = batch.flatMap((state) => {
|
|
1561
|
-
const window = scanWindow(state.cursor, state.safeBlock, maxBlocks);
|
|
1562
|
-
return window ? [window] : [];
|
|
1563
|
-
});
|
|
1564
|
-
const maxWindowBlocks = Math.max(0, ...windows.map((window) => window.to - window.from + 1));
|
|
1565
|
-
const totalWindowBlocks = windows.reduce((total, window) => total + window.to - window.from + 1, 0);
|
|
1566
|
-
options.onScanCheckpoint?.({
|
|
1567
|
-
concurrency: pollConcurrency,
|
|
1568
|
-
eventType,
|
|
1569
|
-
maxWindowBlocks,
|
|
1570
|
-
phase: 'start',
|
|
1571
|
-
rpcMethod: scanRpcMethod(eventType),
|
|
1572
|
-
subscriptionLimit: scanRoundSize,
|
|
1573
|
-
subscriptionCount: batch.length,
|
|
1574
|
-
totalWindowBlocks,
|
|
1575
|
-
});
|
|
1576
|
-
const scanStartedAt = Date.now();
|
|
1577
|
-
const pages = await scanSubscriptions({
|
|
1578
|
-
client,
|
|
1579
|
-
eventType,
|
|
1580
|
-
maxBlocks,
|
|
1581
|
-
schedule: scheduleScan,
|
|
1582
|
-
states: batch,
|
|
1583
|
-
});
|
|
1584
|
-
options.onScanCheckpoint?.({
|
|
1585
|
-
concurrency: pollConcurrency,
|
|
1586
|
-
durationMs: Date.now() - scanStartedAt,
|
|
1587
|
-
eventType,
|
|
1588
|
-
failedSubscriptions: pages.filter((page) => page.status === 'rejected').length,
|
|
1589
|
-
matchedItems: pages.reduce((total, page) => total + (page.status === 'fulfilled' ? page.value.items.length : 0), 0),
|
|
1590
|
-
maxWindowBlocks,
|
|
1591
|
-
outcome: scanResultOutcome(pages),
|
|
1592
|
-
phase: 'complete',
|
|
1593
|
-
rpcMethod: scanRpcMethod(eventType),
|
|
1594
|
-
subscriptionLimit: scanRoundSize,
|
|
1595
|
-
subscriptionCount: batch.length,
|
|
1596
|
-
totalWindowBlocks,
|
|
1597
|
-
});
|
|
1598
|
-
options.signal?.throwIfAborted();
|
|
1599
|
-
durations.scan += Date.now() - scanStartedAt;
|
|
1600
|
-
observed.add('scan');
|
|
1601
|
-
if (pages.some((page) => page.status === 'rejected'))
|
|
1602
|
-
scanOutcome = 'error';
|
|
1603
|
-
const busy = [];
|
|
1604
|
-
const idle = [];
|
|
1605
|
-
for (let i = 0; i < batch.length; i++) {
|
|
1606
|
-
const state = batch[i];
|
|
1607
|
-
const settled = pages[i];
|
|
1608
|
-
if (settled.status === 'rejected') {
|
|
1609
|
-
pollErrors.push(settled.reason);
|
|
1610
|
-
continue;
|
|
1611
|
-
}
|
|
1612
|
-
result.scanned += settled.value.items.length;
|
|
1613
|
-
state.remaining -= settled.value.items.length;
|
|
1614
|
-
(settled.value.items.length > 0 ? busy : idle).push({ page: settled.value, state });
|
|
1615
|
-
}
|
|
1616
|
-
// Empty pages only advance their cursor: no dispatch order to preserve,
|
|
1617
|
-
// so one statement commits the cohort while retaining per-row CAS semantics.
|
|
1618
|
-
const cursorWritesStartedAt = Date.now();
|
|
1619
|
-
const candidates = idle.flatMap(({ page, state }) => {
|
|
1620
|
-
options.signal?.throwIfAborted();
|
|
1621
|
-
const candidate = page.scannedThrough !== undefined
|
|
1622
|
-
? Cursor.encode([page.scannedThrough, endOfBlockIndex])
|
|
1623
|
-
: page.hasMore
|
|
1624
|
-
? undefined
|
|
1625
|
-
: Cursor.encode([state.safeBlock, endOfBlockIndex]);
|
|
1626
|
-
const cursor = candidate ? laterCursor(state.cursor, candidate) : state.cursor;
|
|
1627
|
-
return cursor === state.cursor ? [] : [{ cursor, state }];
|
|
1628
|
-
});
|
|
1629
|
-
if (candidates.length > 0) {
|
|
1630
|
-
const committed = await (async () => {
|
|
1631
|
-
try {
|
|
1632
|
-
return await Webhooks.compareAndSetCursors(db, {
|
|
1633
|
-
cursors: candidates.map(({ cursor, state }) => ({
|
|
1634
|
-
cursor,
|
|
1635
|
-
expectedCursor: state.cursor,
|
|
1636
|
-
subscriptionId: state.subscription.id,
|
|
1637
|
-
})),
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
catch (error) {
|
|
1641
|
-
cursorWriteOutcome = 'error';
|
|
1642
|
-
pollErrors.push(error);
|
|
1643
|
-
return new Set();
|
|
1644
|
-
}
|
|
1645
|
-
})();
|
|
1646
|
-
for (const { cursor, state } of candidates)
|
|
1647
|
-
if (committed.has(state.subscription.id))
|
|
1648
|
-
state.cursor = cursor;
|
|
1649
|
-
durations.cursor_write += Date.now() - cursorWritesStartedAt;
|
|
1650
|
-
observed.add('cursor_write');
|
|
1651
|
-
}
|
|
1652
|
-
for (const { page, state } of busy) {
|
|
1653
|
-
options.signal?.throwIfAborted();
|
|
1654
|
-
const { subscription } = state;
|
|
1655
|
-
let accepted;
|
|
1656
|
-
let failed = false;
|
|
1657
|
-
const buildDispatchable = async (item) => {
|
|
1658
|
-
options.signal?.throwIfAborted();
|
|
1659
|
-
// Token metadata is best-effort for webhooks: transfers use it for display,
|
|
1660
|
-
// while transactions expose fee-token references without blocking delivery.
|
|
1661
|
-
const enrichStartedAt = Date.now();
|
|
1662
|
-
const data = await enrichEventData(eventType, item.data, {
|
|
1663
|
-
cache: cache_metadata,
|
|
1664
|
-
chainId,
|
|
1665
|
-
client,
|
|
1666
|
-
});
|
|
1667
|
-
durations.enrich += Date.now() - enrichStartedAt;
|
|
1668
|
-
observed.add('enrich');
|
|
1669
|
-
options.signal?.throwIfAborted();
|
|
1670
|
-
const envelope = Webhooks.buildEnvelope({
|
|
1671
|
-
blockNumber: item.cursor[0],
|
|
1672
|
-
createdAt: now(),
|
|
1673
|
-
data,
|
|
1674
|
-
logIndex: item.cursor[1],
|
|
1675
|
-
subscription,
|
|
1676
|
-
});
|
|
1677
|
-
return { envelope, subscription };
|
|
1678
|
-
};
|
|
1679
|
-
if (dispatchBatch) {
|
|
1680
|
-
const dispatchables = [];
|
|
1681
|
-
for (const item of page.items)
|
|
1682
|
-
dispatchables.push(await buildDispatchable(item));
|
|
1683
|
-
options.signal?.throwIfAborted();
|
|
1684
|
-
const delivered = await dispatchBatch(dispatchables, { db });
|
|
1685
|
-
if (delivered.ok) {
|
|
1686
|
-
accepted = page.items.at(-1)?.cursor;
|
|
1687
|
-
result.dispatched += dispatchables.length;
|
|
1688
|
-
}
|
|
1689
|
-
else {
|
|
1690
|
-
result.failed += dispatchables.length;
|
|
1691
|
-
failed = true;
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
else {
|
|
1695
|
-
for (const item of page.items) {
|
|
1696
|
-
const dispatchable = await buildDispatchable(item);
|
|
1697
|
-
const delivered = await dispatch(dispatchable);
|
|
1698
|
-
if (!delivered.ok) {
|
|
1699
|
-
result.failed++;
|
|
1700
|
-
failed = true;
|
|
1701
|
-
break;
|
|
1702
|
-
}
|
|
1703
|
-
accepted = item.cursor;
|
|
1704
|
-
result.dispatched++;
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
// Persist every accepted page before another subscription begins its
|
|
1708
|
-
// continuation, so a later scan failure retains completed work.
|
|
1709
|
-
const candidate = (() => {
|
|
1710
|
-
if (failed)
|
|
1711
|
-
return accepted ? Cursor.encode([...accepted]) : undefined;
|
|
1712
|
-
if (page.scannedThrough !== undefined)
|
|
1713
|
-
return Cursor.encode([page.scannedThrough, endOfBlockIndex]);
|
|
1714
|
-
if (accepted)
|
|
1715
|
-
return Cursor.encode([...accepted]);
|
|
1716
|
-
if (!page.hasMore)
|
|
1717
|
-
return Cursor.encode([state.safeBlock, endOfBlockIndex]);
|
|
1718
|
-
return undefined;
|
|
1719
|
-
})();
|
|
1720
|
-
const cursor = candidate ? laterCursor(state.cursor, candidate) : state.cursor;
|
|
1721
|
-
const progressed = cursor !== state.cursor;
|
|
1722
|
-
let committed = false;
|
|
1723
|
-
if (progressed) {
|
|
1724
|
-
committed = await writeCursor({
|
|
1725
|
-
cursor,
|
|
1726
|
-
expectedCursor: state.cursor,
|
|
1727
|
-
subscriptionId: subscription.id,
|
|
1728
|
-
});
|
|
1729
|
-
if (committed)
|
|
1730
|
-
state.cursor = cursor;
|
|
1731
|
-
}
|
|
1732
|
-
if (!failed && page.hasMore === true) {
|
|
1733
|
-
if (committed && state.remaining > 0)
|
|
1734
|
-
continuations.push(state);
|
|
1735
|
-
else
|
|
1736
|
-
result.hasMore = true;
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
states = continuations;
|
|
1741
|
-
}
|
|
1742
|
-
for (const stage of observed) {
|
|
1743
|
-
const outcome = (() => {
|
|
1744
|
-
if (stage === 'cursor_write')
|
|
1745
|
-
return cursorWriteOutcome;
|
|
1746
|
-
if (stage === 'scan')
|
|
1747
|
-
return scanOutcome;
|
|
1748
|
-
return 'ok';
|
|
1749
|
-
})();
|
|
1750
|
-
options.onStage?.({
|
|
1751
|
-
durationMs: durations[stage],
|
|
1752
|
-
eventType,
|
|
1753
|
-
outcome,
|
|
1754
|
-
stage,
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
});
|
|
1758
|
-
for (const result of eventTypeResults)
|
|
1759
|
-
if (result.status === 'rejected')
|
|
1760
|
-
pollErrors.push(result.reason);
|
|
1761
|
-
options.onCursorLag?.(cursorLagBlocks);
|
|
1762
|
-
if (pollErrors.length > 0)
|
|
1763
|
-
throw pollErrors[0];
|
|
1764
|
-
return result;
|
|
1765
|
-
}
|
|
1766
1394
|
/**
|
|
1767
1395
|
* Attaches best-effort `{ symbol, decimals }` token metadata to a
|
|
1768
1396
|
* `token:transfer` row so destinations can render human amounts. Reads the row's
|
|
@@ -1849,62 +1477,9 @@ async function withFeeTokenMetadata(data, deps) {
|
|
|
1849
1477
|
export async function getRpcHead(client) {
|
|
1850
1478
|
return Number(await client.getBlockNumber());
|
|
1851
1479
|
}
|
|
1852
|
-
/** Groups aligned cursors only when their bounded upstream scans are equivalent. */
|
|
1853
|
-
async function scanSubscriptions(options) {
|
|
1854
|
-
const { client, eventType, maxBlocks, schedule, states } = options;
|
|
1855
|
-
if (eventType !== 'token:transfer')
|
|
1856
|
-
return scanIndependent({ client, eventType, maxBlocks, schedule, states });
|
|
1857
|
-
return WebhookTransfer.scan({
|
|
1858
|
-
client,
|
|
1859
|
-
concurrency: pollConcurrency,
|
|
1860
|
-
maxBlocks,
|
|
1861
|
-
schedule,
|
|
1862
|
-
states: states.map((state) => ({
|
|
1863
|
-
filters: state.filters,
|
|
1864
|
-
limit: Math.min(scanPageSize, state.remaining),
|
|
1865
|
-
window: scanWindow(state.cursor, state.safeBlock, maxBlocks),
|
|
1866
|
-
})),
|
|
1867
|
-
});
|
|
1868
|
-
}
|
|
1869
|
-
/** Returns the bounded JSON-RPC method used by an event scanner. */
|
|
1870
|
-
function scanRpcMethod(eventType) {
|
|
1871
|
-
return eventType === 'block:created' || eventType === 'transaction:included'
|
|
1872
|
-
? 'eth_getBlockByNumber'
|
|
1873
|
-
: 'eth_getLogs';
|
|
1874
|
-
}
|
|
1875
|
-
/** Classifies a completed batch without exposing an unbounded upstream error. */
|
|
1876
|
-
function scanResultOutcome(pages) {
|
|
1877
|
-
const rejected = pages.filter((page) => page.status === 'rejected');
|
|
1878
|
-
if (rejected.some((page) => page.reason instanceof BlockUnavailableError))
|
|
1879
|
-
return 'missing';
|
|
1880
|
-
if (rejected.length > 0)
|
|
1881
|
-
return 'error';
|
|
1882
|
-
return pages.every((page) => page.status === 'fulfilled' && page.value.items.length === 0)
|
|
1883
|
-
? 'empty'
|
|
1884
|
-
: 'ok';
|
|
1885
|
-
}
|
|
1886
|
-
/** Scans CPU-matched streams per subscription while sharing concurrent block reads. */
|
|
1887
|
-
function scanIndependent(options) {
|
|
1888
|
-
const { client, eventType, maxBlocks, schedule, states } = options;
|
|
1889
|
-
const cache = createChainCache(client);
|
|
1890
|
-
return mapWithLimit(states, pollConcurrency, (state) => schedule(() => {
|
|
1891
|
-
const shared = {
|
|
1892
|
-
batchSize: Math.min(scanPageSize, state.remaining),
|
|
1893
|
-
cache,
|
|
1894
|
-
cursor: state.cursor,
|
|
1895
|
-
filters: state.filters,
|
|
1896
|
-
maxBlocks,
|
|
1897
|
-
safeBlock: state.safeBlock,
|
|
1898
|
-
};
|
|
1899
|
-
if (eventType === 'block:created')
|
|
1900
|
-
return scanBlocks(shared);
|
|
1901
|
-
if (eventType === 'log:emitted')
|
|
1902
|
-
return scanLogs({ ...shared, client });
|
|
1903
|
-
return scanTxs(shared);
|
|
1904
|
-
}));
|
|
1905
|
-
}
|
|
1906
1480
|
const filterSchemas = {
|
|
1907
1481
|
'block:created': schema.BlockFilters,
|
|
1482
|
+
'funding:deposit.updated': schema.FundingDepositFilters,
|
|
1908
1483
|
'log:emitted': schema.LogFilters,
|
|
1909
1484
|
'token:transfer': schema.TransferFilters,
|
|
1910
1485
|
'transaction:included': schema.TxFilters,
|
|
@@ -1912,7 +1487,7 @@ const filterSchemas = {
|
|
|
1912
1487
|
/**
|
|
1913
1488
|
* Validates `filters` against the schema for `eventType`, **failing closed**: an
|
|
1914
1489
|
* invalid filter throws {@link Webhooks.InvalidFilterError} instead of degrading
|
|
1915
|
-
* to `{}` (a firehose). Used at create/patch time and by
|
|
1490
|
+
* to `{}` (a firehose). Used at create/patch time and by {@link scanBlock} so a stored
|
|
1916
1491
|
* bad filter can never silently widen the match set.
|
|
1917
1492
|
*/
|
|
1918
1493
|
export function parseFilters(eventType, filters) {
|
|
@@ -1962,7 +1537,7 @@ function operandEquals(value, target, kind) {
|
|
|
1962
1537
|
* The JS counterpart of the old SQL `applyOperator`: evaluates one parsed filter
|
|
1963
1538
|
* operator (from {@link schema.eq}/{@link schema.compare}/{@link schema.bytes})
|
|
1964
1539
|
* against a single `0x`-hex field value pulled from an RPC object. The one place
|
|
1965
|
-
* operator semantics live, shared by every
|
|
1540
|
+
* operator semantics live, shared by every event matcher. Returns `false` for a
|
|
1966
1541
|
* missing field so an absent value never matches (fail-closed).
|
|
1967
1542
|
*
|
|
1968
1543
|
* - bare value / `{ eq }` → equality
|
|
@@ -2021,74 +1596,10 @@ function matchesCalls(calls, op) {
|
|
|
2021
1596
|
}
|
|
2022
1597
|
return has(op);
|
|
2023
1598
|
}
|
|
2024
|
-
// RPC window helpers + per-tick lazy cache.
|
|
2025
|
-
/** Renders a block number as a `0x`-hex tag for JSON-RPC params. */
|
|
2026
|
-
const blockTag = (n) => `0x${n.toString(16)}`;
|
|
2027
|
-
/** Issues a JSON-RPC request through the viem client for a method viem can't type. */
|
|
2028
|
-
function rpc(client, method, params) {
|
|
2029
|
-
return client.request({
|
|
2030
|
-
method,
|
|
2031
|
-
params: [...params],
|
|
2032
|
-
});
|
|
2033
|
-
}
|
|
2034
1599
|
/**
|
|
2035
|
-
*
|
|
2036
|
-
* `(cb, ci)` were dispatched: when `ci` is the end-of-block sentinel the whole
|
|
2037
|
-
* block `cb` is done and scanning resumes at `cb + 1`; otherwise block `cb` is
|
|
2038
|
-
* re-scanned (skipping indices `<= ci`). `to` is capped by both `safeBlock` and
|
|
2039
|
-
* the per-tick backfill cap. Returns `undefined` when the subscription is
|
|
2040
|
-
* already caught up (nothing to scan, cursor unchanged).
|
|
2041
|
-
*/
|
|
2042
|
-
function scanWindow(cursor, safeBlock, maxBlocks) {
|
|
2043
|
-
const decoded = cursor ? Cursor.decode(cursor, ['int', 'int']) : undefined;
|
|
2044
|
-
const cb = decoded ? decoded[0] : 0;
|
|
2045
|
-
const ci = decoded ? decoded[1] : endOfBlockIndex;
|
|
2046
|
-
const from = ci >= endOfBlockIndex ? cb + 1 : cb;
|
|
2047
|
-
if (from > safeBlock)
|
|
2048
|
-
return undefined;
|
|
2049
|
-
const to = Math.min(safeBlock, from + maxBlocks - 1);
|
|
2050
|
-
return { from, skip: [cb, ci], to };
|
|
2051
|
-
}
|
|
2052
|
-
/** True when `(block, index)` is at or before the already-dispatched cursor. */
|
|
2053
|
-
function atOrBefore(block, index, skip) {
|
|
2054
|
-
return block < skip[0] || (block === skip[0] && index <= skip[1]);
|
|
2055
|
-
}
|
|
2056
|
-
/** Builds a per-batch {@link ChainCache} bound to a viem client. */
|
|
2057
|
-
function createChainCache(client) {
|
|
2058
|
-
const headers = new Map();
|
|
2059
|
-
const blocks = new Map();
|
|
2060
|
-
const singleFlight = (pending, key, load) => {
|
|
2061
|
-
const existing = pending.get(key);
|
|
2062
|
-
if (existing)
|
|
2063
|
-
return existing;
|
|
2064
|
-
const promise = load().finally(() => pending.delete(key));
|
|
2065
|
-
pending.set(key, promise);
|
|
2066
|
-
return promise;
|
|
2067
|
-
};
|
|
2068
|
-
return {
|
|
2069
|
-
block(n) {
|
|
2070
|
-
return singleFlight(blocks, n, () =>
|
|
2071
|
-
// The transaction payload reuses `humanizeTransaction`, which consumes
|
|
2072
|
-
// the raw RPC tx shape, so the full block stays on the untyped path.
|
|
2073
|
-
rpc(client, 'eth_getBlockByNumber', [
|
|
2074
|
-
blockTag(n),
|
|
2075
|
-
true,
|
|
2076
|
-
]).then((block) => {
|
|
2077
|
-
if (!block)
|
|
2078
|
-
throw new BlockUnavailableError(n);
|
|
2079
|
-
return block;
|
|
2080
|
-
}));
|
|
2081
|
-
},
|
|
2082
|
-
header(n) {
|
|
2083
|
-
return singleFlight(headers, n, () => client.getBlock({ blockNumber: BigInt(n), includeTransactions: false }));
|
|
2084
|
-
},
|
|
2085
|
-
};
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Builds the delivered `log:emitted` payload from a viem-formatted log plus the
|
|
1600
|
+
* Builds the delivered `log:emitted` payload from a viem-shaped log plus the
|
|
2089
1601
|
* block timestamp. Returns `undefined` when a required field is missing/invalid
|
|
2090
|
-
* (the row is skipped).
|
|
2091
|
-
* already decoded `args`/`eventName`, so this just serializes them.
|
|
1602
|
+
* (the row is skipped). Decoded `args`/`eventName` are serialized when present.
|
|
2092
1603
|
*/
|
|
2093
1604
|
function toLogEvent(log, timestamp, signature, event) {
|
|
2094
1605
|
const address = Schema.Address.safeParse(log.address);
|
|
@@ -2153,7 +1664,7 @@ function pushHex(op) {
|
|
|
2153
1664
|
return value;
|
|
2154
1665
|
return undefined;
|
|
2155
1666
|
}
|
|
2156
|
-
/** Narrows an `eq`/`in` address operator into
|
|
1667
|
+
/** Narrows an `eq`/`in` address operator into a pushdown-capable address value. */
|
|
2157
1668
|
function pushAddress(op) {
|
|
2158
1669
|
return pushHex(op);
|
|
2159
1670
|
}
|
|
@@ -2169,7 +1680,7 @@ function parseEvent(signature) {
|
|
|
2169
1680
|
return undefined;
|
|
2170
1681
|
}
|
|
2171
1682
|
}
|
|
2172
|
-
/** Returns exact indexed arguments
|
|
1683
|
+
/** Returns exact indexed arguments usable as a selective log anchor. */
|
|
2173
1684
|
function pushEventArgs(args, event) {
|
|
2174
1685
|
const pushed = {};
|
|
2175
1686
|
for (const [name, op] of Object.entries(args ?? {})) {
|
|
@@ -2180,7 +1691,7 @@ function pushEventArgs(args, event) {
|
|
|
2180
1691
|
}
|
|
2181
1692
|
return pushed;
|
|
2182
1693
|
}
|
|
2183
|
-
/** Checks that the accepted filter
|
|
1694
|
+
/** Checks that the accepted filter carries a selective anchor rather than matching every log. */
|
|
2184
1695
|
function hasLogPushdown(f) {
|
|
2185
1696
|
if (pushAddress(f.address) !== undefined)
|
|
2186
1697
|
return true;
|
|
@@ -2190,19 +1701,7 @@ function hasLogPushdown(f) {
|
|
|
2190
1701
|
return secondaryTopic || Object.keys(pushEventArgs(f.args, event)).length > 0;
|
|
2191
1702
|
return pushHex(f.topic0) !== undefined && secondaryTopic;
|
|
2192
1703
|
}
|
|
2193
|
-
/**
|
|
2194
|
-
function pushTopics(f, event) {
|
|
2195
|
-
const topics = [
|
|
2196
|
-
event ? encodeEventTopics({ abi: [event] })[0] : pushHex(f.topic0),
|
|
2197
|
-
pushHex(f.topic1),
|
|
2198
|
-
pushHex(f.topic2),
|
|
2199
|
-
pushHex(f.topic3),
|
|
2200
|
-
];
|
|
2201
|
-
while (topics.length > 0 && topics.at(-1) === undefined)
|
|
2202
|
-
topics.pop();
|
|
2203
|
-
return topics.map((topic) => topic ?? null);
|
|
2204
|
-
}
|
|
2205
|
-
/** Converts one raw JSON-RPC log into the viem-shaped scanner input. */
|
|
1704
|
+
/** Converts one raw JSON-RPC log into the viem-shaped matcher input. */
|
|
2206
1705
|
function fromRpcLog(log) {
|
|
2207
1706
|
return {
|
|
2208
1707
|
address: log.address,
|
|
@@ -2244,64 +1743,6 @@ export async function enrichEventData(eventType, data, options) {
|
|
|
2244
1743
|
return withFeeTokenMetadata(data, options);
|
|
2245
1744
|
return data;
|
|
2246
1745
|
}
|
|
2247
|
-
/** Scans contract logs for one subscription, then re-checks every filter in memory. */
|
|
2248
|
-
async function scanLogs(options) {
|
|
2249
|
-
const { batchSize, cache, client, cursor, filters, maxBlocks, safeBlock } = options;
|
|
2250
|
-
const f = filters;
|
|
2251
|
-
const window = scanWindow(cursor, safeBlock, maxBlocks);
|
|
2252
|
-
if (!window)
|
|
2253
|
-
return { items: [], scannedThrough: undefined };
|
|
2254
|
-
const event = parseEvent(f.signature);
|
|
2255
|
-
if (f.signature && (!event || !/^[A-Za-z_]\w*$/.test(event.name)))
|
|
2256
|
-
throw new Webhooks.InvalidFilterError('log:emitted', [
|
|
2257
|
-
{ message: 'Unsupported event signature.', path: ['signature'] },
|
|
2258
|
-
]);
|
|
2259
|
-
const address = pushAddress(f.address);
|
|
2260
|
-
const topics = pushTopics(f, event);
|
|
2261
|
-
const hasRawTopics = topics.slice(1).some((topic) => topic !== null);
|
|
2262
|
-
const args = event ? pushEventArgs(f.args, event) : {};
|
|
2263
|
-
const logs = (!event || hasRawTopics
|
|
2264
|
-
? (await rpc(client, 'eth_getLogs', [
|
|
2265
|
-
{
|
|
2266
|
-
...(address ? { address } : {}),
|
|
2267
|
-
fromBlock: blockTag(window.from),
|
|
2268
|
-
...(topics.length > 0 ? { topics } : {}),
|
|
2269
|
-
toBlock: blockTag(window.to),
|
|
2270
|
-
},
|
|
2271
|
-
]))
|
|
2272
|
-
.map(fromRpcLog)
|
|
2273
|
-
.map((log) => (event ? decodeScanLog(log, event) : log))
|
|
2274
|
-
: await client.getLogs({
|
|
2275
|
-
...(address ? { address } : {}),
|
|
2276
|
-
...(Object.keys(args).length > 0 ? { args: args } : {}),
|
|
2277
|
-
event,
|
|
2278
|
-
fromBlock: BigInt(window.from),
|
|
2279
|
-
strict: false,
|
|
2280
|
-
toBlock: BigInt(window.to),
|
|
2281
|
-
}));
|
|
2282
|
-
logs.sort((a, b) => {
|
|
2283
|
-
const block = Number(a.blockNumber ?? 0n) - Number(b.blockNumber ?? 0n);
|
|
2284
|
-
return block || (a.logIndex ?? 0) - (b.logIndex ?? 0);
|
|
2285
|
-
});
|
|
2286
|
-
const items = [];
|
|
2287
|
-
for (const log of logs) {
|
|
2288
|
-
const blockNumber = log.blockNumber === null ? undefined : Number(log.blockNumber);
|
|
2289
|
-
const logIndex = log.logIndex ?? undefined;
|
|
2290
|
-
if (blockNumber === undefined || logIndex === undefined)
|
|
2291
|
-
continue;
|
|
2292
|
-
if (atOrBefore(blockNumber, logIndex, window.skip))
|
|
2293
|
-
continue;
|
|
2294
|
-
if (!matchesLog(log, f, event))
|
|
2295
|
-
continue;
|
|
2296
|
-
const data = toLogEvent(log, Value.blockToIso(await cache.header(blockNumber)), f.signature, event);
|
|
2297
|
-
if (data) {
|
|
2298
|
-
items.push({ cursor: [data.blockNumber, data.logIndex], data });
|
|
2299
|
-
if (items.length >= batchSize)
|
|
2300
|
-
return { hasMore: true, items };
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
return { items, scannedThrough: window.to };
|
|
2304
|
-
}
|
|
2305
1746
|
/** Re-checks all log filters (including non-pushable operators) in JS. */
|
|
2306
1747
|
function matchesLog(log, f, event) {
|
|
2307
1748
|
const topics = log.topics;
|
|
@@ -2315,8 +1756,8 @@ function matchesLog(log, f, event) {
|
|
|
2315
1756
|
if (f.blockNumber !== undefined &&
|
|
2316
1757
|
!matchesOperator(log.blockNumber?.toString(), f.blockNumber, 'number'))
|
|
2317
1758
|
return false;
|
|
2318
|
-
//
|
|
2319
|
-
// `topic0` operator only applies in raw (no-signature) mode.
|
|
1759
|
+
// With a signature, topic0 is anchored on the event selector above, so the
|
|
1760
|
+
// raw `topic0` operator only applies in raw (no-signature) mode.
|
|
2320
1761
|
if (!event && f.topic0 !== undefined && !matchesOperator(topics[0], f.topic0))
|
|
2321
1762
|
return false;
|
|
2322
1763
|
if (f.topic1 !== undefined && !matchesOperator(topics[1], f.topic1))
|
|
@@ -2326,7 +1767,7 @@ function matchesLog(log, f, event) {
|
|
|
2326
1767
|
if (f.topic3 !== undefined && !matchesOperator(topics[3], f.topic3))
|
|
2327
1768
|
return false;
|
|
2328
1769
|
if (event && f.args && Object.keys(f.args).length > 0) {
|
|
2329
|
-
//
|
|
1770
|
+
// Best-effort decoding leaves a non-decodable log without `args`.
|
|
2330
1771
|
if (log.args === undefined || Array.isArray(log.args))
|
|
2331
1772
|
return false;
|
|
2332
1773
|
const args = log.args;
|
|
@@ -2374,31 +1815,6 @@ function toBlockEvent(block) {
|
|
|
2374
1815
|
transactionCount,
|
|
2375
1816
|
};
|
|
2376
1817
|
}
|
|
2377
|
-
/** Scans new blocks over one subscription's confirmed window. */
|
|
2378
|
-
async function scanBlocks(options) {
|
|
2379
|
-
const { batchSize, cache, cursor, filters, maxBlocks, safeBlock } = options;
|
|
2380
|
-
const f = filters;
|
|
2381
|
-
const window = scanWindow(cursor, safeBlock, maxBlocks);
|
|
2382
|
-
if (!window)
|
|
2383
|
-
return { items: [], scannedThrough: undefined };
|
|
2384
|
-
const items = [];
|
|
2385
|
-
for (let blockNumber = window.from; blockNumber <= window.to; blockNumber++) {
|
|
2386
|
-
if (atOrBefore(blockNumber, 0, window.skip))
|
|
2387
|
-
continue;
|
|
2388
|
-
const block = await cache.header(blockNumber);
|
|
2389
|
-
if (!block)
|
|
2390
|
-
continue;
|
|
2391
|
-
if (!matchesBlock(block, f))
|
|
2392
|
-
continue;
|
|
2393
|
-
const data = toBlockEvent(block);
|
|
2394
|
-
if (data) {
|
|
2395
|
-
items.push({ cursor: [data.number, 0], data });
|
|
2396
|
-
if (items.length >= batchSize)
|
|
2397
|
-
return { hasMore: true, items };
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
return { items, scannedThrough: window.to };
|
|
2401
|
-
}
|
|
2402
1818
|
/** Re-checks all block filters in JS. The RPC has no consensus `proposer`. */
|
|
2403
1819
|
function matchesBlock(block, f) {
|
|
2404
1820
|
if (f.number !== undefined && !matchesOperator(block.number?.toString(), f.number, 'number'))
|
|
@@ -2418,7 +1834,7 @@ function matchesBlock(block, f) {
|
|
|
2418
1834
|
return false;
|
|
2419
1835
|
return true;
|
|
2420
1836
|
}
|
|
2421
|
-
// transaction:included
|
|
1837
|
+
// transaction:included
|
|
2422
1838
|
/** Maps a `transaction:included` scalar filter field to its raw-tx field + kind. */
|
|
2423
1839
|
const txFields = {
|
|
2424
1840
|
blockNumber: { field: 'blockNumber', kind: 'number' },
|
|
@@ -2434,43 +1850,6 @@ const txFields = {
|
|
|
2434
1850
|
validBefore: { field: 'validBefore', kind: 'number' },
|
|
2435
1851
|
value: { field: 'value', kind: 'number' },
|
|
2436
1852
|
};
|
|
2437
|
-
/** Scans included transactions over one subscription's confirmed window. */
|
|
2438
|
-
async function scanTxs(options) {
|
|
2439
|
-
const { batchSize, cache, cursor, filters, maxBlocks, safeBlock } = options;
|
|
2440
|
-
const f = filters;
|
|
2441
|
-
const window = scanWindow(cursor, safeBlock, maxBlocks);
|
|
2442
|
-
if (!window)
|
|
2443
|
-
return { items: [], scannedThrough: undefined };
|
|
2444
|
-
const includeCalls = f.includeCalls ?? true;
|
|
2445
|
-
const items = [];
|
|
2446
|
-
for (let blockNumber = window.from; blockNumber <= window.to; blockNumber++) {
|
|
2447
|
-
const block = await cache.block(blockNumber);
|
|
2448
|
-
const transactions = Array.isArray(block['transactions'])
|
|
2449
|
-
? block['transactions']
|
|
2450
|
-
: [];
|
|
2451
|
-
const timestamp = asString(block['timestamp']);
|
|
2452
|
-
// A transaction's own `blockTimestamp` is seconds-only, so the delivered
|
|
2453
|
-
// timestamp comes from the enclosing block, which carries milliseconds.
|
|
2454
|
-
const blockTime = Value.blockToIso(block);
|
|
2455
|
-
for (let offset = 0; offset < transactions.length; offset++) {
|
|
2456
|
-
const transaction = transactions[offset];
|
|
2457
|
-
const transactionIndex = Value.hexToNumber(transaction['transactionIndex']) ?? offset;
|
|
2458
|
-
if (atOrBefore(blockNumber, transactionIndex, window.skip))
|
|
2459
|
-
continue;
|
|
2460
|
-
if (!matchesTx(transaction, f, includeCalls, timestamp, blockNumber))
|
|
2461
|
-
continue;
|
|
2462
|
-
const parsed = Transactions.schema.Rpc.Transaction.safeParse(transaction);
|
|
2463
|
-
if (!parsed.success)
|
|
2464
|
-
continue;
|
|
2465
|
-
const humanized = Transactions.humanizeTransaction(parsed.data);
|
|
2466
|
-
const data = blockTime === undefined ? humanized : { ...humanized, timestamp: blockTime };
|
|
2467
|
-
items.push({ cursor: [blockNumber, transactionIndex], data });
|
|
2468
|
-
if (items.length >= batchSize)
|
|
2469
|
-
return { hasMore: true, items };
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
return { items, scannedThrough: window.to };
|
|
2473
|
-
}
|
|
2474
1853
|
/** Re-checks transaction filters against the raw RPC transaction. */
|
|
2475
1854
|
function matchesTx(tx, f, includeCalls, timestamp, blockNumber) {
|
|
2476
1855
|
const from = tx['from'];
|
|
@@ -2524,6 +1903,16 @@ function ownerFor(c) {
|
|
|
2524
1903
|
const payer = principal.payment?.payer;
|
|
2525
1904
|
return payer ? { payer, type: 'mpp' } : null;
|
|
2526
1905
|
}
|
|
1906
|
+
function accessFor(c) {
|
|
1907
|
+
const principal = Auth.getPrincipal(c);
|
|
1908
|
+
if (principal?.type !== 'api_key')
|
|
1909
|
+
throw new Error('Webhook routes require an API key.');
|
|
1910
|
+
return {
|
|
1911
|
+
environment: principal.environment,
|
|
1912
|
+
...(principal.projectId === undefined ? {} : { projectId: principal.projectId }),
|
|
1913
|
+
scopes: principal.apiKey.scopes,
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
2527
1916
|
/** Strips internal fields (`owner`, `secret`) from a subscription for reads. */
|
|
2528
1917
|
function toPublic(subscription) {
|
|
2529
1918
|
return {
|
|
@@ -2531,6 +1920,10 @@ function toPublic(subscription) {
|
|
|
2531
1920
|
...(subscription.context === undefined ? {} : { context: subscription.context }),
|
|
2532
1921
|
createdAt: subscription.createdAt,
|
|
2533
1922
|
destination: redactDestination(subscription.destination),
|
|
1923
|
+
...(subscription.eventType === 'funding:deposit.updated' &&
|
|
1924
|
+
subscription.environment !== undefined
|
|
1925
|
+
? { environment: subscription.environment }
|
|
1926
|
+
: {}),
|
|
2534
1927
|
eventType: subscription.eventType,
|
|
2535
1928
|
...(subscription.expiresAt === undefined ? {} : { expiresAt: subscription.expiresAt }),
|
|
2536
1929
|
failureCount: subscription.failureCount,
|
|
@@ -2762,10 +2155,4 @@ function toScanHeader(block) {
|
|
|
2762
2155
|
timestamp: quantity(block['timestamp']),
|
|
2763
2156
|
};
|
|
2764
2157
|
}
|
|
2765
|
-
class BlockUnavailableError extends Error {
|
|
2766
|
-
name = 'Webhooks.BlockUnavailableError';
|
|
2767
|
-
constructor(blockNumber) {
|
|
2768
|
-
super(`Block ${blockNumber} is unavailable.`);
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
2158
|
//# sourceMappingURL=webhooks.js.map
|