tapimo 0.0.0 → 0.0.2
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/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/ApiKey.d.ts +65 -0
- package/dist/ApiKey.d.ts.map +1 -0
- package/dist/ApiKey.js +105 -0
- package/dist/ApiKey.js.map +1 -0
- package/dist/ApiKeySource.d.ts +166 -0
- package/dist/ApiKeySource.d.ts.map +1 -0
- package/dist/ApiKeySource.js +234 -0
- package/dist/ApiKeySource.js.map +1 -0
- package/dist/App.d.ts +19317 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +612 -0
- package/dist/App.js.map +1 -0
- package/dist/Assets.d.ts +44 -0
- package/dist/Assets.d.ts.map +1 -0
- package/dist/Assets.js +23 -0
- package/dist/Assets.js.map +1 -0
- package/dist/Auth.d.ts +2 -0
- package/dist/Auth.d.ts.map +1 -0
- package/dist/Auth.js +2 -0
- package/dist/Auth.js.map +1 -0
- package/dist/Cli.d.ts +21 -0
- package/dist/Cli.d.ts.map +1 -0
- package/dist/Cli.js +13 -0
- package/dist/Cli.js.map +1 -0
- package/dist/Client.d.ts +20 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +24 -0
- package/dist/Client.js.map +1 -0
- package/dist/Metrics.d.ts +70 -0
- package/dist/Metrics.d.ts.map +1 -0
- package/dist/Metrics.js +91 -0
- package/dist/Metrics.js.map +1 -0
- package/dist/Scope.d.ts +35 -0
- package/dist/Scope.d.ts.map +1 -0
- package/dist/Scope.js +38 -0
- package/dist/Scope.js.map +1 -0
- package/dist/Webhooks.d.ts +79 -0
- package/dist/Webhooks.d.ts.map +1 -0
- package/dist/Webhooks.js +107 -0
- package/dist/Webhooks.js.map +1 -0
- package/dist/admin/Access.d.ts +41 -0
- package/dist/admin/Access.d.ts.map +1 -0
- package/dist/admin/Access.js +75 -0
- package/dist/admin/Access.js.map +1 -0
- package/dist/admin/App.d.ts +1221 -0
- package/dist/admin/App.d.ts.map +1 -0
- package/dist/admin/App.js +113 -0
- package/dist/admin/App.js.map +1 -0
- package/dist/admin/apps/api-keys.d.ts +209 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -0
- package/dist/admin/apps/api-keys.js +129 -0
- package/dist/admin/apps/api-keys.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +46 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -0
- package/dist/admin/apps/scopes.js +41 -0
- package/dist/admin/apps/scopes.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +1206 -0
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
- package/dist/admin/apps/verified-tokens.js +485 -0
- package/dist/admin/apps/verified-tokens.js.map +1 -0
- package/dist/admin/index.d.ts +3 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/ui.generated.d.ts +11 -0
- package/dist/admin/ui.generated.d.ts.map +1 -0
- package/dist/admin/ui.generated.js +43 -0
- package/dist/admin/ui.generated.js.map +1 -0
- package/dist/apps/activities.d.ts +18725 -0
- package/dist/apps/activities.d.ts.map +1 -0
- package/dist/apps/activities.js +3138 -0
- package/dist/apps/activities.js.map +1 -0
- package/dist/apps/addresses.d.ts +3662 -0
- package/dist/apps/addresses.d.ts.map +1 -0
- package/dist/apps/addresses.js +22 -0
- package/dist/apps/addresses.js.map +1 -0
- package/dist/apps/balances.d.ts +342 -0
- package/dist/apps/balances.d.ts.map +1 -0
- package/dist/apps/balances.js +393 -0
- package/dist/apps/balances.js.map +1 -0
- package/dist/apps/blocks.d.ts +931 -0
- package/dist/apps/blocks.d.ts.map +1 -0
- package/dist/apps/blocks.js +689 -0
- package/dist/apps/blocks.js.map +1 -0
- package/dist/apps/coingecko.d.ts +1053 -0
- package/dist/apps/coingecko.d.ts.map +1 -0
- package/dist/apps/coingecko.js +1015 -0
- package/dist/apps/coingecko.js.map +1 -0
- package/dist/apps/exchanges.d.ts +3000 -0
- package/dist/apps/exchanges.d.ts.map +1 -0
- package/dist/apps/exchanges.js +3570 -0
- package/dist/apps/exchanges.js.map +1 -0
- package/dist/apps/fee-amm.d.ts +640 -0
- package/dist/apps/fee-amm.d.ts.map +1 -0
- package/dist/apps/fee-amm.js +815 -0
- package/dist/apps/fee-amm.js.map +1 -0
- package/dist/apps/indexer.d.ts +116 -0
- package/dist/apps/indexer.d.ts.map +1 -0
- package/dist/apps/indexer.js +270 -0
- package/dist/apps/indexer.js.map +1 -0
- package/dist/apps/receipts.d.ts +1438 -0
- package/dist/apps/receipts.d.ts.map +1 -0
- package/dist/apps/receipts.js +874 -0
- package/dist/apps/receipts.js.map +1 -0
- package/dist/apps/rpc.d.ts +173 -0
- package/dist/apps/rpc.d.ts.map +1 -0
- package/dist/apps/rpc.js +191 -0
- package/dist/apps/rpc.js.map +1 -0
- package/dist/apps/tokenlist.d.ts +192 -0
- package/dist/apps/tokenlist.d.ts.map +1 -0
- package/dist/apps/tokenlist.js +153 -0
- package/dist/apps/tokenlist.js.map +1 -0
- package/dist/apps/tokens.d.ts +2246 -0
- package/dist/apps/tokens.d.ts.map +1 -0
- package/dist/apps/tokens.js +1789 -0
- package/dist/apps/tokens.js.map +1 -0
- package/dist/apps/transactions.d.ts +2327 -0
- package/dist/apps/transactions.d.ts.map +1 -0
- package/dist/apps/transactions.js +975 -0
- package/dist/apps/transactions.js.map +1 -0
- package/dist/apps/transfers.d.ts +589 -0
- package/dist/apps/transfers.d.ts.map +1 -0
- package/dist/apps/transfers.js +769 -0
- package/dist/apps/transfers.js.map +1 -0
- package/dist/apps/verified-tokens.d.ts +544 -0
- package/dist/apps/verified-tokens.d.ts.map +1 -0
- package/dist/apps/verified-tokens.js +234 -0
- package/dist/apps/verified-tokens.js.map +1 -0
- package/dist/apps/webhooks.d.ts +7684 -0
- package/dist/apps/webhooks.d.ts.map +1 -0
- package/dist/apps/webhooks.js +2112 -0
- package/dist/apps/webhooks.js.map +1 -0
- package/dist/cloudflare.d.ts +32 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +45 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/Auth.d.ts +298 -0
- package/dist/internal/Auth.d.ts.map +1 -0
- package/dist/internal/Auth.js +618 -0
- package/dist/internal/Auth.js.map +1 -0
- package/dist/internal/Cache.d.ts +74 -0
- package/dist/internal/Cache.d.ts.map +1 -0
- package/dist/internal/Cache.js +215 -0
- package/dist/internal/Cache.js.map +1 -0
- package/dist/internal/Cursor.d.ts +79 -0
- package/dist/internal/Cursor.d.ts.map +1 -0
- package/dist/internal/Cursor.js +116 -0
- package/dist/internal/Cursor.js.map +1 -0
- package/dist/internal/EdgeCache.d.ts +59 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -0
- package/dist/internal/EdgeCache.js +174 -0
- package/dist/internal/EdgeCache.js.map +1 -0
- package/dist/internal/Log.d.ts +104 -0
- package/dist/internal/Log.d.ts.map +1 -0
- package/dist/internal/Log.js +118 -0
- package/dist/internal/Log.js.map +1 -0
- package/dist/internal/MetricSink.d.ts +65 -0
- package/dist/internal/MetricSink.d.ts.map +1 -0
- package/dist/internal/MetricSink.js +146 -0
- package/dist/internal/MetricSink.js.map +1 -0
- package/dist/internal/Mpp.d.ts +71 -0
- package/dist/internal/Mpp.d.ts.map +1 -0
- package/dist/internal/Mpp.js +105 -0
- package/dist/internal/Mpp.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +313 -0
- package/dist/internal/OpenApi.d.ts.map +1 -0
- package/dist/internal/OpenApi.fonts.d.ts +2 -0
- package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
- package/dist/internal/OpenApi.fonts.js +39 -0
- package/dist/internal/OpenApi.fonts.js.map +1 -0
- package/dist/internal/OpenApi.js +335 -0
- package/dist/internal/OpenApi.js.map +1 -0
- package/dist/internal/OpenApi.logo.d.ts +6 -0
- package/dist/internal/OpenApi.logo.d.ts.map +1 -0
- package/dist/internal/OpenApi.logo.js +19 -0
- package/dist/internal/OpenApi.logo.js.map +1 -0
- package/dist/internal/Path.d.ts +5 -0
- package/dist/internal/Path.d.ts.map +1 -0
- package/dist/internal/Path.js +16 -0
- package/dist/internal/Path.js.map +1 -0
- package/dist/internal/RateLimit.d.ts +60 -0
- package/dist/internal/RateLimit.d.ts.map +1 -0
- package/dist/internal/RateLimit.js +48 -0
- package/dist/internal/RateLimit.js.map +1 -0
- package/dist/internal/Response.d.ts +132 -0
- package/dist/internal/Response.d.ts.map +1 -0
- package/dist/internal/Response.js +72 -0
- package/dist/internal/Response.js.map +1 -0
- package/dist/internal/Schema.d.ts +257 -0
- package/dist/internal/Schema.d.ts.map +1 -0
- package/dist/internal/Schema.js +360 -0
- package/dist/internal/Schema.js.map +1 -0
- package/dist/internal/Store.d.ts +204 -0
- package/dist/internal/Store.d.ts.map +1 -0
- package/dist/internal/Store.js +291 -0
- package/dist/internal/Store.js.map +1 -0
- package/dist/internal/Tidx.d.ts +120 -0
- package/dist/internal/Tidx.d.ts.map +1 -0
- package/dist/internal/Tidx.js +156 -0
- package/dist/internal/Tidx.js.map +1 -0
- package/dist/internal/Timing.d.ts +22 -0
- package/dist/internal/Timing.d.ts.map +1 -0
- package/dist/internal/Timing.js +32 -0
- package/dist/internal/Timing.js.map +1 -0
- package/dist/internal/Ttl.d.ts +25 -0
- package/dist/internal/Ttl.d.ts.map +1 -0
- package/dist/internal/Ttl.js +49 -0
- package/dist/internal/Ttl.js.map +1 -0
- package/dist/internal/Value.d.ts +47 -0
- package/dist/internal/Value.d.ts.map +1 -0
- package/dist/internal/Value.js +122 -0
- package/dist/internal/Value.js.map +1 -0
- package/dist/internal/VerifiedTokens.d.ts +228 -0
- package/dist/internal/VerifiedTokens.d.ts.map +1 -0
- package/dist/internal/VerifiedTokens.js +450 -0
- package/dist/internal/VerifiedTokens.js.map +1 -0
- package/dist/internal/Viem.d.ts +2082 -0
- package/dist/internal/Viem.d.ts.map +1 -0
- package/dist/internal/Viem.js +63 -0
- package/dist/internal/Viem.js.map +1 -0
- package/dist/internal/WebhookDestination.d.ts +198 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -0
- package/dist/internal/WebhookDestination.js +170 -0
- package/dist/internal/WebhookDestination.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +433 -0
- package/dist/internal/Webhooks.d.ts.map +1 -0
- package/dist/internal/Webhooks.js +489 -0
- package/dist/internal/Webhooks.js.map +1 -0
- package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
- package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/betterStack.js +76 -0
- package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
- package/dist/internal/webhookDestinations/slack.d.ts +59 -0
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/slack.js +422 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -0
- package/dist/internal/webhookDestinations/url.d.ts +17 -0
- package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/url.js +130 -0
- package/dist/internal/webhookDestinations/url.js.map +1 -0
- package/dist/openrpc.json +5302 -0
- package/package.json +46 -4
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
- package/seeds/31318/verified-tokens.json +30 -0
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +177 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
- package/seeds/42431/verified-tokens.json +44 -0
- package/seeds/openrpc/README.md +27 -0
- package/seeds/openrpc/execution-apis.json +8989 -0
- package/src/ApiKey.test.ts +91 -0
- package/src/ApiKey.ts +122 -0
- package/src/ApiKeySource.test-d.ts +11 -0
- package/src/ApiKeySource.test.ts +295 -0
- package/src/ApiKeySource.ts +303 -0
- package/src/App.test-d.ts +38 -0
- package/src/App.test.ts +65927 -0
- package/src/App.ts +855 -0
- package/src/Assets.ts +58 -0
- package/src/Auth.ts +1 -0
- package/src/Cli.test.ts +81 -0
- package/src/Cli.ts +38 -0
- package/src/Client.test-d.ts +1438 -0
- package/src/Client.test.ts +134 -0
- package/src/Client.ts +45 -0
- package/src/Metrics.ts +154 -0
- package/src/Scope.ts +50 -0
- package/src/Webhooks.test.ts +49 -0
- package/src/Webhooks.ts +172 -0
- package/src/admin/Access.test.ts +130 -0
- package/src/admin/Access.ts +87 -0
- package/src/admin/App.test.ts +225 -0
- package/src/admin/App.ts +216 -0
- package/src/admin/apps/api-keys.test.ts +156 -0
- package/src/admin/apps/api-keys.ts +157 -0
- package/src/admin/apps/scopes.test.ts +52 -0
- package/src/admin/apps/scopes.ts +47 -0
- package/src/admin/apps/verified-tokens.test.ts +415 -0
- package/src/admin/apps/verified-tokens.ts +566 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/ui/plugin.ts +135 -0
- package/src/admin/ui/src/components/Checkbox.tsx +22 -0
- package/src/admin/ui/src/components/Dialog.tsx +32 -0
- package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
- package/src/admin/ui/src/components/Page.tsx +53 -0
- package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
- package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
- package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
- package/src/admin/ui/src/lib/api-keys.ts +63 -0
- package/src/admin/ui/src/lib/client.ts +15 -0
- package/src/admin/ui/src/lib/config.ts +57 -0
- package/src/admin/ui/src/lib/query-client.ts +26 -0
- package/src/admin/ui/src/lib/scopes.ts +25 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
- package/src/admin/ui/src/lib/wagmi.ts +18 -0
- package/src/admin/ui/src/routeTree.gen.ts +104 -0
- package/src/admin/ui/src/router.tsx +25 -0
- package/src/admin/ui/src/routes/__root.tsx +219 -0
- package/src/admin/ui/src/routes/api-keys.tsx +329 -0
- package/src/admin/ui/src/routes/index.tsx +7 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
- package/src/admin/ui/src/styles.css +20 -0
- package/src/admin/ui/tsconfig.json +16 -0
- package/src/admin/ui/vite.config.ts +29 -0
- package/src/admin/ui.generated.ts +60 -0
- package/src/apps/activities.test.ts +1020 -0
- package/src/apps/activities.ts +3934 -0
- package/src/apps/addresses.ts +24 -0
- package/src/apps/balances.test.ts +274 -0
- package/src/apps/balances.ts +533 -0
- package/src/apps/blocks.test.ts +385 -0
- package/src/apps/blocks.ts +896 -0
- package/src/apps/coingecko.test.ts +218 -0
- package/src/apps/coingecko.ts +1383 -0
- package/src/apps/exchanges.test.ts +958 -0
- package/src/apps/exchanges.ts +4773 -0
- package/src/apps/fee-amm.test.ts +321 -0
- package/src/apps/fee-amm.ts +1121 -0
- package/src/apps/indexer.test.ts +153 -0
- package/src/apps/indexer.ts +330 -0
- package/src/apps/receipts.test.ts +285 -0
- package/src/apps/receipts.ts +1193 -0
- package/src/apps/rpc.test.ts +302 -0
- package/src/apps/rpc.ts +222 -0
- package/src/apps/tokenlist.test.ts +80 -0
- package/src/apps/tokenlist.ts +207 -0
- package/src/apps/tokens.test.ts +1062 -0
- package/src/apps/tokens.ts +2292 -0
- package/src/apps/transactions.test.ts +408 -0
- package/src/apps/transactions.ts +1363 -0
- package/src/apps/transfers.test.ts +371 -0
- package/src/apps/transfers.ts +1116 -0
- package/src/apps/verified-tokens.test.ts +114 -0
- package/src/apps/verified-tokens.ts +295 -0
- package/src/apps/webhooks.test.ts +1937 -0
- package/src/apps/webhooks.ts +2926 -0
- package/src/cloudflare.ts +55 -0
- package/src/index.ts +15 -0
- package/src/internal/Auth.test.ts +980 -0
- package/src/internal/Auth.ts +1012 -0
- package/src/internal/Cache.test.ts +240 -0
- package/src/internal/Cache.ts +262 -0
- package/src/internal/Cursor.test.ts +100 -0
- package/src/internal/Cursor.ts +149 -0
- package/src/internal/EdgeCache.test.ts +234 -0
- package/src/internal/EdgeCache.ts +224 -0
- package/src/internal/Errors.ts +14 -0
- package/src/internal/Log.test.ts +244 -0
- package/src/internal/Log.ts +216 -0
- package/src/internal/MetricSink.ts +220 -0
- package/src/internal/Mpp.test.ts +64 -0
- package/src/internal/Mpp.ts +141 -0
- package/src/internal/OpenApi.fonts.ts +38 -0
- package/src/internal/OpenApi.logo.ts +18 -0
- package/src/internal/OpenApi.ts +488 -0
- package/src/internal/Path.test.ts +14 -0
- package/src/internal/Path.ts +15 -0
- package/src/internal/RateLimit.test.ts +93 -0
- package/src/internal/RateLimit.ts +93 -0
- package/src/internal/Response.ts +131 -0
- package/src/internal/Schema.test.ts +34 -0
- package/src/internal/Schema.ts +523 -0
- package/src/internal/Store.test.ts +545 -0
- package/src/internal/Store.ts +526 -0
- package/src/internal/Tidx.local.test.ts +33 -0
- package/src/internal/Tidx.test.ts +294 -0
- package/src/internal/Tidx.ts +238 -0
- package/src/internal/Timing.test.ts +22 -0
- package/src/internal/Timing.ts +62 -0
- package/src/internal/Ttl.test.ts +18 -0
- package/src/internal/Ttl.ts +59 -0
- package/src/internal/Value.ts +114 -0
- package/src/internal/VerifiedTokens.test.ts +333 -0
- package/src/internal/VerifiedTokens.ts +645 -0
- package/src/internal/Viem.test.ts +115 -0
- package/src/internal/Viem.ts +165 -0
- package/src/internal/WebhookDestination.test.ts +648 -0
- package/src/internal/WebhookDestination.ts +331 -0
- package/src/internal/Webhooks.test.ts +668 -0
- package/src/internal/Webhooks.ts +940 -0
- package/src/internal/webhookDestinations/betterStack.ts +91 -0
- package/src/internal/webhookDestinations/slack.ts +496 -0
- package/src/internal/webhookDestinations/url.ts +128 -0
- package/src/openrpc.json +5302 -0
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { Hex } from 'ox';
|
|
3
|
+
import * as z from 'zod/mini';
|
|
4
|
+
import * as Auth from '../internal/Auth.js';
|
|
5
|
+
import * as Cache from '../internal/Cache.js';
|
|
6
|
+
import * as Cursor from '../internal/Cursor.js';
|
|
7
|
+
import * as OpenApi from '../internal/OpenApi.js';
|
|
8
|
+
import * as Response from '../internal/Response.js';
|
|
9
|
+
import * as Schema from '../internal/Schema.js';
|
|
10
|
+
import * as Store from '../internal/Store.js';
|
|
11
|
+
import * as Timing from '../internal/Timing.js';
|
|
12
|
+
import * as Ttl from '../internal/Ttl.js';
|
|
13
|
+
import * as Value from '../internal/Value.js';
|
|
14
|
+
import * as VerifiedTokens from '../internal/VerifiedTokens.js';
|
|
15
|
+
import * as Receipts from './receipts.js';
|
|
16
|
+
import * as Tokens from './tokens.js';
|
|
17
|
+
/** Example transaction hash surfaced in OpenAPI docs. */
|
|
18
|
+
const exampleHash = '0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665';
|
|
19
|
+
/**
|
|
20
|
+
* Maps a transaction `type` byte (as a number) to its human-readable name. The
|
|
21
|
+
* raw `0x…` byte is always preserved under `rpc.type`.
|
|
22
|
+
*/
|
|
23
|
+
const transactionTypeByNumber = {
|
|
24
|
+
0: 'legacy',
|
|
25
|
+
1: 'eip2930',
|
|
26
|
+
2: 'eip1559',
|
|
27
|
+
3: 'eip4844',
|
|
28
|
+
4: 'eip7702',
|
|
29
|
+
118: 'tempo',
|
|
30
|
+
};
|
|
31
|
+
/** Decodes a transaction `type` (hex string or number) into its name. */
|
|
32
|
+
export function decodeTransactionType(value) {
|
|
33
|
+
const byte = typeof value === 'number' ? value : Value.hexToNumber(value);
|
|
34
|
+
return (byte !== undefined && transactionTypeByNumber[byte]) || 'unknown';
|
|
35
|
+
}
|
|
36
|
+
/** Zod schemas owned by the transaction handlers. */
|
|
37
|
+
export var schema;
|
|
38
|
+
(function (schema) {
|
|
39
|
+
// TODO(ox/zod): replace with the zod schema for `ox`'s `AccessList.Item` once
|
|
40
|
+
// ox ships a `zod` entrypoint.
|
|
41
|
+
/** One EIP-2930 access list entry (RPC format). */
|
|
42
|
+
schema.AccessListItem = z
|
|
43
|
+
.looseObject({
|
|
44
|
+
address: Schema.Address.check(z.describe('The account whose storage slots are listed in this access-list entry.')),
|
|
45
|
+
storageKeys: z
|
|
46
|
+
.array(Schema.Hash)
|
|
47
|
+
.check(z.describe('The storage slot keys the transaction plans to access.')),
|
|
48
|
+
})
|
|
49
|
+
.check(z.describe('One EIP-2930 access-list entry that predeclares an account and storage slots.'));
|
|
50
|
+
// TODO(ox/zod): replace with the zod schema for the RPC `Call` in `ox/tempo`'s
|
|
51
|
+
// `TxEnvelopeTempo`/`Transaction` types once ox ships a `zod` entrypoint.
|
|
52
|
+
/** One call within a Tempo account-abstraction transaction (RPC format). */
|
|
53
|
+
schema.Call = z
|
|
54
|
+
.looseObject({
|
|
55
|
+
data: z
|
|
56
|
+
.nullish(Schema.Hex)
|
|
57
|
+
.check(z.describe('Call data for this call; this is the same bytes as `input` when both are present.'), z.meta({ examples: ['0xdeadbeef'] })),
|
|
58
|
+
input: z
|
|
59
|
+
.nullish(Schema.Hex)
|
|
60
|
+
.check(z.describe('Call data sent to the contract, as `0x`-prefixed bytes.'), z.meta({ examples: ['0xdeadbeef'] })),
|
|
61
|
+
to: z
|
|
62
|
+
.nullish(Schema.Address)
|
|
63
|
+
.check(z.describe('The address this call targets, or `null` when the call creates a contract.')),
|
|
64
|
+
value: z
|
|
65
|
+
.nullish(Schema.Quantity)
|
|
66
|
+
.check(z.describe('Native value sent with this call, as a hex quantity.')),
|
|
67
|
+
})
|
|
68
|
+
.check(z.describe('One operation inside a Tempo account-abstraction transaction.'));
|
|
69
|
+
// TODO(ox/zod): replace with the zod schema for `ox/tempo`'s
|
|
70
|
+
// `SignatureEnvelope.Rpc` (the secp256k1 / p256 / webAuthn / keychain union)
|
|
71
|
+
// once ox ships a `zod` entrypoint.
|
|
72
|
+
/**
|
|
73
|
+
* A signature envelope (RPC format), passed through verbatim. Every field is
|
|
74
|
+
* optional because the envelope is a union (secp256k1 / p256 / webAuthn /
|
|
75
|
+
* keychain) whose shape — including whether a `type` discriminator is present —
|
|
76
|
+
* varies; the loose object preserves whatever the RPC returns.
|
|
77
|
+
*/
|
|
78
|
+
schema.SignatureEnvelope = z
|
|
79
|
+
.looseObject({
|
|
80
|
+
type: z
|
|
81
|
+
.optional(z.string())
|
|
82
|
+
.check(z.describe('The signature scheme used by this envelope, such as `secp256k1`, `p256`, or `keychain` when the RPC includes it.'), z.meta({ examples: ['secp256k1'] })),
|
|
83
|
+
})
|
|
84
|
+
.check(z.describe('The signature envelope exactly as returned by JSON-RPC.'));
|
|
85
|
+
/** The verbatim JSON-RPC payloads, surfaced under each resource's `rpc` field. */
|
|
86
|
+
let Rpc;
|
|
87
|
+
(function (Rpc) {
|
|
88
|
+
// TODO(ox/zod): replace with the zod schema for `ox/tempo`'s
|
|
89
|
+
// `Transaction.Rpc` union once ox ships a `zod` entrypoint.
|
|
90
|
+
/** A JSON-RPC transaction, passed through verbatim. */
|
|
91
|
+
Rpc.Transaction = z
|
|
92
|
+
.looseObject({
|
|
93
|
+
aaAuthorizationList: z
|
|
94
|
+
.optional(z.array(z.looseObject({})))
|
|
95
|
+
.check(z.describe('Tempo account-abstraction authorizations attached to the transaction.')),
|
|
96
|
+
accessList: z
|
|
97
|
+
.optional(z.array(schema.AccessListItem))
|
|
98
|
+
.check(z.describe('EIP-2930 access list that predeclares accounts and storage slots for the transaction.')),
|
|
99
|
+
authorizationList: z
|
|
100
|
+
.optional(z.array(z.looseObject({})))
|
|
101
|
+
.check(z.describe('EIP-7702 authorizations attached to the transaction.')),
|
|
102
|
+
blobVersionedHashes: z
|
|
103
|
+
.optional(z.array(Schema.Hash))
|
|
104
|
+
.check(z.describe('Versioned blob hashes for EIP-4844 blob data.')),
|
|
105
|
+
blockHash: z
|
|
106
|
+
.nullable(Schema.Hash)
|
|
107
|
+
.check(z.describe('The block hash once included, or `null` while the transaction is pending.')),
|
|
108
|
+
blockNumber: z
|
|
109
|
+
.nullable(Schema.Quantity)
|
|
110
|
+
.check(z.describe('The block number once included, or `null` while the transaction is pending.')),
|
|
111
|
+
blockTimestamp: z
|
|
112
|
+
.nullish(Schema.Quantity)
|
|
113
|
+
.check(z.describe('The block timestamp for the block that included this transaction.')),
|
|
114
|
+
calls: z
|
|
115
|
+
.optional(z.union([
|
|
116
|
+
z.array(schema.Call).check(z.meta({
|
|
117
|
+
examples: [[{ data: '0xdeadbeef', input: '0xdeadbeef', to: null, value: '0x0' }]],
|
|
118
|
+
})),
|
|
119
|
+
z.null().check(z.meta({ examples: [null] })),
|
|
120
|
+
]))
|
|
121
|
+
.check(z.describe('Decoded calls for Tempo account-abstraction transactions.'), z.meta({
|
|
122
|
+
examples: [[{ data: '0xdeadbeef', input: '0xdeadbeef', to: null, value: '0x0' }]],
|
|
123
|
+
})),
|
|
124
|
+
chainId: z
|
|
125
|
+
.optional(Schema.Quantity)
|
|
126
|
+
.check(z.describe('The chain ID that the transaction is valid on; legacy transactions may omit it.')),
|
|
127
|
+
feePayerSignature: z
|
|
128
|
+
.nullish(schema.SignatureEnvelope)
|
|
129
|
+
.check(z.describe('Signature from the account that paid the transaction fee.'), z.meta({ examples: [{ type: 'secp256k1' }] })),
|
|
130
|
+
feeToken: z
|
|
131
|
+
.nullish(Schema.Address)
|
|
132
|
+
.check(z.describe('The fee token requested by the transaction; Tempo fees are paid in USD stablecoins such as `pathUSD`.')),
|
|
133
|
+
from: Schema.Address.check(z.describe('The address that submitted or authorized the transaction.')),
|
|
134
|
+
gas: Schema.Quantity.check(z.describe('Maximum gas the transaction is allowed to use.')),
|
|
135
|
+
gasPrice: z
|
|
136
|
+
.optional(Schema.Quantity)
|
|
137
|
+
.check(z.describe('Gas price for legacy-style transactions, expressed as a decimal string when humanized.')),
|
|
138
|
+
hash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies this transaction.')),
|
|
139
|
+
input: z
|
|
140
|
+
.optional(Schema.Hex)
|
|
141
|
+
.check(z.describe('Call data for non-Tempo transaction formats, as `0x`-prefixed bytes.'), z.meta({ examples: ['0xdeadbeef'] })),
|
|
142
|
+
keyAuthorization: z
|
|
143
|
+
.nullish(z.looseObject({}))
|
|
144
|
+
.check(z.describe('Key authorization data attached to this Tempo transaction.'), z.meta({ examples: [{}] })),
|
|
145
|
+
maxFeePerBlobGas: z
|
|
146
|
+
.optional(Schema.Quantity)
|
|
147
|
+
.check(z.describe('Maximum fee per blob gas for EIP-4844 blob data.')),
|
|
148
|
+
maxFeePerGas: z
|
|
149
|
+
.optional(Schema.Quantity)
|
|
150
|
+
.check(z.describe('Maximum total fee per gas the sender is willing to pay.')),
|
|
151
|
+
maxPriorityFeePerGas: z
|
|
152
|
+
.optional(Schema.Quantity)
|
|
153
|
+
.check(z.describe('Maximum priority fee per gas for EIP-1559-style transactions.')),
|
|
154
|
+
nonce: Schema.Quantity.check(z.describe('Nonce from the sender account that orders and de-duplicates transactions.')),
|
|
155
|
+
nonceKey: z
|
|
156
|
+
.optional(Schema.Quantity)
|
|
157
|
+
.check(z.describe('Tempo two-dimensional nonce key used to group nonce sequences.')),
|
|
158
|
+
r: z
|
|
159
|
+
.optional(Schema.Quantity)
|
|
160
|
+
.check(z.describe('The `r` value from the transaction ECDSA signature.')),
|
|
161
|
+
s: z
|
|
162
|
+
.optional(Schema.Quantity)
|
|
163
|
+
.check(z.describe('The `s` value from the transaction ECDSA signature.')),
|
|
164
|
+
signature: z
|
|
165
|
+
.optional(schema.SignatureEnvelope)
|
|
166
|
+
.check(z.describe('Signature from the sender authorizing this transaction.')),
|
|
167
|
+
to: z
|
|
168
|
+
.nullish(Schema.Address)
|
|
169
|
+
.check(z.describe('The recipient address, or `null` when the transaction creates a contract.')),
|
|
170
|
+
transactionIndex: z
|
|
171
|
+
.nullable(Schema.Quantity)
|
|
172
|
+
.check(z.describe('The transaction position within its block, or `null` while pending.')),
|
|
173
|
+
type: Schema.Hex.check(z.describe('Raw transaction type byte, such as `0x2` for EIP-1559 or `0x76` for Tempo.'), z.meta({ examples: ['0x2'] })),
|
|
174
|
+
v: z
|
|
175
|
+
.optional(Schema.Quantity)
|
|
176
|
+
.check(z.describe('The `v` recovery value from the transaction ECDSA signature.')),
|
|
177
|
+
validAfter: z
|
|
178
|
+
.nullish(Schema.Quantity)
|
|
179
|
+
.check(z.describe('Earliest Unix timestamp when this Tempo transaction may be included.'), z.meta({ examples: ['0x0'] })),
|
|
180
|
+
validBefore: z
|
|
181
|
+
.nullish(Schema.Quantity)
|
|
182
|
+
.check(z.describe('Latest Unix timestamp when this Tempo transaction may be included.'), z.meta({ examples: ['0xffffffff'] })),
|
|
183
|
+
value: z
|
|
184
|
+
.optional(Schema.Quantity)
|
|
185
|
+
.check(z.describe('Native value sent by non-Tempo transaction formats.')),
|
|
186
|
+
yParity: z
|
|
187
|
+
.optional(Schema.Quantity)
|
|
188
|
+
.check(z.describe('The y-parity value from the transaction signature.')),
|
|
189
|
+
})
|
|
190
|
+
.check(z.describe('The transaction exactly as returned by JSON-RPC.'));
|
|
191
|
+
})(Rpc = schema.Rpc || (schema.Rpc = {}));
|
|
192
|
+
/**
|
|
193
|
+
* Metadata for a transaction: the verbatim JSON-RPC transaction (always
|
|
194
|
+
* present) plus resources embedded on demand via `include`.
|
|
195
|
+
*
|
|
196
|
+
* The `feeToken` and `receipt` schemas are wrapped in `z.lazy` because
|
|
197
|
+
* `tokens.ts`/`receipts.ts` and `transactions.ts` mutually reference each
|
|
198
|
+
* other's schemas; deferring these lookups avoids the import-cycle crash
|
|
199
|
+
* during module init.
|
|
200
|
+
*/
|
|
201
|
+
schema.TransactionMeta = z
|
|
202
|
+
.object({
|
|
203
|
+
feeToken: z
|
|
204
|
+
.optional(z.lazy(() => Tokens.schema.getToken.Response))
|
|
205
|
+
.check(z.describe('Fee-token details included only when you request `include=feeToken`.')),
|
|
206
|
+
receipt: z
|
|
207
|
+
.optional(z.lazy(() => Receipts.schema.Receipt))
|
|
208
|
+
.check(z.describe('The transaction outcome included only when you request `include=receipt`.')),
|
|
209
|
+
rpc: Rpc.Transaction.check(z.describe('The original JSON-RPC transaction payload.')),
|
|
210
|
+
})
|
|
211
|
+
.check(z.describe('Metadata for this transaction: the original JSON-RPC payload plus any requested `include` resources.'));
|
|
212
|
+
/** Decoded transaction `type`; the raw `0x…` byte stays under `rpc.type`. */
|
|
213
|
+
schema.TransactionType = z
|
|
214
|
+
.enum(['legacy', 'eip2930', 'eip1559', 'eip4844', 'eip7702', 'tempo', 'unknown'])
|
|
215
|
+
.check(z.describe('Human-readable transaction type decoded from the raw JSON-RPC `type` byte.'), z.meta({ examples: ['eip1559'] }));
|
|
216
|
+
/** One humanized call within a Tempo account-abstraction transaction. */
|
|
217
|
+
schema.HumanCall = z
|
|
218
|
+
.object({
|
|
219
|
+
data: z
|
|
220
|
+
.optional(Schema.Hex)
|
|
221
|
+
.check(z.describe('Call data sent to the contract, as `0x`-prefixed bytes.'), z.meta({ examples: ['0xdeadbeef'] })),
|
|
222
|
+
to: z
|
|
223
|
+
.nullable(Schema.Address)
|
|
224
|
+
.check(z.describe('The address this call targets, or `null` when it creates a contract.')),
|
|
225
|
+
})
|
|
226
|
+
.check(z.describe('One decoded call inside a Tempo account-abstraction transaction.'));
|
|
227
|
+
/**
|
|
228
|
+
* A humanized transaction: decoded `type`, decimal-string amounts, ISO 8601
|
|
229
|
+
* timestamps, and plain-number block fields. The verbatim JSON-RPC transaction
|
|
230
|
+
* is always available under `meta.rpc`.
|
|
231
|
+
*/
|
|
232
|
+
schema.Transaction = z
|
|
233
|
+
.object({
|
|
234
|
+
blockHash: z
|
|
235
|
+
.nullable(Schema.Hash)
|
|
236
|
+
.check(z.describe('The block hash once included, or `null` while the transaction is pending.')),
|
|
237
|
+
blockNumber: z
|
|
238
|
+
.nullable(z.number().check(z.int(), z.nonnegative()))
|
|
239
|
+
.check(z.describe('The block number once included, or `null` while the transaction is pending.'), z.meta({ examples: [23456789] })),
|
|
240
|
+
calls: z
|
|
241
|
+
.optional(z.array(schema.HumanCall))
|
|
242
|
+
.check(z.describe('Decoded calls for Tempo account-abstraction transactions.'), z.meta({ examples: [[{ data: '0xdeadbeef', to: null }]] })),
|
|
243
|
+
chainId: z
|
|
244
|
+
.optional(z.number().check(z.int(), z.nonnegative()))
|
|
245
|
+
.check(z.describe('The chain ID that this transaction belongs to.'), z.meta({ examples: [4217] })),
|
|
246
|
+
feeToken: z
|
|
247
|
+
.optional(Schema.Address)
|
|
248
|
+
.check(z.describe('The fee token requested by the transaction; Tempo fees are paid in USD stablecoins such as `pathUSD`.')),
|
|
249
|
+
gas: z
|
|
250
|
+
.number()
|
|
251
|
+
.check(z.int(), z.nonnegative(), z.describe('Maximum gas the transaction is allowed to use.'), z.meta({ examples: [21000] })),
|
|
252
|
+
gasPrice: z
|
|
253
|
+
.optional(Schema.DecimalString)
|
|
254
|
+
.check(z.describe('Gas price for legacy-style transactions, expressed as a decimal string when humanized.')),
|
|
255
|
+
hash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies this transaction.')),
|
|
256
|
+
id: Schema.Hash.check(z.describe('Stable resource ID for this API response; it is the transaction hash.')),
|
|
257
|
+
input: z
|
|
258
|
+
.optional(Schema.Hex)
|
|
259
|
+
.check(z.describe('Call data for non-Tempo transaction formats, as `0x`-prefixed bytes.'), z.meta({ examples: ['0xdeadbeef'] })),
|
|
260
|
+
maxFeePerGas: z
|
|
261
|
+
.optional(Schema.DecimalString)
|
|
262
|
+
.check(z.describe('Maximum total fee per gas the sender is willing to pay.')),
|
|
263
|
+
maxPriorityFeePerGas: z
|
|
264
|
+
.optional(Schema.DecimalString)
|
|
265
|
+
.check(z.describe('Maximum priority fee per gas for EIP-1559-style transactions.')),
|
|
266
|
+
meta: schema.TransactionMeta.check(z.describe('The original JSON-RPC payload plus any resources requested with `include`.')),
|
|
267
|
+
nonce: z
|
|
268
|
+
.number()
|
|
269
|
+
.check(z.int(), z.nonnegative(), z.describe('Nonce from the sender account that orders and de-duplicates transactions.'), z.meta({ examples: [0] })),
|
|
270
|
+
nonceKey: z
|
|
271
|
+
.optional(Schema.Hex)
|
|
272
|
+
.check(z.describe('Tempo two-dimensional nonce key used by Tempo transactions.'), z.meta({ examples: ['0x0'] })),
|
|
273
|
+
recipient: z
|
|
274
|
+
.nullable(Schema.Address)
|
|
275
|
+
.check(z.describe('The recipient address, or `null` when the transaction creates a contract.')),
|
|
276
|
+
sender: Schema.Address.check(z.describe('The address that submitted or authorized the transaction.')),
|
|
277
|
+
timestamp: z
|
|
278
|
+
.nullable(z.iso.datetime())
|
|
279
|
+
.check(z.describe('The block timestamp as ISO 8601, or `null` while the transaction is pending.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
280
|
+
transactionIndex: z
|
|
281
|
+
.nullable(z.number().check(z.int(), z.nonnegative()))
|
|
282
|
+
.check(z.describe('The transaction position within its block, or `null` while pending.'), z.meta({ examples: [0] })),
|
|
283
|
+
type: schema.TransactionType,
|
|
284
|
+
validAfter: z
|
|
285
|
+
.nullish(z.iso.datetime())
|
|
286
|
+
.check(z.describe('Earliest ISO 8601 time when this Tempo transaction may be included.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
287
|
+
validBefore: z
|
|
288
|
+
.nullish(z.iso.datetime())
|
|
289
|
+
.check(z.describe('Latest ISO 8601 time when this Tempo transaction may be included.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
290
|
+
value: Schema.DecimalString.check(z.describe('Native value transferred by this transaction, as a decimal string.')),
|
|
291
|
+
})
|
|
292
|
+
.check(z.describe('A transaction formatted for API clients, with decoded fields and the original JSON-RPC payload under `meta.rpc`.'));
|
|
293
|
+
/** Schemas for the getTransaction operation. */
|
|
294
|
+
let getTransaction;
|
|
295
|
+
(function (getTransaction) {
|
|
296
|
+
/** Path parameters for transaction requests. */
|
|
297
|
+
getTransaction.Params = z
|
|
298
|
+
.object({
|
|
299
|
+
transactionHash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies this transaction.'), z.meta({ examples: [exampleHash] })),
|
|
300
|
+
})
|
|
301
|
+
.check(z.describe('Path parameters used to look up a transaction.'));
|
|
302
|
+
/** Optional related resource that callers opt into via `include`. */
|
|
303
|
+
getTransaction.Include = z
|
|
304
|
+
.enum(['feeToken', 'receipt'])
|
|
305
|
+
.check(z.describe('Related resource you can opt into with the `include` query parameter.'));
|
|
306
|
+
/**
|
|
307
|
+
* Parses a comma-separated `include` query value into a list of optional
|
|
308
|
+
* resources to embed. Extra lookups (e.g. fee-token metadata, the receipt)
|
|
309
|
+
* only run when explicitly requested, keeping the base transaction response
|
|
310
|
+
* fast.
|
|
311
|
+
*/
|
|
312
|
+
getTransaction.includeQuery = Schema.includeQuery(getTransaction.Include, 'Comma-separated related resources to include, such as `feeToken,receipt`.');
|
|
313
|
+
/** Query parameters for transaction requests. */
|
|
314
|
+
getTransaction.Query = z
|
|
315
|
+
.strictObject({
|
|
316
|
+
chainId: Schema.ChainIdQuery,
|
|
317
|
+
include: getTransaction.includeQuery,
|
|
318
|
+
})
|
|
319
|
+
.check(z.describe('Query parameters for looking up a transaction.'));
|
|
320
|
+
/** A humanized transaction, with the verbatim JSON-RPC payload under `rpc`. */
|
|
321
|
+
getTransaction.Response = schema.Transaction;
|
|
322
|
+
})(getTransaction = schema.getTransaction || (schema.getTransaction = {}));
|
|
323
|
+
/** Schemas for the getTransactions (list) operation. */
|
|
324
|
+
let getTransactions;
|
|
325
|
+
(function (getTransactions) {
|
|
326
|
+
/**
|
|
327
|
+
* Optional resources for the transaction list: the per-row `feeToken` /
|
|
328
|
+
* `receipt` embeds plus the response-wide capped `totalCount`. The detail
|
|
329
|
+
* route keeps its own {@link getTransaction.Include}.
|
|
330
|
+
*/
|
|
331
|
+
getTransactions.Include = z
|
|
332
|
+
.enum(['feeToken', 'receipt', 'totalCount'])
|
|
333
|
+
.check(z.describe('Related resources to include in each transaction row or in response metadata.'));
|
|
334
|
+
/** Parses the comma-separated `include` value for the transaction list. */
|
|
335
|
+
getTransactions.includeQuery = Schema.includeQuery(getTransactions.Include, 'Comma-separated related resources to include, such as `feeToken,receipt,totalCount`.');
|
|
336
|
+
/** Query parameters for transaction list requests. */
|
|
337
|
+
getTransactions.Query = z
|
|
338
|
+
.strictObject({
|
|
339
|
+
address: z
|
|
340
|
+
.optional(Schema.Address)
|
|
341
|
+
.check(z.describe('Filter to transactions where this address is either the sender or recipient.')),
|
|
342
|
+
'blockNumber.from': Schema.blockNumberBound('transactions', 'from'),
|
|
343
|
+
'blockNumber.to': Schema.blockNumberBound('transactions', 'to'),
|
|
344
|
+
chainId: Schema.ChainIdQuery,
|
|
345
|
+
cursor: Schema.Cursor,
|
|
346
|
+
feePayer: z
|
|
347
|
+
.optional(Schema.Address)
|
|
348
|
+
.check(z.describe('Filter to transactions whose fee payer is this address.')),
|
|
349
|
+
feeToken: z
|
|
350
|
+
.optional(Schema.TokenAddress)
|
|
351
|
+
.check(z.describe('Filter to transactions whose fee was paid in this token address.')),
|
|
352
|
+
include: getTransactions.includeQuery,
|
|
353
|
+
limit: Schema.Limit,
|
|
354
|
+
order: Schema.Order,
|
|
355
|
+
page: Schema.Page,
|
|
356
|
+
recipient: z
|
|
357
|
+
.optional(Schema.Address)
|
|
358
|
+
.check(z.describe('Filter to transactions sent to this recipient address.')),
|
|
359
|
+
sender: z
|
|
360
|
+
.optional(Schema.Address)
|
|
361
|
+
.check(z.describe('Filter to transactions sent by this sender address.')),
|
|
362
|
+
status: z
|
|
363
|
+
.optional(z.enum(['success', 'reverted']))
|
|
364
|
+
.check(z.describe('Filter to transactions whose receipt shows this execution status.'), z.meta({ examples: ['success'] })),
|
|
365
|
+
'timestamp.from': Schema.timestampBound('transactions', 'from'),
|
|
366
|
+
'timestamp.to': Schema.timestampBound('transactions', 'to'),
|
|
367
|
+
})
|
|
368
|
+
.check(...Schema.pageChecks(), z.describe('Query parameters for listing transactions.'));
|
|
369
|
+
/**
|
|
370
|
+
* Page of transactions. Each item reuses the humanized transaction returned
|
|
371
|
+
* by `GET /transactions/:transactionHash`, so the list and detail endpoints
|
|
372
|
+
* expose one identical shape. Indexed list rows omit fields the indexer does
|
|
373
|
+
* not store (signatures, access list), reflected in their `rpc` payload.
|
|
374
|
+
*/
|
|
375
|
+
getTransactions.Response = z
|
|
376
|
+
.object({
|
|
377
|
+
data: z.array(getTransaction.Response).check(z.describe('Transactions on this page.')),
|
|
378
|
+
meta: z
|
|
379
|
+
.optional(Schema.CountMeta)
|
|
380
|
+
.check(z.describe('Response-level metadata requested with `include`, such as `totalCount`.')),
|
|
381
|
+
nextCursor: Schema.NextCursor,
|
|
382
|
+
})
|
|
383
|
+
.check(z.describe('A page of transactions ordered by block number and transaction position.'));
|
|
384
|
+
})(getTransactions = schema.getTransactions || (schema.getTransactions = {}));
|
|
385
|
+
})(schema || (schema = {}));
|
|
386
|
+
/** Creates transaction handlers. */
|
|
387
|
+
export function transactions() {
|
|
388
|
+
return new Hono()
|
|
389
|
+
.get('/', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getTransactions.Query, {
|
|
390
|
+
code: 'query_invalid',
|
|
391
|
+
message: 'Invalid query parameters',
|
|
392
|
+
}), OpenApi.describeRoute({
|
|
393
|
+
description: 'List transactions across Tempo, with filters for addresses, blocks, timestamps, fees, and status.',
|
|
394
|
+
operationId: 'getTransactions',
|
|
395
|
+
responses: OpenApi.responses({
|
|
396
|
+
errors: { 502: 'Could not read indexed transaction data from TIDX.' },
|
|
397
|
+
success: {
|
|
398
|
+
description: 'A page of transactions.',
|
|
399
|
+
schema: schema.getTransactions.Response,
|
|
400
|
+
},
|
|
401
|
+
}),
|
|
402
|
+
summary: 'List transactions',
|
|
403
|
+
tags: ['Transactions'],
|
|
404
|
+
}), Cache.response({
|
|
405
|
+
cacheControl: Cache.policies.feed,
|
|
406
|
+
name: 'tempo-api:transactions:v1',
|
|
407
|
+
key: (c) => Cache.urlKey(c, schema.getTransactions.Query),
|
|
408
|
+
}), async (c) => {
|
|
409
|
+
if (Auth.narrowAccess)
|
|
410
|
+
return Auth.paidAccessError(c);
|
|
411
|
+
if (OpenApi.narrowValidation)
|
|
412
|
+
return OpenApi.validationError(c, {
|
|
413
|
+
code: 'query_invalid',
|
|
414
|
+
message: 'Invalid query parameters',
|
|
415
|
+
});
|
|
416
|
+
const query = c.req.valid('query');
|
|
417
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
418
|
+
// A cursor page is anchored below the head, so its contents are
|
|
419
|
+
// append-only and effectively immutable; cache it aggressively. The head
|
|
420
|
+
// page (no cursor) keeps the route's short `feed` default.
|
|
421
|
+
if (query.cursor !== undefined)
|
|
422
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
423
|
+
try {
|
|
424
|
+
// `totalCount` is opt-in and shares the page's filters; run it
|
|
425
|
+
// concurrently with the page. Best-effort: a count failure omits
|
|
426
|
+
// `meta` rather than failing the page.
|
|
427
|
+
const countPromise = query.include.includes('totalCount')
|
|
428
|
+
? countTransactions(c, {
|
|
429
|
+
address: query.address,
|
|
430
|
+
chainId,
|
|
431
|
+
feePayer: query.feePayer,
|
|
432
|
+
feeToken: query.feeToken,
|
|
433
|
+
fromBlock: query['blockNumber.from'],
|
|
434
|
+
fromTimestamp: query['timestamp.from'],
|
|
435
|
+
recipient: query.recipient,
|
|
436
|
+
sender: query.sender,
|
|
437
|
+
status: query.status,
|
|
438
|
+
toBlock: query['blockNumber.to'],
|
|
439
|
+
toTimestamp: query['timestamp.to'],
|
|
440
|
+
}).catch(() => undefined)
|
|
441
|
+
: undefined;
|
|
442
|
+
const transactions = await listTransactions(c, {
|
|
443
|
+
address: query.address,
|
|
444
|
+
chainId,
|
|
445
|
+
cursor: query.cursor,
|
|
446
|
+
feePayer: query.feePayer,
|
|
447
|
+
feeToken: query.feeToken,
|
|
448
|
+
fromBlock: query['blockNumber.from'],
|
|
449
|
+
fromTimestamp: query['timestamp.from'],
|
|
450
|
+
include: query.include.filter((resource) => resource !== 'totalCount'),
|
|
451
|
+
limit: query.limit,
|
|
452
|
+
order: query.order,
|
|
453
|
+
page: query.page,
|
|
454
|
+
recipient: query.recipient,
|
|
455
|
+
sender: query.sender,
|
|
456
|
+
status: query.status,
|
|
457
|
+
toBlock: query['blockNumber.to'],
|
|
458
|
+
toTimestamp: query['timestamp.to'],
|
|
459
|
+
});
|
|
460
|
+
const meta = countPromise ? await countPromise : undefined;
|
|
461
|
+
return c.json(Response.validated(schema.getTransactions.Response, {
|
|
462
|
+
...transactions,
|
|
463
|
+
...(meta ? { meta } : {}),
|
|
464
|
+
}), 200);
|
|
465
|
+
}
|
|
466
|
+
catch (cause) {
|
|
467
|
+
return Response.upstream(c, cause);
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
.get('/:transactionHash', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getTransaction.Params, {
|
|
471
|
+
code: 'transaction_invalid',
|
|
472
|
+
message: 'Invalid transaction hash',
|
|
473
|
+
}), OpenApi.validate('query', schema.getTransaction.Query, {
|
|
474
|
+
code: 'query_invalid',
|
|
475
|
+
message: 'Invalid query parameters',
|
|
476
|
+
}), OpenApi.describeRoute({
|
|
477
|
+
description: 'Get one transaction by its 32-byte transaction hash.',
|
|
478
|
+
operationId: 'getTransaction',
|
|
479
|
+
responses: OpenApi.responses({
|
|
480
|
+
errors: {
|
|
481
|
+
404: {
|
|
482
|
+
description: 'No transaction was found for that hash.',
|
|
483
|
+
codes: ['transaction_not_found'],
|
|
484
|
+
},
|
|
485
|
+
502: 'Could not read transaction data from the upstream JSON-RPC node.',
|
|
486
|
+
},
|
|
487
|
+
success: {
|
|
488
|
+
description: 'A single transaction with decoded fields and original JSON-RPC data.',
|
|
489
|
+
schema: schema.getTransaction.Response,
|
|
490
|
+
},
|
|
491
|
+
}),
|
|
492
|
+
summary: 'Get a transaction by hash',
|
|
493
|
+
tags: ['Transactions'],
|
|
494
|
+
}), Cache.response({
|
|
495
|
+
// Default to no-store: a pending (or not-found) transaction must not be
|
|
496
|
+
// cached, or it would mask the mined result. The handler upgrades a mined
|
|
497
|
+
// transaction to `immutable` below.
|
|
498
|
+
cacheControl: Cache.policies.noStore,
|
|
499
|
+
name: 'tempo-api:transactions:v1',
|
|
500
|
+
key: (c) => Cache.urlKey(c, schema.getTransaction.Query),
|
|
501
|
+
}), async (c) => {
|
|
502
|
+
if (Auth.narrowAccess)
|
|
503
|
+
return Auth.paidAccessError(c);
|
|
504
|
+
if (OpenApi.narrowValidation)
|
|
505
|
+
return OpenApi.validationError(c, {
|
|
506
|
+
code: 'transaction_invalid',
|
|
507
|
+
message: 'Invalid request parameters',
|
|
508
|
+
});
|
|
509
|
+
if (OpenApi.narrowValidation)
|
|
510
|
+
return OpenApi.validationError(c, {
|
|
511
|
+
code: 'query_invalid',
|
|
512
|
+
message: 'Invalid request parameters',
|
|
513
|
+
});
|
|
514
|
+
const { transactionHash } = c.req.valid('param');
|
|
515
|
+
const query = c.req.valid('query');
|
|
516
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
517
|
+
const client = c.get('getClient')(chainId);
|
|
518
|
+
// `Schema.Hash` lowercases to a plain string; re-narrow to the hex
|
|
519
|
+
// template literal viem requires at the RPC boundary.
|
|
520
|
+
const hash = transactionHash;
|
|
521
|
+
try {
|
|
522
|
+
// Fetch the JSON-RPC transaction; a `null` result means the
|
|
523
|
+
// transaction is unknown rather than an error. The verbatim payload is
|
|
524
|
+
// surfaced under `rpc`, with humanized fields layered on top. Unmined
|
|
525
|
+
// lookups (pending or unknown hash) are deliberately never cached —
|
|
526
|
+
// confirmation polling must observe the mined result the moment the
|
|
527
|
+
// node does.
|
|
528
|
+
const rpc = await Timing.time(c, 'transaction', () => client.request({ method: 'eth_getTransactionByHash', params: [hash] }));
|
|
529
|
+
if (!rpc)
|
|
530
|
+
return Response.error(c, {
|
|
531
|
+
code: 'transaction_not_found',
|
|
532
|
+
message: 'Transaction not found',
|
|
533
|
+
status: 404,
|
|
534
|
+
});
|
|
535
|
+
let data = humanizeTransaction(Response.validated(schema.Rpc.Transaction, rpc));
|
|
536
|
+
// A mined transaction is immutable, so it can be cached aggressively; a
|
|
537
|
+
// pending one (no block yet) keeps the route's default no-store policy.
|
|
538
|
+
if (data.blockNumber !== null)
|
|
539
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
540
|
+
// The receipt is opt-in: fetching it is an extra RPC, so it only runs
|
|
541
|
+
// when requested via `include=receipt` and the transaction is mined.
|
|
542
|
+
// A null receipt (still pending) is simply omitted.
|
|
543
|
+
if (query.include.includes('receipt') && data.blockNumber !== null) {
|
|
544
|
+
const receipt = await Timing.time(c, 'receipt', () => client.request({ method: 'eth_getTransactionReceipt', params: [hash] }));
|
|
545
|
+
if (receipt) {
|
|
546
|
+
const humanized = Receipts.humanizeReceipt(Response.validated(Receipts.schema.Rpc.Receipt, receipt));
|
|
547
|
+
data = { ...data, meta: { ...data.meta, receipt: humanized } };
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Fee-token metadata is opt-in: resolving it is an extra RPC, so it
|
|
551
|
+
// only runs when requested via `include=feeToken` and the transaction
|
|
552
|
+
// declares a fee token (Tempo AA transactions).
|
|
553
|
+
const feeToken = data.feeToken;
|
|
554
|
+
if (query.include.includes('feeToken') && feeToken !== undefined) {
|
|
555
|
+
const token = await Timing.time(c, 'fee_token', () => Tokens.resolveToken(c, { address: feeToken, chainId }).catch(() => undefined));
|
|
556
|
+
if (token)
|
|
557
|
+
data = { ...data, meta: { ...data.meta, feeToken: token } };
|
|
558
|
+
}
|
|
559
|
+
return c.json(Response.validated(schema.getTransaction.Response, data), 200);
|
|
560
|
+
}
|
|
561
|
+
catch (cause) {
|
|
562
|
+
return Response.upstream(c, cause);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Lists humanized transactions and embeds opt-in `include` resources
|
|
568
|
+
* (`feeToken`, `receipt`). A `status` filter routes the page through the
|
|
569
|
+
* receipts list instead of the `txs` scan, since execution status lives on
|
|
570
|
+
* receipts.
|
|
571
|
+
*/
|
|
572
|
+
export async function listTransactions(c, options) {
|
|
573
|
+
const { include, ...rest } = options;
|
|
574
|
+
// `status` filters on the receipt's execution status, which `txs` does not
|
|
575
|
+
// store: page the receipts list (which already supports `status`) and hydrate
|
|
576
|
+
// each receipt's underlying transaction. The receipts `(block_num, tx_idx)`
|
|
577
|
+
// keyset cursor spans the same domain as the transactions `(block_num, idx)`
|
|
578
|
+
// cursor, so cursors stay interchangeable between the two paths.
|
|
579
|
+
const receiptPage = rest.status === undefined
|
|
580
|
+
? undefined
|
|
581
|
+
: await Receipts.getReceipts(c, {
|
|
582
|
+
address: rest.address,
|
|
583
|
+
chainId: rest.chainId,
|
|
584
|
+
cursor: rest.cursor,
|
|
585
|
+
feePayer: rest.feePayer,
|
|
586
|
+
feeToken: rest.feeToken,
|
|
587
|
+
fromBlock: rest.fromBlock,
|
|
588
|
+
fromTimestamp: rest.fromTimestamp,
|
|
589
|
+
limit: rest.limit,
|
|
590
|
+
order: rest.order,
|
|
591
|
+
page: rest.page,
|
|
592
|
+
recipient: rest.recipient,
|
|
593
|
+
sender: rest.sender,
|
|
594
|
+
status: rest.status,
|
|
595
|
+
toBlock: rest.toBlock,
|
|
596
|
+
toTimestamp: rest.toTimestamp,
|
|
597
|
+
});
|
|
598
|
+
const page = receiptPage
|
|
599
|
+
? {
|
|
600
|
+
data: await transactionsForReceipts(c, {
|
|
601
|
+
chainId: rest.chainId,
|
|
602
|
+
receipts: receiptPage.data,
|
|
603
|
+
}),
|
|
604
|
+
nextCursor: receiptPage.nextCursor,
|
|
605
|
+
}
|
|
606
|
+
: await getTransactions(c, rest);
|
|
607
|
+
let data = page.data;
|
|
608
|
+
if (include.includes('receipt') && data.length > 0) {
|
|
609
|
+
const receipts = await Receipts.embedReceipts(c, {
|
|
610
|
+
chainId: rest.chainId,
|
|
611
|
+
receipts: receiptPage?.data,
|
|
612
|
+
rows: data,
|
|
613
|
+
});
|
|
614
|
+
data = data.map((row) => {
|
|
615
|
+
const receipt = receipts.get(row.hash.toLowerCase());
|
|
616
|
+
return receipt ? { ...row, meta: { ...row.meta, receipt } } : row;
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
if (include.includes('feeToken'))
|
|
620
|
+
data = await embedFeeTokens(c, data, rest.chainId);
|
|
621
|
+
return { ...page, data };
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Fetches the underlying transactions for a page of receipts in one indexed
|
|
625
|
+
* `txs` query and returns them humanized, ordered to match the receipt page.
|
|
626
|
+
* Receipts whose transaction row is missing (not yet indexed) are skipped.
|
|
627
|
+
* Backs the `status`-filtered transactions list, which pages receipts first.
|
|
628
|
+
*/
|
|
629
|
+
async function transactionsForReceipts(c, options) {
|
|
630
|
+
const { chainId, receipts } = options;
|
|
631
|
+
if (receipts.length === 0)
|
|
632
|
+
return [];
|
|
633
|
+
const tidx = c.get('getTidx')(chainId);
|
|
634
|
+
const blocks = [...new Set(receipts.map((receipt) => receipt.blockNumber))];
|
|
635
|
+
const hashes = [...new Set(receipts.map((receipt) => receipt.transactionHash))];
|
|
636
|
+
// `txs` is sorted by `(block_num, idx)` with no hash index, so the hash list
|
|
637
|
+
// must be paired with the page's block numbers — a bare `hash IN` full-scans
|
|
638
|
+
// the table. Raw-table query, so the inline SQL is cast to `string`.
|
|
639
|
+
const result = await Timing.time(c, 'transactions', () => tidx.fetch({
|
|
640
|
+
chainId,
|
|
641
|
+
query: `SELECT ${transactionColumns} FROM txs WHERE block_num IN (${blocks.join(', ')}) AND hash IN (${hashes.map((hash) => `'${hash}'`).join(', ')})`,
|
|
642
|
+
}));
|
|
643
|
+
const byHash = new Map();
|
|
644
|
+
for (const row of result.rows) {
|
|
645
|
+
const rpc = schema.Rpc.Transaction.safeParse(toRpcTransaction(row));
|
|
646
|
+
if (rpc.success)
|
|
647
|
+
byHash.set(rpc.data.hash.toLowerCase(), humanizeTransaction(rpc.data));
|
|
648
|
+
}
|
|
649
|
+
return receipts
|
|
650
|
+
.map((receipt) => byHash.get(receipt.transactionHash.toLowerCase()))
|
|
651
|
+
.filter((transaction) => transaction !== undefined);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Resolves fee-token metadata for each row and embeds it under `meta.feeToken`,
|
|
655
|
+
* sharing a single verified-token fetch. Best-effort: a row whose fee token
|
|
656
|
+
* can't be resolved (or that has none) is returned unchanged. Shared by the
|
|
657
|
+
* transaction and receipt list endpoints (and their address-scoped variants),
|
|
658
|
+
* whose rows all carry an optional `feeToken` address and a `meta` object.
|
|
659
|
+
*/
|
|
660
|
+
export async function embedFeeTokens(c, rows, chainId) {
|
|
661
|
+
if (rows.length === 0)
|
|
662
|
+
return rows;
|
|
663
|
+
const uniqueTokens = Array.from(new Set(rows.map((row) => row.feeToken).filter((address) => address != null)));
|
|
664
|
+
if (uniqueTokens.length === 0)
|
|
665
|
+
return rows;
|
|
666
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId);
|
|
667
|
+
const resolved = await Timing.time(c, 'tokens', () => Promise.all(uniqueTokens.map((token) => Tokens.resolveToken(c, {
|
|
668
|
+
address: token,
|
|
669
|
+
chainId,
|
|
670
|
+
snapshot,
|
|
671
|
+
})
|
|
672
|
+
.then((metadata) => [token, metadata])
|
|
673
|
+
.catch(() => [token, undefined]))));
|
|
674
|
+
const tokensByAddress = new Map(resolved);
|
|
675
|
+
return rows.map((row) => {
|
|
676
|
+
const token = row.feeToken == null ? undefined : tokensByAddress.get(row.feeToken);
|
|
677
|
+
// The runtime shape stays `row`; the generic object spread just can't prove
|
|
678
|
+
// the override keeps it assignable to `row`.
|
|
679
|
+
return (token ? { ...row, meta: { ...row.meta, feeToken: token } } : row);
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
export function getTransactions(c, options) {
|
|
683
|
+
const { chainId } = options;
|
|
684
|
+
const store = c.get('store');
|
|
685
|
+
const tidx = c.get('getTidx')(chainId);
|
|
686
|
+
const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
|
|
687
|
+
return Timing.time(c, 'transactions', () => Store.memoize(async () => {
|
|
688
|
+
const page = await scanTransactions({ tidx }, options);
|
|
689
|
+
return {
|
|
690
|
+
data: page.items.map((item) => item.data),
|
|
691
|
+
nextCursor: page.nextCursor,
|
|
692
|
+
};
|
|
693
|
+
}, {
|
|
694
|
+
key: `transactions:v1:${chainId}:${options.order}:${options.address ?? ''}:${options.sender ?? ''}:${options.recipient ?? ''}:${options.feePayer ?? ''}:${options.feeToken ?? ''}:${options.fromBlock ?? ''}:${options.toBlock ?? ''}:${options.fromTimestamp ?? ''}:${options.toTimestamp ?? ''}:${cursor ? `cursor:${cursor[0]}:${cursor[1]}` : options.page !== undefined && options.page > 1 ? `page:${options.page}` : 'head'}:${options.limit}`,
|
|
695
|
+
store,
|
|
696
|
+
ttl: Ttl.seconds(15),
|
|
697
|
+
}));
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Context-free core of {@link getTransactions}: builds the `txs` query (with the
|
|
701
|
+
* address-scoped `UNION` planner workaround), applies keyset pagination on
|
|
702
|
+
* `(block_num, idx)`, and reconstructs each row through the same
|
|
703
|
+
* `toRpcTransaction` → `humanizeTransaction` path the detail endpoint uses. Used
|
|
704
|
+
* by the Hono routes (via {@link getTransactions}, which adds Server-Timing +
|
|
705
|
+
* memoization) and by the webhook poller (uncached, ascending, per-row cursors).
|
|
706
|
+
*/
|
|
707
|
+
export async function scanTransactions(deps, options) {
|
|
708
|
+
const { address, chainId, feePayer, feeToken, fromBlock, fromTimestamp, includeCallRecipients, limit, order, recipient, sender, toBlock, toTimestamp, } = options;
|
|
709
|
+
const { tidx } = deps;
|
|
710
|
+
const sortDirection = order === 'asc' ? 'ASC' : 'DESC';
|
|
711
|
+
// Keyset pagination: anchor the page below the previous row's `(block, idx)`
|
|
712
|
+
// position instead of a numeric offset, so rows arriving at the head can't
|
|
713
|
+
// shift items across pages. A malformed cursor decodes to `undefined` and
|
|
714
|
+
// falls back to the head page.
|
|
715
|
+
const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
|
|
716
|
+
// Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
|
|
717
|
+
// the head page, so it shares the head's SQL and cache entries; deeper pages
|
|
718
|
+
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
719
|
+
const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
|
|
720
|
+
// Raw transactions from the `txs` table (Postgres engine). `txs` is a
|
|
721
|
+
// standard table, not a signature-decoded CTE, so the inline query is
|
|
722
|
+
// cast to `string` and needs no `signatures`. Order by block then
|
|
723
|
+
// in-block index so transactions stay stable across pages, and fetch one
|
|
724
|
+
// extra row to detect `hasMore` without a separate count query.
|
|
725
|
+
//
|
|
726
|
+
// Build the shared filters once (everything except the address-scope
|
|
727
|
+
// predicate); the `address` filter is special-cased below because the
|
|
728
|
+
// TIDX engine errors out on `(from = X OR to = X) ORDER BY ...` for
|
|
729
|
+
// contracts with non-trivial traffic and an index-friendly `UNION`
|
|
730
|
+
// over the two sides plans cleanly.
|
|
731
|
+
const filters = [];
|
|
732
|
+
if (sender !== undefined)
|
|
733
|
+
filters.push(`"from" = '${sender}'`);
|
|
734
|
+
if (recipient !== undefined)
|
|
735
|
+
// For Tempo AA transactions the meaningful recipient is often an inner call
|
|
736
|
+
// target, so `includeCallRecipients` also matches `calls[].to` (a JSONB array
|
|
737
|
+
// of `{ to, value, input }`). The `@>` containment hits the partial GIN index
|
|
738
|
+
// on `calls`; the root `"to"` branch covers non-AA and single-call AA txs
|
|
739
|
+
// (whose `"to"` mirrors `calls[0].to` and which the partial index omits).
|
|
740
|
+
filters.push(includeCallRecipients
|
|
741
|
+
? `("to" = '${recipient}' OR calls @> '[{"to": "${recipient}"}]')`
|
|
742
|
+
: `"to" = '${recipient}'`);
|
|
743
|
+
if (feePayer !== undefined)
|
|
744
|
+
filters.push(`fee_payer = '${feePayer}'`);
|
|
745
|
+
if (feeToken !== undefined)
|
|
746
|
+
filters.push(`fee_token = '${feeToken}'`);
|
|
747
|
+
if (fromBlock !== undefined)
|
|
748
|
+
filters.push(`block_num >= ${fromBlock}`);
|
|
749
|
+
if (toBlock !== undefined)
|
|
750
|
+
filters.push(`block_num <= ${toBlock}`);
|
|
751
|
+
if (fromTimestamp !== undefined)
|
|
752
|
+
filters.push(`block_timestamp >= '${fromTimestamp}'`);
|
|
753
|
+
if (toTimestamp !== undefined)
|
|
754
|
+
filters.push(`block_timestamp <= '${toTimestamp}'`);
|
|
755
|
+
if (cursor !== undefined)
|
|
756
|
+
filters.push(Cursor.keyset([
|
|
757
|
+
{ literal: Cursor.literal(cursor[0], 'int'), name: 'block_num', order },
|
|
758
|
+
{ literal: Cursor.literal(cursor[1], 'int'), name: 'idx', order },
|
|
759
|
+
]));
|
|
760
|
+
// For address-scoped queries, run one branch per side and `UNION` so
|
|
761
|
+
// each branch can use its own (`from`/`to`) index. The `ORDER BY` and
|
|
762
|
+
// `LIMIT` must live *inside* each branch — TIDX's planner crashes on
|
|
763
|
+
// a UNION whose outer query orders by `block_num, idx` for
|
|
764
|
+
// high-traffic contracts (and on the `(from = X OR to = X) ORDER BY`
|
|
765
|
+
// form). Pushing the sort+limit down means each branch returns at
|
|
766
|
+
// most `limit + 1` index-ordered rows, the outer query re-merges,
|
|
767
|
+
// and plain `UNION` dedupes the self-transfer case where
|
|
768
|
+
// `from = to = address`.
|
|
769
|
+
// In the offset lane each branch must over-fetch the full window
|
|
770
|
+
// (`offset + limit + 1` rows) because the skip applies to the merged set:
|
|
771
|
+
// the outer query re-sorts the union and discards the first `offset` rows.
|
|
772
|
+
const sideQuery = (side, addr) => {
|
|
773
|
+
const where = [`"${side}" = '${addr}'`, ...filters].join(' AND ');
|
|
774
|
+
return `(SELECT ${transactionColumns} FROM txs WHERE ${where} ORDER BY block_num ${sortDirection}, idx ${sortDirection} LIMIT ${(offset ?? 0) + limit + 1})`;
|
|
775
|
+
};
|
|
776
|
+
const skip = offset !== undefined ? ` OFFSET ${offset}` : '';
|
|
777
|
+
const query = address !== undefined
|
|
778
|
+
? `
|
|
779
|
+
${sideQuery('from', address)}
|
|
780
|
+
UNION
|
|
781
|
+
${sideQuery('to', address)}
|
|
782
|
+
ORDER BY block_num ${sortDirection}, idx ${sortDirection}
|
|
783
|
+
LIMIT ${limit + 1}${skip}
|
|
784
|
+
`
|
|
785
|
+
: `
|
|
786
|
+
SELECT ${transactionColumns}
|
|
787
|
+
FROM txs
|
|
788
|
+
${filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''}
|
|
789
|
+
ORDER BY block_num ${sortDirection}, idx ${sortDirection}
|
|
790
|
+
LIMIT ${limit + 1}${skip}
|
|
791
|
+
`;
|
|
792
|
+
const result = await tidx.fetch({ chainId, query: query });
|
|
793
|
+
// The next page anchors below the last fetched row's `(block, idx)`.
|
|
794
|
+
const page = Cursor.paginate({
|
|
795
|
+
rows: result.rows,
|
|
796
|
+
limit,
|
|
797
|
+
key: (row) => {
|
|
798
|
+
const block = Value.toNumber(row['block_num']);
|
|
799
|
+
const index = Value.toNumber(row['idx']);
|
|
800
|
+
return block !== undefined && index !== undefined ? [block, index] : undefined;
|
|
801
|
+
},
|
|
802
|
+
});
|
|
803
|
+
// Reconstruct the RPC shape from the indexed columns, then humanize it
|
|
804
|
+
// through the same path as the detail endpoint so the list and detail
|
|
805
|
+
// responses are identical. Rows that fail to reconstruct are skipped.
|
|
806
|
+
const items = [];
|
|
807
|
+
for (const row of page.rows) {
|
|
808
|
+
const rpc = schema.Rpc.Transaction.safeParse(toRpcTransaction(row));
|
|
809
|
+
const block = Value.toNumber(row['block_num']);
|
|
810
|
+
const index = Value.toNumber(row['idx']);
|
|
811
|
+
if (rpc.success && block !== undefined && index !== undefined)
|
|
812
|
+
items.push({ cursor: [block, index], data: humanizeTransaction(rpc.data) });
|
|
813
|
+
}
|
|
814
|
+
return { hasMore: page.hasMore, items, limit, nextCursor: page.nextCursor };
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Builds the non-cursor, non-address-scope `txs` filters shared by the page
|
|
818
|
+
* ({@link scanTransactions}) and the capped count ({@link countTransactions}),
|
|
819
|
+
* so the count always matches the page it annotates. The address scope is
|
|
820
|
+
* handled separately (it `UNION`s the two sides, as the page does).
|
|
821
|
+
*/
|
|
822
|
+
function countFilters(options) {
|
|
823
|
+
const filters = [];
|
|
824
|
+
if (options.sender !== undefined)
|
|
825
|
+
filters.push(`"from" = '${options.sender}'`);
|
|
826
|
+
if (options.recipient !== undefined)
|
|
827
|
+
filters.push(`"to" = '${options.recipient}'`);
|
|
828
|
+
if (options.feePayer !== undefined)
|
|
829
|
+
filters.push(`fee_payer = '${options.feePayer}'`);
|
|
830
|
+
if (options.feeToken !== undefined)
|
|
831
|
+
filters.push(`fee_token = '${options.feeToken}'`);
|
|
832
|
+
if (options.fromBlock !== undefined)
|
|
833
|
+
filters.push(`block_num >= ${options.fromBlock}`);
|
|
834
|
+
if (options.toBlock !== undefined)
|
|
835
|
+
filters.push(`block_num <= ${options.toBlock}`);
|
|
836
|
+
if (options.fromTimestamp !== undefined)
|
|
837
|
+
filters.push(`block_timestamp >= '${options.fromTimestamp}'`);
|
|
838
|
+
if (options.toTimestamp !== undefined)
|
|
839
|
+
filters.push(`block_timestamp <= '${options.toTimestamp}'`);
|
|
840
|
+
return filters;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Capped total count of transactions matching the same filters as the page.
|
|
844
|
+
*
|
|
845
|
+
* A `status` filter is delegated to {@link Receipts.countReceipts}, mirroring
|
|
846
|
+
* the page's receipts-driven `status` path (execution status lives on receipts,
|
|
847
|
+
* not `txs`). The address scope reuses the page's `UNION` of the two sides —
|
|
848
|
+
* each side hits its own (`from`/`to`) index, and the deduped keyset count
|
|
849
|
+
* avoids double-counting self-transactions (`from = to`). `totalCountCapped: true` is a
|
|
850
|
+
* lower bound — at least {@link Schema.countCap} matches.
|
|
851
|
+
*/
|
|
852
|
+
export function countTransactions(c, options) {
|
|
853
|
+
// Status lives on receipts, so route the count through the same path the page
|
|
854
|
+
// uses: count receipts under the identical filters.
|
|
855
|
+
if (options.status !== undefined)
|
|
856
|
+
return Receipts.countReceipts(c, options);
|
|
857
|
+
const { address, chainId } = options;
|
|
858
|
+
const store = c.get('store');
|
|
859
|
+
const tidx = c.get('getTidx')(chainId);
|
|
860
|
+
return Timing.time(c, 'transactions_count', () => Store.memoize(async () => {
|
|
861
|
+
const filters = countFilters(options);
|
|
862
|
+
let query;
|
|
863
|
+
if (address !== undefined) {
|
|
864
|
+
const fromWhere = [`"from" = '${address}'`, ...filters].join(' AND ');
|
|
865
|
+
const toWhere = [`"to" = '${address}'`, ...filters].join(' AND ');
|
|
866
|
+
query = `
|
|
867
|
+
SELECT count(*) AS total
|
|
868
|
+
FROM (
|
|
869
|
+
(SELECT block_num, idx FROM txs WHERE ${fromWhere} LIMIT ${Schema.countCap})
|
|
870
|
+
UNION
|
|
871
|
+
(SELECT block_num, idx FROM txs WHERE ${toWhere} LIMIT ${Schema.countCap})
|
|
872
|
+
) AS capped
|
|
873
|
+
`;
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
877
|
+
query = `
|
|
878
|
+
SELECT count(*) AS total
|
|
879
|
+
FROM (
|
|
880
|
+
SELECT 1 FROM txs ${where} LIMIT ${Schema.countCap}
|
|
881
|
+
) AS capped
|
|
882
|
+
`;
|
|
883
|
+
}
|
|
884
|
+
const result = await tidx.fetch({ chainId, query: query });
|
|
885
|
+
const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0;
|
|
886
|
+
return { totalCountCapped: totalCount >= Schema.countCap, totalCount };
|
|
887
|
+
}, {
|
|
888
|
+
key: `transactions-count:v1:${chainId}:${address ?? ''}:${options.sender ?? ''}:${options.recipient ?? ''}:${options.feePayer ?? ''}:${options.feeToken ?? ''}:${options.fromBlock ?? ''}:${options.toBlock ?? ''}:${options.fromTimestamp ?? ''}:${options.toTimestamp ?? ''}`,
|
|
889
|
+
store,
|
|
890
|
+
ttl: Ttl.seconds(15),
|
|
891
|
+
}));
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* The `txs` columns {@link toRpcTransaction} reconstructs a transaction from.
|
|
895
|
+
* Shared by the list scan and the receipt-driven `status` path so the two
|
|
896
|
+
* queries cannot drift from the reconstruction.
|
|
897
|
+
*/
|
|
898
|
+
const transactionColumns = `block_num, block_timestamp, idx, hash, type, "from", "to", value, input,
|
|
899
|
+
gas_limit, max_fee_per_gas, max_priority_fee_per_gas,
|
|
900
|
+
nonce_key, nonce, fee_token, fee_payer, valid_before, valid_after`;
|
|
901
|
+
/**
|
|
902
|
+
* Re-encodes a TIDX `txs` row into the JSON-RPC transaction shape, which becomes
|
|
903
|
+
* the `rpc` payload that the list endpoint humanizes through the same path as
|
|
904
|
+
* the detail endpoint. Numeric columns become `0x` quantities; fields the
|
|
905
|
+
* indexer does not store (signatures, access list) are omitted, and `blockHash`
|
|
906
|
+
* is null since the schema requires it. The all-zero `nonce_key` that the
|
|
907
|
+
* indexer records for non-Tempo transactions is dropped to match the RPC
|
|
908
|
+
* response, which omits it.
|
|
909
|
+
*/
|
|
910
|
+
function toRpcTransaction(row) {
|
|
911
|
+
const timestamp = Value.toIsoDateTime(row['block_timestamp']);
|
|
912
|
+
const nonceKey = row['nonce_key'];
|
|
913
|
+
return {
|
|
914
|
+
blockHash: null,
|
|
915
|
+
blockNumber: Value.toHex(row['block_num']) ?? null,
|
|
916
|
+
blockTimestamp: timestamp ? Value.toHex(Math.floor(Date.parse(timestamp) / 1000)) : undefined,
|
|
917
|
+
feeToken: typeof row['fee_token'] === 'string' ? row['fee_token'] : undefined,
|
|
918
|
+
from: row['from'],
|
|
919
|
+
gas: Value.toHex(row['gas_limit']),
|
|
920
|
+
hash: row['hash'],
|
|
921
|
+
input: typeof row['input'] === 'string' ? row['input'] : undefined,
|
|
922
|
+
maxFeePerGas: Value.toHex(row['max_fee_per_gas']),
|
|
923
|
+
maxPriorityFeePerGas: Value.toHex(row['max_priority_fee_per_gas']),
|
|
924
|
+
nonce: Value.toHex(row['nonce']),
|
|
925
|
+
nonceKey: typeof nonceKey === 'string' && !/^0x0+$/.test(nonceKey) ? nonceKey : undefined,
|
|
926
|
+
to: typeof row['to'] === 'string' ? row['to'] : undefined,
|
|
927
|
+
transactionIndex: Value.toHex(row['idx']) ?? null,
|
|
928
|
+
type: Value.toHex(row['type']),
|
|
929
|
+
validAfter: Value.toHex(row['valid_after']),
|
|
930
|
+
validBefore: Value.toHex(row['valid_before']),
|
|
931
|
+
value: Value.toHex(row['value']),
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
/** Humanizes one call within a Tempo account-abstraction transaction. */
|
|
935
|
+
function humanizeCall(call) {
|
|
936
|
+
return {
|
|
937
|
+
data: call.input ?? call.data ?? undefined,
|
|
938
|
+
to: call.to ?? null,
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Humanizes a JSON-RPC transaction: decodes `type`, converts hex quantities to
|
|
943
|
+
* decimal strings, hex block fields to numbers, and timestamps to ISO 8601,
|
|
944
|
+
* while preserving the verbatim payload under `rpc`. The same function serves
|
|
945
|
+
* the detail endpoint (RPC source) and the list endpoint (reconstructed from the
|
|
946
|
+
* indexer), so both expose an identical shape.
|
|
947
|
+
*/
|
|
948
|
+
export function humanizeTransaction(rpc) {
|
|
949
|
+
return {
|
|
950
|
+
blockHash: rpc.blockHash,
|
|
951
|
+
blockNumber: Value.hexToNumber(rpc.blockNumber) ?? null,
|
|
952
|
+
calls: rpc.calls?.map(humanizeCall),
|
|
953
|
+
chainId: Value.hexToNumber(rpc.chainId),
|
|
954
|
+
feeToken: rpc.feeToken ?? undefined,
|
|
955
|
+
gas: Value.hexToNumber(rpc.gas) ?? 0,
|
|
956
|
+
gasPrice: Value.fromHex(rpc.gasPrice),
|
|
957
|
+
hash: rpc.hash,
|
|
958
|
+
id: rpc.hash,
|
|
959
|
+
input: rpc.input,
|
|
960
|
+
maxFeePerGas: Value.fromHex(rpc.maxFeePerGas),
|
|
961
|
+
maxPriorityFeePerGas: Value.fromHex(rpc.maxPriorityFeePerGas),
|
|
962
|
+
meta: { rpc },
|
|
963
|
+
nonce: Value.hexToNumber(rpc.nonce) ?? 0,
|
|
964
|
+
nonceKey: rpc.nonceKey,
|
|
965
|
+
recipient: rpc.to ?? null,
|
|
966
|
+
sender: rpc.from,
|
|
967
|
+
timestamp: Value.hexSecondsToIso(rpc.blockTimestamp) ?? null,
|
|
968
|
+
transactionIndex: Value.hexToNumber(rpc.transactionIndex) ?? null,
|
|
969
|
+
type: decodeTransactionType(rpc.type),
|
|
970
|
+
validAfter: rpc.validAfter == null ? undefined : Value.hexSecondsToIso(rpc.validAfter),
|
|
971
|
+
validBefore: rpc.validBefore == null ? undefined : Value.hexSecondsToIso(rpc.validBefore),
|
|
972
|
+
value: Value.fromHex(rpc.value) ?? '0',
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
//# sourceMappingURL=transactions.js.map
|