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,1020 @@
|
|
|
1
|
+
import { Address, Hex } from 'ox'
|
|
2
|
+
import { encodeAbiParameters, encodeEventTopics } from 'viem'
|
|
3
|
+
import { Abis } from 'viem/tempo'
|
|
4
|
+
|
|
5
|
+
import * as TestApp from '../../test/App.js'
|
|
6
|
+
import * as Runtime from '../../test/runtime.js'
|
|
7
|
+
import * as Store from '../internal/Store.js'
|
|
8
|
+
import * as Activities from './activities.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Real Tempo (Moderato) accounts, each exercising specific activity types.
|
|
12
|
+
* Discovered by scanning indexed `logs` for the event each type classifies
|
|
13
|
+
* from, then confirming the classified feed through the live endpoint. If the
|
|
14
|
+
* testnet prunes this history, rediscover replacements the same way.
|
|
15
|
+
*/
|
|
16
|
+
const liveAccounts = {
|
|
17
|
+
// Payment-channel close (session-closed), folded into access-key groups.
|
|
18
|
+
channel: '0x5217c502adf3c96a9551770af4caa5f5144a6984',
|
|
19
|
+
// TokenCreated + Mint + Transfer.
|
|
20
|
+
creator: '0x253fb3bf1e3d75ad6ee1554f68f4ea0568f7d24e',
|
|
21
|
+
// Token burns.
|
|
22
|
+
burner: '0x69519497b59bd64e109d993157d45f14455cc7e6',
|
|
23
|
+
// KeyAuthorized + KeyRevoked + Approval (+ transfer/mint/exchange).
|
|
24
|
+
keychain: '0xfe776fd28e06ecc71a157398b2c9991318f1ea2e',
|
|
25
|
+
// Swaps through the stablecoin DEX (output kept, so it stays a plain `swap`).
|
|
26
|
+
swapper: '0x7f4e7fdd75e4c5beb77d0eb223823d3a5a9865c0',
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
const runtime = Runtime.get()
|
|
30
|
+
const localAccounts = runtime.fixtures?.activityAccounts
|
|
31
|
+
const accounts =
|
|
32
|
+
runtime.mode === 'localnet' && localAccounts
|
|
33
|
+
? {
|
|
34
|
+
channel: localAccounts.keychain,
|
|
35
|
+
creator: localAccounts.creator,
|
|
36
|
+
burner: localAccounts.burner,
|
|
37
|
+
keychain: localAccounts.keychain,
|
|
38
|
+
swapper: localAccounts.swapper,
|
|
39
|
+
}
|
|
40
|
+
: liveAccounts
|
|
41
|
+
|
|
42
|
+
/** The account expected to surface each activity type. */
|
|
43
|
+
const coverage =
|
|
44
|
+
runtime.mode === 'localnet'
|
|
45
|
+
? {
|
|
46
|
+
approval: accounts.keychain,
|
|
47
|
+
burn: accounts.burner,
|
|
48
|
+
mint: accounts.creator,
|
|
49
|
+
'token-created': accounts.creator,
|
|
50
|
+
transfer: accounts.creator,
|
|
51
|
+
}
|
|
52
|
+
: {
|
|
53
|
+
'access-key-created': accounts.keychain,
|
|
54
|
+
'access-key-revoked': accounts.keychain,
|
|
55
|
+
approval: accounts.keychain,
|
|
56
|
+
burn: accounts.burner,
|
|
57
|
+
mint: accounts.creator,
|
|
58
|
+
'session-closed': accounts.channel,
|
|
59
|
+
swap: accounts.swapper,
|
|
60
|
+
'token-created': accounts.creator,
|
|
61
|
+
transfer: accounts.creator,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const client = TestApp.client()
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Requests an address's activity page, retrying transient upstream `502`s
|
|
68
|
+
* (TIDX surfaces a busy ClickHouse planner as a `422` the route maps to `502`).
|
|
69
|
+
*/
|
|
70
|
+
async function request(address: string, query: Record<string, string> = {}) {
|
|
71
|
+
let response = await activitiesRequest(address, query)
|
|
72
|
+
for (let attempt = 0; attempt < 2 && response.status !== 200; attempt++)
|
|
73
|
+
response = await activitiesRequest(address, query)
|
|
74
|
+
return response
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function activitiesRequest(address: string, query: Record<string, string>) {
|
|
78
|
+
return client.v1.addresses[':address'].activities.$get(
|
|
79
|
+
{ param: { address: address as Hex.Hex }, query: { limit: '50', ...query } },
|
|
80
|
+
TestApp.auth,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Requests and schema-validates an address's activity page. */
|
|
85
|
+
async function activities(address: string, query: Record<string, string> = {}) {
|
|
86
|
+
return TestApp.json(
|
|
87
|
+
await request(address, query),
|
|
88
|
+
Activities.schema.getAddressActivities.Response,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Finds an entry (or grouped item) of the given type, searching inside groups. */
|
|
93
|
+
function find(items: readonly Activities.Entry[], type: string): Activities.Entry | undefined {
|
|
94
|
+
for (const entry of items) {
|
|
95
|
+
if (entry.type === type) return entry
|
|
96
|
+
if (entry.type === 'group') {
|
|
97
|
+
const item = entry.data.items.find((i) => i.type === type)
|
|
98
|
+
if (item) return item
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe('GET /addresses/:address/activities', () => {
|
|
105
|
+
test('returns a newest-first, schema-valid feed with timing', async () => {
|
|
106
|
+
const response = await request(accounts.keychain, { limit: '10' })
|
|
107
|
+
expect(response.status).toBe(200)
|
|
108
|
+
expect(response.headers.get('server-timing')?.includes('address_activities;dur=')).toBe(true)
|
|
109
|
+
|
|
110
|
+
const body = await TestApp.json(response, Activities.schema.getAddressActivities.Response)
|
|
111
|
+
expect(Array.isArray(body.data)).toBe(true)
|
|
112
|
+
expect(body.nextCursor === null || typeof body.nextCursor === 'string').toBe(true)
|
|
113
|
+
|
|
114
|
+
// Entries are ordered newest-first by timestamp (ISO 8601 sorts
|
|
115
|
+
// lexicographically the same as chronologically).
|
|
116
|
+
const timestamps = body.data.map((entry) => entry.timestamp)
|
|
117
|
+
expect(timestamps.every((ts, i) => i === 0 || timestamps[i - 1]! >= ts)).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test.each(Object.entries(coverage))('classifies %s activity', async (type, address) => {
|
|
121
|
+
const body = await activities(address)
|
|
122
|
+
expect(find(body.data, type)).toBeDefined()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('transfer data mirrors the `Transfer` resource', async () => {
|
|
126
|
+
const transfer = find((await activities(accounts.creator)).data, 'transfer')
|
|
127
|
+
expect(transfer?.type).toBe('transfer')
|
|
128
|
+
if (transfer?.type !== 'transfer') return
|
|
129
|
+
expect(['in', 'out']).toContain(transfer.data.direction)
|
|
130
|
+
expect(transfer.data.transactionHash).toMatch(/^0x[0-9a-f]{64}$/)
|
|
131
|
+
expect(typeof transfer.data.blockNumber).toBe('number')
|
|
132
|
+
expect(transfer.data.sourceToken.address).toMatch(/^0x[0-9a-f]{40}$/)
|
|
133
|
+
expect(transfer.data.sourceToken.amount).toMatch(/^\d+$/)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test.skipIf(runtime.mode === 'localnet')('swap data carries both sides of the swap', async () => {
|
|
137
|
+
const swap = find((await activities(accounts.swapper)).data, 'swap')
|
|
138
|
+
expect(swap?.type).toBe('swap')
|
|
139
|
+
if (swap?.type !== 'swap') return
|
|
140
|
+
expect(swap.data.sourceToken.address).toMatch(/^0x[0-9a-f]{40}$/)
|
|
141
|
+
expect(swap.data.destinationToken.address).toMatch(/^0x[0-9a-f]{40}$/)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test.skipIf(runtime.mode !== 'localnet')(
|
|
145
|
+
'surfaces a cross-token transfer as outgoing in the sender feed',
|
|
146
|
+
async () => {
|
|
147
|
+
const crossToken = runtime.fixtures?.crossTokenTransfer
|
|
148
|
+
expect(crossToken).toBeDefined()
|
|
149
|
+
if (!crossToken) return
|
|
150
|
+
|
|
151
|
+
const { data } = await activities(crossToken.sender)
|
|
152
|
+
const transfer = data.find(
|
|
153
|
+
(entry) =>
|
|
154
|
+
entry.type === 'transfer' &&
|
|
155
|
+
entry.transactionHash === crossToken.hash &&
|
|
156
|
+
entry.data.destinationToken?.address === crossToken.destinationToken,
|
|
157
|
+
)
|
|
158
|
+
expect(transfer?.type).toBe('transfer')
|
|
159
|
+
if (transfer?.type !== 'transfer') return
|
|
160
|
+
expect(transfer.data.direction).toBe('out')
|
|
161
|
+
expect(transfer.data.sender).toBe(crossToken.sender)
|
|
162
|
+
expect(transfer.data.recipient).toBe(crossToken.recipient)
|
|
163
|
+
expect(transfer.data.sourceToken.address).toBe(crossToken.sourceToken)
|
|
164
|
+
expect(transfer.data.destinationToken?.address).toBe(crossToken.destinationToken)
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
test.skipIf(runtime.mode !== 'localnet')(
|
|
169
|
+
'surfaces a cross-token transfer as a plain incoming delivery in the recipient feed',
|
|
170
|
+
async () => {
|
|
171
|
+
const crossToken = runtime.fixtures?.crossTokenTransfer
|
|
172
|
+
expect(crossToken).toBeDefined()
|
|
173
|
+
if (!crossToken) return
|
|
174
|
+
|
|
175
|
+
// The recipient feed is built only from the inbound delivery leg (the
|
|
176
|
+
// sender's `from`/`fee_payer` rows, which carry the swap, are out of
|
|
177
|
+
// scope for the recipient). So the recipient sees an ordinary incoming
|
|
178
|
+
// transfer of the token they received, with no cross-token framing: the
|
|
179
|
+
// delivered token is the `sourceToken` and there is no `destinationToken`.
|
|
180
|
+
const { data } = await activities(crossToken.recipient)
|
|
181
|
+
const transfer = data.find(
|
|
182
|
+
(entry) => entry.type === 'transfer' && entry.transactionHash === crossToken.hash,
|
|
183
|
+
)
|
|
184
|
+
expect(transfer?.type).toBe('transfer')
|
|
185
|
+
if (transfer?.type !== 'transfer') return
|
|
186
|
+
expect(transfer.data.direction).toBe('in')
|
|
187
|
+
expect(transfer.data.sender).toBe(crossToken.sender)
|
|
188
|
+
expect(transfer.data.recipient).toBe(crossToken.recipient)
|
|
189
|
+
expect(transfer.data.sourceToken.address).toBe(crossToken.destinationToken)
|
|
190
|
+
expect(transfer.data.destinationToken).toBeUndefined()
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
test.skipIf(runtime.mode === 'localnet')(
|
|
195
|
+
'folds consecutive access-key items into a group when group=true',
|
|
196
|
+
async () => {
|
|
197
|
+
const group = find((await activities(accounts.channel, { group: 'true' })).data, 'group')
|
|
198
|
+
expect(group?.type).toBe('group')
|
|
199
|
+
if (group?.type !== 'group') return
|
|
200
|
+
expect(group.data.items.length).toBeGreaterThan(1)
|
|
201
|
+
expect(group.data.signer).toMatch(/^0x[0-9a-f]{40}$/)
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
test('returns every item at the root by default (no group entries)', async () => {
|
|
206
|
+
const body = await activities(accounts.channel)
|
|
207
|
+
expect(body.data.every((entry) => entry.type !== 'group')).toBe(true)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('paginates with an opaque cursor', async () => {
|
|
211
|
+
const first = await activities(accounts.keychain, { limit: '5' })
|
|
212
|
+
if (!first.nextCursor) return
|
|
213
|
+
const second = await activities(accounts.keychain, { limit: '5', cursor: first.nextCursor })
|
|
214
|
+
expect(Array.isArray(second.data)).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
/** Resolves a real transaction hash from an address's (non-grouped) activity feed. */
|
|
219
|
+
async function firstTransactionHash(address: string): Promise<Hex.Hex | undefined> {
|
|
220
|
+
const { data } = await activities(address)
|
|
221
|
+
for (const entry of data) if (entry.type !== 'group') return entry.transactionHash
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function transactionActivitiesRequest(transactionHash: string, query: Record<string, string> = {}) {
|
|
226
|
+
return client.v1.transactions[':transactionHash'].activities.$get(
|
|
227
|
+
{ param: { transactionHash: transactionHash as Hex.Hex }, query },
|
|
228
|
+
TestApp.auth,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
describe('GET /transactions/:transactionHash/activities', () => {
|
|
233
|
+
test('classifies the activity on a real transaction', async () => {
|
|
234
|
+
// Resolve a real transaction hash from the address feed, then classify it
|
|
235
|
+
// through the transaction-scoped endpoint.
|
|
236
|
+
const transactionHash = await firstTransactionHash(accounts.creator)
|
|
237
|
+
expect(transactionHash).toBeDefined()
|
|
238
|
+
if (!transactionHash) return
|
|
239
|
+
|
|
240
|
+
const response = await transactionActivitiesRequest(transactionHash)
|
|
241
|
+
expect(response.status).toBe(200)
|
|
242
|
+
expect(response.headers.get('server-timing')?.includes('transaction_activities;dur=')).toBe(
|
|
243
|
+
true,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
const body = await TestApp.json(response, Activities.schema.getTransactionActivities.Response)
|
|
247
|
+
expect(body.data.length).toBeGreaterThan(0)
|
|
248
|
+
// Every classified item belongs to the requested transaction.
|
|
249
|
+
expect(body.data.every((item) => item.transactionHash === transactionHash)).toBe(true)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('attaches the full event set when logs=true', async () => {
|
|
253
|
+
const transactionHash = await firstTransactionHash(accounts.creator)
|
|
254
|
+
if (!transactionHash) return
|
|
255
|
+
|
|
256
|
+
const body = await TestApp.json(
|
|
257
|
+
await transactionActivitiesRequest(transactionHash, { logs: 'true' }),
|
|
258
|
+
Activities.schema.getTransactionActivities.Response,
|
|
259
|
+
)
|
|
260
|
+
expect(body.data.some((item) => Array.isArray(item.events) && item.events.length > 0)).toBe(
|
|
261
|
+
true,
|
|
262
|
+
)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test.skipIf(runtime.mode !== 'localnet')(
|
|
266
|
+
'classifies a seeded cross-token transfer with both source and destination tokens',
|
|
267
|
+
async () => {
|
|
268
|
+
const crossToken = runtime.fixtures?.crossTokenTransfer
|
|
269
|
+
expect(crossToken).toBeDefined()
|
|
270
|
+
if (!crossToken) return
|
|
271
|
+
|
|
272
|
+
const body = await TestApp.json(
|
|
273
|
+
await transactionActivitiesRequest(crossToken.hash),
|
|
274
|
+
Activities.schema.getTransactionActivities.Response,
|
|
275
|
+
)
|
|
276
|
+
// The swap and its onward delivery leg fold into a single cross-token
|
|
277
|
+
// `transfer`; there is no separate `swap` item.
|
|
278
|
+
expect(body.data.some((item) => item.type === 'swap')).toBe(false)
|
|
279
|
+
const transfer = body.data.find((item) => item.type === 'transfer')
|
|
280
|
+
expect(transfer?.type).toBe('transfer')
|
|
281
|
+
if (transfer?.type !== 'transfer') return
|
|
282
|
+
expect(transfer.data.direction).toBe('out')
|
|
283
|
+
expect(transfer.data.recipient).toBe(crossToken.recipient)
|
|
284
|
+
expect(transfer.data.sourceToken.address).toBe(crossToken.sourceToken)
|
|
285
|
+
expect(transfer.data.destinationToken?.address).toBe(crossToken.destinationToken)
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
test('404s for an unknown transaction hash', async () => {
|
|
290
|
+
const response = await transactionActivitiesRequest(`0x${'0'.repeat(64)}`)
|
|
291
|
+
expect(response.status).toBe(404)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe('Activities.list classification', () => {
|
|
296
|
+
const viewer = '0x1111111111111111111111111111111111111111' as Address.Address
|
|
297
|
+
|
|
298
|
+
/** A memory store pre-seeded so the MPP directory fetch never hits the network. */
|
|
299
|
+
function store() {
|
|
300
|
+
const store = Store.memory()
|
|
301
|
+
void store.put('mpp-services:v1', JSON.stringify({}))
|
|
302
|
+
return store
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** A {@link Activities.Provider} that returns a fixed set of rows once. */
|
|
306
|
+
function provider(rows: Activities.Row[]): Activities.Provider {
|
|
307
|
+
return {
|
|
308
|
+
cursorForGroup: (group) => `${group.blockNum}:${group.txIdx}`,
|
|
309
|
+
fetchRows: async () => ({ rows }),
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
test('classifies an unrecognized Q1 tx as `unknown` with decoded events', async () => {
|
|
314
|
+
// A gas-fee Transfer to the fee manager decodes as a known `Transfer`
|
|
315
|
+
// event, but the classifier excludes fee-manager transfers, so the tx has
|
|
316
|
+
// no classifiable activity and falls through to `unknown`.
|
|
317
|
+
const token = '0x2222222222222222222222222222222222222222' as Address.Address
|
|
318
|
+
const feeManager = '0xfeec000000000000000000000000000000000000'
|
|
319
|
+
const transferSelector = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
|
|
320
|
+
const pad = (a: string) => `0x${a.slice(2).padStart(64, '0')}`
|
|
321
|
+
const { items } = await Activities.list(
|
|
322
|
+
provider([
|
|
323
|
+
{
|
|
324
|
+
blockNum: 100,
|
|
325
|
+
blockTimestamp: 1_700_000_000,
|
|
326
|
+
data: `0x${(5).toString(16).padStart(64, '0')}`,
|
|
327
|
+
feePayer: null,
|
|
328
|
+
logAddress: token,
|
|
329
|
+
logIdx: 0,
|
|
330
|
+
selector: transferSelector,
|
|
331
|
+
source: 'q1',
|
|
332
|
+
topic1: pad(viewer),
|
|
333
|
+
topic2: pad(feeManager),
|
|
334
|
+
topic3: null,
|
|
335
|
+
txHash: `0x${'a'.repeat(64)}` as Hex.Hex,
|
|
336
|
+
txIdx: 0,
|
|
337
|
+
txSender: viewer,
|
|
338
|
+
},
|
|
339
|
+
]),
|
|
340
|
+
{ address: viewer, store: store() },
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
expect(items).toHaveLength(1)
|
|
344
|
+
const item = items[0]!
|
|
345
|
+
expect(item.type).toBe('unknown')
|
|
346
|
+
if (item.type !== 'unknown') return
|
|
347
|
+
expect(item.data.signer).toBe('self')
|
|
348
|
+
expect(item.transactionHash).toBe(`0x${'a'.repeat(64)}`)
|
|
349
|
+
expect(item.events).toHaveLength(1)
|
|
350
|
+
const event = item.events![0]!
|
|
351
|
+
expect(event.eventName).toBe('Transfer')
|
|
352
|
+
expect(event.args!['amount']).toBe('5')
|
|
353
|
+
// Addresses are normalized to lowercase (viem decodes them checksummed).
|
|
354
|
+
expect(event.args!['from']).toBe(viewer)
|
|
355
|
+
expect(event.args!['to']).toBe(feeManager)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
test('captures undecoded logs on an `unknown` tx so no log is dropped', async () => {
|
|
359
|
+
// A log whose selector is not in any known Tempo ABI cannot decode, so it
|
|
360
|
+
// would be invisible without the raw `logs` capture.
|
|
361
|
+
const contract = '0x3333333333333333333333333333333333333333' as Address.Address
|
|
362
|
+
const unknownSelector = `0x${'b'.repeat(64)}`
|
|
363
|
+
const pad = (a: string) => `0x${a.slice(2).padStart(64, '0')}`
|
|
364
|
+
const { items } = await Activities.list(
|
|
365
|
+
provider([
|
|
366
|
+
{
|
|
367
|
+
blockNum: 100,
|
|
368
|
+
blockTimestamp: 1_700_000_000,
|
|
369
|
+
data: '0x1234',
|
|
370
|
+
feePayer: null,
|
|
371
|
+
logAddress: contract,
|
|
372
|
+
logIdx: 2,
|
|
373
|
+
selector: unknownSelector,
|
|
374
|
+
source: 'q1',
|
|
375
|
+
topic1: pad(viewer),
|
|
376
|
+
topic2: null,
|
|
377
|
+
topic3: null,
|
|
378
|
+
txHash: `0x${'c'.repeat(64)}` as Hex.Hex,
|
|
379
|
+
txIdx: 0,
|
|
380
|
+
txSender: viewer,
|
|
381
|
+
},
|
|
382
|
+
]),
|
|
383
|
+
{ address: viewer, store: store() },
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
expect(items).toHaveLength(1)
|
|
387
|
+
const item = items[0]!
|
|
388
|
+
expect(item.type).toBe('unknown')
|
|
389
|
+
if (item.type !== 'unknown') return
|
|
390
|
+
// The undecoded log is surfaced as a raw event entry (no `eventName`/`args`).
|
|
391
|
+
expect(item.events).toHaveLength(1)
|
|
392
|
+
const log = item.events![0]!
|
|
393
|
+
expect(log.eventName).toBeUndefined()
|
|
394
|
+
expect(log.args).toBeUndefined()
|
|
395
|
+
expect(log.address).toBe(contract)
|
|
396
|
+
expect(log.logIndex).toBe(2)
|
|
397
|
+
expect(log.data).toBe('0x1234')
|
|
398
|
+
expect(log.topics).toEqual([unknownSelector, pad(viewer)])
|
|
399
|
+
})
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
describe('Activities.list precompile classification', () => {
|
|
403
|
+
const viewer = '0x1111111111111111111111111111111111111111' as Address.Address
|
|
404
|
+
const tokenA = '0x2222222222222222222222222222222222222222' as Address.Address
|
|
405
|
+
const stablecoinDex = '0xdec0000000000000000000000000000000000000' as Address.Address
|
|
406
|
+
const feeManager = '0xfeec000000000000000000000000000000000000' as Address.Address
|
|
407
|
+
const accountKeychain = '0xaaaaaaaa00000000000000000000000000000000' as Address.Address
|
|
408
|
+
const channelReserve = '0x4d50500000000000000000000000000000000000' as Address.Address
|
|
409
|
+
const tip20Factory = '0x20fc000000000000000000000000000000000000' as Address.Address
|
|
410
|
+
const txHash = `0x${'b'.repeat(64)}` as Hex.Hex
|
|
411
|
+
|
|
412
|
+
function store() {
|
|
413
|
+
const store = Store.memory()
|
|
414
|
+
void store.put('mpp-services:v1', JSON.stringify({}))
|
|
415
|
+
return store
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function provider(rows: Activities.Row[]): Activities.Provider {
|
|
419
|
+
return {
|
|
420
|
+
cursorForGroup: (group) => `${group.blockNum}:${group.txIdx}`,
|
|
421
|
+
fetchRows: async () => ({ rows }),
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Builds a Q1 log row for `eventName` by ABI-encoding its indexed topics and data. */
|
|
426
|
+
function logRow(options: {
|
|
427
|
+
address: Address.Address
|
|
428
|
+
args: Record<string, unknown>
|
|
429
|
+
eventName: string
|
|
430
|
+
logIdx: number
|
|
431
|
+
}): Activities.Row {
|
|
432
|
+
type Param = { indexed?: boolean; name?: string; type: string }
|
|
433
|
+
const event = Abis.abis.find((a) => a.type === 'event' && a.name === options.eventName) as {
|
|
434
|
+
inputs: readonly Param[]
|
|
435
|
+
}
|
|
436
|
+
const topics = encodeEventTopics({
|
|
437
|
+
abi: Abis.abis,
|
|
438
|
+
eventName: options.eventName as never,
|
|
439
|
+
args: options.args as never,
|
|
440
|
+
}) as (string | null)[]
|
|
441
|
+
const dataInputs = event.inputs.filter((i) => !i.indexed)
|
|
442
|
+
const data = encodeAbiParameters(
|
|
443
|
+
dataInputs,
|
|
444
|
+
dataInputs.map((i) => options.args[i.name!]),
|
|
445
|
+
)
|
|
446
|
+
return {
|
|
447
|
+
blockNum: 100,
|
|
448
|
+
blockTimestamp: 1_700_000_000,
|
|
449
|
+
data,
|
|
450
|
+
feePayer: null,
|
|
451
|
+
logAddress: options.address,
|
|
452
|
+
logIdx: options.logIdx,
|
|
453
|
+
selector: topics[0] ?? null,
|
|
454
|
+
source: 'q1',
|
|
455
|
+
topic1: topics[1] ?? null,
|
|
456
|
+
topic2: topics[2] ?? null,
|
|
457
|
+
topic3: topics[3] ?? null,
|
|
458
|
+
txHash,
|
|
459
|
+
txIdx: 0,
|
|
460
|
+
txSender: viewer,
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** A Q1 TIP-20 `Transfer` log row, used to back channel token recovery. */
|
|
465
|
+
function transferRow(options: {
|
|
466
|
+
from: Address.Address
|
|
467
|
+
logIdx: number
|
|
468
|
+
to: Address.Address
|
|
469
|
+
token: Address.Address
|
|
470
|
+
value: bigint
|
|
471
|
+
}): Activities.Row {
|
|
472
|
+
return logRow({
|
|
473
|
+
address: options.token,
|
|
474
|
+
args: { amount: options.value, from: options.from, to: options.to },
|
|
475
|
+
eventName: 'Transfer',
|
|
476
|
+
logIdx: options.logIdx,
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function classify(rows: Activities.Row[]) {
|
|
481
|
+
const { items } = await Activities.list(provider(rows), { address: viewer, store: store() })
|
|
482
|
+
return items
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
test('classifies ChannelOpened as `channel-opened` with the deposit token', async () => {
|
|
486
|
+
const items = await classify([
|
|
487
|
+
logRow({
|
|
488
|
+
address: channelReserve,
|
|
489
|
+
args: {
|
|
490
|
+
authorizedSigner: viewer,
|
|
491
|
+
channelId: `0x${'c'.repeat(64)}`,
|
|
492
|
+
deposit: 1000n,
|
|
493
|
+
expiringNonceHash: `0x${'0'.repeat(64)}`,
|
|
494
|
+
operator: viewer,
|
|
495
|
+
payee: tokenA,
|
|
496
|
+
payer: viewer,
|
|
497
|
+
salt: `0x${'0'.repeat(64)}`,
|
|
498
|
+
token: tokenA,
|
|
499
|
+
},
|
|
500
|
+
eventName: 'ChannelOpened',
|
|
501
|
+
logIdx: 0,
|
|
502
|
+
}),
|
|
503
|
+
])
|
|
504
|
+
expect(items).toHaveLength(1)
|
|
505
|
+
expect(items[0]!.type).toBe('channel-opened')
|
|
506
|
+
if (items[0]!.type !== 'channel-opened') return
|
|
507
|
+
expect(items[0]!.data.sourceToken.amount).toBe('1000')
|
|
508
|
+
expect(items[0]!.data.payer).toBe(viewer)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
test('classifies TopUp as `channel-funded`, recovering token from the paired transfer', async () => {
|
|
512
|
+
const items = await classify([
|
|
513
|
+
transferRow({ from: viewer, logIdx: 0, to: channelReserve, token: tokenA, value: 500n }),
|
|
514
|
+
logRow({
|
|
515
|
+
address: channelReserve,
|
|
516
|
+
args: {
|
|
517
|
+
additionalDeposit: 500n,
|
|
518
|
+
channelId: `0x${'c'.repeat(64)}`,
|
|
519
|
+
newDeposit: 1500n,
|
|
520
|
+
payee: tokenA,
|
|
521
|
+
payer: viewer,
|
|
522
|
+
},
|
|
523
|
+
eventName: 'TopUp',
|
|
524
|
+
logIdx: 1,
|
|
525
|
+
}),
|
|
526
|
+
])
|
|
527
|
+
expect(items).toHaveLength(1)
|
|
528
|
+
expect(items[0]!.type).toBe('channel-funded')
|
|
529
|
+
if (items[0]!.type !== 'channel-funded') return
|
|
530
|
+
expect(items[0]!.data.sourceToken.address).toBe(tokenA)
|
|
531
|
+
expect(items[0]!.data.sourceToken.amount).toBe('500')
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
test('classifies Settled as `channel-settled`', async () => {
|
|
535
|
+
const payee = '0x3333333333333333333333333333333333333333' as Address.Address
|
|
536
|
+
const items = await classify([
|
|
537
|
+
transferRow({ from: channelReserve, logIdx: 0, to: payee, token: tokenA, value: 250n }),
|
|
538
|
+
logRow({
|
|
539
|
+
address: channelReserve,
|
|
540
|
+
args: {
|
|
541
|
+
channelId: `0x${'c'.repeat(64)}`,
|
|
542
|
+
cumulativeAmount: 250n,
|
|
543
|
+
deltaPaid: 250n,
|
|
544
|
+
newSettled: 250n,
|
|
545
|
+
payee,
|
|
546
|
+
payer: viewer,
|
|
547
|
+
},
|
|
548
|
+
eventName: 'Settled',
|
|
549
|
+
logIdx: 1,
|
|
550
|
+
}),
|
|
551
|
+
])
|
|
552
|
+
expect(items[0]!.type).toBe('channel-settled')
|
|
553
|
+
if (items[0]!.type !== 'channel-settled') return
|
|
554
|
+
expect(items[0]!.data.sourceToken.amount).toBe('250')
|
|
555
|
+
expect(items[0]!.data.payee).toBe(payee)
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
test('classifies ChannelClosed as `channel-closed` with payout and refund', async () => {
|
|
559
|
+
const payee = '0x3333333333333333333333333333333333333333' as Address.Address
|
|
560
|
+
const items = await classify([
|
|
561
|
+
transferRow({ from: channelReserve, logIdx: 0, to: payee, token: tokenA, value: 600n }),
|
|
562
|
+
transferRow({ from: channelReserve, logIdx: 1, to: viewer, token: tokenA, value: 400n }),
|
|
563
|
+
logRow({
|
|
564
|
+
address: channelReserve,
|
|
565
|
+
args: {
|
|
566
|
+
channelId: `0x${'c'.repeat(64)}`,
|
|
567
|
+
payee,
|
|
568
|
+
payer: viewer,
|
|
569
|
+
refundedToPayer: 400n,
|
|
570
|
+
settledToPayee: 600n,
|
|
571
|
+
},
|
|
572
|
+
eventName: 'ChannelClosed',
|
|
573
|
+
logIdx: 2,
|
|
574
|
+
}),
|
|
575
|
+
])
|
|
576
|
+
expect(items[0]!.type).toBe('channel-closed')
|
|
577
|
+
if (items[0]!.type !== 'channel-closed') return
|
|
578
|
+
expect(items[0]!.data.sourceToken?.amount).toBe('600')
|
|
579
|
+
expect(items[0]!.data.refund?.amount).toBe('400')
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
test('classifies CloseRequestCancelled as `channel-close-cancelled`', async () => {
|
|
583
|
+
const payee = '0x3333333333333333333333333333333333333333' as Address.Address
|
|
584
|
+
const items = await classify([
|
|
585
|
+
logRow({
|
|
586
|
+
address: channelReserve,
|
|
587
|
+
args: { channelId: `0x${'c'.repeat(64)}`, payee, payer: viewer },
|
|
588
|
+
eventName: 'CloseRequestCancelled',
|
|
589
|
+
logIdx: 0,
|
|
590
|
+
}),
|
|
591
|
+
])
|
|
592
|
+
expect(items[0]!.type).toBe('channel-close-cancelled')
|
|
593
|
+
if (items[0]!.type !== 'channel-close-cancelled') return
|
|
594
|
+
expect(items[0]!.data.channelId).toBe(`0x${'c'.repeat(64)}`)
|
|
595
|
+
expect(items[0]!.data.payee).toBe(payee)
|
|
596
|
+
expect(items[0]!.data.payer).toBe(viewer)
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
test('classifies OrderPlaced as `order-placed`', async () => {
|
|
600
|
+
const items = await classify([
|
|
601
|
+
transferRow({ from: viewer, logIdx: 0, to: stablecoinDex, token: tokenA, value: 1000n }),
|
|
602
|
+
logRow({
|
|
603
|
+
address: stablecoinDex,
|
|
604
|
+
args: {
|
|
605
|
+
amount: 1000n,
|
|
606
|
+
flipTick: 0,
|
|
607
|
+
isBid: true,
|
|
608
|
+
isFlipOrder: false,
|
|
609
|
+
maker: viewer,
|
|
610
|
+
orderId: 42n,
|
|
611
|
+
tick: 5,
|
|
612
|
+
token: tokenA,
|
|
613
|
+
},
|
|
614
|
+
eventName: 'OrderPlaced',
|
|
615
|
+
logIdx: 1,
|
|
616
|
+
}),
|
|
617
|
+
])
|
|
618
|
+
expect(items[0]!.type).toBe('order-placed')
|
|
619
|
+
if (items[0]!.type !== 'order-placed') return
|
|
620
|
+
expect(items[0]!.data.orderId).toBe('42')
|
|
621
|
+
expect(items[0]!.data.side).toBe('bid')
|
|
622
|
+
expect(items[0]!.data.tick).toBe(5)
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
test('folds a swap plus onward delivery into one cross-token `transfer`', async () => {
|
|
626
|
+
const tokenB = '0x5555555555555555555555555555555555555555' as Address.Address
|
|
627
|
+
const recipient = '0x7777777777777777777777777777777777777777' as Address.Address
|
|
628
|
+
const items = await classify([
|
|
629
|
+
// Source token spent into the DEX.
|
|
630
|
+
transferRow({ from: viewer, logIdx: 0, to: stablecoinDex, token: tokenA, value: 1_000n }),
|
|
631
|
+
// Destination token received back from the DEX.
|
|
632
|
+
transferRow({ from: stablecoinDex, logIdx: 1, to: viewer, token: tokenB, value: 990n }),
|
|
633
|
+
// Destination token forwarded to a third-party recipient in the same tx.
|
|
634
|
+
transferRow({ from: viewer, logIdx: 2, to: recipient, token: tokenB, value: 990n }),
|
|
635
|
+
])
|
|
636
|
+
// The delivery leg folds into the swap, so there is no separate `swap`.
|
|
637
|
+
expect(items.some((item) => item.type === 'swap')).toBe(false)
|
|
638
|
+
const transfer = items.find((item) => item.type === 'transfer')
|
|
639
|
+
expect(transfer?.type).toBe('transfer')
|
|
640
|
+
if (transfer?.type !== 'transfer') return
|
|
641
|
+
expect(transfer.data.direction).toBe('out')
|
|
642
|
+
expect(transfer.data.sender).toBe(viewer)
|
|
643
|
+
expect(transfer.data.recipient).toBe(recipient)
|
|
644
|
+
expect(transfer.data.sourceToken).toEqual({ address: tokenA, amount: '1000' })
|
|
645
|
+
expect(transfer.data.destinationToken).toEqual({ address: tokenB, amount: '990' })
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
test('keeps a swap whose output stays with the swapper as a `swap`', async () => {
|
|
649
|
+
const tokenB = '0x5555555555555555555555555555555555555555' as Address.Address
|
|
650
|
+
const items = await classify([
|
|
651
|
+
transferRow({ from: viewer, logIdx: 0, to: stablecoinDex, token: tokenA, value: 1_000n }),
|
|
652
|
+
transferRow({ from: stablecoinDex, logIdx: 1, to: viewer, token: tokenB, value: 990n }),
|
|
653
|
+
])
|
|
654
|
+
const swap = items.find((item) => item.type === 'swap')
|
|
655
|
+
expect(swap?.type).toBe('swap')
|
|
656
|
+
if (swap?.type !== 'swap') return
|
|
657
|
+
expect(swap.data.sourceToken).toEqual({ address: tokenA, amount: '1000' })
|
|
658
|
+
expect(swap.data.destinationToken).toEqual({ address: tokenB, amount: '990' })
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
test('classifies OrderCancelled as `order-cancelled`', async () => {
|
|
662
|
+
const items = await classify([
|
|
663
|
+
logRow({
|
|
664
|
+
address: stablecoinDex,
|
|
665
|
+
args: { orderId: 42n },
|
|
666
|
+
eventName: 'OrderCancelled',
|
|
667
|
+
logIdx: 0,
|
|
668
|
+
}),
|
|
669
|
+
])
|
|
670
|
+
expect(items[0]!.type).toBe('order-cancelled')
|
|
671
|
+
if (items[0]!.type !== 'order-cancelled') return
|
|
672
|
+
expect(items[0]!.data.orderId).toBe('42')
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
test('classifies FeesDistributed as `fees-distributed`', async () => {
|
|
676
|
+
const validator = '0xcccccccc00000000000000000000000000000000' as Address.Address
|
|
677
|
+
const items = await classify([
|
|
678
|
+
transferRow({ from: feeManager, logIdx: 0, to: validator, token: tokenA, value: 77n }),
|
|
679
|
+
logRow({
|
|
680
|
+
address: feeManager,
|
|
681
|
+
args: { amount: 77n, token: tokenA, validator },
|
|
682
|
+
eventName: 'FeesDistributed',
|
|
683
|
+
logIdx: 1,
|
|
684
|
+
}),
|
|
685
|
+
])
|
|
686
|
+
expect(items[0]!.type).toBe('fees-distributed')
|
|
687
|
+
if (items[0]!.type !== 'fees-distributed') return
|
|
688
|
+
expect(items[0]!.data.sourceToken.amount).toBe('77')
|
|
689
|
+
expect(items[0]!.data.validator).toBe(validator)
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
test('classifies RebalanceSwap as `fee-rebalance-swap`', async () => {
|
|
693
|
+
const tokenB = '0x4444444444444444444444444444444444444444' as Address.Address
|
|
694
|
+
const items = await classify([
|
|
695
|
+
logRow({
|
|
696
|
+
address: feeManager,
|
|
697
|
+
args: {
|
|
698
|
+
amountIn: 10n,
|
|
699
|
+
amountOut: 9n,
|
|
700
|
+
swapper: viewer,
|
|
701
|
+
userToken: tokenB,
|
|
702
|
+
validatorToken: tokenA,
|
|
703
|
+
},
|
|
704
|
+
eventName: 'RebalanceSwap',
|
|
705
|
+
logIdx: 0,
|
|
706
|
+
}),
|
|
707
|
+
])
|
|
708
|
+
expect(items[0]!.type).toBe('fee-rebalance-swap')
|
|
709
|
+
if (items[0]!.type !== 'fee-rebalance-swap') return
|
|
710
|
+
expect(items[0]!.data.sourceToken.address).toBe(tokenA)
|
|
711
|
+
expect(items[0]!.data.destinationToken.address).toBe(tokenB)
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
test('classifies RewardDistributed as `reward-distributed` on the token contract', async () => {
|
|
715
|
+
const items = await classify([
|
|
716
|
+
transferRow({ from: viewer, logIdx: 0, to: tokenA, token: tokenA, value: 30n }),
|
|
717
|
+
logRow({
|
|
718
|
+
address: tokenA,
|
|
719
|
+
args: { amount: 30n, funder: viewer },
|
|
720
|
+
eventName: 'RewardDistributed',
|
|
721
|
+
logIdx: 1,
|
|
722
|
+
}),
|
|
723
|
+
])
|
|
724
|
+
expect(items[0]!.type).toBe('reward-distributed')
|
|
725
|
+
if (items[0]!.type !== 'reward-distributed') return
|
|
726
|
+
expect(items[0]!.data.sourceToken.address).toBe(tokenA)
|
|
727
|
+
expect(items[0]!.data.sourceToken.amount).toBe('30')
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
test('classifies RewardRecipientSet as `reward-recipient-set`', async () => {
|
|
731
|
+
const recipient = '0x5555555555555555555555555555555555555555' as Address.Address
|
|
732
|
+
const items = await classify([
|
|
733
|
+
logRow({
|
|
734
|
+
address: tokenA,
|
|
735
|
+
args: { holder: viewer, recipient },
|
|
736
|
+
eventName: 'RewardRecipientSet',
|
|
737
|
+
logIdx: 0,
|
|
738
|
+
}),
|
|
739
|
+
])
|
|
740
|
+
expect(items[0]!.type).toBe('reward-recipient-set')
|
|
741
|
+
if (items[0]!.type !== 'reward-recipient-set') return
|
|
742
|
+
expect(items[0]!.data.recipient).toBe(recipient)
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
test('classifies SpendingLimitUpdated as `spending-limit-updated`', async () => {
|
|
746
|
+
const publicKey = '0x6666666666666666666666666666666666666666' as Address.Address
|
|
747
|
+
const items = await classify([
|
|
748
|
+
logRow({
|
|
749
|
+
address: accountKeychain,
|
|
750
|
+
args: { account: viewer, newLimit: 999n, publicKey, token: tokenA },
|
|
751
|
+
eventName: 'SpendingLimitUpdated',
|
|
752
|
+
logIdx: 0,
|
|
753
|
+
}),
|
|
754
|
+
])
|
|
755
|
+
expect(items[0]!.type).toBe('spending-limit-updated')
|
|
756
|
+
if (items[0]!.type !== 'spending-limit-updated') return
|
|
757
|
+
expect(items[0]!.data.publicKey).toBe(publicKey)
|
|
758
|
+
expect(items[0]!.data.sourceToken.amount).toBe('999')
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
test('classifies BurnBlocked as `burn-blocked` before plain burn', async () => {
|
|
762
|
+
const items = await classify([
|
|
763
|
+
transferRow({
|
|
764
|
+
from: viewer,
|
|
765
|
+
logIdx: 0,
|
|
766
|
+
to: '0x0000000000000000000000000000000000000000' as Address.Address,
|
|
767
|
+
token: tokenA,
|
|
768
|
+
value: 15n,
|
|
769
|
+
}),
|
|
770
|
+
logRow({
|
|
771
|
+
address: tokenA,
|
|
772
|
+
args: { amount: 15n, from: viewer },
|
|
773
|
+
eventName: 'BurnBlocked',
|
|
774
|
+
logIdx: 1,
|
|
775
|
+
}),
|
|
776
|
+
])
|
|
777
|
+
expect(items[0]!.type).toBe('burn-blocked')
|
|
778
|
+
if (items[0]!.type !== 'burn-blocked') return
|
|
779
|
+
expect(items[0]!.data.sourceToken.amount).toBe('15')
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
test('classifies OrderFilled as `order-filled`', async () => {
|
|
783
|
+
const maker = '0x4444444444444444444444444444444444444444' as Address.Address
|
|
784
|
+
const items = await classify([
|
|
785
|
+
logRow({
|
|
786
|
+
address: stablecoinDex,
|
|
787
|
+
args: { amountFilled: 250n, maker, orderId: 7n, partialFill: true, taker: viewer },
|
|
788
|
+
eventName: 'OrderFilled',
|
|
789
|
+
logIdx: 0,
|
|
790
|
+
}),
|
|
791
|
+
])
|
|
792
|
+
expect(items[0]!.type).toBe('order-filled')
|
|
793
|
+
if (items[0]!.type !== 'order-filled') return
|
|
794
|
+
expect(items[0]!.data.orderId).toBe('7')
|
|
795
|
+
expect(items[0]!.data.amountFilled).toBe('250')
|
|
796
|
+
expect(items[0]!.data.partialFill).toBe(true)
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
test('classifies PairCreated as `pair-created`', async () => {
|
|
800
|
+
const quote = '0x5555555555555555555555555555555555555555' as Address.Address
|
|
801
|
+
const items = await classify([
|
|
802
|
+
logRow({
|
|
803
|
+
address: stablecoinDex,
|
|
804
|
+
args: { base: tokenA, key: `0x${'a'.repeat(64)}`, quote },
|
|
805
|
+
eventName: 'PairCreated',
|
|
806
|
+
logIdx: 0,
|
|
807
|
+
}),
|
|
808
|
+
])
|
|
809
|
+
expect(items[0]!.type).toBe('pair-created')
|
|
810
|
+
if (items[0]!.type !== 'pair-created') return
|
|
811
|
+
expect(items[0]!.data.base).toBe(tokenA)
|
|
812
|
+
expect(items[0]!.data.quote).toBe(quote)
|
|
813
|
+
})
|
|
814
|
+
|
|
815
|
+
test('classifies PauseStateUpdate as `token-pause-set`', async () => {
|
|
816
|
+
const items = await classify([
|
|
817
|
+
logRow({
|
|
818
|
+
address: tokenA,
|
|
819
|
+
args: { isPaused: true, updater: viewer },
|
|
820
|
+
eventName: 'PauseStateUpdate',
|
|
821
|
+
logIdx: 0,
|
|
822
|
+
}),
|
|
823
|
+
])
|
|
824
|
+
expect(items[0]!.type).toBe('token-pause-set')
|
|
825
|
+
if (items[0]!.type !== 'token-pause-set') return
|
|
826
|
+
expect(items[0]!.data.paused).toBe(true)
|
|
827
|
+
expect(items[0]!.data.updater).toBe(viewer)
|
|
828
|
+
})
|
|
829
|
+
|
|
830
|
+
test('classifies LogoURIUpdated as `token-logo-set`', async () => {
|
|
831
|
+
const items = await classify([
|
|
832
|
+
logRow({
|
|
833
|
+
address: tokenA,
|
|
834
|
+
args: { newLogoURI: 'https://example.com/logo.svg', updater: viewer },
|
|
835
|
+
eventName: 'LogoURIUpdated',
|
|
836
|
+
logIdx: 0,
|
|
837
|
+
}),
|
|
838
|
+
])
|
|
839
|
+
expect(items[0]!.type).toBe('token-logo-set')
|
|
840
|
+
if (items[0]!.type !== 'token-logo-set') return
|
|
841
|
+
expect(items[0]!.data.logoUri).toBe('https://example.com/logo.svg')
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
test('classifies RoleMembershipUpdated as `role-membership-set`', async () => {
|
|
845
|
+
const account = '0x6666666666666666666666666666666666666666' as Address.Address
|
|
846
|
+
const items = await classify([
|
|
847
|
+
logRow({
|
|
848
|
+
address: tokenA,
|
|
849
|
+
args: { account, hasRole: true, role: `0x${'1'.repeat(64)}`, sender: viewer },
|
|
850
|
+
eventName: 'RoleMembershipUpdated',
|
|
851
|
+
logIdx: 0,
|
|
852
|
+
}),
|
|
853
|
+
])
|
|
854
|
+
expect(items[0]!.type).toBe('role-membership-set')
|
|
855
|
+
if (items[0]!.type !== 'role-membership-set') return
|
|
856
|
+
expect(items[0]!.data.account).toBe(account)
|
|
857
|
+
expect(items[0]!.data.granted).toBe(true)
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
test('classifies TokenCreated before a co-located RoleMembershipUpdated', async () => {
|
|
861
|
+
const items = await classify([
|
|
862
|
+
logRow({
|
|
863
|
+
address: tokenA,
|
|
864
|
+
args: { account: viewer, hasRole: true, role: `0x${'1'.repeat(64)}`, sender: viewer },
|
|
865
|
+
eventName: 'RoleMembershipUpdated',
|
|
866
|
+
logIdx: 0,
|
|
867
|
+
}),
|
|
868
|
+
logRow({
|
|
869
|
+
address: tip20Factory,
|
|
870
|
+
args: {
|
|
871
|
+
admin: viewer,
|
|
872
|
+
currency: 'USD',
|
|
873
|
+
name: 'Test',
|
|
874
|
+
quoteToken: tokenA,
|
|
875
|
+
salt: `0x${'0'.repeat(64)}`,
|
|
876
|
+
symbol: 'TST',
|
|
877
|
+
token: tokenA,
|
|
878
|
+
},
|
|
879
|
+
eventName: 'TokenCreated',
|
|
880
|
+
logIdx: 1,
|
|
881
|
+
}),
|
|
882
|
+
])
|
|
883
|
+
expect(items[0]!.type).toBe('token-created')
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
test('classifies PolicyCreated before the WhitelistUpdated it bundles', async () => {
|
|
887
|
+
const account = '0x7777777777777777777777777777777777777777' as Address.Address
|
|
888
|
+
const items = await classify([
|
|
889
|
+
logRow({
|
|
890
|
+
address: tokenA,
|
|
891
|
+
args: { account, allowed: true, policyId: 9n, updater: viewer },
|
|
892
|
+
eventName: 'WhitelistUpdated',
|
|
893
|
+
logIdx: 0,
|
|
894
|
+
}),
|
|
895
|
+
logRow({
|
|
896
|
+
address: tokenA,
|
|
897
|
+
args: { policyId: 9n, policyType: 1, updater: viewer },
|
|
898
|
+
eventName: 'PolicyCreated',
|
|
899
|
+
logIdx: 1,
|
|
900
|
+
}),
|
|
901
|
+
])
|
|
902
|
+
expect(items[0]!.type).toBe('policy-created')
|
|
903
|
+
if (items[0]!.type !== 'policy-created') return
|
|
904
|
+
expect(items[0]!.data.policyId).toBe('9')
|
|
905
|
+
expect(items[0]!.data.policyType).toBe(1)
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
test('classifies WhitelistUpdated as `whitelist-updated`', async () => {
|
|
909
|
+
const account = '0x7777777777777777777777777777777777777777' as Address.Address
|
|
910
|
+
const items = await classify([
|
|
911
|
+
logRow({
|
|
912
|
+
address: tokenA,
|
|
913
|
+
args: { account, allowed: false, policyId: 9n, updater: viewer },
|
|
914
|
+
eventName: 'WhitelistUpdated',
|
|
915
|
+
logIdx: 0,
|
|
916
|
+
}),
|
|
917
|
+
])
|
|
918
|
+
expect(items[0]!.type).toBe('whitelist-updated')
|
|
919
|
+
if (items[0]!.type !== 'whitelist-updated') return
|
|
920
|
+
expect(items[0]!.data.allowed).toBe(false)
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
test('classifies MasterRegistered as `master-registered`', async () => {
|
|
924
|
+
const master = '0x8888888888888888888888888888888888888888' as Address.Address
|
|
925
|
+
const items = await classify([
|
|
926
|
+
logRow({
|
|
927
|
+
address: accountKeychain,
|
|
928
|
+
args: { masterAddress: master, masterId: '0xdeadbeef' },
|
|
929
|
+
eventName: 'MasterRegistered',
|
|
930
|
+
logIdx: 0,
|
|
931
|
+
}),
|
|
932
|
+
])
|
|
933
|
+
expect(items[0]!.type).toBe('master-registered')
|
|
934
|
+
if (items[0]!.type !== 'master-registered') return
|
|
935
|
+
expect(items[0]!.data.masterId).toBe('0xdeadbeef')
|
|
936
|
+
expect(items[0]!.data.masterAddress).toBe(master)
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
test('classifies NonceIncremented as `nonce-incremented`', async () => {
|
|
940
|
+
const items = await classify([
|
|
941
|
+
logRow({
|
|
942
|
+
address: accountKeychain,
|
|
943
|
+
args: { account: viewer, newNonce: 5n, nonceKey: 0n },
|
|
944
|
+
eventName: 'NonceIncremented',
|
|
945
|
+
logIdx: 0,
|
|
946
|
+
}),
|
|
947
|
+
])
|
|
948
|
+
expect(items[0]!.type).toBe('nonce-incremented')
|
|
949
|
+
if (items[0]!.type !== 'nonce-incremented') return
|
|
950
|
+
expect(items[0]!.data.nonce).toBe('5')
|
|
951
|
+
})
|
|
952
|
+
|
|
953
|
+
test('classifies ValidatorRotated before the ValidatorAdded it bundles', async () => {
|
|
954
|
+
const validator = '0x9999999999999999999999999999999999999999' as Address.Address
|
|
955
|
+
const feeRecipient = '0xabababababababababababababababababababab' as Address.Address
|
|
956
|
+
const items = await classify([
|
|
957
|
+
logRow({
|
|
958
|
+
address: accountKeychain,
|
|
959
|
+
args: {
|
|
960
|
+
egress: 'https://v.example.com:2',
|
|
961
|
+
feeRecipient,
|
|
962
|
+
index: 2n,
|
|
963
|
+
ingress: 'https://v.example.com:1',
|
|
964
|
+
publicKey: `0x${'2'.repeat(64)}`,
|
|
965
|
+
validatorAddress: validator,
|
|
966
|
+
},
|
|
967
|
+
eventName: 'ValidatorAdded',
|
|
968
|
+
logIdx: 0,
|
|
969
|
+
}),
|
|
970
|
+
logRow({
|
|
971
|
+
address: accountKeychain,
|
|
972
|
+
args: {
|
|
973
|
+
caller: viewer,
|
|
974
|
+
deactivatedIndex: 1n,
|
|
975
|
+
egress: 'https://v.example.com:2',
|
|
976
|
+
index: 2n,
|
|
977
|
+
ingress: 'https://v.example.com:1',
|
|
978
|
+
newPublicKey: `0x${'3'.repeat(64)}`,
|
|
979
|
+
oldPublicKey: `0x${'2'.repeat(64)}`,
|
|
980
|
+
validatorAddress: validator,
|
|
981
|
+
},
|
|
982
|
+
eventName: 'ValidatorRotated',
|
|
983
|
+
logIdx: 1,
|
|
984
|
+
}),
|
|
985
|
+
])
|
|
986
|
+
expect(items[0]!.type).toBe('validator-rotated')
|
|
987
|
+
if (items[0]!.type !== 'validator-rotated') return
|
|
988
|
+
expect(items[0]!.data.index).toBe('2')
|
|
989
|
+
expect(items[0]!.data.deactivatedIndex).toBe('1')
|
|
990
|
+
})
|
|
991
|
+
|
|
992
|
+
test('classifies OwnershipTransferred as `ownership-transferred`', async () => {
|
|
993
|
+
const newOwner = '0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' as Address.Address
|
|
994
|
+
const items = await classify([
|
|
995
|
+
logRow({
|
|
996
|
+
address: accountKeychain,
|
|
997
|
+
args: { newOwner, oldOwner: viewer },
|
|
998
|
+
eventName: 'OwnershipTransferred',
|
|
999
|
+
logIdx: 0,
|
|
1000
|
+
}),
|
|
1001
|
+
])
|
|
1002
|
+
expect(items[0]!.type).toBe('ownership-transferred')
|
|
1003
|
+
if (items[0]!.type !== 'ownership-transferred') return
|
|
1004
|
+
expect(items[0]!.data.newOwner).toBe(newOwner)
|
|
1005
|
+
})
|
|
1006
|
+
|
|
1007
|
+
test('classifies Initialized as `initialized`', async () => {
|
|
1008
|
+
const items = await classify([
|
|
1009
|
+
logRow({
|
|
1010
|
+
address: accountKeychain,
|
|
1011
|
+
args: { height: 42n },
|
|
1012
|
+
eventName: 'Initialized',
|
|
1013
|
+
logIdx: 0,
|
|
1014
|
+
}),
|
|
1015
|
+
])
|
|
1016
|
+
expect(items[0]!.type).toBe('initialized')
|
|
1017
|
+
if (items[0]!.type !== 'initialized') return
|
|
1018
|
+
expect(items[0]!.data.height).toBe('42')
|
|
1019
|
+
})
|
|
1020
|
+
})
|