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,3138 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { Address, Hex } from 'ox';
|
|
3
|
+
import { parseEventLogs, zeroAddress } from 'viem';
|
|
4
|
+
import { Abis, Addresses } from 'viem/tempo';
|
|
5
|
+
import * as z from 'zod/mini';
|
|
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 Mpp from '../internal/Mpp.js';
|
|
10
|
+
import * as OpenApi from '../internal/OpenApi.js';
|
|
11
|
+
import * as Response from '../internal/Response.js';
|
|
12
|
+
import * as Schema from '../internal/Schema.js';
|
|
13
|
+
import * as Store from '../internal/Store.js';
|
|
14
|
+
import * as Timing from '../internal/Timing.js';
|
|
15
|
+
import * as Ttl from '../internal/Ttl.js';
|
|
16
|
+
import * as Value from '../internal/Value.js';
|
|
17
|
+
import * as Tokens from './tokens.js';
|
|
18
|
+
import * as Transfers from './transfers.js';
|
|
19
|
+
/** Example transaction hash surfaced in OpenAPI docs. */
|
|
20
|
+
const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474';
|
|
21
|
+
/** Zod schemas owned by the activity handlers. */
|
|
22
|
+
export var schema;
|
|
23
|
+
(function (schema) {
|
|
24
|
+
/** A signer: the literal `self`, or the access-key address that signed. */
|
|
25
|
+
const Signer = z
|
|
26
|
+
.union([z.literal('self'), Schema.Address])
|
|
27
|
+
.check(z.describe('Who signed the activity: `self`, or the access-key address used to sign it.'));
|
|
28
|
+
/**
|
|
29
|
+
* A token and the amount that moved, in the same shape as the API's transfer
|
|
30
|
+
* resource (`Transfers.schema.SourceToken`): a contract `address` and a
|
|
31
|
+
* decimal-integer `amount`, plus best-effort `decimals`/`symbol` metadata when
|
|
32
|
+
* the token has been resolved.
|
|
33
|
+
*/
|
|
34
|
+
schema.Token = Transfers.schema.SourceToken;
|
|
35
|
+
/**
|
|
36
|
+
* A token transfer as classified by the feed: the `/transfers` row shape
|
|
37
|
+
* plus the feed-only, subject-relative fields (`direction`, `signer`) that
|
|
38
|
+
* the unscoped resource cannot carry, with `memo`/`attribution` always
|
|
39
|
+
* resolved (the feed already decodes the underlying events).
|
|
40
|
+
*/
|
|
41
|
+
schema.Transfer = z
|
|
42
|
+
.object({
|
|
43
|
+
attribution: z
|
|
44
|
+
.optional(z.string())
|
|
45
|
+
.check(z.describe('MPP service name matched from the transfer memo, when Tempo recognizes the fingerprint.'), z.meta({ examples: ['Acme Payments'] })),
|
|
46
|
+
blockNumber: z
|
|
47
|
+
.number()
|
|
48
|
+
.check(z.int(), z.nonnegative(), z.describe('Block number where this transfer was included.'), z.meta({ examples: [12345678] })),
|
|
49
|
+
destinationToken: z
|
|
50
|
+
.optional(schema.Token)
|
|
51
|
+
.check(z.describe('Token and amount delivered to the recipient when it differs from the source token — a cross-token transfer routed through the DEX. Omitted for a same-token transfer.')),
|
|
52
|
+
direction: z
|
|
53
|
+
.enum(['in', 'out'])
|
|
54
|
+
.check(z.describe('The address’s side of the transfer: `in` means received, and `out` means sent.')),
|
|
55
|
+
memo: z
|
|
56
|
+
.optional(z.string())
|
|
57
|
+
.check(z.describe('Text memo attached to the transfer, when one was provided.'), z.meta({ examples: ['Invoice #1234'] })), // prettier-ignore
|
|
58
|
+
recipient: Schema.Address.check(z.describe('Address that received the transfer.')),
|
|
59
|
+
sender: Schema.Address.check(z.describe('Address that sent the transfer.')),
|
|
60
|
+
signer: z
|
|
61
|
+
.optional(Signer)
|
|
62
|
+
.check(z.describe('For outbound transfers, who signed it: `self` or the access-key address. Omitted for inbound transfers.')),
|
|
63
|
+
sourceToken: schema.Token.check(z.describe('Token and amount that moved in the transfer.')),
|
|
64
|
+
timestamp: z
|
|
65
|
+
.iso.datetime()
|
|
66
|
+
.check(z.describe('Block timestamp for the transfer.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })), // prettier-ignore
|
|
67
|
+
transactionHash: Schema.Hash.check(z.describe('Transaction hash that contains this transfer.')),
|
|
68
|
+
})
|
|
69
|
+
.check(z.describe('One token transfer involving the requested address.'));
|
|
70
|
+
/**
|
|
71
|
+
* One transaction log, decoded into a known Tempo event when possible. The
|
|
72
|
+
* raw fields (`address`/`data`/`logIndex`/`topics`) are always present so no
|
|
73
|
+
* log is hidden; the decoded fields (`args`/`eventName`/`title`) are present
|
|
74
|
+
* only when the log matched a known `viem/tempo` ABI.
|
|
75
|
+
*/
|
|
76
|
+
const event = z
|
|
77
|
+
.object({
|
|
78
|
+
address: Schema.Address.check(z.describe('Contract address that emitted the log.')),
|
|
79
|
+
args: z
|
|
80
|
+
.optional(z.record(z.string(), z.unknown()))
|
|
81
|
+
.check(z.describe('Decoded event arguments keyed by parameter name, present when the log matched a known ABI. Integer values are decimal strings.')),
|
|
82
|
+
data: Schema.Hex.check(z.describe('Unindexed log data as a hex string (`0x` when empty).')),
|
|
83
|
+
eventName: z
|
|
84
|
+
.optional(z.string())
|
|
85
|
+
.check(z.describe('Event name decoded from the `viem/tempo` ABIs, present when the log matched a known ABI.'), z.meta({ examples: ['Transfer'] })), // prettier-ignore
|
|
86
|
+
logIndex: z
|
|
87
|
+
.number()
|
|
88
|
+
.check(z.int(), z.nonnegative(), z.describe('Log index for this log within the block.'), z.meta({ examples: [4] })), // prettier-ignore
|
|
89
|
+
title: z
|
|
90
|
+
.optional(z.string())
|
|
91
|
+
.check(z.describe('Human-readable label for the decoded event, present when the log matched a known ABI.'), z.meta({ examples: ['Token transferred'] })), // prettier-ignore
|
|
92
|
+
topics: z
|
|
93
|
+
.array(Schema.Hash)
|
|
94
|
+
.check(z.describe('Raw log topics; the first topic is the event signature hash.')),
|
|
95
|
+
})
|
|
96
|
+
.check(z.describe('One transaction log. Decoded fields (`eventName`/`args`/`title`) are present when it matched a known Tempo ABI; otherwise only the raw fields are set.'));
|
|
97
|
+
/**
|
|
98
|
+
* Fields shared by every activity item. `transactionHash`/`timestamp` identify and sort
|
|
99
|
+
* the entry; `data` carries the type-specific payload — shaped like the
|
|
100
|
+
* matching API resource where one exists, plus any feed-only fields (e.g. a
|
|
101
|
+
* transfer's `direction`/`memo`/`attribution`/`signer`) the resource lacks.
|
|
102
|
+
*/
|
|
103
|
+
const base = {
|
|
104
|
+
perspective: z
|
|
105
|
+
.enum(['incoming', 'outgoing'])
|
|
106
|
+
.check(z.describe('Your role in the transaction: `outgoing` when you sent or paid for it, `incoming` when it happened to you (someone else initiated it). For a `transfer`, `data.direction` is the finer-grained token-flow view.')),
|
|
107
|
+
events: z
|
|
108
|
+
.optional(z.array(event))
|
|
109
|
+
.check(z.describe('Every log in the transaction, decoded when possible, so nothing is hidden. Returned only when the request sets `logs=true`, except on `unknown` items where it is always present.')),
|
|
110
|
+
id: z.string().check(z.describe('Stable activity ID built from the transaction hash and log index (`${transactionHash}-${logIndex}`).'), z.meta({
|
|
111
|
+
examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-3'],
|
|
112
|
+
})),
|
|
113
|
+
logIndex: z
|
|
114
|
+
.number()
|
|
115
|
+
.check(z.int(), z.nonnegative(), z.describe('Log index for this activity within the block.'), z.meta({ examples: [3] })),
|
|
116
|
+
timestamp: z
|
|
117
|
+
.iso.datetime()
|
|
118
|
+
.check(z.describe('Block timestamp as an ISO 8601 string.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })), // prettier-ignore
|
|
119
|
+
title: z
|
|
120
|
+
.string()
|
|
121
|
+
.check(z.describe('Human-readable label for the activity `type`, suitable for display in a feed. Past-tense, sentence case (e.g. `Token transferred`, `Tokens swapped`, `Burn blocked`).'), z.meta({ examples: ['Token transferred'] })),
|
|
122
|
+
transactionHash: Schema.Hash.check(z.describe('Transaction hash for this activity.')),
|
|
123
|
+
};
|
|
124
|
+
/** The classified activity item variants (discriminated by `type`). */
|
|
125
|
+
schema.items = [
|
|
126
|
+
z
|
|
127
|
+
.object({
|
|
128
|
+
...base,
|
|
129
|
+
data: schema.Transfer.check(z.describe('Transfer details, using the `/transfers` shape plus feed-only fields such as direction and signer.')),
|
|
130
|
+
type: z
|
|
131
|
+
.literal('transfer')
|
|
132
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
133
|
+
})
|
|
134
|
+
.check(z.meta({ title: 'Token transferred' })),
|
|
135
|
+
z
|
|
136
|
+
.object({
|
|
137
|
+
...base,
|
|
138
|
+
data: z
|
|
139
|
+
.object({
|
|
140
|
+
recipient: Schema.Address.check(z.describe('Address that received the transfer.')),
|
|
141
|
+
sourceToken: schema.Token.check(z.describe('Token and amount that were minted.')),
|
|
142
|
+
})
|
|
143
|
+
.check(z.describe('A token mint activity.')),
|
|
144
|
+
type: z
|
|
145
|
+
.literal('mint')
|
|
146
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
147
|
+
})
|
|
148
|
+
.check(z.meta({ title: 'Token minted' })),
|
|
149
|
+
z
|
|
150
|
+
.object({
|
|
151
|
+
...base,
|
|
152
|
+
data: z
|
|
153
|
+
.object({
|
|
154
|
+
sender: Schema.Address.check(z.describe('Address whose token balance was burned.')),
|
|
155
|
+
signer: Signer,
|
|
156
|
+
sourceToken: schema.Token.check(z.describe('Token and amount that were burned.')),
|
|
157
|
+
})
|
|
158
|
+
.check(z.describe('A token burn activity.')),
|
|
159
|
+
type: z
|
|
160
|
+
.literal('burn')
|
|
161
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
162
|
+
})
|
|
163
|
+
.check(z.meta({ title: 'Token burned' })),
|
|
164
|
+
z
|
|
165
|
+
.object({
|
|
166
|
+
...base,
|
|
167
|
+
data: z
|
|
168
|
+
.object({
|
|
169
|
+
destinationToken: schema.Token.check(z.describe('Token the address received in the swap.')),
|
|
170
|
+
signer: Signer,
|
|
171
|
+
sourceToken: schema.Token.check(z.describe('Token the address sent into the swap.')),
|
|
172
|
+
})
|
|
173
|
+
.check(z.describe('Swap inferred from token transfers through the DEX precompile. This feed entry is a summary, so per-fill details, route, and rate are not included.')),
|
|
174
|
+
type: z
|
|
175
|
+
.literal('swap')
|
|
176
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
177
|
+
})
|
|
178
|
+
.check(z.meta({ title: 'Tokens swapped' })),
|
|
179
|
+
z
|
|
180
|
+
.object({
|
|
181
|
+
...base,
|
|
182
|
+
data: z
|
|
183
|
+
.object({
|
|
184
|
+
signer: Signer,
|
|
185
|
+
sourceToken: schema.Token.check(z.describe('Token and allowance amount approved for spending.')),
|
|
186
|
+
spender: Schema.Address.check(z.describe('Address allowed to spend the approved amount.')),
|
|
187
|
+
})
|
|
188
|
+
.check(z.describe('A token allowance approval activity.')),
|
|
189
|
+
type: z
|
|
190
|
+
.literal('approval')
|
|
191
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
192
|
+
})
|
|
193
|
+
.check(z.meta({ title: 'Allowance approved' })),
|
|
194
|
+
z
|
|
195
|
+
.object({
|
|
196
|
+
...base,
|
|
197
|
+
data: z
|
|
198
|
+
.object({
|
|
199
|
+
refund: z
|
|
200
|
+
.optional(schema.Token)
|
|
201
|
+
.check(z.describe('Token refunded to the address when the session closed, if any.')),
|
|
202
|
+
signer: Signer,
|
|
203
|
+
})
|
|
204
|
+
.check(z.describe('A payment-channel session close activity.')),
|
|
205
|
+
type: z
|
|
206
|
+
.literal('session-closed')
|
|
207
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
208
|
+
})
|
|
209
|
+
.check(z.meta({ title: 'Session closed' })),
|
|
210
|
+
z
|
|
211
|
+
.object({
|
|
212
|
+
...base,
|
|
213
|
+
data: z
|
|
214
|
+
.object({
|
|
215
|
+
account: Schema.Address.check(z.describe('Account this access key was authorized for.')),
|
|
216
|
+
expiry: z
|
|
217
|
+
.number()
|
|
218
|
+
.check(z.int(), z.describe('Access-key expiration time as a Unix timestamp.'), z.meta({ examples: [1735689600] })), // prettier-ignore
|
|
219
|
+
publicKey: Schema.Address.check(z.describe('Public key for the authorized access key.')),
|
|
220
|
+
signatureType: z
|
|
221
|
+
.number()
|
|
222
|
+
.check(z.int(), z.describe('Signature type used by the access key.'), z.meta({ examples: [0] })),
|
|
223
|
+
})
|
|
224
|
+
.check(z.describe('An access key was created for the account.')),
|
|
225
|
+
type: z
|
|
226
|
+
.literal('access-key-created')
|
|
227
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
228
|
+
})
|
|
229
|
+
.check(z.meta({ title: 'Access key created' })),
|
|
230
|
+
z
|
|
231
|
+
.object({
|
|
232
|
+
...base,
|
|
233
|
+
data: z
|
|
234
|
+
.object({
|
|
235
|
+
account: Schema.Address.check(z.describe('Account this access key was revoked from.')),
|
|
236
|
+
publicKey: Schema.Address.check(z.describe('Public key for the revoked access key.')),
|
|
237
|
+
})
|
|
238
|
+
.check(z.describe('An access key was revoked from the account.')),
|
|
239
|
+
type: z
|
|
240
|
+
.literal('access-key-revoked')
|
|
241
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
242
|
+
})
|
|
243
|
+
.check(z.meta({ title: 'Access key revoked' })),
|
|
244
|
+
z
|
|
245
|
+
.object({
|
|
246
|
+
...base,
|
|
247
|
+
data: z
|
|
248
|
+
.object({
|
|
249
|
+
address: Schema.Address.check(z.describe('Contract address of the new TIP-20 token.')),
|
|
250
|
+
currency: z.string().check(z.describe('Display currency for the token, such as USD.'), z.meta({ examples: ['USD'] })), // prettier-ignore
|
|
251
|
+
name: z.string().check(z.describe('Human-readable token name.'), z.meta({ examples: ['Tempo USD'] })), // prettier-ignore
|
|
252
|
+
symbol: z
|
|
253
|
+
.string()
|
|
254
|
+
.check(z.describe('Token ticker symbol.'), z.meta({ examples: ['TUSD'] })),
|
|
255
|
+
})
|
|
256
|
+
.check(z.describe('A new TIP-20 token was created.')),
|
|
257
|
+
type: z
|
|
258
|
+
.literal('token-created')
|
|
259
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
260
|
+
})
|
|
261
|
+
.check(z.meta({ title: 'Token created' })),
|
|
262
|
+
z
|
|
263
|
+
.object({
|
|
264
|
+
...base,
|
|
265
|
+
data: z
|
|
266
|
+
.object({
|
|
267
|
+
sender: Schema.Address.check(z.describe('Address whose blocked balance was burned by policy.')),
|
|
268
|
+
signer: Signer,
|
|
269
|
+
sourceToken: schema.Token.check(z.describe('Token and amount that were burned.')),
|
|
270
|
+
})
|
|
271
|
+
.check(z.describe('A compliance burn of funds the receive policy blocked, distinct from a normal burn.')),
|
|
272
|
+
type: z
|
|
273
|
+
.literal('burn-blocked')
|
|
274
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
275
|
+
})
|
|
276
|
+
.check(z.meta({ title: 'Burn blocked' })),
|
|
277
|
+
z
|
|
278
|
+
.object({
|
|
279
|
+
...base,
|
|
280
|
+
data: z
|
|
281
|
+
.object({
|
|
282
|
+
channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
|
|
283
|
+
payee: Schema.Address.check(z.describe('Address that receives payments from the channel.')),
|
|
284
|
+
payer: Schema.Address.check(z.describe('Address that funds the channel.')),
|
|
285
|
+
signer: Signer,
|
|
286
|
+
sourceToken: schema.Token.check(z.describe('Token and deposit amount locked into the channel.')),
|
|
287
|
+
})
|
|
288
|
+
.check(z.describe('A payment channel was opened and funded.')),
|
|
289
|
+
type: z
|
|
290
|
+
.literal('channel-opened')
|
|
291
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
292
|
+
})
|
|
293
|
+
.check(z.meta({ title: 'Channel opened' })),
|
|
294
|
+
z
|
|
295
|
+
.object({
|
|
296
|
+
...base,
|
|
297
|
+
data: z
|
|
298
|
+
.object({
|
|
299
|
+
channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
|
|
300
|
+
signer: Signer,
|
|
301
|
+
sourceToken: schema.Token.check(z.describe('Token and amount added to the channel deposit.')),
|
|
302
|
+
})
|
|
303
|
+
.check(z.describe('A payment channel deposit was topped up.')),
|
|
304
|
+
type: z
|
|
305
|
+
.literal('channel-funded')
|
|
306
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
307
|
+
})
|
|
308
|
+
.check(z.meta({ title: 'Channel funded' })),
|
|
309
|
+
z
|
|
310
|
+
.object({
|
|
311
|
+
...base,
|
|
312
|
+
data: z
|
|
313
|
+
.object({
|
|
314
|
+
channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
|
|
315
|
+
payee: Schema.Address.check(z.describe('Address that received the settled payment.')),
|
|
316
|
+
payer: Schema.Address.check(z.describe('Address that funded the channel.')),
|
|
317
|
+
signer: Signer,
|
|
318
|
+
sourceToken: schema.Token.check(z.describe('Token and amount paid out in this settlement.')),
|
|
319
|
+
})
|
|
320
|
+
.check(z.describe('An incremental payment-channel settlement to the payee.')),
|
|
321
|
+
type: z
|
|
322
|
+
.literal('channel-settled')
|
|
323
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
324
|
+
})
|
|
325
|
+
.check(z.meta({ title: 'Channel settled' })),
|
|
326
|
+
z
|
|
327
|
+
.object({
|
|
328
|
+
...base,
|
|
329
|
+
data: z
|
|
330
|
+
.object({
|
|
331
|
+
channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
|
|
332
|
+
payee: Schema.Address.check(z.describe('Address that receives payments from the channel.')),
|
|
333
|
+
payer: Schema.Address.check(z.describe('Address that funded the channel.')),
|
|
334
|
+
refund: z
|
|
335
|
+
.optional(schema.Token)
|
|
336
|
+
.check(z.describe('Token and amount refunded to the payer on close, if any.')),
|
|
337
|
+
signer: Signer,
|
|
338
|
+
sourceToken: z
|
|
339
|
+
.optional(schema.Token)
|
|
340
|
+
.check(z.describe('Token and final amount paid out to the payee on close, if any.')),
|
|
341
|
+
})
|
|
342
|
+
.check(z.describe('A payment channel was closed, paying out the payee and refunding the payer.')),
|
|
343
|
+
type: z
|
|
344
|
+
.literal('channel-closed')
|
|
345
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
346
|
+
})
|
|
347
|
+
.check(z.meta({ title: 'Channel closed' })),
|
|
348
|
+
z
|
|
349
|
+
.object({
|
|
350
|
+
...base,
|
|
351
|
+
data: z
|
|
352
|
+
.object({
|
|
353
|
+
channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
|
|
354
|
+
payee: Schema.Address.check(z.describe('Address that receives payments from the channel.')),
|
|
355
|
+
payer: Schema.Address.check(z.describe('Address that funds the channel.')),
|
|
356
|
+
signer: Signer,
|
|
357
|
+
})
|
|
358
|
+
.check(z.describe('A pending payment-channel close request was cancelled.')),
|
|
359
|
+
type: z
|
|
360
|
+
.literal('channel-close-cancelled')
|
|
361
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
362
|
+
})
|
|
363
|
+
.check(z.meta({ title: 'Channel close cancelled' })),
|
|
364
|
+
z
|
|
365
|
+
.object({
|
|
366
|
+
...base,
|
|
367
|
+
data: z
|
|
368
|
+
.object({
|
|
369
|
+
orderId: Schema.DecimalString.check(z.describe('Identifier of the DEX order.')),
|
|
370
|
+
side: z
|
|
371
|
+
.enum(['bid', 'ask'])
|
|
372
|
+
.check(z.describe('Order side: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
|
|
373
|
+
signer: Signer,
|
|
374
|
+
sourceToken: schema.Token.check(z.describe('Token and amount escrowed for the order.')),
|
|
375
|
+
tick: z
|
|
376
|
+
.number()
|
|
377
|
+
.check(z.int(), z.describe('Price tick the order was placed at.'), z.meta({ examples: [0] })),
|
|
378
|
+
})
|
|
379
|
+
.check(z.describe('A limit order was placed on the stablecoin DEX.')),
|
|
380
|
+
type: z
|
|
381
|
+
.literal('order-placed')
|
|
382
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
383
|
+
})
|
|
384
|
+
.check(z.meta({ title: 'Order placed' })),
|
|
385
|
+
z
|
|
386
|
+
.object({
|
|
387
|
+
...base,
|
|
388
|
+
data: z
|
|
389
|
+
.object({
|
|
390
|
+
orderId: Schema.DecimalString.check(z.describe('Identifier of the cancelled DEX order.')),
|
|
391
|
+
signer: Signer,
|
|
392
|
+
})
|
|
393
|
+
.check(z.describe('A DEX order was cancelled.')),
|
|
394
|
+
type: z
|
|
395
|
+
.literal('order-cancelled')
|
|
396
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
397
|
+
})
|
|
398
|
+
.check(z.meta({ title: 'Order cancelled' })),
|
|
399
|
+
z
|
|
400
|
+
.object({
|
|
401
|
+
...base,
|
|
402
|
+
data: z
|
|
403
|
+
.object({
|
|
404
|
+
signer: Signer,
|
|
405
|
+
sourceToken: schema.Token.check(z.describe('Token and amount of fees distributed.')),
|
|
406
|
+
validator: Schema.Address.check(z.describe('Validator that received the distributed fees.')),
|
|
407
|
+
})
|
|
408
|
+
.check(z.describe('Accrued validator fees were distributed.')),
|
|
409
|
+
type: z
|
|
410
|
+
.literal('fees-distributed')
|
|
411
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
412
|
+
})
|
|
413
|
+
.check(z.meta({ title: 'Fees distributed' })),
|
|
414
|
+
z
|
|
415
|
+
.object({
|
|
416
|
+
...base,
|
|
417
|
+
data: z
|
|
418
|
+
.object({
|
|
419
|
+
destinationToken: schema.Token.check(z.describe('Token and amount received from the fee AMM.')),
|
|
420
|
+
signer: Signer,
|
|
421
|
+
sourceToken: schema.Token.check(z.describe('Token and amount sent into the fee AMM.')),
|
|
422
|
+
swapper: Schema.Address.check(z.describe('Address that performed the rebalance swap.')),
|
|
423
|
+
})
|
|
424
|
+
.check(z.describe('A fee-AMM rebalance swap between a validator and user token.')),
|
|
425
|
+
type: z
|
|
426
|
+
.literal('fee-rebalance-swap')
|
|
427
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
428
|
+
})
|
|
429
|
+
.check(z.meta({ title: 'Fees rebalanced' })),
|
|
430
|
+
z
|
|
431
|
+
.object({
|
|
432
|
+
...base,
|
|
433
|
+
data: z
|
|
434
|
+
.object({
|
|
435
|
+
funder: Schema.Address.check(z.describe('Address that funded the reward pool.')),
|
|
436
|
+
signer: Signer,
|
|
437
|
+
sourceToken: schema.Token.check(z.describe('Token and amount added to the reward pool.')),
|
|
438
|
+
})
|
|
439
|
+
.check(z.describe('A token reward pool was funded.')),
|
|
440
|
+
type: z
|
|
441
|
+
.literal('reward-distributed')
|
|
442
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
443
|
+
})
|
|
444
|
+
.check(z.meta({ title: 'Reward distributed' })),
|
|
445
|
+
z
|
|
446
|
+
.object({
|
|
447
|
+
...base,
|
|
448
|
+
data: z
|
|
449
|
+
.object({
|
|
450
|
+
holder: Schema.Address.check(z.describe('Holder whose rewards were redirected.')),
|
|
451
|
+
recipient: Schema.Address.check(z.describe('Address that now receives the holder’s rewards.')),
|
|
452
|
+
signer: Signer,
|
|
453
|
+
})
|
|
454
|
+
.check(z.describe('A holder redirected where their token rewards are paid.')),
|
|
455
|
+
type: z
|
|
456
|
+
.literal('reward-recipient-set')
|
|
457
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
458
|
+
})
|
|
459
|
+
.check(z.meta({ title: 'Reward recipient set' })),
|
|
460
|
+
z
|
|
461
|
+
.object({
|
|
462
|
+
...base,
|
|
463
|
+
data: z
|
|
464
|
+
.object({
|
|
465
|
+
account: Schema.Address.check(z.describe('Account the access key belongs to.')),
|
|
466
|
+
publicKey: Schema.Address.check(z.describe('Public key of the access key whose limit changed.')),
|
|
467
|
+
signer: Signer,
|
|
468
|
+
sourceToken: schema.Token.check(z.describe('Token and new spending limit set for the access key.')),
|
|
469
|
+
})
|
|
470
|
+
.check(z.describe('An access key’s per-token spending limit was updated.')),
|
|
471
|
+
type: z
|
|
472
|
+
.literal('spending-limit-updated')
|
|
473
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
474
|
+
})
|
|
475
|
+
.check(z.meta({ title: 'Spending limit updated' })),
|
|
476
|
+
z
|
|
477
|
+
.object({
|
|
478
|
+
...base,
|
|
479
|
+
data: z
|
|
480
|
+
.object({
|
|
481
|
+
amountFilled: Schema.DecimalString.check(z.describe('Amount filled, in the token’s smallest unit.')),
|
|
482
|
+
maker: Schema.Address.check(z.describe('Maker whose resting order was filled.')),
|
|
483
|
+
orderId: Schema.DecimalString.check(z.describe('Identifier of the filled DEX order.')),
|
|
484
|
+
partialFill: z
|
|
485
|
+
.boolean()
|
|
486
|
+
.check(z.describe('Whether the order was only partially filled.'), z.meta({ examples: [true] })),
|
|
487
|
+
signer: Signer,
|
|
488
|
+
taker: Schema.Address.check(z.describe('Taker that filled the order.')),
|
|
489
|
+
})
|
|
490
|
+
.check(z.describe('A resting DEX order was filled by a taker.')),
|
|
491
|
+
type: z
|
|
492
|
+
.literal('order-filled')
|
|
493
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
494
|
+
})
|
|
495
|
+
.check(z.meta({ title: 'Order filled' })),
|
|
496
|
+
z
|
|
497
|
+
.object({
|
|
498
|
+
...base,
|
|
499
|
+
data: z
|
|
500
|
+
.object({
|
|
501
|
+
flipTick: z
|
|
502
|
+
.number()
|
|
503
|
+
.check(z.int(), z.describe('Price tick the flipped order was placed at.'), z.meta({ examples: [0] })),
|
|
504
|
+
maker: Schema.Address.check(z.describe('Maker the order belongs to.')),
|
|
505
|
+
orderId: Schema.DecimalString.check(z.describe('Identifier of the DEX order.')),
|
|
506
|
+
side: z
|
|
507
|
+
.enum(['bid', 'ask'])
|
|
508
|
+
.check(z.describe('Side of the flipped order: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
|
|
509
|
+
signer: Signer,
|
|
510
|
+
sourceToken: schema.Token.check(z.describe('Token and amount the flipped order rests with.')),
|
|
511
|
+
tick: z
|
|
512
|
+
.number()
|
|
513
|
+
.check(z.int(), z.describe('Price tick the original order filled at.'), z.meta({ examples: [0] })),
|
|
514
|
+
})
|
|
515
|
+
.check(z.describe('A flip order was rotated to the opposite side after filling.')),
|
|
516
|
+
type: z
|
|
517
|
+
.literal('order-flipped')
|
|
518
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
519
|
+
})
|
|
520
|
+
.check(z.meta({ title: 'Order flipped' })),
|
|
521
|
+
z
|
|
522
|
+
.object({
|
|
523
|
+
...base,
|
|
524
|
+
data: z
|
|
525
|
+
.object({
|
|
526
|
+
base: Schema.Address.check(z.describe('Base token of the new trading pair.')),
|
|
527
|
+
key: Schema.Hex.check(z.describe('Identifier of the trading pair.')),
|
|
528
|
+
quote: Schema.Address.check(z.describe('Quote token of the new trading pair.')),
|
|
529
|
+
signer: Signer,
|
|
530
|
+
})
|
|
531
|
+
.check(z.describe('A new trading pair was created on the stablecoin DEX.')),
|
|
532
|
+
type: z
|
|
533
|
+
.literal('pair-created')
|
|
534
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
535
|
+
})
|
|
536
|
+
.check(z.meta({ title: 'Trading pair created' })),
|
|
537
|
+
z
|
|
538
|
+
.object({
|
|
539
|
+
...base,
|
|
540
|
+
data: z
|
|
541
|
+
.object({
|
|
542
|
+
paused: z
|
|
543
|
+
.boolean()
|
|
544
|
+
.check(z.describe('Whether the token is now paused.'), z.meta({ examples: [true] })),
|
|
545
|
+
signer: Signer,
|
|
546
|
+
updater: Schema.Address.check(z.describe('Address that changed the pause state.')),
|
|
547
|
+
})
|
|
548
|
+
.check(z.describe('A TIP-20 token was paused or unpaused.')),
|
|
549
|
+
type: z
|
|
550
|
+
.literal('token-pause-set')
|
|
551
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
552
|
+
})
|
|
553
|
+
.check(z.meta({ title: 'Token pause set' })),
|
|
554
|
+
z
|
|
555
|
+
.object({
|
|
556
|
+
...base,
|
|
557
|
+
data: z
|
|
558
|
+
.object({
|
|
559
|
+
signer: Signer,
|
|
560
|
+
supplyCap: Schema.DecimalString.check(z.describe('New maximum total supply, in the token’s smallest unit.')),
|
|
561
|
+
updater: Schema.Address.check(z.describe('Address that changed the supply cap.')),
|
|
562
|
+
})
|
|
563
|
+
.check(z.describe('A TIP-20 token’s supply cap was changed.')),
|
|
564
|
+
type: z
|
|
565
|
+
.literal('token-supply-cap-set')
|
|
566
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
567
|
+
})
|
|
568
|
+
.check(z.meta({ title: 'Token supply cap set' })),
|
|
569
|
+
z
|
|
570
|
+
.object({
|
|
571
|
+
...base,
|
|
572
|
+
data: z
|
|
573
|
+
.object({
|
|
574
|
+
policyId: Schema.DecimalString.check(z.describe('Identifier of the transfer policy now linked to the token.')),
|
|
575
|
+
signer: Signer,
|
|
576
|
+
updater: Schema.Address.check(z.describe('Address that linked the transfer policy.')),
|
|
577
|
+
})
|
|
578
|
+
.check(z.describe('A TIP-20 token’s transfer policy was changed.')),
|
|
579
|
+
type: z
|
|
580
|
+
.literal('token-transfer-policy-set')
|
|
581
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
582
|
+
})
|
|
583
|
+
.check(z.meta({ title: 'Token transfer policy set' })),
|
|
584
|
+
z
|
|
585
|
+
.object({
|
|
586
|
+
...base,
|
|
587
|
+
data: z
|
|
588
|
+
.object({
|
|
589
|
+
quoteToken: Schema.Address.check(z.describe('Token now used as the quote token.')),
|
|
590
|
+
signer: Signer,
|
|
591
|
+
updater: Schema.Address.check(z.describe('Address that changed the quote token.')),
|
|
592
|
+
})
|
|
593
|
+
.check(z.describe('A TIP-20 token’s quote token was changed.')),
|
|
594
|
+
type: z
|
|
595
|
+
.literal('token-quote-token-set')
|
|
596
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
597
|
+
})
|
|
598
|
+
.check(z.meta({ title: 'Token quote token set' })),
|
|
599
|
+
z
|
|
600
|
+
.object({
|
|
601
|
+
...base,
|
|
602
|
+
data: z
|
|
603
|
+
.object({
|
|
604
|
+
nextQuoteToken: Schema.Address.check(z.describe('Token queued to become the next quote token.')),
|
|
605
|
+
signer: Signer,
|
|
606
|
+
updater: Schema.Address.check(z.describe('Address that queued the next quote token.')),
|
|
607
|
+
})
|
|
608
|
+
.check(z.describe('A TIP-20 token’s next quote token was queued.')),
|
|
609
|
+
type: z
|
|
610
|
+
.literal('token-next-quote-token-set')
|
|
611
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
612
|
+
})
|
|
613
|
+
.check(z.meta({ title: 'Token next quote token set' })),
|
|
614
|
+
z
|
|
615
|
+
.object({
|
|
616
|
+
...base,
|
|
617
|
+
data: z
|
|
618
|
+
.object({
|
|
619
|
+
logoUri: z.string().check(z.describe('New logo URL for the token.'), z.meta({
|
|
620
|
+
examples: [
|
|
621
|
+
'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
|
|
622
|
+
],
|
|
623
|
+
})),
|
|
624
|
+
signer: Signer,
|
|
625
|
+
updater: Schema.Address.check(z.describe('Address that changed the logo.')),
|
|
626
|
+
})
|
|
627
|
+
.check(z.describe('A TIP-20 token’s logo URI was changed.')),
|
|
628
|
+
type: z
|
|
629
|
+
.literal('token-logo-set')
|
|
630
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
631
|
+
})
|
|
632
|
+
.check(z.meta({ title: 'Token logo set' })),
|
|
633
|
+
z
|
|
634
|
+
.object({
|
|
635
|
+
...base,
|
|
636
|
+
data: z
|
|
637
|
+
.object({
|
|
638
|
+
account: Schema.Address.check(z.describe('Account whose role membership changed.')),
|
|
639
|
+
granted: z
|
|
640
|
+
.boolean()
|
|
641
|
+
.check(z.describe('Whether the role was granted (`true`) or revoked (`false`).'), z.meta({ examples: [true] })),
|
|
642
|
+
role: Schema.Hex.check(z.describe('Identifier of the role (a `bytes32` role hash).')),
|
|
643
|
+
sender: Schema.Address.check(z.describe('Address that made the role change.')),
|
|
644
|
+
signer: Signer,
|
|
645
|
+
})
|
|
646
|
+
.check(z.describe('A role was granted to or revoked from an account.')),
|
|
647
|
+
type: z
|
|
648
|
+
.literal('role-membership-set')
|
|
649
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
650
|
+
})
|
|
651
|
+
.check(z.meta({ title: 'Role membership set' })),
|
|
652
|
+
z
|
|
653
|
+
.object({
|
|
654
|
+
...base,
|
|
655
|
+
data: z
|
|
656
|
+
.object({
|
|
657
|
+
newAdminRole: Schema.Hex.check(z.describe('Identifier of the new admin role.')),
|
|
658
|
+
role: Schema.Hex.check(z.describe('Identifier of the role whose admin changed.')),
|
|
659
|
+
sender: Schema.Address.check(z.describe('Address that changed the admin role.')),
|
|
660
|
+
signer: Signer,
|
|
661
|
+
})
|
|
662
|
+
.check(z.describe('A role’s admin role was changed.')),
|
|
663
|
+
type: z
|
|
664
|
+
.literal('role-admin-set')
|
|
665
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
666
|
+
})
|
|
667
|
+
.check(z.meta({ title: 'Role admin set' })),
|
|
668
|
+
z
|
|
669
|
+
.object({
|
|
670
|
+
...base,
|
|
671
|
+
data: z
|
|
672
|
+
.object({
|
|
673
|
+
signer: Signer,
|
|
674
|
+
token: Schema.Address.check(z.describe('Token the user will pay fees in.')),
|
|
675
|
+
user: Schema.Address.check(z.describe('Address whose fee token was set.')),
|
|
676
|
+
})
|
|
677
|
+
.check(z.describe('An account’s fee-payment token was set.')),
|
|
678
|
+
type: z
|
|
679
|
+
.literal('fee-user-token-set')
|
|
680
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
681
|
+
})
|
|
682
|
+
.check(z.meta({ title: 'Fee user token set' })),
|
|
683
|
+
z
|
|
684
|
+
.object({
|
|
685
|
+
...base,
|
|
686
|
+
data: z
|
|
687
|
+
.object({
|
|
688
|
+
signer: Signer,
|
|
689
|
+
token: Schema.Address.check(z.describe('Token the validator will be paid fees in.')),
|
|
690
|
+
validator: Schema.Address.check(z.describe('Validator whose payout token was set.')),
|
|
691
|
+
})
|
|
692
|
+
.check(z.describe('A validator’s fee-payout token was set.')),
|
|
693
|
+
type: z
|
|
694
|
+
.literal('fee-validator-token-set')
|
|
695
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
696
|
+
})
|
|
697
|
+
.check(z.meta({ title: 'Fee validator token set' })),
|
|
698
|
+
z
|
|
699
|
+
.object({
|
|
700
|
+
...base,
|
|
701
|
+
data: z
|
|
702
|
+
.object({
|
|
703
|
+
policyId: Schema.DecimalString.check(z.describe('Identifier of the created policy.')),
|
|
704
|
+
policyType: z
|
|
705
|
+
.number()
|
|
706
|
+
.check(z.int(), z.describe('Numeric policy type.'), z.meta({ examples: [0] })),
|
|
707
|
+
signer: Signer,
|
|
708
|
+
updater: Schema.Address.check(z.describe('Address that created the policy.')),
|
|
709
|
+
})
|
|
710
|
+
.check(z.describe('A TIP-403 compliance policy was created.')),
|
|
711
|
+
type: z
|
|
712
|
+
.literal('policy-created')
|
|
713
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
714
|
+
})
|
|
715
|
+
.check(z.meta({ title: 'Policy created' })),
|
|
716
|
+
z
|
|
717
|
+
.object({
|
|
718
|
+
...base,
|
|
719
|
+
data: z
|
|
720
|
+
.object({
|
|
721
|
+
admin: Schema.Address.check(z.describe('New admin of the policy.')),
|
|
722
|
+
policyId: Schema.DecimalString.check(z.describe('Identifier of the policy.')),
|
|
723
|
+
signer: Signer,
|
|
724
|
+
updater: Schema.Address.check(z.describe('Address that changed the policy admin.')),
|
|
725
|
+
})
|
|
726
|
+
.check(z.describe('A TIP-403 policy’s admin was changed.')),
|
|
727
|
+
type: z
|
|
728
|
+
.literal('policy-admin-set')
|
|
729
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
730
|
+
})
|
|
731
|
+
.check(z.meta({ title: 'Policy admin set' })),
|
|
732
|
+
z
|
|
733
|
+
.object({
|
|
734
|
+
...base,
|
|
735
|
+
data: z
|
|
736
|
+
.object({
|
|
737
|
+
account: Schema.Address.check(z.describe('Account whose whitelist status changed.')),
|
|
738
|
+
allowed: z
|
|
739
|
+
.boolean()
|
|
740
|
+
.check(z.describe('Whether the account is now whitelisted.'), z.meta({ examples: [true] })),
|
|
741
|
+
policyId: Schema.DecimalString.check(z.describe('Identifier of the policy.')),
|
|
742
|
+
signer: Signer,
|
|
743
|
+
updater: Schema.Address.check(z.describe('Address that changed the whitelist.')),
|
|
744
|
+
})
|
|
745
|
+
.check(z.describe('An account’s whitelist status on a policy was changed.')),
|
|
746
|
+
type: z
|
|
747
|
+
.literal('whitelist-updated')
|
|
748
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
749
|
+
})
|
|
750
|
+
.check(z.meta({ title: 'Whitelist updated' })),
|
|
751
|
+
z
|
|
752
|
+
.object({
|
|
753
|
+
...base,
|
|
754
|
+
data: z
|
|
755
|
+
.object({
|
|
756
|
+
account: Schema.Address.check(z.describe('Account whose blacklist status changed.')),
|
|
757
|
+
policyId: Schema.DecimalString.check(z.describe('Identifier of the policy.')),
|
|
758
|
+
restricted: z
|
|
759
|
+
.boolean()
|
|
760
|
+
.check(z.describe('Whether the account is now blacklisted.'), z.meta({ examples: [true] })),
|
|
761
|
+
signer: Signer,
|
|
762
|
+
updater: Schema.Address.check(z.describe('Address that changed the blacklist.')),
|
|
763
|
+
})
|
|
764
|
+
.check(z.describe('An account’s blacklist status on a policy was changed.')),
|
|
765
|
+
type: z
|
|
766
|
+
.literal('blacklist-updated')
|
|
767
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
768
|
+
})
|
|
769
|
+
.check(z.meta({ title: 'Blacklist updated' })),
|
|
770
|
+
z
|
|
771
|
+
.object({
|
|
772
|
+
...base,
|
|
773
|
+
data: z
|
|
774
|
+
.object({
|
|
775
|
+
creator: Schema.Address.check(z.describe('Address that created the compound policy.')),
|
|
776
|
+
mintRecipientPolicyId: Schema.DecimalString.check(z.describe('Sub-policy applied to mint recipients.')),
|
|
777
|
+
policyId: Schema.DecimalString.check(z.describe('Identifier of the created compound policy.')),
|
|
778
|
+
recipientPolicyId: Schema.DecimalString.check(z.describe('Sub-policy applied to recipients.')),
|
|
779
|
+
senderPolicyId: Schema.DecimalString.check(z.describe('Sub-policy applied to senders.')),
|
|
780
|
+
signer: Signer,
|
|
781
|
+
})
|
|
782
|
+
.check(z.describe('A TIP-403 compound policy was created from sub-policies.')),
|
|
783
|
+
type: z
|
|
784
|
+
.literal('compound-policy-created')
|
|
785
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
786
|
+
})
|
|
787
|
+
.check(z.meta({ title: 'Compound policy created' })),
|
|
788
|
+
z
|
|
789
|
+
.object({
|
|
790
|
+
...base,
|
|
791
|
+
data: z
|
|
792
|
+
.object({
|
|
793
|
+
masterAddress: Schema.Address.check(z.describe('Address of the registered master contract.')),
|
|
794
|
+
masterId: Schema.Hex.check(z.describe('Identifier of the master contract (a `bytes4` selector).')),
|
|
795
|
+
signer: Signer,
|
|
796
|
+
})
|
|
797
|
+
.check(z.describe('A master contract was registered in the address registry.')),
|
|
798
|
+
type: z
|
|
799
|
+
.literal('master-registered')
|
|
800
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
801
|
+
})
|
|
802
|
+
.check(z.meta({ title: 'Master registered' })),
|
|
803
|
+
z
|
|
804
|
+
.object({
|
|
805
|
+
...base,
|
|
806
|
+
data: z
|
|
807
|
+
.object({
|
|
808
|
+
account: Schema.Address.check(z.describe('Account whose nonce advanced.')),
|
|
809
|
+
nonce: Schema.DecimalString.check(z.describe('New nonce value.')),
|
|
810
|
+
nonceKey: Schema.DecimalString.check(z.describe('Nonce key that was advanced.')),
|
|
811
|
+
signer: Signer,
|
|
812
|
+
})
|
|
813
|
+
.check(z.describe('An account or access-key nonce was advanced.')),
|
|
814
|
+
type: z
|
|
815
|
+
.literal('nonce-incremented')
|
|
816
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
817
|
+
})
|
|
818
|
+
.check(z.meta({ title: 'Nonce incremented' })),
|
|
819
|
+
z
|
|
820
|
+
.object({
|
|
821
|
+
...base,
|
|
822
|
+
data: z
|
|
823
|
+
.object({
|
|
824
|
+
account: Schema.Address.check(z.describe('Account the witness belongs to.')),
|
|
825
|
+
signer: Signer,
|
|
826
|
+
witness: Schema.Hex.check(z.describe('Witness commitment recorded for the key authorization.')),
|
|
827
|
+
})
|
|
828
|
+
.check(z.describe('A key-authorization witness was recorded.')),
|
|
829
|
+
type: z
|
|
830
|
+
.literal('key-authorization-witness')
|
|
831
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
832
|
+
})
|
|
833
|
+
.check(z.meta({ title: 'Key authorization witnessed' })),
|
|
834
|
+
z
|
|
835
|
+
.object({
|
|
836
|
+
...base,
|
|
837
|
+
data: z
|
|
838
|
+
.object({
|
|
839
|
+
account: Schema.Address.check(z.describe('Account the witness belonged to.')),
|
|
840
|
+
signer: Signer,
|
|
841
|
+
witness: Schema.Hex.check(z.describe('Witness commitment that was consumed.')),
|
|
842
|
+
})
|
|
843
|
+
.check(z.describe('A key-authorization witness was consumed.')),
|
|
844
|
+
type: z
|
|
845
|
+
.literal('key-authorization-witness-burned')
|
|
846
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
847
|
+
})
|
|
848
|
+
.check(z.meta({ title: 'Key authorization witness burned' })),
|
|
849
|
+
z
|
|
850
|
+
.object({
|
|
851
|
+
...base,
|
|
852
|
+
data: z
|
|
853
|
+
.object({
|
|
854
|
+
egress: z
|
|
855
|
+
.string()
|
|
856
|
+
.check(z.describe('Egress network endpoint.'), z.meta({ examples: ['https://validator.example.com:8443'] })),
|
|
857
|
+
feeRecipient: Schema.Address.check(z.describe('Validator fee recipient address.')),
|
|
858
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
859
|
+
ingress: z
|
|
860
|
+
.string()
|
|
861
|
+
.check(z.describe('Ingress network endpoint.'), z.meta({ examples: ['https://validator.example.com:8443'] })),
|
|
862
|
+
publicKey: Schema.Hex.check(z.describe('Validator public key.')),
|
|
863
|
+
signer: Signer,
|
|
864
|
+
validator: Schema.Address.check(z.describe('Validator address.')),
|
|
865
|
+
})
|
|
866
|
+
.check(z.describe('A validator was added to the validator set.')),
|
|
867
|
+
type: z
|
|
868
|
+
.literal('validator-added')
|
|
869
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
870
|
+
})
|
|
871
|
+
.check(z.meta({ title: 'Validator added' })),
|
|
872
|
+
z
|
|
873
|
+
.object({
|
|
874
|
+
...base,
|
|
875
|
+
data: z
|
|
876
|
+
.object({
|
|
877
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
878
|
+
signer: Signer,
|
|
879
|
+
validator: Schema.Address.check(z.describe('Validator address.')),
|
|
880
|
+
})
|
|
881
|
+
.check(z.describe('A validator was deactivated.')),
|
|
882
|
+
type: z
|
|
883
|
+
.literal('validator-deactivated')
|
|
884
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
885
|
+
})
|
|
886
|
+
.check(z.meta({ title: 'Validator deactivated' })),
|
|
887
|
+
z
|
|
888
|
+
.object({
|
|
889
|
+
...base,
|
|
890
|
+
data: z
|
|
891
|
+
.object({
|
|
892
|
+
caller: Schema.Address.check(z.describe('Address that rotated the validator.')),
|
|
893
|
+
deactivatedIndex: Schema.DecimalString.check(z.describe('Index of the deactivated validator entry.')),
|
|
894
|
+
egress: z
|
|
895
|
+
.string()
|
|
896
|
+
.check(z.describe('Egress network endpoint.'), z.meta({ examples: ['https://validator.example.com:8443'] })),
|
|
897
|
+
index: Schema.DecimalString.check(z.describe('New validator index.')),
|
|
898
|
+
ingress: z
|
|
899
|
+
.string()
|
|
900
|
+
.check(z.describe('Ingress network endpoint.'), z.meta({ examples: ['https://validator.example.com:8443'] })),
|
|
901
|
+
newPublicKey: Schema.Hex.check(z.describe('New validator public key.')),
|
|
902
|
+
oldPublicKey: Schema.Hex.check(z.describe('Previous validator public key.')),
|
|
903
|
+
signer: Signer,
|
|
904
|
+
validator: Schema.Address.check(z.describe('Validator address.')),
|
|
905
|
+
})
|
|
906
|
+
.check(z.describe('A validator’s keys and endpoints were rotated.')),
|
|
907
|
+
type: z
|
|
908
|
+
.literal('validator-rotated')
|
|
909
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
910
|
+
})
|
|
911
|
+
.check(z.meta({ title: 'Validator rotated' })),
|
|
912
|
+
z
|
|
913
|
+
.object({
|
|
914
|
+
...base,
|
|
915
|
+
data: z
|
|
916
|
+
.object({
|
|
917
|
+
caller: Schema.Address.check(z.describe('Address that changed the fee recipient.')),
|
|
918
|
+
feeRecipient: Schema.Address.check(z.describe('New validator fee recipient.')),
|
|
919
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
920
|
+
signer: Signer,
|
|
921
|
+
})
|
|
922
|
+
.check(z.describe('A validator’s fee recipient was changed.')),
|
|
923
|
+
type: z
|
|
924
|
+
.literal('validator-fee-recipient-set')
|
|
925
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
926
|
+
})
|
|
927
|
+
.check(z.meta({ title: 'Validator fee recipient set' })),
|
|
928
|
+
z
|
|
929
|
+
.object({
|
|
930
|
+
...base,
|
|
931
|
+
data: z
|
|
932
|
+
.object({
|
|
933
|
+
caller: Schema.Address.check(z.describe('Address that changed the endpoints.')),
|
|
934
|
+
egress: z
|
|
935
|
+
.string()
|
|
936
|
+
.check(z.describe('Egress network endpoint.'), z.meta({ examples: ['https://validator.example.com:8443'] })),
|
|
937
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
938
|
+
ingress: z
|
|
939
|
+
.string()
|
|
940
|
+
.check(z.describe('Ingress network endpoint.'), z.meta({ examples: ['https://validator.example.com:8443'] })),
|
|
941
|
+
signer: Signer,
|
|
942
|
+
})
|
|
943
|
+
.check(z.describe('A validator’s network endpoints were changed.')),
|
|
944
|
+
type: z
|
|
945
|
+
.literal('validator-ip-set')
|
|
946
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
947
|
+
})
|
|
948
|
+
.check(z.meta({ title: 'Validator IP set' })),
|
|
949
|
+
z
|
|
950
|
+
.object({
|
|
951
|
+
...base,
|
|
952
|
+
data: z
|
|
953
|
+
.object({
|
|
954
|
+
caller: Schema.Address.check(z.describe('Address that transferred ownership.')),
|
|
955
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
956
|
+
newAddress: Schema.Address.check(z.describe('New validator owner address.')),
|
|
957
|
+
oldAddress: Schema.Address.check(z.describe('Previous validator owner address.')),
|
|
958
|
+
signer: Signer,
|
|
959
|
+
})
|
|
960
|
+
.check(z.describe('A validator’s owner address was changed.')),
|
|
961
|
+
type: z
|
|
962
|
+
.literal('validator-ownership-transferred')
|
|
963
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
964
|
+
})
|
|
965
|
+
.check(z.meta({ title: 'Validator ownership transferred' })),
|
|
966
|
+
z
|
|
967
|
+
.object({
|
|
968
|
+
...base,
|
|
969
|
+
data: z
|
|
970
|
+
.object({
|
|
971
|
+
newOwner: Schema.Address.check(z.describe('New contract owner.')),
|
|
972
|
+
oldOwner: Schema.Address.check(z.describe('Previous contract owner.')),
|
|
973
|
+
signer: Signer,
|
|
974
|
+
})
|
|
975
|
+
.check(z.describe('Contract ownership was transferred.')),
|
|
976
|
+
type: z
|
|
977
|
+
.literal('ownership-transferred')
|
|
978
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
979
|
+
})
|
|
980
|
+
.check(z.meta({ title: 'Ownership transferred' })),
|
|
981
|
+
z
|
|
982
|
+
.object({
|
|
983
|
+
...base,
|
|
984
|
+
data: z
|
|
985
|
+
.object({
|
|
986
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
987
|
+
publicKey: Schema.Hex.check(z.describe('Validator public key.')),
|
|
988
|
+
signer: Signer,
|
|
989
|
+
validator: Schema.Address.check(z.describe('Validator address.')),
|
|
990
|
+
})
|
|
991
|
+
.check(z.describe('A validator was migrated.')),
|
|
992
|
+
type: z
|
|
993
|
+
.literal('validator-migrated')
|
|
994
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
995
|
+
})
|
|
996
|
+
.check(z.meta({ title: 'Validator migrated' })),
|
|
997
|
+
z
|
|
998
|
+
.object({
|
|
999
|
+
...base,
|
|
1000
|
+
data: z
|
|
1001
|
+
.object({
|
|
1002
|
+
index: Schema.DecimalString.check(z.describe('Validator index.')),
|
|
1003
|
+
publicKey: Schema.Hex.check(z.describe('Validator public key.')),
|
|
1004
|
+
signer: Signer,
|
|
1005
|
+
validator: Schema.Address.check(z.describe('Validator address.')),
|
|
1006
|
+
})
|
|
1007
|
+
.check(z.describe('A validator migration was skipped.')),
|
|
1008
|
+
type: z
|
|
1009
|
+
.literal('validator-migration-skipped')
|
|
1010
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
1011
|
+
})
|
|
1012
|
+
.check(z.meta({ title: 'Validator migration skipped' })),
|
|
1013
|
+
z
|
|
1014
|
+
.object({
|
|
1015
|
+
...base,
|
|
1016
|
+
data: z
|
|
1017
|
+
.object({
|
|
1018
|
+
nextEpoch: Schema.DecimalString.check(z.describe('Epoch the rotation is scheduled for.')),
|
|
1019
|
+
previousEpoch: Schema.DecimalString.check(z.describe('Previous rotation epoch.')),
|
|
1020
|
+
signer: Signer,
|
|
1021
|
+
})
|
|
1022
|
+
.check(z.describe('A network-identity rotation epoch was scheduled.')),
|
|
1023
|
+
type: z
|
|
1024
|
+
.literal('network-identity-rotation-epoch-set')
|
|
1025
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
1026
|
+
})
|
|
1027
|
+
.check(z.meta({ title: 'Network identity rotation epoch set' })),
|
|
1028
|
+
z
|
|
1029
|
+
.object({
|
|
1030
|
+
...base,
|
|
1031
|
+
data: z
|
|
1032
|
+
.object({
|
|
1033
|
+
height: Schema.DecimalString.check(z.describe('Block height at initialization.')),
|
|
1034
|
+
signer: Signer,
|
|
1035
|
+
})
|
|
1036
|
+
.check(z.describe('A contract was initialized.')),
|
|
1037
|
+
type: z
|
|
1038
|
+
.literal('initialized')
|
|
1039
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
1040
|
+
})
|
|
1041
|
+
.check(z.meta({ title: 'Initialized' })),
|
|
1042
|
+
z
|
|
1043
|
+
.object({
|
|
1044
|
+
...base,
|
|
1045
|
+
data: z
|
|
1046
|
+
.object({ signer: Signer })
|
|
1047
|
+
.check(z.describe('A transaction the address sent or paid for that Tempo could not classify into a known activity type. The decoded and raw logs are surfaced in the top-level `events` array.')),
|
|
1048
|
+
type: z
|
|
1049
|
+
.literal('unknown')
|
|
1050
|
+
.check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
|
|
1051
|
+
})
|
|
1052
|
+
.check(z.meta({ title: 'Unknown' })),
|
|
1053
|
+
];
|
|
1054
|
+
/** A single classified activity item. */
|
|
1055
|
+
schema.Item = z
|
|
1056
|
+
.union(schema.items)
|
|
1057
|
+
.check(z.describe('One classified activity item in the address feed.'));
|
|
1058
|
+
/** A folded access-key group: consecutive same-signer items on one UTC day. */
|
|
1059
|
+
schema.Group = z
|
|
1060
|
+
.object({
|
|
1061
|
+
data: z
|
|
1062
|
+
.object({
|
|
1063
|
+
items: z.array(schema.Item).check(z.describe('Activity items included in this group.')),
|
|
1064
|
+
signer: Schema.Address.check(z.describe('Access-key address that signed every item in the group.')),
|
|
1065
|
+
})
|
|
1066
|
+
.check(z.describe('Grouped activity entries folded together for readability.')),
|
|
1067
|
+
perspective: z
|
|
1068
|
+
.enum(['incoming', 'outgoing'])
|
|
1069
|
+
.check(z.describe('The shared perspective of the items in this group (see an item’s `perspective`).')),
|
|
1070
|
+
id: z.string().check(z.describe('Stable group ID derived from the activity IDs inside the group.'), z.meta({
|
|
1071
|
+
examples: [
|
|
1072
|
+
'0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-3..0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-7:5',
|
|
1073
|
+
],
|
|
1074
|
+
})),
|
|
1075
|
+
timestamp: z
|
|
1076
|
+
.iso.datetime()
|
|
1077
|
+
.check(z.describe('Timestamp of the first item in the group, as an ISO 8601 string.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })), // prettier-ignore
|
|
1078
|
+
title: z
|
|
1079
|
+
.literal('Group')
|
|
1080
|
+
.check(z.describe('Human-readable label for this entry, suitable for display in a feed.')),
|
|
1081
|
+
type: z.literal('group'),
|
|
1082
|
+
})
|
|
1083
|
+
.check(z.meta({ title: 'Group' }), z.describe('A group of consecutive activity items signed by the same access key.'));
|
|
1084
|
+
/** A single activity item, or a folded access-key group. */
|
|
1085
|
+
schema.Entry = z
|
|
1086
|
+
.union([...schema.items, schema.Group])
|
|
1087
|
+
.check(z.describe('Either one activity item, or a grouped set of access-key activity items.'));
|
|
1088
|
+
/** Schemas for the getAddressActivities operation. */
|
|
1089
|
+
let getAddressActivities;
|
|
1090
|
+
(function (getAddressActivities) {
|
|
1091
|
+
/** Path parameters for address activity requests. */
|
|
1092
|
+
getAddressActivities.Params = z
|
|
1093
|
+
.object({
|
|
1094
|
+
address: Schema.Address.check(z.describe('Account address whose activity you want to list.')),
|
|
1095
|
+
})
|
|
1096
|
+
.check(z.describe('Path parameters for the address activity request.'));
|
|
1097
|
+
/** Query parameters for address activity requests. */
|
|
1098
|
+
getAddressActivities.Query = z
|
|
1099
|
+
.strictObject({
|
|
1100
|
+
chainId: Schema.ChainIdQuery,
|
|
1101
|
+
cursor: Schema.Cursor,
|
|
1102
|
+
// Folding is opt-in: by default every item is returned at the root.
|
|
1103
|
+
group: z
|
|
1104
|
+
._default(Schema.booleanQuery(), false)
|
|
1105
|
+
.check(z.describe('Set to `true` to fold consecutive items signed by the same access key on the same UTC day into `group` entries. Defaults to `false`, which returns every item separately.'), z.meta({ examples: [true] })),
|
|
1106
|
+
limit: Schema.Limit,
|
|
1107
|
+
// Raw-log capture is opt-in: it can add a large `events` array to every
|
|
1108
|
+
// item, so the default feed stays lean.
|
|
1109
|
+
logs: z
|
|
1110
|
+
._default(Schema.booleanQuery(), false)
|
|
1111
|
+
.check(z.describe('Set to `true` to attach each item’s full transaction log set (decoded when possible) as a top-level `events` array, so no co-located log is hidden. Defaults to `false`. `unknown` items always include it.'), z.meta({ examples: [true] })),
|
|
1112
|
+
})
|
|
1113
|
+
.check(z.describe('Query parameters for the address activity request. This feed uses cursor pagination only because activity classification can fold a variable number of rows into each entry, so positional `page` pagination would not be stable.'));
|
|
1114
|
+
/**
|
|
1115
|
+
* Page of classified activity. Mirrors the wallet activity feed: a
|
|
1116
|
+
* newest-first list of `data` entries (items, or access-key groups when
|
|
1117
|
+
* `group=true`) plus the root `nextCursor` pagination field shared with
|
|
1118
|
+
* other list endpoints.
|
|
1119
|
+
*/
|
|
1120
|
+
getAddressActivities.Response = z
|
|
1121
|
+
.object({
|
|
1122
|
+
data: z.array(schema.Entry).check(z.describe('Activity feed entries for the address.')),
|
|
1123
|
+
nextCursor: Schema.NextCursor,
|
|
1124
|
+
})
|
|
1125
|
+
.check(z.describe('A page of human-readable activity for the address.'));
|
|
1126
|
+
})(getAddressActivities = schema.getAddressActivities || (schema.getAddressActivities = {}));
|
|
1127
|
+
/** Schemas for the getTransactionActivities operation. */
|
|
1128
|
+
let getTransactionActivities;
|
|
1129
|
+
(function (getTransactionActivities) {
|
|
1130
|
+
/** Path parameters for transaction activity requests. */
|
|
1131
|
+
getTransactionActivities.Params = z
|
|
1132
|
+
.object({
|
|
1133
|
+
transactionHash: Schema.hash(exampleHash).check(z.describe('A 32-byte hash, `0x`-prefixed and returned in lowercase, that identifies the transaction whose activity you want to list.')),
|
|
1134
|
+
})
|
|
1135
|
+
.check(z.describe('Path parameters for the transaction activity request.'));
|
|
1136
|
+
/** Query parameters for transaction activity requests. */
|
|
1137
|
+
getTransactionActivities.Query = z
|
|
1138
|
+
.strictObject({
|
|
1139
|
+
chainId: Schema.ChainIdQuery,
|
|
1140
|
+
// Raw-log capture is opt-in: it can add a large `events` array to every
|
|
1141
|
+
// item, so the default response stays lean.
|
|
1142
|
+
logs: z
|
|
1143
|
+
._default(Schema.booleanQuery(), false)
|
|
1144
|
+
.check(z.describe('Set to `true` to attach each item’s full transaction log set (decoded when possible) as a top-level `events` array, so no co-located log is hidden. Defaults to `false`. `unknown` items always include it.'), z.meta({ examples: [true] })),
|
|
1145
|
+
})
|
|
1146
|
+
.check(z.describe('Query parameters for the transaction activity request.'));
|
|
1147
|
+
/**
|
|
1148
|
+
* The classified activity for one transaction: a list of `data` items
|
|
1149
|
+
* (usually one headline item, occasionally more) describing what the
|
|
1150
|
+
* transaction did onchain. Unpaginated — a single transaction is finite —
|
|
1151
|
+
* so there is no `nextCursor`.
|
|
1152
|
+
*/
|
|
1153
|
+
getTransactionActivities.Response = z
|
|
1154
|
+
.object({
|
|
1155
|
+
data: z.array(schema.Item).check(z.describe('Activity items classified from the transaction.')),
|
|
1156
|
+
})
|
|
1157
|
+
.check(z.describe('The human-readable activity that happened on a transaction.'));
|
|
1158
|
+
})(getTransactionActivities = schema.getTransactionActivities || (schema.getTransactionActivities = {}));
|
|
1159
|
+
})(schema || (schema = {}));
|
|
1160
|
+
/**
|
|
1161
|
+
* Human-readable label for each activity {@link Item.type}, mirroring the per-variant
|
|
1162
|
+
* OpenAPI schema titles. Used to stamp {@link Item.title} during {@link list}.
|
|
1163
|
+
*/
|
|
1164
|
+
const titleByType = {
|
|
1165
|
+
approval: 'Allowance approved',
|
|
1166
|
+
'access-key-created': 'Access key created',
|
|
1167
|
+
'access-key-revoked': 'Access key revoked',
|
|
1168
|
+
'blacklist-updated': 'Blacklist updated',
|
|
1169
|
+
burn: 'Token burned',
|
|
1170
|
+
'burn-blocked': 'Burn blocked',
|
|
1171
|
+
'channel-close-cancelled': 'Channel close cancelled',
|
|
1172
|
+
'channel-closed': 'Channel closed',
|
|
1173
|
+
'channel-funded': 'Channel funded',
|
|
1174
|
+
'channel-opened': 'Channel opened',
|
|
1175
|
+
'channel-settled': 'Channel settled',
|
|
1176
|
+
'compound-policy-created': 'Compound policy created',
|
|
1177
|
+
'fee-rebalance-swap': 'Fees rebalanced',
|
|
1178
|
+
'fee-user-token-set': 'Fee user token set',
|
|
1179
|
+
'fee-validator-token-set': 'Fee validator token set',
|
|
1180
|
+
'fees-distributed': 'Fees distributed',
|
|
1181
|
+
initialized: 'Initialized',
|
|
1182
|
+
'key-authorization-witness': 'Key authorization witnessed',
|
|
1183
|
+
'key-authorization-witness-burned': 'Key authorization witness burned',
|
|
1184
|
+
'master-registered': 'Master registered',
|
|
1185
|
+
mint: 'Token minted',
|
|
1186
|
+
'network-identity-rotation-epoch-set': 'Network identity rotation epoch set',
|
|
1187
|
+
'nonce-incremented': 'Nonce incremented',
|
|
1188
|
+
'order-cancelled': 'Order cancelled',
|
|
1189
|
+
'order-filled': 'Order filled',
|
|
1190
|
+
'order-flipped': 'Order flipped',
|
|
1191
|
+
'order-placed': 'Order placed',
|
|
1192
|
+
'ownership-transferred': 'Ownership transferred',
|
|
1193
|
+
'pair-created': 'Trading pair created',
|
|
1194
|
+
'policy-admin-set': 'Policy admin set',
|
|
1195
|
+
'policy-created': 'Policy created',
|
|
1196
|
+
'reward-distributed': 'Reward distributed',
|
|
1197
|
+
'reward-recipient-set': 'Reward recipient set',
|
|
1198
|
+
'role-admin-set': 'Role admin set',
|
|
1199
|
+
'role-membership-set': 'Role membership set',
|
|
1200
|
+
'session-closed': 'Session closed',
|
|
1201
|
+
'spending-limit-updated': 'Spending limit updated',
|
|
1202
|
+
swap: 'Tokens swapped',
|
|
1203
|
+
'token-created': 'Token created',
|
|
1204
|
+
'token-logo-set': 'Token logo set',
|
|
1205
|
+
'token-next-quote-token-set': 'Token next quote token set',
|
|
1206
|
+
'token-pause-set': 'Token pause set',
|
|
1207
|
+
'token-quote-token-set': 'Token quote token set',
|
|
1208
|
+
'token-supply-cap-set': 'Token supply cap set',
|
|
1209
|
+
'token-transfer-policy-set': 'Token transfer policy set',
|
|
1210
|
+
transfer: 'Token transferred',
|
|
1211
|
+
unknown: 'Unknown',
|
|
1212
|
+
'validator-added': 'Validator added',
|
|
1213
|
+
'validator-deactivated': 'Validator deactivated',
|
|
1214
|
+
'validator-fee-recipient-set': 'Validator fee recipient set',
|
|
1215
|
+
'validator-ip-set': 'Validator IP set',
|
|
1216
|
+
'validator-migrated': 'Validator migrated',
|
|
1217
|
+
'validator-migration-skipped': 'Validator migration skipped',
|
|
1218
|
+
'validator-ownership-transferred': 'Validator ownership transferred',
|
|
1219
|
+
'validator-rotated': 'Validator rotated',
|
|
1220
|
+
'whitelist-updated': 'Whitelist updated',
|
|
1221
|
+
};
|
|
1222
|
+
/** Stamps the display {@link Item.title} onto a classified item from {@link titleByType}. */
|
|
1223
|
+
function withTitle(item) {
|
|
1224
|
+
return { ...item, title: titleByType[item.type] };
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Human-readable label for each `viem/tempo` ABI event name, used to title the
|
|
1228
|
+
* entries of an item's raw `events` capture. Keyed by event name (not activity
|
|
1229
|
+
* type), so co-located logs read naturally even when they are not the headline.
|
|
1230
|
+
*/
|
|
1231
|
+
const titleByEventName = {
|
|
1232
|
+
AccessKeySpend: 'Access key spent',
|
|
1233
|
+
Approval: 'Allowance approved',
|
|
1234
|
+
BlacklistUpdated: 'Blacklist updated',
|
|
1235
|
+
Burn: 'Token burned',
|
|
1236
|
+
BurnBlocked: 'Burn blocked',
|
|
1237
|
+
ChannelClosed: 'Channel closed',
|
|
1238
|
+
ChannelOpened: 'Channel opened',
|
|
1239
|
+
CloseRequestCancelled: 'Channel close cancelled',
|
|
1240
|
+
CloseRequested: 'Channel close requested',
|
|
1241
|
+
CompoundPolicyCreated: 'Compound policy created',
|
|
1242
|
+
FeeRecipientUpdated: 'Validator fee recipient set',
|
|
1243
|
+
FeesDistributed: 'Fees distributed',
|
|
1244
|
+
Initialized: 'Initialized',
|
|
1245
|
+
IpAddressesUpdated: 'Validator IP set',
|
|
1246
|
+
KeyAuthorizationWitness: 'Key authorization witnessed',
|
|
1247
|
+
KeyAuthorizationWitnessBurned: 'Key authorization witness burned',
|
|
1248
|
+
KeyAuthorized: 'Access key created',
|
|
1249
|
+
KeyRevoked: 'Access key revoked',
|
|
1250
|
+
LogoURIUpdated: 'Token logo set',
|
|
1251
|
+
MasterRegistered: 'Master registered',
|
|
1252
|
+
Mint: 'Token minted',
|
|
1253
|
+
NetworkIdentityRotationEpochSet: 'Network identity rotation epoch set',
|
|
1254
|
+
NextQuoteTokenSet: 'Token next quote token set',
|
|
1255
|
+
NonceIncremented: 'Nonce incremented',
|
|
1256
|
+
OrderCancelled: 'Order cancelled',
|
|
1257
|
+
OrderFilled: 'Order filled',
|
|
1258
|
+
OrderFlipped: 'Order flipped',
|
|
1259
|
+
OrderPlaced: 'Order placed',
|
|
1260
|
+
OwnershipTransferred: 'Ownership transferred',
|
|
1261
|
+
PairCreated: 'Trading pair created',
|
|
1262
|
+
PauseStateUpdate: 'Token pause set',
|
|
1263
|
+
PolicyAdminUpdated: 'Policy admin set',
|
|
1264
|
+
PolicyCreated: 'Policy created',
|
|
1265
|
+
QuoteTokenUpdate: 'Token quote token set',
|
|
1266
|
+
RebalanceSwap: 'Fees rebalanced',
|
|
1267
|
+
RewardDistributed: 'Reward distributed',
|
|
1268
|
+
RewardRecipientSet: 'Reward recipient set',
|
|
1269
|
+
RoleAdminUpdated: 'Role admin set',
|
|
1270
|
+
RoleMembershipUpdated: 'Role membership set',
|
|
1271
|
+
Settled: 'Channel settled',
|
|
1272
|
+
SkippedValidatorMigration: 'Validator migration skipped',
|
|
1273
|
+
SpendingLimitUpdated: 'Spending limit updated',
|
|
1274
|
+
SupplyCapUpdate: 'Token supply cap set',
|
|
1275
|
+
TokenCreated: 'Token created',
|
|
1276
|
+
TopUp: 'Channel funded',
|
|
1277
|
+
Transfer: 'Token transferred',
|
|
1278
|
+
TransferPolicyUpdate: 'Token transfer policy set',
|
|
1279
|
+
TransferWithMemo: 'Token transferred',
|
|
1280
|
+
UserTokenSet: 'Fee user token set',
|
|
1281
|
+
ValidatorAdded: 'Validator added',
|
|
1282
|
+
ValidatorDeactivated: 'Validator deactivated',
|
|
1283
|
+
ValidatorMigrated: 'Validator migrated',
|
|
1284
|
+
ValidatorOwnershipTransferred: 'Validator ownership transferred',
|
|
1285
|
+
ValidatorRotated: 'Validator rotated',
|
|
1286
|
+
ValidatorTokenSet: 'Fee validator token set',
|
|
1287
|
+
WhitelistUpdated: 'Whitelist updated',
|
|
1288
|
+
};
|
|
1289
|
+
// keccak256("CloseRequested(bytes32,address,address,uint256)") — emitted by a
|
|
1290
|
+
// payment channel when settlement is requested; matched on the raw selector
|
|
1291
|
+
// because it is not part of the merged `viem/tempo` ABI.
|
|
1292
|
+
const closeRequestedSelector = '0xf5a36fc00a96cbb9cf1f8f59299165e1d8ffffe94396d82904b4da524d16bbce';
|
|
1293
|
+
// keccak256("AccessKeySpend(address,address,address,uint256,uint256)") — emitted
|
|
1294
|
+
// by the accountKeychain whenever a tx is signed by an access key. topic2 is the
|
|
1295
|
+
// access-key publicKey, i.e. the real signer.
|
|
1296
|
+
const accessKeySpendSelector = '0xe0815e3aaadddf4dd75bde97fc060f0c38afe18e87a169be86a3f5c28247f192';
|
|
1297
|
+
/** TIP-20 / ERC-20 `Transfer` event signature for the decoded CTE. */
|
|
1298
|
+
const transferSignature = 'event Transfer(address indexed from, address indexed to, uint256 value)';
|
|
1299
|
+
// TIP-20 payment-channel reserve precompile. Not exported by `viem/tempo`
|
|
1300
|
+
// `Addresses`, so the known address is pinned here: channel events
|
|
1301
|
+
// (`ChannelOpened`/`Settled`/`TopUp`/`ChannelClosed`) are emitted here, and
|
|
1302
|
+
// their paired TIP-20 `Transfer` legs use it as the funding counterparty, which
|
|
1303
|
+
// is how `channel-funded`/`channel-settled`/`channel-closed` recover the token.
|
|
1304
|
+
const tip20ChannelReserve = '0x4d50500000000000000000000000000000000000';
|
|
1305
|
+
/** Number of grouped entries to target before stopping the page-fetch loop. */
|
|
1306
|
+
const minGroupEntries = 5;
|
|
1307
|
+
/** Largest number of items folded into a single access-key group. */
|
|
1308
|
+
const maxGroupSize = 50;
|
|
1309
|
+
/** Token-bearing keys on an item's `data` payload, walked for enrichment. */
|
|
1310
|
+
const tokenKeys = ['destinationToken', 'refund', 'sourceToken'];
|
|
1311
|
+
/** Builds common public activity fields from the normalized transaction hash and representative log. */
|
|
1312
|
+
function baseFor(options) {
|
|
1313
|
+
return {
|
|
1314
|
+
perspective: options.perspective,
|
|
1315
|
+
id: `${options.transactionHash}-${options.logIndex}`,
|
|
1316
|
+
logIndex: options.logIndex,
|
|
1317
|
+
timestamp: options.timestamp,
|
|
1318
|
+
transactionHash: options.transactionHash,
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Creates address-scoped activity handlers, mounted under the `/addresses`
|
|
1323
|
+
* composer. Exposes `GET /:address/activities`.
|
|
1324
|
+
*
|
|
1325
|
+
* Runs the full activity classification pipeline: three TIDX sub-queries
|
|
1326
|
+
* (outbound `receipts ⋈ logs`, inbound transfers, outbound transfers) merged
|
|
1327
|
+
* and decoded into typed items (`transfer`/`mint`/`burn`/`swap`/
|
|
1328
|
+
* `approval`/`session-closed`/`access-key-created`/`access-key-revoked`/
|
|
1329
|
+
* `token-created`/`burn-blocked`/`channel-opened`/`channel-funded`/
|
|
1330
|
+
* `channel-settled`/`channel-closed`/`channel-close-cancelled`/
|
|
1331
|
+
* `order-placed`/`order-cancelled`/`order-filled`/`order-flipped`/
|
|
1332
|
+
* `pair-created`/`fees-distributed`/`fee-rebalance-swap`/`reward-distributed`/
|
|
1333
|
+
* `reward-recipient-set`/`spending-limit-updated`/ token, fee, policy, registry,
|
|
1334
|
+
* access-key-witness and validator admin events (e.g. `token-pause-set`,
|
|
1335
|
+
* `policy-created`, `validator-added`)/`unknown`), then folds
|
|
1336
|
+
* consecutive access-key-signed items into `group` entries. Pagination uses the
|
|
1337
|
+
* same `(block_num, tx_idx)` keyset cursor the pipeline emits.
|
|
1338
|
+
*/
|
|
1339
|
+
export function addresses() {
|
|
1340
|
+
return new Hono().get('/:address/activities', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getAddressActivities.Params, {
|
|
1341
|
+
code: 'address_invalid',
|
|
1342
|
+
message: 'Check the account address and try again.',
|
|
1343
|
+
}), OpenApi.validate('query', schema.getAddressActivities.Query, {
|
|
1344
|
+
code: 'query_invalid',
|
|
1345
|
+
message: 'Check the query parameters and try again.',
|
|
1346
|
+
}), OpenApi.describeRoute({
|
|
1347
|
+
description: 'Get a human-readable feed of what an address has been doing onchain, including payments sent and received, swaps, approvals, and more.',
|
|
1348
|
+
operationId: 'getAddressActivities',
|
|
1349
|
+
responses: OpenApi.responses({
|
|
1350
|
+
errors: { 502: 'Tempo could not read activity data from the upstream indexer.' },
|
|
1351
|
+
success: {
|
|
1352
|
+
description: 'A page of address activity entries.',
|
|
1353
|
+
schema: schema.getAddressActivities.Response,
|
|
1354
|
+
},
|
|
1355
|
+
}),
|
|
1356
|
+
summary: 'List address activities',
|
|
1357
|
+
tags: ['Activities'],
|
|
1358
|
+
}), Cache.response({
|
|
1359
|
+
cacheControl: Cache.policies.feed,
|
|
1360
|
+
name: 'tempo-api:addresses:v1',
|
|
1361
|
+
key: (c) => Cache.urlKey(c, schema.getAddressActivities.Query),
|
|
1362
|
+
}), async (c) => {
|
|
1363
|
+
if (Auth.narrowAccess)
|
|
1364
|
+
return Auth.paidAccessError(c);
|
|
1365
|
+
if (OpenApi.narrowValidation)
|
|
1366
|
+
return OpenApi.validationError(c, {
|
|
1367
|
+
code: 'address_invalid',
|
|
1368
|
+
message: 'Check the account address and try again.',
|
|
1369
|
+
});
|
|
1370
|
+
if (OpenApi.narrowValidation)
|
|
1371
|
+
return OpenApi.validationError(c, {
|
|
1372
|
+
code: 'query_invalid',
|
|
1373
|
+
message: 'Check the query parameters and try again.',
|
|
1374
|
+
});
|
|
1375
|
+
const { address } = c.req.valid('param');
|
|
1376
|
+
const query = c.req.valid('query');
|
|
1377
|
+
// Cursor pages are anchored below the head and effectively immutable, so
|
|
1378
|
+
// cache them aggressively; the head page keeps the route's short `feed`
|
|
1379
|
+
// default.
|
|
1380
|
+
if (query.cursor !== undefined)
|
|
1381
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
1382
|
+
try {
|
|
1383
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
1384
|
+
const provider = tidx({ chainId, tidx: c.get('getTidx')(chainId) });
|
|
1385
|
+
const { items, nextCursor } = await Timing.time(c, 'address_activities', () => Store.memoize(() => list(provider, {
|
|
1386
|
+
address: address,
|
|
1387
|
+
cursor: query.cursor,
|
|
1388
|
+
group: query.group,
|
|
1389
|
+
limit: query.limit,
|
|
1390
|
+
logs: query.logs,
|
|
1391
|
+
store: c.get('store'),
|
|
1392
|
+
}), {
|
|
1393
|
+
key: `activities:v1:${chainId}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
|
|
1394
|
+
store: c.get('store'),
|
|
1395
|
+
ttl: Ttl.seconds(15),
|
|
1396
|
+
}));
|
|
1397
|
+
// Resolve token metadata for every token referenced across the page and
|
|
1398
|
+
// spread it into each item's stub tokens. Goes through the shared
|
|
1399
|
+
// `Tokens.getTokenMetadata` path so it reuses the `token:v1:…` cache with
|
|
1400
|
+
// `/tokens` and `/transfers` instead of a parallel metadata fetch.
|
|
1401
|
+
await enrichTokens(c, { chainId, items });
|
|
1402
|
+
// Fold consecutive access-key-signed items into `group` entries only
|
|
1403
|
+
// when requested; otherwise every item is returned at the root.
|
|
1404
|
+
const data = query.group ? groupByAccessKey(items) : items;
|
|
1405
|
+
return c.json(Response.validated(schema.getAddressActivities.Response, {
|
|
1406
|
+
data,
|
|
1407
|
+
nextCursor,
|
|
1408
|
+
}), 200);
|
|
1409
|
+
}
|
|
1410
|
+
catch (cause) {
|
|
1411
|
+
return Response.upstream(c, cause);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Creates transaction-scoped activity handlers, mounted under the
|
|
1417
|
+
* `/transactions` composer. Exposes `GET /:transactionHash/activities`.
|
|
1418
|
+
*
|
|
1419
|
+
* Runs the same `groupByTx` → `classifyGroup` pipeline the address feed uses
|
|
1420
|
+
* over a single transaction's logs, anchored on the transaction sender as the
|
|
1421
|
+
* viewer (a transaction-scoped feed has no subject account). Returns the
|
|
1422
|
+
* classified item(s) — usually one headline activity — with the full per-tx
|
|
1423
|
+
* event set available via `logs=true`.
|
|
1424
|
+
*/
|
|
1425
|
+
export function transactions() {
|
|
1426
|
+
return new Hono().get('/:transactionHash/activities', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getTransactionActivities.Params, {
|
|
1427
|
+
code: 'transaction_invalid',
|
|
1428
|
+
message: 'Check the transaction hash and try again.',
|
|
1429
|
+
}), OpenApi.validate('query', schema.getTransactionActivities.Query, {
|
|
1430
|
+
code: 'query_invalid',
|
|
1431
|
+
message: 'Check the query parameters and try again.',
|
|
1432
|
+
}), OpenApi.describeRoute({
|
|
1433
|
+
description: 'Get a human-readable list of what happened on one transaction onchain, including payments, swaps, approvals, and more.',
|
|
1434
|
+
operationId: 'getTransactionActivities',
|
|
1435
|
+
responses: OpenApi.responses({
|
|
1436
|
+
errors: {
|
|
1437
|
+
404: {
|
|
1438
|
+
description: 'No mined transaction was found for that hash.',
|
|
1439
|
+
codes: ['transaction_not_found'],
|
|
1440
|
+
},
|
|
1441
|
+
502: 'Tempo could not read activity data from the upstream indexer.',
|
|
1442
|
+
},
|
|
1443
|
+
success: {
|
|
1444
|
+
description: 'The classified activity for the transaction.',
|
|
1445
|
+
schema: schema.getTransactionActivities.Response,
|
|
1446
|
+
},
|
|
1447
|
+
}),
|
|
1448
|
+
summary: 'List transaction activities',
|
|
1449
|
+
tags: ['Activities'],
|
|
1450
|
+
}), Cache.response({
|
|
1451
|
+
// Activity exists only once the transaction is mined; once mined it is
|
|
1452
|
+
// immutable. Default to no-store so a 404 (unknown / not-yet-mined hash)
|
|
1453
|
+
// is not cached with a long header that would mask the eventual result;
|
|
1454
|
+
// the handler upgrades the 200 to `immutable` below.
|
|
1455
|
+
cacheControl: Cache.policies.noStore,
|
|
1456
|
+
name: 'tempo-api:transactions:v1',
|
|
1457
|
+
key: (c) => Cache.urlKey(c, schema.getTransactionActivities.Query),
|
|
1458
|
+
}), async (c) => {
|
|
1459
|
+
if (Auth.narrowAccess)
|
|
1460
|
+
return Auth.paidAccessError(c);
|
|
1461
|
+
if (OpenApi.narrowValidation)
|
|
1462
|
+
return OpenApi.validationError(c, {
|
|
1463
|
+
code: 'transaction_invalid',
|
|
1464
|
+
message: 'Check the transaction hash and try again.',
|
|
1465
|
+
});
|
|
1466
|
+
if (OpenApi.narrowValidation)
|
|
1467
|
+
return OpenApi.validationError(c, {
|
|
1468
|
+
code: 'query_invalid',
|
|
1469
|
+
message: 'Check the query parameters and try again.',
|
|
1470
|
+
});
|
|
1471
|
+
const { transactionHash } = c.req.valid('param');
|
|
1472
|
+
const query = c.req.valid('query');
|
|
1473
|
+
const chainId = query.chainId ?? c.get('chainId');
|
|
1474
|
+
try {
|
|
1475
|
+
const items = await Timing.time(c, 'transaction_activities', () => Store.memoize(() => getTransactionActivities({
|
|
1476
|
+
chainId,
|
|
1477
|
+
logs: query.logs,
|
|
1478
|
+
store: c.get('store'),
|
|
1479
|
+
tidx: c.get('getTidx')(chainId),
|
|
1480
|
+
transactionHash,
|
|
1481
|
+
}), {
|
|
1482
|
+
key: `activities:v1:${chainId}:tx:${transactionHash.toLowerCase()}:${query.logs ?? false}`,
|
|
1483
|
+
store: c.get('store'),
|
|
1484
|
+
ttl: Ttl.seconds(15),
|
|
1485
|
+
}));
|
|
1486
|
+
if (items === null)
|
|
1487
|
+
return Response.error(c, {
|
|
1488
|
+
code: 'transaction_not_found',
|
|
1489
|
+
message: 'Transaction not found',
|
|
1490
|
+
status: 404,
|
|
1491
|
+
});
|
|
1492
|
+
// A mined transaction's activity is final, so cache it aggressively.
|
|
1493
|
+
Cache.setPolicy(c, Cache.policies.immutable);
|
|
1494
|
+
// Resolve token metadata for every token referenced and spread it into
|
|
1495
|
+
// each item's stub tokens (shared `token:v1:…` cache path).
|
|
1496
|
+
await enrichTokens(c, { chainId, items });
|
|
1497
|
+
return c.json(Response.validated(schema.getTransactionActivities.Response, { data: items }), 200);
|
|
1498
|
+
}
|
|
1499
|
+
catch (cause) {
|
|
1500
|
+
return Response.upstream(c, cause);
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Classifies the activity for a single transaction. Fetches the transaction's
|
|
1506
|
+
* receipt (for its block position, sender, and fee payer) and its logs, then
|
|
1507
|
+
* runs the same `groupByTx` → `classifyGroup` pipeline the address feed uses,
|
|
1508
|
+
* anchored on the transaction sender as the viewer. Returns `null` when no
|
|
1509
|
+
* receipt exists for the hash (an unknown or not-yet-mined transaction), or an
|
|
1510
|
+
* empty list for a mined transaction that emitted no classifiable logs.
|
|
1511
|
+
*
|
|
1512
|
+
* Items carry stub tokens; the caller enriches them via {@link enrichTokens}.
|
|
1513
|
+
*/
|
|
1514
|
+
export async function getTransactionActivities(options) {
|
|
1515
|
+
const { chainId, logs = false, tidx: tidxClient } = options;
|
|
1516
|
+
const hash = options.transactionHash.toLowerCase();
|
|
1517
|
+
// The `receipts` table is indexed by `tx_hash`, so this point lookup needs no
|
|
1518
|
+
// block bound; it also yields the block number the `logs` lookup requires.
|
|
1519
|
+
// Raw-table query (no signature-decoded CTE), so the inline SQL is cast to
|
|
1520
|
+
// `string`.
|
|
1521
|
+
const receiptResult = await tidxClient.fetch({
|
|
1522
|
+
chainId,
|
|
1523
|
+
query: `SELECT block_num, tx_idx, "from", fee_payer FROM receipts WHERE tx_hash = '${hash}' LIMIT 1`,
|
|
1524
|
+
});
|
|
1525
|
+
const receipt = receiptResult.rows[0];
|
|
1526
|
+
if (!receipt)
|
|
1527
|
+
return null;
|
|
1528
|
+
const blockNum = Number(Value.toNumber(receipt['block_num']));
|
|
1529
|
+
const feePayer = lowerOrNull(receipt['fee_payer']);
|
|
1530
|
+
const txSender = lowerOrNull(receipt['from']);
|
|
1531
|
+
// `logs` is sorted by block position, so a bare `tx_hash =` scans to genesis;
|
|
1532
|
+
// pair it with the receipt's block number (see `embedReceipts` in receipts.ts).
|
|
1533
|
+
const logsResult = await tidxClient.fetch({
|
|
1534
|
+
chainId,
|
|
1535
|
+
query: `SELECT tx_hash, block_num, block_timestamp, tx_idx, log_idx, address, selector, topic1, topic2, topic3, data FROM logs WHERE block_num = ${blockNum} AND tx_hash = '${hash}' ORDER BY log_idx ASC`,
|
|
1536
|
+
});
|
|
1537
|
+
const rows = logsResult.rows.map((r) => ({
|
|
1538
|
+
blockNum: Number(Value.toNumber(r['block_num'])),
|
|
1539
|
+
blockTimestamp: Number(Value.toNumber(r['block_timestamp'])),
|
|
1540
|
+
data: Value.toText(r['data']) ?? null,
|
|
1541
|
+
feePayer,
|
|
1542
|
+
logAddress: lower(r['address']),
|
|
1543
|
+
logIdx: Number(Value.toNumber(r['log_idx'])),
|
|
1544
|
+
selector: Value.toText(r['selector']) ?? null,
|
|
1545
|
+
source: 'q1',
|
|
1546
|
+
topic1: Value.toText(r['topic1']) ?? null,
|
|
1547
|
+
topic2: Value.toText(r['topic2']) ?? null,
|
|
1548
|
+
topic3: Value.toText(r['topic3']) ?? null,
|
|
1549
|
+
txHash: lowerHash(r['tx_hash']),
|
|
1550
|
+
txIdx: Number(Value.toNumber(r['tx_idx'])),
|
|
1551
|
+
txSender,
|
|
1552
|
+
}));
|
|
1553
|
+
// A mined transaction with no logs (e.g. a plain value transfer) has no
|
|
1554
|
+
// classifiable activity; return an empty list rather than a fabricated item.
|
|
1555
|
+
if (rows.length === 0)
|
|
1556
|
+
return [];
|
|
1557
|
+
// Best-effort MPP service directory: a fetch failure yields an empty map, so
|
|
1558
|
+
// enrichment never blocks classification.
|
|
1559
|
+
const fingerprintMap = await Mpp.fingerprintMap({ store: options.store }).catch(() => ({}));
|
|
1560
|
+
// Anchor the viewer on the transaction sender: the sender initiated every
|
|
1561
|
+
// activity in the transaction, so `perspective` is `outgoing` and `signer`
|
|
1562
|
+
// resolves to `self`. Fall back to the zero address when the sender is absent.
|
|
1563
|
+
const viewer = (txSender ?? zeroAddress);
|
|
1564
|
+
const group = groupByTx(rows)[0];
|
|
1565
|
+
return classifyGroup(group, viewer, fingerprintMap, logs).map(withTitle);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Lists classified activity for an address by running the shared pipeline
|
|
1569
|
+
* (`groupByTx` → `classifyGroup` → `enrichItem`) over rows from a
|
|
1570
|
+
* {@link Provider}. Returns enriched, chain-tagged items plus the next cursor;
|
|
1571
|
+
* callers run {@link groupByAccessKey} over the result.
|
|
1572
|
+
*/
|
|
1573
|
+
export async function list(provider, options) {
|
|
1574
|
+
const { address, cursor, group = false, limit = 20, logs = false } = options;
|
|
1575
|
+
const viewer = address.toLowerCase();
|
|
1576
|
+
// Best-effort MPP service directory: a fetch failure yields an empty map, so
|
|
1577
|
+
// enrichment never blocks the feed.
|
|
1578
|
+
const fingerprintMap = await Mpp.fingerprintMap({ store: options.store }).catch(() => ({}));
|
|
1579
|
+
let currentCursor = cursor;
|
|
1580
|
+
const allItems = [];
|
|
1581
|
+
let hasMore = false;
|
|
1582
|
+
let lastGroup;
|
|
1583
|
+
// Fetch pages until the target entry count is reached (or no more rows
|
|
1584
|
+
// exist), up to three attempts. When folding is requested the target is
|
|
1585
|
+
// measured against grouped entries (folding can collapse a page to very few),
|
|
1586
|
+
// otherwise against the raw item count.
|
|
1587
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1588
|
+
const { rows } = await provider.fetchRows({ address: viewer, cursor: currentCursor, limit });
|
|
1589
|
+
const grouped = groupByTx(rows);
|
|
1590
|
+
const txGroups = grouped.slice(0, limit + 1);
|
|
1591
|
+
hasMore = txGroups.length > limit;
|
|
1592
|
+
const page = txGroups.slice(0, limit);
|
|
1593
|
+
if (page.length === 0)
|
|
1594
|
+
break;
|
|
1595
|
+
allItems.push(...page.flatMap((txGroup) => classifyGroup(txGroup, viewer, fingerprintMap, logs).map(withTitle)));
|
|
1596
|
+
lastGroup = page[page.length - 1];
|
|
1597
|
+
const enough = group
|
|
1598
|
+
? groupByAccessKey(allItems).length >= minGroupEntries
|
|
1599
|
+
: allItems.length >= limit;
|
|
1600
|
+
if (enough || !hasMore)
|
|
1601
|
+
break;
|
|
1602
|
+
if (lastGroup)
|
|
1603
|
+
currentCursor = provider.cursorForGroup(lastGroup);
|
|
1604
|
+
}
|
|
1605
|
+
let nextCursor = null;
|
|
1606
|
+
if (hasMore && lastGroup)
|
|
1607
|
+
nextCursor = provider.cursorForGroup(lastGroup);
|
|
1608
|
+
// Items carry stub tokens (`address`/`amount`); the caller enriches them with
|
|
1609
|
+
// metadata via {@link enrichTokens}.
|
|
1610
|
+
return { items: allItems, nextCursor };
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Creates a TIDX-backed {@link Provider} that runs three sub-queries in
|
|
1614
|
+
* parallel and merges them:
|
|
1615
|
+
* - Q1 (outbound): `receipts ⋈ logs` for txs where the address is `from` or
|
|
1616
|
+
* `fee_payer`, yielding full log rows.
|
|
1617
|
+
* - Q2 (inbound): decoded `Transfer` events to the address.
|
|
1618
|
+
* - Q3 (outbound transfers): decoded `Transfer` events from the address
|
|
1619
|
+
* (catches access-key-signed sends where `tx.from` differs from the address).
|
|
1620
|
+
*/
|
|
1621
|
+
export function tidx(deps) {
|
|
1622
|
+
const { chainId, tidx: tidxClient } = deps;
|
|
1623
|
+
function parseCursor(cursor) {
|
|
1624
|
+
// Opaque `[blockNum, txIdx]` keyset cursor, the same encoding every other
|
|
1625
|
+
// list endpoint uses; a malformed/forged cursor decodes to `undefined` and
|
|
1626
|
+
// falls back to the head page.
|
|
1627
|
+
const decoded = Cursor.decode(cursor, ['int', 'int']);
|
|
1628
|
+
if (!decoded)
|
|
1629
|
+
return undefined;
|
|
1630
|
+
return { blockNum: decoded[0], txIdx: decoded[1] };
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Q1: every log of each transaction the address sent or paid the fee for,
|
|
1634
|
+
* via a single `receipts ⋈ logs` join filtered on `from OR fee_payer`.
|
|
1635
|
+
*
|
|
1636
|
+
* The cursor uses a two-predicate keyset (`block_num <= N` AND `(block_num <
|
|
1637
|
+
* N OR tx_idx < M)`) rather than a plain `block_num < N`: the `<= N` upper
|
|
1638
|
+
* bound lets the ClickHouse planner seek to the cursor block on the
|
|
1639
|
+
* `(block_num, tx_idx)` primary key instead of scanning from the tip, which
|
|
1640
|
+
* is what avoids planner 422s on busy addresses. Over-fetch `limit * 4`
|
|
1641
|
+
* (never `OFFSET`) because one tx fans out into many log rows that
|
|
1642
|
+
* `groupByTx` collapses back into a single group.
|
|
1643
|
+
*/
|
|
1644
|
+
async function queryOutbound(options) {
|
|
1645
|
+
const { address, cursor, limit } = options;
|
|
1646
|
+
const keyset = cursor
|
|
1647
|
+
? `AND receipts.block_num <= ${cursor.blockNum} AND (receipts.block_num < ${cursor.blockNum} OR receipts.tx_idx < ${cursor.txIdx})`
|
|
1648
|
+
: '';
|
|
1649
|
+
// Raw-table join (no signature-decoded CTE), so inline SQL is cast to `string`.
|
|
1650
|
+
const result = await tidxClient.fetch({
|
|
1651
|
+
chainId,
|
|
1652
|
+
query: `
|
|
1653
|
+
SELECT
|
|
1654
|
+
receipts.tx_hash AS tx_hash,
|
|
1655
|
+
receipts.block_num AS block_num,
|
|
1656
|
+
receipts.block_timestamp AS block_timestamp,
|
|
1657
|
+
receipts.tx_idx AS tx_idx,
|
|
1658
|
+
receipts."from" AS tx_sender,
|
|
1659
|
+
receipts.fee_payer AS fee_payer,
|
|
1660
|
+
logs.address AS log_address,
|
|
1661
|
+
logs.selector AS selector,
|
|
1662
|
+
logs.topic1 AS topic1,
|
|
1663
|
+
logs.topic2 AS topic2,
|
|
1664
|
+
logs.topic3 AS topic3,
|
|
1665
|
+
logs.data AS data,
|
|
1666
|
+
logs.log_idx AS log_idx
|
|
1667
|
+
FROM receipts
|
|
1668
|
+
INNER JOIN logs ON logs.tx_hash = receipts.tx_hash
|
|
1669
|
+
WHERE (receipts."from" = '${address}' OR receipts.fee_payer = '${address}') ${keyset}
|
|
1670
|
+
ORDER BY receipts.block_num DESC, receipts.tx_idx DESC
|
|
1671
|
+
LIMIT ${limit * 4}
|
|
1672
|
+
`,
|
|
1673
|
+
});
|
|
1674
|
+
return result.rows.map((r) => ({
|
|
1675
|
+
blockNum: Number(Value.toNumber(r['block_num'])),
|
|
1676
|
+
blockTimestamp: Number(Value.toNumber(r['block_timestamp'])),
|
|
1677
|
+
data: Value.toText(r['data']) ?? null,
|
|
1678
|
+
feePayer: lowerOrNull(r['fee_payer']),
|
|
1679
|
+
logAddress: lower(r['log_address']),
|
|
1680
|
+
logIdx: Number(Value.toNumber(r['log_idx'])),
|
|
1681
|
+
selector: Value.toText(r['selector']) ?? null,
|
|
1682
|
+
source: 'q1',
|
|
1683
|
+
topic1: Value.toText(r['topic1']) ?? null,
|
|
1684
|
+
topic2: Value.toText(r['topic2']) ?? null,
|
|
1685
|
+
topic3: Value.toText(r['topic3']) ?? null,
|
|
1686
|
+
txHash: lowerHash(r['tx_hash']),
|
|
1687
|
+
txIdx: Number(Value.toNumber(r['tx_idx'])),
|
|
1688
|
+
txSender: lowerOrNull(r['tx_sender']),
|
|
1689
|
+
}));
|
|
1690
|
+
}
|
|
1691
|
+
/** Q2/Q3: decoded `Transfer` events on one side of the address. */
|
|
1692
|
+
async function queryTransfers(options) {
|
|
1693
|
+
const { address, cursor, limit, side } = options;
|
|
1694
|
+
const where = side === 'inbound'
|
|
1695
|
+
? `"to" = '${address}' AND "from" != '${address}'`
|
|
1696
|
+
: `"from" = '${address}' AND "to" != '${address}'`;
|
|
1697
|
+
const keyset = cursor ? `AND block_num < ${cursor.blockNum}` : '';
|
|
1698
|
+
const result = await tidxClient.fetch({
|
|
1699
|
+
chainId,
|
|
1700
|
+
query: `
|
|
1701
|
+
SELECT "from", "to", address, value, tx_hash, block_num, block_timestamp, log_idx, tx_idx
|
|
1702
|
+
FROM Transfer
|
|
1703
|
+
WHERE ${where} ${keyset}
|
|
1704
|
+
ORDER BY block_num DESC, tx_idx DESC
|
|
1705
|
+
LIMIT ${limit * 4}
|
|
1706
|
+
`,
|
|
1707
|
+
signatures: [transferSignature],
|
|
1708
|
+
});
|
|
1709
|
+
return result.rows.map((r) => ({
|
|
1710
|
+
blockNum: Number(Value.toNumber(r['block_num'])),
|
|
1711
|
+
blockTimestamp: Number(Value.toNumber(r['block_timestamp'])),
|
|
1712
|
+
data: null,
|
|
1713
|
+
feePayer: null,
|
|
1714
|
+
logAddress: lower(r['address']),
|
|
1715
|
+
logIdx: Number(Value.toNumber(r['log_idx'])),
|
|
1716
|
+
selector: null,
|
|
1717
|
+
source: 'q2',
|
|
1718
|
+
topic1: null,
|
|
1719
|
+
topic2: null,
|
|
1720
|
+
topic3: null,
|
|
1721
|
+
transferAmount: BigInt(Value.toIntegerString(r['value']) ?? '0'),
|
|
1722
|
+
transferFrom: lower(r['from']),
|
|
1723
|
+
transferTo: lower(r['to']),
|
|
1724
|
+
transferToken: lower(r['address']),
|
|
1725
|
+
txHash: lowerHash(r['tx_hash']),
|
|
1726
|
+
txIdx: Number(Value.toNumber(r['tx_idx'])),
|
|
1727
|
+
txSender: null,
|
|
1728
|
+
}));
|
|
1729
|
+
}
|
|
1730
|
+
return {
|
|
1731
|
+
cursorForGroup(group) {
|
|
1732
|
+
return Cursor.encode([group.blockNum, group.txIdx]);
|
|
1733
|
+
},
|
|
1734
|
+
async fetchRows({ address, cursor, limit }) {
|
|
1735
|
+
const parsed = cursor ? parseCursor(cursor) : undefined;
|
|
1736
|
+
const [q1, q2, q3] = await Promise.all([
|
|
1737
|
+
queryOutbound({ address, cursor: parsed, limit }),
|
|
1738
|
+
queryTransfers({ address, cursor: parsed, limit, side: 'inbound' }),
|
|
1739
|
+
queryTransfers({ address, cursor: parsed, limit, side: 'outbound' }),
|
|
1740
|
+
]);
|
|
1741
|
+
return { rows: mergeAndSort(q1, q2, q3) };
|
|
1742
|
+
},
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Merges three sub-query results into one `(blockNum desc, txIdx desc)` stream.
|
|
1747
|
+
* Q2/Q3 rows whose hash already appears in Q1 are dropped (Q1 rows are richer),
|
|
1748
|
+
* and Q3 is also deduped against Q2.
|
|
1749
|
+
*/
|
|
1750
|
+
export function mergeAndSort(q1, q2, q3) {
|
|
1751
|
+
const q1Hashes = new Set(q1.map((r) => r.txHash));
|
|
1752
|
+
const filteredQ2 = q2.filter((r) => !q1Hashes.has(r.txHash));
|
|
1753
|
+
const filteredQ3 = q3.filter((r) => !q1Hashes.has(r.txHash));
|
|
1754
|
+
const q2Hashes = new Set(filteredQ2.map((r) => r.txHash));
|
|
1755
|
+
const dedupedQ3 = filteredQ3.filter((r) => !q2Hashes.has(r.txHash));
|
|
1756
|
+
return [...q1, ...filteredQ2, ...dedupedQ3].sort((a, b) => {
|
|
1757
|
+
if (a.blockNum !== b.blockNum)
|
|
1758
|
+
return b.blockNum - a.blockNum;
|
|
1759
|
+
return b.txIdx - a.txIdx;
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
/** Groups rows by transaction hash, preserving sort order. */
|
|
1763
|
+
function groupByTx(rows) {
|
|
1764
|
+
const map = new Map();
|
|
1765
|
+
const order = [];
|
|
1766
|
+
for (const row of rows) {
|
|
1767
|
+
const key = row.txHash;
|
|
1768
|
+
let group = map.get(key);
|
|
1769
|
+
if (!group) {
|
|
1770
|
+
group = {
|
|
1771
|
+
blockNum: row.blockNum,
|
|
1772
|
+
blockTimestamp: row.blockTimestamp,
|
|
1773
|
+
feePayer: row.feePayer,
|
|
1774
|
+
rows: [],
|
|
1775
|
+
txHash: row.txHash,
|
|
1776
|
+
txIdx: row.txIdx,
|
|
1777
|
+
txSender: row.txSender,
|
|
1778
|
+
};
|
|
1779
|
+
map.set(key, group);
|
|
1780
|
+
order.push(key);
|
|
1781
|
+
}
|
|
1782
|
+
// Inherit tx sender / fee payer from richer Q1 rows when present.
|
|
1783
|
+
if (row.txSender && !group.txSender)
|
|
1784
|
+
group.txSender = row.txSender;
|
|
1785
|
+
if (row.feePayer && !group.feePayer)
|
|
1786
|
+
group.feePayer = row.feePayer;
|
|
1787
|
+
group.rows.push(row);
|
|
1788
|
+
}
|
|
1789
|
+
return order.map((key) => map.get(key));
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Classifies a transaction group into one or more activity items. Q1 groups go
|
|
1793
|
+
* through full log decoding; Q2-only groups (transfers without a joined receipt)
|
|
1794
|
+
* are classified directly from the transfer columns.
|
|
1795
|
+
*/
|
|
1796
|
+
function classifyGroup(group, viewer, fingerprintMap, includeLogs = false) {
|
|
1797
|
+
const { blockNum, blockTimestamp, feePayer, txHash, txSender } = group;
|
|
1798
|
+
// `blockTimestamp` is epoch seconds; the public feed uses ISO 8601 like every
|
|
1799
|
+
// other resource's `timestamp`.
|
|
1800
|
+
const timestamp = new Date(Number(blockTimestamp) * 1000).toISOString();
|
|
1801
|
+
// `outgoing` when the viewer initiated the transaction (sent it or paid the
|
|
1802
|
+
// fee); `incoming` when it merely happened to them (someone else initiated it).
|
|
1803
|
+
const perspective = (txSender && Address.isEqual(txSender, viewer)) ||
|
|
1804
|
+
(feePayer && Address.isEqual(feePayer, viewer))
|
|
1805
|
+
? 'outgoing'
|
|
1806
|
+
: 'incoming';
|
|
1807
|
+
const itemBase = (logIndex) => baseFor({ logIndex, perspective, timestamp, transactionHash: txHash });
|
|
1808
|
+
const signer = txSender && Address.isEqual(txSender, viewer) ? 'self' : (txSender ?? 'self');
|
|
1809
|
+
const q1Rows = group.rows.filter((r) => r.source === 'q1');
|
|
1810
|
+
const q2Rows = group.rows.filter((r) => r.source === 'q2');
|
|
1811
|
+
if (q1Rows.length > 0)
|
|
1812
|
+
return classifyFromLogs(q1Rows, {
|
|
1813
|
+
blockNum,
|
|
1814
|
+
fingerprintMap,
|
|
1815
|
+
includeLogs,
|
|
1816
|
+
itemBase,
|
|
1817
|
+
signer,
|
|
1818
|
+
viewer,
|
|
1819
|
+
});
|
|
1820
|
+
// Q2-only — a transfer row without a joined receipt. Classify by comparing
|
|
1821
|
+
// `from`/`to` against the viewer (zero-address sides are mints/burns).
|
|
1822
|
+
if (q2Rows.length > 0) {
|
|
1823
|
+
const r = q2Rows[0];
|
|
1824
|
+
const base = itemBase(r.logIdx);
|
|
1825
|
+
const from = r.transferFrom;
|
|
1826
|
+
const to = r.transferTo;
|
|
1827
|
+
const sourceToken = token(r.transferAmount, r.transferToken);
|
|
1828
|
+
if (Address.isEqual(from, zeroAddress))
|
|
1829
|
+
return [{ ...base, data: { recipient: viewer, sourceToken }, type: 'mint' }];
|
|
1830
|
+
if (Address.isEqual(to, zeroAddress))
|
|
1831
|
+
return [{ ...base, data: { sender: from, signer, sourceToken }, type: 'burn' }];
|
|
1832
|
+
// Q2-only rows carry no memo, so MPP service attribution is unavailable
|
|
1833
|
+
// here; `attribution` is resolved only on the log-bearing (Q1) path.
|
|
1834
|
+
if (Address.isEqual(from, viewer))
|
|
1835
|
+
return [
|
|
1836
|
+
transfer(base, blockNum, {
|
|
1837
|
+
direction: 'out',
|
|
1838
|
+
recipient: to,
|
|
1839
|
+
sender: viewer,
|
|
1840
|
+
signer,
|
|
1841
|
+
sourceToken,
|
|
1842
|
+
}),
|
|
1843
|
+
];
|
|
1844
|
+
return [
|
|
1845
|
+
transfer(base, blockNum, { direction: 'in', recipient: viewer, sender: from, sourceToken }),
|
|
1846
|
+
];
|
|
1847
|
+
}
|
|
1848
|
+
// Fallback — should not happen (a group with neither q1 nor q2 rows).
|
|
1849
|
+
return [
|
|
1850
|
+
unknown(itemBase(Math.min(...group.rows.map((r) => r.logIdx))), {
|
|
1851
|
+
events: buildTxEvents(group.rows, []),
|
|
1852
|
+
signer,
|
|
1853
|
+
}),
|
|
1854
|
+
];
|
|
1855
|
+
}
|
|
1856
|
+
/** Classifies a Q1 (log-bearing) group via `parseEventLogs`. */
|
|
1857
|
+
function classifyFromLogs(rows, ctx) {
|
|
1858
|
+
const items = classifyFromLogsCore(rows, ctx);
|
|
1859
|
+
// When the caller requested raw logs, attach the full per-transaction log set
|
|
1860
|
+
// (decoded when possible) to every classified item so no co-located log is
|
|
1861
|
+
// hidden behind the headline. `unknown` items already carry it.
|
|
1862
|
+
if (!ctx.includeLogs)
|
|
1863
|
+
return items;
|
|
1864
|
+
const events = buildTxEvents(rows, decodeRows(rows));
|
|
1865
|
+
return items.map((item) => (item.type === 'unknown' ? item : { ...item, events }));
|
|
1866
|
+
}
|
|
1867
|
+
/** Classifies a Q1 (log-bearing) group via `parseEventLogs`, picking one headline item. */
|
|
1868
|
+
function classifyFromLogsCore(rows, ctx) {
|
|
1869
|
+
const { blockNum, fingerprintMap, itemBase, viewer } = ctx;
|
|
1870
|
+
// An access-key-signed tx emits AccessKeySpend at the accountKeychain; topic2
|
|
1871
|
+
// is the access-key publicKey. Use it as the signer so successive access-key
|
|
1872
|
+
// actions group together.
|
|
1873
|
+
const accessKeySpendRow = rows.find((r) => r.selector === accessKeySpendSelector &&
|
|
1874
|
+
Address.isEqual(r.logAddress, Addresses.accountKeychain));
|
|
1875
|
+
const accessKeySigner = accessKeySpendRow?.topic2
|
|
1876
|
+
? `0x${accessKeySpendRow.topic2.slice(-40)}`.toLowerCase()
|
|
1877
|
+
: null;
|
|
1878
|
+
const signer = accessKeySigner && !Address.isEqual(accessKeySigner, viewer) ? accessKeySigner : ctx.signer;
|
|
1879
|
+
const events = decodeRows(rows);
|
|
1880
|
+
const minLogIndex = Math.min(...rows.map((r) => r.logIdx));
|
|
1881
|
+
// Payment-channel settlement: a CloseRequested topic plus an optional refund
|
|
1882
|
+
// transfer back to the viewer.
|
|
1883
|
+
const hasCloseRequested = rows.some((r) => r.selector === closeRequestedSelector);
|
|
1884
|
+
if (hasCloseRequested) {
|
|
1885
|
+
const closeRequestedLogIndex = rows.find((r) => r.selector === closeRequestedSelector)?.logIdx ?? minLogIndex;
|
|
1886
|
+
const refundTransfer = events.find((e) => (e.eventName === 'Transfer' || e.eventName === 'TransferWithMemo') &&
|
|
1887
|
+
'to' in e.args &&
|
|
1888
|
+
Address.isEqual(e.args.to, viewer));
|
|
1889
|
+
const refund = refundTransfer
|
|
1890
|
+
? token(refundTransfer.args.amount, refundTransfer.address)
|
|
1891
|
+
: undefined;
|
|
1892
|
+
return [
|
|
1893
|
+
{
|
|
1894
|
+
...itemBase(closeRequestedLogIndex),
|
|
1895
|
+
data: { ...(refund ? { refund } : {}), signer },
|
|
1896
|
+
type: 'session-closed',
|
|
1897
|
+
},
|
|
1898
|
+
];
|
|
1899
|
+
}
|
|
1900
|
+
// Dedup preference: TransferWithMemo beats Transfer; Mint/Burn beat
|
|
1901
|
+
// zero-address Transfer.
|
|
1902
|
+
const preferenceMap = new Map();
|
|
1903
|
+
for (const event of events) {
|
|
1904
|
+
if (event.eventName === 'TransferWithMemo' && 'from' in event.args && 'to' in event.args) {
|
|
1905
|
+
const args = event.args;
|
|
1906
|
+
preferenceMap.set(`${args.from}:${args.to}:${args.amount}`, 'TransferWithMemo');
|
|
1907
|
+
}
|
|
1908
|
+
if (event.eventName === 'Mint' && 'amount' in event.args)
|
|
1909
|
+
preferenceMap.set(`mint:${event.address}:${event.args.amount}`, 'Mint');
|
|
1910
|
+
if (event.eventName === 'Burn' && 'amount' in event.args)
|
|
1911
|
+
preferenceMap.set(`burn:${event.address}:${event.args.amount}`, 'Burn');
|
|
1912
|
+
}
|
|
1913
|
+
const dedupedEvents = events.filter((event) => {
|
|
1914
|
+
if (event.eventName !== 'Transfer')
|
|
1915
|
+
return true;
|
|
1916
|
+
const args = event.args;
|
|
1917
|
+
if (preferenceMap.has(`${args.from}:${args.to}:${args.amount}`))
|
|
1918
|
+
return false;
|
|
1919
|
+
if (Address.isEqual(args.from, zeroAddress) &&
|
|
1920
|
+
preferenceMap.has(`mint:${event.address}:${args.amount}`))
|
|
1921
|
+
return false;
|
|
1922
|
+
if (Address.isEqual(args.to, zeroAddress) &&
|
|
1923
|
+
preferenceMap.has(`burn:${event.address}:${args.amount}`))
|
|
1924
|
+
return false;
|
|
1925
|
+
return true;
|
|
1926
|
+
});
|
|
1927
|
+
const finalEvents = dedupedEvents.filter((event) => {
|
|
1928
|
+
if (event.eventName !== 'TransferWithMemo')
|
|
1929
|
+
return true;
|
|
1930
|
+
const args = event.args;
|
|
1931
|
+
if (Address.isEqual(args.from, zeroAddress) &&
|
|
1932
|
+
preferenceMap.has(`mint:${event.address}:${args.amount}`))
|
|
1933
|
+
return false;
|
|
1934
|
+
if (Address.isEqual(args.to, zeroAddress) &&
|
|
1935
|
+
preferenceMap.has(`burn:${event.address}:${args.amount}`))
|
|
1936
|
+
return false;
|
|
1937
|
+
return true;
|
|
1938
|
+
});
|
|
1939
|
+
const transfers = finalEvents.filter((e) => (e.eventName === 'Transfer' || e.eventName === 'TransferWithMemo') &&
|
|
1940
|
+
'to' in e.args &&
|
|
1941
|
+
'from' in e.args &&
|
|
1942
|
+
'amount' in e.args);
|
|
1943
|
+
// Swap: a transfer into the DEX paired with one out of the DEX.
|
|
1944
|
+
const toDex = transfers.find((e) => Address.isEqual(e.args.to, Addresses.stablecoinDex));
|
|
1945
|
+
const fromDex = transfers.find((e) => Address.isEqual(e.args.from, Addresses.stablecoinDex));
|
|
1946
|
+
if (toDex && fromDex) {
|
|
1947
|
+
const swapper = fromDex.args.to;
|
|
1948
|
+
const destTokenAddress = fromDex.address;
|
|
1949
|
+
const sourceToken = token(toDex.args.amount, toDex.address);
|
|
1950
|
+
// Cross-token transfer: the swapped-out token is forwarded from the swapper
|
|
1951
|
+
// to a different recipient in the same transaction (the account server's
|
|
1952
|
+
// approve+swap+transfer multicall). Fold the swap and its delivery leg into
|
|
1953
|
+
// one `transfer` carrying both the source token spent into the DEX and the
|
|
1954
|
+
// destination token delivered to the recipient. A swap whose output stays
|
|
1955
|
+
// with the swapper (no onward leg) stays a plain `swap` below.
|
|
1956
|
+
const delivery = transfers.find((e) => Address.isEqual(e.address, destTokenAddress) &&
|
|
1957
|
+
Address.isEqual(e.args.from, swapper) &&
|
|
1958
|
+
!Address.isEqual(e.args.to, Addresses.stablecoinDex) &&
|
|
1959
|
+
!Address.isEqual(e.args.to, swapper));
|
|
1960
|
+
if (delivery) {
|
|
1961
|
+
const args = delivery.args;
|
|
1962
|
+
const rawMemo = delivery.eventName === 'TransferWithMemo' ? args.memo : undefined;
|
|
1963
|
+
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined;
|
|
1964
|
+
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo });
|
|
1965
|
+
return [
|
|
1966
|
+
transfer(itemBase(logIndexOfEvent(delivery)), blockNum, {
|
|
1967
|
+
attribution,
|
|
1968
|
+
destinationToken: token(args.amount, delivery.address),
|
|
1969
|
+
direction: Address.isEqual(args.to, viewer) ? 'in' : 'out',
|
|
1970
|
+
memo,
|
|
1971
|
+
recipient: args.to.toLowerCase(),
|
|
1972
|
+
sender: swapper.toLowerCase(),
|
|
1973
|
+
signer,
|
|
1974
|
+
sourceToken,
|
|
1975
|
+
}),
|
|
1976
|
+
];
|
|
1977
|
+
}
|
|
1978
|
+
return [
|
|
1979
|
+
{
|
|
1980
|
+
...itemBase(Math.min(logIndexOfEvent(toDex), logIndexOfEvent(fromDex))),
|
|
1981
|
+
data: {
|
|
1982
|
+
destinationToken: token(fromDex.args.amount, destTokenAddress),
|
|
1983
|
+
signer,
|
|
1984
|
+
sourceToken,
|
|
1985
|
+
},
|
|
1986
|
+
type: 'swap',
|
|
1987
|
+
},
|
|
1988
|
+
];
|
|
1989
|
+
}
|
|
1990
|
+
// Mint: explicit `Mint` event (not the fee manager) or a transfer from zero.
|
|
1991
|
+
const mintEvent = finalEvents.find((e) => e.eventName === 'Mint' &&
|
|
1992
|
+
'amount' in e.args &&
|
|
1993
|
+
!Address.isEqual(e.address, Addresses.feeManager));
|
|
1994
|
+
if (mintEvent) {
|
|
1995
|
+
const args = mintEvent.args;
|
|
1996
|
+
return [
|
|
1997
|
+
{
|
|
1998
|
+
...itemBase(logIndexOfEvent(mintEvent)),
|
|
1999
|
+
data: {
|
|
2000
|
+
recipient: args.to.toLowerCase(),
|
|
2001
|
+
sourceToken: token(args.amount, mintEvent.address),
|
|
2002
|
+
},
|
|
2003
|
+
type: 'mint',
|
|
2004
|
+
},
|
|
2005
|
+
];
|
|
2006
|
+
}
|
|
2007
|
+
const zeroTransferIn = transfers.find((e) => Address.isEqual(e.args.from, zeroAddress));
|
|
2008
|
+
if (zeroTransferIn) {
|
|
2009
|
+
const args = zeroTransferIn.args;
|
|
2010
|
+
return [
|
|
2011
|
+
{
|
|
2012
|
+
...itemBase(logIndexOfEvent(zeroTransferIn)),
|
|
2013
|
+
data: {
|
|
2014
|
+
recipient: args.to.toLowerCase(),
|
|
2015
|
+
sourceToken: token(args.amount, zeroTransferIn.address),
|
|
2016
|
+
},
|
|
2017
|
+
type: 'mint',
|
|
2018
|
+
},
|
|
2019
|
+
];
|
|
2020
|
+
}
|
|
2021
|
+
// Burn blocked: a compliance burn of receive-policy-blocked funds. It also
|
|
2022
|
+
// emits a zero-address `Transfer` that would otherwise classify as `burn`, so
|
|
2023
|
+
// detect the distinct `BurnBlocked` event first.
|
|
2024
|
+
const burnBlockedEvent = finalEvents.find((e) => e.eventName === 'BurnBlocked');
|
|
2025
|
+
if (burnBlockedEvent) {
|
|
2026
|
+
const args = burnBlockedEvent.args;
|
|
2027
|
+
return [
|
|
2028
|
+
{
|
|
2029
|
+
...itemBase(logIndexOfEvent(burnBlockedEvent)),
|
|
2030
|
+
data: {
|
|
2031
|
+
sender: args.from.toLowerCase(),
|
|
2032
|
+
signer,
|
|
2033
|
+
sourceToken: token(args.amount, burnBlockedEvent.address),
|
|
2034
|
+
},
|
|
2035
|
+
type: 'burn-blocked',
|
|
2036
|
+
},
|
|
2037
|
+
];
|
|
2038
|
+
}
|
|
2039
|
+
// Burn: explicit `Burn` event or a transfer to zero.
|
|
2040
|
+
const burnEvent = finalEvents.find((e) => e.eventName === 'Burn' && 'amount' in e.args);
|
|
2041
|
+
if (burnEvent) {
|
|
2042
|
+
const args = burnEvent.args;
|
|
2043
|
+
return [
|
|
2044
|
+
{
|
|
2045
|
+
...itemBase(logIndexOfEvent(burnEvent)),
|
|
2046
|
+
data: {
|
|
2047
|
+
sender: args.from.toLowerCase(),
|
|
2048
|
+
signer,
|
|
2049
|
+
sourceToken: token(args.amount, burnEvent.address),
|
|
2050
|
+
},
|
|
2051
|
+
type: 'burn',
|
|
2052
|
+
},
|
|
2053
|
+
];
|
|
2054
|
+
}
|
|
2055
|
+
const zeroTransferOut = transfers.find((e) => Address.isEqual(e.args.to, zeroAddress));
|
|
2056
|
+
if (zeroTransferOut) {
|
|
2057
|
+
const args = zeroTransferOut.args;
|
|
2058
|
+
return [
|
|
2059
|
+
{
|
|
2060
|
+
...itemBase(logIndexOfEvent(zeroTransferOut)),
|
|
2061
|
+
data: {
|
|
2062
|
+
sender: args.from.toLowerCase(),
|
|
2063
|
+
signer,
|
|
2064
|
+
sourceToken: token(args.amount, zeroTransferOut.address),
|
|
2065
|
+
},
|
|
2066
|
+
type: 'burn',
|
|
2067
|
+
},
|
|
2068
|
+
];
|
|
2069
|
+
}
|
|
2070
|
+
// Approval (excluding zone-outbox spenders).
|
|
2071
|
+
const approvalEvent = finalEvents.find((e) => e.eventName === 'Approval' &&
|
|
2072
|
+
!Address.isEqual(e.args.spender, Addresses.zoneOutbox));
|
|
2073
|
+
if (approvalEvent) {
|
|
2074
|
+
const args = approvalEvent.args;
|
|
2075
|
+
return [
|
|
2076
|
+
{
|
|
2077
|
+
...itemBase(logIndexOfEvent(approvalEvent)),
|
|
2078
|
+
data: {
|
|
2079
|
+
signer,
|
|
2080
|
+
sourceToken: token(args.amount, approvalEvent.address),
|
|
2081
|
+
spender: args.spender.toLowerCase(),
|
|
2082
|
+
},
|
|
2083
|
+
type: 'approval',
|
|
2084
|
+
},
|
|
2085
|
+
];
|
|
2086
|
+
}
|
|
2087
|
+
// Precompile-event activities. These run before the generic transfer
|
|
2088
|
+
// fallback because each is emitted alongside a co-located TIP-20 `Transfer`
|
|
2089
|
+
// whose counterparty is the precompile (the DEX, the channel reserve, the fee
|
|
2090
|
+
// manager, the token itself), which would otherwise classify as a plain
|
|
2091
|
+
// transfer. They are grouped by (transaction + precompile counterparty), not
|
|
2092
|
+
// by log adjacency.
|
|
2093
|
+
// Payment channel opened: `ChannelOpened` carries the deposit token/amount
|
|
2094
|
+
// directly, so no paired-transfer lookup is needed.
|
|
2095
|
+
const channelOpened = finalEvents.find((e) => e.eventName === 'ChannelOpened');
|
|
2096
|
+
if (channelOpened) {
|
|
2097
|
+
const args = channelOpened.args;
|
|
2098
|
+
return [
|
|
2099
|
+
{
|
|
2100
|
+
...itemBase(logIndexOfEvent(channelOpened)),
|
|
2101
|
+
data: {
|
|
2102
|
+
channelId: args.channelId.toLowerCase(),
|
|
2103
|
+
payee: args.payee.toLowerCase(),
|
|
2104
|
+
payer: args.payer.toLowerCase(),
|
|
2105
|
+
signer,
|
|
2106
|
+
sourceToken: token(args.deposit, args.token),
|
|
2107
|
+
},
|
|
2108
|
+
type: 'channel-opened',
|
|
2109
|
+
},
|
|
2110
|
+
];
|
|
2111
|
+
}
|
|
2112
|
+
// The channel reserve doesn't put the token on `TopUp`/`Settled`/
|
|
2113
|
+
// `ChannelClosed`, so recover it from the co-located funding/payout transfer.
|
|
2114
|
+
const reserveInflow = transfers.find((e) => Address.isEqual(e.args.to, tip20ChannelReserve));
|
|
2115
|
+
const reserveOutflowTo = (recipient) => transfers.find((e) => Address.isEqual(e.args.from, tip20ChannelReserve) &&
|
|
2116
|
+
Address.isEqual(e.args.to, recipient));
|
|
2117
|
+
// Payment channel topped up: `additionalDeposit` from the event, token from
|
|
2118
|
+
// the inbound funding transfer to the reserve.
|
|
2119
|
+
const topUp = finalEvents.find((e) => e.eventName === 'TopUp');
|
|
2120
|
+
if (topUp) {
|
|
2121
|
+
const args = topUp.args;
|
|
2122
|
+
const tokenAddress = reserveInflow
|
|
2123
|
+
? reserveInflow.address
|
|
2124
|
+
: zeroAddress;
|
|
2125
|
+
return [
|
|
2126
|
+
{
|
|
2127
|
+
...itemBase(logIndexOfEvent(topUp)),
|
|
2128
|
+
data: {
|
|
2129
|
+
channelId: args.channelId.toLowerCase(),
|
|
2130
|
+
signer,
|
|
2131
|
+
sourceToken: token(args.additionalDeposit, tokenAddress),
|
|
2132
|
+
},
|
|
2133
|
+
type: 'channel-funded',
|
|
2134
|
+
},
|
|
2135
|
+
];
|
|
2136
|
+
}
|
|
2137
|
+
// Payment channel settled: `deltaPaid` from the event, token from the payout
|
|
2138
|
+
// transfer out of the reserve to the payee.
|
|
2139
|
+
const settled = finalEvents.find((e) => e.eventName === 'Settled');
|
|
2140
|
+
if (settled) {
|
|
2141
|
+
const args = settled.args;
|
|
2142
|
+
const payeeLeg = reserveOutflowTo(args.payee.toLowerCase());
|
|
2143
|
+
const tokenAddress = payeeLeg
|
|
2144
|
+
? payeeLeg.address
|
|
2145
|
+
: zeroAddress;
|
|
2146
|
+
return [
|
|
2147
|
+
{
|
|
2148
|
+
...itemBase(logIndexOfEvent(settled)),
|
|
2149
|
+
data: {
|
|
2150
|
+
channelId: args.channelId.toLowerCase(),
|
|
2151
|
+
payee: args.payee.toLowerCase(),
|
|
2152
|
+
payer: args.payer.toLowerCase(),
|
|
2153
|
+
signer,
|
|
2154
|
+
sourceToken: token(args.deltaPaid, tokenAddress),
|
|
2155
|
+
},
|
|
2156
|
+
type: 'channel-settled',
|
|
2157
|
+
},
|
|
2158
|
+
];
|
|
2159
|
+
}
|
|
2160
|
+
// Payment channel closed: payout to the payee and refund to the payer arrive
|
|
2161
|
+
// as up to two transfers out of the reserve; attach whichever are present.
|
|
2162
|
+
const channelClosed = finalEvents.find((e) => e.eventName === 'ChannelClosed');
|
|
2163
|
+
if (channelClosed) {
|
|
2164
|
+
const args = channelClosed.args;
|
|
2165
|
+
const payeeLeg = reserveOutflowTo(args.payee.toLowerCase());
|
|
2166
|
+
const payerLeg = reserveOutflowTo(args.payer.toLowerCase());
|
|
2167
|
+
return [
|
|
2168
|
+
{
|
|
2169
|
+
...itemBase(logIndexOfEvent(channelClosed)),
|
|
2170
|
+
data: {
|
|
2171
|
+
channelId: args.channelId.toLowerCase(),
|
|
2172
|
+
payee: args.payee.toLowerCase(),
|
|
2173
|
+
payer: args.payer.toLowerCase(),
|
|
2174
|
+
...(payerLeg && args.refundedToPayer > 0n
|
|
2175
|
+
? { refund: token(args.refundedToPayer, payerLeg.address) }
|
|
2176
|
+
: {}),
|
|
2177
|
+
signer,
|
|
2178
|
+
...(payeeLeg
|
|
2179
|
+
? { sourceToken: token(args.settledToPayee, payeeLeg.address) }
|
|
2180
|
+
: {}),
|
|
2181
|
+
},
|
|
2182
|
+
type: 'channel-closed',
|
|
2183
|
+
},
|
|
2184
|
+
];
|
|
2185
|
+
}
|
|
2186
|
+
// Payment channel close cancelled: the payer revoked a pending close request.
|
|
2187
|
+
// When bundled with a top-up the `TopUp` classifier above wins; on its own
|
|
2188
|
+
// this carries no transfer, so the event itself is the signal.
|
|
2189
|
+
const closeCancelled = finalEvents.find((e) => e.eventName === 'CloseRequestCancelled');
|
|
2190
|
+
if (closeCancelled) {
|
|
2191
|
+
const args = closeCancelled.args;
|
|
2192
|
+
return [
|
|
2193
|
+
{
|
|
2194
|
+
...itemBase(logIndexOfEvent(closeCancelled)),
|
|
2195
|
+
data: {
|
|
2196
|
+
channelId: args.channelId.toLowerCase(),
|
|
2197
|
+
payee: args.payee.toLowerCase(),
|
|
2198
|
+
payer: args.payer.toLowerCase(),
|
|
2199
|
+
signer,
|
|
2200
|
+
},
|
|
2201
|
+
type: 'channel-close-cancelled',
|
|
2202
|
+
},
|
|
2203
|
+
];
|
|
2204
|
+
}
|
|
2205
|
+
// DEX order placed: `OrderPlaced` carries the escrow token/amount and side.
|
|
2206
|
+
// (Auto-flip re-places are inside swaps, which already returned above.)
|
|
2207
|
+
const orderPlaced = finalEvents.find((e) => e.eventName === 'OrderPlaced');
|
|
2208
|
+
if (orderPlaced) {
|
|
2209
|
+
const args = orderPlaced.args;
|
|
2210
|
+
return [
|
|
2211
|
+
{
|
|
2212
|
+
...itemBase(logIndexOfEvent(orderPlaced)),
|
|
2213
|
+
data: {
|
|
2214
|
+
orderId: args.orderId.toString(),
|
|
2215
|
+
side: args.isBid ? 'bid' : 'ask',
|
|
2216
|
+
signer,
|
|
2217
|
+
sourceToken: token(args.amount, args.token),
|
|
2218
|
+
tick: Number(args.tick),
|
|
2219
|
+
},
|
|
2220
|
+
type: 'order-placed',
|
|
2221
|
+
},
|
|
2222
|
+
];
|
|
2223
|
+
}
|
|
2224
|
+
// DEX order cancelled: refund is credited to the internal DEX balance, so
|
|
2225
|
+
// there is no co-located transfer — the `OrderCancelled` event is the signal.
|
|
2226
|
+
const orderCancelled = finalEvents.find((e) => e.eventName === 'OrderCancelled');
|
|
2227
|
+
if (orderCancelled) {
|
|
2228
|
+
const args = orderCancelled.args;
|
|
2229
|
+
return [
|
|
2230
|
+
{
|
|
2231
|
+
...itemBase(logIndexOfEvent(orderCancelled)),
|
|
2232
|
+
data: { orderId: args.orderId.toString(), signer },
|
|
2233
|
+
type: 'order-cancelled',
|
|
2234
|
+
},
|
|
2235
|
+
];
|
|
2236
|
+
}
|
|
2237
|
+
// Fee-AMM rebalance swap: `validatorToken` in, `userToken` out.
|
|
2238
|
+
const rebalanceSwap = finalEvents.find((e) => e.eventName === 'RebalanceSwap');
|
|
2239
|
+
if (rebalanceSwap) {
|
|
2240
|
+
const args = rebalanceSwap.args;
|
|
2241
|
+
return [
|
|
2242
|
+
{
|
|
2243
|
+
...itemBase(logIndexOfEvent(rebalanceSwap)),
|
|
2244
|
+
data: {
|
|
2245
|
+
destinationToken: token(args.amountOut, args.userToken),
|
|
2246
|
+
signer,
|
|
2247
|
+
sourceToken: token(args.amountIn, args.validatorToken),
|
|
2248
|
+
swapper: args.swapper.toLowerCase(),
|
|
2249
|
+
},
|
|
2250
|
+
type: 'fee-rebalance-swap',
|
|
2251
|
+
},
|
|
2252
|
+
];
|
|
2253
|
+
}
|
|
2254
|
+
// Validator fees distributed.
|
|
2255
|
+
const feesDistributed = finalEvents.find((e) => e.eventName === 'FeesDistributed');
|
|
2256
|
+
if (feesDistributed) {
|
|
2257
|
+
const args = feesDistributed.args;
|
|
2258
|
+
return [
|
|
2259
|
+
{
|
|
2260
|
+
...itemBase(logIndexOfEvent(feesDistributed)),
|
|
2261
|
+
data: {
|
|
2262
|
+
signer,
|
|
2263
|
+
sourceToken: token(args.amount, args.token),
|
|
2264
|
+
validator: args.validator.toLowerCase(),
|
|
2265
|
+
},
|
|
2266
|
+
type: 'fees-distributed',
|
|
2267
|
+
},
|
|
2268
|
+
];
|
|
2269
|
+
}
|
|
2270
|
+
// Reward pool funded: the funded token is the emitting token contract itself.
|
|
2271
|
+
const rewardDistributed = finalEvents.find((e) => e.eventName === 'RewardDistributed');
|
|
2272
|
+
if (rewardDistributed) {
|
|
2273
|
+
const args = rewardDistributed.args;
|
|
2274
|
+
return [
|
|
2275
|
+
{
|
|
2276
|
+
...itemBase(logIndexOfEvent(rewardDistributed)),
|
|
2277
|
+
data: {
|
|
2278
|
+
funder: args.funder.toLowerCase(),
|
|
2279
|
+
signer,
|
|
2280
|
+
sourceToken: token(args.amount, rewardDistributed.address),
|
|
2281
|
+
},
|
|
2282
|
+
type: 'reward-distributed',
|
|
2283
|
+
},
|
|
2284
|
+
];
|
|
2285
|
+
}
|
|
2286
|
+
// Reward recipient redirected.
|
|
2287
|
+
const rewardRecipientSet = finalEvents.find((e) => e.eventName === 'RewardRecipientSet');
|
|
2288
|
+
if (rewardRecipientSet) {
|
|
2289
|
+
const args = rewardRecipientSet.args;
|
|
2290
|
+
return [
|
|
2291
|
+
{
|
|
2292
|
+
...itemBase(logIndexOfEvent(rewardRecipientSet)),
|
|
2293
|
+
data: {
|
|
2294
|
+
holder: args.holder.toLowerCase(),
|
|
2295
|
+
recipient: args.recipient.toLowerCase(),
|
|
2296
|
+
signer,
|
|
2297
|
+
},
|
|
2298
|
+
type: 'reward-recipient-set',
|
|
2299
|
+
},
|
|
2300
|
+
];
|
|
2301
|
+
}
|
|
2302
|
+
// Access-key spending limit updated: `newLimit` is carried as the token
|
|
2303
|
+
// amount so it enriches with the token's metadata.
|
|
2304
|
+
const spendingLimitUpdated = finalEvents.find((e) => e.eventName === 'SpendingLimitUpdated');
|
|
2305
|
+
if (spendingLimitUpdated) {
|
|
2306
|
+
const args = spendingLimitUpdated.args;
|
|
2307
|
+
return [
|
|
2308
|
+
{
|
|
2309
|
+
...itemBase(logIndexOfEvent(spendingLimitUpdated)),
|
|
2310
|
+
data: {
|
|
2311
|
+
account: args.account.toLowerCase(),
|
|
2312
|
+
publicKey: args.publicKey.toLowerCase(),
|
|
2313
|
+
signer,
|
|
2314
|
+
sourceToken: token(args.newLimit, args.token),
|
|
2315
|
+
},
|
|
2316
|
+
type: 'spending-limit-updated',
|
|
2317
|
+
},
|
|
2318
|
+
];
|
|
2319
|
+
}
|
|
2320
|
+
// Access-key lifecycle events — emitted alongside any co-located transfer.
|
|
2321
|
+
const accessKeyItems = [];
|
|
2322
|
+
for (const event of finalEvents) {
|
|
2323
|
+
if (event.eventName === 'KeyAuthorized' &&
|
|
2324
|
+
Address.isEqual(event.address, Addresses.accountKeychain)) {
|
|
2325
|
+
const args = event.args;
|
|
2326
|
+
accessKeyItems.push({
|
|
2327
|
+
...itemBase(logIndexOfEvent(event)),
|
|
2328
|
+
data: {
|
|
2329
|
+
account: args.account.toLowerCase(),
|
|
2330
|
+
expiry: Number(args.expiry),
|
|
2331
|
+
publicKey: args.publicKey.toLowerCase(),
|
|
2332
|
+
signatureType: Number(args.signatureType),
|
|
2333
|
+
},
|
|
2334
|
+
type: 'access-key-created',
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
else if (event.eventName === 'KeyRevoked' &&
|
|
2338
|
+
Address.isEqual(event.address, Addresses.accountKeychain)) {
|
|
2339
|
+
const args = event.args;
|
|
2340
|
+
accessKeyItems.push({
|
|
2341
|
+
...itemBase(logIndexOfEvent(event)),
|
|
2342
|
+
data: {
|
|
2343
|
+
account: args.account.toLowerCase(),
|
|
2344
|
+
publicKey: args.publicKey.toLowerCase(),
|
|
2345
|
+
},
|
|
2346
|
+
type: 'access-key-revoked',
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
// Token creation.
|
|
2351
|
+
const tokenCreated = finalEvents.find((e) => e.eventName === 'TokenCreated' && Address.isEqual(e.address, Addresses.tip20Factory));
|
|
2352
|
+
if (tokenCreated) {
|
|
2353
|
+
const args = tokenCreated.args;
|
|
2354
|
+
return [
|
|
2355
|
+
{
|
|
2356
|
+
...itemBase(logIndexOfEvent(tokenCreated)),
|
|
2357
|
+
data: {
|
|
2358
|
+
address: args.token.toLowerCase(),
|
|
2359
|
+
currency: args.currency,
|
|
2360
|
+
name: args.name,
|
|
2361
|
+
symbol: args.symbol,
|
|
2362
|
+
},
|
|
2363
|
+
type: 'token-created',
|
|
2364
|
+
},
|
|
2365
|
+
...accessKeyItems,
|
|
2366
|
+
];
|
|
2367
|
+
}
|
|
2368
|
+
// Governance / admin / DEX-bookkeeping events. Each maps a single decoded
|
|
2369
|
+
// event to a typed item. Checked after token creation (a token-creation tx
|
|
2370
|
+
// also emits `RoleMembershipUpdated`) and before the plain-transfer fallback;
|
|
2371
|
+
// these carry no token movement, so the event itself is the whole signal.
|
|
2372
|
+
const lc = (value) => value.toLowerCase();
|
|
2373
|
+
const hx = (value) => value.toLowerCase();
|
|
2374
|
+
const findEvent = (name) => finalEvents.find((e) => e.eventName === name);
|
|
2375
|
+
// DEX bookkeeping (taker swaps and `OrderPlaced` already returned above).
|
|
2376
|
+
const orderFilled = findEvent('OrderFilled');
|
|
2377
|
+
if (orderFilled) {
|
|
2378
|
+
const a = orderFilled.args;
|
|
2379
|
+
return [
|
|
2380
|
+
{
|
|
2381
|
+
...itemBase(logIndexOfEvent(orderFilled)),
|
|
2382
|
+
data: {
|
|
2383
|
+
amountFilled: a.amountFilled.toString(),
|
|
2384
|
+
maker: lc(a.maker),
|
|
2385
|
+
orderId: a.orderId.toString(),
|
|
2386
|
+
partialFill: a.partialFill,
|
|
2387
|
+
signer,
|
|
2388
|
+
taker: lc(a.taker),
|
|
2389
|
+
},
|
|
2390
|
+
type: 'order-filled',
|
|
2391
|
+
},
|
|
2392
|
+
];
|
|
2393
|
+
}
|
|
2394
|
+
const orderFlipped = findEvent('OrderFlipped');
|
|
2395
|
+
if (orderFlipped) {
|
|
2396
|
+
const a = orderFlipped.args;
|
|
2397
|
+
return [
|
|
2398
|
+
{
|
|
2399
|
+
...itemBase(logIndexOfEvent(orderFlipped)),
|
|
2400
|
+
data: {
|
|
2401
|
+
flipTick: Number(a.flipTick),
|
|
2402
|
+
maker: lc(a.maker),
|
|
2403
|
+
orderId: a.orderId.toString(),
|
|
2404
|
+
side: a.isBid ? 'bid' : 'ask',
|
|
2405
|
+
signer,
|
|
2406
|
+
sourceToken: token(a.amount, a.token),
|
|
2407
|
+
tick: Number(a.tick),
|
|
2408
|
+
},
|
|
2409
|
+
type: 'order-flipped',
|
|
2410
|
+
},
|
|
2411
|
+
];
|
|
2412
|
+
}
|
|
2413
|
+
const pairCreated = findEvent('PairCreated');
|
|
2414
|
+
if (pairCreated) {
|
|
2415
|
+
const a = pairCreated.args;
|
|
2416
|
+
return [
|
|
2417
|
+
{
|
|
2418
|
+
...itemBase(logIndexOfEvent(pairCreated)),
|
|
2419
|
+
data: { base: lc(a.base), key: hx(a.key), quote: lc(a.quote), signer },
|
|
2420
|
+
type: 'pair-created',
|
|
2421
|
+
},
|
|
2422
|
+
];
|
|
2423
|
+
}
|
|
2424
|
+
// TIP-20 token configuration / admin.
|
|
2425
|
+
const pauseStateUpdate = findEvent('PauseStateUpdate');
|
|
2426
|
+
if (pauseStateUpdate) {
|
|
2427
|
+
const a = pauseStateUpdate.args;
|
|
2428
|
+
return [
|
|
2429
|
+
{
|
|
2430
|
+
...itemBase(logIndexOfEvent(pauseStateUpdate)),
|
|
2431
|
+
data: { paused: a.isPaused, signer, updater: lc(a.updater) },
|
|
2432
|
+
type: 'token-pause-set',
|
|
2433
|
+
},
|
|
2434
|
+
];
|
|
2435
|
+
}
|
|
2436
|
+
const supplyCapUpdate = findEvent('SupplyCapUpdate');
|
|
2437
|
+
if (supplyCapUpdate) {
|
|
2438
|
+
const a = supplyCapUpdate.args;
|
|
2439
|
+
return [
|
|
2440
|
+
{
|
|
2441
|
+
...itemBase(logIndexOfEvent(supplyCapUpdate)),
|
|
2442
|
+
data: { signer, supplyCap: a.newSupplyCap.toString(), updater: lc(a.updater) },
|
|
2443
|
+
type: 'token-supply-cap-set',
|
|
2444
|
+
},
|
|
2445
|
+
];
|
|
2446
|
+
}
|
|
2447
|
+
const transferPolicyUpdate = findEvent('TransferPolicyUpdate');
|
|
2448
|
+
if (transferPolicyUpdate) {
|
|
2449
|
+
const a = transferPolicyUpdate.args;
|
|
2450
|
+
return [
|
|
2451
|
+
{
|
|
2452
|
+
...itemBase(logIndexOfEvent(transferPolicyUpdate)),
|
|
2453
|
+
data: { policyId: a.newPolicyId.toString(), signer, updater: lc(a.updater) },
|
|
2454
|
+
type: 'token-transfer-policy-set',
|
|
2455
|
+
},
|
|
2456
|
+
];
|
|
2457
|
+
}
|
|
2458
|
+
const quoteTokenUpdate = findEvent('QuoteTokenUpdate');
|
|
2459
|
+
if (quoteTokenUpdate) {
|
|
2460
|
+
const a = quoteTokenUpdate.args;
|
|
2461
|
+
return [
|
|
2462
|
+
{
|
|
2463
|
+
...itemBase(logIndexOfEvent(quoteTokenUpdate)),
|
|
2464
|
+
data: { quoteToken: lc(a.newQuoteToken), signer, updater: lc(a.updater) },
|
|
2465
|
+
type: 'token-quote-token-set',
|
|
2466
|
+
},
|
|
2467
|
+
];
|
|
2468
|
+
}
|
|
2469
|
+
const nextQuoteTokenSet = findEvent('NextQuoteTokenSet');
|
|
2470
|
+
if (nextQuoteTokenSet) {
|
|
2471
|
+
const a = nextQuoteTokenSet.args;
|
|
2472
|
+
return [
|
|
2473
|
+
{
|
|
2474
|
+
...itemBase(logIndexOfEvent(nextQuoteTokenSet)),
|
|
2475
|
+
data: { nextQuoteToken: lc(a.nextQuoteToken), signer, updater: lc(a.updater) },
|
|
2476
|
+
type: 'token-next-quote-token-set',
|
|
2477
|
+
},
|
|
2478
|
+
];
|
|
2479
|
+
}
|
|
2480
|
+
const logoUriUpdated = findEvent('LogoURIUpdated');
|
|
2481
|
+
if (logoUriUpdated) {
|
|
2482
|
+
const a = logoUriUpdated.args;
|
|
2483
|
+
return [
|
|
2484
|
+
{
|
|
2485
|
+
...itemBase(logIndexOfEvent(logoUriUpdated)),
|
|
2486
|
+
data: { logoUri: a.newLogoURI, signer, updater: lc(a.updater) },
|
|
2487
|
+
type: 'token-logo-set',
|
|
2488
|
+
},
|
|
2489
|
+
];
|
|
2490
|
+
}
|
|
2491
|
+
const roleMembershipUpdated = findEvent('RoleMembershipUpdated');
|
|
2492
|
+
if (roleMembershipUpdated) {
|
|
2493
|
+
const a = roleMembershipUpdated.args;
|
|
2494
|
+
return [
|
|
2495
|
+
{
|
|
2496
|
+
...itemBase(logIndexOfEvent(roleMembershipUpdated)),
|
|
2497
|
+
data: {
|
|
2498
|
+
account: lc(a.account),
|
|
2499
|
+
granted: a.hasRole,
|
|
2500
|
+
role: hx(a.role),
|
|
2501
|
+
sender: lc(a.sender),
|
|
2502
|
+
signer,
|
|
2503
|
+
},
|
|
2504
|
+
type: 'role-membership-set',
|
|
2505
|
+
},
|
|
2506
|
+
];
|
|
2507
|
+
}
|
|
2508
|
+
const roleAdminUpdated = findEvent('RoleAdminUpdated');
|
|
2509
|
+
if (roleAdminUpdated) {
|
|
2510
|
+
const a = roleAdminUpdated.args;
|
|
2511
|
+
return [
|
|
2512
|
+
{
|
|
2513
|
+
...itemBase(logIndexOfEvent(roleAdminUpdated)),
|
|
2514
|
+
data: {
|
|
2515
|
+
newAdminRole: hx(a.newAdminRole),
|
|
2516
|
+
role: hx(a.role),
|
|
2517
|
+
sender: lc(a.sender),
|
|
2518
|
+
signer,
|
|
2519
|
+
},
|
|
2520
|
+
type: 'role-admin-set',
|
|
2521
|
+
},
|
|
2522
|
+
];
|
|
2523
|
+
}
|
|
2524
|
+
// Fee-manager token configuration.
|
|
2525
|
+
const userTokenSet = findEvent('UserTokenSet');
|
|
2526
|
+
if (userTokenSet) {
|
|
2527
|
+
const a = userTokenSet.args;
|
|
2528
|
+
return [
|
|
2529
|
+
{
|
|
2530
|
+
...itemBase(logIndexOfEvent(userTokenSet)),
|
|
2531
|
+
data: { signer, token: lc(a.token), user: lc(a.user) },
|
|
2532
|
+
type: 'fee-user-token-set',
|
|
2533
|
+
},
|
|
2534
|
+
];
|
|
2535
|
+
}
|
|
2536
|
+
const validatorTokenSet = findEvent('ValidatorTokenSet');
|
|
2537
|
+
if (validatorTokenSet) {
|
|
2538
|
+
const a = validatorTokenSet.args;
|
|
2539
|
+
return [
|
|
2540
|
+
{
|
|
2541
|
+
...itemBase(logIndexOfEvent(validatorTokenSet)),
|
|
2542
|
+
data: { signer, token: lc(a.token), validator: lc(a.validator) },
|
|
2543
|
+
type: 'fee-validator-token-set',
|
|
2544
|
+
},
|
|
2545
|
+
];
|
|
2546
|
+
}
|
|
2547
|
+
// TIP-403 policy registry (create wins over the member events it bundles).
|
|
2548
|
+
const compoundPolicyCreated = findEvent('CompoundPolicyCreated');
|
|
2549
|
+
if (compoundPolicyCreated) {
|
|
2550
|
+
const a = compoundPolicyCreated.args;
|
|
2551
|
+
return [
|
|
2552
|
+
{
|
|
2553
|
+
...itemBase(logIndexOfEvent(compoundPolicyCreated)),
|
|
2554
|
+
data: {
|
|
2555
|
+
creator: lc(a.creator),
|
|
2556
|
+
mintRecipientPolicyId: a.mintRecipientPolicyId.toString(),
|
|
2557
|
+
policyId: a.policyId.toString(),
|
|
2558
|
+
recipientPolicyId: a.recipientPolicyId.toString(),
|
|
2559
|
+
senderPolicyId: a.senderPolicyId.toString(),
|
|
2560
|
+
signer,
|
|
2561
|
+
},
|
|
2562
|
+
type: 'compound-policy-created',
|
|
2563
|
+
},
|
|
2564
|
+
];
|
|
2565
|
+
}
|
|
2566
|
+
const policyCreated = findEvent('PolicyCreated');
|
|
2567
|
+
if (policyCreated) {
|
|
2568
|
+
const a = policyCreated.args;
|
|
2569
|
+
return [
|
|
2570
|
+
{
|
|
2571
|
+
...itemBase(logIndexOfEvent(policyCreated)),
|
|
2572
|
+
data: {
|
|
2573
|
+
policyId: a.policyId.toString(),
|
|
2574
|
+
policyType: Number(a.policyType),
|
|
2575
|
+
signer,
|
|
2576
|
+
updater: lc(a.updater),
|
|
2577
|
+
},
|
|
2578
|
+
type: 'policy-created',
|
|
2579
|
+
},
|
|
2580
|
+
];
|
|
2581
|
+
}
|
|
2582
|
+
const policyAdminUpdated = findEvent('PolicyAdminUpdated');
|
|
2583
|
+
if (policyAdminUpdated) {
|
|
2584
|
+
const a = policyAdminUpdated.args;
|
|
2585
|
+
return [
|
|
2586
|
+
{
|
|
2587
|
+
...itemBase(logIndexOfEvent(policyAdminUpdated)),
|
|
2588
|
+
data: {
|
|
2589
|
+
admin: lc(a.admin),
|
|
2590
|
+
policyId: a.policyId.toString(),
|
|
2591
|
+
signer,
|
|
2592
|
+
updater: lc(a.updater),
|
|
2593
|
+
},
|
|
2594
|
+
type: 'policy-admin-set',
|
|
2595
|
+
},
|
|
2596
|
+
];
|
|
2597
|
+
}
|
|
2598
|
+
const whitelistUpdated = findEvent('WhitelistUpdated');
|
|
2599
|
+
if (whitelistUpdated) {
|
|
2600
|
+
const a = whitelistUpdated.args;
|
|
2601
|
+
return [
|
|
2602
|
+
{
|
|
2603
|
+
...itemBase(logIndexOfEvent(whitelistUpdated)),
|
|
2604
|
+
data: {
|
|
2605
|
+
account: lc(a.account),
|
|
2606
|
+
allowed: a.allowed,
|
|
2607
|
+
policyId: a.policyId.toString(),
|
|
2608
|
+
signer,
|
|
2609
|
+
updater: lc(a.updater),
|
|
2610
|
+
},
|
|
2611
|
+
type: 'whitelist-updated',
|
|
2612
|
+
},
|
|
2613
|
+
];
|
|
2614
|
+
}
|
|
2615
|
+
const blacklistUpdated = findEvent('BlacklistUpdated');
|
|
2616
|
+
if (blacklistUpdated) {
|
|
2617
|
+
const a = blacklistUpdated.args;
|
|
2618
|
+
return [
|
|
2619
|
+
{
|
|
2620
|
+
...itemBase(logIndexOfEvent(blacklistUpdated)),
|
|
2621
|
+
data: {
|
|
2622
|
+
account: lc(a.account),
|
|
2623
|
+
policyId: a.policyId.toString(),
|
|
2624
|
+
restricted: a.restricted,
|
|
2625
|
+
signer,
|
|
2626
|
+
updater: lc(a.updater),
|
|
2627
|
+
},
|
|
2628
|
+
type: 'blacklist-updated',
|
|
2629
|
+
},
|
|
2630
|
+
];
|
|
2631
|
+
}
|
|
2632
|
+
// Address / nonce registry.
|
|
2633
|
+
const masterRegistered = findEvent('MasterRegistered');
|
|
2634
|
+
if (masterRegistered) {
|
|
2635
|
+
const a = masterRegistered.args;
|
|
2636
|
+
return [
|
|
2637
|
+
{
|
|
2638
|
+
...itemBase(logIndexOfEvent(masterRegistered)),
|
|
2639
|
+
data: { masterAddress: lc(a.masterAddress), masterId: hx(a.masterId), signer },
|
|
2640
|
+
type: 'master-registered',
|
|
2641
|
+
},
|
|
2642
|
+
];
|
|
2643
|
+
}
|
|
2644
|
+
const nonceIncremented = findEvent('NonceIncremented');
|
|
2645
|
+
if (nonceIncremented) {
|
|
2646
|
+
const a = nonceIncremented.args;
|
|
2647
|
+
return [
|
|
2648
|
+
{
|
|
2649
|
+
...itemBase(logIndexOfEvent(nonceIncremented)),
|
|
2650
|
+
data: {
|
|
2651
|
+
account: lc(a.account),
|
|
2652
|
+
nonce: a.newNonce.toString(),
|
|
2653
|
+
nonceKey: a.nonceKey.toString(),
|
|
2654
|
+
signer,
|
|
2655
|
+
},
|
|
2656
|
+
type: 'nonce-incremented',
|
|
2657
|
+
},
|
|
2658
|
+
];
|
|
2659
|
+
}
|
|
2660
|
+
// Access-key authorization witnesses.
|
|
2661
|
+
const witnessRecorded = findEvent('KeyAuthorizationWitness');
|
|
2662
|
+
if (witnessRecorded) {
|
|
2663
|
+
const a = witnessRecorded.args;
|
|
2664
|
+
return [
|
|
2665
|
+
{
|
|
2666
|
+
...itemBase(logIndexOfEvent(witnessRecorded)),
|
|
2667
|
+
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
2668
|
+
type: 'key-authorization-witness',
|
|
2669
|
+
},
|
|
2670
|
+
];
|
|
2671
|
+
}
|
|
2672
|
+
const witnessBurned = findEvent('KeyAuthorizationWitnessBurned');
|
|
2673
|
+
if (witnessBurned) {
|
|
2674
|
+
const a = witnessBurned.args;
|
|
2675
|
+
return [
|
|
2676
|
+
{
|
|
2677
|
+
...itemBase(logIndexOfEvent(witnessBurned)),
|
|
2678
|
+
data: { account: lc(a.account), signer, witness: hx(a.witness) },
|
|
2679
|
+
type: 'key-authorization-witness-burned',
|
|
2680
|
+
},
|
|
2681
|
+
];
|
|
2682
|
+
}
|
|
2683
|
+
// Validator configuration (rotation supersedes the add/deactivate it bundles).
|
|
2684
|
+
const validatorRotated = findEvent('ValidatorRotated');
|
|
2685
|
+
if (validatorRotated) {
|
|
2686
|
+
const a = validatorRotated.args;
|
|
2687
|
+
return [
|
|
2688
|
+
{
|
|
2689
|
+
...itemBase(logIndexOfEvent(validatorRotated)),
|
|
2690
|
+
data: {
|
|
2691
|
+
caller: lc(a.caller),
|
|
2692
|
+
deactivatedIndex: a.deactivatedIndex.toString(),
|
|
2693
|
+
egress: a.egress,
|
|
2694
|
+
index: a.index.toString(),
|
|
2695
|
+
ingress: a.ingress,
|
|
2696
|
+
newPublicKey: hx(a.newPublicKey),
|
|
2697
|
+
oldPublicKey: hx(a.oldPublicKey),
|
|
2698
|
+
signer,
|
|
2699
|
+
validator: lc(a.validatorAddress),
|
|
2700
|
+
},
|
|
2701
|
+
type: 'validator-rotated',
|
|
2702
|
+
},
|
|
2703
|
+
];
|
|
2704
|
+
}
|
|
2705
|
+
const validatorAdded = findEvent('ValidatorAdded');
|
|
2706
|
+
if (validatorAdded) {
|
|
2707
|
+
const a = validatorAdded.args;
|
|
2708
|
+
return [
|
|
2709
|
+
{
|
|
2710
|
+
...itemBase(logIndexOfEvent(validatorAdded)),
|
|
2711
|
+
data: {
|
|
2712
|
+
egress: a.egress,
|
|
2713
|
+
feeRecipient: lc(a.feeRecipient),
|
|
2714
|
+
index: a.index.toString(),
|
|
2715
|
+
ingress: a.ingress,
|
|
2716
|
+
publicKey: hx(a.publicKey),
|
|
2717
|
+
signer,
|
|
2718
|
+
validator: lc(a.validatorAddress),
|
|
2719
|
+
},
|
|
2720
|
+
type: 'validator-added',
|
|
2721
|
+
},
|
|
2722
|
+
];
|
|
2723
|
+
}
|
|
2724
|
+
const validatorDeactivated = findEvent('ValidatorDeactivated');
|
|
2725
|
+
if (validatorDeactivated) {
|
|
2726
|
+
const a = validatorDeactivated.args;
|
|
2727
|
+
return [
|
|
2728
|
+
{
|
|
2729
|
+
...itemBase(logIndexOfEvent(validatorDeactivated)),
|
|
2730
|
+
data: { index: a.index.toString(), signer, validator: lc(a.validatorAddress) },
|
|
2731
|
+
type: 'validator-deactivated',
|
|
2732
|
+
},
|
|
2733
|
+
];
|
|
2734
|
+
}
|
|
2735
|
+
const validatorMigrated = findEvent('ValidatorMigrated');
|
|
2736
|
+
if (validatorMigrated) {
|
|
2737
|
+
const a = validatorMigrated.args;
|
|
2738
|
+
return [
|
|
2739
|
+
{
|
|
2740
|
+
...itemBase(logIndexOfEvent(validatorMigrated)),
|
|
2741
|
+
data: {
|
|
2742
|
+
index: a.index.toString(),
|
|
2743
|
+
publicKey: hx(a.publicKey),
|
|
2744
|
+
signer,
|
|
2745
|
+
validator: lc(a.validatorAddress),
|
|
2746
|
+
},
|
|
2747
|
+
type: 'validator-migrated',
|
|
2748
|
+
},
|
|
2749
|
+
];
|
|
2750
|
+
}
|
|
2751
|
+
const validatorMigrationSkipped = findEvent('SkippedValidatorMigration');
|
|
2752
|
+
if (validatorMigrationSkipped) {
|
|
2753
|
+
const a = validatorMigrationSkipped.args;
|
|
2754
|
+
return [
|
|
2755
|
+
{
|
|
2756
|
+
...itemBase(logIndexOfEvent(validatorMigrationSkipped)),
|
|
2757
|
+
data: {
|
|
2758
|
+
index: a.index.toString(),
|
|
2759
|
+
publicKey: hx(a.publicKey),
|
|
2760
|
+
signer,
|
|
2761
|
+
validator: lc(a.validatorAddress),
|
|
2762
|
+
},
|
|
2763
|
+
type: 'validator-migration-skipped',
|
|
2764
|
+
},
|
|
2765
|
+
];
|
|
2766
|
+
}
|
|
2767
|
+
const feeRecipientUpdated = findEvent('FeeRecipientUpdated');
|
|
2768
|
+
if (feeRecipientUpdated) {
|
|
2769
|
+
const a = feeRecipientUpdated.args;
|
|
2770
|
+
return [
|
|
2771
|
+
{
|
|
2772
|
+
...itemBase(logIndexOfEvent(feeRecipientUpdated)),
|
|
2773
|
+
data: {
|
|
2774
|
+
caller: lc(a.caller),
|
|
2775
|
+
feeRecipient: lc(a.feeRecipient),
|
|
2776
|
+
index: a.index.toString(),
|
|
2777
|
+
signer,
|
|
2778
|
+
},
|
|
2779
|
+
type: 'validator-fee-recipient-set',
|
|
2780
|
+
},
|
|
2781
|
+
];
|
|
2782
|
+
}
|
|
2783
|
+
const ipAddressesUpdated = findEvent('IpAddressesUpdated');
|
|
2784
|
+
if (ipAddressesUpdated) {
|
|
2785
|
+
const a = ipAddressesUpdated.args;
|
|
2786
|
+
return [
|
|
2787
|
+
{
|
|
2788
|
+
...itemBase(logIndexOfEvent(ipAddressesUpdated)),
|
|
2789
|
+
data: {
|
|
2790
|
+
caller: lc(a.caller),
|
|
2791
|
+
egress: a.egress,
|
|
2792
|
+
index: a.index.toString(),
|
|
2793
|
+
ingress: a.ingress,
|
|
2794
|
+
signer,
|
|
2795
|
+
},
|
|
2796
|
+
type: 'validator-ip-set',
|
|
2797
|
+
},
|
|
2798
|
+
];
|
|
2799
|
+
}
|
|
2800
|
+
const validatorOwnershipTransferred = findEvent('ValidatorOwnershipTransferred');
|
|
2801
|
+
if (validatorOwnershipTransferred) {
|
|
2802
|
+
const a = validatorOwnershipTransferred.args;
|
|
2803
|
+
return [
|
|
2804
|
+
{
|
|
2805
|
+
...itemBase(logIndexOfEvent(validatorOwnershipTransferred)),
|
|
2806
|
+
data: {
|
|
2807
|
+
caller: lc(a.caller),
|
|
2808
|
+
index: a.index.toString(),
|
|
2809
|
+
newAddress: lc(a.newAddress),
|
|
2810
|
+
oldAddress: lc(a.oldAddress),
|
|
2811
|
+
signer,
|
|
2812
|
+
},
|
|
2813
|
+
type: 'validator-ownership-transferred',
|
|
2814
|
+
},
|
|
2815
|
+
];
|
|
2816
|
+
}
|
|
2817
|
+
const ownershipTransferred = findEvent('OwnershipTransferred');
|
|
2818
|
+
if (ownershipTransferred) {
|
|
2819
|
+
const a = ownershipTransferred.args;
|
|
2820
|
+
return [
|
|
2821
|
+
{
|
|
2822
|
+
...itemBase(logIndexOfEvent(ownershipTransferred)),
|
|
2823
|
+
data: { newOwner: lc(a.newOwner), oldOwner: lc(a.oldOwner), signer },
|
|
2824
|
+
type: 'ownership-transferred',
|
|
2825
|
+
},
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
const rotationEpochSet = findEvent('NetworkIdentityRotationEpochSet');
|
|
2829
|
+
if (rotationEpochSet) {
|
|
2830
|
+
const a = rotationEpochSet.args;
|
|
2831
|
+
return [
|
|
2832
|
+
{
|
|
2833
|
+
...itemBase(logIndexOfEvent(rotationEpochSet)),
|
|
2834
|
+
data: {
|
|
2835
|
+
nextEpoch: a.nextEpoch.toString(),
|
|
2836
|
+
previousEpoch: a.previousEpoch.toString(),
|
|
2837
|
+
signer,
|
|
2838
|
+
},
|
|
2839
|
+
type: 'network-identity-rotation-epoch-set',
|
|
2840
|
+
},
|
|
2841
|
+
];
|
|
2842
|
+
}
|
|
2843
|
+
const initialized = findEvent('Initialized');
|
|
2844
|
+
if (initialized) {
|
|
2845
|
+
const a = initialized.args;
|
|
2846
|
+
return [
|
|
2847
|
+
{
|
|
2848
|
+
...itemBase(logIndexOfEvent(initialized)),
|
|
2849
|
+
data: { height: a.height.toString(), signer },
|
|
2850
|
+
type: 'initialized',
|
|
2851
|
+
},
|
|
2852
|
+
];
|
|
2853
|
+
}
|
|
2854
|
+
// Default: plain transfer (excluding transfers to the fee manager).
|
|
2855
|
+
const userTransfers = transfers.filter((e) => !Address.isEqual(e.args.to, Addresses.feeManager));
|
|
2856
|
+
if (userTransfers.length > 0) {
|
|
2857
|
+
const t = userTransfers[0];
|
|
2858
|
+
const args = t.args;
|
|
2859
|
+
const to = args.to.toLowerCase();
|
|
2860
|
+
const from = args.from.toLowerCase();
|
|
2861
|
+
const rawMemo = t.eventName === 'TransferWithMemo' ? args.memo : undefined;
|
|
2862
|
+
// The raw memo (not the suppressed display string) carries the MPP
|
|
2863
|
+
// attribution fingerprint, so resolve the service name from it.
|
|
2864
|
+
const memo = rawMemo ? Transfers.memoToString(rawMemo) : undefined;
|
|
2865
|
+
const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo });
|
|
2866
|
+
const sourceToken = token(args.amount, t.address);
|
|
2867
|
+
if (Address.isEqual(to, viewer))
|
|
2868
|
+
return [
|
|
2869
|
+
transfer(itemBase(logIndexOfEvent(t)), blockNum, {
|
|
2870
|
+
attribution,
|
|
2871
|
+
direction: 'in',
|
|
2872
|
+
memo,
|
|
2873
|
+
recipient: viewer,
|
|
2874
|
+
sender: from,
|
|
2875
|
+
sourceToken,
|
|
2876
|
+
}),
|
|
2877
|
+
...accessKeyItems,
|
|
2878
|
+
];
|
|
2879
|
+
return [
|
|
2880
|
+
transfer(itemBase(logIndexOfEvent(t)), blockNum, {
|
|
2881
|
+
attribution,
|
|
2882
|
+
direction: 'out',
|
|
2883
|
+
memo,
|
|
2884
|
+
recipient: to,
|
|
2885
|
+
sender: from,
|
|
2886
|
+
signer,
|
|
2887
|
+
sourceToken,
|
|
2888
|
+
}),
|
|
2889
|
+
...accessKeyItems,
|
|
2890
|
+
];
|
|
2891
|
+
}
|
|
2892
|
+
// Pure access-key tx (no transfer / mint / burn / etc.).
|
|
2893
|
+
if (accessKeyItems.length > 0)
|
|
2894
|
+
return accessKeyItems;
|
|
2895
|
+
// Unclassified: a tx the address sent or paid for whose logs matched no known
|
|
2896
|
+
// classifier. Surface it honestly as an `unknown` item carrying every log
|
|
2897
|
+
// (decoded when possible), rather than fabricating a zero transfer.
|
|
2898
|
+
return [unknown(itemBase(minLogIndex), { events: buildTxEvents(rows, events), signer })];
|
|
2899
|
+
}
|
|
2900
|
+
/** Extracts the block-wide log index from a decoded viem event. */
|
|
2901
|
+
function logIndexOfEvent(event) {
|
|
2902
|
+
return event.logIndex;
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Builds a `transfer` item whose `data` is the `Transfer` API resource plus the
|
|
2906
|
+
* feed-only `direction`/`memo`/`attribution`/`signer` fields. Optional fields
|
|
2907
|
+
* are omitted when absent (`exactOptionalPropertyTypes`).
|
|
2908
|
+
*/
|
|
2909
|
+
function transfer(base, blockNumber, data) {
|
|
2910
|
+
return {
|
|
2911
|
+
...base,
|
|
2912
|
+
data: {
|
|
2913
|
+
...(data.attribution ? { attribution: data.attribution } : {}),
|
|
2914
|
+
blockNumber,
|
|
2915
|
+
...(data.destinationToken ? { destinationToken: data.destinationToken } : {}),
|
|
2916
|
+
direction: data.direction,
|
|
2917
|
+
...(data.memo ? { memo: data.memo } : {}),
|
|
2918
|
+
recipient: data.recipient,
|
|
2919
|
+
sender: data.sender,
|
|
2920
|
+
...(data.signer ? { signer: data.signer } : {}),
|
|
2921
|
+
sourceToken: data.sourceToken,
|
|
2922
|
+
timestamp: base.timestamp,
|
|
2923
|
+
transactionHash: base.transactionHash,
|
|
2924
|
+
},
|
|
2925
|
+
type: 'transfer',
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
/**
|
|
2929
|
+
* Builds an `unknown` item for a transaction the address sent or paid for that
|
|
2930
|
+
* did not match any known classifier. Carries the real `transactionHash`/
|
|
2931
|
+
* `timestamp` plus every log of the transaction in the top-level `events`
|
|
2932
|
+
* array (decoded into `eventName`/`args`/`title` when the log matched a known
|
|
2933
|
+
* `viem/tempo` ABI, otherwise just the raw `topics`/`data`), so no log is ever
|
|
2934
|
+
* silently dropped.
|
|
2935
|
+
*/
|
|
2936
|
+
function unknown(base, data) {
|
|
2937
|
+
return { ...base, data: { signer: data.signer }, events: data.events, type: 'unknown' };
|
|
2938
|
+
}
|
|
2939
|
+
/** Decodes a group's raw rows into `viem/tempo` events via `parseEventLogs` (non-strict). */
|
|
2940
|
+
function decodeRows(rows) {
|
|
2941
|
+
const viemLogs = rows.map((r) => ({
|
|
2942
|
+
address: r.logAddress,
|
|
2943
|
+
blockHash: '0x0',
|
|
2944
|
+
blockNumber: BigInt(r.blockNum),
|
|
2945
|
+
data: (r.data ?? '0x'),
|
|
2946
|
+
logIndex: r.logIdx,
|
|
2947
|
+
removed: false,
|
|
2948
|
+
topics: [r.selector, r.topic1, r.topic2, r.topic3].filter(Boolean),
|
|
2949
|
+
transactionHash: r.txHash,
|
|
2950
|
+
transactionIndex: r.txIdx,
|
|
2951
|
+
}));
|
|
2952
|
+
return parseEventLogs({ abi: [...Abis.abis], logs: viemLogs, strict: false });
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* Builds the full per-transaction `events` capture: one entry per raw log,
|
|
2956
|
+
* enriched with `eventName`/`args`/`title` when that log decoded into a known
|
|
2957
|
+
* event. Decoded entries are matched to their raw log by block-wide log index,
|
|
2958
|
+
* so the result covers every log (decoded or not) exactly once.
|
|
2959
|
+
*/
|
|
2960
|
+
function buildTxEvents(rows, decoded) {
|
|
2961
|
+
const decodedByIndex = new Map(decoded.map((event) => [event.logIndex, event]));
|
|
2962
|
+
return rows
|
|
2963
|
+
.map((row) => {
|
|
2964
|
+
const base = {
|
|
2965
|
+
address: row.logAddress,
|
|
2966
|
+
data: (row.data ?? '0x'),
|
|
2967
|
+
logIndex: row.logIdx,
|
|
2968
|
+
topics: [row.selector, row.topic1, row.topic2, row.topic3].filter((topic) => topic !== null),
|
|
2969
|
+
};
|
|
2970
|
+
const event = decodedByIndex.get(row.logIdx);
|
|
2971
|
+
if (!event)
|
|
2972
|
+
return base;
|
|
2973
|
+
return {
|
|
2974
|
+
...base,
|
|
2975
|
+
args: (jsonSafe(event.args) ?? {}),
|
|
2976
|
+
eventName: event.eventName,
|
|
2977
|
+
...(titleByEventName[event.eventName] ? { title: titleByEventName[event.eventName] } : {}),
|
|
2978
|
+
};
|
|
2979
|
+
})
|
|
2980
|
+
.sort((a, b) => a.logIndex - b.logIndex);
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Recursively converts decoded-event values into JSON-serializable form: bigints
|
|
2984
|
+
* become decimal strings, address-shaped strings are lowercased to match the
|
|
2985
|
+
* API's address convention (viem returns them checksummed), arrays/objects are
|
|
2986
|
+
* walked, everything else passes through. Applied to `parseEventLogs` args
|
|
2987
|
+
* before they cross the API boundary.
|
|
2988
|
+
*/
|
|
2989
|
+
function jsonSafe(value) {
|
|
2990
|
+
if (typeof value === 'bigint')
|
|
2991
|
+
return value.toString();
|
|
2992
|
+
if (typeof value === 'string')
|
|
2993
|
+
return /^0x[0-9a-fA-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
2994
|
+
if (Array.isArray(value))
|
|
2995
|
+
return value.map(jsonSafe);
|
|
2996
|
+
if (value && typeof value === 'object') {
|
|
2997
|
+
const out = {};
|
|
2998
|
+
for (const [key, val] of Object.entries(value))
|
|
2999
|
+
out[key] = jsonSafe(val);
|
|
3000
|
+
return out;
|
|
3001
|
+
}
|
|
3002
|
+
return value;
|
|
3003
|
+
}
|
|
3004
|
+
/** Lowercases a TIDX address column, defaulting to the zero address. */
|
|
3005
|
+
function lower(value) {
|
|
3006
|
+
return (Value.toText(value)?.toLowerCase() ?? zeroAddress);
|
|
3007
|
+
}
|
|
3008
|
+
/** Lowercases a TIDX transaction hash column, defaulting to `0x` when absent. */
|
|
3009
|
+
function lowerHash(value) {
|
|
3010
|
+
return (Value.toText(value)?.toLowerCase() ?? '0x');
|
|
3011
|
+
}
|
|
3012
|
+
/** Lowercases an optional TIDX address column, or null when absent. */
|
|
3013
|
+
function lowerOrNull(value) {
|
|
3014
|
+
const text = Value.toText(value);
|
|
3015
|
+
return text ? text.toLowerCase() : null;
|
|
3016
|
+
}
|
|
3017
|
+
/** Builds a stub token; `decimals`/`symbol` are filled by {@link enrichItem}. */
|
|
3018
|
+
function token(amount, address) {
|
|
3019
|
+
return { address: address.toLowerCase(), amount: (amount ?? 0n).toString() };
|
|
3020
|
+
}
|
|
3021
|
+
/** Collects every token address referenced by an item's `data`. */
|
|
3022
|
+
function collectTokens(item, set) {
|
|
3023
|
+
const data = item.data;
|
|
3024
|
+
for (const key of tokenKeys) {
|
|
3025
|
+
const t = data[key];
|
|
3026
|
+
if (t)
|
|
3027
|
+
set.add(t.address.toLowerCase());
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Resolves metadata for every token referenced across a page of items and
|
|
3032
|
+
* spreads it into each item's stub tokens, in place. Routes through the shared
|
|
3033
|
+
* {@link Tokens.getTokenMetadata} (one `token.getMetadata` RPC per token,
|
|
3034
|
+
* memoized under the `token:v1:…` key shared with `/tokens` and `/transfers`)
|
|
3035
|
+
* instead of a parallel metadata fetch. Resolution is best-effort: a token
|
|
3036
|
+
* whose metadata is unavailable keeps just `address`/`amount`.
|
|
3037
|
+
*/
|
|
3038
|
+
async function enrichTokens(c, options) {
|
|
3039
|
+
const { chainId, items } = options;
|
|
3040
|
+
const addresses = new Set();
|
|
3041
|
+
for (const item of items)
|
|
3042
|
+
collectTokens(item, addresses);
|
|
3043
|
+
if (addresses.size === 0)
|
|
3044
|
+
return;
|
|
3045
|
+
const resolved = await Timing.time(c, 'tokens', () => Promise.all([...addresses].map((address) => Tokens.getTokenMetadata(c, { address, chainId })
|
|
3046
|
+
.then((metadata) => [address.toLowerCase(), metadata])
|
|
3047
|
+
.catch(() => [address.toLowerCase(), undefined]))));
|
|
3048
|
+
const byAddress = new Map(resolved);
|
|
3049
|
+
for (const item of items)
|
|
3050
|
+
enrichItem(item, byAddress);
|
|
3051
|
+
}
|
|
3052
|
+
/** Spreads resolved token metadata into an item's stub tokens, in place. */
|
|
3053
|
+
function enrichItem(item, byAddress) {
|
|
3054
|
+
const data = item.data;
|
|
3055
|
+
for (const key of tokenKeys) {
|
|
3056
|
+
const t = data[key];
|
|
3057
|
+
if (!t)
|
|
3058
|
+
continue;
|
|
3059
|
+
const meta = byAddress.get(t.address.toLowerCase());
|
|
3060
|
+
if (!meta)
|
|
3061
|
+
continue;
|
|
3062
|
+
// Carry only the per-row token fields the `Token` (SourceToken) shape
|
|
3063
|
+
// declares; `address`/`amount` come from the stub.
|
|
3064
|
+
data[key] = {
|
|
3065
|
+
...t,
|
|
3066
|
+
currency: meta.currency,
|
|
3067
|
+
decimals: meta.decimals,
|
|
3068
|
+
name: meta.name,
|
|
3069
|
+
symbol: meta.symbol,
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Folds adjacent items signed by the same access key on the same UTC calendar
|
|
3075
|
+
* day into a single `group` entry. Self-signed items pass through untouched.
|
|
3076
|
+
* Must be called once over the full, timestamp-sorted item list.
|
|
3077
|
+
*/
|
|
3078
|
+
export function groupByAccessKey(items) {
|
|
3079
|
+
const entries = [];
|
|
3080
|
+
let currentGroup = null;
|
|
3081
|
+
for (const item of items) {
|
|
3082
|
+
const itemSigner = signerOf(item);
|
|
3083
|
+
if (itemSigner !== 'self') {
|
|
3084
|
+
if (currentGroup &&
|
|
3085
|
+
Address.isEqual(currentGroup.signer, itemSigner) &&
|
|
3086
|
+
sameCalendarDay(currentGroup.items[0].timestamp, item.timestamp)) {
|
|
3087
|
+
currentGroup.items.push(item);
|
|
3088
|
+
if (currentGroup.items.length >= maxGroupSize) {
|
|
3089
|
+
entries.push(flushGroup(currentGroup));
|
|
3090
|
+
currentGroup = null;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
else {
|
|
3094
|
+
if (currentGroup)
|
|
3095
|
+
entries.push(flushGroup(currentGroup));
|
|
3096
|
+
currentGroup = { items: [item], signer: itemSigner };
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
else {
|
|
3100
|
+
if (currentGroup) {
|
|
3101
|
+
entries.push(flushGroup(currentGroup));
|
|
3102
|
+
currentGroup = null;
|
|
3103
|
+
}
|
|
3104
|
+
entries.push(item);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
if (currentGroup)
|
|
3108
|
+
entries.push(flushGroup(currentGroup));
|
|
3109
|
+
return entries;
|
|
3110
|
+
}
|
|
3111
|
+
/** The access-key signer of an item, or `self` when it has no access-key signer. */
|
|
3112
|
+
function signerOf(item) {
|
|
3113
|
+
return item.data.signer ?? 'self';
|
|
3114
|
+
}
|
|
3115
|
+
/** Unwraps single-item groups; otherwise wraps the items in a `group` entry. */
|
|
3116
|
+
function flushGroup(group) {
|
|
3117
|
+
if (group.items.length === 1)
|
|
3118
|
+
return group.items[0];
|
|
3119
|
+
const firstItem = group.items[0];
|
|
3120
|
+
const lastItem = group.items[group.items.length - 1];
|
|
3121
|
+
return {
|
|
3122
|
+
data: { items: group.items, signer: group.signer },
|
|
3123
|
+
perspective: firstItem.perspective,
|
|
3124
|
+
id: `${firstItem.id}..${lastItem.id}:${group.items.length}`,
|
|
3125
|
+
timestamp: firstItem.timestamp,
|
|
3126
|
+
title: 'Group',
|
|
3127
|
+
type: 'group',
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
/** Whether two ISO 8601 timestamps fall on the same UTC calendar day. */
|
|
3131
|
+
function sameCalendarDay(a, b) {
|
|
3132
|
+
const da = new Date(a);
|
|
3133
|
+
const db = new Date(b);
|
|
3134
|
+
return (da.getUTCFullYear() === db.getUTCFullYear() &&
|
|
3135
|
+
da.getUTCMonth() === db.getUTCMonth() &&
|
|
3136
|
+
da.getUTCDate() === db.getUTCDate());
|
|
3137
|
+
}
|
|
3138
|
+
//# sourceMappingURL=activities.js.map
|