tapimo 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -16
- package/dist/ApiKey.d.ts +14 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +11 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +207 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +282 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +6771 -3819
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +145 -317
- package/dist/App.js.map +1 -1
- package/dist/Handler.d.ts +86 -0
- package/dist/Handler.d.ts.map +1 -0
- package/dist/Handler.js +83 -0
- package/dist/Handler.js.map +1 -0
- package/dist/Scope.d.ts +14 -4
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +16 -4
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +5 -7
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +11 -4
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/Access.d.ts +1 -3
- package/dist/admin/Access.d.ts.map +1 -1
- package/dist/admin/Access.js +1 -3
- package/dist/admin/Access.js.map +1 -1
- package/dist/admin/App.d.ts +167 -205
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +2 -4
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +105 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +75 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +6 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +35 -149
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +18 -39
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +1 -1
- package/dist/apps/data/App.d.ts +19176 -0
- package/dist/apps/data/App.d.ts.map +1 -0
- package/dist/apps/data/App.js +236 -0
- package/dist/apps/data/App.js.map +1 -0
- package/dist/apps/data/index.d.ts +3 -0
- package/dist/apps/data/index.d.ts.map +1 -0
- package/dist/apps/data/index.js +3 -0
- package/dist/apps/data/index.js.map +1 -0
- package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
- package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
- package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
- package/dist/apps/data/routes/activities.js.map +1 -0
- package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
- package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
- package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
- package/dist/apps/data/routes/addresses.js.map +1 -0
- package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +49 -49
- package/dist/apps/data/routes/balances.d.ts.map +1 -0
- package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
- package/dist/apps/data/routes/balances.js.map +1 -0
- package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
- package/dist/apps/data/routes/blocks.d.ts.map +1 -0
- package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
- package/dist/apps/data/routes/blocks.js.map +1 -0
- package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +183 -183
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
- package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
- package/dist/apps/data/routes/coingecko.js.map +1 -0
- package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +460 -460
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
- package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
- package/dist/apps/data/routes/exchanges.js.map +1 -0
- package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +94 -94
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
- package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
- package/dist/apps/data/routes/fee-amm.js.map +1 -0
- package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
- package/dist/apps/data/routes/indexer.d.ts.map +1 -0
- package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
- package/dist/apps/data/routes/indexer.js.map +1 -0
- package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +6 -6
- package/dist/apps/data/routes/receipts.d.ts.map +1 -0
- package/dist/apps/{receipts.js → data/routes/receipts.js} +12 -12
- package/dist/apps/data/routes/receipts.js.map +1 -0
- package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +4 -4
- package/dist/apps/data/routes/rpc.d.ts.map +1 -0
- package/dist/apps/{rpc.js → data/routes/rpc.js} +6 -6
- package/dist/apps/data/routes/rpc.js.map +1 -0
- package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +4 -4
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
- package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
- package/dist/apps/data/routes/tokenlist.js.map +1 -0
- package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +11 -11
- package/dist/apps/data/routes/tokens.d.ts.map +1 -0
- package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
- package/dist/apps/data/routes/tokens.js.map +1 -0
- package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +288 -288
- package/dist/apps/data/routes/transactions.d.ts.map +1 -0
- package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
- package/dist/apps/data/routes/transactions.js.map +1 -0
- package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +35 -35
- package/dist/apps/data/routes/transfers.d.ts.map +1 -0
- package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
- package/dist/apps/data/routes/transfers.js.map +1 -0
- package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +42 -42
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
- package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -0
- package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +66 -63
- package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
- package/dist/apps/{webhooks.js → data/routes/webhooks.js} +67 -59
- package/dist/apps/data/routes/webhooks.js.map +1 -0
- package/dist/apps/docs/App.d.ts +27 -0
- package/dist/apps/docs/App.d.ts.map +1 -0
- package/dist/apps/docs/App.js +122 -0
- package/dist/apps/docs/App.js.map +1 -0
- package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
- package/dist/apps/docs/fonts.d.ts.map +1 -0
- package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
- package/dist/apps/docs/fonts.js.map +1 -0
- package/dist/apps/docs/index.d.ts +2 -0
- package/dist/apps/docs/index.d.ts.map +1 -0
- package/dist/apps/docs/index.js +2 -0
- package/dist/apps/docs/index.js.map +1 -0
- package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
- package/dist/apps/docs/logo.d.ts.map +1 -0
- package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
- package/dist/apps/docs/logo.js.map +1 -0
- package/dist/apps/index.d.ts +4 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +4 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/metadata.d.ts +7 -0
- package/dist/apps/metadata.d.ts.map +1 -0
- package/dist/apps/metadata.js +15 -0
- package/dist/apps/metadata.js.map +1 -0
- package/dist/apps/platform/App.d.ts +2985 -0
- package/dist/apps/platform/App.d.ts.map +1 -0
- package/dist/apps/platform/App.js +74 -0
- package/dist/apps/platform/App.js.map +1 -0
- package/dist/apps/platform/index.d.ts +2 -0
- package/dist/apps/platform/index.d.ts.map +1 -0
- package/dist/apps/platform/index.js +2 -0
- package/dist/apps/platform/index.js.map +1 -0
- package/dist/apps/platform/routes/api-keys.d.ts +681 -0
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/platform/routes/api-keys.js +328 -0
- package/dist/apps/platform/routes/api-keys.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +733 -0
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
- package/dist/apps/platform/routes/invitations.js +413 -0
- package/dist/apps/platform/routes/invitations.js.map +1 -0
- package/dist/apps/platform/routes/me.d.ts +95 -0
- package/dist/apps/platform/routes/me.d.ts.map +1 -0
- package/dist/apps/platform/routes/me.js +81 -0
- package/dist/apps/platform/routes/me.js.map +1 -0
- package/dist/apps/platform/routes/members.d.ts +554 -0
- package/dist/apps/platform/routes/members.d.ts.map +1 -0
- package/dist/apps/platform/routes/members.js +251 -0
- package/dist/apps/platform/routes/members.js.map +1 -0
- package/dist/apps/platform/routes/orgs.d.ts +582 -0
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
- package/dist/apps/platform/routes/orgs.js +265 -0
- package/dist/apps/platform/routes/orgs.js.map +1 -0
- package/dist/apps/platform/routes/projects.d.ts +740 -0
- package/dist/apps/platform/routes/projects.d.ts.map +1 -0
- package/dist/apps/platform/routes/projects.js +286 -0
- package/dist/apps/platform/routes/projects.js.map +1 -0
- package/dist/apps/relay/App.d.ts +41 -0
- package/dist/apps/relay/App.d.ts.map +1 -0
- package/dist/apps/relay/App.js +103 -0
- package/dist/apps/relay/App.js.map +1 -0
- package/dist/apps/relay/Sponsorships.d.ts +107 -0
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
- package/dist/apps/relay/Sponsorships.js +193 -0
- package/dist/apps/relay/Sponsorships.js.map +1 -0
- package/dist/apps/relay/index.d.ts +3 -0
- package/dist/apps/relay/index.d.ts.map +1 -0
- package/dist/apps/relay/index.js +3 -0
- package/dist/apps/relay/index.js.map +1 -0
- package/dist/{Cli.d.ts → cli/Cli.d.ts} +13 -2
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +19 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +15 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +60 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cloudflare.d.ts +7 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +9 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +113 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +402 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +168 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +229 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +84 -0
- package/dist/db/tables/invitations.d.ts.map +1 -0
- package/dist/db/tables/invitations.js +165 -0
- package/dist/db/tables/invitations.js.map +1 -0
- package/dist/db/tables/memberships.d.ts +108 -0
- package/dist/db/tables/memberships.d.ts.map +1 -0
- package/dist/db/tables/memberships.js +153 -0
- package/dist/db/tables/memberships.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +123 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +170 -0
- package/dist/db/tables/organizations.js.map +1 -0
- package/dist/db/tables/projects.d.ts +65 -0
- package/dist/db/tables/projects.d.ts.map +1 -0
- package/dist/db/tables/projects.js +75 -0
- package/dist/db/tables/projects.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +120 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
- package/dist/db/tables/sponsoredTransactions.js +161 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -0
- package/dist/db/tables/users.d.ts +48 -0
- package/dist/db/tables/users.d.ts.map +1 -0
- package/dist/db/tables/users.js +65 -0
- package/dist/db/tables/users.js.map +1 -0
- package/dist/db/tables/verifiedTokens.d.ts +53 -0
- package/dist/db/tables/verifiedTokens.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokens.js +74 -0
- package/dist/db/tables/verifiedTokens.js.map +1 -0
- package/dist/db/tables/webhookDeliveries.d.ts +71 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -0
- package/dist/db/tables/webhookDeliveries.js +119 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +158 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -0
- package/dist/db/tables/webhookSubscriptions.js +272 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -0
- package/dist/handlers/internal/executionError.d.ts +25 -0
- package/dist/handlers/internal/executionError.d.ts.map +1 -0
- package/dist/handlers/internal/executionError.js +186 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +233 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +542 -0
- package/dist/handlers/internal/multisig.js.map +1 -0
- package/dist/handlers/internal/requestListener.d.ts +124 -0
- package/dist/handlers/internal/requestListener.d.ts.map +1 -0
- package/dist/handlers/internal/requestListener.js +173 -0
- package/dist/handlers/internal/requestListener.js.map +1 -0
- package/dist/handlers/internal/sponsorship.d.ts +113 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +128 -0
- package/dist/handlers/internal/sponsorship.js.map +1 -0
- package/dist/handlers/internal/types.d.ts +284 -0
- package/dist/handlers/internal/types.d.ts.map +1 -0
- package/dist/handlers/internal/types.js +2 -0
- package/dist/handlers/internal/types.js.map +1 -0
- package/dist/handlers/internal/utils.d.ts +34 -0
- package/dist/handlers/internal/utils.d.ts.map +1 -0
- package/dist/handlers/internal/utils.js +131 -0
- package/dist/handlers/internal/utils.js.map +1 -0
- package/dist/handlers/relay.d.ts +123 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1278 -0
- package/dist/handlers/relay.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +224 -16
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +572 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Log.d.ts +7 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +0 -19
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +0 -57
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RpcSchema.d.ts +10 -0
- package/dist/internal/RpcSchema.d.ts.map +1 -0
- package/dist/internal/RpcSchema.js +2 -0
- package/dist/internal/RpcSchema.js.map +1 -0
- package/dist/internal/Store.d.ts +49 -4
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +81 -5
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts +15 -34
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +115 -159
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +84 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Webhooks.d.ts +50 -36
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +92 -205
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +37 -6
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +11 -12
- package/src/ApiKeys.test.ts +278 -0
- package/src/ApiKeys.ts +358 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +40508 -19083
- package/src/App.ts +239 -416
- package/src/Client.test-d.ts +307 -16
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +17 -4
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +13 -7
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +70 -18
- package/src/admin/apps/api-keys.ts +92 -9
- package/src/admin/apps/scopes.test.ts +8 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +18 -37
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/composition.test.ts +66 -0
- package/src/apps/data/App.ts +295 -0
- package/src/apps/data/index.ts +2 -0
- package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
- package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
- package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
- package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +6 -5
- package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
- package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +4 -4
- package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
- package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
- package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
- package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +2 -2
- package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
- package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
- package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
- package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +27 -6
- package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
- package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +4 -4
- package/src/apps/{receipts.ts → data/routes/receipts.ts} +13 -13
- package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +142 -13
- package/src/apps/{rpc.ts → data/routes/rpc.ts} +7 -7
- package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +8 -10
- package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
- package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +6 -5
- package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
- package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +4 -4
- package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
- package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +3 -3
- package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
- package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +27 -15
- package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
- package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +137 -150
- package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +83 -81
- package/src/apps/docs/App.ts +136 -0
- package/src/apps/docs/index.ts +1 -0
- package/src/apps/index.ts +3 -0
- package/src/apps/metadata.ts +23 -0
- package/src/apps/platform/App.ts +130 -0
- package/src/apps/platform/index.ts +1 -0
- package/src/apps/platform/routes/api-keys.test.ts +330 -0
- package/src/apps/platform/routes/api-keys.ts +427 -0
- package/src/apps/platform/routes/invitations.test.ts +361 -0
- package/src/apps/platform/routes/invitations.ts +519 -0
- package/src/apps/platform/routes/me.test.ts +121 -0
- package/src/apps/platform/routes/me.ts +106 -0
- package/src/apps/platform/routes/members.test.ts +195 -0
- package/src/apps/platform/routes/members.ts +313 -0
- package/src/apps/platform/routes/orgs.test.ts +313 -0
- package/src/apps/platform/routes/orgs.ts +343 -0
- package/src/apps/platform/routes/projects.test.ts +181 -0
- package/src/apps/platform/routes/projects.ts +367 -0
- package/src/apps/relay/App.test.ts +484 -0
- package/src/apps/relay/App.ts +128 -0
- package/src/apps/relay/Sponsorships.test.ts +375 -0
- package/src/apps/relay/Sponsorships.ts +305 -0
- package/src/apps/relay/index.ts +2 -0
- package/src/{Cli.test.ts → cli/Cli.test.ts} +10 -9
- package/src/{Cli.ts → cli/Cli.ts} +16 -9
- package/src/cli/bin.ts +4 -0
- package/src/cli/commands/admin.test.ts +71 -0
- package/src/cli/commands/admin.ts +60 -0
- package/src/cloudflare.ts +10 -0
- package/src/db/Db.test.ts +84 -0
- package/src/db/Db.ts +542 -0
- package/src/db/Schema.ts +301 -0
- package/src/db/tables/invitations.ts +198 -0
- package/src/db/tables/memberships.ts +208 -0
- package/src/db/tables/organizations.test.ts +160 -0
- package/src/db/tables/organizations.ts +232 -0
- package/src/db/tables/projects.ts +107 -0
- package/src/db/tables/sponsoredTransactions.test.ts +218 -0
- package/src/db/tables/sponsoredTransactions.ts +236 -0
- package/src/db/tables/users.ts +85 -0
- package/src/db/tables/verifiedTokens.ts +111 -0
- package/src/db/tables/webhookDeliveries.ts +159 -0
- package/src/db/tables/webhookSubscriptions.ts +358 -0
- package/src/handlers/internal/executionError.ts +207 -0
- package/src/handlers/internal/multisig.test.ts +818 -0
- package/src/handlers/internal/multisig.ts +823 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +245 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.test.ts +2035 -0
- package/src/handlers/relay.ts +1623 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +337 -21
- package/src/internal/Auth.ts +783 -44
- package/src/internal/Cursor.test.ts +3 -2
- package/src/internal/Log.test.ts +1 -1
- package/src/internal/Log.ts +11 -3
- package/src/internal/MetricSink.ts +1 -1
- package/src/internal/OpenApi.ts +0 -59
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +150 -10
- package/src/internal/Store.ts +136 -11
- package/src/internal/Tidx.local.test.ts +1 -1
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/WebhookDestination.test.ts +1 -1
- package/src/internal/Webhooks.test.ts +111 -111
- package/src/internal/Webhooks.ts +103 -221
- package/dist/ApiKeySource.d.ts +0 -166
- package/dist/ApiKeySource.d.ts.map +0 -1
- package/dist/ApiKeySource.js +0 -234
- package/dist/ApiKeySource.js.map +0 -1
- package/dist/Cli.d.ts.map +0 -1
- package/dist/Cli.js +0 -13
- package/dist/Cli.js.map +0 -1
- package/dist/apps/activities.js.map +0 -1
- package/dist/apps/addresses.js.map +0 -1
- package/dist/apps/balances.d.ts.map +0 -1
- package/dist/apps/balances.js.map +0 -1
- package/dist/apps/blocks.d.ts.map +0 -1
- package/dist/apps/blocks.js.map +0 -1
- package/dist/apps/coingecko.d.ts.map +0 -1
- package/dist/apps/coingecko.js.map +0 -1
- package/dist/apps/exchanges.d.ts.map +0 -1
- package/dist/apps/exchanges.js.map +0 -1
- package/dist/apps/fee-amm.d.ts.map +0 -1
- package/dist/apps/fee-amm.js.map +0 -1
- package/dist/apps/indexer.d.ts.map +0 -1
- package/dist/apps/indexer.js.map +0 -1
- package/dist/apps/receipts.d.ts.map +0 -1
- package/dist/apps/receipts.js.map +0 -1
- package/dist/apps/rpc.d.ts.map +0 -1
- package/dist/apps/rpc.js.map +0 -1
- package/dist/apps/tokenlist.d.ts.map +0 -1
- package/dist/apps/tokenlist.js.map +0 -1
- package/dist/apps/tokens.d.ts.map +0 -1
- package/dist/apps/tokens.js.map +0 -1
- package/dist/apps/transactions.d.ts.map +0 -1
- package/dist/apps/transactions.js.map +0 -1
- package/dist/apps/transfers.d.ts.map +0 -1
- package/dist/apps/transfers.js.map +0 -1
- package/dist/apps/verified-tokens.d.ts.map +0 -1
- package/dist/apps/verified-tokens.js.map +0 -1
- package/dist/apps/webhooks.js.map +0 -1
- package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
- package/dist/internal/OpenApi.fonts.js.map +0 -1
- package/dist/internal/OpenApi.logo.d.ts.map +0 -1
- package/dist/internal/OpenApi.logo.js.map +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/31318/verified-tokens.json +0 -30
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +0 -4
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +0 -30
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +0 -11
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +0 -1
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +0 -5
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +0 -177
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +0 -1
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +0 -1
- package/seeds/42431/verified-tokens.json +0 -44
- package/seeds/openrpc/README.md +0 -27
- package/seeds/openrpc/execution-apis.json +0 -8989
- package/src/ApiKeySource.test-d.ts +0 -11
- package/src/ApiKeySource.test.ts +0 -295
- package/src/ApiKeySource.ts +0 -303
- /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
- /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid'
|
|
2
|
+
import { TxEnvelopeTempo } from 'ox/tempo'
|
|
3
|
+
import type { Client } from 'viem'
|
|
4
|
+
|
|
5
|
+
import { postgresUrl } from '../../../test/containers.js'
|
|
6
|
+
import * as Db from '../../db/Db.js'
|
|
7
|
+
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
8
|
+
import * as Sponsorships from './Sponsorships.js'
|
|
9
|
+
|
|
10
|
+
const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
|
|
11
|
+
|
|
12
|
+
/** Baseline insert input; tests override per case. */
|
|
13
|
+
const input = {
|
|
14
|
+
apiKeyId: 'key_1',
|
|
15
|
+
billable: true,
|
|
16
|
+
chainId: 42431,
|
|
17
|
+
environment: 'production',
|
|
18
|
+
orgId: 'org_1',
|
|
19
|
+
projectId: 'prj_1',
|
|
20
|
+
signPayload: `0x${'bb'.repeat(32)}`,
|
|
21
|
+
transaction: `0x76${'cc'.repeat(16)}`,
|
|
22
|
+
transactionHash: `0x${'aa'.repeat(32)}`,
|
|
23
|
+
} satisfies SponsoredTransactions.upsert.Input
|
|
24
|
+
|
|
25
|
+
/** Stubs the receipt lookup: hashes present in `receipts` resolve, others null. */
|
|
26
|
+
function client(receipts: Record<string, { effectiveGasPrice: string; gasUsed: string }>): Client {
|
|
27
|
+
return {
|
|
28
|
+
request: async ({ params }: { params: readonly [string] }) => receipts[params[0]] ?? null,
|
|
29
|
+
} as never
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('finalize', () => {
|
|
33
|
+
test('behavior: finalizes mined rows with gasUsed × effectiveGasPrice', async () => {
|
|
34
|
+
const db = create()
|
|
35
|
+
await db.migrate()
|
|
36
|
+
const record = await SponsoredTransactions.upsert(db, input)
|
|
37
|
+
|
|
38
|
+
const result = await Sponsorships.finalize(db, {
|
|
39
|
+
getClient: () =>
|
|
40
|
+
client({ [input.transactionHash]: { effectiveGasPrice: '0x3', gasUsed: '0x5208' } }),
|
|
41
|
+
})
|
|
42
|
+
expect(result).toMatchInlineSnapshot(`
|
|
43
|
+
{
|
|
44
|
+
"failed": 0,
|
|
45
|
+
"finalized": 1,
|
|
46
|
+
"pending": 0,
|
|
47
|
+
}
|
|
48
|
+
`)
|
|
49
|
+
const finalized = await SponsoredTransactions.get(db, record.id)
|
|
50
|
+
expect(finalized?.status).toMatchInlineSnapshot(`"finalized"`)
|
|
51
|
+
// 21000 gas × 3 = 63000 base units.
|
|
52
|
+
expect(finalized?.feeAmount).toMatchInlineSnapshot(`"63000"`)
|
|
53
|
+
expect(finalized?.finalizedAt).not.toBeNull()
|
|
54
|
+
await db.close()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('behavior: leaves young unmined rows pending', async () => {
|
|
58
|
+
const db = create()
|
|
59
|
+
await db.migrate()
|
|
60
|
+
const record = await SponsoredTransactions.upsert(db, input)
|
|
61
|
+
|
|
62
|
+
const result = await Sponsorships.finalize(db, { getClient: () => client({}) })
|
|
63
|
+
expect(result).toMatchInlineSnapshot(`
|
|
64
|
+
{
|
|
65
|
+
"failed": 0,
|
|
66
|
+
"finalized": 0,
|
|
67
|
+
"pending": 1,
|
|
68
|
+
}
|
|
69
|
+
`)
|
|
70
|
+
expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
|
|
71
|
+
`"pending"`,
|
|
72
|
+
)
|
|
73
|
+
await db.close()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('behavior: hash-less fill intents settle by TTL without a receipt lookup', async () => {
|
|
77
|
+
const db = create()
|
|
78
|
+
await db.migrate()
|
|
79
|
+
const { transactionHash: _, ...intent } = input
|
|
80
|
+
const record = await SponsoredTransactions.upsert(db, intent)
|
|
81
|
+
|
|
82
|
+
// Young intent: left pending; the stub client would throw if queried.
|
|
83
|
+
const young = await Sponsorships.finalize(db, {
|
|
84
|
+
getClient: () => {
|
|
85
|
+
throw new Error('unexpected receipt lookup for a hash-less intent')
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
expect(young).toMatchInlineSnapshot(`
|
|
89
|
+
{
|
|
90
|
+
"failed": 0,
|
|
91
|
+
"finalized": 0,
|
|
92
|
+
"pending": 1,
|
|
93
|
+
}
|
|
94
|
+
`)
|
|
95
|
+
|
|
96
|
+
// Past the TTL: fails with no fee.
|
|
97
|
+
const settled = await Sponsorships.finalize(db, {
|
|
98
|
+
getClient: () => {
|
|
99
|
+
throw new Error('unexpected receipt lookup for a hash-less intent')
|
|
100
|
+
},
|
|
101
|
+
now: () => new Date(Date.now() + 2 * 3_600_000),
|
|
102
|
+
})
|
|
103
|
+
expect(settled).toMatchInlineSnapshot(`
|
|
104
|
+
{
|
|
105
|
+
"failed": 1,
|
|
106
|
+
"finalized": 0,
|
|
107
|
+
"pending": 0,
|
|
108
|
+
}
|
|
109
|
+
`)
|
|
110
|
+
expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
|
|
111
|
+
`"failed"`,
|
|
112
|
+
)
|
|
113
|
+
await db.close()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('behavior: fails unmined rows past the pending TTL', async () => {
|
|
117
|
+
const db = create()
|
|
118
|
+
await db.migrate()
|
|
119
|
+
const record = await SponsoredTransactions.upsert(db, input)
|
|
120
|
+
|
|
121
|
+
const result = await Sponsorships.finalize(db, {
|
|
122
|
+
getClient: () => client({}),
|
|
123
|
+
now: () => new Date(Date.now() + 2 * 3_600_000),
|
|
124
|
+
})
|
|
125
|
+
expect(result).toMatchInlineSnapshot(`
|
|
126
|
+
{
|
|
127
|
+
"failed": 1,
|
|
128
|
+
"finalized": 0,
|
|
129
|
+
"pending": 0,
|
|
130
|
+
}
|
|
131
|
+
`)
|
|
132
|
+
const failed = await SponsoredTransactions.get(db, record.id)
|
|
133
|
+
expect(failed?.status).toMatchInlineSnapshot(`"failed"`)
|
|
134
|
+
expect(failed?.feeAmount).toBeNull()
|
|
135
|
+
await db.close()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('behavior: an erroring receipt lookup leaves the row pending, even past the TTL', async () => {
|
|
139
|
+
const db = create()
|
|
140
|
+
await db.migrate()
|
|
141
|
+
const record = await SponsoredTransactions.upsert(db, input)
|
|
142
|
+
|
|
143
|
+
const result = await Sponsorships.finalize(db, {
|
|
144
|
+
getClient: () =>
|
|
145
|
+
({
|
|
146
|
+
request: async () => {
|
|
147
|
+
throw new Error('rpc outage')
|
|
148
|
+
},
|
|
149
|
+
}) as never,
|
|
150
|
+
now: () => new Date(Date.now() + 2 * 3_600_000),
|
|
151
|
+
})
|
|
152
|
+
expect(result).toMatchInlineSnapshot(`
|
|
153
|
+
{
|
|
154
|
+
"failed": 0,
|
|
155
|
+
"finalized": 0,
|
|
156
|
+
"pending": 1,
|
|
157
|
+
}
|
|
158
|
+
`)
|
|
159
|
+
expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
|
|
160
|
+
`"pending"`,
|
|
161
|
+
)
|
|
162
|
+
await db.close()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('behavior: an erroring receipt lookup fails past the error TTL', async () => {
|
|
166
|
+
const db = create()
|
|
167
|
+
await db.migrate()
|
|
168
|
+
const record = await SponsoredTransactions.upsert(db, input)
|
|
169
|
+
|
|
170
|
+
const result = await Sponsorships.finalize(db, {
|
|
171
|
+
getClient: () =>
|
|
172
|
+
({
|
|
173
|
+
request: async () => {
|
|
174
|
+
throw new Error('rpc outage')
|
|
175
|
+
},
|
|
176
|
+
}) as never,
|
|
177
|
+
now: () => new Date(Date.now() + 25 * 3_600_000),
|
|
178
|
+
})
|
|
179
|
+
expect(result).toMatchInlineSnapshot(`
|
|
180
|
+
{
|
|
181
|
+
"failed": 1,
|
|
182
|
+
"finalized": 0,
|
|
183
|
+
"pending": 0,
|
|
184
|
+
}
|
|
185
|
+
`)
|
|
186
|
+
expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
|
|
187
|
+
`"failed"`,
|
|
188
|
+
)
|
|
189
|
+
await db.close()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('behavior: batchSize caps rows processed per pass', async () => {
|
|
193
|
+
const db = create()
|
|
194
|
+
await db.migrate()
|
|
195
|
+
await SponsoredTransactions.upsert(db, { ...input, signPayload: `0x${'b1'.repeat(32)}`, transactionHash: `0x${'a1'.repeat(32)}` }) // prettier-ignore
|
|
196
|
+
await SponsoredTransactions.upsert(db, { ...input, signPayload: `0x${'b2'.repeat(32)}`, transactionHash: `0x${'a2'.repeat(32)}` }) // prettier-ignore
|
|
197
|
+
|
|
198
|
+
// Both hashes are mined so the outcome is order-independent.
|
|
199
|
+
const receipts = {
|
|
200
|
+
[`0x${'a1'.repeat(32)}`]: { effectiveGasPrice: '0x1', gasUsed: '0x1' },
|
|
201
|
+
[`0x${'a2'.repeat(32)}`]: { effectiveGasPrice: '0x1', gasUsed: '0x1' },
|
|
202
|
+
}
|
|
203
|
+
const result = await Sponsorships.finalize(db, {
|
|
204
|
+
batchSize: 1,
|
|
205
|
+
getClient: () => client(receipts),
|
|
206
|
+
})
|
|
207
|
+
expect(result).toMatchInlineSnapshot(`
|
|
208
|
+
{
|
|
209
|
+
"failed": 0,
|
|
210
|
+
"finalized": 1,
|
|
211
|
+
"pending": 0,
|
|
212
|
+
}
|
|
213
|
+
`)
|
|
214
|
+
expect(await SponsoredTransactions.listPending(db)).toHaveLength(1)
|
|
215
|
+
await db.close()
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('reconcile', () => {
|
|
220
|
+
const sender = '0x407368320192a21238825437c02c2e2a34ffc38d'
|
|
221
|
+
const minedHash = `0x${'dd'.repeat(32)}`
|
|
222
|
+
|
|
223
|
+
/** The mined sponsored envelope, in ox form. */
|
|
224
|
+
const mined = TxEnvelopeTempo.from({
|
|
225
|
+
calls: [{ data: '0x', to: '0x20c0000000000000000000000000000000000001', value: 0n }],
|
|
226
|
+
chainId: 42431,
|
|
227
|
+
gas: 100_000n,
|
|
228
|
+
maxFeePerGas: 1_000_000n,
|
|
229
|
+
maxPriorityFeePerGas: 0n,
|
|
230
|
+
nonce: 0n,
|
|
231
|
+
nonceKey: 0n,
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
/** The intent's identity, computed exactly as the recording sites do. */
|
|
235
|
+
const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(mined, { sender })
|
|
236
|
+
|
|
237
|
+
/** Fakes the node: `eth_getRawTransactionByHash` serves the mined bytes. */
|
|
238
|
+
const rpc = {
|
|
239
|
+
request: async () => TxEnvelopeTempo.serialize(mined),
|
|
240
|
+
} as never as Client
|
|
241
|
+
|
|
242
|
+
/** Fakes the indexer: the fee payer's recent transactions. */
|
|
243
|
+
const tidx = (rows: readonly { from: string; hash: string }[]) =>
|
|
244
|
+
({ fetch: async () => ({ rows }) }) as never
|
|
245
|
+
|
|
246
|
+
test('behavior: matches intents by sign payload and fills the hash', async () => {
|
|
247
|
+
const db = create()
|
|
248
|
+
await db.migrate()
|
|
249
|
+
const { transactionHash: _, ...rest } = input
|
|
250
|
+
const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload })
|
|
251
|
+
|
|
252
|
+
const result = await Sponsorships.reconcile(db, {
|
|
253
|
+
feePayer: '0xFee0000000000000000000000000000000000000',
|
|
254
|
+
getClient: () => rpc,
|
|
255
|
+
getTidx: () => tidx([{ from: sender, hash: minedHash }]),
|
|
256
|
+
})
|
|
257
|
+
expect(result).toMatchInlineSnapshot(`
|
|
258
|
+
{
|
|
259
|
+
"matched": 1,
|
|
260
|
+
"scanned": 1,
|
|
261
|
+
}
|
|
262
|
+
`)
|
|
263
|
+
const matched = await SponsoredTransactions.get(db, intent.id)
|
|
264
|
+
expect(matched?.transactionHash).toBe(minedHash)
|
|
265
|
+
expect(matched?.status).toMatchInlineSnapshot(`"pending"`)
|
|
266
|
+
await db.close()
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('behavior: recovers recently failed intents by sign payload', async () => {
|
|
270
|
+
const db = create()
|
|
271
|
+
await db.migrate()
|
|
272
|
+
const { transactionHash: _, ...rest } = input
|
|
273
|
+
const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload })
|
|
274
|
+
await SponsoredTransactions.fail(db, intent.id, '2026-01-01T00:00:00.000Z')
|
|
275
|
+
|
|
276
|
+
vi.useFakeTimers({ now: new Date('2026-01-01T00:01:00.000Z') })
|
|
277
|
+
const result = await (async () => {
|
|
278
|
+
try {
|
|
279
|
+
return await Sponsorships.reconcile(db, {
|
|
280
|
+
feePayer: '0xFee0000000000000000000000000000000000000',
|
|
281
|
+
getClient: () => rpc,
|
|
282
|
+
getTidx: () => tidx([{ from: sender, hash: minedHash }]),
|
|
283
|
+
})
|
|
284
|
+
} finally {
|
|
285
|
+
vi.useRealTimers()
|
|
286
|
+
}
|
|
287
|
+
})()
|
|
288
|
+
expect(result).toMatchInlineSnapshot(`
|
|
289
|
+
{
|
|
290
|
+
"matched": 1,
|
|
291
|
+
"scanned": 1,
|
|
292
|
+
}
|
|
293
|
+
`)
|
|
294
|
+
const matched = await SponsoredTransactions.get(db, intent.id)
|
|
295
|
+
expect(matched?.transactionHash).toBe(minedHash)
|
|
296
|
+
expect(matched?.status).toMatchInlineSnapshot(`"pending"`)
|
|
297
|
+
expect(matched?.finalizedAt).toBeNull()
|
|
298
|
+
await db.close()
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test('behavior: unmatched candidates leave intents pending', async () => {
|
|
302
|
+
const db = create()
|
|
303
|
+
await db.migrate()
|
|
304
|
+
const { transactionHash: _, ...rest } = input
|
|
305
|
+
const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload: `0x${'ee'.repeat(32)}` }) // prettier-ignore
|
|
306
|
+
|
|
307
|
+
const result = await Sponsorships.reconcile(db, {
|
|
308
|
+
feePayer: '0xFee0000000000000000000000000000000000000',
|
|
309
|
+
getClient: () => rpc,
|
|
310
|
+
getTidx: () => tidx([{ from: sender, hash: minedHash }]),
|
|
311
|
+
})
|
|
312
|
+
expect(result).toMatchInlineSnapshot(`
|
|
313
|
+
{
|
|
314
|
+
"matched": 0,
|
|
315
|
+
"scanned": 1,
|
|
316
|
+
}
|
|
317
|
+
`)
|
|
318
|
+
expect((await SponsoredTransactions.get(db, intent.id))?.transactionHash).toBeNull()
|
|
319
|
+
await db.close()
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('behavior: queries on-chain candidates oldest first', async () => {
|
|
323
|
+
const db = create()
|
|
324
|
+
await db.migrate()
|
|
325
|
+
const { transactionHash: _, ...rest } = input
|
|
326
|
+
await SponsoredTransactions.upsert(db, { ...rest, signPayload })
|
|
327
|
+
let query: string | undefined
|
|
328
|
+
|
|
329
|
+
await Sponsorships.reconcile(db, {
|
|
330
|
+
candidateCap: 17,
|
|
331
|
+
feePayer: '0xFee0000000000000000000000000000000000000',
|
|
332
|
+
getClient: () => rpc,
|
|
333
|
+
getTidx: () =>
|
|
334
|
+
({
|
|
335
|
+
fetch: async (options: { query: string }) => {
|
|
336
|
+
query = options.query
|
|
337
|
+
return { rows: [] }
|
|
338
|
+
},
|
|
339
|
+
}) as never,
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
expect(
|
|
343
|
+
query?.replace(/block_timestamp >= '[^']+'/, "block_timestamp >= '<since>'"),
|
|
344
|
+
).toMatchInlineSnapshot(
|
|
345
|
+
`"SELECT hash, "from" FROM txs WHERE fee_payer = '0xfee0000000000000000000000000000000000000' AND block_timestamp >= '<since>' ORDER BY block_timestamp ASC LIMIT 17"`,
|
|
346
|
+
)
|
|
347
|
+
await db.close()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test('behavior: an indexer error leaves intents for the next pass', async () => {
|
|
351
|
+
const db = create()
|
|
352
|
+
await db.migrate()
|
|
353
|
+
const { transactionHash: _, ...rest } = input
|
|
354
|
+
await SponsoredTransactions.upsert(db, { ...rest, signPayload })
|
|
355
|
+
|
|
356
|
+
const result = await Sponsorships.reconcile(db, {
|
|
357
|
+
feePayer: '0xFee0000000000000000000000000000000000000',
|
|
358
|
+
getClient: () => rpc,
|
|
359
|
+
getTidx: () =>
|
|
360
|
+
({
|
|
361
|
+
fetch: async () => {
|
|
362
|
+
throw new Error('indexer outage')
|
|
363
|
+
},
|
|
364
|
+
}) as never,
|
|
365
|
+
})
|
|
366
|
+
expect(result).toMatchInlineSnapshot(`
|
|
367
|
+
{
|
|
368
|
+
"matched": 0,
|
|
369
|
+
"scanned": 0,
|
|
370
|
+
}
|
|
371
|
+
`)
|
|
372
|
+
expect(await SponsoredTransactions.listIntents(db)).toHaveLength(1)
|
|
373
|
+
await db.close()
|
|
374
|
+
})
|
|
375
|
+
})
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import type { Hex } from 'ox'
|
|
2
|
+
import { TxEnvelopeTempo } from 'ox/tempo'
|
|
3
|
+
import {
|
|
4
|
+
type Address,
|
|
5
|
+
type Client,
|
|
6
|
+
type EIP1193RequestFn,
|
|
7
|
+
type TransactionReceipt,
|
|
8
|
+
TransactionReceiptNotFoundError,
|
|
9
|
+
} from 'viem'
|
|
10
|
+
import { getTransactionReceipt } from 'viem/actions'
|
|
11
|
+
|
|
12
|
+
import * as Db from '../../db/Db.js'
|
|
13
|
+
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
14
|
+
import * as Tidx from '../../internal/Tidx.js'
|
|
15
|
+
import * as Viem from '../../internal/Viem.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Runs one finalization pass over pending sponsored transactions: rows with a
|
|
19
|
+
* receipt finalize with the actual fee (`gasUsed × effectiveGasPrice`, in
|
|
20
|
+
* fee-token base units); rows still unmined past the pending TTL fail, as do
|
|
21
|
+
* hash-less fill intents the TTL outlives. Rows whose receipt lookup errors
|
|
22
|
+
* persist past the error TTL fail so they cannot monopolize every batch.
|
|
23
|
+
*
|
|
24
|
+
* @param db - The database holding `sponsored_transactions`.
|
|
25
|
+
* @param options - Finalization options.
|
|
26
|
+
* @returns Counters for the pass.
|
|
27
|
+
*/
|
|
28
|
+
export async function finalize(db: Db.Db, options: finalize.Options): Promise<finalize.Result> {
|
|
29
|
+
const {
|
|
30
|
+
batchSize = 100,
|
|
31
|
+
getClient,
|
|
32
|
+
now = () => new Date(),
|
|
33
|
+
pendingTtlMs = 3_600_000,
|
|
34
|
+
receiptErrorTtlMs = 86_400_000,
|
|
35
|
+
} = options
|
|
36
|
+
|
|
37
|
+
const rows = await SponsoredTransactions.listPending(db, { limit: batchSize })
|
|
38
|
+
const result = { failed: 0, finalized: 0, pending: 0 }
|
|
39
|
+
|
|
40
|
+
for (const row of rows) {
|
|
41
|
+
// Fill intents carry no hash until reconciliation matches them on-chain;
|
|
42
|
+
// here only the TTL can settle them.
|
|
43
|
+
if (row.transactionHash === null) {
|
|
44
|
+
if (Date.parse(row.createdAt) + pendingTtlMs <= now().getTime()) {
|
|
45
|
+
await SponsoredTransactions.fail(db, row.id, now().toISOString())
|
|
46
|
+
result.failed++
|
|
47
|
+
} else result.pending++
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let receipt: TransactionReceipt | null
|
|
52
|
+
try {
|
|
53
|
+
receipt = await getTransactionReceipt(getClient(row.chainId), {
|
|
54
|
+
hash: row.transactionHash as Hex.Hex,
|
|
55
|
+
})
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Unmined transactions surface as a typed not-found; let the TTL decide.
|
|
58
|
+
if (error instanceof TransactionReceiptNotFoundError) receipt = null
|
|
59
|
+
else {
|
|
60
|
+
if (Date.parse(row.createdAt) + receiptErrorTtlMs <= now().getTime()) {
|
|
61
|
+
await SponsoredTransactions.fail(db, row.id, now().toISOString())
|
|
62
|
+
result.failed++
|
|
63
|
+
} else result.pending++
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (receipt) {
|
|
69
|
+
// A reverted transaction still finalizes: the fee payer paid its fees.
|
|
70
|
+
const feeAmount = receipt.gasUsed * receipt.effectiveGasPrice
|
|
71
|
+
await SponsoredTransactions.finalize(db, row.id, {
|
|
72
|
+
feeAmount: feeAmount.toString(),
|
|
73
|
+
finalizedAt: now().toISOString(),
|
|
74
|
+
})
|
|
75
|
+
result.finalized++
|
|
76
|
+
} else if (Date.parse(row.createdAt) + pendingTtlMs <= now().getTime()) {
|
|
77
|
+
await SponsoredTransactions.fail(db, row.id, now().toISOString())
|
|
78
|
+
result.failed++
|
|
79
|
+
} else result.pending++
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export declare namespace finalize {
|
|
86
|
+
/** Options for {@link finalize}. */
|
|
87
|
+
type Options = {
|
|
88
|
+
/** Maximum pending rows processed per pass. @default 100 */
|
|
89
|
+
batchSize?: number | undefined
|
|
90
|
+
/** Client resolver keyed by the row's chain id. */
|
|
91
|
+
getClient: (chainId: number) => Client
|
|
92
|
+
/** Clock; injectable for tests. */
|
|
93
|
+
now?: (() => Date) | undefined
|
|
94
|
+
/** Age after which an unmined sponsorship fails. @default 1 hour */
|
|
95
|
+
pendingTtlMs?: number | undefined
|
|
96
|
+
/** Age after which repeated receipt lookup errors fail a row. @default 24 hours */
|
|
97
|
+
receiptErrorTtlMs?: number | undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Counters returned by one finalization pass. */
|
|
101
|
+
type Result = {
|
|
102
|
+
/** Rows marked failed (unmined past the pending TTL). */
|
|
103
|
+
failed: number
|
|
104
|
+
/** Rows finalized with an actual fee. */
|
|
105
|
+
finalized: number
|
|
106
|
+
/** Rows left pending (unmined, or receipt lookup errored within the error TTL). */
|
|
107
|
+
pending: number
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Runs one reconciliation pass over pending fill intents (rows with no
|
|
113
|
+
* transaction hash): fetches the fee payer's recent on-chain transactions
|
|
114
|
+
* from TIDX, recomputes each candidate's fee-payer sign payload, and fills
|
|
115
|
+
* matching intents' hashes so the receipt pass can finalize them. Query or
|
|
116
|
+
* candidate errors leave intents for the next pass.
|
|
117
|
+
*
|
|
118
|
+
* @param db - The database holding `sponsored_transactions`.
|
|
119
|
+
* @param options - Reconciliation options.
|
|
120
|
+
* @returns Counters for the pass.
|
|
121
|
+
*/
|
|
122
|
+
export async function reconcile(db: Db.Db, options: reconcile.Options): Promise<reconcile.Result> {
|
|
123
|
+
const {
|
|
124
|
+
batchSize = 100,
|
|
125
|
+
candidateCap = 500,
|
|
126
|
+
failedIntentTtlMs = 86_400_000,
|
|
127
|
+
feePayer,
|
|
128
|
+
getClient,
|
|
129
|
+
getTidx,
|
|
130
|
+
skewMs = 120_000,
|
|
131
|
+
} = options
|
|
132
|
+
|
|
133
|
+
const intents = await SponsoredTransactions.listIntents(db, {
|
|
134
|
+
failedSince: new Date(Date.now() - failedIntentTtlMs).toISOString(),
|
|
135
|
+
limit: batchSize,
|
|
136
|
+
})
|
|
137
|
+
const result = { matched: 0, scanned: 0 }
|
|
138
|
+
if (intents.length === 0) return result
|
|
139
|
+
|
|
140
|
+
const chains = new Map<number, SponsoredTransactions.Record[]>()
|
|
141
|
+
for (const intent of intents) chains.set(intent.chainId, [...(chains.get(intent.chainId) ?? []), intent]) // prettier-ignore
|
|
142
|
+
|
|
143
|
+
for (const [chainId, rows] of chains) {
|
|
144
|
+
const bySignPayload = new Map(rows.map((row) => [row.signPayload, row]))
|
|
145
|
+
// The intent set bounds the scan window: no cursor state to persist.
|
|
146
|
+
const since = new Date(
|
|
147
|
+
Math.min(...rows.map((row) => Date.parse(row.createdAt))) - skewMs,
|
|
148
|
+
).toISOString()
|
|
149
|
+
|
|
150
|
+
let candidates: readonly globalThis.Record<string, unknown>[]
|
|
151
|
+
try {
|
|
152
|
+
// Dynamic filter values; the tidx client's query type wants literals.
|
|
153
|
+
const fetched = await getTidx(chainId).fetch({
|
|
154
|
+
chainId,
|
|
155
|
+
query:
|
|
156
|
+
`SELECT hash, "from" FROM txs WHERE fee_payer = '${feePayer.toLowerCase()}' AND block_timestamp >= '${since}' ORDER BY block_timestamp ASC LIMIT ${candidateCap}` as string,
|
|
157
|
+
})
|
|
158
|
+
candidates = fetched.rows
|
|
159
|
+
} catch {
|
|
160
|
+
// Indexer outage must not fail intents; retry on the next pass.
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const candidate of candidates) {
|
|
165
|
+
if (bySignPayload.size === 0) break
|
|
166
|
+
const hash = candidate['hash'] as Hex.Hex | undefined
|
|
167
|
+
const sender = candidate['from'] as Address | undefined
|
|
168
|
+
if (!hash || !sender) continue
|
|
169
|
+
result.scanned++
|
|
170
|
+
try {
|
|
171
|
+
const serialized = await getRawTransaction(getClient(chainId), hash)
|
|
172
|
+
if (!serialized?.startsWith(TxEnvelopeTempo.serializedType)) continue
|
|
173
|
+
const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(
|
|
174
|
+
TxEnvelopeTempo.deserialize(serialized as TxEnvelopeTempo.Serialized),
|
|
175
|
+
{ sender },
|
|
176
|
+
)
|
|
177
|
+
const intent = bySignPayload.get(signPayload)
|
|
178
|
+
if (!intent) continue
|
|
179
|
+
await SponsoredTransactions.assignTransactionHash(db, intent.id, hash)
|
|
180
|
+
bySignPayload.delete(signPayload)
|
|
181
|
+
result.matched++
|
|
182
|
+
} catch {
|
|
183
|
+
// Unresolvable candidate (lookup error, malformed envelope): skip it.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export declare namespace reconcile {
|
|
192
|
+
/** Options for {@link reconcile}. */
|
|
193
|
+
type Options = {
|
|
194
|
+
/** Maximum intents considered per pass. @default 100 */
|
|
195
|
+
batchSize?: number | undefined
|
|
196
|
+
/** Maximum on-chain candidates fetched per chain per pass. @default 500 */
|
|
197
|
+
candidateCap?: number | undefined
|
|
198
|
+
/** How long failed intents remain reconciliation candidates. @default 24 hours */
|
|
199
|
+
failedIntentTtlMs?: number | undefined
|
|
200
|
+
/** Fee payer address whose on-chain transactions are candidates. */
|
|
201
|
+
feePayer: Address
|
|
202
|
+
/** Client resolver keyed by the intent's chain id. */
|
|
203
|
+
getClient: (chainId: number) => Client
|
|
204
|
+
/** TIDX client resolver keyed by the intent's chain id. */
|
|
205
|
+
getTidx: (chainId: number) => Tidx.Client
|
|
206
|
+
/** Lookback slack before the oldest intent's creation. @default 2 minutes */
|
|
207
|
+
skewMs?: number | undefined
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Counters returned by one reconciliation pass. */
|
|
211
|
+
type Result = {
|
|
212
|
+
/** Intents matched to an on-chain transaction. */
|
|
213
|
+
matched: number
|
|
214
|
+
/** On-chain candidates inspected. */
|
|
215
|
+
scanned: number
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Fetches a mined transaction's canonical envelope bytes.
|
|
221
|
+
* `eth_getRawTransactionByHash` is a node extension absent from viem's typed
|
|
222
|
+
* RPC schema, hence the request-fn narrowing.
|
|
223
|
+
*/
|
|
224
|
+
function getRawTransaction(client: Client, hash: Hex.Hex) {
|
|
225
|
+
type Request = EIP1193RequestFn<
|
|
226
|
+
[
|
|
227
|
+
{
|
|
228
|
+
Method: 'eth_getRawTransactionByHash'
|
|
229
|
+
Parameters: readonly [Hex.Hex]
|
|
230
|
+
ReturnType: Hex.Hex | null
|
|
231
|
+
},
|
|
232
|
+
]
|
|
233
|
+
>
|
|
234
|
+
return (client.request as Request)({ method: 'eth_getRawTransactionByHash', params: [hash] })
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Builds a scheduled sponsorship finalizer bound to a database and RPC config.
|
|
239
|
+
* A host runtime drives {@link createFinalizer.Finalizer.tick} on a timer (a
|
|
240
|
+
* Cloudflare `scheduled` cron handler, a self-host `setInterval`, …); each
|
|
241
|
+
* tick reconciles fill intents (when `feePayer` + `tidx` are configured) and
|
|
242
|
+
* runs one {@link finalize} pass, with memoized per-chain clients.
|
|
243
|
+
*/
|
|
244
|
+
export function createFinalizer(options: createFinalizer.Options): createFinalizer.Finalizer {
|
|
245
|
+
const clients = new Map<number, Viem.getClient.ReturnType>()
|
|
246
|
+
const getClient = (chainId: number) => {
|
|
247
|
+
let client = clients.get(chainId)
|
|
248
|
+
if (!client) {
|
|
249
|
+
client = Viem.getClient({ chainId: chainId as Viem.ChainId, rpc: options.rpc })
|
|
250
|
+
clients.set(chainId, client)
|
|
251
|
+
}
|
|
252
|
+
return client
|
|
253
|
+
}
|
|
254
|
+
const getTidx = options.tidx
|
|
255
|
+
? Tidx.createGetClient({ defaultChainId: options.defaultChainId, tidx: options.tidx })
|
|
256
|
+
: undefined
|
|
257
|
+
const feePayer = options.feePayer
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
async tick() {
|
|
261
|
+
// Resolve per tick so Workers factories build a fresh Hyperdrive-pooled
|
|
262
|
+
// connection each run.
|
|
263
|
+
const db = Db.get(options.db)
|
|
264
|
+
// Reconcile first so freshly matched intents finalize in the same tick.
|
|
265
|
+
const reconciled =
|
|
266
|
+
feePayer && getTidx
|
|
267
|
+
? await reconcile(db, {
|
|
268
|
+
batchSize: options.batchSize,
|
|
269
|
+
feePayer,
|
|
270
|
+
getClient,
|
|
271
|
+
getTidx: (chainId) => getTidx(chainId as Viem.ChainId),
|
|
272
|
+
})
|
|
273
|
+
: { matched: 0, scanned: 0 }
|
|
274
|
+
const finalized = await finalize(db, {
|
|
275
|
+
batchSize: options.batchSize,
|
|
276
|
+
getClient,
|
|
277
|
+
now: options.now,
|
|
278
|
+
pendingTtlMs: options.pendingTtlMs,
|
|
279
|
+
})
|
|
280
|
+
return { ...finalized, ...reconciled }
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export declare namespace createFinalizer {
|
|
286
|
+
/** Options for {@link createFinalizer}. */
|
|
287
|
+
type Options = Pick<finalize.Options, 'batchSize' | 'now' | 'pendingTtlMs'> & {
|
|
288
|
+
/** Database holding sponsorship rows, or a factory resolved per tick (Workers/Hyperdrive). */
|
|
289
|
+
db: Db.Source
|
|
290
|
+
/** Default chain id for the TIDX client resolver. */
|
|
291
|
+
defaultChainId?: Viem.ChainId | undefined
|
|
292
|
+
/** Fee payer address whose on-chain transactions reconcile fill intents; omit to skip reconciliation. */
|
|
293
|
+
feePayer?: Address | undefined
|
|
294
|
+
/** RPC options for the receipt-lookup clients. */
|
|
295
|
+
rpc?: Viem.getClient.Rpc | undefined
|
|
296
|
+
/** TIDX query client options for intent reconciliation; omit to skip reconciliation. */
|
|
297
|
+
tidx?: Tidx.getClient.Tidx | undefined
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** A scheduled sponsorship finalizer. */
|
|
301
|
+
type Finalizer = {
|
|
302
|
+
/** Runs one reconcile + finalize pass. */
|
|
303
|
+
tick(): Promise<finalize.Result & reconcile.Result>
|
|
304
|
+
}
|
|
305
|
+
}
|