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,689 @@
|
|
|
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
|
+
/** Example block number surfaced in OpenAPI docs. */
|
|
15
|
+
const exampleBlockNumber = '1000000';
|
|
16
|
+
/** The EVM block tags accepted by the `:block` selector. */
|
|
17
|
+
const blockTags = ['latest', 'finalized'];
|
|
18
|
+
/**
|
|
19
|
+
* ISO 8601 timestamp selector for the `:block` parameter. Shared between the
|
|
20
|
+
* route's param schema (validation + OpenAPI `date-time` format) and
|
|
21
|
+
* {@link parseBlockIdentifier} (runtime dispatch) so the two cannot drift.
|
|
22
|
+
*/
|
|
23
|
+
const blockTimestamp = z.iso.datetime({ offset: true });
|
|
24
|
+
/** Zod schemas owned by the block handlers. */
|
|
25
|
+
export var schema;
|
|
26
|
+
(function (schema) {
|
|
27
|
+
/** The verbatim JSON-RPC payloads, surfaced under each resource's `rpc` field. */
|
|
28
|
+
let Rpc;
|
|
29
|
+
(function (Rpc) {
|
|
30
|
+
// TODO(ox/zod): replace with the zod schema for `ox/Block.Rpc` once ox
|
|
31
|
+
// ships a `zod` entrypoint, so the wire format stays in lockstep with
|
|
32
|
+
// ox/viem.
|
|
33
|
+
/** A JSON-RPC block, passed through verbatim. */
|
|
34
|
+
Rpc.Block = z
|
|
35
|
+
.looseObject({
|
|
36
|
+
baseFeePerGas: z
|
|
37
|
+
.nullish(Schema.Quantity)
|
|
38
|
+
.check(z.describe('Base fee per gas for EIP-1559-style fee markets, if present.')),
|
|
39
|
+
difficulty: z
|
|
40
|
+
.optional(Schema.Quantity)
|
|
41
|
+
.check(z.describe('Block difficulty value from legacy proof-of-work fields, when present.')),
|
|
42
|
+
extraData: z
|
|
43
|
+
.optional(Schema.Hex)
|
|
44
|
+
.check(z.describe('Extra data bytes included by the block producer.'), z.meta({ examples: ['0x00'] })),
|
|
45
|
+
gasLimit: Schema.Quantity.check(z.describe('Maximum gas available for all transactions in this block.')),
|
|
46
|
+
gasUsed: Schema.Quantity.check(z.describe('Total gas used by every transaction in this block.')),
|
|
47
|
+
hash: z
|
|
48
|
+
.nullable(Schema.Hash)
|
|
49
|
+
.check(z.describe('The block hash, or `null` for a pending block.')),
|
|
50
|
+
logsBloom: z
|
|
51
|
+
.nullish(Schema.Hex)
|
|
52
|
+
.check(z.describe('Bloom filter summarizing logs in the block, or `null` while pending.'), z.meta({ examples: ['0x00'] })),
|
|
53
|
+
miner: Schema.Address.check(z.describe('The address of the block producer, also called the proposer or miner.')),
|
|
54
|
+
mixHash: z
|
|
55
|
+
.optional(Schema.Hash)
|
|
56
|
+
.check(z.describe('Consensus mix hash carried in the JSON-RPC block payload.')),
|
|
57
|
+
nonce: z
|
|
58
|
+
.nullish(Schema.Hex)
|
|
59
|
+
.check(z.describe('Legacy block nonce, or `null` while pending.'), z.meta({ examples: ['0x00'] })),
|
|
60
|
+
number: z
|
|
61
|
+
.nullable(Schema.Quantity)
|
|
62
|
+
.check(z.describe('The block number, or `null` for a pending block.')),
|
|
63
|
+
parentHash: Schema.Hash.check(z.describe('The hash of the previous block in the chain.')),
|
|
64
|
+
receiptsRoot: Schema.Hash.check(z.describe('Root hash of the receipts trie for this block.')),
|
|
65
|
+
sha3Uncles: z
|
|
66
|
+
.optional(Schema.Hash)
|
|
67
|
+
.check(z.describe('Keccak-256 hash of the uncle blocks list.')),
|
|
68
|
+
size: Schema.Quantity.check(z.describe('Size of the block in bytes.')),
|
|
69
|
+
stateRoot: Schema.Hash.check(z.describe('Root hash of the world-state trie after this block.')),
|
|
70
|
+
timestamp: Schema.Quantity.check(z.describe('Block timestamp as Unix seconds.')),
|
|
71
|
+
totalDifficulty: z
|
|
72
|
+
.optional(Schema.Quantity)
|
|
73
|
+
.check(z.describe('Total cumulative difficulty through this block, when present.')),
|
|
74
|
+
transactions: z
|
|
75
|
+
.array(Schema.Hash)
|
|
76
|
+
.check(z.describe('Transaction hashes included in this ordered batch.')),
|
|
77
|
+
transactionsRoot: Schema.Hash.check(z.describe('Root hash of the transactions trie for this block.')),
|
|
78
|
+
uncles: z
|
|
79
|
+
.optional(z.array(Schema.Hash))
|
|
80
|
+
.check(z.describe('Hashes of uncle blocks included in this block, when present.')),
|
|
81
|
+
withdrawals: z
|
|
82
|
+
.optional(z.array(z.looseObject({})))
|
|
83
|
+
.check(z.describe('EIP-4895 withdrawals included in this block, when present.')),
|
|
84
|
+
withdrawalsRoot: z
|
|
85
|
+
.optional(Schema.Hash)
|
|
86
|
+
.check(z.describe('Root hash of the withdrawals trie, when present.')),
|
|
87
|
+
})
|
|
88
|
+
.check(z.describe('The block exactly as returned by JSON-RPC.'));
|
|
89
|
+
})(Rpc = schema.Rpc || (schema.Rpc = {}));
|
|
90
|
+
/**
|
|
91
|
+
* Metadata for a block: the verbatim JSON-RPC block (always present). Held in
|
|
92
|
+
* a `meta` envelope so future opt-in `include` resources (e.g. embedded
|
|
93
|
+
* transactions) can layer in without breaking the response shape.
|
|
94
|
+
*/
|
|
95
|
+
schema.BlockMeta = Schema.describe(z.object({
|
|
96
|
+
rpc: Rpc.Block.check(z.describe('The original JSON-RPC block payload.')),
|
|
97
|
+
}), 'Metadata for this block: the original JSON-RPC payload plus any future `include` resources.');
|
|
98
|
+
/**
|
|
99
|
+
* A humanized block: decimal-string amounts, ISO 8601 timestamps, and plain
|
|
100
|
+
* numbers for block fields. The verbatim JSON-RPC payload is always
|
|
101
|
+
* available under `meta.rpc`. Transactions are omitted from the response —
|
|
102
|
+
* clients fetch them via `GET /blocks/:block/transactions`.
|
|
103
|
+
*/
|
|
104
|
+
schema.Block = z
|
|
105
|
+
.object({
|
|
106
|
+
baseFeePerGas: z
|
|
107
|
+
.nullable(Schema.DecimalString)
|
|
108
|
+
.check(z.describe('Base fee per gas for EIP-1559-style fee markets, or `null` when unsupported.')),
|
|
109
|
+
gasLimit: z
|
|
110
|
+
.number()
|
|
111
|
+
.check(z.int(), z.nonnegative(), z.describe('Maximum gas available for all transactions in this block.'), z.meta({ examples: [21000] })),
|
|
112
|
+
gasUsed: z
|
|
113
|
+
.number()
|
|
114
|
+
.check(z.int(), z.nonnegative(), z.describe('Total gas used by transactions in this block.'), z.meta({ examples: [21000] })),
|
|
115
|
+
hash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies this block.')),
|
|
116
|
+
id: Schema.Hash.check(z.describe('Stable resource ID for this API response; it is the block hash.')),
|
|
117
|
+
meta: schema.BlockMeta.check(z.describe('The original JSON-RPC payload plus any resources requested with `include`.')),
|
|
118
|
+
miner: Schema.Address.check(z.describe('The address of the block producer, also called the proposer or miner.')),
|
|
119
|
+
number: z
|
|
120
|
+
.number()
|
|
121
|
+
.check(z.int(), z.nonnegative(), z.describe('The block height, starting from genesis block 0.'), z.meta({ examples: [23456789] })),
|
|
122
|
+
parentHash: Schema.Hash.check(z.describe('The hash of the previous block in the chain.')),
|
|
123
|
+
size: z
|
|
124
|
+
.number()
|
|
125
|
+
.check(z.int(), z.nonnegative(), z.describe('Size of the block in bytes.'), z.meta({ examples: [1024] })),
|
|
126
|
+
timestamp: z.iso
|
|
127
|
+
.datetime()
|
|
128
|
+
.check(z.describe('Block timestamp formatted as ISO 8601.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
129
|
+
transactionCount: z
|
|
130
|
+
.number()
|
|
131
|
+
.check(z.int(), z.nonnegative(), z.describe('Number of transactions included in this block.'), z.meta({ examples: [25] })),
|
|
132
|
+
})
|
|
133
|
+
.check(z.describe('A block formatted for API clients, with number, timestamp, gas usage, producer, and original JSON-RPC data under `meta.rpc`.'));
|
|
134
|
+
/**
|
|
135
|
+
* A lightweight block row, returned by the `/blocks` list endpoint. Full
|
|
136
|
+
* fields (parentHash, miner, gasUsed/gasLimit, …) would require one RPC
|
|
137
|
+
* roundtrip per row; the list endpoint stays cheap and clients fetch
|
|
138
|
+
* `/blocks/:block` for individual blocks they care about. The block hash is
|
|
139
|
+
* selected from the index so `id` stays tied to the resolved block identity.
|
|
140
|
+
*/
|
|
141
|
+
schema.BlockSummary = z
|
|
142
|
+
.object({
|
|
143
|
+
hash: Schema.Hash.check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies this block.')),
|
|
144
|
+
id: Schema.Hash.check(z.describe('Stable resource ID for this API response; it is the block hash.')),
|
|
145
|
+
number: z
|
|
146
|
+
.number()
|
|
147
|
+
.check(z.int(), z.nonnegative(), z.describe('The block height, starting from genesis block 0.'), z.meta({ examples: [23456789] })),
|
|
148
|
+
timestamp: z.iso
|
|
149
|
+
.datetime()
|
|
150
|
+
.check(z.describe('Block timestamp formatted as ISO 8601.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
151
|
+
transactionCount: z
|
|
152
|
+
.number()
|
|
153
|
+
.check(z.int(), z.nonnegative(), z.describe('Number of transactions included in this block.'), z.meta({ examples: [25] })),
|
|
154
|
+
})
|
|
155
|
+
.check(z.describe('A lightweight block summary for list responses.'));
|
|
156
|
+
/** Schemas for the getBlocks operation. */
|
|
157
|
+
let getBlocks;
|
|
158
|
+
(function (getBlocks) {
|
|
159
|
+
/** Query parameters for `GET /blocks`. */
|
|
160
|
+
getBlocks.Query = z
|
|
161
|
+
.strictObject({
|
|
162
|
+
chainId: Schema.ChainIdQuery,
|
|
163
|
+
cursor: Schema.Cursor,
|
|
164
|
+
limit: Schema.Limit,
|
|
165
|
+
order: Schema.Order,
|
|
166
|
+
page: Schema.Page,
|
|
167
|
+
})
|
|
168
|
+
.check(...Schema.pageChecks(), z.describe('Query parameters for listing blocks.'));
|
|
169
|
+
/** Page of lightweight block rows, including empty blocks. */
|
|
170
|
+
getBlocks.Response = z
|
|
171
|
+
.object({
|
|
172
|
+
data: z.array(schema.BlockSummary).check(z.describe('Blocks on this page.')),
|
|
173
|
+
nextCursor: Schema.NextCursor,
|
|
174
|
+
})
|
|
175
|
+
.check(z.describe('A page of blocks ordered by block number.'));
|
|
176
|
+
})(getBlocks = schema.getBlocks || (schema.getBlocks = {}));
|
|
177
|
+
/** Schemas for the getBlock operation. */
|
|
178
|
+
let getBlock;
|
|
179
|
+
(function (getBlock) {
|
|
180
|
+
/**
|
|
181
|
+
* Path parameter for block requests. Resolves a single block from any of:
|
|
182
|
+
* a block tag (`latest`, `finalized`); a decimal or `0x`-prefixed hex block
|
|
183
|
+
* number; a `0x`-prefixed 32-byte block hash; or an ISO 8601 timestamp
|
|
184
|
+
* (`2023-11-14T22:13:20Z`), which resolves to the first block at or after
|
|
185
|
+
* that instant.
|
|
186
|
+
*/
|
|
187
|
+
getBlock.Params = z
|
|
188
|
+
.object({
|
|
189
|
+
block: z
|
|
190
|
+
.union([
|
|
191
|
+
// Block tag (`latest`, `finalized`).
|
|
192
|
+
z.enum(blockTags),
|
|
193
|
+
// Block number (decimal or hex) or a 0x-prefixed 32-byte hash.
|
|
194
|
+
z.string().check(z.regex(/^(0x[0-9a-fA-F]+|[0-9]+)$/)),
|
|
195
|
+
// ISO 8601 timestamp, resolved to the first block at or after it.
|
|
196
|
+
blockTimestamp,
|
|
197
|
+
])
|
|
198
|
+
.check(z.describe('Block selector: `latest`, `finalized`, a decimal or hex block number, a 32-byte block hash, or an ISO 8601 timestamp.'), z.meta({ examples: [exampleBlockNumber, 'latest', '2023-11-14T22:13:20Z'] })),
|
|
199
|
+
})
|
|
200
|
+
.check(z.describe('Path parameter used to select a block.'));
|
|
201
|
+
/** Query parameters for `GET /blocks/:block`. */
|
|
202
|
+
getBlock.Query = z
|
|
203
|
+
.strictObject({
|
|
204
|
+
chainId: Schema.ChainIdQuery,
|
|
205
|
+
})
|
|
206
|
+
.check(z.describe('Query parameters for looking up a block.'));
|
|
207
|
+
/** A humanized block, with the verbatim JSON-RPC payload under `meta.rpc`. */
|
|
208
|
+
getBlock.Response = schema.Block;
|
|
209
|
+
})(getBlock = schema.getBlock || (schema.getBlock = {}));
|
|
210
|
+
/**
|
|
211
|
+
* Schemas for the legacy `getBlockByTimestamp` compatibility operation
|
|
212
|
+
* (`GET /chains/:chainId/blocks/by-timestamp`). Hidden from the docs; kept to
|
|
213
|
+
* keep pre-existing clients of the prior Tempo API working unchanged.
|
|
214
|
+
*/
|
|
215
|
+
let getBlockByTimestamp;
|
|
216
|
+
(function (getBlockByTimestamp) {
|
|
217
|
+
/** Path parameters: the numeric Tempo chain id. */
|
|
218
|
+
getBlockByTimestamp.Params = z
|
|
219
|
+
.object({ chainId: Schema.ChainId })
|
|
220
|
+
.check(z.describe('Path parameters for the legacy block-by-timestamp lookup.'));
|
|
221
|
+
/** Query parameters: a required unix timestamp and the search direction. */
|
|
222
|
+
getBlockByTimestamp.Query = z
|
|
223
|
+
.strictObject({
|
|
224
|
+
closest: z
|
|
225
|
+
._default(z.enum(['before', 'after']), 'before')
|
|
226
|
+
.check(z.describe('Choose whether the closest block should be at or before the timestamp, or at or after it.')),
|
|
227
|
+
timestamp: z.coerce
|
|
228
|
+
.number()
|
|
229
|
+
.check(z.int(), z.nonnegative(), z.describe('Unix timestamp in seconds.')),
|
|
230
|
+
})
|
|
231
|
+
.check(z.describe('Query parameters for the legacy block-by-timestamp lookup.'));
|
|
232
|
+
/** The legacy response envelope: `{ block: { blockNumber, blockTimestamp } }`. */
|
|
233
|
+
getBlockByTimestamp.Response = z
|
|
234
|
+
.object({
|
|
235
|
+
block: z
|
|
236
|
+
.object({
|
|
237
|
+
blockNumber: z
|
|
238
|
+
.number()
|
|
239
|
+
.check(z.int(), z.nonnegative(), z.describe('The block height, starting from genesis block 0.')),
|
|
240
|
+
blockTimestamp: z
|
|
241
|
+
.number()
|
|
242
|
+
.check(z.int(), z.nonnegative(), z.describe('Block timestamp as Unix seconds.')),
|
|
243
|
+
})
|
|
244
|
+
.check(z.describe('The closest indexed block found for the requested timestamp.')),
|
|
245
|
+
})
|
|
246
|
+
.check(z.describe('The closest indexed block for the supplied timestamp.'));
|
|
247
|
+
})(getBlockByTimestamp = schema.getBlockByTimestamp || (schema.getBlockByTimestamp = {}));
|
|
248
|
+
})(schema || (schema = {}));
|
|
249
|
+
/** Window, in seconds, searched on either side of a `by-timestamp` lookup. */
|
|
250
|
+
const byTimestampWindowSeconds = 60;
|
|
251
|
+
/** Creates block handlers. */
|
|
252
|
+
export function blocks() {
|
|
253
|
+
// Only cursor pages of the block list are cacheable: they are anchored below
|
|
254
|
+
// the head and effectively immutable. The head page is a point-in-time read
|
|
255
|
+
// of the live chain tip, so it skips the cache entirely and carries no
|
|
256
|
+
// `Cache-Control` header at all.
|
|
257
|
+
const cacheBlocksList = Cache.response({
|
|
258
|
+
cacheControl: Cache.policies.immutable,
|
|
259
|
+
name: 'tempo-api:blocks:v1',
|
|
260
|
+
key: (c) => Cache.urlKey(c, schema.getBlocks.Query),
|
|
261
|
+
});
|
|
262
|
+
const cacheBlocksListIfCursor = (c, next) => c.req.query('cursor') === undefined ? next() : cacheBlocksList(c, next);
|
|
263
|
+
return new Hono()
|
|
264
|
+
.get('/', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getBlocks.Query, {
|
|
265
|
+
code: 'query_invalid',
|
|
266
|
+
message: 'Invalid query parameters',
|
|
267
|
+
}), OpenApi.describeRoute({
|
|
268
|
+
description: 'List the ordered batches of transactions that make up Tempo.',
|
|
269
|
+
operationId: 'getBlocks',
|
|
270
|
+
responses: OpenApi.responses({
|
|
271
|
+
errors: { 502: 'Could not read indexed block data from TIDX.' },
|
|
272
|
+
success: { description: 'A page of block summaries.', schema: schema.getBlocks.Response },
|
|
273
|
+
}),
|
|
274
|
+
summary: 'List blocks',
|
|
275
|
+
tags: ['Blocks'],
|
|
276
|
+
}), cacheBlocksListIfCursor, async (c) => {
|
|
277
|
+
if (Auth.narrowAccess)
|
|
278
|
+
return Auth.paidAccessError(c);
|
|
279
|
+
if (OpenApi.narrowValidation)
|
|
280
|
+
return OpenApi.validationError(c, {
|
|
281
|
+
code: 'query_invalid',
|
|
282
|
+
message: 'Invalid query parameters',
|
|
283
|
+
});
|
|
284
|
+
const query = c.req.valid('query');
|
|
285
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
286
|
+
try {
|
|
287
|
+
const page = await listBlocks(c, {
|
|
288
|
+
chainId,
|
|
289
|
+
cursor: query.cursor,
|
|
290
|
+
limit: query.limit,
|
|
291
|
+
order: query.order,
|
|
292
|
+
page: query.page,
|
|
293
|
+
});
|
|
294
|
+
return c.json(Response.validated(schema.getBlocks.Response, page), 200);
|
|
295
|
+
}
|
|
296
|
+
catch (cause) {
|
|
297
|
+
return Response.upstream(c, cause);
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
.get('/:block', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getBlock.Params, {
|
|
301
|
+
code: 'block_invalid',
|
|
302
|
+
message: 'Invalid block identifier',
|
|
303
|
+
}), OpenApi.validate('query', schema.getBlock.Query, {
|
|
304
|
+
code: 'query_invalid',
|
|
305
|
+
message: 'Invalid query parameters',
|
|
306
|
+
}), OpenApi.describeRoute({
|
|
307
|
+
description: 'Get one block by tag, number, hash, or timestamp selector.',
|
|
308
|
+
operationId: 'getBlock',
|
|
309
|
+
responses: OpenApi.responses({
|
|
310
|
+
errors: {
|
|
311
|
+
404: {
|
|
312
|
+
description: 'No block matched the selector, or no indexed block exists at or after the timestamp.',
|
|
313
|
+
codes: ['block_not_found'],
|
|
314
|
+
},
|
|
315
|
+
502: 'Could not read block data from the upstream JSON-RPC node or TIDX.',
|
|
316
|
+
},
|
|
317
|
+
success: {
|
|
318
|
+
description: 'A single block with number, timestamp, gas usage, producer, and original JSON-RPC data.',
|
|
319
|
+
schema: schema.Block,
|
|
320
|
+
},
|
|
321
|
+
}),
|
|
322
|
+
summary: 'Get a block by selector',
|
|
323
|
+
tags: ['Blocks'],
|
|
324
|
+
}), Cache.response({
|
|
325
|
+
// Default to no-store so a 404 cannot mask the eventual mined block.
|
|
326
|
+
// The handler upgrades a mined block to `immutable` below.
|
|
327
|
+
cacheControl: Cache.policies.noStore,
|
|
328
|
+
name: 'tempo-api:blocks:v1',
|
|
329
|
+
key: (c) => Cache.urlKey(c, schema.getBlock.Query),
|
|
330
|
+
}), async (c) => {
|
|
331
|
+
if (Auth.narrowAccess)
|
|
332
|
+
return Auth.paidAccessError(c);
|
|
333
|
+
if (OpenApi.narrowValidation)
|
|
334
|
+
return OpenApi.validationError(c, {
|
|
335
|
+
code: 'block_invalid',
|
|
336
|
+
message: 'Invalid request parameters',
|
|
337
|
+
});
|
|
338
|
+
if (OpenApi.narrowValidation)
|
|
339
|
+
return OpenApi.validationError(c, {
|
|
340
|
+
code: 'query_invalid',
|
|
341
|
+
message: 'Invalid request parameters',
|
|
342
|
+
});
|
|
343
|
+
const { block } = c.req.valid('param');
|
|
344
|
+
const query = c.req.valid('query');
|
|
345
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
346
|
+
const client = c.get('getClient')(chainId);
|
|
347
|
+
const identifier = parseBlockIdentifier(block);
|
|
348
|
+
if (!identifier)
|
|
349
|
+
return Response.error(c, {
|
|
350
|
+
code: 'block_invalid',
|
|
351
|
+
message: 'Invalid block identifier',
|
|
352
|
+
status: 400,
|
|
353
|
+
});
|
|
354
|
+
try {
|
|
355
|
+
// A timestamp selector resolves to a concrete block number via the
|
|
356
|
+
// indexer first (the `blocks` table records every block, empty ones
|
|
357
|
+
// included, so this is exact rather than rounding to the next
|
|
358
|
+
// non-empty block). A distinct 404 separates "no indexed block at or
|
|
359
|
+
// after this instant" from a missing block by number/hash.
|
|
360
|
+
let timestampBlock;
|
|
361
|
+
if (identifier.kind === 'timestamp') {
|
|
362
|
+
const tidx = c.get('getTidx')(chainId);
|
|
363
|
+
const store = c.get('store');
|
|
364
|
+
const iso = identifier.iso;
|
|
365
|
+
timestampBlock = await Timing.time(c, 'block_by_timestamp', () => Store.memoize(async () => {
|
|
366
|
+
const result = await tidx.fetch({
|
|
367
|
+
chainId,
|
|
368
|
+
query: `
|
|
369
|
+
SELECT num
|
|
370
|
+
FROM blocks
|
|
371
|
+
WHERE timestamp >= '${iso}'
|
|
372
|
+
ORDER BY num ASC
|
|
373
|
+
LIMIT 1
|
|
374
|
+
`,
|
|
375
|
+
});
|
|
376
|
+
const row = result.rows[0];
|
|
377
|
+
if (!row)
|
|
378
|
+
return undefined;
|
|
379
|
+
return Value.toNumber(row['num']);
|
|
380
|
+
}, {
|
|
381
|
+
key: `blocks:v1:${chainId}:by_timestamp:${iso}`,
|
|
382
|
+
store,
|
|
383
|
+
ttl: Ttl.seconds(30),
|
|
384
|
+
}));
|
|
385
|
+
if (timestampBlock === undefined)
|
|
386
|
+
return Response.error(c, {
|
|
387
|
+
code: 'block_not_found',
|
|
388
|
+
message: 'No indexed block at or after this timestamp',
|
|
389
|
+
status: 404,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
// Dispatch by selector shape: a 32-byte hash uses
|
|
393
|
+
// `eth_getBlockByHash`; tags, numbers, and the timestamp-resolved
|
|
394
|
+
// number all go through `eth_getBlockByNumber`. The moving tags
|
|
395
|
+
// (`latest`/`finalized`) are deliberately never cached — not even
|
|
396
|
+
// briefly — because they are point-in-time reads of the live tip.
|
|
397
|
+
const rpc = await Timing.time(c, 'block', () => {
|
|
398
|
+
if (identifier.kind === 'hash')
|
|
399
|
+
return client.request({
|
|
400
|
+
method: 'eth_getBlockByHash',
|
|
401
|
+
params: [identifier.hash, false],
|
|
402
|
+
});
|
|
403
|
+
const block = identifier.kind === 'tag'
|
|
404
|
+
? identifier.tag
|
|
405
|
+
: identifier.kind === 'number'
|
|
406
|
+
? Hex.fromNumber(identifier.number)
|
|
407
|
+
: Hex.fromNumber(BigInt(timestampBlock));
|
|
408
|
+
return client.request({ method: 'eth_getBlockByNumber', params: [block, false] });
|
|
409
|
+
});
|
|
410
|
+
if (!rpc)
|
|
411
|
+
return Response.error(c, {
|
|
412
|
+
code: 'block_not_found',
|
|
413
|
+
message: 'Block not found',
|
|
414
|
+
status: 404,
|
|
415
|
+
});
|
|
416
|
+
// Cache policy by selector: a specific block (number or hash) is
|
|
417
|
+
// immutable; a timestamp can land on the moving head or a historic
|
|
418
|
+
// block, so `state` is the safe middle; the moving tags (`latest`,
|
|
419
|
+
// `finalized`) keep the route's no-store default.
|
|
420
|
+
if (identifier.kind === 'number' || identifier.kind === 'hash')
|
|
421
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
422
|
+
else if (identifier.kind === 'timestamp')
|
|
423
|
+
Cache.setPolicy(c, Cache.policies.state);
|
|
424
|
+
const data = humanizeBlock(Response.validated(schema.Rpc.Block, rpc));
|
|
425
|
+
return c.json(Response.validated(schema.Block, data), 200);
|
|
426
|
+
}
|
|
427
|
+
catch (cause) {
|
|
428
|
+
return Response.upstream(c, cause);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Mounts the hidden legacy `GET /chains/:chainId/blocks/by-timestamp`
|
|
434
|
+
* compatibility route at the app root. The prior Tempo API exposed this shape
|
|
435
|
+
* (chain id in the path, a required unix `timestamp`, and a `closest` direction)
|
|
436
|
+
* and returned `{ block: { blockNumber, blockTimestamp } }`; this preserves it
|
|
437
|
+
* so existing clients keep working while the canonical surface is
|
|
438
|
+
* `GET /v1/blocks/:block` with an ISO 8601 selector.
|
|
439
|
+
*
|
|
440
|
+
* Like the original it searches a ±{@link byTimestampWindowSeconds}s window and
|
|
441
|
+
* returns `404` when no indexed block falls within it. Because the chain id is a
|
|
442
|
+
* path parameter (not the `chainId` query the global guard inspects), this
|
|
443
|
+
* validates it against the deployment's supported chains itself.
|
|
444
|
+
*/
|
|
445
|
+
export function blocksByTimestampCompat() {
|
|
446
|
+
return new Hono().get('/:chainId/blocks/by-timestamp', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getBlockByTimestamp.Params, {
|
|
447
|
+
code: 'chain_id_invalid',
|
|
448
|
+
message: 'Invalid chain id',
|
|
449
|
+
}), OpenApi.validate('query', schema.getBlockByTimestamp.Query, {
|
|
450
|
+
code: 'query_invalid',
|
|
451
|
+
message: 'Invalid query parameters',
|
|
452
|
+
}), OpenApi.describeRoute({
|
|
453
|
+
hide: true,
|
|
454
|
+
operationId: 'getBlockByTimestamp',
|
|
455
|
+
responses: OpenApi.responses({
|
|
456
|
+
errors: {
|
|
457
|
+
404: {
|
|
458
|
+
description: 'No indexed block was found within the timestamp search window.',
|
|
459
|
+
codes: ['block_not_found'],
|
|
460
|
+
},
|
|
461
|
+
502: 'Could not read indexed block data from TIDX.',
|
|
462
|
+
},
|
|
463
|
+
success: {
|
|
464
|
+
description: 'The closest indexed block for the supplied timestamp.',
|
|
465
|
+
schema: schema.getBlockByTimestamp.Response,
|
|
466
|
+
},
|
|
467
|
+
}),
|
|
468
|
+
summary: 'Get a legacy block by timestamp',
|
|
469
|
+
tags: ['Blocks'],
|
|
470
|
+
}), Cache.response({
|
|
471
|
+
cacheControl: Cache.policies.state,
|
|
472
|
+
name: 'tempo-api:blocks:v1',
|
|
473
|
+
key: (c) => Cache.urlKey(c, schema.getBlockByTimestamp.Query),
|
|
474
|
+
}), async (c) => {
|
|
475
|
+
if (Auth.narrowAccess)
|
|
476
|
+
return Auth.paidAccessError(c);
|
|
477
|
+
if (OpenApi.narrowValidation)
|
|
478
|
+
return OpenApi.validationError(c, {
|
|
479
|
+
code: 'query_invalid',
|
|
480
|
+
message: 'Invalid request parameters',
|
|
481
|
+
});
|
|
482
|
+
const { chainId } = c.req.valid('param');
|
|
483
|
+
const { closest, timestamp } = c.req.valid('query');
|
|
484
|
+
// The global unsupported-chain guard only inspects the `chainId` query, so
|
|
485
|
+
// reject an unserved chain here before it reaches the indexer.
|
|
486
|
+
const supportedChainIds = c.get('supportedChainIds');
|
|
487
|
+
if (!supportedChainIds.has(chainId))
|
|
488
|
+
return Response.unsupportedChainId(c, chainId, supportedChainIds);
|
|
489
|
+
try {
|
|
490
|
+
const block = await findBlockByTimestamp(c, { chainId, closest, timestamp });
|
|
491
|
+
if (!block)
|
|
492
|
+
return Response.error(c, {
|
|
493
|
+
code: 'block_not_found',
|
|
494
|
+
message: 'No indexed block matches the supplied timestamp',
|
|
495
|
+
status: 404,
|
|
496
|
+
});
|
|
497
|
+
return c.json(Response.validated(schema.getBlockByTimestamp.Response, { block }), 200);
|
|
498
|
+
}
|
|
499
|
+
catch (cause) {
|
|
500
|
+
return Response.upstream(c, cause);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Resolves the indexed block closest to `timestamp` within a
|
|
506
|
+
* ±{@link byTimestampWindowSeconds}s window, in the requested direction. Reads
|
|
507
|
+
* the `blocks` table (every block, empty ones included) so the match is exact.
|
|
508
|
+
*/
|
|
509
|
+
function findBlockByTimestamp(c, options) {
|
|
510
|
+
const { chainId, closest, timestamp } = options;
|
|
511
|
+
const store = c.get('store');
|
|
512
|
+
const tidx = c.get('getTidx')(chainId);
|
|
513
|
+
const at = new Date(timestamp * 1000).toISOString();
|
|
514
|
+
const bound = new Date((closest === 'before'
|
|
515
|
+
? timestamp - byTimestampWindowSeconds
|
|
516
|
+
: timestamp + byTimestampWindowSeconds) * 1000).toISOString();
|
|
517
|
+
return Timing.time(c, 'block_by_timestamp', () => Store.memoize(async () => {
|
|
518
|
+
// `before`: latest block in [at - window, at]; `after`: earliest block
|
|
519
|
+
// in [at, at + window]. Order on timestamp then `num` so ties resolve to
|
|
520
|
+
// the block nearest the requested instant.
|
|
521
|
+
const where = closest === 'before'
|
|
522
|
+
? `timestamp <= '${at}' AND timestamp >= '${bound}'`
|
|
523
|
+
: `timestamp >= '${at}' AND timestamp <= '${bound}'`;
|
|
524
|
+
const direction = closest === 'before' ? 'DESC' : 'ASC';
|
|
525
|
+
const result = await tidx.fetch({
|
|
526
|
+
chainId,
|
|
527
|
+
query: `
|
|
528
|
+
SELECT num, timestamp
|
|
529
|
+
FROM blocks
|
|
530
|
+
WHERE ${where}
|
|
531
|
+
ORDER BY timestamp ${direction}, num ${direction}
|
|
532
|
+
LIMIT 1
|
|
533
|
+
`,
|
|
534
|
+
});
|
|
535
|
+
const row = result.rows[0];
|
|
536
|
+
if (!row)
|
|
537
|
+
return undefined;
|
|
538
|
+
const blockNumber = Value.toNumber(row['num']);
|
|
539
|
+
const iso = Value.toIsoDateTime(row['timestamp']);
|
|
540
|
+
if (blockNumber === undefined || iso === undefined)
|
|
541
|
+
return undefined;
|
|
542
|
+
return { blockNumber, blockTimestamp: Math.floor(Date.parse(iso) / 1000) };
|
|
543
|
+
}, {
|
|
544
|
+
key: `blocks:v1:${chainId}:by_timestamp_compat:${closest}:${timestamp}`,
|
|
545
|
+
store,
|
|
546
|
+
ttl: Ttl.seconds(30),
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Parses a `:block` path parameter, dispatching by shape. Returns the
|
|
551
|
+
* discriminated identifier, or `undefined` when the input matches no known
|
|
552
|
+
* shape (the schema should have caught this earlier; this guards the type at
|
|
553
|
+
* the use site).
|
|
554
|
+
*
|
|
555
|
+
* Selectors are unambiguous by shape: a block tag is a fixed keyword; the
|
|
556
|
+
* 66-char `0x`-prefixed length identifies a 32-byte hash; any other
|
|
557
|
+
* `0x`-prefixed hex (or plain decimal) is a block number; and an ISO 8601
|
|
558
|
+
* timestamp (validated with the same {@link blockTimestamp} schema as the
|
|
559
|
+
* route) is resolved to its UTC instant.
|
|
560
|
+
*/
|
|
561
|
+
export function parseBlockIdentifier(value) {
|
|
562
|
+
if (blockTags.includes(value))
|
|
563
|
+
return { kind: 'tag', tag: value };
|
|
564
|
+
if (/^0x[0-9a-fA-F]{64}$/.test(value))
|
|
565
|
+
return { kind: 'hash', hash: value.toLowerCase() };
|
|
566
|
+
if (/^0x[0-9a-fA-F]+$/.test(value))
|
|
567
|
+
return { kind: 'number', number: BigInt(value) };
|
|
568
|
+
if (/^[0-9]+$/.test(value))
|
|
569
|
+
return { kind: 'number', number: BigInt(value) };
|
|
570
|
+
if (blockTimestamp.safeParse(value).success)
|
|
571
|
+
return { kind: 'timestamp', iso: new Date(Date.parse(value)).toISOString() };
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Lists blocks from the indexer. Reads every block from the `blocks` table
|
|
576
|
+
* (so empty blocks are included) and resolves transaction counts via a
|
|
577
|
+
* second narrow `txs` query keyed to the page's block numbers.
|
|
578
|
+
*/
|
|
579
|
+
export function listBlocks(c, options) {
|
|
580
|
+
const { chainId, limit, order } = options;
|
|
581
|
+
const store = c.get('store');
|
|
582
|
+
const tidx = c.get('getTidx')(chainId);
|
|
583
|
+
const sortDirection = order === 'asc' ? 'ASC' : 'DESC';
|
|
584
|
+
// Single-column keyset cursor on `num` — block timestamps aren't strictly
|
|
585
|
+
// monotonic on every chain, so the block number alone is the stable order.
|
|
586
|
+
const cursor = options.cursor ? Cursor.decode(options.cursor, ['int']) : undefined;
|
|
587
|
+
// Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
|
|
588
|
+
// the head page, so it shares the head's SQL and cache entries; deeper pages
|
|
589
|
+
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
590
|
+
const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
|
|
591
|
+
return Timing.time(c, 'blocks', () => Store.memoize(async () => {
|
|
592
|
+
const filters = [];
|
|
593
|
+
if (cursor !== undefined)
|
|
594
|
+
filters.push(Cursor.keyset([{ literal: Cursor.literal(cursor[0], 'int'), name: 'num', order }]));
|
|
595
|
+
const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
|
|
596
|
+
const blocks = await tidx.fetch({
|
|
597
|
+
chainId,
|
|
598
|
+
query: `
|
|
599
|
+
SELECT hash, num, timestamp
|
|
600
|
+
FROM blocks
|
|
601
|
+
${where}
|
|
602
|
+
ORDER BY num ${sortDirection}
|
|
603
|
+
LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
|
|
604
|
+
`,
|
|
605
|
+
});
|
|
606
|
+
const page = Cursor.paginate({
|
|
607
|
+
rows: blocks.rows,
|
|
608
|
+
limit,
|
|
609
|
+
key: (row) => {
|
|
610
|
+
const block = Value.toNumber(row['num']);
|
|
611
|
+
return block !== undefined ? [block] : undefined;
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
// Pull transaction counts in a single follow-up query scoped to the
|
|
615
|
+
// page's block numbers so empty blocks correctly report `0`.
|
|
616
|
+
const numbers = [];
|
|
617
|
+
for (const row of page.rows) {
|
|
618
|
+
const block = Value.toNumber(row['num']);
|
|
619
|
+
if (block !== undefined)
|
|
620
|
+
numbers.push(block);
|
|
621
|
+
}
|
|
622
|
+
const countsByBlock = new Map();
|
|
623
|
+
if (numbers.length > 0) {
|
|
624
|
+
const counts = await tidx.fetch({
|
|
625
|
+
chainId,
|
|
626
|
+
engine: 'clickhouse',
|
|
627
|
+
query: `
|
|
628
|
+
SELECT block_num, count() AS transaction_count
|
|
629
|
+
FROM txs
|
|
630
|
+
WHERE block_num IN (${numbers.join(', ')})
|
|
631
|
+
GROUP BY block_num
|
|
632
|
+
`,
|
|
633
|
+
});
|
|
634
|
+
for (const row of counts.rows) {
|
|
635
|
+
const block = Value.toNumber(row['block_num']);
|
|
636
|
+
const count = Value.toNumber(row['transaction_count']);
|
|
637
|
+
if (block !== undefined && count !== undefined)
|
|
638
|
+
countsByBlock.set(block, count);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const data = [];
|
|
642
|
+
for (const row of page.rows) {
|
|
643
|
+
const hash = Value.toText(row['hash'])?.toLowerCase();
|
|
644
|
+
const number = Value.toNumber(row['num']);
|
|
645
|
+
const timestamp = Value.toIsoDateTime(row['timestamp']);
|
|
646
|
+
if (hash === undefined || number === undefined || timestamp === undefined)
|
|
647
|
+
continue;
|
|
648
|
+
data.push({
|
|
649
|
+
hash,
|
|
650
|
+
id: hash,
|
|
651
|
+
number,
|
|
652
|
+
timestamp,
|
|
653
|
+
transactionCount: countsByBlock.get(number) ?? 0,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
return { data, nextCursor: page.nextCursor };
|
|
657
|
+
}, {
|
|
658
|
+
key: `blocks:v1:${chainId}:${order}:${cursor ? `cursor:${cursor[0]}` : offset !== undefined ? `page:${options.page}` : 'head'}:${limit}`,
|
|
659
|
+
store,
|
|
660
|
+
ttl: Ttl.seconds(15),
|
|
661
|
+
}));
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Humanizes a JSON-RPC block: converts hex quantities to decimal strings, hex
|
|
665
|
+
* block fields to numbers, and timestamps to ISO 8601, while preserving the
|
|
666
|
+
* verbatim payload under `meta.rpc`. Transactions are surfaced as a count;
|
|
667
|
+
* full transaction objects live on `GET /transactions` (equal `blockNumber.*` bounds).
|
|
668
|
+
*/
|
|
669
|
+
function humanizeBlock(rpc) {
|
|
670
|
+
// A non-pending block always has `number`, `hash`, and `timestamp`. The
|
|
671
|
+
// pending case is uninteresting for this surface (the latest endpoint asks
|
|
672
|
+
// for `'latest'`, which is mined), so these are treated as required after
|
|
673
|
+
// the upstream call returns a non-null result.
|
|
674
|
+
return {
|
|
675
|
+
baseFeePerGas: Value.fromHex(rpc.baseFeePerGas) ?? null,
|
|
676
|
+
gasLimit: Value.hexToNumber(rpc.gasLimit) ?? 0,
|
|
677
|
+
gasUsed: Value.hexToNumber(rpc.gasUsed) ?? 0,
|
|
678
|
+
hash: rpc.hash,
|
|
679
|
+
id: rpc.hash,
|
|
680
|
+
meta: { rpc },
|
|
681
|
+
miner: rpc.miner,
|
|
682
|
+
number: Value.hexToNumber(rpc.number) ?? 0,
|
|
683
|
+
parentHash: rpc.parentHash,
|
|
684
|
+
size: Value.hexToNumber(rpc.size) ?? 0,
|
|
685
|
+
timestamp: Value.hexSecondsToIso(rpc.timestamp) ?? new Date(0).toISOString(),
|
|
686
|
+
transactionCount: rpc.transactions.length,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
//# sourceMappingURL=blocks.js.map
|