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,302 @@
|
|
|
1
|
+
import type * as Viem from '../internal/Viem.js'
|
|
2
|
+
import * as TestApp from '../../test/App.js'
|
|
3
|
+
import * as Runtime from '../../test/runtime.js'
|
|
4
|
+
|
|
5
|
+
const runtime = Runtime.get()
|
|
6
|
+
|
|
7
|
+
describe('POST /rpc', () => {
|
|
8
|
+
test('routes by principal: a resolver sees the request principal and routes accordingly', async () => {
|
|
9
|
+
const fetch = mockRpc()
|
|
10
|
+
// A resolver that routes API-key principals to the private upstream and
|
|
11
|
+
// everything else (here, the keyless `auth: false` caller) to the public one.
|
|
12
|
+
const rpc = ({ principal }: Viem.resolve.Context) =>
|
|
13
|
+
principal?.type === 'api_key'
|
|
14
|
+
? { basicAuth: 'rpc-secret', url: 'https://internal.rpc.test/json-rpc' }
|
|
15
|
+
: { url: 'https://public.rpc.test/json-rpc' }
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Auth disabled ⇒ no API-key principal ⇒ public RPC, no basic auth.
|
|
19
|
+
const publicApp = TestApp.create({ auth: false, rpc })
|
|
20
|
+
await publicApp.fetch(
|
|
21
|
+
new Request('http://tempo-api.test/rpc', {
|
|
22
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
23
|
+
method: 'POST',
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Valid API key ⇒ api_key principal ⇒ private profile: internal RPC + basic auth.
|
|
28
|
+
const authedApp = TestApp.create({ rpc })
|
|
29
|
+
await authedApp.fetch(
|
|
30
|
+
new Request('http://tempo-api.test/rpc', {
|
|
31
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
32
|
+
headers: { 'tempo-api-key': TestApp.key.token },
|
|
33
|
+
method: 'POST',
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
expect(fetch.calls[0]?.url).toMatchInlineSnapshot(`"https://public.rpc.test/json-rpc"`)
|
|
38
|
+
expect(fetch.calls[0]?.headers['authorization']).toMatchInlineSnapshot(`null`)
|
|
39
|
+
expect(fetch.calls[1]?.url).toMatchInlineSnapshot(`"https://internal.rpc.test/json-rpc"`)
|
|
40
|
+
expect(fetch.calls[1]?.headers['authorization']).toMatchInlineSnapshot(
|
|
41
|
+
`"Basic cnBjLXNlY3JldA=="`,
|
|
42
|
+
)
|
|
43
|
+
} finally {
|
|
44
|
+
fetch.restore()
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('proxies JSON-RPC requests to the configured upstream RPC', async () => {
|
|
49
|
+
const fetch = mockRpc()
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const app = TestApp.create({
|
|
53
|
+
auth: false,
|
|
54
|
+
rpc: {
|
|
55
|
+
basicAuth: 'rpc-secret',
|
|
56
|
+
url: (chainId) => `https://${chainId}.rpc.test/json-rpc`,
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const response = await app.fetch(
|
|
61
|
+
new Request('http://tempo-api.test/rpc', {
|
|
62
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId', params: [] }),
|
|
63
|
+
headers: {
|
|
64
|
+
authorization: 'Bearer caller-secret',
|
|
65
|
+
'content-type': 'application/json',
|
|
66
|
+
cookie: 'session=secret',
|
|
67
|
+
'tempo-api-key': 'caller-key',
|
|
68
|
+
'x-client-trace': 'trace_test',
|
|
69
|
+
},
|
|
70
|
+
method: 'POST',
|
|
71
|
+
}),
|
|
72
|
+
)
|
|
73
|
+
const body = await response.json()
|
|
74
|
+
const call = fetch.calls[0]!
|
|
75
|
+
|
|
76
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
77
|
+
expect(response.headers.get('x-upstream')).toMatchInlineSnapshot(`"rpc"`)
|
|
78
|
+
expect(body).toMatchInlineSnapshot(`
|
|
79
|
+
{
|
|
80
|
+
"id": 1,
|
|
81
|
+
"jsonrpc": "2.0",
|
|
82
|
+
"result": "0xa5bf",
|
|
83
|
+
}
|
|
84
|
+
`)
|
|
85
|
+
expect(call.method).toMatchInlineSnapshot(`"POST"`)
|
|
86
|
+
expect(call.url).toBe(`https://${runtime.chainId}.rpc.test/json-rpc`)
|
|
87
|
+
expect(call.body).toMatchInlineSnapshot(
|
|
88
|
+
`"{"id":1,"jsonrpc":"2.0","method":"eth_chainId","params":[]}"`,
|
|
89
|
+
)
|
|
90
|
+
expect(call.headers['authorization']).toMatchInlineSnapshot(`"Basic cnBjLXNlY3JldA=="`)
|
|
91
|
+
expect(call.headers['cookie']).toMatchInlineSnapshot(`null`)
|
|
92
|
+
expect(call.headers['tempo-api-key']).toMatchInlineSnapshot(`null`)
|
|
93
|
+
expect(call.headers['x-client-trace']).toMatchInlineSnapshot(`"trace_test"`)
|
|
94
|
+
} finally {
|
|
95
|
+
fetch.restore()
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('routes anonymous requests to the public RPC without basic auth', async () => {
|
|
100
|
+
const fetch = mockRpc()
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const app = TestApp.create({
|
|
104
|
+
rpc: {
|
|
105
|
+
basicAuth: 'rpc-secret',
|
|
106
|
+
url: (chainId) => `https://${chainId}.rpc.test/json-rpc`,
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const response = await app.fetch(
|
|
111
|
+
new Request('http://tempo-api.test/rpc/testnet', {
|
|
112
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
113
|
+
method: 'POST',
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
118
|
+
expect(fetch.calls[0]?.url).toMatchInlineSnapshot(`"https://rpc.testnet.tempo.xyz/"`)
|
|
119
|
+
expect(fetch.calls[0]?.headers['authorization']).toMatchInlineSnapshot(`null`)
|
|
120
|
+
} finally {
|
|
121
|
+
fetch.restore()
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('uses the requested chain id for upstream URL resolution', async () => {
|
|
126
|
+
const fetch = mockRpc()
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const app = TestApp.create({
|
|
130
|
+
auth: false,
|
|
131
|
+
rpc: { url: (chainId) => `https://${chainId}.rpc.test` },
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const response = await app.fetch(
|
|
135
|
+
new Request('http://tempo-api.test/rpc/4217', {
|
|
136
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
137
|
+
method: 'POST',
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
142
|
+
expect(fetch.calls[0]?.url).toMatchInlineSnapshot(`"https://4217.rpc.test/"`)
|
|
143
|
+
} finally {
|
|
144
|
+
fetch.restore()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('uses mainnet for the mainnet shortcut', async () => {
|
|
149
|
+
const fetch = mockRpc()
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const app = TestApp.create({
|
|
153
|
+
auth: false,
|
|
154
|
+
rpc: { url: (chainId) => `https://${chainId}.rpc.test` },
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const response = await app.fetch(
|
|
158
|
+
new Request('http://tempo-api.test/rpc/mainnet', {
|
|
159
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
160
|
+
method: 'POST',
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
165
|
+
expect(fetch.calls[0]?.url).toMatchInlineSnapshot(`"https://4217.rpc.test/"`)
|
|
166
|
+
} finally {
|
|
167
|
+
fetch.restore()
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('uses testnet for the testnet shortcut', async () => {
|
|
172
|
+
const fetch = mockRpc()
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const app = TestApp.create({
|
|
176
|
+
auth: false,
|
|
177
|
+
rpc: { url: (chainId) => `https://${chainId}.rpc.test` },
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const response = await app.fetch(
|
|
181
|
+
new Request('http://tempo-api.test/rpc/testnet', {
|
|
182
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
183
|
+
method: 'POST',
|
|
184
|
+
}),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
188
|
+
expect(fetch.calls[0]?.url).toMatchInlineSnapshot(`"https://42431.rpc.test/"`)
|
|
189
|
+
} finally {
|
|
190
|
+
fetch.restore()
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('rejects invalid chain ids before proxying upstream', async () => {
|
|
195
|
+
const fetch = mockRpc()
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const app = TestApp.create({ auth: false })
|
|
199
|
+
const response = await app.fetch(
|
|
200
|
+
new Request('http://tempo-api.test/rpc/0', {
|
|
201
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
202
|
+
method: 'POST',
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
const body = (await response.json()) as Record<string, unknown>
|
|
206
|
+
const { requestId, ...stable } = body
|
|
207
|
+
|
|
208
|
+
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
209
|
+
expect(typeof requestId).toMatchInlineSnapshot(`"string"`)
|
|
210
|
+
expect(stable).toMatchInlineSnapshot(`
|
|
211
|
+
{
|
|
212
|
+
"error": {
|
|
213
|
+
"code": "chain_id_invalid",
|
|
214
|
+
"message": "Invalid chain id",
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
`)
|
|
218
|
+
expect(fetch.calls).toMatchInlineSnapshot(`[]`)
|
|
219
|
+
} finally {
|
|
220
|
+
fetch.restore()
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('requires the read scope for API keys', async () => {
|
|
225
|
+
const fetch = mockRpc()
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const app = TestApp.create({
|
|
229
|
+
auth: {
|
|
230
|
+
source: TestApp.source({
|
|
231
|
+
keys: [
|
|
232
|
+
{
|
|
233
|
+
id: 'key_indexer_only',
|
|
234
|
+
orgId: 'org_test',
|
|
235
|
+
scopes: ['indexer:query'],
|
|
236
|
+
token: 'secret_indexer_key',
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
}),
|
|
240
|
+
},
|
|
241
|
+
})
|
|
242
|
+
const response = await app.fetch(
|
|
243
|
+
new Request('http://tempo-api.test/rpc', {
|
|
244
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
245
|
+
headers: { 'tempo-api-key': 'secret_indexer_key' },
|
|
246
|
+
method: 'POST',
|
|
247
|
+
}),
|
|
248
|
+
)
|
|
249
|
+
const body = (await response.json()) as Record<string, unknown>
|
|
250
|
+
const { requestId, ...stable } = body
|
|
251
|
+
|
|
252
|
+
expect(response.status).toMatchInlineSnapshot(`403`)
|
|
253
|
+
expect(typeof requestId).toMatchInlineSnapshot(`"string"`)
|
|
254
|
+
expect(stable).toMatchInlineSnapshot(`
|
|
255
|
+
{
|
|
256
|
+
"error": {
|
|
257
|
+
"code": "api_key_forbidden",
|
|
258
|
+
"message": "API key missing required scope",
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
`)
|
|
262
|
+
expect(fetch.calls).toMatchInlineSnapshot(`[]`)
|
|
263
|
+
} finally {
|
|
264
|
+
fetch.restore()
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
function mockRpc() {
|
|
270
|
+
const original = globalThis.fetch
|
|
271
|
+
const calls: {
|
|
272
|
+
body: string
|
|
273
|
+
headers: Record<string, string | null>
|
|
274
|
+
method: string
|
|
275
|
+
url: string
|
|
276
|
+
}[] = []
|
|
277
|
+
const spy = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
|
|
278
|
+
const request = input instanceof Request ? input : new Request(input, init)
|
|
279
|
+
calls.push({
|
|
280
|
+
body: await request.text(),
|
|
281
|
+
headers: {
|
|
282
|
+
authorization: request.headers.get('authorization'),
|
|
283
|
+
cookie: request.headers.get('cookie'),
|
|
284
|
+
'tempo-api-key': request.headers.get('tempo-api-key'),
|
|
285
|
+
'x-client-trace': request.headers.get('x-client-trace'),
|
|
286
|
+
},
|
|
287
|
+
method: request.method,
|
|
288
|
+
url: request.url,
|
|
289
|
+
})
|
|
290
|
+
return new Response(JSON.stringify({ id: 1, jsonrpc: '2.0', result: '0xa5bf' }), {
|
|
291
|
+
headers: { 'content-type': 'application/json', 'x-upstream': 'rpc' },
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
calls,
|
|
297
|
+
restore() {
|
|
298
|
+
spy.mockRestore()
|
|
299
|
+
globalThis.fetch = original
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
}
|
package/src/apps/rpc.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Hono, type Context, type TypedResponse } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import type * as App from '../App.js'
|
|
5
|
+
import * as Auth from '../internal/Auth.js'
|
|
6
|
+
import * as OpenApi from '../internal/OpenApi.js'
|
|
7
|
+
import * as Response from '../internal/Response.js'
|
|
8
|
+
import * as Schema from '../internal/Schema.js'
|
|
9
|
+
import * as Viem from '../internal/Viem.js'
|
|
10
|
+
|
|
11
|
+
// Request headers that must not be forwarded upstream: hop-by-hop headers, the
|
|
12
|
+
// inbound host, and any caller auth so the consumer's Tempo API credentials never
|
|
13
|
+
// reach the RPC service (the proxy injects its own RPC basic auth instead).
|
|
14
|
+
const strippedHeaders = new Set([
|
|
15
|
+
'authorization',
|
|
16
|
+
'connection',
|
|
17
|
+
'cookie',
|
|
18
|
+
'host',
|
|
19
|
+
'keep-alive',
|
|
20
|
+
'proxy-authorization',
|
|
21
|
+
'te',
|
|
22
|
+
'trailer',
|
|
23
|
+
'transfer-encoding',
|
|
24
|
+
'upgrade',
|
|
25
|
+
'tempo-api-key',
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
/** Never reached; carries typed passthrough response variants into Hono client inference. */
|
|
29
|
+
const narrowPassthrough = false as boolean
|
|
30
|
+
|
|
31
|
+
/** Zod schemas owned by the raw RPC passthrough. */
|
|
32
|
+
export namespace schema {
|
|
33
|
+
/** JSON-RPC request identifier. */
|
|
34
|
+
export const Id = z
|
|
35
|
+
.union([z.string(), z.number(), z.null()])
|
|
36
|
+
.check(z.describe('Client-supplied JSON-RPC request id used to match responses to requests.'))
|
|
37
|
+
|
|
38
|
+
/** JSON-RPC error payload. */
|
|
39
|
+
export const Error = z
|
|
40
|
+
.object({
|
|
41
|
+
code: z
|
|
42
|
+
.number()
|
|
43
|
+
.check(
|
|
44
|
+
z.int(),
|
|
45
|
+
z.describe('Numeric JSON-RPC error code returned by the upstream RPC server.'),
|
|
46
|
+
),
|
|
47
|
+
data: z
|
|
48
|
+
.optional(z.unknown())
|
|
49
|
+
.check(z.describe('Optional extra error details returned by the upstream RPC server.')),
|
|
50
|
+
message: z.string().check(z.describe('Human-readable JSON-RPC error message.')),
|
|
51
|
+
})
|
|
52
|
+
.check(z.describe('JSON-RPC error object returned when a method fails.'))
|
|
53
|
+
|
|
54
|
+
/** JSON-RPC request payload. */
|
|
55
|
+
export const Request = z
|
|
56
|
+
.object({
|
|
57
|
+
id: z
|
|
58
|
+
.optional(Id)
|
|
59
|
+
.check(
|
|
60
|
+
z.describe('Client-supplied JSON-RPC request id used to match responses to requests.'),
|
|
61
|
+
),
|
|
62
|
+
jsonrpc: z.literal('2.0').check(z.describe('JSON-RPC protocol version; Tempo uses `2.0`.')),
|
|
63
|
+
method: z
|
|
64
|
+
.string()
|
|
65
|
+
.check(z.describe('Ethereum JSON-RPC method to call, such as `eth_blockNumber`.')),
|
|
66
|
+
params: z
|
|
67
|
+
.optional(z.unknown())
|
|
68
|
+
.check(z.describe('Parameters for the JSON-RPC method, usually an array.')),
|
|
69
|
+
})
|
|
70
|
+
.check(z.describe('One Ethereum JSON-RPC request to send to Tempo.'))
|
|
71
|
+
|
|
72
|
+
/** JSON-RPC response payload. */
|
|
73
|
+
export const Response = z
|
|
74
|
+
.object({
|
|
75
|
+
error: z
|
|
76
|
+
.optional(Error)
|
|
77
|
+
.check(z.describe('JSON-RPC error object returned when a method fails.')),
|
|
78
|
+
id: z
|
|
79
|
+
.optional(Id)
|
|
80
|
+
.check(
|
|
81
|
+
z.describe('Client-supplied JSON-RPC request id used to match responses to requests.'),
|
|
82
|
+
),
|
|
83
|
+
jsonrpc: z.literal('2.0').check(z.describe('JSON-RPC protocol version; Tempo uses `2.0`.')),
|
|
84
|
+
result: z
|
|
85
|
+
.optional(z.unknown())
|
|
86
|
+
.check(z.describe('Result returned by the upstream RPC method.')),
|
|
87
|
+
})
|
|
88
|
+
.check(z.describe('One JSON-RPC response returned by Tempo.'))
|
|
89
|
+
|
|
90
|
+
/** JSON-RPC request or batch request payload. */
|
|
91
|
+
export const RequestBody = z
|
|
92
|
+
.union([Request, z.array(Request)])
|
|
93
|
+
.check(z.describe('A single JSON-RPC request or an array of requests for batch calls.'))
|
|
94
|
+
|
|
95
|
+
/** JSON-RPC response or batch response payload. */
|
|
96
|
+
export const ResponseBody = z
|
|
97
|
+
.union([Response, z.array(Response)])
|
|
98
|
+
.check(z.describe('A single JSON-RPC response or an array of responses for batch calls.'))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Creates the raw Tempo RPC passthrough handler. */
|
|
102
|
+
export function rpc() {
|
|
103
|
+
return new Hono<App.Environment>().post(
|
|
104
|
+
'/:chain{mainnet|testnet|[0-9]+}?',
|
|
105
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, public: true }),
|
|
106
|
+
OpenApi.describeRoute({
|
|
107
|
+
description:
|
|
108
|
+
'Sends a single or batch Ethereum JSON-RPC request directly to Tempo. Use `/rpc/testnet`, `/rpc/mainnet`, or `/rpc/:chain` to choose a chain. This passthrough is not part of the stable API contract and is supported on a best-effort basis only. It has no compatibility, latency, availability, or data-freshness guarantees, and breaking changes may happen with limited notice.',
|
|
109
|
+
operationId: 'rpcRequest',
|
|
110
|
+
parameters: [
|
|
111
|
+
{
|
|
112
|
+
description:
|
|
113
|
+
'Optional chain selector. Use the alias `mainnet` or `testnet`, or a numeric chain id (mainnet is `4217`).',
|
|
114
|
+
example: 'testnet',
|
|
115
|
+
in: 'path' as const,
|
|
116
|
+
name: 'chain',
|
|
117
|
+
required: false,
|
|
118
|
+
schema: { pattern: '^(mainnet|testnet|[0-9]+)$', type: 'string' as const },
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
requestBody: {
|
|
122
|
+
content: {
|
|
123
|
+
'application/json': {
|
|
124
|
+
example: { jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
required: true,
|
|
128
|
+
},
|
|
129
|
+
responses: OpenApi.responses({
|
|
130
|
+
errors: {
|
|
131
|
+
400: {
|
|
132
|
+
description: 'The chain id is invalid or this API deployment does not support it.',
|
|
133
|
+
codes: ['chain_id_invalid', 'chain_id_unsupported'],
|
|
134
|
+
},
|
|
135
|
+
502: 'The upstream Tempo RPC endpoint could not complete the request.',
|
|
136
|
+
},
|
|
137
|
+
success: {
|
|
138
|
+
description: 'JSON-RPC response returned by the upstream Tempo RPC endpoint.',
|
|
139
|
+
example: { jsonrpc: '2.0', id: 1, result: '0x10f2c' },
|
|
140
|
+
schema: schema.ResponseBody,
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
summary: 'Call JSON-RPC',
|
|
144
|
+
tags: ['RPC'],
|
|
145
|
+
}),
|
|
146
|
+
(c) => {
|
|
147
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
148
|
+
|
|
149
|
+
const chainId = resolveChainId(c)
|
|
150
|
+
if (!chainId)
|
|
151
|
+
return Response.error(c, {
|
|
152
|
+
code: 'chain_id_invalid',
|
|
153
|
+
message: 'Invalid chain id',
|
|
154
|
+
status: 400,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (narrowPassthrough)
|
|
158
|
+
return c.json(Response.validated(schema.ResponseBody, { jsonrpc: '2.0' }), 200)
|
|
159
|
+
return passthrough(c, chainId)
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function passthrough(
|
|
165
|
+
c: Context<App.Environment>,
|
|
166
|
+
chainId: z.output<typeof Schema.ChainId>,
|
|
167
|
+
): Promise<globalThis.Response & TypedResponse<never, never, never>> {
|
|
168
|
+
const rpc = c.get('rpc')
|
|
169
|
+
const principal = Auth.getPrincipal(c)
|
|
170
|
+
const upstream_rpc = principal?.type === 'public' && typeof rpc !== 'function' ? undefined : rpc
|
|
171
|
+
// Anonymous callers use the built-in public RPC unless a resolver chooses a
|
|
172
|
+
// different public upstream.
|
|
173
|
+
const { basicAuth, url } = Viem.resolve(upstream_rpc, { chainId, principal })
|
|
174
|
+
// The chain id is syntactically valid but this deployment has no RPC upstream
|
|
175
|
+
// configured for it (no `rpc` config and no built-in default).
|
|
176
|
+
if (!url)
|
|
177
|
+
return Response.error(c, {
|
|
178
|
+
code: 'chain_id_unsupported',
|
|
179
|
+
message: 'Unsupported chain id',
|
|
180
|
+
status: 400,
|
|
181
|
+
}) as never
|
|
182
|
+
const upstream = new URL(url)
|
|
183
|
+
|
|
184
|
+
const headers = new Headers()
|
|
185
|
+
for (const [name, value] of c.req.raw.headers)
|
|
186
|
+
if (!strippedHeaders.has(name.toLowerCase())) headers.set(name, value)
|
|
187
|
+
if (basicAuth) headers.set('authorization', `Basic ${btoa(basicAuth)}`)
|
|
188
|
+
|
|
189
|
+
// `duplex` is required when streaming a request body, but is not yet in
|
|
190
|
+
// the DOM `RequestInit` type.
|
|
191
|
+
const init: RequestInit & { duplex?: 'half' } = {
|
|
192
|
+
headers,
|
|
193
|
+
method: c.req.method,
|
|
194
|
+
redirect: 'manual',
|
|
195
|
+
signal: AbortSignal.timeout(35_000),
|
|
196
|
+
}
|
|
197
|
+
if (c.req.raw.body) {
|
|
198
|
+
init.body = c.req.raw.body
|
|
199
|
+
init.duplex = 'half'
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const response = await fetch(new Request(upstream, init))
|
|
204
|
+
|
|
205
|
+
// Stream the upstream response back verbatim so JSON-RPC bodies, status
|
|
206
|
+
// codes, and headers survive exactly as clients expect.
|
|
207
|
+
return c.newResponse(response.body, response) as globalThis.Response &
|
|
208
|
+
TypedResponse<never, never, never>
|
|
209
|
+
} catch (cause) {
|
|
210
|
+
// Timeouts and network failures become the standard 502 envelope instead
|
|
211
|
+
// of an unhandled 500.
|
|
212
|
+
return Response.upstream(c, cause) as never
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function resolveChainId(c: Context<App.Environment>) {
|
|
217
|
+
const parameter = c.req.param('chain')
|
|
218
|
+
if (parameter === undefined) return c.get('chainId')
|
|
219
|
+
|
|
220
|
+
const parsed = Schema.ChainId.safeParse(parameter)
|
|
221
|
+
return parsed.success ? parsed.data : undefined
|
|
222
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Store } from 'cadent'
|
|
2
|
+
|
|
3
|
+
import * as TestApp from '../../test/App.js'
|
|
4
|
+
import * as Runtime from '../../test/runtime.js'
|
|
5
|
+
import * as VerifiedTokens from '../internal/VerifiedTokens.js'
|
|
6
|
+
import * as Tokenlist from './tokenlist.js'
|
|
7
|
+
|
|
8
|
+
/** Reader key for the data routes. */
|
|
9
|
+
const reader = {
|
|
10
|
+
id: 'key_reader',
|
|
11
|
+
orgId: 'org_test',
|
|
12
|
+
scopes: ['data:read'],
|
|
13
|
+
token: 'secret_reader',
|
|
14
|
+
} satisfies TestApp.source.Key
|
|
15
|
+
|
|
16
|
+
/** A known list published into the store before exercising the read endpoint. */
|
|
17
|
+
const seedList = [
|
|
18
|
+
{ address: '0x20c0000000000000000000000000000000001001', currency: 'USD', decimals: 6, logoUri: 'https://example.com/susd.svg', name: 'Seed USD', symbol: 'sUSD' }, // prettier-ignore
|
|
19
|
+
{ address: '0x20c0000000000000000000000000000000001002', currency: 'EUR', decimals: 2, name: 'Seed EUR', symbol: 'sEUR' }, // prettier-ignore
|
|
20
|
+
] as const
|
|
21
|
+
const chainId = Runtime.get().chainId
|
|
22
|
+
|
|
23
|
+
/** RequestInit carrying a bearer token for the given key. */
|
|
24
|
+
function as(key: { token: string }) {
|
|
25
|
+
return { headers: { authorization: `Bearer ${key.token}` } } as const
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Client whose verified-token store holds a published {@link seedList} snapshot. */
|
|
29
|
+
async function seeded() {
|
|
30
|
+
const store = Store.memory()
|
|
31
|
+
await VerifiedTokens.replace(store, chainId, seedList)
|
|
32
|
+
return TestApp.client({
|
|
33
|
+
auth: { source: TestApp.source({ keys: [reader] }) },
|
|
34
|
+
verifiedTokens: { store },
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('GET /tokenlist', () => {
|
|
39
|
+
test('serves verified tokens in the Uniswap token-list format', async () => {
|
|
40
|
+
const response = await (await seeded()).v1.tokenlist.$get({ query: {} }, as(reader))
|
|
41
|
+
expect(response.status).toBe(200)
|
|
42
|
+
const body = await TestApp.json(response, Tokenlist.schema.getTokenList.Response)
|
|
43
|
+
|
|
44
|
+
expect(body.name).toMatch(/^Tempo/)
|
|
45
|
+
expect(body.version).toEqual({ major: 1, minor: 0, patch: 2 })
|
|
46
|
+
|
|
47
|
+
// Token entries carry the standard fields, scoped to the requested chain,
|
|
48
|
+
// with no `extensions`; a curated `logoUri` is preserved when present.
|
|
49
|
+
expect(body.tokens).toEqual([
|
|
50
|
+
{
|
|
51
|
+
address: '0x20c0000000000000000000000000000000001001',
|
|
52
|
+
chainId,
|
|
53
|
+
decimals: 6,
|
|
54
|
+
logoURI: 'https://example.com/susd.svg',
|
|
55
|
+
name: 'Seed USD',
|
|
56
|
+
symbol: 'sUSD',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
address: '0x20c0000000000000000000000000000000001002',
|
|
60
|
+
chainId,
|
|
61
|
+
decimals: 2,
|
|
62
|
+
name: 'Seed EUR',
|
|
63
|
+
symbol: 'sEUR',
|
|
64
|
+
},
|
|
65
|
+
])
|
|
66
|
+
expect(body.tokens[0]).not.toHaveProperty('extensions')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('serves an empty list for a chain with no verified tokens', async () => {
|
|
70
|
+
const response = await (
|
|
71
|
+
await seeded()
|
|
72
|
+
).v1.tokenlist.$get({ query: { chainId: '4217' } }, as(reader))
|
|
73
|
+
expect(response.status).toBe(200)
|
|
74
|
+
const body = await TestApp.json(response, Tokenlist.schema.getTokenList.Response)
|
|
75
|
+
|
|
76
|
+
expect(body.name).toBe('Tempo Mainnet')
|
|
77
|
+
expect(body.tokens).toEqual([])
|
|
78
|
+
expect(body.version.patch).toBe(0)
|
|
79
|
+
})
|
|
80
|
+
})
|