tapimo 0.1.0 → 0.3.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 +57 -16
- package/dist/ApiKey.d.ts +4 -1
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +6 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +98 -3
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +150 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +7917 -2315
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +141 -329
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +20 -4
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +23 -4
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +2 -2
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +2 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +203 -120
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +111 -6
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +67 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +12 -1
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +3 -0
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +116 -116
- package/dist/admin/apps/verified-tokens.js +1 -1
- 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 +13 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +151 -0
- package/dist/analytics/Analytics.d.ts.map +1 -0
- package/dist/analytics/Analytics.js +212 -0
- package/dist/analytics/Analytics.js.map +1 -0
- package/dist/analytics/tables/requestEvents.d.ts +215 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
- package/dist/analytics/tables/requestEvents.js +264 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -0
- package/dist/apps/data/App.d.ts +19576 -0
- package/dist/apps/data/App.d.ts.map +1 -0
- package/dist/apps/data/App.js +272 -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} +4 -4
- 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} +8 -8
- 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} +108 -108
- 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} +39 -39
- 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/data/routes/funding.d.ts +765 -0
- package/dist/apps/data/routes/funding.d.ts.map +1 -0
- package/dist/apps/data/routes/funding.js +383 -0
- package/dist/apps/data/routes/funding.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} +49 -49
- package/dist/apps/data/routes/receipts.d.ts.map +1 -0
- package/dist/apps/{receipts.js → data/routes/receipts.js} +14 -20
- package/dist/apps/data/routes/receipts.js.map +1 -0
- package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +33 -40
- package/dist/apps/data/routes/rpc.d.ts.map +1 -0
- package/dist/apps/{rpc.js → data/routes/rpc.js} +35 -7
- package/dist/apps/data/routes/rpc.js.map +1 -0
- package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +40 -40
- 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} +468 -468
- 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} +304 -304
- 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} +62 -62
- 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} +6 -6
- 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} +17 -17
- package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
- package/dist/apps/{webhooks.js → data/routes/webhooks.js} +38 -34
- package/dist/apps/data/routes/webhooks.js.map +1 -0
- package/dist/apps/docs/App.d.ts +29 -0
- package/dist/apps/docs/App.d.ts.map +1 -0
- package/dist/apps/docs/App.js +54 -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/docs/reference.d.ts +21 -0
- package/dist/apps/docs/reference.d.ts.map +1 -0
- package/dist/apps/docs/reference.js +112 -0
- package/dist/apps/docs/reference.js.map +1 -0
- package/dist/apps/index.d.ts +5 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +5 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/mcp.d.ts +76 -0
- package/dist/apps/mcp.d.ts.map +1 -0
- package/dist/apps/mcp.js +188 -0
- package/dist/apps/mcp.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 +5262 -0
- package/dist/apps/platform/App.d.ts.map +1 -0
- package/dist/apps/platform/App.js +84 -0
- package/dist/apps/platform/App.js.map +1 -0
- package/dist/apps/platform/Billing.d.ts +279 -0
- package/dist/apps/platform/Billing.d.ts.map +1 -0
- package/dist/apps/platform/Billing.js +477 -0
- package/dist/apps/platform/Billing.js.map +1 -0
- package/dist/apps/platform/index.d.ts +3 -0
- package/dist/apps/platform/index.d.ts.map +1 -0
- package/dist/apps/platform/index.js +3 -0
- package/dist/apps/platform/index.js.map +1 -0
- package/dist/apps/platform/routes/api-keys.d.ts +973 -0
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/platform/routes/api-keys.js +430 -0
- package/dist/apps/platform/routes/api-keys.js.map +1 -0
- package/dist/apps/platform/routes/billing.d.ts +1520 -0
- package/dist/apps/platform/routes/billing.d.ts.map +1 -0
- package/dist/apps/platform/routes/billing.js +795 -0
- package/dist/apps/platform/routes/billing.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +851 -0
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
- package/dist/apps/platform/routes/invitations.js +471 -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 +596 -0
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
- package/dist/apps/platform/routes/orgs.js +276 -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/platform/routes/scopes.d.ts +107 -0
- package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
- package/dist/apps/platform/routes/scopes.js +66 -0
- package/dist/apps/platform/routes/scopes.js.map +1 -0
- package/dist/apps/platform/routes/usage.d.ts +588 -0
- package/dist/apps/platform/routes/usage.d.ts.map +1 -0
- package/dist/apps/platform/routes/usage.js +391 -0
- package/dist/apps/platform/routes/usage.js.map +1 -0
- package/dist/apps/relay/App.d.ts +44 -0
- package/dist/apps/relay/App.d.ts.map +1 -0
- package/dist/apps/relay/App.js +211 -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 +194 -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/Cli.d.ts +9 -2
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +12 -4
- package/dist/cli/Cli.js.map +1 -1
- package/dist/cli/commands/admin.d.ts +5 -1
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/admin.js +82 -3
- package/dist/cli/commands/admin.js.map +1 -1
- package/dist/db/Db.d.ts +17 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +254 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +159 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +187 -2
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/billingSettings.d.ts +55 -0
- package/dist/db/tables/billingSettings.d.ts.map +1 -0
- package/dist/db/tables/billingSettings.js +70 -0
- package/dist/db/tables/billingSettings.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 +48 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +97 -2
- package/dist/db/tables/organizations.js.map +1 -1
- 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/requestUsage.d.ts +121 -0
- package/dist/db/tables/requestUsage.d.ts.map +1 -0
- package/dist/db/tables/requestUsage.js +150 -0
- package/dist/db/tables/requestUsage.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +239 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
- package/dist/db/tables/sponsoredTransactions.js +314 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -0
- package/dist/db/tables/stripeCustomers.d.ts +81 -0
- package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
- package/dist/db/tables/stripeCustomers.js +120 -0
- package/dist/db/tables/stripeCustomers.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/handlers/internal/executionError.d.ts.map +1 -1
- package/dist/handlers/internal/executionError.js +2 -0
- package/dist/handlers/internal/executionError.js.map +1 -1
- package/dist/handlers/internal/multisig.d.ts +2 -6
- package/dist/handlers/internal/multisig.d.ts.map +1 -1
- package/dist/handlers/internal/multisig.js +11 -36
- package/dist/handlers/internal/multisig.js.map +1 -1
- package/dist/handlers/internal/sponsorship.d.ts +35 -2
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
- package/dist/handlers/internal/sponsorship.js +53 -14
- package/dist/handlers/internal/sponsorship.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +10 -3
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +14 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts +6 -2
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +16 -2
- package/dist/handlers/relay.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +222 -6
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +625 -74
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +4 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +10 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +39 -8
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Fees.d.ts +27 -0
- package/dist/internal/Fees.d.ts.map +1 -0
- package/dist/internal/Fees.js +48 -0
- package/dist/internal/Fees.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +183 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -0
- package/dist/internal/FundingRoutes.js +490 -0
- package/dist/internal/FundingRoutes.js.map +1 -0
- package/dist/internal/Id.d.ts +15 -0
- package/dist/internal/Id.d.ts.map +1 -0
- package/dist/internal/Id.js +22 -0
- package/dist/internal/Id.js.map +1 -0
- package/dist/internal/Log.d.ts +9 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +7 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +10 -20
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +8 -58
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +25 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +51 -0
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +8 -0
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +7 -0
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +7 -1
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +16 -2
- package/dist/internal/Store.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 +1 -1
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +3 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +33 -6
- package/src/ApiKey.ts +10 -2
- package/src/ApiKeys.test.ts +155 -1
- package/src/ApiKeys.ts +203 -1
- package/src/App.test.ts +64 -75741
- package/src/App.ts +218 -420
- package/src/Client.test-d.ts +657 -15
- package/src/Scope.ts +26 -4
- package/src/Webhooks.ts +2 -2
- package/src/admin/apps/api-keys.test.ts +51 -0
- package/src/admin/apps/api-keys.ts +84 -0
- package/src/admin/apps/scopes.test.ts +11 -1
- package/src/admin/apps/scopes.ts +3 -0
- package/src/admin/apps/verified-tokens.ts +1 -1
- package/src/admin/ui/src/components/Page.tsx +2 -3
- package/src/admin/ui/src/routes/__root.tsx +2 -2
- package/src/admin/ui/src/routes/api-keys.tsx +2 -2
- package/src/admin/ui.generated.ts +13 -13
- package/src/analytics/Analytics.test.ts +527 -0
- package/src/analytics/Analytics.ts +318 -0
- package/src/analytics/tables/requestEvents.ts +523 -0
- package/src/apps/composition.test.ts +66 -0
- package/src/apps/data/App.test.ts +72 -0
- package/src/apps/data/App.ts +330 -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} +2 -2
- package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
- package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +3 -3
- 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} +1 -1
- 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/data/routes/funding.test.ts +214 -0
- package/src/apps/data/routes/funding.ts +584 -0
- package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +26 -5
- package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
- package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +3 -3
- package/src/apps/{receipts.ts → data/routes/receipts.ts} +15 -22
- package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +122 -7
- package/src/apps/{rpc.ts → data/routes/rpc.ts} +38 -8
- package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +3 -3
- package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
- package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +2 -2
- package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
- package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +3 -3
- package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
- package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +2 -2
- package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
- package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +3 -3
- package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
- package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +7 -7
- package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +50 -43
- package/src/apps/docs/App.ts +65 -0
- package/src/apps/docs/index.ts +1 -0
- package/src/apps/docs/reference.ts +132 -0
- package/src/apps/index.ts +4 -0
- package/src/apps/mcp.test.ts +206 -0
- package/src/apps/mcp.ts +250 -0
- package/src/apps/metadata.ts +23 -0
- package/src/apps/platform/App.ts +169 -0
- package/src/apps/platform/Billing.test.ts +530 -0
- package/src/apps/platform/Billing.ts +682 -0
- package/src/apps/platform/index.ts +2 -0
- package/src/apps/platform/routes/api-keys.test.ts +493 -0
- package/src/apps/platform/routes/api-keys.ts +543 -0
- package/src/apps/platform/routes/billing.test.ts +1100 -0
- package/src/apps/platform/routes/billing.ts +966 -0
- package/src/apps/platform/routes/invitations.test.ts +409 -0
- package/src/apps/platform/routes/invitations.ts +587 -0
- package/src/apps/platform/routes/me.test.ts +351 -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 +365 -0
- package/src/apps/platform/routes/projects.test.ts +181 -0
- package/src/apps/platform/routes/projects.ts +367 -0
- package/src/apps/platform/routes/scopes.test.ts +29 -0
- package/src/apps/platform/routes/scopes.ts +85 -0
- package/src/apps/platform/routes/usage.test.ts +422 -0
- package/src/apps/platform/routes/usage.ts +527 -0
- package/src/apps/relay/App.test.ts +933 -0
- package/src/apps/relay/App.ts +236 -0
- package/src/apps/relay/Sponsorships.test.ts +375 -0
- package/src/apps/relay/Sponsorships.ts +306 -0
- package/src/apps/relay/index.ts +2 -0
- package/src/cli/Cli.test.ts +25 -2
- package/src/cli/Cli.ts +18 -3
- package/src/cli/commands/admin.test.ts +83 -0
- package/src/cli/commands/admin.ts +91 -3
- package/src/db/Db.ts +296 -0
- package/src/db/Schema.ts +241 -2
- package/src/db/tables/billingSettings.test.ts +87 -0
- package/src/db/tables/billingSettings.ts +108 -0
- package/src/db/tables/invitations.ts +198 -0
- package/src/db/tables/memberships.ts +208 -0
- package/src/db/tables/organizations.test.ts +38 -2
- package/src/db/tables/organizations.ts +117 -2
- package/src/db/tables/projects.ts +107 -0
- package/src/db/tables/requestUsage.ts +231 -0
- package/src/db/tables/sponsoredTransactions.test.ts +325 -0
- package/src/db/tables/sponsoredTransactions.ts +464 -0
- package/src/db/tables/stripeCustomers.test.ts +109 -0
- package/src/db/tables/stripeCustomers.ts +162 -0
- package/src/db/tables/users.ts +85 -0
- package/src/handlers/internal/executionError.ts +2 -0
- package/src/handlers/internal/multisig.test.ts +84 -10
- package/src/handlers/internal/multisig.ts +15 -50
- package/src/handlers/internal/sponsorship.ts +106 -20
- package/src/handlers/internal/utils.ts +19 -7
- package/src/handlers/{internal/relay.localnet.test.ts → relay.test.ts} +2 -2
- package/src/handlers/relay.ts +23 -5
- package/src/index.ts +1 -0
- package/src/internal/Auth.test.ts +491 -25
- package/src/internal/Auth.ts +839 -84
- package/src/internal/Cache.ts +5 -1
- package/src/internal/Cursor.test.ts +3 -2
- package/src/internal/EdgeCache.test.ts +27 -4
- package/src/internal/EdgeCache.ts +47 -9
- package/src/internal/Fees.test.ts +32 -0
- package/src/internal/Fees.ts +51 -0
- package/src/internal/FundingRoutes.ts +583 -0
- package/src/internal/Id.test.ts +17 -0
- package/src/internal/Id.ts +24 -0
- package/src/internal/Log.test.ts +2 -1
- package/src/internal/Log.ts +16 -3
- package/src/internal/MetricSink.ts +1 -1
- package/src/internal/OpenApi.ts +12 -60
- package/src/internal/RateLimit.test.ts +84 -0
- package/src/internal/RateLimit.ts +70 -0
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.ts +8 -0
- package/src/internal/Store.test.ts +52 -0
- package/src/internal/Store.ts +24 -5
- package/src/internal/Tidx.local.test.ts +1 -1
- package/src/internal/VerifiedTokens.test.ts +1 -1
- package/src/internal/WebhookDestination.test.ts +1 -1
- package/src/internal/Webhooks.test.ts +7 -7
- package/src/internal/Webhooks.ts +4 -4
- 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/src/handlers/internal/multisig.localnet.test.ts +0 -146
- /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,236 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { createMiddleware } from 'hono/factory'
|
|
3
|
+
import { RpcResponse } from 'ox'
|
|
4
|
+
import { Addresses, TokenId, Transaction } from 'viem/tempo'
|
|
5
|
+
|
|
6
|
+
import type * as App from '../../App.js'
|
|
7
|
+
import * as BillingSettings from '../../db/tables/billingSettings.js'
|
|
8
|
+
import * as Db from '../../db/Db.js'
|
|
9
|
+
import * as Fees from '../../internal/Fees.js'
|
|
10
|
+
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
11
|
+
import * as Billing from '../platform/Billing.js'
|
|
12
|
+
import * as Handler from '../../Handler.js'
|
|
13
|
+
import * as Auth from '../../internal/Auth.js'
|
|
14
|
+
import * as OpenApi from '../../internal/OpenApi.js'
|
|
15
|
+
import * as Path from '../../internal/Path.js'
|
|
16
|
+
import * as VerifiedTokens from '../../internal/VerifiedTokens.js'
|
|
17
|
+
import * as Viem from '../../internal/Viem.js'
|
|
18
|
+
import * as Scope from '../../Scope.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The relay route group: the wallet relay at `/rpc/relay`. Mount
|
|
22
|
+
* after `data()`, whose published chain clients and supported-chain set the
|
|
23
|
+
* middleware reads from request context.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* App.create(options).route('/', data()).route('/', relay())
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function relay(options: relay.Options = {}) {
|
|
30
|
+
const handler = middleware(options)
|
|
31
|
+
return new Hono<App.Environment>()
|
|
32
|
+
.use(
|
|
33
|
+
'/rpc/relay',
|
|
34
|
+
Auth.policy({ apiKey: { scopes: ['rpc-relay:read'] }, mpp: false, public: false }),
|
|
35
|
+
OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
|
|
36
|
+
)
|
|
37
|
+
.use(
|
|
38
|
+
'/rpc/relay/*',
|
|
39
|
+
Auth.policy({ apiKey: { scopes: ['rpc-relay:read'] }, mpp: false, public: false }),
|
|
40
|
+
OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
|
|
41
|
+
)
|
|
42
|
+
.all('/rpc/relay', handler)
|
|
43
|
+
.all('/rpc/relay/:chainId', handler)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export declare namespace relay {
|
|
47
|
+
/** Options for the relay route group. */
|
|
48
|
+
type Options = {
|
|
49
|
+
feePayer?:
|
|
50
|
+
| (Pick<
|
|
51
|
+
NonNullable<Handler.relay.Options['feePayer']>,
|
|
52
|
+
'account' | 'feeToken' | 'name' | 'url'
|
|
53
|
+
> & {
|
|
54
|
+
/** Per-transaction fee cap (decimal USD string); orgs tighten it, never loosen. @default '1.00' */
|
|
55
|
+
txFeeLimit?: string | undefined
|
|
56
|
+
})
|
|
57
|
+
| undefined
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The wallet relay middleware: proxies wallet JSON-RPC (fills, sponsored
|
|
62
|
+
// sends) with wallet-aware enrichment. Built per request from context rather
|
|
63
|
+
// than closed over group locals; relay traffic is low-QPS and the viem
|
|
64
|
+
// clients are memoized upstream, so per-request handler construction is cheap.
|
|
65
|
+
function middleware(options: relay.Options = {}) {
|
|
66
|
+
// Platform-wide per-transaction cap; org settings tighten it, never loosen.
|
|
67
|
+
const txFeeLimit = options.feePayer
|
|
68
|
+
? Billing.toBaseUnits(options.feePayer.txFeeLimit ?? '1.00')
|
|
69
|
+
: 0n
|
|
70
|
+
return createMiddleware<App.Environment>(async (c) => {
|
|
71
|
+
const verifiedTokens = c.get('verifiedTokens')
|
|
72
|
+
|
|
73
|
+
const feePayer = (() => {
|
|
74
|
+
if (!options.feePayer) return undefined
|
|
75
|
+
const principal = Auth.getPrincipal(c)
|
|
76
|
+
const attribution =
|
|
77
|
+
principal?.type === 'api_key' && principal.projectId !== undefined
|
|
78
|
+
? {
|
|
79
|
+
apiKeyId: principal.id,
|
|
80
|
+
environment: principal.environment,
|
|
81
|
+
orgId: principal.orgId,
|
|
82
|
+
projectId: principal.projectId,
|
|
83
|
+
}
|
|
84
|
+
: undefined
|
|
85
|
+
const sponsorable =
|
|
86
|
+
attribution !== undefined &&
|
|
87
|
+
principal?.type === 'api_key' &&
|
|
88
|
+
(principal.apiKey.scopes.includes('rpc-relay:sponsor') ||
|
|
89
|
+
principal.apiKey.scopes.includes(Scope.wildcard))
|
|
90
|
+
// Lifted so validation and the sponsored-token default read one value.
|
|
91
|
+
const feeToken_sponsor = options.feePayer.feeToken ?? Addresses.pathUsd
|
|
92
|
+
return {
|
|
93
|
+
...options.feePayer,
|
|
94
|
+
feeToken: feeToken_sponsor,
|
|
95
|
+
validate: async (transaction) => {
|
|
96
|
+
// Allowlist the chains this deployment serves rather than denylisting
|
|
97
|
+
// only mainnet: the relay resolves chainId from the request body, so
|
|
98
|
+
// the query-string chain guard never sees it.
|
|
99
|
+
const id = Number(transaction.chainId)
|
|
100
|
+
if (!attribution || !sponsorable || !c.get('supportedChainIds').has(id)) return false
|
|
101
|
+
if (!Viem.isMainnet(id)) return true
|
|
102
|
+
// Mainnet is production-only: a sandbox key resolves chainId from the
|
|
103
|
+
// request body, so the auth-layer sandbox/mainnet guard never sees
|
|
104
|
+
// it. Refuse here, or sandbox spend would sponsor mainnet unbilled.
|
|
105
|
+
if (attribution.environment !== 'production') return false
|
|
106
|
+
|
|
107
|
+
// Enforcement reads hit the primary, never the cached replica: a
|
|
108
|
+
// stale `past_due`, a lowered limit, or another isolate's pending
|
|
109
|
+
// spend must not keep the gate open.
|
|
110
|
+
const db = Db.get(c.get('db'))
|
|
111
|
+
// Mainnet sponsorship requires an active billing source; deployments
|
|
112
|
+
// without billing configured have no rows, so mainnet stays closed.
|
|
113
|
+
// `past_due` gets its own reason: the caller must settle an invoice,
|
|
114
|
+
// not attach a card.
|
|
115
|
+
const billing = await Billing.status(db, attribution.orgId)
|
|
116
|
+
if (billing === 'past_due') return 'billing_past_due'
|
|
117
|
+
if (billing !== 'active') return 'billing_required'
|
|
118
|
+
|
|
119
|
+
const feeToken = (() => {
|
|
120
|
+
try {
|
|
121
|
+
return TokenId.toAddress(transaction.feeToken ?? feeToken_sponsor)
|
|
122
|
+
} catch {
|
|
123
|
+
return undefined
|
|
124
|
+
}
|
|
125
|
+
})()
|
|
126
|
+
const verified = feeToken
|
|
127
|
+
? (await VerifiedTokens.read(Db.get(c.get('dbCached')), id))?.byAddress.get(feeToken.toLowerCase()) // prettier-ignore
|
|
128
|
+
: undefined
|
|
129
|
+
if (
|
|
130
|
+
!verified ||
|
|
131
|
+
verified.currency.toLowerCase() !== 'usd' ||
|
|
132
|
+
verified.decimals !== Fees.tokenDecimals
|
|
133
|
+
)
|
|
134
|
+
// prettier-ignore
|
|
135
|
+
return 'fee_token_unsupported'
|
|
136
|
+
|
|
137
|
+
// Fail closed: a transaction whose signed cap cannot be computed
|
|
138
|
+
// cannot be bounded, so it never sponsors on mainnet.
|
|
139
|
+
const feeMax = Fees.maxOf(transaction)
|
|
140
|
+
if (feeMax === undefined) return 'tx_fee_limit_exceeded'
|
|
141
|
+
const settings = await BillingSettings.get(db, attribution.orgId)
|
|
142
|
+
const cap = (() => {
|
|
143
|
+
if (settings?.txFeeLimit == null) return txFeeLimit
|
|
144
|
+
const org = Billing.toBaseUnits(settings.txFeeLimit)
|
|
145
|
+
return org < txFeeLimit ? org : txFeeLimit
|
|
146
|
+
})()
|
|
147
|
+
if (feeMax > cap) return 'tx_fee_limit_exceeded'
|
|
148
|
+
// The period spend limit is enforced atomically at recording time
|
|
149
|
+
// (`onSponsored` → `SponsoredTransactions.reserve`); a check here
|
|
150
|
+
// would be a check-then-act race a burst could slip through.
|
|
151
|
+
return true
|
|
152
|
+
},
|
|
153
|
+
onSponsored: async (event) => {
|
|
154
|
+
if (!attribution) return
|
|
155
|
+
const mainnet = Viem.isMainnet(event.chainId)
|
|
156
|
+
// Defense in depth: `validate` already refuses non-production
|
|
157
|
+
// mainnet, so reaching here with that pair is a wiring bug.
|
|
158
|
+
if (mainnet && attribution.environment !== 'production')
|
|
159
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Sponsorship rejected.' })
|
|
160
|
+
|
|
161
|
+
// The cap recomputes from canonical envelope bytes, mirroring the
|
|
162
|
+
// reconciliation convention; pending rows charge it to spend limits.
|
|
163
|
+
const feeMax = Fees.maxOf(Transaction.deserialize(event.transaction as `0x76${string}`))
|
|
164
|
+
const db = Db.get(c.get('db'))
|
|
165
|
+
|
|
166
|
+
const feeToken = (() => {
|
|
167
|
+
try {
|
|
168
|
+
return event.feeToken !== undefined ? TokenId.toAddress(event.feeToken) : undefined
|
|
169
|
+
} catch {
|
|
170
|
+
return undefined
|
|
171
|
+
}
|
|
172
|
+
})()
|
|
173
|
+
|
|
174
|
+
const currency = feeToken
|
|
175
|
+
? (await VerifiedTokens.read(Db.get(c.get('dbCached')), event.chainId))?.byAddress
|
|
176
|
+
.get(feeToken.toLowerCase())
|
|
177
|
+
?.currency.toLowerCase()
|
|
178
|
+
: undefined
|
|
179
|
+
|
|
180
|
+
const input = {
|
|
181
|
+
apiKeyId: attribution.apiKeyId,
|
|
182
|
+
billable: attribution.environment === 'production',
|
|
183
|
+
chainId: event.chainId,
|
|
184
|
+
environment: attribution.environment,
|
|
185
|
+
orgId: attribution.orgId,
|
|
186
|
+
projectId: attribution.projectId,
|
|
187
|
+
signPayload: event.signPayload,
|
|
188
|
+
transaction: event.transaction,
|
|
189
|
+
...(currency ? { currency } : {}),
|
|
190
|
+
...(feeMax !== undefined ? { feeMax: feeMax.toString() } : {}),
|
|
191
|
+
...(feeToken ? { feeToken } : {}),
|
|
192
|
+
...(event.transactionHash ? { transactionHash: event.transactionHash } : {}),
|
|
193
|
+
}
|
|
194
|
+
// Re-read settings on the authoritative path: the limit may have
|
|
195
|
+
// changed since `validate`, and only mainnet spend is limited.
|
|
196
|
+
const settings = mainnet ? await BillingSettings.get(db, attribution.orgId) : undefined
|
|
197
|
+
if (settings?.spendLimit == null) return void (await SponsoredTransactions.upsert(db, input)) // prettier-ignore
|
|
198
|
+
try {
|
|
199
|
+
await SponsoredTransactions.reserve(db, input, {
|
|
200
|
+
chainIds: [Viem.chainId.mainnet],
|
|
201
|
+
max: Billing.toBaseUnits(settings.spendLimit),
|
|
202
|
+
since: Billing.periodStart(settings.period),
|
|
203
|
+
})
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error instanceof SponsoredTransactions.PeriodSpendLimitError)
|
|
206
|
+
throw new RpcResponse.InvalidParamsError({
|
|
207
|
+
data: { code: 'spend_limit_exceeded' },
|
|
208
|
+
message: 'Spend limit exceeded.',
|
|
209
|
+
})
|
|
210
|
+
throw error
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
} satisfies Handler.relay.Options['feePayer']
|
|
214
|
+
})()
|
|
215
|
+
|
|
216
|
+
// Dispatching a standalone Hono via `fetch(c.req.raw)` bypasses Hono's
|
|
217
|
+
// mount-path stripping, so its internal routes register at the request's
|
|
218
|
+
// full external path (`basePath` + `/rpc/relay`).
|
|
219
|
+
const handler = Handler.relay({
|
|
220
|
+
cache: c.get('store'),
|
|
221
|
+
features: 'all',
|
|
222
|
+
getClient: (chainId) => c.get('getClient')(chainId as Viem.ChainId | undefined),
|
|
223
|
+
path: Path.join(c.get('basePath'), 'rpc/relay'),
|
|
224
|
+
...(feePayer ? { feePayer } : {}),
|
|
225
|
+
...(verifiedTokens
|
|
226
|
+
? {
|
|
227
|
+
resolveTokens: async (chainId: number) => {
|
|
228
|
+
const snapshot = await VerifiedTokens.read(Db.get(c.get('dbCached')), chainId)
|
|
229
|
+
return snapshot?.list.map((token) => token.address) ?? []
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
: {}),
|
|
233
|
+
})
|
|
234
|
+
return handler.fetch(c.req.raw)
|
|
235
|
+
})
|
|
236
|
+
}
|
|
@@ -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(`"1"`)
|
|
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
|
+
})
|