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,874 @@
|
|
|
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 Tokens from './tokens.js';
|
|
15
|
+
// Receipts and transactions deliberately form an import cycle (receipts reuses
|
|
16
|
+
// the transaction-type decoder and fee-token embedder; transactions embeds
|
|
17
|
+
// receipts). Neither module may touch the other's exports during module
|
|
18
|
+
// evaluation: schema references across the cycle must stay inside `z.lazy`, and
|
|
19
|
+
// function references are call-time only.
|
|
20
|
+
import { decodeTransactionType, embedFeeTokens, schema as txSchema } from './transactions.js';
|
|
21
|
+
/** Example transaction hash surfaced in OpenAPI docs. */
|
|
22
|
+
const exampleHash = '0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665';
|
|
23
|
+
// Tempo gas prices are denominated in attodollars (10^-18 USD) while fee-token
|
|
24
|
+
// balances use 6 decimals (microdollars, 10^-6 USD), so the fee charged to the
|
|
25
|
+
// fee payer — in the fee token's base units — is
|
|
26
|
+
// `ceil(gasUsed * effectiveGasPrice / 10^12)` (ceiling division). Mirrors
|
|
27
|
+
// `calc_gas_balance_spending` in tempoxyz/tempo.
|
|
28
|
+
const feeScalingFactor = 10n ** 12n;
|
|
29
|
+
/**
|
|
30
|
+
* Computes the fee charged to the fee payer, in the fee token's base units, from
|
|
31
|
+
* the receipt's `gasUsed` and decimal `effectiveGasPrice`. Returns undefined
|
|
32
|
+
* when either input is missing.
|
|
33
|
+
*/
|
|
34
|
+
function computeFeeAmount(gasUsed, effectiveGasPrice) {
|
|
35
|
+
if (gasUsed === undefined || effectiveGasPrice === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
const product = BigInt(gasUsed) * BigInt(effectiveGasPrice);
|
|
38
|
+
return ((product + feeScalingFactor - 1n) / feeScalingFactor).toString();
|
|
39
|
+
}
|
|
40
|
+
/** Zod schemas owned by the receipt handlers. */
|
|
41
|
+
export var schema;
|
|
42
|
+
(function (schema) {
|
|
43
|
+
// TODO(ox/zod): replace with the zod schema for `ox`'s `Log.Rpc` once ox ships
|
|
44
|
+
// a `zod` entrypoint.
|
|
45
|
+
/** One event log emitted by a transaction (RPC format). */
|
|
46
|
+
schema.Log = z
|
|
47
|
+
.looseObject({
|
|
48
|
+
address: Schema.Address.check(z.describe('The contract address that emitted this event log.')),
|
|
49
|
+
blockHash: z
|
|
50
|
+
.nullable(Schema.Hash)
|
|
51
|
+
.check(z.describe('The block hash once included, or `null` while pending.')),
|
|
52
|
+
blockNumber: z
|
|
53
|
+
.nullable(Schema.Quantity)
|
|
54
|
+
.check(z.describe('The block number once included, or `null` while pending.')),
|
|
55
|
+
blockTimestamp: z
|
|
56
|
+
.nullish(Schema.Quantity)
|
|
57
|
+
.check(z.describe('Tempo-provided block timestamp for this log.')),
|
|
58
|
+
data: Schema.Hex.check(z.describe('ABI-encoded data for the event parameters that are not indexed.'), z.meta({ examples: ['0xdeadbeef'] })),
|
|
59
|
+
logIndex: z
|
|
60
|
+
.nullable(Schema.Quantity)
|
|
61
|
+
.check(z.describe('The log position within the block, or `null` while pending.')),
|
|
62
|
+
removed: z
|
|
63
|
+
.boolean()
|
|
64
|
+
.check(z.describe('Whether this log was removed by a chain reorganization; Tempo finality means finalized logs do not reorg.'), z.meta({ examples: [false] })),
|
|
65
|
+
topics: z
|
|
66
|
+
.array(Schema.Hash)
|
|
67
|
+
.check(z.describe('Indexed event topics, including the event signature hash as the first topic.')),
|
|
68
|
+
transactionHash: z
|
|
69
|
+
.nullable(Schema.Hash)
|
|
70
|
+
.check(z.describe('The transaction hash for this log, or `null` while pending.')),
|
|
71
|
+
transactionIndex: z
|
|
72
|
+
.nullable(Schema.Quantity)
|
|
73
|
+
.check(z.describe('The transaction position within the block, or `null` while pending.')),
|
|
74
|
+
})
|
|
75
|
+
.check(z.describe('One event log emitted by a contract during transaction execution.'));
|
|
76
|
+
/** The verbatim JSON-RPC payloads, surfaced under each resource's `rpc` field. */
|
|
77
|
+
let Rpc;
|
|
78
|
+
(function (Rpc) {
|
|
79
|
+
// TODO(ox/zod): replace with the zod schema for `ox/tempo`'s
|
|
80
|
+
// `TransactionReceipt.Rpc` once ox ships a `zod` entrypoint. `blockHash`,
|
|
81
|
+
// `logsBloom`, and `blockTimestamp` are nullish/optional so the receipt
|
|
82
|
+
// *list* (reconstructed from the indexer, which stores neither block hash nor
|
|
83
|
+
// bloom but does carry the block timestamp) reuses this same shape; the
|
|
84
|
+
// detail endpoint's verbatim RPC receipt populates all of them.
|
|
85
|
+
/** A JSON-RPC transaction receipt, passed through verbatim. */
|
|
86
|
+
Rpc.Receipt = z
|
|
87
|
+
.looseObject({
|
|
88
|
+
blobGasPrice: z
|
|
89
|
+
.optional(Schema.Quantity)
|
|
90
|
+
.check(z.describe('Blob gas price for EIP-4844 blob data.')),
|
|
91
|
+
blobGasUsed: z
|
|
92
|
+
.optional(Schema.Quantity)
|
|
93
|
+
.check(z.describe('Blob gas used by EIP-4844 blob data.')),
|
|
94
|
+
blockHash: z
|
|
95
|
+
.nullable(Schema.Hash)
|
|
96
|
+
.check(z.describe('The block hash, or `null` for receipts reconstructed from the index.')),
|
|
97
|
+
blockNumber: Schema.Quantity.check(z.describe('The block number that included this receipt.')),
|
|
98
|
+
blockTimestamp: z
|
|
99
|
+
.nullish(Schema.Quantity)
|
|
100
|
+
.check(z.describe('Block timestamp added when this receipt is reconstructed from indexed data.')),
|
|
101
|
+
contractAddress: z
|
|
102
|
+
.nullish(Schema.Address)
|
|
103
|
+
.check(z.describe('The created contract address, or `null` if the transaction did not create a contract.')),
|
|
104
|
+
cumulativeGasUsed: Schema.Quantity.check(z.describe('Total gas used in the block up to and including this transaction.')),
|
|
105
|
+
effectiveGasPrice: Schema.Quantity.check(z.describe('Effective gas price paid for this transaction.')),
|
|
106
|
+
feePayer: z
|
|
107
|
+
.nullish(Schema.Address)
|
|
108
|
+
.check(z.describe('The address that paid the transaction fee.')),
|
|
109
|
+
feeToken: z
|
|
110
|
+
.nullish(Schema.Address)
|
|
111
|
+
.check(z.describe('The token used to pay the fee; on Tempo this is a USD stablecoin such as `pathUSD`.')),
|
|
112
|
+
from: Schema.Address.check(z.describe('The address that sent or authorized the transaction.')),
|
|
113
|
+
gasUsed: Schema.Quantity.check(z.describe('Gas actually used to execute the transaction.')),
|
|
114
|
+
logs: z
|
|
115
|
+
.array(schema.Log)
|
|
116
|
+
.check(z.describe('Event logs emitted by contracts while this transaction executed.')),
|
|
117
|
+
logsBloom: z
|
|
118
|
+
.optional(Schema.Hex)
|
|
119
|
+
.check(z.describe('Bloom filter summarizing the logs in this receipt.'), z.meta({ examples: ['0x00'] })),
|
|
120
|
+
root: z.optional(Schema.Hex).check(z.describe('Post-transaction state root used by pre-Byzantium Ethereum receipts.'), z.meta({
|
|
121
|
+
examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665'],
|
|
122
|
+
})),
|
|
123
|
+
status: Schema.Quantity.check(z.describe('Execution status as a hex quantity: `0x1` means success and `0x0` means reverted.'), z.meta({ examples: ['0x1'] })),
|
|
124
|
+
to: z
|
|
125
|
+
.nullable(Schema.Address)
|
|
126
|
+
.check(z.describe('The recipient address, or `null` when the transaction created a contract.')),
|
|
127
|
+
transactionHash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies the transaction.')),
|
|
128
|
+
transactionIndex: Schema.Quantity.check(z.describe('The transaction position within its block.')),
|
|
129
|
+
type: Schema.Hex.check(z.describe('Raw transaction type byte, such as `0x2` for EIP-1559 or `0x76` for Tempo.'), z.meta({ examples: ['0x2'] })),
|
|
130
|
+
})
|
|
131
|
+
.check(z.describe('The transaction receipt exactly as returned by JSON-RPC.'));
|
|
132
|
+
})(Rpc = schema.Rpc || (schema.Rpc = {}));
|
|
133
|
+
/**
|
|
134
|
+
* Metadata for a receipt: the verbatim JSON-RPC receipt (always present) plus
|
|
135
|
+
* resources embedded on demand via `include`.
|
|
136
|
+
*/
|
|
137
|
+
schema.ReceiptMeta = z
|
|
138
|
+
.object({
|
|
139
|
+
feeToken: z
|
|
140
|
+
.optional(z.lazy(() => Tokens.schema.getToken.Response))
|
|
141
|
+
.check(z.describe('Fee-token details included only when you request `include=feeToken`.')),
|
|
142
|
+
rpc: Rpc.Receipt.check(z.describe('The original JSON-RPC receipt payload.')),
|
|
143
|
+
})
|
|
144
|
+
.check(z.describe('Metadata for this receipt: the original JSON-RPC payload plus any requested `include` resources.'));
|
|
145
|
+
/**
|
|
146
|
+
* A humanized transaction receipt: decoded `status`/`type`, plain-number gas
|
|
147
|
+
* and block fields, and decimal-string fee/price values. The raw event logs
|
|
148
|
+
* are surfaced verbatim under `logs`, and the full JSON-RPC receipt under
|
|
149
|
+
* `meta.rpc`.
|
|
150
|
+
*/
|
|
151
|
+
schema.Receipt = z
|
|
152
|
+
.object({
|
|
153
|
+
blockHash: z
|
|
154
|
+
.nullable(Schema.Hash)
|
|
155
|
+
.check(z.describe('The block hash, or `null` when the receipt was reconstructed from indexed data.')),
|
|
156
|
+
blockNumber: z
|
|
157
|
+
.number()
|
|
158
|
+
.check(z.int(), z.nonnegative(), z.describe('The block number that included this receipt.'), z.meta({ examples: [23456789] })),
|
|
159
|
+
contractAddress: z
|
|
160
|
+
.nullable(Schema.Address)
|
|
161
|
+
.check(z.describe('The created contract address, or `null` if the transaction did not create a contract.')),
|
|
162
|
+
cumulativeGasUsed: z
|
|
163
|
+
.number()
|
|
164
|
+
.check(z.int(), z.nonnegative(), z.describe('Total gas used in the block up to and including this transaction.'), z.meta({ examples: [21000] })),
|
|
165
|
+
effectiveGasPrice: Schema.DecimalString.check(z.describe('Effective gas price paid for this transaction, in attodollars per gas.')),
|
|
166
|
+
feeAmount: Schema.DecimalString.check(z.describe('Fee charged to the fee payer, as a decimal integer string in the fee token’s base units.')),
|
|
167
|
+
feePayer: z
|
|
168
|
+
.optional(Schema.Address)
|
|
169
|
+
.check(z.describe('The address that paid the transaction fee.')),
|
|
170
|
+
feeToken: z
|
|
171
|
+
.optional(Schema.Address)
|
|
172
|
+
.check(z.describe('The token used to pay the fee; on Tempo this is a USD stablecoin such as `pathUSD`.')),
|
|
173
|
+
gasUsed: z
|
|
174
|
+
.number()
|
|
175
|
+
.check(z.int(), z.nonnegative(), z.describe('Gas actually used to execute the transaction.'), z.meta({ examples: [21000] })),
|
|
176
|
+
id: Schema.Hash.check(z.describe('Stable resource ID for this API response; it is the transaction hash.')),
|
|
177
|
+
logs: z
|
|
178
|
+
.array(schema.Log)
|
|
179
|
+
.check(z.describe('Event logs emitted by this transaction, returned in JSON-RPC log format.')),
|
|
180
|
+
meta: schema.ReceiptMeta.check(z.describe('The original JSON-RPC payload plus any resources requested with `include`.')),
|
|
181
|
+
recipient: z
|
|
182
|
+
.nullable(Schema.Address)
|
|
183
|
+
.check(z.describe('The recipient address, or `null` when the transaction created a contract.')),
|
|
184
|
+
sender: Schema.Address.check(z.describe('The address that sent or authorized the transaction.')),
|
|
185
|
+
status: z
|
|
186
|
+
.enum(['success', 'reverted'])
|
|
187
|
+
.check(z.describe('Whether the transaction succeeded or reverted.'), z.meta({ examples: ['success'] })),
|
|
188
|
+
timestamp: z
|
|
189
|
+
.nullable(z.iso.datetime())
|
|
190
|
+
.check(z.describe('The block timestamp as ISO 8601, or `null` when it is unavailable.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
191
|
+
transactionHash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies the transaction.')),
|
|
192
|
+
transactionIndex: z
|
|
193
|
+
.number()
|
|
194
|
+
.check(z.int(), z.nonnegative(), z.describe('The transaction position within its block.'), z.meta({ examples: [0] })),
|
|
195
|
+
type: z.lazy(() => txSchema.TransactionType).check(z.meta({ examples: ['eip1559'] })),
|
|
196
|
+
})
|
|
197
|
+
.check(z.describe('A transaction outcome formatted for API clients, with status, gas, fees, logs, and the original JSON-RPC receipt under `meta.rpc`.'));
|
|
198
|
+
/** Schemas for the getReceipt operation. */
|
|
199
|
+
let getReceipt;
|
|
200
|
+
(function (getReceipt) {
|
|
201
|
+
/** Path parameters for receipt requests. */
|
|
202
|
+
getReceipt.Params = z
|
|
203
|
+
.object({
|
|
204
|
+
transactionHash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies the transaction.'), z.meta({ examples: [exampleHash] })),
|
|
205
|
+
})
|
|
206
|
+
.check(z.describe('Path parameters used to look up a receipt by transaction hash.'));
|
|
207
|
+
/** Optional related resource that callers opt into via `include`. */
|
|
208
|
+
getReceipt.Include = z
|
|
209
|
+
.enum(['feeToken'])
|
|
210
|
+
.check(z.describe('Related resource you can opt into with the `include` query parameter.'));
|
|
211
|
+
/**
|
|
212
|
+
* Parses a comma-separated `include` query value into a list of optional
|
|
213
|
+
* resources to embed. Extra lookups (e.g. fee-token metadata) only run when
|
|
214
|
+
* explicitly requested, keeping the base receipt response fast.
|
|
215
|
+
*/
|
|
216
|
+
getReceipt.includeQuery = Schema.includeQuery(getReceipt.Include, 'Comma-separated related resources to include, such as `feeToken`.');
|
|
217
|
+
/** Query parameters for receipt requests. */
|
|
218
|
+
getReceipt.Query = z
|
|
219
|
+
.strictObject({
|
|
220
|
+
chainId: Schema.ChainIdQuery,
|
|
221
|
+
include: getReceipt.includeQuery,
|
|
222
|
+
})
|
|
223
|
+
.check(z.describe('Query parameters for looking up a receipt.'));
|
|
224
|
+
/** A humanized receipt, with the verbatim JSON-RPC payload under `meta.rpc`. */
|
|
225
|
+
getReceipt.Response = schema.Receipt;
|
|
226
|
+
})(getReceipt = schema.getReceipt || (schema.getReceipt = {}));
|
|
227
|
+
/** Schemas for the getReceipts (list) operation. */
|
|
228
|
+
let getReceipts;
|
|
229
|
+
(function (getReceipts) {
|
|
230
|
+
/**
|
|
231
|
+
* Optional resources for the receipt list: the per-row `feeToken` embed
|
|
232
|
+
* plus the response-wide capped `totalCount`. The detail route keeps its
|
|
233
|
+
* own `feeToken`-only {@link getReceipt.Include}.
|
|
234
|
+
*/
|
|
235
|
+
getReceipts.Include = z
|
|
236
|
+
.enum(['feeToken', 'totalCount'])
|
|
237
|
+
.check(z.describe('Related resources to include in each receipt row or in response metadata.'));
|
|
238
|
+
/** Parses the comma-separated `include` value for the receipt list. */
|
|
239
|
+
getReceipts.includeQuery = Schema.includeQuery(getReceipts.Include, 'Comma-separated related resources to include, such as `feeToken,totalCount`.');
|
|
240
|
+
/** Query parameters for receipt list requests. */
|
|
241
|
+
getReceipts.Query = z
|
|
242
|
+
.strictObject({
|
|
243
|
+
address: z
|
|
244
|
+
.optional(Schema.Address)
|
|
245
|
+
.check(z.describe('Filter to receipts whose transaction has this address as sender or recipient.')),
|
|
246
|
+
'blockNumber.from': Schema.blockNumberBound('receipts', 'from'),
|
|
247
|
+
'blockNumber.to': Schema.blockNumberBound('receipts', 'to'),
|
|
248
|
+
chainId: Schema.ChainIdQuery,
|
|
249
|
+
cursor: Schema.Cursor,
|
|
250
|
+
feePayer: z
|
|
251
|
+
.optional(Schema.Address)
|
|
252
|
+
.check(z.describe('Filter to receipts whose transaction fee was paid by this address.')),
|
|
253
|
+
feeToken: z
|
|
254
|
+
.optional(Schema.TokenAddress)
|
|
255
|
+
.check(z.describe('Filter to receipts whose transaction fee was paid in this token address.')),
|
|
256
|
+
include: getReceipts.includeQuery,
|
|
257
|
+
limit: Schema.Limit,
|
|
258
|
+
order: Schema.Order,
|
|
259
|
+
page: Schema.Page,
|
|
260
|
+
recipient: z
|
|
261
|
+
.optional(Schema.Address)
|
|
262
|
+
.check(z.describe('Filter to receipts for transactions sent to this recipient address.')),
|
|
263
|
+
sender: z
|
|
264
|
+
.optional(Schema.Address)
|
|
265
|
+
.check(z.describe('Filter to receipts for transactions sent by this sender address.')),
|
|
266
|
+
status: z
|
|
267
|
+
.optional(z.enum(['success', 'reverted']))
|
|
268
|
+
.check(z.describe('Filter to receipts with this execution status.'), z.meta({ examples: ['success'] })),
|
|
269
|
+
'timestamp.from': Schema.timestampBound('receipts', 'from'),
|
|
270
|
+
'timestamp.to': Schema.timestampBound('receipts', 'to'),
|
|
271
|
+
})
|
|
272
|
+
.check(...Schema.pageChecks(), z.describe('Query parameters for listing receipts.'));
|
|
273
|
+
/**
|
|
274
|
+
* Page of receipts. Each item reuses the humanized receipt returned by
|
|
275
|
+
* `GET /transactions/:transactionHash/receipt`, so the list and detail endpoints expose one
|
|
276
|
+
* identical shape. Indexed list rows are reconstructed from the indexer,
|
|
277
|
+
* which stores neither the block hash nor the event logs, so those fields are
|
|
278
|
+
* `null`/empty in the list `meta.rpc` payload.
|
|
279
|
+
*/
|
|
280
|
+
getReceipts.Response = z
|
|
281
|
+
.object({
|
|
282
|
+
data: z.array(getReceipt.Response).check(z.describe('Receipts on this page.')),
|
|
283
|
+
meta: z
|
|
284
|
+
.optional(Schema.CountMeta)
|
|
285
|
+
.check(z.describe('Response-level metadata requested with `include`, such as `totalCount`.')),
|
|
286
|
+
nextCursor: Schema.NextCursor,
|
|
287
|
+
})
|
|
288
|
+
.check(z.describe('A page of receipts ordered by block number and transaction position.'));
|
|
289
|
+
})(getReceipts = schema.getReceipts || (schema.getReceipts = {}));
|
|
290
|
+
})(schema || (schema = {}));
|
|
291
|
+
/** Creates receipt handlers. */
|
|
292
|
+
export function receipts() {
|
|
293
|
+
return new Hono()
|
|
294
|
+
.get('/receipts', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getReceipts.Query, {
|
|
295
|
+
code: 'query_invalid',
|
|
296
|
+
message: 'Invalid query parameters',
|
|
297
|
+
}), OpenApi.describeRoute({
|
|
298
|
+
description: 'List transaction receipts across Tempo, with filters for addresses, blocks, timestamps, fees, and status.',
|
|
299
|
+
operationId: 'getReceipts',
|
|
300
|
+
responses: OpenApi.responses({
|
|
301
|
+
errors: { 502: 'Could not read indexed receipt data from TIDX.' },
|
|
302
|
+
success: {
|
|
303
|
+
description: 'A page of transaction receipts.',
|
|
304
|
+
schema: schema.getReceipts.Response,
|
|
305
|
+
},
|
|
306
|
+
}),
|
|
307
|
+
summary: 'List transaction receipts',
|
|
308
|
+
tags: ['Transactions'],
|
|
309
|
+
}), Cache.response({
|
|
310
|
+
cacheControl: Cache.policies.feed,
|
|
311
|
+
name: 'tempo-api:receipts:v1',
|
|
312
|
+
key: (c) => Cache.urlKey(c, schema.getReceipts.Query),
|
|
313
|
+
}), async (c) => {
|
|
314
|
+
if (Auth.narrowAccess)
|
|
315
|
+
return Auth.paidAccessError(c);
|
|
316
|
+
if (OpenApi.narrowValidation)
|
|
317
|
+
return OpenApi.validationError(c, {
|
|
318
|
+
code: 'query_invalid',
|
|
319
|
+
message: 'Invalid query parameters',
|
|
320
|
+
});
|
|
321
|
+
const query = c.req.valid('query');
|
|
322
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
323
|
+
// Cursor pages are anchored below the head and effectively immutable, so
|
|
324
|
+
// cache them longer; the head page keeps the route's short `feed` default.
|
|
325
|
+
if (query.cursor !== undefined)
|
|
326
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
327
|
+
try {
|
|
328
|
+
// `totalCount` is opt-in and shares the page's filters; run it
|
|
329
|
+
// concurrently with the page. Best-effort: a count failure omits
|
|
330
|
+
// `meta` rather than failing the page.
|
|
331
|
+
const countPromise = query.include.includes('totalCount')
|
|
332
|
+
? countReceipts(c, {
|
|
333
|
+
address: query.address,
|
|
334
|
+
chainId,
|
|
335
|
+
feePayer: query.feePayer,
|
|
336
|
+
feeToken: query.feeToken,
|
|
337
|
+
fromBlock: query['blockNumber.from'],
|
|
338
|
+
fromTimestamp: query['timestamp.from'],
|
|
339
|
+
recipient: query.recipient,
|
|
340
|
+
sender: query.sender,
|
|
341
|
+
status: query.status,
|
|
342
|
+
toBlock: query['blockNumber.to'],
|
|
343
|
+
toTimestamp: query['timestamp.to'],
|
|
344
|
+
}).catch(() => undefined)
|
|
345
|
+
: undefined;
|
|
346
|
+
const receipts = await listReceipts(c, {
|
|
347
|
+
address: query.address,
|
|
348
|
+
chainId,
|
|
349
|
+
cursor: query.cursor,
|
|
350
|
+
feePayer: query.feePayer,
|
|
351
|
+
feeToken: query.feeToken,
|
|
352
|
+
fromBlock: query['blockNumber.from'],
|
|
353
|
+
fromTimestamp: query['timestamp.from'],
|
|
354
|
+
include: query.include.includes('feeToken') ? ['feeToken'] : [],
|
|
355
|
+
limit: query.limit,
|
|
356
|
+
order: query.order,
|
|
357
|
+
page: query.page,
|
|
358
|
+
recipient: query.recipient,
|
|
359
|
+
sender: query.sender,
|
|
360
|
+
status: query.status,
|
|
361
|
+
toBlock: query['blockNumber.to'],
|
|
362
|
+
toTimestamp: query['timestamp.to'],
|
|
363
|
+
});
|
|
364
|
+
const meta = countPromise ? await countPromise : undefined;
|
|
365
|
+
return c.json(Response.validated(schema.getReceipts.Response, {
|
|
366
|
+
...receipts,
|
|
367
|
+
...(meta ? { meta } : {}),
|
|
368
|
+
}), 200);
|
|
369
|
+
}
|
|
370
|
+
catch (cause) {
|
|
371
|
+
return Response.upstream(c, cause);
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
.get('/:transactionHash/receipt', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getReceipt.Params, {
|
|
375
|
+
code: 'transaction_invalid',
|
|
376
|
+
message: 'Invalid transaction hash',
|
|
377
|
+
}), OpenApi.validate('query', schema.getReceipt.Query, {
|
|
378
|
+
code: 'query_invalid',
|
|
379
|
+
message: 'Invalid query parameters',
|
|
380
|
+
}), OpenApi.describeRoute({
|
|
381
|
+
description: 'Get the outcome of one transaction by its transaction hash.',
|
|
382
|
+
operationId: 'getReceipt',
|
|
383
|
+
responses: OpenApi.responses({
|
|
384
|
+
errors: {
|
|
385
|
+
404: {
|
|
386
|
+
description: 'No receipt was found for that transaction hash.',
|
|
387
|
+
codes: ['receipt_not_found'],
|
|
388
|
+
},
|
|
389
|
+
502: 'Could not read receipt data from the upstream JSON-RPC node.',
|
|
390
|
+
},
|
|
391
|
+
success: {
|
|
392
|
+
description: 'A single transaction receipt with status, gas, fees, and logs.',
|
|
393
|
+
schema: schema.getReceipt.Response,
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
summary: 'Get a transaction receipt',
|
|
397
|
+
tags: ['Transactions'],
|
|
398
|
+
}), Cache.response({
|
|
399
|
+
// A receipt exists only once the transaction is mined: the lookup is
|
|
400
|
+
// either a 404 (unknown / not-yet-mined hash) or a final 200, never a
|
|
401
|
+
// pending 200. Default to no-store so the 404 isn't cached with a
|
|
402
|
+
// long-lived header (which would mask the receipt once mined); the handler
|
|
403
|
+
// unconditionally upgrades the 200 to `immutable` below.
|
|
404
|
+
cacheControl: Cache.policies.noStore,
|
|
405
|
+
name: 'tempo-api:receipts:v1',
|
|
406
|
+
key: (c) => Cache.urlKey(c, schema.getReceipt.Query),
|
|
407
|
+
}), async (c) => {
|
|
408
|
+
if (Auth.narrowAccess)
|
|
409
|
+
return Auth.paidAccessError(c);
|
|
410
|
+
if (OpenApi.narrowValidation)
|
|
411
|
+
return OpenApi.validationError(c, {
|
|
412
|
+
code: 'transaction_invalid',
|
|
413
|
+
message: 'Invalid request parameters',
|
|
414
|
+
});
|
|
415
|
+
if (OpenApi.narrowValidation)
|
|
416
|
+
return OpenApi.validationError(c, {
|
|
417
|
+
code: 'query_invalid',
|
|
418
|
+
message: 'Invalid request parameters',
|
|
419
|
+
});
|
|
420
|
+
const { transactionHash } = c.req.valid('param');
|
|
421
|
+
const query = c.req.valid('query');
|
|
422
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
423
|
+
const client = c.get('getClient')(chainId);
|
|
424
|
+
// `Schema.Hash` lowercases to a plain string; re-narrow to the hex
|
|
425
|
+
// template literal viem requires at the RPC boundary.
|
|
426
|
+
const hash = transactionHash;
|
|
427
|
+
try {
|
|
428
|
+
// Fetch the JSON-RPC receipt; a `null` result means the receipt is
|
|
429
|
+
// unknown or the tx is still pending. The verbatim payload is surfaced
|
|
430
|
+
// under `meta.rpc`, with humanized fields layered on top. Unmined
|
|
431
|
+
// lookups are deliberately never cached — confirmation polling must
|
|
432
|
+
// observe the receipt the moment the node has it.
|
|
433
|
+
const rpc = await Timing.time(c, 'transaction_receipt', () => client.request({ method: 'eth_getTransactionReceipt', params: [hash] }));
|
|
434
|
+
if (!rpc)
|
|
435
|
+
return Response.error(c, {
|
|
436
|
+
code: 'receipt_not_found',
|
|
437
|
+
message: 'Transaction receipt not found',
|
|
438
|
+
status: 404,
|
|
439
|
+
});
|
|
440
|
+
const data = humanizeReceipt(Response.validated(schema.Rpc.Receipt, rpc));
|
|
441
|
+
// A receipt is only returned once the transaction is mined, so it is
|
|
442
|
+
// immutable and can be cached aggressively.
|
|
443
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
444
|
+
// Fee-token metadata is opt-in: resolving it is an extra lookup, so it
|
|
445
|
+
// only runs when requested via `include=feeToken` and the receipt
|
|
446
|
+
// declares a fee token.
|
|
447
|
+
const feeToken = data.feeToken;
|
|
448
|
+
if (!query.include.includes('feeToken') || feeToken === undefined)
|
|
449
|
+
return c.json(Response.validated(schema.getReceipt.Response, data), 200);
|
|
450
|
+
const token = await Timing.time(c, 'fee_token', () => Tokens.resolveToken(c, { address: feeToken, chainId }).catch(() => undefined));
|
|
451
|
+
return c.json(Response.validated(schema.getReceipt.Response, token ? { ...data, meta: { ...data.meta, feeToken: token } } : data), 200);
|
|
452
|
+
}
|
|
453
|
+
catch (cause) {
|
|
454
|
+
return Response.upstream(c, cause);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Lists humanized receipts and embeds opt-in `include` resources (currently
|
|
460
|
+
* `feeToken`).
|
|
461
|
+
*/
|
|
462
|
+
export async function listReceipts(c, options) {
|
|
463
|
+
const { include, ...rest } = options;
|
|
464
|
+
const page = await getReceipts(c, rest);
|
|
465
|
+
const data = include.includes('feeToken')
|
|
466
|
+
? await embedFeeTokens(c, page.data, rest.chainId)
|
|
467
|
+
: page.data;
|
|
468
|
+
return { ...page, data };
|
|
469
|
+
}
|
|
470
|
+
export function getReceipts(c, options) {
|
|
471
|
+
const { address, chainId, feePayer, feeToken, fromBlock, fromTimestamp, limit, order, recipient, sender, status, toBlock, toTimestamp, } = options;
|
|
472
|
+
const store = c.get('store');
|
|
473
|
+
const tidx = c.get('getTidx')(chainId);
|
|
474
|
+
const sortDirection = order === 'asc' ? 'ASC' : 'DESC';
|
|
475
|
+
// Keyset pagination: anchor the page below the previous row's `(block, tx_idx)`
|
|
476
|
+
// position instead of a numeric offset, so rows arriving at the head can't
|
|
477
|
+
// shift items across pages. A malformed cursor falls back to the head page.
|
|
478
|
+
const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
|
|
479
|
+
// Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
|
|
480
|
+
// the head page, so it shares the head's SQL and cache entries; deeper pages
|
|
481
|
+
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
482
|
+
const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
|
|
483
|
+
return Timing.time(c, 'receipts', () => Store.memoize(async () => {
|
|
484
|
+
// Page the `receipts` table alone, then enrich each row with the
|
|
485
|
+
// `type`/`fee_token` it does not store via a follow-up `txs` lookup
|
|
486
|
+
// (the blocks-feed pattern). Paging straight off `receipts` avoids the
|
|
487
|
+
// `receipts JOIN txs` shape, which TIDX's planner cannot execute under
|
|
488
|
+
// address scope: a `UNION` of two JOINs (or the `(from = X OR to = X)`
|
|
489
|
+
// form) reliably returns `db error` (HTTP 422) for high-traffic
|
|
490
|
+
// addresses. Two index-friendly single-table queries plan cleanly.
|
|
491
|
+
//
|
|
492
|
+
// Build the shared filters once (everything except the address-scope
|
|
493
|
+
// predicate, which is special-cased below).
|
|
494
|
+
const filters = [];
|
|
495
|
+
if (sender !== undefined)
|
|
496
|
+
filters.push(`"from" = '${sender}'`);
|
|
497
|
+
if (recipient !== undefined)
|
|
498
|
+
filters.push(`"to" = '${recipient}'`);
|
|
499
|
+
if (feePayer !== undefined)
|
|
500
|
+
filters.push(`fee_payer = '${feePayer}'`);
|
|
501
|
+
// `fee_token` lives on `txs`, not `receipts`; filter via a subquery so
|
|
502
|
+
// the page query stays single-table.
|
|
503
|
+
if (feeToken !== undefined)
|
|
504
|
+
filters.push(`tx_hash IN (SELECT hash FROM txs WHERE fee_token = '${feeToken}')`);
|
|
505
|
+
if (fromBlock !== undefined)
|
|
506
|
+
filters.push(`block_num >= ${fromBlock}`);
|
|
507
|
+
if (toBlock !== undefined)
|
|
508
|
+
filters.push(`block_num <= ${toBlock}`);
|
|
509
|
+
if (fromTimestamp !== undefined)
|
|
510
|
+
filters.push(`block_timestamp >= '${fromTimestamp}'`);
|
|
511
|
+
if (toTimestamp !== undefined)
|
|
512
|
+
filters.push(`block_timestamp <= '${toTimestamp}'`);
|
|
513
|
+
if (status !== undefined)
|
|
514
|
+
filters.push(`status = ${status === 'success' ? 1 : 0}`);
|
|
515
|
+
if (cursor !== undefined)
|
|
516
|
+
filters.push(Cursor.keyset([
|
|
517
|
+
{ literal: Cursor.literal(cursor[0], 'int'), name: 'block_num', order },
|
|
518
|
+
{ literal: Cursor.literal(cursor[1], 'int'), name: 'tx_idx', order },
|
|
519
|
+
]));
|
|
520
|
+
const columns = `block_num, block_timestamp, tx_idx, tx_hash, "from", "to",
|
|
521
|
+
contract_address, gas_used, cumulative_gas_used, effective_gas_price,
|
|
522
|
+
status, fee_payer`;
|
|
523
|
+
// For address-scoped queries, run one branch per side and `UNION` so
|
|
524
|
+
// each branch returns at most `limit + 1` index-ordered rows, the outer
|
|
525
|
+
// query re-merges, and plain `UNION` dedupes the self-receipt case
|
|
526
|
+
// where `from = to = address`. The sort+limit live *inside* each branch
|
|
527
|
+
// so the merge stays bounded.
|
|
528
|
+
//
|
|
529
|
+
// The `receipts` table only indexes `from`, not `to` (a `to` scan over
|
|
530
|
+
// ~25M rows hits the indexer's statement timeout and returns `db error`,
|
|
531
|
+
// HTTP 422). The `from` branch filters `receipts` directly; the `to`
|
|
532
|
+
// branch resolves matching transaction hashes from `txs` (which indexes
|
|
533
|
+
// `to`) and looks them up by the indexed `receipts.tx_hash`.
|
|
534
|
+
const sideQuery = (side, addr) => {
|
|
535
|
+
const predicate = side === 'from'
|
|
536
|
+
? `"from" = '${addr}'`
|
|
537
|
+
: `tx_hash IN (SELECT hash FROM txs WHERE "to" = '${addr}')`;
|
|
538
|
+
const where = [predicate, ...filters].join(' AND ');
|
|
539
|
+
// In the offset lane each branch over-fetches the full window
|
|
540
|
+
// (`offset + limit + 1` rows) because the skip applies to the merged
|
|
541
|
+
// set: the outer query re-sorts the union and discards the first
|
|
542
|
+
// `offset` rows.
|
|
543
|
+
return `(SELECT ${columns} FROM receipts WHERE ${where} ORDER BY block_num ${sortDirection}, tx_idx ${sortDirection} LIMIT ${(offset ?? 0) + limit + 1})`;
|
|
544
|
+
};
|
|
545
|
+
const skip = offset !== undefined ? ` OFFSET ${offset}` : '';
|
|
546
|
+
const query = address !== undefined
|
|
547
|
+
? `
|
|
548
|
+
${sideQuery('from', address)}
|
|
549
|
+
UNION
|
|
550
|
+
${sideQuery('to', address)}
|
|
551
|
+
ORDER BY block_num ${sortDirection}, tx_idx ${sortDirection}
|
|
552
|
+
LIMIT ${limit + 1}${skip}
|
|
553
|
+
`
|
|
554
|
+
: `
|
|
555
|
+
SELECT ${columns}
|
|
556
|
+
FROM receipts
|
|
557
|
+
${filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''}
|
|
558
|
+
ORDER BY block_num ${sortDirection}, tx_idx ${sortDirection}
|
|
559
|
+
LIMIT ${limit + 1}${skip}
|
|
560
|
+
`;
|
|
561
|
+
const result = await tidx.fetch({ chainId, query: query });
|
|
562
|
+
// The next page anchors below the last fetched row's `(block, tx_idx)`.
|
|
563
|
+
const page = Cursor.paginate({
|
|
564
|
+
rows: result.rows,
|
|
565
|
+
limit,
|
|
566
|
+
key: (row) => {
|
|
567
|
+
const block = Value.toNumber(row['block_num']);
|
|
568
|
+
const index = Value.toNumber(row['tx_idx']);
|
|
569
|
+
return block !== undefined && index !== undefined ? [block, index] : undefined;
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
// Enrich the page with `type`/`fee_token` from `txs` in a single
|
|
573
|
+
// `IN (...)` lookup scoped to the page's transaction hashes. `txs` is
|
|
574
|
+
// sorted by `(block_num, idx)` with no hash index, so the hash list
|
|
575
|
+
// must be paired with the page's block numbers — a bare `hash IN`
|
|
576
|
+
// full-scans the table.
|
|
577
|
+
const hashes = [
|
|
578
|
+
...new Set(page.rows
|
|
579
|
+
.map((row) => row['tx_hash'])
|
|
580
|
+
.filter((hash) => typeof hash === 'string')),
|
|
581
|
+
];
|
|
582
|
+
const blocks = [
|
|
583
|
+
...new Set(page.rows
|
|
584
|
+
.map((row) => Value.toNumber(row['block_num']))
|
|
585
|
+
.filter((block) => block !== undefined)),
|
|
586
|
+
];
|
|
587
|
+
const txByHash = new Map();
|
|
588
|
+
if (hashes.length > 0 && blocks.length > 0) {
|
|
589
|
+
const txs = await tidx.fetch({
|
|
590
|
+
chainId,
|
|
591
|
+
query: `SELECT hash, type, fee_token FROM txs WHERE block_num IN (${blocks.join(', ')}) AND hash IN (${hashes.map((hash) => `'${hash}'`).join(', ')})`,
|
|
592
|
+
});
|
|
593
|
+
for (const row of txs.rows) {
|
|
594
|
+
const hash = row['hash'];
|
|
595
|
+
if (typeof hash === 'string')
|
|
596
|
+
txByHash.set(hash, { feeToken: row['fee_token'], type: row['type'] });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// Reconstruct the RPC shape from the indexed columns, then humanize it
|
|
600
|
+
// through the same path as the detail endpoint so the list and detail
|
|
601
|
+
// responses are identical. Rows that fail to reconstruct are skipped.
|
|
602
|
+
const data = [];
|
|
603
|
+
for (const row of page.rows) {
|
|
604
|
+
const tx = typeof row['tx_hash'] === 'string' ? txByHash.get(row['tx_hash']) : undefined;
|
|
605
|
+
const merged = { ...row, fee_token: tx?.feeToken, type: tx?.type };
|
|
606
|
+
const rpc = schema.Rpc.Receipt.safeParse(toRpcReceipt(merged));
|
|
607
|
+
if (rpc.success)
|
|
608
|
+
data.push(humanizeReceipt(rpc.data));
|
|
609
|
+
}
|
|
610
|
+
return { data, nextCursor: page.nextCursor };
|
|
611
|
+
}, {
|
|
612
|
+
key: `receipts:v1:${chainId}:${order}:${address ?? ''}:${sender ?? ''}:${recipient ?? ''}:${feePayer ?? ''}:${feeToken ?? ''}:${fromBlock ?? ''}:${toBlock ?? ''}:${fromTimestamp ?? ''}:${toTimestamp ?? ''}:${status ?? ''}:${cursor ? `cursor:${cursor[0]}:${cursor[1]}` : offset !== undefined ? `page:${options.page}` : 'head'}:${limit}`,
|
|
613
|
+
store,
|
|
614
|
+
ttl: Ttl.seconds(15),
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Builds the non-cursor, non-address-scope filters shared by the receipt page
|
|
619
|
+
* ({@link getReceipts}) and the capped count ({@link countReceipts}), so the
|
|
620
|
+
* count always matches the page it annotates. The address scope is handled
|
|
621
|
+
* separately (its `UNION` workaround differs between page and count).
|
|
622
|
+
*/
|
|
623
|
+
function countFilters(options) {
|
|
624
|
+
const filters = [];
|
|
625
|
+
if (options.sender !== undefined)
|
|
626
|
+
filters.push(`"from" = '${options.sender}'`);
|
|
627
|
+
if (options.recipient !== undefined)
|
|
628
|
+
filters.push(`"to" = '${options.recipient}'`);
|
|
629
|
+
if (options.feePayer !== undefined)
|
|
630
|
+
filters.push(`fee_payer = '${options.feePayer}'`);
|
|
631
|
+
if (options.feeToken !== undefined)
|
|
632
|
+
filters.push(`tx_hash IN (SELECT hash FROM txs WHERE fee_token = '${options.feeToken}')`);
|
|
633
|
+
if (options.fromBlock !== undefined)
|
|
634
|
+
filters.push(`block_num >= ${options.fromBlock}`);
|
|
635
|
+
if (options.toBlock !== undefined)
|
|
636
|
+
filters.push(`block_num <= ${options.toBlock}`);
|
|
637
|
+
if (options.fromTimestamp !== undefined)
|
|
638
|
+
filters.push(`block_timestamp >= '${options.fromTimestamp}'`);
|
|
639
|
+
if (options.toTimestamp !== undefined)
|
|
640
|
+
filters.push(`block_timestamp <= '${options.toTimestamp}'`);
|
|
641
|
+
if (options.status !== undefined)
|
|
642
|
+
filters.push(`status = ${options.status === 'success' ? 1 : 0}`);
|
|
643
|
+
return filters;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Capped total count of receipts matching the same filters as the page. The
|
|
647
|
+
* address scope reuses the page's `UNION` workaround (`receipts` indexes `from`
|
|
648
|
+
* but not `to`, so the `to` side resolves hashes via the `txs` index), counting
|
|
649
|
+
* the deduped keyset so self-receipts (`from = to`) aren't double-counted.
|
|
650
|
+
* `totalCountCapped: true` is a lower bound — at least {@link Schema.countCap} matches.
|
|
651
|
+
*/
|
|
652
|
+
export function countReceipts(c, options) {
|
|
653
|
+
const { address, chainId } = options;
|
|
654
|
+
const store = c.get('store');
|
|
655
|
+
const tidx = c.get('getTidx')(chainId);
|
|
656
|
+
return Timing.time(c, 'receipts_count', () => Store.memoize(async () => {
|
|
657
|
+
const filters = countFilters(options);
|
|
658
|
+
let query;
|
|
659
|
+
if (address !== undefined) {
|
|
660
|
+
const fromWhere = [`"from" = '${address}'`, ...filters].join(' AND ');
|
|
661
|
+
const toWhere = [
|
|
662
|
+
`tx_hash IN (SELECT hash FROM txs WHERE "to" = '${address}')`,
|
|
663
|
+
...filters,
|
|
664
|
+
].join(' AND ');
|
|
665
|
+
query = `
|
|
666
|
+
SELECT count(*) AS total
|
|
667
|
+
FROM (
|
|
668
|
+
(SELECT block_num, tx_idx FROM receipts WHERE ${fromWhere} LIMIT ${Schema.countCap})
|
|
669
|
+
UNION
|
|
670
|
+
(SELECT block_num, tx_idx FROM receipts WHERE ${toWhere} LIMIT ${Schema.countCap})
|
|
671
|
+
) AS capped
|
|
672
|
+
`;
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
676
|
+
query = `
|
|
677
|
+
SELECT count(*) AS total
|
|
678
|
+
FROM (
|
|
679
|
+
SELECT 1 FROM receipts ${where} LIMIT ${Schema.countCap}
|
|
680
|
+
) AS capped
|
|
681
|
+
`;
|
|
682
|
+
}
|
|
683
|
+
const result = await tidx.fetch({ chainId, query: query });
|
|
684
|
+
const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0;
|
|
685
|
+
return { totalCountCapped: totalCount >= Schema.countCap, totalCount };
|
|
686
|
+
}, {
|
|
687
|
+
key: `receipts-count:v1:${chainId}:${address ?? ''}:${options.sender ?? ''}:${options.recipient ?? ''}:${options.feePayer ?? ''}:${options.feeToken ?? ''}:${options.fromBlock ?? ''}:${options.toBlock ?? ''}:${options.fromTimestamp ?? ''}:${options.toTimestamp ?? ''}:${options.status ?? ''}`,
|
|
688
|
+
store,
|
|
689
|
+
ttl: Ttl.seconds(15),
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Resolves humanized receipts — including their event logs — for a page of
|
|
694
|
+
* transactions, keyed by lowercased transaction hash. Rows without a receipt
|
|
695
|
+
* (pending, or not yet indexed) are simply absent from the map.
|
|
696
|
+
*
|
|
697
|
+
* Receipt rows come from the indexed `receipts` table (skipped when the caller
|
|
698
|
+
* already paged receipts via the `status` path) and event logs from the raw
|
|
699
|
+
* `logs` table, both fetched once for the whole page. No `Store.memoize` here:
|
|
700
|
+
* the route-level response cache (`Cache.response`) already absorbs repeated
|
|
701
|
+
* requests for the same page.
|
|
702
|
+
*/
|
|
703
|
+
export async function embedReceipts(c, options) {
|
|
704
|
+
const { chainId, rows } = options;
|
|
705
|
+
const found = new Map();
|
|
706
|
+
// Only mined rows can have a receipt; their block numbers also feed the
|
|
707
|
+
// block bound the `logs` lookup requires.
|
|
708
|
+
const blocks = [];
|
|
709
|
+
const hashes = new Set();
|
|
710
|
+
for (const row of rows) {
|
|
711
|
+
if (row.blockNumber === null)
|
|
712
|
+
continue;
|
|
713
|
+
blocks.push(row.blockNumber);
|
|
714
|
+
hashes.add(row.hash.toLowerCase());
|
|
715
|
+
}
|
|
716
|
+
if (hashes.size === 0)
|
|
717
|
+
return found;
|
|
718
|
+
const tidx = c.get('getTidx')(chainId);
|
|
719
|
+
const inList = [...hashes].map((hash) => `'${hash}'`).join(', ');
|
|
720
|
+
// Raw-table queries (no signature-decoded CTE), so the inline SQL is cast to
|
|
721
|
+
// `string`. The receipts lookup needs no block bound (`receipts.tx_hash` is
|
|
722
|
+
// indexed — see the `sideQuery` comment in `getReceipts`); the logs lookup
|
|
723
|
+
// REQUIRES one: `logs` is sorted by block position, so a bare `tx_hash IN`
|
|
724
|
+
// scans to genesis (see `blockBound` in `transfers.ts`).
|
|
725
|
+
const [receipts_result, logs_result] = await Timing.time(c, 'receipts_embed', () => Promise.all([
|
|
726
|
+
options.receipts
|
|
727
|
+
? undefined
|
|
728
|
+
: tidx.fetch({
|
|
729
|
+
chainId,
|
|
730
|
+
query: `SELECT block_num, block_timestamp, tx_idx, tx_hash, "from", "to", contract_address, gas_used, cumulative_gas_used, effective_gas_price, status, fee_payer FROM receipts WHERE tx_hash IN (${inList})`,
|
|
731
|
+
}),
|
|
732
|
+
tidx.fetch({
|
|
733
|
+
chainId,
|
|
734
|
+
query: `SELECT tx_hash, block_num, tx_idx, log_idx, address, selector, topic1, topic2, topic3, data, block_timestamp FROM logs WHERE block_num BETWEEN ${Math.min(...blocks)} AND ${Math.max(...blocks)} AND tx_hash IN (${inList}) ORDER BY block_num ASC, log_idx ASC`,
|
|
735
|
+
}),
|
|
736
|
+
]));
|
|
737
|
+
// Reconstruct each raw `logs` row into the RPC log shape `schema.Log`
|
|
738
|
+
// consumes (the postgres `logs` table names `topic0` `selector`), grouped by
|
|
739
|
+
// transaction hash. Logs that fail to reconstruct are skipped.
|
|
740
|
+
const logsByHash = new Map();
|
|
741
|
+
for (const row of logs_result.rows) {
|
|
742
|
+
const hash = Value.toText(row['tx_hash'])?.toLowerCase();
|
|
743
|
+
if (!hash)
|
|
744
|
+
continue;
|
|
745
|
+
const timestamp = Value.toIsoDateTime(row['block_timestamp']);
|
|
746
|
+
const log = schema.Log.safeParse({
|
|
747
|
+
address: row['address'],
|
|
748
|
+
blockHash: null,
|
|
749
|
+
blockNumber: Value.toHex(row['block_num']),
|
|
750
|
+
blockTimestamp: timestamp ? Value.toHex(Math.floor(Date.parse(timestamp) / 1000)) : undefined,
|
|
751
|
+
data: Value.toText(row['data']) ?? '0x',
|
|
752
|
+
logIndex: Value.toHex(row['log_idx']),
|
|
753
|
+
removed: false,
|
|
754
|
+
topics: [row['selector'], row['topic1'], row['topic2'], row['topic3']].filter((topic) => typeof topic === 'string' && topic.length > 0),
|
|
755
|
+
transactionHash: hash,
|
|
756
|
+
transactionIndex: Value.toHex(row['tx_idx']),
|
|
757
|
+
});
|
|
758
|
+
if (!log.success)
|
|
759
|
+
continue;
|
|
760
|
+
const logs = logsByHash.get(hash);
|
|
761
|
+
if (logs)
|
|
762
|
+
logs.push(log.data);
|
|
763
|
+
else
|
|
764
|
+
logsByHash.set(hash, [log.data]);
|
|
765
|
+
}
|
|
766
|
+
// Status path: the caller already paged humanized receipts, which the
|
|
767
|
+
// receipts list reconstructs without logs — rebuild each with the page's
|
|
768
|
+
// logs attached (both on the row and inside its `meta.rpc` payload).
|
|
769
|
+
if (options.receipts) {
|
|
770
|
+
for (const receipt of options.receipts) {
|
|
771
|
+
const hash = receipt.transactionHash.toLowerCase();
|
|
772
|
+
const logs = logsByHash.get(hash) ?? [];
|
|
773
|
+
found.set(hash, {
|
|
774
|
+
...receipt,
|
|
775
|
+
logs,
|
|
776
|
+
meta: { ...receipt.meta, rpc: { ...receipt.meta.rpc, logs } },
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return found;
|
|
780
|
+
}
|
|
781
|
+
const receiptRowByHash = new Map();
|
|
782
|
+
for (const row of receipts_result?.rows ?? []) {
|
|
783
|
+
const hash = Value.toText(row['tx_hash'])?.toLowerCase();
|
|
784
|
+
if (hash)
|
|
785
|
+
receiptRowByHash.set(hash, row);
|
|
786
|
+
}
|
|
787
|
+
// Per transaction row, merge in the `type`/`fee_token` the `receipts` table
|
|
788
|
+
// does not store from the transaction row itself (saving the `txs` follow-up
|
|
789
|
+
// lookup the receipts list needs), inject the reconstructed logs, and
|
|
790
|
+
// humanize through the same path as the receipt endpoints.
|
|
791
|
+
for (const row of rows) {
|
|
792
|
+
const hash = row.hash.toLowerCase();
|
|
793
|
+
const receipt = receiptRowByHash.get(hash);
|
|
794
|
+
if (!receipt)
|
|
795
|
+
continue;
|
|
796
|
+
const merged = {
|
|
797
|
+
...receipt,
|
|
798
|
+
fee_token: row.feeToken,
|
|
799
|
+
// `toRpcReceipt` hex-encodes numeric columns; decode the row's raw hex
|
|
800
|
+
// `type` byte so it round-trips through the same path.
|
|
801
|
+
type: Value.hexToNumber(row.meta.rpc.type),
|
|
802
|
+
};
|
|
803
|
+
const rpc = schema.Rpc.Receipt.safeParse({
|
|
804
|
+
...toRpcReceipt(merged),
|
|
805
|
+
logs: logsByHash.get(hash) ?? [],
|
|
806
|
+
});
|
|
807
|
+
if (rpc.success)
|
|
808
|
+
found.set(hash, humanizeReceipt(rpc.data));
|
|
809
|
+
}
|
|
810
|
+
return found;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Humanizes a JSON-RPC transaction receipt: decodes `status`/`type`, converts
|
|
814
|
+
* hex quantities to decimal strings and block fields to numbers, and derives the
|
|
815
|
+
* block `timestamp` from the top-level `blockTimestamp` (present when
|
|
816
|
+
* reconstructed from the index) or the (shared) per-log timestamp — `null` when
|
|
817
|
+
* neither is available. The verbatim payload is preserved under `meta.rpc` and
|
|
818
|
+
* the raw logs under `logs`. Shared by the receipt detail (RPC source) and list
|
|
819
|
+
* (reconstructed from the indexer) endpoints, so both expose an identical shape.
|
|
820
|
+
*/
|
|
821
|
+
export function humanizeReceipt(rpc) {
|
|
822
|
+
const gasUsed = Value.hexToNumber(rpc.gasUsed) ?? 0;
|
|
823
|
+
const effectiveGasPrice = Value.fromHex(rpc.effectiveGasPrice) ?? '0';
|
|
824
|
+
return {
|
|
825
|
+
blockHash: rpc.blockHash,
|
|
826
|
+
blockNumber: Value.hexToNumber(rpc.blockNumber) ?? 0,
|
|
827
|
+
contractAddress: rpc.contractAddress ?? null,
|
|
828
|
+
cumulativeGasUsed: Value.hexToNumber(rpc.cumulativeGasUsed) ?? 0,
|
|
829
|
+
effectiveGasPrice,
|
|
830
|
+
feeAmount: computeFeeAmount(gasUsed, effectiveGasPrice) ?? '0',
|
|
831
|
+
feePayer: rpc.feePayer ?? undefined,
|
|
832
|
+
feeToken: rpc.feeToken ?? undefined,
|
|
833
|
+
gasUsed,
|
|
834
|
+
id: rpc.transactionHash,
|
|
835
|
+
logs: rpc.logs,
|
|
836
|
+
meta: { rpc },
|
|
837
|
+
recipient: rpc.to,
|
|
838
|
+
sender: rpc.from,
|
|
839
|
+
status: Value.hexToNumber(rpc.status) === 1 ? 'success' : 'reverted',
|
|
840
|
+
timestamp: Value.hexSecondsToIso(rpc.blockTimestamp ?? rpc.logs[0]?.blockTimestamp) ?? null,
|
|
841
|
+
transactionHash: rpc.transactionHash,
|
|
842
|
+
transactionIndex: Value.hexToNumber(rpc.transactionIndex) ?? 0,
|
|
843
|
+
type: decodeTransactionType(rpc.type),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Re-encodes a TIDX `receipts` row (joined to `txs`) into the JSON-RPC receipt
|
|
848
|
+
* shape, which becomes the `rpc` payload the list endpoint humanizes through the
|
|
849
|
+
* same path as the detail endpoint. Numeric columns become `0x` quantities;
|
|
850
|
+
* fields the indexer does not store — the block hash, logs bloom, and event logs
|
|
851
|
+
* — are null/empty since the `receipts` table records neither.
|
|
852
|
+
*/
|
|
853
|
+
export function toRpcReceipt(row) {
|
|
854
|
+
const timestamp = Value.toIsoDateTime(row['block_timestamp']);
|
|
855
|
+
return {
|
|
856
|
+
blockHash: null,
|
|
857
|
+
blockNumber: Value.toHex(row['block_num']),
|
|
858
|
+
blockTimestamp: timestamp ? Value.toHex(Math.floor(Date.parse(timestamp) / 1000)) : undefined,
|
|
859
|
+
contractAddress: typeof row['contract_address'] === 'string' ? row['contract_address'] : null,
|
|
860
|
+
cumulativeGasUsed: Value.toHex(row['cumulative_gas_used']),
|
|
861
|
+
effectiveGasPrice: Value.toHex(row['effective_gas_price']),
|
|
862
|
+
feePayer: typeof row['fee_payer'] === 'string' ? row['fee_payer'] : undefined,
|
|
863
|
+
feeToken: typeof row['fee_token'] === 'string' ? row['fee_token'] : undefined,
|
|
864
|
+
from: row['from'],
|
|
865
|
+
gasUsed: Value.toHex(row['gas_used']),
|
|
866
|
+
logs: [],
|
|
867
|
+
status: Value.toHex(row['status']),
|
|
868
|
+
to: typeof row['to'] === 'string' ? row['to'] : null,
|
|
869
|
+
transactionHash: row['tx_hash'],
|
|
870
|
+
transactionIndex: Value.toHex(row['tx_idx']),
|
|
871
|
+
type: Value.toHex(row['type']),
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
//# sourceMappingURL=receipts.js.map
|