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,815 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { AbiEvent, AbiParameters, Hash, Value as core_Value } from 'ox';
|
|
3
|
+
import { Abis, Addresses } from 'viem/tempo';
|
|
4
|
+
import * as z from 'zod/mini';
|
|
5
|
+
import * as Auth from '../internal/Auth.js';
|
|
6
|
+
import * as Cache from '../internal/Cache.js';
|
|
7
|
+
import * as Cursor from '../internal/Cursor.js';
|
|
8
|
+
import * as OpenApi from '../internal/OpenApi.js';
|
|
9
|
+
import * as Response from '../internal/Response.js';
|
|
10
|
+
import * as Schema from '../internal/Schema.js';
|
|
11
|
+
import * as Store from '../internal/Store.js';
|
|
12
|
+
import * as Timing from '../internal/Timing.js';
|
|
13
|
+
import * as Ttl from '../internal/Ttl.js';
|
|
14
|
+
import * as Value from '../internal/Value.js';
|
|
15
|
+
import * as VerifiedTokens from '../internal/VerifiedTokens.js';
|
|
16
|
+
import * as Tokens from './tokens.js';
|
|
17
|
+
// The protocol Fee AMM lives at the reserved FeeManager address. Pools are
|
|
18
|
+
// `(userToken, validatorToken)` pairs discovered from the `Mint` events the
|
|
19
|
+
// FeeManager emits; there is no on-chain pool registry to enumerate.
|
|
20
|
+
const feeManager = Addresses.feeManager.toLowerCase();
|
|
21
|
+
// pathUSD is the protocol's default fee token: a transaction whose fee token
|
|
22
|
+
// matches the validator's token (or falls back to pathUSD) pays fees without
|
|
23
|
+
// needing a Fee AMM pool, so pathUSD is always fee-usable even when it has no
|
|
24
|
+
// user-side pool. Lowercased to match the indexer's stored address casing.
|
|
25
|
+
const pathUsd = Addresses.pathUsd.toLowerCase();
|
|
26
|
+
// Topic0 hashes of the FeeManager `Mint` event, current and legacy. Both
|
|
27
|
+
// signatures index `userToken` as topic2 and `validatorToken` as topic3, but
|
|
28
|
+
// their other fields differ, so rows are decoded per selector: the current
|
|
29
|
+
// event carries data words `[sender, amountValidatorToken, liquidity]` with
|
|
30
|
+
// topic1 = `to`; the legacy event carries `[amountUserToken,
|
|
31
|
+
// amountValidatorToken, liquidity]` with topic1 = `sender`. Queries hit the
|
|
32
|
+
// raw `logs` table rather than a signature CTE: the event-CTE planner rejects
|
|
33
|
+
// filters on event params, and the two signatures need different decoders
|
|
34
|
+
// anyway.
|
|
35
|
+
const mintTopic = AbiEvent.getSelector('event Mint(address sender, address indexed to, address indexed userToken, address indexed validatorToken, uint256 amountValidatorToken, uint256 liquidity)');
|
|
36
|
+
const legacyMintTopic = AbiEvent.getSelector('event Mint(address indexed sender, address indexed userToken, address indexed validatorToken, uint256 amountUserToken, uint256 amountValidatorToken, uint256 liquidity)');
|
|
37
|
+
/** Zod schemas owned by the fee AMM handlers. */
|
|
38
|
+
export var schema;
|
|
39
|
+
(function (schema) {
|
|
40
|
+
/**
|
|
41
|
+
* A token referenced by a fee pool: its contract address plus best-effort
|
|
42
|
+
* metadata. Mirrors the transfer feed's `sourceToken` technique: `address`
|
|
43
|
+
* is always present; the metadata fields spread in only when resolution
|
|
44
|
+
* succeeds, so a token whose metadata is unavailable keeps just the bare
|
|
45
|
+
* reference. The pools listing extends this shape with the side's reserve
|
|
46
|
+
* (`getFeeAmmPools.Token`).
|
|
47
|
+
*/
|
|
48
|
+
schema.PoolToken = Schema.describe(z.object({
|
|
49
|
+
address: Schema.TokenAddress.check(z.describe('TIP-20 token contract address used by this fee pool.')),
|
|
50
|
+
currency: z
|
|
51
|
+
.optional(z.string())
|
|
52
|
+
.check(z.describe('Display currency for the token, when available.'), z.meta({ examples: ['USD'] })),
|
|
53
|
+
decimals: z
|
|
54
|
+
.optional(z.number().check(z.int(), z.nonnegative()))
|
|
55
|
+
.check(z.describe('Number of decimal places the token uses.'), z.meta({ examples: [6] })),
|
|
56
|
+
logoUri: z.optional(z.string()).check(z.describe('URL for the token logo SVG, when available.'), z.meta({
|
|
57
|
+
examples: [
|
|
58
|
+
'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
|
|
59
|
+
],
|
|
60
|
+
})),
|
|
61
|
+
name: z
|
|
62
|
+
.optional(z.string())
|
|
63
|
+
.check(z.describe('Human-readable token name.'), z.meta({ examples: ['USD Coin'] })),
|
|
64
|
+
symbol: z
|
|
65
|
+
.optional(z.string())
|
|
66
|
+
.check(z.describe('Short token ticker symbol.'), z.meta({ examples: ['USDC'] })),
|
|
67
|
+
verified: z
|
|
68
|
+
.optional(z.boolean())
|
|
69
|
+
.check(z.describe('Whether Tempo has verified this token’s metadata.'), z.meta({ examples: [true] })),
|
|
70
|
+
}), 'A TIP-20 token used by a Fee AMM pool, with metadata when available.');
|
|
71
|
+
/** Schemas for the getFeeAmmPools operation. */
|
|
72
|
+
let getFeeAmmPools;
|
|
73
|
+
(function (getFeeAmmPools) {
|
|
74
|
+
/** Optional related resources that callers opt into via `include`. */
|
|
75
|
+
getFeeAmmPools.Include = z
|
|
76
|
+
.enum(['token'])
|
|
77
|
+
.check(z.describe('Optional related data you can request with `include`.'));
|
|
78
|
+
/**
|
|
79
|
+
* Parses a comma-separated `include` query value into a list of optional
|
|
80
|
+
* resources to embed. Token metadata resolution is one extra (batched)
|
|
81
|
+
* `token.getMetadata` round trip, so it runs only when `include=token` is
|
|
82
|
+
* requested; the base page returns each side's bare `address` plus its
|
|
83
|
+
* reserve and skips the lookup entirely.
|
|
84
|
+
*/
|
|
85
|
+
getFeeAmmPools.includeQuery = Schema.includeQuery(getFeeAmmPools.Include, 'Comma-separated related resources to include, such as `token` metadata.');
|
|
86
|
+
/** Query parameters for the fee pool list request. */
|
|
87
|
+
getFeeAmmPools.Query = z
|
|
88
|
+
.strictObject({
|
|
89
|
+
chainId: Schema.ChainIdQuery,
|
|
90
|
+
cursor: Schema.Cursor,
|
|
91
|
+
include: getFeeAmmPools.includeQuery,
|
|
92
|
+
limit: Schema.Limit,
|
|
93
|
+
page: Schema.Page,
|
|
94
|
+
})
|
|
95
|
+
.check(...Schema.pageChecks(), z.describe('Query parameters for listing Fee AMM pools.'));
|
|
96
|
+
/**
|
|
97
|
+
* One side of a fee pool: the token reference carrying the side's current
|
|
98
|
+
* on-chain reserve. `amount`/`formatted` come from `FeeManager.getPool`
|
|
99
|
+
* and are absent when the read fails (best-effort, like the metadata
|
|
100
|
+
* fields).
|
|
101
|
+
*/
|
|
102
|
+
getFeeAmmPools.Token = Schema.describe(z.extend(schema.PoolToken, {
|
|
103
|
+
amount: z
|
|
104
|
+
.optional(Schema.DecimalString)
|
|
105
|
+
.check(z.describe('Current onchain reserve for this token, in base units, from `FeeManager.getPool`. Omitted if the read fails.')),
|
|
106
|
+
formatted: z
|
|
107
|
+
.optional(z.string())
|
|
108
|
+
.check(z.describe('Reserve as a human-readable decimal string using the token’s `decimals`. Omitted if the read fails.'), z.meta({ examples: ['1.5'] })),
|
|
109
|
+
}), 'One token side of a Fee AMM pool, including its current onchain reserve when available.');
|
|
110
|
+
/** A fee AMM pool, aggregated from its `Mint` events. */
|
|
111
|
+
getFeeAmmPools.Pool = z
|
|
112
|
+
.object({
|
|
113
|
+
createdAt: z.iso
|
|
114
|
+
.datetime()
|
|
115
|
+
.check(z.describe('Timestamp when liquidity was first minted into this pool.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
116
|
+
id: Schema.Hash.check(z.describe('Stable resource id for the pool; this is the same value as `poolId`.')),
|
|
117
|
+
lastMintAt: z.iso
|
|
118
|
+
.datetime()
|
|
119
|
+
.check(z.describe('Timestamp when liquidity was most recently minted into this pool.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
120
|
+
mintCount: z
|
|
121
|
+
.number()
|
|
122
|
+
.check(z.int(), z.nonnegative(), z.describe('Number of liquidity mints into this pool.'), z.meta({ examples: [25] })),
|
|
123
|
+
poolId: Schema.Hash.check(z.describe('Pool id computed as `keccak256(abi.encode(userToken, validatorToken))`, matching `FeeManager.getPoolId`.')),
|
|
124
|
+
userToken: getFeeAmmPools.Token.check(z.describe('Token users pay fees with in this pool, including its current reserve when available.')),
|
|
125
|
+
validatorToken: getFeeAmmPools.Token.check(z.describe('Token validators receive from this pool, including its current reserve when available.')),
|
|
126
|
+
})
|
|
127
|
+
.check(z.describe('A Fee AMM pool discovered from its onchain `Mint` events.'));
|
|
128
|
+
/** Page of fee pools, ordered by mint count (most active first). */
|
|
129
|
+
getFeeAmmPools.Response = z
|
|
130
|
+
.object({
|
|
131
|
+
data: z
|
|
132
|
+
.array(getFeeAmmPools.Pool)
|
|
133
|
+
.check(z.describe('Fee AMM pools ordered by mint count, most active first.')),
|
|
134
|
+
nextCursor: Schema.NextCursor,
|
|
135
|
+
})
|
|
136
|
+
.check(z.describe('A page of Fee AMM pools ordered by mint count, most active first.'));
|
|
137
|
+
})(getFeeAmmPools = schema.getFeeAmmPools || (schema.getFeeAmmPools = {}));
|
|
138
|
+
/** Schemas for the getFeeAmmMints operation. */
|
|
139
|
+
let getFeeAmmMints;
|
|
140
|
+
(function (getFeeAmmMints) {
|
|
141
|
+
/** Query parameters for the pool mint list request. */
|
|
142
|
+
getFeeAmmMints.Query = z
|
|
143
|
+
.strictObject({
|
|
144
|
+
chainId: Schema.ChainIdQuery,
|
|
145
|
+
cursor: Schema.Cursor,
|
|
146
|
+
include: Schema.totalCountInclude,
|
|
147
|
+
limit: Schema.Limit,
|
|
148
|
+
order: Schema.Order,
|
|
149
|
+
page: Schema.Page,
|
|
150
|
+
userToken: z
|
|
151
|
+
.optional(Schema.TokenAddress)
|
|
152
|
+
.check(z.describe('Only include mints for pools where users paid fees with this token.')),
|
|
153
|
+
validatorToken: z
|
|
154
|
+
.optional(Schema.TokenAddress)
|
|
155
|
+
.check(z.describe('Only include mints for pools where validators receive this token.')),
|
|
156
|
+
})
|
|
157
|
+
.check(...Schema.pageChecks(), z.describe('Query parameters for listing Fee AMM liquidity mints.'));
|
|
158
|
+
/** A single liquidity mint into a fee pool. */
|
|
159
|
+
getFeeAmmMints.Mint = z
|
|
160
|
+
.object({
|
|
161
|
+
amountUserToken: z
|
|
162
|
+
.optional(Schema.DecimalString)
|
|
163
|
+
.check(z.describe('Amount of the user-side token deposited, in base units. Only present on legacy-signature mints.')),
|
|
164
|
+
amountValidatorToken: Schema.DecimalString.check(z.describe('Amount of the validator-side token deposited, in base units.')),
|
|
165
|
+
blockNumber: z
|
|
166
|
+
.number()
|
|
167
|
+
.check(z.int(), z.nonnegative(), z.describe('Block number where the mint occurred.'), z.meta({ examples: [23456789] })),
|
|
168
|
+
id: z.string().check(z.describe('Stable mint id built from the transaction hash and log index (`${transactionHash}-${logIndex}`).'), z.meta({
|
|
169
|
+
examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-0'],
|
|
170
|
+
})),
|
|
171
|
+
liquidity: Schema.DecimalString.check(z.describe('Amount of pool liquidity minted, as an integer string.')),
|
|
172
|
+
logIndex: z
|
|
173
|
+
.number()
|
|
174
|
+
.check(z.int(), z.nonnegative(), z.describe('Log index of this mint within the block.'), z.meta({ examples: [0] })),
|
|
175
|
+
minter: Schema.Address.check(z.describe('Address that deposited tokens into the pool.')),
|
|
176
|
+
recipient: z
|
|
177
|
+
.optional(Schema.Address)
|
|
178
|
+
.check(z.describe('Address that received the minted liquidity. Only present on current-signature mints.')),
|
|
179
|
+
timestamp: z.iso
|
|
180
|
+
.datetime()
|
|
181
|
+
.check(z.describe('Block timestamp when the mint occurred.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
|
|
182
|
+
transactionHash: Schema.Hash.check(z.describe('Hash of the transaction that emitted this mint.')),
|
|
183
|
+
userToken: schema.PoolToken.check(z.describe('Token users pay fees with in this pool.')),
|
|
184
|
+
validatorToken: schema.PoolToken.check(z.describe('Token validators receive from this pool.')),
|
|
185
|
+
})
|
|
186
|
+
.check(z.describe('One liquidity mint into a Fee AMM pool.'));
|
|
187
|
+
/** Page of fee pool mints, ordered by block then log index. */
|
|
188
|
+
getFeeAmmMints.Response = z
|
|
189
|
+
.object({
|
|
190
|
+
data: z.array(getFeeAmmMints.Mint).check(z.describe('Liquidity mints returned on this page.')),
|
|
191
|
+
meta: z
|
|
192
|
+
.optional(Schema.CountMeta)
|
|
193
|
+
.check(z.describe('Response-wide metadata requested with `include`, such as `totalCount`.')),
|
|
194
|
+
nextCursor: Schema.NextCursor,
|
|
195
|
+
})
|
|
196
|
+
.check(z.describe('A page of Fee AMM liquidity mints.'));
|
|
197
|
+
})(getFeeAmmMints = schema.getFeeAmmMints || (schema.getFeeAmmMints = {}));
|
|
198
|
+
})(schema || (schema = {}));
|
|
199
|
+
/** Creates fee AMM handlers. */
|
|
200
|
+
export function feeAmm() {
|
|
201
|
+
return new Hono()
|
|
202
|
+
.get('/pools', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getFeeAmmPools.Query, {
|
|
203
|
+
code: 'query_invalid',
|
|
204
|
+
message: 'Invalid query parameters',
|
|
205
|
+
}), OpenApi.describeRoute({
|
|
206
|
+
description: 'Lists Fee AMM pools, most active first, so you can see which stablecoin fee conversions have the most liquidity activity.',
|
|
207
|
+
operationId: 'getFeeAmmPools',
|
|
208
|
+
responses: OpenApi.responses({
|
|
209
|
+
errors: { 502: 'The upstream Tempo indexer could not complete the request.' },
|
|
210
|
+
success: {
|
|
211
|
+
description: 'List of Fee AMM pools.',
|
|
212
|
+
schema: schema.getFeeAmmPools.Response,
|
|
213
|
+
},
|
|
214
|
+
}),
|
|
215
|
+
summary: 'List pools',
|
|
216
|
+
tags: ['Fee AMM'],
|
|
217
|
+
}), Cache.response({
|
|
218
|
+
cacheControl: Cache.policies.state,
|
|
219
|
+
name: 'tempo-api:fee-amm:v1',
|
|
220
|
+
key: (c) => Cache.urlKey(c, schema.getFeeAmmPools.Query),
|
|
221
|
+
}), async (c) => {
|
|
222
|
+
if (Auth.narrowAccess)
|
|
223
|
+
return Auth.paidAccessError(c);
|
|
224
|
+
if (OpenApi.narrowValidation)
|
|
225
|
+
return OpenApi.validationError(c, {
|
|
226
|
+
code: 'query_invalid',
|
|
227
|
+
message: 'Invalid query parameters',
|
|
228
|
+
});
|
|
229
|
+
const query = c.req.valid('query');
|
|
230
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
231
|
+
try {
|
|
232
|
+
const page = await listPools(c, {
|
|
233
|
+
chainId,
|
|
234
|
+
cursor: query.cursor,
|
|
235
|
+
limit: query.limit,
|
|
236
|
+
page: query.page,
|
|
237
|
+
});
|
|
238
|
+
const pools = page.data;
|
|
239
|
+
// Reserves are one `getPool` eth_call per pool; viem auto-batches
|
|
240
|
+
// the concurrent calls into a single deployless multicall round
|
|
241
|
+
// trip (see `internal/Viem.ts`).
|
|
242
|
+
const reserves = await Timing.time(c, 'fee_amm_reserves', () => Promise.all(pools.map((pool) => getPoolReserves(c, {
|
|
243
|
+
chainId,
|
|
244
|
+
userToken: pool.userToken,
|
|
245
|
+
validatorToken: pool.validatorToken,
|
|
246
|
+
}))));
|
|
247
|
+
// Token metadata is one extra (batched) `token.getMetadata` round
|
|
248
|
+
// trip, opt-in via `include=token`. Without it each side carries
|
|
249
|
+
// just its `address` and reserve; reserves still format via the
|
|
250
|
+
// `reserve` helper's TIP-20 (6-decimal) fallback.
|
|
251
|
+
const tokensByAddress = query.include.includes('token')
|
|
252
|
+
? await resolveTokenMap(c, {
|
|
253
|
+
addresses: Array.from(new Set(pools.flatMap((pool) => [pool.userToken, pool.validatorToken]))),
|
|
254
|
+
chainId,
|
|
255
|
+
})
|
|
256
|
+
: undefined;
|
|
257
|
+
const data = pools.map((pool, index) => {
|
|
258
|
+
const id = poolId(pool.userToken, pool.validatorToken);
|
|
259
|
+
const userToken = { address: pool.userToken, ...tokensByAddress?.get(pool.userToken) };
|
|
260
|
+
const validatorToken = {
|
|
261
|
+
address: pool.validatorToken,
|
|
262
|
+
...tokensByAddress?.get(pool.validatorToken),
|
|
263
|
+
};
|
|
264
|
+
// Each side's reserve rides on its token reference (like the
|
|
265
|
+
// metadata fields): absent when the `getPool` read failed.
|
|
266
|
+
return {
|
|
267
|
+
createdAt: pool.createdAt,
|
|
268
|
+
id,
|
|
269
|
+
lastMintAt: pool.lastMintAt,
|
|
270
|
+
mintCount: pool.mintCount,
|
|
271
|
+
poolId: id,
|
|
272
|
+
userToken: {
|
|
273
|
+
...userToken,
|
|
274
|
+
...reserve(reserves[index]?.userToken, userToken.decimals),
|
|
275
|
+
},
|
|
276
|
+
validatorToken: {
|
|
277
|
+
...validatorToken,
|
|
278
|
+
...reserve(reserves[index]?.validatorToken, validatorToken.decimals),
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
return c.json(Response.validated(schema.getFeeAmmPools.Response, {
|
|
283
|
+
data,
|
|
284
|
+
nextCursor: page.nextCursor,
|
|
285
|
+
}), 200);
|
|
286
|
+
}
|
|
287
|
+
catch (cause) {
|
|
288
|
+
return Response.upstream(c, cause);
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
.get('/mints', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getFeeAmmMints.Query, {
|
|
292
|
+
code: 'query_invalid',
|
|
293
|
+
message: 'Invalid query parameters',
|
|
294
|
+
}), OpenApi.describeRoute({
|
|
295
|
+
description: 'Lists liquidity mints into Fee AMM pools.',
|
|
296
|
+
operationId: 'getFeeAmmMints',
|
|
297
|
+
responses: OpenApi.responses({
|
|
298
|
+
errors: { 502: 'The upstream Tempo indexer could not complete the request.' },
|
|
299
|
+
success: {
|
|
300
|
+
description: 'Page of Fee AMM liquidity mints.',
|
|
301
|
+
schema: schema.getFeeAmmMints.Response,
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
summary: 'List mints',
|
|
305
|
+
tags: ['Fee AMM'],
|
|
306
|
+
}), Cache.response({
|
|
307
|
+
cacheControl: Cache.policies.feed,
|
|
308
|
+
name: 'tempo-api:fee-amm:v1',
|
|
309
|
+
key: (c) => Cache.urlKey(c, schema.getFeeAmmMints.Query),
|
|
310
|
+
}), async (c) => {
|
|
311
|
+
if (Auth.narrowAccess)
|
|
312
|
+
return Auth.paidAccessError(c);
|
|
313
|
+
if (OpenApi.narrowValidation)
|
|
314
|
+
return OpenApi.validationError(c, {
|
|
315
|
+
code: 'query_invalid',
|
|
316
|
+
message: 'Invalid query parameters',
|
|
317
|
+
});
|
|
318
|
+
const query = c.req.valid('query');
|
|
319
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
320
|
+
// A cursor page is anchored below the head, so its contents are
|
|
321
|
+
// append-only and effectively immutable; cache it aggressively. The
|
|
322
|
+
// head page (no cursor) keeps the route's short `feed` default.
|
|
323
|
+
if (query.cursor !== undefined)
|
|
324
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
325
|
+
try {
|
|
326
|
+
// `totalCount` is opt-in and shares the page's filters; run it
|
|
327
|
+
// concurrently with the page. Best-effort: a count failure omits
|
|
328
|
+
// `meta` rather than failing the page.
|
|
329
|
+
const countPromise = query.include.includes('totalCount')
|
|
330
|
+
? countMints(c, {
|
|
331
|
+
chainId,
|
|
332
|
+
userToken: query.userToken,
|
|
333
|
+
validatorToken: query.validatorToken,
|
|
334
|
+
}).catch(() => undefined)
|
|
335
|
+
: undefined;
|
|
336
|
+
const page = await listMints(c, {
|
|
337
|
+
chainId,
|
|
338
|
+
cursor: query.cursor,
|
|
339
|
+
limit: query.limit,
|
|
340
|
+
order: query.order,
|
|
341
|
+
page: query.page,
|
|
342
|
+
userToken: query.userToken,
|
|
343
|
+
validatorToken: query.validatorToken,
|
|
344
|
+
});
|
|
345
|
+
// Token references take the same enriched shape as the pools
|
|
346
|
+
// listing. The page's unique tokens are few (often one pool), and
|
|
347
|
+
// each resolution is memoized, so this stays one bounded fan-out.
|
|
348
|
+
const tokensByAddress = await resolveTokenMap(c, {
|
|
349
|
+
addresses: Array.from(new Set(page.data.flatMap((mint) => [mint.userToken, mint.validatorToken]))),
|
|
350
|
+
chainId,
|
|
351
|
+
});
|
|
352
|
+
const data = page.data.map((mint) => ({
|
|
353
|
+
...mint,
|
|
354
|
+
userToken: { address: mint.userToken, ...tokensByAddress.get(mint.userToken) },
|
|
355
|
+
validatorToken: {
|
|
356
|
+
address: mint.validatorToken,
|
|
357
|
+
...tokensByAddress.get(mint.validatorToken),
|
|
358
|
+
},
|
|
359
|
+
}));
|
|
360
|
+
const meta = countPromise ? await countPromise : undefined;
|
|
361
|
+
return c.json(Response.validated(schema.getFeeAmmMints.Response, {
|
|
362
|
+
data,
|
|
363
|
+
...(meta ? { meta } : {}),
|
|
364
|
+
nextCursor: page.nextCursor,
|
|
365
|
+
}), 200);
|
|
366
|
+
}
|
|
367
|
+
catch (cause) {
|
|
368
|
+
return Response.upstream(c, cause);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Discovers fee pools by aggregating the FeeManager's `Mint` logs into one
|
|
374
|
+
* row per `(userToken, validatorToken)` pair, paginated and ordered by mint
|
|
375
|
+
* count (most active first), so the real fee pools (thousands to millions of
|
|
376
|
+
* mints) lead the head pages and the one-shot spam pairs (mint count 1) sink
|
|
377
|
+
* to the tail.
|
|
378
|
+
*
|
|
379
|
+
* Keyset pagination on `(mint_count, topic2, topic3)`: `mint_count` ranks the
|
|
380
|
+
* page (mutable — it grows as pools mint — so this is best-effort across mint
|
|
381
|
+
* activity, the inherent limit of a ranking feed), and the `(topic2, topic3)`
|
|
382
|
+
* group key is a stable, unique tiebreaker. A malformed cursor falls back to
|
|
383
|
+
* the head page.
|
|
384
|
+
*/
|
|
385
|
+
function listPools(c, options) {
|
|
386
|
+
const { chainId, limit } = options;
|
|
387
|
+
const store = c.get('store');
|
|
388
|
+
const tidx = c.get('getTidx')(chainId);
|
|
389
|
+
const cursor = options.cursor
|
|
390
|
+
? Cursor.decode(options.cursor, ['int', 'address', 'address'])
|
|
391
|
+
: undefined;
|
|
392
|
+
// Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
|
|
393
|
+
// the head page, so it shares the head's SQL and cache entries; deeper pages
|
|
394
|
+
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
395
|
+
const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
|
|
396
|
+
return Timing.time(c, 'fee_amm_pools', () => Store.memoize(async () => {
|
|
397
|
+
// The keyset compares the aggregate `mint_count`, so it lives in the
|
|
398
|
+
// outer WHERE over the GROUP BY subquery. Topics are padded 32-byte
|
|
399
|
+
// log topics, so render their literals via `padTopic` (lowercased to
|
|
400
|
+
// match ClickHouse's stored topic casing) rather than `Cursor.literal`.
|
|
401
|
+
const keyset = cursor !== undefined
|
|
402
|
+
? ` WHERE ${Cursor.keyset([
|
|
403
|
+
{ literal: Cursor.literal(cursor[0], 'int'), name: 'mint_count', order: 'desc' },
|
|
404
|
+
{
|
|
405
|
+
literal: `'${padTopic(cursor[1]).toLowerCase()}'`,
|
|
406
|
+
name: 'topic2',
|
|
407
|
+
order: 'desc',
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
literal: `'${padTopic(cursor[2]).toLowerCase()}'`,
|
|
411
|
+
name: 'topic3',
|
|
412
|
+
order: 'desc',
|
|
413
|
+
},
|
|
414
|
+
])}`
|
|
415
|
+
: '';
|
|
416
|
+
// Raw-`logs` ClickHouse aggregate (dynamic query, hence the cast):
|
|
417
|
+
// ordering is by mint count so the head pages surface the real fee
|
|
418
|
+
// pools and the one-shot spam pairs sink to the tail. Fetch one extra
|
|
419
|
+
// row to detect `hasMore` without a separate count query.
|
|
420
|
+
const result = await tidx.fetch({
|
|
421
|
+
chainId,
|
|
422
|
+
engine: 'clickhouse',
|
|
423
|
+
query: `
|
|
424
|
+
SELECT topic2, topic3, mint_count, first_at, last_at
|
|
425
|
+
FROM (
|
|
426
|
+
SELECT topic2, topic3, count() AS mint_count, min(block_timestamp) AS first_at, max(block_timestamp) AS last_at
|
|
427
|
+
FROM logs
|
|
428
|
+
WHERE address = '${feeManager}' AND selector IN ('${mintTopic}', '${legacyMintTopic}')
|
|
429
|
+
GROUP BY topic2, topic3
|
|
430
|
+
)${keyset}
|
|
431
|
+
ORDER BY mint_count DESC, topic2 DESC, topic3 DESC
|
|
432
|
+
LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
|
|
433
|
+
`,
|
|
434
|
+
});
|
|
435
|
+
// The next page anchors below the last row's `(mint_count, topic2,
|
|
436
|
+
// topic3)`; topics use the lowercase unpadded address so they round
|
|
437
|
+
// trip through the keyset literals above.
|
|
438
|
+
const page = Cursor.paginate({
|
|
439
|
+
rows: result.rows,
|
|
440
|
+
limit,
|
|
441
|
+
key: (row) => {
|
|
442
|
+
const mintCount = Value.toNumber(row['mint_count']);
|
|
443
|
+
const userToken = unpadTopic(Value.toText(row['topic2']))?.toLowerCase();
|
|
444
|
+
const validatorToken = unpadTopic(Value.toText(row['topic3']))?.toLowerCase();
|
|
445
|
+
return mintCount !== undefined &&
|
|
446
|
+
userToken !== undefined &&
|
|
447
|
+
validatorToken !== undefined
|
|
448
|
+
? [mintCount, userToken, validatorToken]
|
|
449
|
+
: undefined;
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
const data = [];
|
|
453
|
+
for (const row of page.rows) {
|
|
454
|
+
const userToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic2'])));
|
|
455
|
+
const validatorToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic3'])));
|
|
456
|
+
const mintCount = Value.toNumber(row['mint_count']);
|
|
457
|
+
const createdAt = Value.toIsoDateTime(row['first_at']);
|
|
458
|
+
const lastMintAt = Value.toIsoDateTime(row['last_at']);
|
|
459
|
+
if (!userToken.success ||
|
|
460
|
+
!validatorToken.success ||
|
|
461
|
+
mintCount === undefined ||
|
|
462
|
+
createdAt === undefined ||
|
|
463
|
+
lastMintAt === undefined)
|
|
464
|
+
continue;
|
|
465
|
+
data.push({
|
|
466
|
+
createdAt,
|
|
467
|
+
lastMintAt,
|
|
468
|
+
mintCount,
|
|
469
|
+
userToken: userToken.data,
|
|
470
|
+
validatorToken: validatorToken.data,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return { data, nextCursor: page.nextCursor };
|
|
474
|
+
}, {
|
|
475
|
+
key: `feeamm:v1:${chainId}:pools:${cursor ? `cursor:${cursor[0]}:${cursor[1]}:${cursor[2]}` : offset !== undefined ? `page:${options.page}` : 'head'}:${limit}`,
|
|
476
|
+
store,
|
|
477
|
+
ttl: Ttl.minutes(1),
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* The set of token addresses usable to pay transaction fees on a chain: the
|
|
482
|
+
* curated verified tokens that have at least one Fee AMM pool (i.e. appear as a
|
|
483
|
+
* `userToken` / topic2 in the FeeManager's `Mint` logs), plus the protocol
|
|
484
|
+
* default fee token (`pathUSD`), which pays fees without needing a pool.
|
|
485
|
+
* Addresses are lowercased to match the indexer's stored casing.
|
|
486
|
+
*
|
|
487
|
+
* The set is intentionally gated to the verified list rather than every pooled
|
|
488
|
+
* token: anyone can mint a one-shot spam pool, so the raw set of pool
|
|
489
|
+
* `userToken`s is unbounded (tens of thousands on busy chains) and those
|
|
490
|
+
* liquidity-less pools can't actually settle fees. Restricting to verified
|
|
491
|
+
* tokens keeps the set small (a handful of USD stablecoins), bounds the scan
|
|
492
|
+
* (the `topic2 IN (…)` candidate list is the verified list), and matches the
|
|
493
|
+
* tokens fees are realistically paid in.
|
|
494
|
+
*
|
|
495
|
+
* Memoized per chain and verified-snapshot version (the only inputs), so
|
|
496
|
+
* callers — e.g. the address balances endpoint — can flag which holdings are
|
|
497
|
+
* fee-usable without a second `/fee-amm/pools` query.
|
|
498
|
+
*/
|
|
499
|
+
export async function feeTokenSet(c, chainId) {
|
|
500
|
+
const store = c.get('store');
|
|
501
|
+
const tidx = c.get('getTidx')(chainId);
|
|
502
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId);
|
|
503
|
+
// pathUSD is always fee-usable; the verified tokens are the only other
|
|
504
|
+
// candidates we probe for a pool. With no verified candidates there is
|
|
505
|
+
// nothing to query — pathUSD stands alone.
|
|
506
|
+
const candidates = snapshot.list.map((token) => token.address.toLowerCase());
|
|
507
|
+
if (candidates.length === 0)
|
|
508
|
+
return new Set([pathUsd]);
|
|
509
|
+
// `memoize` JSON-encodes its value, so cache a plain `string[]` and rebuild
|
|
510
|
+
// the `Set` per call (cheap; the list is a handful of stablecoins).
|
|
511
|
+
const addresses = await Timing.time(c, 'fee_token_set', () => Store.memoize(async () => {
|
|
512
|
+
// Dynamic raw-`logs` query (hence the cast), mirroring the pools
|
|
513
|
+
// listing's FeeManager `Mint` filter but collapsing to the distinct
|
|
514
|
+
// user-side tokens — and scoped to the verified candidates via
|
|
515
|
+
// `topic2 IN (…)` so the result is bounded by the verified list.
|
|
516
|
+
const topics = candidates.map((address) => `'${padTopic(address)}'`);
|
|
517
|
+
const result = await tidx.fetch({
|
|
518
|
+
chainId,
|
|
519
|
+
engine: 'clickhouse',
|
|
520
|
+
query: `
|
|
521
|
+
SELECT DISTINCT topic2
|
|
522
|
+
FROM logs
|
|
523
|
+
WHERE address = '${feeManager}' AND selector IN ('${mintTopic}', '${legacyMintTopic}')
|
|
524
|
+
AND topic2 IN (${topics.join(', ')})
|
|
525
|
+
`,
|
|
526
|
+
});
|
|
527
|
+
const tokens = new Set([pathUsd]);
|
|
528
|
+
for (const row of result.rows) {
|
|
529
|
+
const token = unpadTopic(Value.toText(row['topic2']))?.toLowerCase();
|
|
530
|
+
if (token !== undefined && Schema.Address.safeParse(token).success)
|
|
531
|
+
tokens.add(token);
|
|
532
|
+
}
|
|
533
|
+
return Array.from(tokens);
|
|
534
|
+
}, {
|
|
535
|
+
key: `feeamm:v2:${chainId}:fee-token-set:v:${snapshot.version}`,
|
|
536
|
+
store,
|
|
537
|
+
ttl: Ttl.minutes(5),
|
|
538
|
+
}));
|
|
539
|
+
return new Set(addresses);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Reads a pool's current on-chain reserves via `FeeManager.getPool`, memoized
|
|
543
|
+
* briefly per pool. Reserves are best-effort decoration on the pool listing:
|
|
544
|
+
* a failing RPC read resolves to `null` (degrading that pool's reserve
|
|
545
|
+
* fields) rather than failing the whole response. Failures are not memoized,
|
|
546
|
+
* so the next request retries.
|
|
547
|
+
*/
|
|
548
|
+
function getPoolReserves(c, options) {
|
|
549
|
+
const { chainId, userToken, validatorToken } = options;
|
|
550
|
+
const client = c.get('getClient')(chainId);
|
|
551
|
+
const store = c.get('store');
|
|
552
|
+
return Store.memoize(async () => {
|
|
553
|
+
const pool = await client.readContract({
|
|
554
|
+
abi: Abis.feeAmm,
|
|
555
|
+
address: Addresses.feeManager,
|
|
556
|
+
args: [userToken, validatorToken],
|
|
557
|
+
functionName: 'getPool',
|
|
558
|
+
});
|
|
559
|
+
return {
|
|
560
|
+
userToken: pool.reserveUserToken.toString(),
|
|
561
|
+
validatorToken: pool.reserveValidatorToken.toString(),
|
|
562
|
+
};
|
|
563
|
+
}, {
|
|
564
|
+
key: `feeamm:v1:${chainId}:pool:${userToken}:${validatorToken}:reserves`,
|
|
565
|
+
store,
|
|
566
|
+
ttl: Ttl.seconds(30),
|
|
567
|
+
}).catch(() => null);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Lists `Mint` logs as a keyset-paginated page on `(block_num, log_idx)`,
|
|
571
|
+
* optionally filtered by the pool's tokens. Runs on ClickHouse: the postgres
|
|
572
|
+
* `logs` table has no topic index, so topic-filtered pages exceed its
|
|
573
|
+
* statement timeout, while ClickHouse serves every filter shape.
|
|
574
|
+
*/
|
|
575
|
+
function listMints(c, options) {
|
|
576
|
+
const { chainId, limit, order, userToken, validatorToken } = options;
|
|
577
|
+
const store = c.get('store');
|
|
578
|
+
const tidx = c.get('getTidx')(chainId);
|
|
579
|
+
const direction = order === 'asc' ? 'ASC' : 'DESC';
|
|
580
|
+
// Keyset pagination: anchor the page below the previous row's
|
|
581
|
+
// `(block, log_idx)` position instead of a numeric offset, so rows arriving
|
|
582
|
+
// at the head can't shift items across pages. A malformed cursor falls back
|
|
583
|
+
// to the head page.
|
|
584
|
+
const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
|
|
585
|
+
// Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
|
|
586
|
+
// the head page, so it shares the head's SQL and cache entries; deeper pages
|
|
587
|
+
// translate to a SQL OFFSET of `(page - 1) * limit` rows.
|
|
588
|
+
const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
|
|
589
|
+
return Timing.time(c, 'fee_amm_mints', () => Store.memoize(async () => {
|
|
590
|
+
const filters = [
|
|
591
|
+
`address = '${feeManager}'`,
|
|
592
|
+
`selector IN ('${mintTopic}', '${legacyMintTopic}')`,
|
|
593
|
+
];
|
|
594
|
+
if (userToken !== undefined)
|
|
595
|
+
filters.push(`topic2 = '${padTopic(userToken)}'`);
|
|
596
|
+
if (validatorToken !== undefined)
|
|
597
|
+
filters.push(`topic3 = '${padTopic(validatorToken)}'`);
|
|
598
|
+
if (cursor !== undefined)
|
|
599
|
+
filters.push(Cursor.keyset([
|
|
600
|
+
{ literal: Cursor.literal(cursor[0], 'int'), name: 'block_num', order },
|
|
601
|
+
{ literal: Cursor.literal(cursor[1], 'int'), name: 'log_idx', order },
|
|
602
|
+
]));
|
|
603
|
+
// Fetch one extra row to detect `hasMore` without a separate count
|
|
604
|
+
// query. Raw-`logs` dynamic query, hence the cast. `block_timestamp`
|
|
605
|
+
// is aliased to a non-standard name on purpose: the TIDX client
|
|
606
|
+
// coerces standard `logs` columns, and its coercion parses
|
|
607
|
+
// ClickHouse's timezone-less `DateTime` text as *local* time. The
|
|
608
|
+
// alias passes the raw text through so `Value.toIsoDateTime` can
|
|
609
|
+
// apply its UTC normalization instead.
|
|
610
|
+
const result = await tidx.fetch({
|
|
611
|
+
chainId,
|
|
612
|
+
engine: 'clickhouse',
|
|
613
|
+
query: `
|
|
614
|
+
SELECT tx_hash, block_num, log_idx, block_timestamp AS block_time, selector, topic1, topic2, topic3, data
|
|
615
|
+
FROM logs
|
|
616
|
+
WHERE ${filters.join(' AND ')}
|
|
617
|
+
ORDER BY block_num ${direction}, log_idx ${direction}
|
|
618
|
+
LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
|
|
619
|
+
`,
|
|
620
|
+
});
|
|
621
|
+
// The next page anchors below the last fetched row's `(block, log_idx)`.
|
|
622
|
+
const page = Cursor.paginate({
|
|
623
|
+
rows: result.rows,
|
|
624
|
+
limit,
|
|
625
|
+
key: (row) => {
|
|
626
|
+
const block = Value.toNumber(row['block_num']);
|
|
627
|
+
const index = Value.toNumber(row['log_idx']);
|
|
628
|
+
return block !== undefined && index !== undefined ? [block, index] : undefined;
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
const data = [];
|
|
632
|
+
for (const row of page.rows) {
|
|
633
|
+
const mint = decodeMint(row);
|
|
634
|
+
if (mint)
|
|
635
|
+
data.push(mint);
|
|
636
|
+
}
|
|
637
|
+
return { data, nextCursor: page.nextCursor };
|
|
638
|
+
}, {
|
|
639
|
+
key: `feeamm:v1:${chainId}:mints:${order}:${userToken ?? ''}:${validatorToken ?? ''}:${cursor ? `cursor:${cursor[0]}:${cursor[1]}` : offset !== undefined ? `page:${options.page}` : 'head'}:${limit}`,
|
|
640
|
+
store,
|
|
641
|
+
ttl: Ttl.seconds(15),
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Exact total count of pool mints matching the same filters as the page. Runs
|
|
646
|
+
* on ClickHouse over `logs`, pruning on the FeeManager `address` (the page's
|
|
647
|
+
* own engine/key). Because `address` aligns with the `logs` sort key,
|
|
648
|
+
* ClickHouse counts via the sparse primary index without a row scan, so the
|
|
649
|
+
* count is exact and cheap with no cap (`totalCountCapped` is always `false`) —
|
|
650
|
+
* verified at ~20M matching mints in ~1s.
|
|
651
|
+
*/
|
|
652
|
+
function countMints(c, options) {
|
|
653
|
+
const { chainId, userToken, validatorToken } = options;
|
|
654
|
+
const store = c.get('store');
|
|
655
|
+
const tidx = c.get('getTidx')(chainId);
|
|
656
|
+
return Timing.time(c, 'fee_amm_mints_count', () => Store.memoize(async () => {
|
|
657
|
+
const filters = [
|
|
658
|
+
`address = '${feeManager}'`,
|
|
659
|
+
`selector IN ('${mintTopic}', '${legacyMintTopic}')`,
|
|
660
|
+
];
|
|
661
|
+
if (userToken !== undefined)
|
|
662
|
+
filters.push(`topic2 = '${padTopic(userToken)}'`);
|
|
663
|
+
if (validatorToken !== undefined)
|
|
664
|
+
filters.push(`topic3 = '${padTopic(validatorToken)}'`);
|
|
665
|
+
const result = await tidx.fetch({
|
|
666
|
+
chainId,
|
|
667
|
+
engine: 'clickhouse',
|
|
668
|
+
query: `SELECT count(*) AS total FROM logs WHERE ${filters.join(' AND ')}`,
|
|
669
|
+
});
|
|
670
|
+
const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0;
|
|
671
|
+
return { totalCountCapped: false, totalCount };
|
|
672
|
+
}, {
|
|
673
|
+
key: `feeamm:v1:${chainId}:mints-count:${userToken ?? ''}:${validatorToken ?? ''}`,
|
|
674
|
+
store,
|
|
675
|
+
ttl: Ttl.seconds(15),
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Decodes one raw `Mint` log row, dispatching on its selector (current vs
|
|
680
|
+
* legacy signature — see the topic constants for the field layouts). Returns
|
|
681
|
+
* undefined for rows that fail to decode so callers can skip them.
|
|
682
|
+
*/
|
|
683
|
+
function decodeMint(row) {
|
|
684
|
+
const selector = Value.toText(row['selector']);
|
|
685
|
+
const transactionHash = Schema.Hash.safeParse(row['tx_hash']);
|
|
686
|
+
const blockNumber = Value.toNumber(row['block_num']);
|
|
687
|
+
const logIndex = Value.toNumber(row['log_idx']);
|
|
688
|
+
const timestamp = Value.toIsoDateTime(row['block_time']);
|
|
689
|
+
const userToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic2'])));
|
|
690
|
+
const validatorToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic3'])));
|
|
691
|
+
const data = Value.toText(row['data']);
|
|
692
|
+
if (!transactionHash.success ||
|
|
693
|
+
blockNumber === undefined ||
|
|
694
|
+
logIndex === undefined ||
|
|
695
|
+
timestamp === undefined ||
|
|
696
|
+
!userToken.success ||
|
|
697
|
+
!validatorToken.success ||
|
|
698
|
+
data === undefined)
|
|
699
|
+
return undefined;
|
|
700
|
+
const word = (index) => data.slice(2 + 64 * index, 2 + 64 * (index + 1));
|
|
701
|
+
const uint = (value) => /^[0-9a-fA-F]{64}$/.test(value) ? BigInt(`0x${value}`).toString() : undefined;
|
|
702
|
+
if (selector === mintTopic) {
|
|
703
|
+
const minter = Schema.Address.safeParse(`0x${word(0).slice(24)}`);
|
|
704
|
+
const recipient = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic1'])));
|
|
705
|
+
const amountValidatorToken = uint(word(1));
|
|
706
|
+
const liquidity = uint(word(2));
|
|
707
|
+
if (!minter.success ||
|
|
708
|
+
!recipient.success ||
|
|
709
|
+
amountValidatorToken === undefined ||
|
|
710
|
+
liquidity === undefined)
|
|
711
|
+
return undefined;
|
|
712
|
+
return {
|
|
713
|
+
amountValidatorToken,
|
|
714
|
+
blockNumber,
|
|
715
|
+
id: `${transactionHash.data}-${logIndex}`,
|
|
716
|
+
liquidity,
|
|
717
|
+
logIndex,
|
|
718
|
+
minter: minter.data,
|
|
719
|
+
recipient: recipient.data,
|
|
720
|
+
timestamp,
|
|
721
|
+
transactionHash: transactionHash.data,
|
|
722
|
+
userToken: userToken.data,
|
|
723
|
+
validatorToken: validatorToken.data,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
if (selector === legacyMintTopic) {
|
|
727
|
+
const minter = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic1'])));
|
|
728
|
+
const amountUserToken = uint(word(0));
|
|
729
|
+
const amountValidatorToken = uint(word(1));
|
|
730
|
+
const liquidity = uint(word(2));
|
|
731
|
+
if (!minter.success ||
|
|
732
|
+
amountUserToken === undefined ||
|
|
733
|
+
amountValidatorToken === undefined ||
|
|
734
|
+
liquidity === undefined)
|
|
735
|
+
return undefined;
|
|
736
|
+
return {
|
|
737
|
+
amountUserToken,
|
|
738
|
+
amountValidatorToken,
|
|
739
|
+
blockNumber,
|
|
740
|
+
id: `${transactionHash.data}-${logIndex}`,
|
|
741
|
+
liquidity,
|
|
742
|
+
logIndex,
|
|
743
|
+
minter: minter.data,
|
|
744
|
+
timestamp,
|
|
745
|
+
transactionHash: transactionHash.data,
|
|
746
|
+
userToken: userToken.data,
|
|
747
|
+
validatorToken: validatorToken.data,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
return undefined;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Resolves best-effort metadata for the given token addresses, sharing one
|
|
754
|
+
* verified-token snapshot across the lookups. Mirrors the transfer feed's
|
|
755
|
+
* `sourceToken` enrichment: a token whose metadata is unavailable maps to
|
|
756
|
+
* undefined, so call sites spreading the result keep just the bare `address`
|
|
757
|
+
* (the `PoolToken` shape).
|
|
758
|
+
*
|
|
759
|
+
* Unlike the full token resource, this resolves only the on-chain metadata
|
|
760
|
+
* (one `token.getMetadata` read per token, auto-batched into a single
|
|
761
|
+
* multicall) plus the in-memory verified snapshot. It deliberately skips the
|
|
762
|
+
* curated R2 logo lookup (`getTokenLogo`), which is an un-batched storage read
|
|
763
|
+
* per token and the dominant cost of the pools/mints listings; `logoUri`
|
|
764
|
+
* therefore falls back to the verified entry's curated URL or the precompile's
|
|
765
|
+
* on-chain `logoURI`, both already in hand.
|
|
766
|
+
*/
|
|
767
|
+
async function resolveTokenMap(c, options) {
|
|
768
|
+
const { addresses, chainId } = options;
|
|
769
|
+
const snapshot = await VerifiedTokens.snapshot(c, chainId);
|
|
770
|
+
const resolved = await Timing.time(c, 'tokens', () => Promise.all(addresses.map((token) => Tokens.getTokenMetadata(c, { address: token, chainId })
|
|
771
|
+
.then((metadata) => {
|
|
772
|
+
const verified = snapshot.byAddress.get(token);
|
|
773
|
+
return [
|
|
774
|
+
token,
|
|
775
|
+
{
|
|
776
|
+
currency: metadata.currency,
|
|
777
|
+
decimals: metadata.decimals,
|
|
778
|
+
logoUri: verified?.logoUri ?? metadata.logoUri,
|
|
779
|
+
name: metadata.name,
|
|
780
|
+
symbol: metadata.symbol,
|
|
781
|
+
verified: snapshot.byAddress.has(token),
|
|
782
|
+
},
|
|
783
|
+
];
|
|
784
|
+
})
|
|
785
|
+
.catch(() => [token, undefined]))));
|
|
786
|
+
return new Map(resolved);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Computes a pool id locally: `keccak256(abi.encode(userToken,
|
|
790
|
+
* validatorToken))`, matching the on-chain `FeeManager.getPoolId`.
|
|
791
|
+
*/
|
|
792
|
+
function poolId(userToken, validatorToken) {
|
|
793
|
+
return Hash.keccak256(AbiParameters.encode(AbiParameters.from(['address', 'address']), [userToken, validatorToken]));
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Shapes one side's on-chain reserve for spreading into its token reference:
|
|
797
|
+
* the base-unit `amount` plus its `formatted` decimal form. `decimals` comes
|
|
798
|
+
* from the side's resolved token metadata; an unresolvable token falls back
|
|
799
|
+
* to 6, the fixed TIP-20 precision, so the reserve still formats. A missing
|
|
800
|
+
* amount (failed `getPool` read) yields undefined, leaving both fields absent.
|
|
801
|
+
*/
|
|
802
|
+
function reserve(amount, decimals) {
|
|
803
|
+
if (amount === undefined)
|
|
804
|
+
return undefined;
|
|
805
|
+
return { amount, formatted: core_Value.format(BigInt(amount), decimals ?? 6) };
|
|
806
|
+
}
|
|
807
|
+
/** Left-pads a 20-byte address to its 32-byte log-topic form. */
|
|
808
|
+
function padTopic(address) {
|
|
809
|
+
return `0x${'0'.repeat(24)}${address.slice(2)}`;
|
|
810
|
+
}
|
|
811
|
+
/** Unpads a 32-byte log topic to its trailing 20-byte address. */
|
|
812
|
+
function unpadTopic(topic) {
|
|
813
|
+
return topic === undefined ? undefined : `0x${topic.slice(26)}`;
|
|
814
|
+
}
|
|
815
|
+
//# sourceMappingURL=fee-amm.js.map
|