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,91 @@
|
|
|
1
|
+
import * as WebhookDestination from '../WebhookDestination.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds a `betterstack` destination. Its `deliver` formats the envelope into a
|
|
5
|
+
* structured log event and POSTs it to the Better Stack ingest URL authenticated
|
|
6
|
+
* with the source token (`Authorization: Bearer …`; host-locked to
|
|
7
|
+
* `*.betterstackdata.com`). Deliver with `.deliver({ envelope })`.
|
|
8
|
+
*/
|
|
9
|
+
export function betterStack(
|
|
10
|
+
options: betterStack.Options,
|
|
11
|
+
): WebhookDestination.Instance<WebhookDestination.BetterStack> {
|
|
12
|
+
const { token, url } = options
|
|
13
|
+
assertBetterstackUrl(url)
|
|
14
|
+
return {
|
|
15
|
+
deliver(deliverOptions) {
|
|
16
|
+
const { envelope } = deliverOptions
|
|
17
|
+
return WebhookDestination.send({
|
|
18
|
+
// A structured log event: a human-readable `message`, a `dt` timestamp
|
|
19
|
+
// (Better Stack indexes this as the event time), and the raw event
|
|
20
|
+
// fields flattened alongside for querying.
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
chainId: envelope.chainId,
|
|
23
|
+
data: envelope.data,
|
|
24
|
+
...(envelope.context?.description === undefined
|
|
25
|
+
? {}
|
|
26
|
+
: { description: envelope.context.description }),
|
|
27
|
+
dt: envelope.createdAt,
|
|
28
|
+
event: envelope.type,
|
|
29
|
+
eventId: envelope.id,
|
|
30
|
+
// The subscription's `context.title` (what the webhook is for) becomes
|
|
31
|
+
// the log `message` when set; otherwise fall back to the event identity.
|
|
32
|
+
message:
|
|
33
|
+
envelope.context?.title ??
|
|
34
|
+
(envelope.type === 'ping'
|
|
35
|
+
? 'Tempo webhook test ping'
|
|
36
|
+
: `Tempo event: ${envelope.type}`),
|
|
37
|
+
...(envelope.context?.metadata === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { metadata: envelope.context.metadata }),
|
|
40
|
+
subscriptionId: envelope.subscriptionId,
|
|
41
|
+
}),
|
|
42
|
+
errorPrefix: 'betterstack ',
|
|
43
|
+
fetch: deliverOptions.fetch,
|
|
44
|
+
headers: { authorization: `Bearer ${token}` },
|
|
45
|
+
now: deliverOptions.now,
|
|
46
|
+
timeoutMs: deliverOptions.timeoutMs,
|
|
47
|
+
url,
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
token,
|
|
51
|
+
type: 'betterstack',
|
|
52
|
+
url,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export declare namespace betterStack {
|
|
57
|
+
/** Options for {@link betterStack}. */
|
|
58
|
+
type Options = {
|
|
59
|
+
/** Better Stack source token, sent as `Authorization: Bearer …`. */
|
|
60
|
+
token: string
|
|
61
|
+
/** Better Stack ingest host URL (`https://<host>.betterstackdata.com`). */
|
|
62
|
+
url: string
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Host suffix that Better Stack ingest URLs must use. */
|
|
67
|
+
const betterstackHostSuffix = '.betterstackdata.com'
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validates a `betterstack` destination's ingest URL: must be `https` and hosted
|
|
71
|
+
* under {@link betterstackHostSuffix} (`s95.eu-nbg-2.betterstackdata.com` etc.).
|
|
72
|
+
* As with Slack, the host lock keeps `betterstack` from becoming a generic
|
|
73
|
+
* "POST anywhere with a bearer token" primitive — the source token is attached
|
|
74
|
+
* server-side, so the request must only ever reach Better Stack.
|
|
75
|
+
*/
|
|
76
|
+
export function assertBetterstackUrl(input: string): URL {
|
|
77
|
+
let url: URL
|
|
78
|
+
try {
|
|
79
|
+
url = new URL(input)
|
|
80
|
+
} catch {
|
|
81
|
+
throw new WebhookDestination.InvalidUrlError('malformed', input)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (url.protocol !== 'https:') throw new WebhookDestination.InvalidUrlError('protocol', input)
|
|
85
|
+
if (url.username || url.password)
|
|
86
|
+
throw new WebhookDestination.InvalidUrlError('credentials', input)
|
|
87
|
+
if (!url.hostname.endsWith(betterstackHostSuffix))
|
|
88
|
+
throw new WebhookDestination.InvalidUrlError('betterstack_host', input)
|
|
89
|
+
|
|
90
|
+
return url
|
|
91
|
+
}
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { formatUnits } from 'viem'
|
|
2
|
+
|
|
3
|
+
import type * as Webhooks from '../Webhooks.js'
|
|
4
|
+
import * as Viem from '../Viem.js'
|
|
5
|
+
import * as WebhookDestination from '../WebhookDestination.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds a `slack` destination. Its `deliver` formats the envelope into a
|
|
9
|
+
* per-event Block Kit message ({@link formatBlocks}) and POSTs it to the Slack
|
|
10
|
+
* incoming-webhook URL unsigned (the URL is the shared secret; host-locked to
|
|
11
|
+
* `hooks.slack.com`). Deliver with `.deliver({ envelope })`.
|
|
12
|
+
*/
|
|
13
|
+
export function slack(url: string): WebhookDestination.Instance<WebhookDestination.Slack> {
|
|
14
|
+
assertSlackUrl(url)
|
|
15
|
+
return {
|
|
16
|
+
deliver(options) {
|
|
17
|
+
return WebhookDestination.send({
|
|
18
|
+
body: JSON.stringify(formatBlocks(options.envelope)),
|
|
19
|
+
errorPrefix: 'slack ',
|
|
20
|
+
fetch: options.fetch,
|
|
21
|
+
now: options.now,
|
|
22
|
+
timeoutMs: options.timeoutMs,
|
|
23
|
+
url,
|
|
24
|
+
})
|
|
25
|
+
},
|
|
26
|
+
type: 'slack',
|
|
27
|
+
url,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A Slack `section.fields` / `context.elements` entry. */
|
|
32
|
+
type Field = { text: string; type: 'mrkdwn' }
|
|
33
|
+
|
|
34
|
+
/** A Slack Block Kit block (loosely typed; only the shapes we emit). */
|
|
35
|
+
type Block =
|
|
36
|
+
| { elements: Field[]; type: 'context' }
|
|
37
|
+
| { fields: Field[]; type: 'section' }
|
|
38
|
+
| { text: { emoji: true; text: string; type: 'plain_text' }; type: 'header' }
|
|
39
|
+
| { text: Field; type: 'section' }
|
|
40
|
+
|
|
41
|
+
/** Per-event emoji + friendly header name; also the notification fallback lead. */
|
|
42
|
+
const friendlyName: Record<Webhooks.Envelope['type'], string> = {
|
|
43
|
+
'block:created': ':link: Block created',
|
|
44
|
+
'log:emitted': ':label: Log emitted',
|
|
45
|
+
ping: ':bell: Webhook test ping',
|
|
46
|
+
'token:transfer': ':money_with_wings: Token transfer',
|
|
47
|
+
'transaction:included': ':package: Transaction included',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Chain id → block-explorer base URL, derived from {@link Viem.chains}. Used to
|
|
52
|
+
* render addresses/hashes/tokens/blocks as explorer links; an unknown chain id
|
|
53
|
+
* (no base) degrades to plain truncated text.
|
|
54
|
+
*/
|
|
55
|
+
const explorerBase: Record<number, string> = Object.fromEntries(
|
|
56
|
+
Viem.chains.flatMap((chain) => {
|
|
57
|
+
const base = chain.blockExplorers?.default.url
|
|
58
|
+
return base ? [[chain.id, base] as const] : []
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Formats an envelope into a Slack Block Kit message (`{ blocks, text }`) using a
|
|
64
|
+
* per-event layout. Every message shares the cross-cutting rules: a header (the
|
|
65
|
+
* subscription's `context.title` when set, else the event's friendly name), an
|
|
66
|
+
* optional description section, an event-specific lead + fields grid, and a
|
|
67
|
+
* trailing context line that always preserves the raw event type/id/chain/time.
|
|
68
|
+
* Pure and synchronous — all inputs come from the envelope or static chain data.
|
|
69
|
+
*/
|
|
70
|
+
export function formatBlocks(envelope: Webhooks.Envelope): { blocks: Block[]; text: string } {
|
|
71
|
+
const base = explorerBase[envelope.chainId]
|
|
72
|
+
const data =
|
|
73
|
+
typeof envelope.data === 'object' && envelope.data !== null
|
|
74
|
+
? (envelope.data as Record<string, unknown>)
|
|
75
|
+
: {}
|
|
76
|
+
|
|
77
|
+
const layout = renderEvent(envelope.type, data, base)
|
|
78
|
+
|
|
79
|
+
const blocks: Block[] = [
|
|
80
|
+
{
|
|
81
|
+
text: {
|
|
82
|
+
emoji: true,
|
|
83
|
+
text: clamp(envelope.context?.title ?? layout.header, 150),
|
|
84
|
+
type: 'plain_text',
|
|
85
|
+
},
|
|
86
|
+
type: 'header',
|
|
87
|
+
},
|
|
88
|
+
]
|
|
89
|
+
if (envelope.context?.description !== undefined)
|
|
90
|
+
blocks.push({
|
|
91
|
+
text: { text: escape(envelope.context.description), type: 'mrkdwn' },
|
|
92
|
+
type: 'section',
|
|
93
|
+
})
|
|
94
|
+
if (layout.lead !== undefined)
|
|
95
|
+
blocks.push({ text: { text: layout.lead, type: 'mrkdwn' }, type: 'section' })
|
|
96
|
+
if (layout.fields.length > 0) blocks.push({ fields: layout.fields.slice(0, 10), type: 'section' })
|
|
97
|
+
const metadata = envelope.context?.metadata
|
|
98
|
+
if (metadata && Object.keys(metadata).length > 0)
|
|
99
|
+
blocks.push({
|
|
100
|
+
fields: Object.entries(metadata)
|
|
101
|
+
.slice(0, 10)
|
|
102
|
+
.map(([key, value]) => ({
|
|
103
|
+
text: `*${escape(key)}*\n${escape(value)}`,
|
|
104
|
+
type: 'mrkdwn' as const,
|
|
105
|
+
})),
|
|
106
|
+
type: 'section',
|
|
107
|
+
})
|
|
108
|
+
blocks.push({
|
|
109
|
+
// Keep the raw event type/id/chain/time here so event identity survives a
|
|
110
|
+
// custom `context.title` header.
|
|
111
|
+
elements: [
|
|
112
|
+
{
|
|
113
|
+
text: `\`${escape(envelope.id)}\` · \`${escape(envelope.type)}\` · chain ${envelope.chainId}${
|
|
114
|
+
date(envelope.createdAt) ? ` · ${date(envelope.createdAt)}` : ''
|
|
115
|
+
}`,
|
|
116
|
+
type: 'mrkdwn',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
type: 'context',
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// The notification/push preview: a custom title when set, else the event's
|
|
123
|
+
// one-line summary (with the chain appended). Never contains mrkdwn link /
|
|
124
|
+
// `<!date>` syntax.
|
|
125
|
+
return {
|
|
126
|
+
blocks,
|
|
127
|
+
text: envelope.context?.title ?? `${layout.text} · chain ${envelope.chainId}`,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The event-specific parts of a message, before the shared header/context wrap. */
|
|
132
|
+
type Layout = {
|
|
133
|
+
/** Field grid entries (capped to 10 by the caller). */
|
|
134
|
+
fields: Field[]
|
|
135
|
+
/** Friendly header name (emoji + label) when there's no `context.title`. */
|
|
136
|
+
header: string
|
|
137
|
+
/** Optional summary section under the header. */
|
|
138
|
+
lead?: string | undefined
|
|
139
|
+
/** One-line notification fallback (plain text; no links/`<!date>`). */
|
|
140
|
+
text: string
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Dispatches to the per-event layout builder. */
|
|
144
|
+
function renderEvent(
|
|
145
|
+
type: Webhooks.Envelope['type'],
|
|
146
|
+
data: Record<string, unknown>,
|
|
147
|
+
base: string | undefined,
|
|
148
|
+
): Layout {
|
|
149
|
+
if (type === 'token:transfer') return transferLayout(data, base)
|
|
150
|
+
if (type === 'transaction:included') return transactionLayout(data, base)
|
|
151
|
+
if (type === 'log:emitted') return logLayout(data, base)
|
|
152
|
+
if (type === 'block:created') return blockLayout(data, base)
|
|
153
|
+
return pingLayout()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function transferLayout(data: Record<string, unknown>, base: string | undefined): Layout {
|
|
157
|
+
const amount = str(data['amount'])
|
|
158
|
+
const token =
|
|
159
|
+
typeof data['token'] === 'object' && data['token'] !== null
|
|
160
|
+
? (data['token'] as { decimals?: number | undefined; symbol?: string | undefined })
|
|
161
|
+
: undefined
|
|
162
|
+
const human = humanAmount(amount, token)
|
|
163
|
+
const sender = str(data['sender'])
|
|
164
|
+
const recipient = str(data['recipient'])
|
|
165
|
+
const tokenAddress = str(data['address'])
|
|
166
|
+
|
|
167
|
+
const fields: Field[] = []
|
|
168
|
+
if (tokenAddress !== undefined)
|
|
169
|
+
field(fields, 'Token', tokenLink(base, tokenAddress, token?.symbol))
|
|
170
|
+
if (amount !== undefined) field(fields, 'Amount', escape(human ?? amount))
|
|
171
|
+
if (sender !== undefined) field(fields, 'Sender', addressLink(base, sender))
|
|
172
|
+
if (recipient !== undefined) field(fields, 'Recipient', addressLink(base, recipient))
|
|
173
|
+
field(fields, 'Block', blockLink(base, data['blockNumber']))
|
|
174
|
+
if (str(data['transactionHash']) !== undefined)
|
|
175
|
+
field(fields, 'Tx', txLink(base, str(data['transactionHash'])!))
|
|
176
|
+
field(fields, 'Timestamp', date(str(data['timestamp'])))
|
|
177
|
+
|
|
178
|
+
const lead =
|
|
179
|
+
sender !== undefined && recipient !== undefined
|
|
180
|
+
? `*${escape(human ?? amount ?? 'Transfer')}* from ${addressLink(base, sender)} → ${addressLink(base, recipient)}`
|
|
181
|
+
: undefined
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
fields,
|
|
185
|
+
header: friendlyName['token:transfer'],
|
|
186
|
+
...(lead === undefined ? {} : { lead }),
|
|
187
|
+
text: `Token transfer · ${human ?? amount ?? 'transfer'}`,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function transactionLayout(data: Record<string, unknown>, base: string | undefined): Layout {
|
|
192
|
+
const meta = typeof data['meta'] === 'object' && data['meta'] !== null ? data['meta'] : undefined
|
|
193
|
+
const receipt =
|
|
194
|
+
meta && typeof (meta as Record<string, unknown>)['receipt'] === 'object'
|
|
195
|
+
? ((meta as Record<string, unknown>)['receipt'] as Record<string, unknown>)
|
|
196
|
+
: undefined
|
|
197
|
+
const status = receipt ? str(receipt['status']) : undefined
|
|
198
|
+
const reverted = status === 'reverted'
|
|
199
|
+
|
|
200
|
+
const type = str(data['type'])
|
|
201
|
+
const sender = str(data['sender'])
|
|
202
|
+
const contractCreation = data['recipient'] === null
|
|
203
|
+
const recipient = str(data['recipient'])
|
|
204
|
+
const recipientText = contractCreation
|
|
205
|
+
? 'contract creation'
|
|
206
|
+
: recipient && addressLink(base, recipient)
|
|
207
|
+
const calls = Array.isArray(data['calls']) ? (data['calls'] as unknown[]) : undefined
|
|
208
|
+
|
|
209
|
+
const fields: Field[] = []
|
|
210
|
+
if (status !== undefined) field(fields, 'Status', escape(status))
|
|
211
|
+
if (type !== undefined) field(fields, 'Type', escape(type))
|
|
212
|
+
if (sender !== undefined) field(fields, 'Sender', addressLink(base, sender))
|
|
213
|
+
if (recipientText) field(fields, 'Recipient', recipientText)
|
|
214
|
+
if (str(data['value']) !== undefined) field(fields, 'Value', escape(str(data['value'])!))
|
|
215
|
+
if (str(data['hash']) !== undefined) field(fields, 'Tx', txLink(base, str(data['hash'])!))
|
|
216
|
+
field(
|
|
217
|
+
fields,
|
|
218
|
+
'Block',
|
|
219
|
+
data['blockNumber'] === null ? 'pending' : blockLink(base, data['blockNumber']),
|
|
220
|
+
)
|
|
221
|
+
const gas = scalar(data['gas'])
|
|
222
|
+
const gasPrice = scalar(data['gasPrice'])
|
|
223
|
+
if (gas !== undefined)
|
|
224
|
+
field(
|
|
225
|
+
fields,
|
|
226
|
+
'Gas',
|
|
227
|
+
gasPrice === undefined ? escape(gas) : `${escape(gas)} @ ${escape(gasPrice)}`,
|
|
228
|
+
)
|
|
229
|
+
if (calls && calls.length > 0) field(fields, 'Calls', String(calls.length))
|
|
230
|
+
field(fields, 'Timestamp', date(str(data['timestamp'])))
|
|
231
|
+
|
|
232
|
+
const accent = reverted ? ':x: *reverted* · ' : status === 'success' ? ':white_check_mark: ' : ''
|
|
233
|
+
const flow =
|
|
234
|
+
sender !== undefined && recipientText
|
|
235
|
+
? `${addressLink(base, sender)} → ${recipientText}`
|
|
236
|
+
: undefined
|
|
237
|
+
const lead =
|
|
238
|
+
flow === undefined && type === undefined && accent === ''
|
|
239
|
+
? undefined
|
|
240
|
+
: `${accent}${type ? `${escape(type)} ` : ''}${flow ?? ''}`.trim()
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
fields,
|
|
244
|
+
header: reverted ? ':x: Transaction reverted' : friendlyName['transaction:included'],
|
|
245
|
+
...(lead ? { lead } : {}),
|
|
246
|
+
text: `Transaction included${status ? ` · ${status}` : ''}`,
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function logLayout(data: Record<string, unknown>, base: string | undefined): Layout {
|
|
251
|
+
const event =
|
|
252
|
+
typeof data['event'] === 'object' && data['event'] !== null
|
|
253
|
+
? (data['event'] as Record<string, unknown>)
|
|
254
|
+
: undefined
|
|
255
|
+
const name = event ? str(event['name']) : undefined
|
|
256
|
+
const decoded = name !== undefined
|
|
257
|
+
const address = str(data['address'])
|
|
258
|
+
const args =
|
|
259
|
+
typeof data['args'] === 'object' && data['args'] !== null
|
|
260
|
+
? (data['args'] as Record<string, unknown>)
|
|
261
|
+
: undefined
|
|
262
|
+
|
|
263
|
+
// Reserve the always-present coordinates first, then fill remaining slots (up
|
|
264
|
+
// to 10 total) with decoded args, with a trailing "…and N more" if truncated.
|
|
265
|
+
const fields: Field[] = []
|
|
266
|
+
if (address !== undefined) field(fields, 'Contract', addressLink(base, address))
|
|
267
|
+
if (!decoded && event && str(event['topic0']) !== undefined)
|
|
268
|
+
field(fields, 'Topic0', escape(truncateHash(str(event['topic0'])!)))
|
|
269
|
+
field(fields, 'Block', blockLink(base, data['blockNumber']))
|
|
270
|
+
if (str(data['transactionHash']) !== undefined)
|
|
271
|
+
field(fields, 'Tx', txLink(base, str(data['transactionHash'])!))
|
|
272
|
+
if (!decoded) field(fields, 'Log index', scalar(data['logIndex']))
|
|
273
|
+
field(fields, 'Timestamp', date(str(data['timestamp'])))
|
|
274
|
+
|
|
275
|
+
if (decoded && args) {
|
|
276
|
+
const entries = Object.entries(args)
|
|
277
|
+
const room = Math.max(0, 10 - fields.length)
|
|
278
|
+
for (const [key, value] of entries.slice(0, room)) field(fields, key, argValue(value, base))
|
|
279
|
+
if (entries.length > room) field(fields, '', `…and ${entries.length - room} more`)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const lead = decoded
|
|
283
|
+
? `*${escape(name)}* on ${address ? addressLink(base, address) : 'contract'}`
|
|
284
|
+
: address !== undefined
|
|
285
|
+
? `Log on ${addressLink(base, address)}${
|
|
286
|
+
event && str(event['topic0'])
|
|
287
|
+
? ` · topic0 ${escape(truncateHash(str(event['topic0'])!))}`
|
|
288
|
+
: ''
|
|
289
|
+
}`
|
|
290
|
+
: undefined
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
fields,
|
|
294
|
+
header: decoded ? clamp(`:label: Log: ${name}`, 150) : friendlyName['log:emitted'],
|
|
295
|
+
...(lead === undefined ? {} : { lead }),
|
|
296
|
+
text: decoded
|
|
297
|
+
? `Log: ${name}${address ? ` · ${truncateAddress(address)}` : ''}`
|
|
298
|
+
: `Log emitted${address ? ` · ${truncateAddress(address)}` : ''}`,
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function blockLayout(data: Record<string, unknown>, base: string | undefined): Layout {
|
|
303
|
+
const number = scalar(data['number'])
|
|
304
|
+
const txCount = scalar(data['transactionCount'])
|
|
305
|
+
const gasUsedNum = data['gasUsed']
|
|
306
|
+
const gasLimitNum = data['gasLimit']
|
|
307
|
+
const gasUsed = scalar(gasUsedNum)
|
|
308
|
+
const gasLimit = scalar(gasLimitNum)
|
|
309
|
+
const miner = str(data['miner'])
|
|
310
|
+
const proposer = str(data['proposer'])
|
|
311
|
+
const hash = str(data['hash'])
|
|
312
|
+
|
|
313
|
+
const fields: Field[] = []
|
|
314
|
+
field(fields, 'Number', blockLink(base, data['number']))
|
|
315
|
+
field(fields, 'Txns', txCount)
|
|
316
|
+
if (gasUsed !== undefined && gasLimit !== undefined)
|
|
317
|
+
field(fields, 'Gas', `${escape(gasUsed)} / ${escape(gasLimit)}`)
|
|
318
|
+
if (miner !== undefined) field(fields, 'Producer', addressLink(base, miner))
|
|
319
|
+
if (proposer !== undefined) field(fields, 'Proposer', addressLink(base, proposer))
|
|
320
|
+
if (hash !== undefined) field(fields, 'Hash', blockHashLink(base, hash))
|
|
321
|
+
field(fields, 'Timestamp', date(str(data['timestamp'])))
|
|
322
|
+
|
|
323
|
+
const percent =
|
|
324
|
+
typeof gasUsedNum === 'number' && typeof gasLimitNum === 'number' && gasLimitNum > 0
|
|
325
|
+
? ` (${((gasUsedNum / gasLimitNum) * 100).toFixed(1)}%)`
|
|
326
|
+
: ''
|
|
327
|
+
const lead =
|
|
328
|
+
txCount !== undefined
|
|
329
|
+
? `*${escape(txCount)}* transactions${
|
|
330
|
+
gasUsed !== undefined && gasLimit !== undefined
|
|
331
|
+
? ` · gas ${escape(gasUsed)} / ${escape(gasLimit)}${percent}`
|
|
332
|
+
: ''
|
|
333
|
+
}`
|
|
334
|
+
: undefined
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
fields,
|
|
338
|
+
header:
|
|
339
|
+
number === undefined
|
|
340
|
+
? friendlyName['block:created']
|
|
341
|
+
: clamp(`:link: Block created #${number}`, 150),
|
|
342
|
+
...(lead === undefined ? {} : { lead }),
|
|
343
|
+
text: `Block created${number === undefined ? '' : ` #${number}`}${
|
|
344
|
+
txCount === undefined ? '' : ` · ${txCount} txns`
|
|
345
|
+
}`,
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function pingLayout(): Layout {
|
|
350
|
+
return {
|
|
351
|
+
fields: [],
|
|
352
|
+
header: friendlyName.ping,
|
|
353
|
+
lead: 'Your Slack destination is wired up correctly.',
|
|
354
|
+
text: 'Webhook test ping',
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Appends a `*Label*\nvalue` field when the value is present. */
|
|
359
|
+
function field(fields: Field[], label: string, value: string | undefined): void {
|
|
360
|
+
if (value === undefined) return
|
|
361
|
+
fields.push({ text: label ? `*${label}*\n${value}` : value, type: 'mrkdwn' })
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Renders a human token amount (`10.00 USDC`) from base units + metadata. */
|
|
365
|
+
function humanAmount(
|
|
366
|
+
amount: string | undefined,
|
|
367
|
+
token: { decimals?: number | undefined; symbol?: string | undefined } | undefined,
|
|
368
|
+
): string | undefined {
|
|
369
|
+
if (amount === undefined) return undefined
|
|
370
|
+
if (token?.decimals === undefined) return amount
|
|
371
|
+
try {
|
|
372
|
+
const formatted = formatUnits(BigInt(amount), token.decimals)
|
|
373
|
+
return token.symbol ? `${formatted} ${token.symbol}` : formatted
|
|
374
|
+
} catch {
|
|
375
|
+
return amount
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Renders a decoded log arg: address args link, everything else escaped text. */
|
|
380
|
+
function argValue(value: unknown, base: string | undefined): string {
|
|
381
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value))
|
|
382
|
+
return addressLink(base, value)
|
|
383
|
+
return escape(scalar(value) ?? JSON.stringify(value))
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** `<base/address/…|0x…>` when an explorer is known, else plain truncated text. */
|
|
387
|
+
function addressLink(base: string | undefined, address: string): string {
|
|
388
|
+
return link(base, 'address', address, truncateAddress(address))
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** `<base/tx/…|0x…>` for a transaction hash. */
|
|
392
|
+
function txLink(base: string | undefined, hash: string): string {
|
|
393
|
+
return link(base, 'tx', hash, truncateHash(hash))
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** `<base/token/…|SYMBOL>` (or truncated address) for a token contract. */
|
|
397
|
+
function tokenLink(base: string | undefined, address: string, symbol: string | undefined): string {
|
|
398
|
+
return link(base, 'token', address, symbol ?? truncateAddress(address))
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** `<base/block/N|N>` for a block number (accepts number/string). */
|
|
402
|
+
function blockLink(base: string | undefined, number: unknown): string | undefined {
|
|
403
|
+
const id = scalar(number)
|
|
404
|
+
if (id === undefined) return undefined
|
|
405
|
+
return link(base, 'block', id, id)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** `<base/block/0x…|0x…>` linking a block by its hash. */
|
|
409
|
+
function blockHashLink(base: string | undefined, hash: string): string {
|
|
410
|
+
return link(base, 'block', hash, truncateHash(hash))
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Builds a Slack mrkdwn link, falling back to escaped plain text with no base. */
|
|
414
|
+
function link(base: string | undefined, path: string, id: string, label: string): string {
|
|
415
|
+
if (!base) return escape(label)
|
|
416
|
+
return `<${base}/${path}/${encodeURIComponent(id)}|${escape(label)}>`
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Localized time via Slack's `<!date>` token; `undefined` for a bad timestamp. */
|
|
420
|
+
function date(iso: string | undefined): string | undefined {
|
|
421
|
+
if (iso === undefined) return undefined
|
|
422
|
+
const ms = Date.parse(iso)
|
|
423
|
+
if (Number.isNaN(ms)) return undefined
|
|
424
|
+
return `<!date^${Math.floor(ms / 1_000)}^{date_short_pretty} {time}|${escape(iso)}>`
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Middle-truncates an address to `0x9e39…1754` (6+4). */
|
|
428
|
+
function truncateAddress(value: string): string {
|
|
429
|
+
return value.length > 12 ? `${value.slice(0, 6)}…${value.slice(-4)}` : value
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Middle-truncates a hash to `0x3d24a706…24bbf9d3` (10+8). */
|
|
433
|
+
function truncateHash(value: string): string {
|
|
434
|
+
return value.length > 20 ? `${value.slice(0, 10)}…${value.slice(-8)}` : value
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Escapes the three mrkdwn-significant characters in interpolated values. */
|
|
438
|
+
function escape(value: string): string {
|
|
439
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Clamps a header string to Slack's length cap. */
|
|
443
|
+
function clamp(value: string, max: number): string {
|
|
444
|
+
return value.length > max ? `${value.slice(0, max - 1)}…` : value
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Reads a string value, or `undefined` when the key isn't a string. */
|
|
448
|
+
function str(value: unknown): string | undefined {
|
|
449
|
+
return typeof value === 'string' ? value : undefined
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Reads a scalar (number/bigint/boolean/string) value as a plain string. */
|
|
453
|
+
function scalar(value: unknown): string | undefined {
|
|
454
|
+
if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean')
|
|
455
|
+
return String(value)
|
|
456
|
+
return typeof value === 'string' ? value : undefined
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Host that Slack incoming-webhook URLs must use. */
|
|
460
|
+
const slackWebhookHost = 'hooks.slack.com'
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Validates a `slack` destination's incoming-webhook URL: must be `https` and
|
|
464
|
+
* hosted at {@link slackWebhookHost}. The host lock keeps `slack` destinations
|
|
465
|
+
* from becoming a generic "POST an unsigned message anywhere" primitive — a
|
|
466
|
+
* `slack` delivery carries no signature, so it must only ever reach Slack.
|
|
467
|
+
*/
|
|
468
|
+
export function assertSlackUrl(input: string): URL {
|
|
469
|
+
let url: URL
|
|
470
|
+
try {
|
|
471
|
+
url = new URL(input)
|
|
472
|
+
} catch {
|
|
473
|
+
throw new WebhookDestination.InvalidUrlError('malformed', input)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (url.protocol !== 'https:') throw new WebhookDestination.InvalidUrlError('protocol', input)
|
|
477
|
+
if (url.username || url.password)
|
|
478
|
+
throw new WebhookDestination.InvalidUrlError('credentials', input)
|
|
479
|
+
if (url.hostname !== slackWebhookHost)
|
|
480
|
+
throw new WebhookDestination.InvalidUrlError('slack_host', input)
|
|
481
|
+
|
|
482
|
+
return url
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Masks the secret path of a Slack incoming-webhook URL for logging, keeping the
|
|
487
|
+
* host so the destination is still recognizable: `https://hooks.slack.com/…`.
|
|
488
|
+
* Falls back to the bare origin if the URL can't be parsed.
|
|
489
|
+
*/
|
|
490
|
+
export function redactSlackUrl(url: string): string {
|
|
491
|
+
try {
|
|
492
|
+
return `${new URL(url).origin}/…`
|
|
493
|
+
} catch {
|
|
494
|
+
return 'https://hooks.slack.com/…'
|
|
495
|
+
}
|
|
496
|
+
}
|