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,533 @@
|
|
|
1
|
+
import { Hono, type Context } from 'hono'
|
|
2
|
+
import { Hex, Value as core_Value } from 'ox'
|
|
3
|
+
import * as z from 'zod/mini'
|
|
4
|
+
|
|
5
|
+
import type * as App from '../App.js'
|
|
6
|
+
import * as Auth from '../internal/Auth.js'
|
|
7
|
+
import * as Cache from '../internal/Cache.js'
|
|
8
|
+
import * as Cursor from '../internal/Cursor.js'
|
|
9
|
+
import * as OpenApi from '../internal/OpenApi.js'
|
|
10
|
+
import * as Response from '../internal/Response.js'
|
|
11
|
+
import * as Schema from '../internal/Schema.js'
|
|
12
|
+
import * as Timing from '../internal/Timing.js'
|
|
13
|
+
import * as Value from '../internal/Value.js'
|
|
14
|
+
import * as VerifiedTokens from '../internal/VerifiedTokens.js'
|
|
15
|
+
import * as FeeAmm from './fee-amm.js'
|
|
16
|
+
import * as Tokens from './tokens.js'
|
|
17
|
+
|
|
18
|
+
// TIP-20 tokens are minted by the TIP-20 factory and live at deterministic
|
|
19
|
+
// `0x20c0…`-prefixed addresses (e.g. pathUSD `0x20c0…0000`). The indexer's
|
|
20
|
+
// `token_balances_snapshot` view, by contrast, records balances for every
|
|
21
|
+
// Transfer-emitting contract, so balance queries filter on this prefix to keep
|
|
22
|
+
// non-TIP-20 tokens (which have no resolvable TIP-20 metadata) out of results.
|
|
23
|
+
const tip20AddressPrefix = '0x20c0'
|
|
24
|
+
|
|
25
|
+
/** Zod schemas owned by the balance handlers. */
|
|
26
|
+
export namespace schema {
|
|
27
|
+
/** Schemas for the getAddressBalances operation. */
|
|
28
|
+
export namespace getAddressBalances {
|
|
29
|
+
/** Path parameters for address balance requests. */
|
|
30
|
+
export const Params = z
|
|
31
|
+
.object({
|
|
32
|
+
address: Schema.Address.check(
|
|
33
|
+
z.describe('The account address whose token balances you want to list.'),
|
|
34
|
+
),
|
|
35
|
+
})
|
|
36
|
+
.check(z.describe('Path parameters for listing an account’s token balances.'))
|
|
37
|
+
|
|
38
|
+
/** Query parameters for address balance requests. */
|
|
39
|
+
export const Query = z
|
|
40
|
+
.strictObject({
|
|
41
|
+
chainId: Schema.ChainIdQuery,
|
|
42
|
+
currency: z
|
|
43
|
+
.optional(z.string())
|
|
44
|
+
.check(
|
|
45
|
+
z.meta({ examples: VerifiedTokens.currencies }),
|
|
46
|
+
z.describe(
|
|
47
|
+
'Only include verified tokens denominated in this currency (e.g. `USD`). ' +
|
|
48
|
+
'Case-insensitive. Implies `verified=true` because the balances ' +
|
|
49
|
+
'snapshot does not store currency for unverified tokens. ' +
|
|
50
|
+
'Unknown currency strings are accepted but match no rows.',
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
cursor: Schema.Cursor,
|
|
54
|
+
feeEligible: z
|
|
55
|
+
.optional(Schema.booleanQuery())
|
|
56
|
+
.check(
|
|
57
|
+
z.describe(
|
|
58
|
+
'When `true`, include only tokens eligible to pay transaction fees on Tempo: ' +
|
|
59
|
+
'verified tokens that have a Fee AMM pool, plus the default fee token ' +
|
|
60
|
+
'(`pathUSD`). Use this to avoid a second `/fee-amm/pools` query when you ' +
|
|
61
|
+
'only want fee-payable holdings.',
|
|
62
|
+
),
|
|
63
|
+
z.meta({ examples: [true] }),
|
|
64
|
+
),
|
|
65
|
+
include: Schema.totalCountInclude,
|
|
66
|
+
limit: Schema.Limit,
|
|
67
|
+
page: Schema.Page,
|
|
68
|
+
verified: z
|
|
69
|
+
.optional(Schema.booleanQuery())
|
|
70
|
+
.check(
|
|
71
|
+
z.describe(
|
|
72
|
+
'When `true`, include only tokens from Tempo’s curated verified token list.',
|
|
73
|
+
),
|
|
74
|
+
z.meta({ examples: [true] }),
|
|
75
|
+
),
|
|
76
|
+
})
|
|
77
|
+
.check(
|
|
78
|
+
...Schema.pageChecks(),
|
|
79
|
+
z.describe('Query parameters for listing an account’s token balances.'),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A TIP-20 token balance held by an address: the held `amount` in the
|
|
84
|
+
* token's base unit, its `formatted` value, and the trimmed `token`
|
|
85
|
+
* reference inlined per row. Rows whose token metadata cannot be resolved
|
|
86
|
+
* are omitted.
|
|
87
|
+
*/
|
|
88
|
+
export const Balance = Schema.describe(
|
|
89
|
+
z.object({
|
|
90
|
+
amount: z
|
|
91
|
+
.string()
|
|
92
|
+
.check(
|
|
93
|
+
z.regex(/^\d+$/),
|
|
94
|
+
z.describe(
|
|
95
|
+
'A non-negative whole number, given as a decimal string so very large token amounts keep full precision. Expressed in the smallest unit of the token.',
|
|
96
|
+
),
|
|
97
|
+
z.meta({ examples: ['1500000'] }),
|
|
98
|
+
),
|
|
99
|
+
formatted: z
|
|
100
|
+
.string()
|
|
101
|
+
.check(
|
|
102
|
+
z.describe(
|
|
103
|
+
'The same balance in human-readable decimal form, using the token’s `decimals`.',
|
|
104
|
+
),
|
|
105
|
+
z.meta({ examples: ['1.5'] }),
|
|
106
|
+
),
|
|
107
|
+
id: z
|
|
108
|
+
.string()
|
|
109
|
+
.check(
|
|
110
|
+
z.describe(
|
|
111
|
+
'A stable resource ID for this balance, equal to the token contract address.',
|
|
112
|
+
),
|
|
113
|
+
z.meta({ examples: ['0x20c0000000000000000000008f5425160ebe5525'] }),
|
|
114
|
+
),
|
|
115
|
+
feeEligible: z
|
|
116
|
+
.optional(z.boolean())
|
|
117
|
+
.check(
|
|
118
|
+
z.describe(
|
|
119
|
+
'Whether this token can be used to pay transaction fees on Tempo: `true` when ' +
|
|
120
|
+
'the token is verified and has a Fee AMM pool, or is the default fee token ' +
|
|
121
|
+
'(`pathUSD`). Lets you identify fee-payable holdings without a separate ' +
|
|
122
|
+
'`/fee-amm/pools` query. Omitted (best-effort) when the fee-token lookup ' +
|
|
123
|
+
'is unavailable.',
|
|
124
|
+
),
|
|
125
|
+
z.meta({ examples: [true] }),
|
|
126
|
+
),
|
|
127
|
+
token: Tokens.schema.Token,
|
|
128
|
+
}),
|
|
129
|
+
'One TIP-20 token balance held by this account, with both raw and human-readable amounts.',
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
/** Page of TIP-20 token balances held by an address, ordered by amount descending. */
|
|
133
|
+
export const Response = z
|
|
134
|
+
.object({
|
|
135
|
+
data: z
|
|
136
|
+
.array(Balance)
|
|
137
|
+
.check(z.describe('The balances in this page, ordered from largest to smallest amount.')),
|
|
138
|
+
meta: z
|
|
139
|
+
.optional(Schema.CountMeta)
|
|
140
|
+
.check(z.describe('Page-level resources you requested with `include`.')),
|
|
141
|
+
nextCursor: Schema.NextCursor,
|
|
142
|
+
})
|
|
143
|
+
.check(z.describe('A page of TIP-20 token balances held by this account.'))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates address-scoped balance handlers, mounted under the `/addresses`
|
|
149
|
+
* composer. Exposes `GET /:address/balances`, the held-balance page.
|
|
150
|
+
*/
|
|
151
|
+
export function addresses() {
|
|
152
|
+
return new Hono<App.Environment>().get(
|
|
153
|
+
'/:address/balances',
|
|
154
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
|
|
155
|
+
OpenApi.validate('param', schema.getAddressBalances.Params, {
|
|
156
|
+
code: 'address_invalid',
|
|
157
|
+
message: 'Invalid account address',
|
|
158
|
+
}),
|
|
159
|
+
OpenApi.validate('query', schema.getAddressBalances.Query, {
|
|
160
|
+
code: 'query_invalid',
|
|
161
|
+
message: 'Invalid query parameters',
|
|
162
|
+
}),
|
|
163
|
+
OpenApi.describeRoute({
|
|
164
|
+
description:
|
|
165
|
+
'Lists how much of each TIP-20 token an account holds, ordered from largest to smallest balance. Amounts are returned in raw base units and human-readable form.',
|
|
166
|
+
operationId: 'getAddressBalances',
|
|
167
|
+
responses: OpenApi.responses({
|
|
168
|
+
errors: { 502: 'The upstream indexer could not serve this request.' },
|
|
169
|
+
success: {
|
|
170
|
+
description: 'A page of token balances for this account.',
|
|
171
|
+
schema: schema.getAddressBalances.Response,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
summary: 'List address balances',
|
|
175
|
+
tags: ['Balances'],
|
|
176
|
+
}),
|
|
177
|
+
Cache.response<App.Environment>({
|
|
178
|
+
cacheControl: Cache.policies.noStore,
|
|
179
|
+
name: 'tempo-api:addresses:v1',
|
|
180
|
+
key: (c) => Cache.urlKey(c, schema.getAddressBalances.Query),
|
|
181
|
+
}),
|
|
182
|
+
async (c) => {
|
|
183
|
+
if (Auth.narrowAccess) return Auth.paidAccessError(c)
|
|
184
|
+
if (OpenApi.narrowValidation)
|
|
185
|
+
return OpenApi.validationError(c, {
|
|
186
|
+
code: 'address_invalid',
|
|
187
|
+
message: 'Invalid request parameters',
|
|
188
|
+
})
|
|
189
|
+
if (OpenApi.narrowValidation)
|
|
190
|
+
return OpenApi.validationError(c, {
|
|
191
|
+
code: 'query_invalid',
|
|
192
|
+
message: 'Invalid request parameters',
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const { address } = c.req.valid('param')
|
|
196
|
+
const query = c.req.valid('query')
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const chainId = query.chainId ?? c.get('chainId')
|
|
200
|
+
// `currency` only makes sense against verified tokens, so it implies
|
|
201
|
+
// `verified=true` from the caller's perspective.
|
|
202
|
+
const verified = query.verified || query.currency !== undefined
|
|
203
|
+
const feeEligible = query.feeEligible ?? false
|
|
204
|
+
|
|
205
|
+
// The fee-token set powers both the per-row `feeEligible` flag (always)
|
|
206
|
+
// and the optional `feeEligible=true` page restriction. It is memoized
|
|
207
|
+
// per chain (a near-constant), so one read feeds the page, the count,
|
|
208
|
+
// and the row enrichment. It is a best-effort enrichment: a lookup
|
|
209
|
+
// failure omits `feeEligible` rather than failing the page — unless the
|
|
210
|
+
// caller explicitly asked to filter by `feeEligible`, where the filter
|
|
211
|
+
// can't be honored, so the failure surfaces as the declared `502`.
|
|
212
|
+
const feeTokens = await FeeAmm.feeTokenSet(c, chainId).catch((cause) => {
|
|
213
|
+
if (feeEligible) throw cause
|
|
214
|
+
return undefined
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// `totalCount` (the number of matching holdings) is opt-in and shares
|
|
218
|
+
// the page's filters; run it concurrently with the page. Best-effort: a
|
|
219
|
+
// count failure omits `meta` rather than failing the page.
|
|
220
|
+
const countPromise = query.include.includes('totalCount')
|
|
221
|
+
? countAddressBalances(c, {
|
|
222
|
+
address,
|
|
223
|
+
chainId,
|
|
224
|
+
currency: query.currency,
|
|
225
|
+
feeEligible,
|
|
226
|
+
feeTokens,
|
|
227
|
+
verified,
|
|
228
|
+
}).catch(() => undefined)
|
|
229
|
+
: undefined
|
|
230
|
+
|
|
231
|
+
const balances = await getAddressBalances(c, {
|
|
232
|
+
address,
|
|
233
|
+
chainId,
|
|
234
|
+
currency: query.currency,
|
|
235
|
+
cursor: query.cursor,
|
|
236
|
+
feeEligible,
|
|
237
|
+
feeTokens,
|
|
238
|
+
limit: query.limit,
|
|
239
|
+
page: query.page,
|
|
240
|
+
verified,
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const meta = countPromise ? await countPromise : undefined
|
|
244
|
+
|
|
245
|
+
return c.json(
|
|
246
|
+
Response.validated(schema.getAddressBalances.Response, {
|
|
247
|
+
...balances,
|
|
248
|
+
...(meta ? { meta } : {}),
|
|
249
|
+
}),
|
|
250
|
+
200,
|
|
251
|
+
)
|
|
252
|
+
} catch (cause) {
|
|
253
|
+
return Response.upstream(c, cause)
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function getAddressBalances(
|
|
260
|
+
c: Context<App.Environment>,
|
|
261
|
+
options: getAddressBalances.Options,
|
|
262
|
+
) {
|
|
263
|
+
const { address, chainId, currency, feeEligible, feeTokens, limit, verified } = options
|
|
264
|
+
const tidx = c.get('getTidx')(chainId)
|
|
265
|
+
// Keyset pagination on `(balance, token)`: balance is descending and `token`
|
|
266
|
+
// is a stable tiebreaker for the many holdings sharing a balance. A malformed
|
|
267
|
+
// cursor falls back to the head page. Note: because `balance` is mutable, this
|
|
268
|
+
// is best-effort across balance changes (the inherent limit of ranking feeds).
|
|
269
|
+
const cursor = options.cursor ? Cursor.decode(options.cursor, ['uint', 'address']) : undefined
|
|
270
|
+
// Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
|
|
271
|
+
// the head page, so it shares the head's SQL and cache entries; deeper pages
|
|
272
|
+
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
273
|
+
const offset =
|
|
274
|
+
options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined
|
|
275
|
+
|
|
276
|
+
// Build the optional `token IN (…)` restriction before hitting TIDX. The
|
|
277
|
+
// verified/`currency` filter and the `feeEligible` filter each contribute a set
|
|
278
|
+
// of allowed addresses (`currency` matched case-insensitively so callers can
|
|
279
|
+
// type `usd`/`USD`/`Usd`); when both are present the page is their
|
|
280
|
+
// intersection. If any active filter rules every token out, return an empty
|
|
281
|
+
// page without a round trip. The snapshot is reused for the page enrichment
|
|
282
|
+
// below.
|
|
283
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId)
|
|
284
|
+
const allowed = allowedTokens({ currency, feeEligible, feeTokens, snapshot, verified })
|
|
285
|
+
const tokenFilter = allowed?.map((address) => `'${address}'`)
|
|
286
|
+
if (tokenFilter?.length === 0) return { data: [], nextCursor: null }
|
|
287
|
+
|
|
288
|
+
const holdings = await Timing.time(c, 'address_balances', async () => {
|
|
289
|
+
// A held token is one this address holds a positive amount of. Read the
|
|
290
|
+
// live `address_balances` view (`holder`-keyed: one row per
|
|
291
|
+
// `(holder, token, balance)`), which aggregates `address_holder_deltas`
|
|
292
|
+
// directly so balances reflect the indexer's chain head (no periodic
|
|
293
|
+
// snapshot refresh — see the indexer's `address_holder_deltas` MV). The
|
|
294
|
+
// `holder` equality prunes the view by its leading sort key, so this stays
|
|
295
|
+
// a bounded read per request. Fetch one extra row to detect `hasMore`
|
|
296
|
+
// without a separate count query. `startsWith` restricts results to TIP-20
|
|
297
|
+
// tokens (see `tip20AddressPrefix`) before pagination, so `hasMore`/page
|
|
298
|
+
// sizes stay correct and every returned token has resolvable metadata. The
|
|
299
|
+
// inline query is cast to `string` so TIDX treats it as a dynamic
|
|
300
|
+
// ClickHouse query that needs no event signature for the
|
|
301
|
+
// `address_balances` view.
|
|
302
|
+
//
|
|
303
|
+
// Let upstream failures propagate: this is a dedicated endpoint, so a
|
|
304
|
+
// failure should surface as the declared `502` rather than masquerade as
|
|
305
|
+
// an address holding zero tokens.
|
|
306
|
+
const keyset =
|
|
307
|
+
cursor !== undefined
|
|
308
|
+
? ` AND ${Cursor.keyset([
|
|
309
|
+
{ literal: Cursor.literal(cursor[0]!, 'uint'), name: 'balance', order: 'desc' },
|
|
310
|
+
{ literal: Cursor.literal(cursor[1]!, 'address'), name: 'token', order: 'asc' },
|
|
311
|
+
])}`
|
|
312
|
+
: ''
|
|
313
|
+
const tokenIn = tokenFilter ? ` AND token IN (${tokenFilter.join(', ')})` : ''
|
|
314
|
+
|
|
315
|
+
const result = await tidx.fetch({
|
|
316
|
+
chainId,
|
|
317
|
+
engine: 'clickhouse',
|
|
318
|
+
// `balance` is UInt256; SELECT it as a string so values > 2^53
|
|
319
|
+
// survive JSON without precision loss. The keyset cursor encodes the
|
|
320
|
+
// row's exact balance, so any rounding would re-include the cursor
|
|
321
|
+
// row on the next page. Alias is `balance_str` (not `balance`)
|
|
322
|
+
// because TIDX rejects expression aliases that shadow the source
|
|
323
|
+
// column (422).
|
|
324
|
+
query: `
|
|
325
|
+
SELECT token, toString(balance) AS balance_str
|
|
326
|
+
FROM address_balances
|
|
327
|
+
WHERE holder = '${address}' AND startsWith(token, '${tip20AddressPrefix}') AND balance > 0${tokenIn}${keyset}
|
|
328
|
+
ORDER BY balance DESC, token ASC
|
|
329
|
+
LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
|
|
330
|
+
` as string,
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// The next page anchors below the last fetched row's `(balance, token)`.
|
|
334
|
+
const page = Cursor.paginate({
|
|
335
|
+
rows: result.rows,
|
|
336
|
+
limit,
|
|
337
|
+
key: (row) => {
|
|
338
|
+
const balance = Value.toIntegerString(row['balance_str'])
|
|
339
|
+
const token = Schema.Address.safeParse(row['token'])
|
|
340
|
+
return balance !== undefined && token.success ? [balance, token.data] : undefined
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const data: { address: Hex.Hex; amount: string }[] = []
|
|
345
|
+
for (const row of page.rows) {
|
|
346
|
+
const token = Schema.Address.safeParse(row['token'])
|
|
347
|
+
const amount = Value.toIntegerString(row['balance_str'])
|
|
348
|
+
if (token.success && amount !== undefined) data.push({ address: token.data, amount })
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { data, nextCursor: page.nextCursor }
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
if (holdings.data.length === 0) return { data: [], nextCursor: holdings.nextCursor }
|
|
355
|
+
|
|
356
|
+
// Resolve token metadata for each balance. The endpoint's contract is a
|
|
357
|
+
// balance with its `token` reference inlined per row (and `formatted` needs
|
|
358
|
+
// the token `decimals`), so a row whose metadata cannot be resolved is
|
|
359
|
+
// dropped rather than returned with placeholders. Because the TIDX filter
|
|
360
|
+
// already restricts to TIP-20 (`0x20c0…`) tokens, resolution should succeed
|
|
361
|
+
// for nearly every row in practice.
|
|
362
|
+
//
|
|
363
|
+
// The verified `snapshot` fetched above is reused across the page instead of
|
|
364
|
+
// re-resolved per row. `createdAt` is not part of this endpoint's response,
|
|
365
|
+
// so it is never fetched here.
|
|
366
|
+
const resolved = await Timing.time(c, 'tokens', () =>
|
|
367
|
+
Promise.all(
|
|
368
|
+
holdings.data.map((holding) =>
|
|
369
|
+
Tokens.resolveToken(c, {
|
|
370
|
+
address: holding.address,
|
|
371
|
+
chainId,
|
|
372
|
+
snapshot,
|
|
373
|
+
})
|
|
374
|
+
.then((metadata) => ({ amount: holding.amount, metadata }) as const)
|
|
375
|
+
.catch(() => ({ amount: holding.amount, metadata: undefined }) as const),
|
|
376
|
+
),
|
|
377
|
+
),
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
const data: z.output<typeof schema.getAddressBalances.Balance>[] = []
|
|
381
|
+
for (const entry of resolved) {
|
|
382
|
+
if (!entry.metadata) continue
|
|
383
|
+
data.push({
|
|
384
|
+
amount: entry.amount,
|
|
385
|
+
formatted: core_Value.format(BigInt(entry.amount), entry.metadata.decimals),
|
|
386
|
+
id: entry.metadata.address,
|
|
387
|
+
// Best-effort: omit `feeEligible` entirely when the fee-token set was
|
|
388
|
+
// unavailable, rather than reporting a misleading `false`.
|
|
389
|
+
...(feeTokens ? { feeEligible: feeTokens.has(entry.metadata.address.toLowerCase()) } : {}),
|
|
390
|
+
token: {
|
|
391
|
+
address: entry.metadata.address,
|
|
392
|
+
currency: entry.metadata.currency,
|
|
393
|
+
decimals: entry.metadata.decimals,
|
|
394
|
+
id: entry.metadata.id,
|
|
395
|
+
logoUri: entry.metadata.logoUri,
|
|
396
|
+
name: entry.metadata.name,
|
|
397
|
+
symbol: entry.metadata.symbol,
|
|
398
|
+
verified: entry.metadata.verified,
|
|
399
|
+
},
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return { data, nextCursor: holdings.nextCursor }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
declare namespace getAddressBalances {
|
|
407
|
+
type Options = {
|
|
408
|
+
address: z.output<typeof Schema.Address>
|
|
409
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
410
|
+
/** Only include verified tokens denominated in this currency (implies `verified`). */
|
|
411
|
+
currency?: string | undefined
|
|
412
|
+
/** Opaque keyset cursor anchoring the page; omit for the head page. */
|
|
413
|
+
cursor?: string | undefined
|
|
414
|
+
/** When true, restrict the page to fee-usable tokens (members of `feeTokens`). */
|
|
415
|
+
feeEligible?: boolean | undefined
|
|
416
|
+
/**
|
|
417
|
+
* The chain's fee-token set, if resolved (best-effort): flags each row's
|
|
418
|
+
* `feeEligible` and backs the `feeEligible` filter. Undefined when the lookup
|
|
419
|
+
* failed, in which case `feeEligible` is omitted from rows.
|
|
420
|
+
*/
|
|
421
|
+
feeTokens?: ReadonlySet<string> | undefined
|
|
422
|
+
limit: number
|
|
423
|
+
/** 1-indexed page number (positional pagination; exclusive with `cursor`). */
|
|
424
|
+
page?: number | undefined
|
|
425
|
+
/** When true, restrict the page to addresses in the curated verified list. */
|
|
426
|
+
verified?: boolean | undefined
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Computes the optional set of allowed token addresses for the page's
|
|
432
|
+
* `token IN (…)` restriction, combining the verified/`currency` filter with the
|
|
433
|
+
* `feeEligible` filter. Each active filter contributes a candidate address list;
|
|
434
|
+
* when both are active the result is their intersection. Returns `undefined`
|
|
435
|
+
* when no filter is active (no restriction), or an empty array when an active
|
|
436
|
+
* filter excludes every token (caller short-circuits to an empty page).
|
|
437
|
+
* Addresses are lowercased to match the indexer's stored casing.
|
|
438
|
+
*/
|
|
439
|
+
function allowedTokens(options: allowedTokens.Options): string[] | undefined {
|
|
440
|
+
const { currency, feeEligible, feeTokens, snapshot, verified } = options
|
|
441
|
+
let allowed: string[] | undefined
|
|
442
|
+
if (verified) {
|
|
443
|
+
const matching = currency
|
|
444
|
+
? (snapshot.byCurrency.get(currency.toLowerCase()) ?? [])
|
|
445
|
+
: snapshot.list
|
|
446
|
+
allowed = matching.map((token) => token.address.toLowerCase())
|
|
447
|
+
}
|
|
448
|
+
// The handler guarantees `feeTokens` is present whenever `feeEligible` filtering
|
|
449
|
+
// is requested (a lookup failure surfaces as a `502` instead); the `?? []`
|
|
450
|
+
// is a defensive fall-through that yields an empty page rather than silently
|
|
451
|
+
// dropping the filter.
|
|
452
|
+
if (feeEligible) {
|
|
453
|
+
if (!feeTokens) allowed = []
|
|
454
|
+
else if (allowed) allowed = allowed.filter((address) => feeTokens.has(address))
|
|
455
|
+
else allowed = Array.from(feeTokens)
|
|
456
|
+
}
|
|
457
|
+
return allowed
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
declare namespace allowedTokens {
|
|
461
|
+
type Options = {
|
|
462
|
+
/** Only include verified tokens denominated in this currency (implies `verified`). */
|
|
463
|
+
currency?: string | undefined
|
|
464
|
+
/** When true, restrict to fee-usable tokens (members of `feeTokens`). */
|
|
465
|
+
feeEligible?: boolean | undefined
|
|
466
|
+
/** The chain's fee-token set, if resolved (best-effort). */
|
|
467
|
+
feeTokens?: ReadonlySet<string> | undefined
|
|
468
|
+
/** Compiled verified-token snapshot for the chain. */
|
|
469
|
+
snapshot: VerifiedTokens.Snapshot
|
|
470
|
+
/** When true, restrict to addresses in the curated verified list. */
|
|
471
|
+
verified?: boolean | undefined
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Exact count of an address's matching TIP-20 holdings, sharing
|
|
477
|
+
* {@link getAddressBalances}'s `holder`/`verified`/`currency` filters (minus
|
|
478
|
+
* pagination) so the count matches the page it annotates. The `holder` equality
|
|
479
|
+
* prunes the `token_balances_snapshot` view by its sort key, so ClickHouse
|
|
480
|
+
* counts via the sparse primary index without a row scan — the count is exact
|
|
481
|
+
* and cheap with no cap (`totalCountCapped` is always `false`), verified at
|
|
482
|
+
* ~370k holdings in <1s. Feeds the opt-in `meta.totalCount`.
|
|
483
|
+
*/
|
|
484
|
+
async function countAddressBalances(
|
|
485
|
+
c: Context<App.Environment>,
|
|
486
|
+
options: countAddressBalances.Options,
|
|
487
|
+
): Promise<{ totalCountCapped: boolean; totalCount: number }> {
|
|
488
|
+
const { address, chainId, currency, feeEligible, feeTokens, verified } = options
|
|
489
|
+
const tidx = c.get('getTidx')(chainId)
|
|
490
|
+
|
|
491
|
+
// Same verified/currency/feeEligible `token IN (…)` restriction as the page; an
|
|
492
|
+
// empty filter means nothing matches, so the count is zero without a round
|
|
493
|
+
// trip.
|
|
494
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId)
|
|
495
|
+
const allowed = allowedTokens({ currency, feeEligible, feeTokens, snapshot, verified })
|
|
496
|
+
const tokenFilter = allowed?.map((address) => `'${address}'`)
|
|
497
|
+
if (tokenFilter?.length === 0) return { totalCountCapped: false, totalCount: 0 }
|
|
498
|
+
const tokenIn = tokenFilter ? ` AND token IN (${tokenFilter.join(', ')})` : ''
|
|
499
|
+
|
|
500
|
+
return Timing.time(c, 'address_balances_count', async () => {
|
|
501
|
+
// Exact count over the holder-pruned `address_balances` rows (mirrors the
|
|
502
|
+
// page's WHERE, minus the keyset cursor). The `holder` sort-key prune lets
|
|
503
|
+
// ClickHouse count via the sparse index, so no cap is needed. The inline
|
|
504
|
+
// query is cast to `string` so TIDX treats it as a dynamic ClickHouse
|
|
505
|
+
// query needing no signature.
|
|
506
|
+
const result = await tidx.fetch({
|
|
507
|
+
chainId,
|
|
508
|
+
engine: 'clickhouse',
|
|
509
|
+
query: `
|
|
510
|
+
SELECT count(*) AS total
|
|
511
|
+
FROM address_balances
|
|
512
|
+
WHERE holder = '${address}' AND startsWith(token, '${tip20AddressPrefix}') AND balance > 0${tokenIn}
|
|
513
|
+
` as string,
|
|
514
|
+
})
|
|
515
|
+
const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0
|
|
516
|
+
return { totalCountCapped: false, totalCount }
|
|
517
|
+
})
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
declare namespace countAddressBalances {
|
|
521
|
+
type Options = {
|
|
522
|
+
address: z.output<typeof Schema.Address>
|
|
523
|
+
chainId: z.output<typeof Schema.ChainId>
|
|
524
|
+
/** Only include verified tokens denominated in this currency (implies `verified`). */
|
|
525
|
+
currency?: string | undefined
|
|
526
|
+
/** When true, restrict the count to fee-usable tokens (members of `feeTokens`). */
|
|
527
|
+
feeEligible?: boolean | undefined
|
|
528
|
+
/** The chain's fee-token set (best-effort), backing the `feeEligible` restriction. */
|
|
529
|
+
feeTokens?: ReadonlySet<string> | undefined
|
|
530
|
+
/** When true, restrict the count to addresses in the curated verified list. */
|
|
531
|
+
verified?: boolean | undefined
|
|
532
|
+
}
|
|
533
|
+
}
|