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,134 @@
|
|
|
1
|
+
import * as Client from './Client.js'
|
|
2
|
+
import * as TestApp from '../test/App.js'
|
|
3
|
+
|
|
4
|
+
test('uses hosted API URL by default', () => {
|
|
5
|
+
const client = Client.create()
|
|
6
|
+
|
|
7
|
+
expect(Client.defaultUrl).toMatchInlineSnapshot(`"https://api.tempo.xyz"`)
|
|
8
|
+
expect(
|
|
9
|
+
client.v1.blocks[':block'].$url({ param: { block: 'latest' } }).toString(),
|
|
10
|
+
).toMatchInlineSnapshot(`"https://api.tempo.xyz/v1/blocks/latest"`)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('calls Tempo API app through configured fetch implementation', async () => {
|
|
14
|
+
const app = TestApp.create()
|
|
15
|
+
const fetch = (async (input, init) =>
|
|
16
|
+
app.fetch(new Request(input, init))) satisfies typeof globalThis.fetch
|
|
17
|
+
const client = Client.create({ fetch, url: 'http://tempo-api.test' })
|
|
18
|
+
|
|
19
|
+
const response = await client.v1['verified-tokens'].currencies.$get()
|
|
20
|
+
const body = await response.json()
|
|
21
|
+
|
|
22
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
23
|
+
expect(body).toMatchInlineSnapshot(`
|
|
24
|
+
{
|
|
25
|
+
"data": [],
|
|
26
|
+
}
|
|
27
|
+
`)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('applies `apiKey` to canonical header over base headers', async () => {
|
|
31
|
+
const { calls, fetch } = captureFetch()
|
|
32
|
+
const client = Client.create({
|
|
33
|
+
apiKey: 'key_option',
|
|
34
|
+
fetch,
|
|
35
|
+
headers: {
|
|
36
|
+
authorization: 'Payment proof',
|
|
37
|
+
'tempo-api-key': 'key_header',
|
|
38
|
+
'user-agent': 'tempo-api-test',
|
|
39
|
+
},
|
|
40
|
+
url: 'https://api.test',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
await client.v1.blocks[':block'].$get({ param: { block: 'latest' }, query: {} })
|
|
44
|
+
|
|
45
|
+
expect(calls).toMatchInlineSnapshot(`
|
|
46
|
+
[
|
|
47
|
+
{
|
|
48
|
+
"headers": {
|
|
49
|
+
"authorization": "Payment proof",
|
|
50
|
+
"tempo-api-key": "key_option",
|
|
51
|
+
"user-agent": "tempo-api-test",
|
|
52
|
+
},
|
|
53
|
+
"url": "https://api.test/v1/blocks/latest?",
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
`)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('merges `apiKey` with dynamic base headers', async () => {
|
|
60
|
+
const { calls, fetch } = captureFetch()
|
|
61
|
+
const client = Client.create({
|
|
62
|
+
apiKey: 'key_option',
|
|
63
|
+
fetch,
|
|
64
|
+
headers: async () => ({ authorization: 'Payment proof' }),
|
|
65
|
+
url: 'https://api.test',
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await client.v1.blocks[':block'].$get({ param: { block: 'latest' }, query: {} })
|
|
69
|
+
|
|
70
|
+
expect(calls).toMatchInlineSnapshot(`
|
|
71
|
+
[
|
|
72
|
+
{
|
|
73
|
+
"headers": {
|
|
74
|
+
"authorization": "Payment proof",
|
|
75
|
+
"tempo-api-key": "key_option",
|
|
76
|
+
},
|
|
77
|
+
"url": "https://api.test/v1/blocks/latest?",
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
`)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('lets per-request headers override base `apiKey`', async () => {
|
|
84
|
+
const { calls, fetch } = captureFetch()
|
|
85
|
+
const client = Client.create({ apiKey: 'key_base', fetch, url: 'https://api.test' })
|
|
86
|
+
|
|
87
|
+
await client.v1.blocks[':block'].$get(
|
|
88
|
+
{ param: { block: 'latest' }, query: {} },
|
|
89
|
+
{ headers: { 'tempo-api-key': 'key_request' } },
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect(calls).toMatchInlineSnapshot(`
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"headers": {
|
|
96
|
+
"tempo-api-key": "key_request",
|
|
97
|
+
},
|
|
98
|
+
"url": "https://api.test/v1/blocks/latest?",
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
`)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('omits empty API keys', async () => {
|
|
105
|
+
const { calls, fetch } = captureFetch()
|
|
106
|
+
const client = Client.create({ apiKey: '', fetch, url: 'https://api.test' })
|
|
107
|
+
|
|
108
|
+
await client.v1.blocks[':block'].$get({ param: { block: 'latest' }, query: {} })
|
|
109
|
+
|
|
110
|
+
expect(calls).toMatchInlineSnapshot(`
|
|
111
|
+
[
|
|
112
|
+
{
|
|
113
|
+
"headers": {},
|
|
114
|
+
"url": "https://api.test/v1/blocks/latest?",
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
`)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
function captureFetch() {
|
|
121
|
+
const calls: { headers: Record<string, string>; url: string }[] = []
|
|
122
|
+
const fetch = (async (input, init) => {
|
|
123
|
+
calls.push({
|
|
124
|
+
headers: Object.fromEntries(new Headers(init?.headers).entries()),
|
|
125
|
+
url: (() => {
|
|
126
|
+
if (typeof input === 'string') return input
|
|
127
|
+
if (input instanceof URL) return input.toString()
|
|
128
|
+
return input.url
|
|
129
|
+
})(),
|
|
130
|
+
})
|
|
131
|
+
return new Response('{}', { headers: { 'content-type': 'application/json' }, status: 200 })
|
|
132
|
+
}) satisfies typeof globalThis.fetch
|
|
133
|
+
return { calls, fetch }
|
|
134
|
+
}
|
package/src/Client.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { hc, type ClientRequestOptions } from 'hono/client'
|
|
2
|
+
|
|
3
|
+
import type * as App from './App.js'
|
|
4
|
+
|
|
5
|
+
/** Default hosted Tempo API URL. */
|
|
6
|
+
export const defaultUrl = 'https://api.tempo.xyz'
|
|
7
|
+
|
|
8
|
+
/** Creates a typed client for the hosted Tempo API. */
|
|
9
|
+
export function create(options: create.Options = {}): create.ReturnType {
|
|
10
|
+
const { apiKey, url = defaultUrl, ...client } = options
|
|
11
|
+
const mergedHeaders = headers(client.headers, apiKey)
|
|
12
|
+
if (!mergedHeaders) return hc<App.App>(url, client)
|
|
13
|
+
return hc<App.App>(url, { ...client, headers: mergedHeaders })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Typed Hono client for the Tempo API. */
|
|
17
|
+
export type Client = ReturnType<typeof hc<App.App>>
|
|
18
|
+
|
|
19
|
+
export declare namespace create {
|
|
20
|
+
/** Typed Hono client returned by {@link create}. */
|
|
21
|
+
type ReturnType = Client
|
|
22
|
+
|
|
23
|
+
/** Options for creating a typed Tempo API client. */
|
|
24
|
+
type Options = ClientRequestOptions & {
|
|
25
|
+
/** API key sent on the canonical `tempo-api-key` header. */
|
|
26
|
+
apiKey?: string | undefined
|
|
27
|
+
/** Tempo API URL. Defaults to {@link defaultUrl}. */
|
|
28
|
+
url?: string | undefined
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function headers(
|
|
33
|
+
base: ClientRequestOptions['headers'],
|
|
34
|
+
apiKey: string | undefined,
|
|
35
|
+
): ClientRequestOptions['headers'] {
|
|
36
|
+
if (!apiKey) return base
|
|
37
|
+
if (typeof base === 'function') return async () => mergeHeaders(await base(), apiKey)
|
|
38
|
+
return mergeHeaders(base, apiKey)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function mergeHeaders(base: Record<string, string> | undefined, apiKey: string) {
|
|
42
|
+
const headers = new Headers(base)
|
|
43
|
+
headers.set('tempo-api-key', apiKey)
|
|
44
|
+
return Object.fromEntries(headers.entries())
|
|
45
|
+
}
|
package/src/Metrics.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type * as Log from './internal/Log.js'
|
|
2
|
+
|
|
3
|
+
/** Tag values a metric backend accepts. */
|
|
4
|
+
export type Tags = Record<string, string | number | boolean | undefined | null>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The bounded StatsD-style surface the built-in metric sinks emit to, plus an
|
|
8
|
+
* optional high-cardinality {@link Metrics.analytics} hook. Apps supply a
|
|
9
|
+
* concrete backend — e.g. {@link cloudflare} — so core carries no metrics-vendor
|
|
10
|
+
* dependency. Pass it as `metrics` to `App.create` and the webhook poller/queue
|
|
11
|
+
* handlers.
|
|
12
|
+
*/
|
|
13
|
+
export type Metrics = {
|
|
14
|
+
/**
|
|
15
|
+
* Optional per-request analytics sink, called once with the request-log entry
|
|
16
|
+
* for every response. Unlike the StatsD `count`/`gauge`/`histogram` surface
|
|
17
|
+
* (which stays low-cardinality), this hook carries the full entry — caller
|
|
18
|
+
* ids, org ids, route, status — for a wide-event store (e.g. ClickHouse).
|
|
19
|
+
* Attach it via {@link cloudflare}'s `analytics` option (or {@link from} for a
|
|
20
|
+
* custom backend); omit it to drop analytics. May be async; `App.create` does
|
|
21
|
+
* not await it.
|
|
22
|
+
*/
|
|
23
|
+
analytics?: ((entry: Log.Entry) => Promise<void> | void) | undefined
|
|
24
|
+
/** Increments a counter; same name+tags are summed. */
|
|
25
|
+
count(name: string, value: number, tags?: Tags): void
|
|
26
|
+
/** Ships accumulated metrics. */
|
|
27
|
+
flush(): void
|
|
28
|
+
/** Sets a gauge; last write wins for the same name+tags. */
|
|
29
|
+
gauge(name: string, value: number, tags?: Tags): void
|
|
30
|
+
/** Records a histogram observation. */
|
|
31
|
+
histogram(name: string, value: number, tags?: Tags): void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a {@link Metrics} from a value, type-checking it against the contract.
|
|
36
|
+
* The escape hatch for bringing your own backend (Datadog, Prometheus, …):
|
|
37
|
+
* author the implementation inline and have it validated against {@link Metrics}.
|
|
38
|
+
*/
|
|
39
|
+
export function from<const metrics extends Metrics>(metrics: metrics): metrics {
|
|
40
|
+
return metrics
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Options for the {@link cloudflare} preset and its backend. */
|
|
44
|
+
export declare namespace cloudflare {
|
|
45
|
+
/** Shared context handed to the {@link Options.analytics} factory. */
|
|
46
|
+
type Context = {
|
|
47
|
+
/** Resolved `enabled` flag; the analytics sink should drop events when false. */
|
|
48
|
+
enabled: boolean
|
|
49
|
+
/** Deployment environment, also added as the `environment` global tag. */
|
|
50
|
+
environment?: string | undefined
|
|
51
|
+
/** Service name, also added as the `service` global tag. */
|
|
52
|
+
service?: string | undefined
|
|
53
|
+
}
|
|
54
|
+
type Options = {
|
|
55
|
+
/**
|
|
56
|
+
* Builds the per-request {@link Metrics.analytics} hook from the shared
|
|
57
|
+
* {@link Context}, so the sink reuses `enabled`/`environment`/`service`
|
|
58
|
+
* instead of repeating them. Omit to drop analytics.
|
|
59
|
+
*/
|
|
60
|
+
analytics?: ((context: Context) => Metrics['analytics']) | undefined
|
|
61
|
+
/** Set false (e.g. in local `wrangler dev`) to drop every metric and analytics event. Default true. */
|
|
62
|
+
enabled?: boolean | undefined
|
|
63
|
+
/** Deployment environment, added as the `environment` global tag and shared with {@link Options.analytics}. */
|
|
64
|
+
environment?: string | undefined
|
|
65
|
+
/** Service name, added as the `service` global tag and shared with {@link Options.analytics}. */
|
|
66
|
+
service?: string | undefined
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A Cloudflare Workers {@link Metrics} — pass it as `metrics` to `App.create`
|
|
72
|
+
* and the webhook poller/queue handlers. It buffers metrics and emits them on
|
|
73
|
+
* `flush()` as `cwm-`-prefixed `console.log` lines (one per ≤250 KiB batch) for
|
|
74
|
+
* a Workers Logs → metrics pipeline to scrape; no external dependency.
|
|
75
|
+
*/
|
|
76
|
+
export function cloudflare(options: cloudflare.Options = {}): Metrics {
|
|
77
|
+
type Entry = { n: string; t: 'c' | 'g' | 'h'; tags: Tags; ts: number; v: number }
|
|
78
|
+
|
|
79
|
+
const enabled = options.enabled !== false
|
|
80
|
+
|
|
81
|
+
const analytics = options.analytics?.({
|
|
82
|
+
enabled,
|
|
83
|
+
environment: options.environment,
|
|
84
|
+
service: options.service,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (!enabled) return { analytics, count() {}, flush() {}, gauge() {}, histogram() {} }
|
|
88
|
+
|
|
89
|
+
const globalTags: Tags = {
|
|
90
|
+
...(options.environment === undefined ? {} : { environment: options.environment }),
|
|
91
|
+
...(options.service === undefined ? {} : { service: options.service }),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const counts = new Map<string, Entry>()
|
|
95
|
+
const gauges = new Map<string, Entry>()
|
|
96
|
+
const histograms: Entry[] = []
|
|
97
|
+
|
|
98
|
+
const key = (name: string, tags: Tags) => {
|
|
99
|
+
let s = name
|
|
100
|
+
for (const k of Object.keys(tags).sort()) s += `\0${k}\0${tags[k]}`
|
|
101
|
+
return s
|
|
102
|
+
}
|
|
103
|
+
const withGlobal = (tags?: Tags): Tags => ({ ...globalTags, ...tags })
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
analytics,
|
|
107
|
+
count(name, value, tags) {
|
|
108
|
+
const merged = withGlobal(tags)
|
|
109
|
+
const k = key(name, merged)
|
|
110
|
+
const existing = counts.get(k)
|
|
111
|
+
if (existing) existing.v += value
|
|
112
|
+
else counts.set(k, { n: name, t: 'c', tags: merged, ts: 0, v: value })
|
|
113
|
+
},
|
|
114
|
+
flush() {
|
|
115
|
+
const ts = Date.now()
|
|
116
|
+
const entries: Entry[] = []
|
|
117
|
+
for (const e of counts.values()) entries.push({ ...e, ts })
|
|
118
|
+
for (const e of gauges.values()) entries.push({ ...e, ts })
|
|
119
|
+
entries.push(...histograms)
|
|
120
|
+
counts.clear()
|
|
121
|
+
gauges.clear()
|
|
122
|
+
histograms.length = 0
|
|
123
|
+
if (!entries.length) return
|
|
124
|
+
|
|
125
|
+
// Emit as `cwm-[…]` lines, splitting batches over the size cap. Serialize
|
|
126
|
+
// with the field order the Workers Logs pipeline emits (`t,n,v,tags,ts`).
|
|
127
|
+
const parts = entries.map((e) =>
|
|
128
|
+
JSON.stringify({ t: e.t, n: e.n, v: e.v, tags: e.tags, ts: e.ts }),
|
|
129
|
+
)
|
|
130
|
+
let buf: string[] = []
|
|
131
|
+
let bufLen = 0
|
|
132
|
+
for (const part of parts) {
|
|
133
|
+
// 5 bytes for the `cwm-[` / `]` frame, plus one comma per joined part.
|
|
134
|
+
const wouldBe = 5 + (buf.length + 1) + bufLen + part.length
|
|
135
|
+
// 250 KiB: the largest `console.log` payload Workers Logs keeps untruncated.
|
|
136
|
+
if (buf.length > 0 && wouldBe > 250 * 1024) {
|
|
137
|
+
console.log(`cwm-[${buf.join(',')}]`)
|
|
138
|
+
buf = []
|
|
139
|
+
bufLen = 0
|
|
140
|
+
}
|
|
141
|
+
buf.push(part)
|
|
142
|
+
bufLen += part.length
|
|
143
|
+
}
|
|
144
|
+
if (buf.length) console.log(`cwm-[${buf.join(',')}]`)
|
|
145
|
+
},
|
|
146
|
+
gauge(name, value, tags) {
|
|
147
|
+
const merged = withGlobal(tags)
|
|
148
|
+
gauges.set(key(name, merged), { n: name, t: 'g', tags: merged, ts: 0, v: value })
|
|
149
|
+
},
|
|
150
|
+
histogram(name, value, tags) {
|
|
151
|
+
histograms.push({ n: name, t: 'h', tags: withGlobal(tags), ts: Date.now(), v: value })
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/Scope.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The issuable API-key scope vocabulary: the single source of truth for the
|
|
3
|
+
* `/scopes` catalog, for key minting (`ApiKeySource.Source.mint`), and for the scopes
|
|
4
|
+
* required by route policies (`Auth.policy`). Exposing it publicly lets operator
|
|
5
|
+
* tooling (e.g. the provisioning CLI) validate scopes against the same list the
|
|
6
|
+
* server enforces.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wildcard scope. A key whose `scopes` contains this value satisfies every
|
|
11
|
+
* required scope, so it never needs re-provisioning when new scopes are added.
|
|
12
|
+
* Use sparingly — it grants full access.
|
|
13
|
+
*/
|
|
14
|
+
export const wildcard = '*'
|
|
15
|
+
|
|
16
|
+
/** Catalog entry describing an issuable API-key scope. */
|
|
17
|
+
type Entry = {
|
|
18
|
+
/** Human-readable explanation of what the scope grants. */
|
|
19
|
+
description: string
|
|
20
|
+
/** Scope identifier used in a key's `scopes` and in route policies. */
|
|
21
|
+
scope: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The issuable API-key scope vocabulary — the single source of truth for the
|
|
26
|
+
* `/scopes` catalog and for key minting. Route policies require values from this
|
|
27
|
+
* set; {@link wildcard} (`'*'`) is an implicit grant-all and is intentionally
|
|
28
|
+
* not listed as an issuable scope.
|
|
29
|
+
*/
|
|
30
|
+
export const catalog = [
|
|
31
|
+
{
|
|
32
|
+
description: 'Read public chain data: tokens, transactions, transfers, blocks, and receipts.',
|
|
33
|
+
scope: 'data:read',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
description: 'Run ad-hoc queries against the raw indexer passthrough.',
|
|
37
|
+
scope: 'indexer:query',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
description: 'Read webhook subscriptions.',
|
|
41
|
+
scope: 'webhooks:read',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
description: 'Create, update, and delete webhook subscriptions.',
|
|
45
|
+
scope: 'webhooks:write',
|
|
46
|
+
},
|
|
47
|
+
] as const satisfies readonly Entry[]
|
|
48
|
+
|
|
49
|
+
/** Issuable API-key scope identifier — the union of every {@link catalog} entry. */
|
|
50
|
+
export type Id = (typeof catalog)[number]['scope']
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Store, Webhooks } from 'cadent'
|
|
2
|
+
|
|
3
|
+
describe('createPoller', () => {
|
|
4
|
+
/** Decodes the opaque `(block, index)` keyset cursor the poller stores. */
|
|
5
|
+
function decodeCursor(token: string | null): [number, number] | null {
|
|
6
|
+
if (!token) return null
|
|
7
|
+
const json = atob(token.replaceAll('-', '+').replaceAll('_', '/'))
|
|
8
|
+
return JSON.parse(json) as [number, number]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test('binds a state store and runs a tick at an injected head', async () => {
|
|
12
|
+
const store = Store.memory()
|
|
13
|
+
const subscription = await Webhooks.createSubscription(store, {
|
|
14
|
+
chainId: 42431,
|
|
15
|
+
destination: { type: 'url', url: 'https://hooks.example.com/x' },
|
|
16
|
+
eventType: 'token:transfer',
|
|
17
|
+
owner: { orgId: 'org_test', type: 'api_key' },
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const poller = Webhooks.createPoller({
|
|
21
|
+
defaultChainId: 42431,
|
|
22
|
+
webhook: { store },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// A brand-new subscription anchors at the confirmed head on its first tick
|
|
26
|
+
// (no backfill), so this exercises the createPoller → poll wiring (store
|
|
27
|
+
// binding, default chain id, injected head, result shape) without touching
|
|
28
|
+
// TIDX. Detailed poll/getIndexedHead behavior lives in
|
|
29
|
+
// src/apps/webhooks.test.ts.
|
|
30
|
+
const result = await poller.tick({ headBlockNumber: 200 })
|
|
31
|
+
|
|
32
|
+
expect(result).toMatchInlineSnapshot(`
|
|
33
|
+
{
|
|
34
|
+
"chainId": 42431,
|
|
35
|
+
"dispatched": 0,
|
|
36
|
+
"failed": 0,
|
|
37
|
+
"safeBlock": 198,
|
|
38
|
+
"scanned": 0,
|
|
39
|
+
"subscriptions": 1,
|
|
40
|
+
}
|
|
41
|
+
`)
|
|
42
|
+
expect(decodeCursor(await Webhooks.getCursor(store, subscription.id))).toMatchInlineSnapshot(`
|
|
43
|
+
[
|
|
44
|
+
198,
|
|
45
|
+
2147483647,
|
|
46
|
+
]
|
|
47
|
+
`)
|
|
48
|
+
})
|
|
49
|
+
})
|
package/src/Webhooks.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as AppWebhooks from './apps/webhooks.js'
|
|
2
|
+
import * as MetricSink from './internal/MetricSink.js'
|
|
3
|
+
import type * as Store from './internal/Store.js'
|
|
4
|
+
import type * as Metrics from './Metrics.js'
|
|
5
|
+
import * as Tidx from './internal/Tidx.js'
|
|
6
|
+
import * as Viem from './internal/Viem.js'
|
|
7
|
+
|
|
8
|
+
// Re-export the runtime-agnostic primitives (subscriptions, signing, delivery,
|
|
9
|
+
// `deliverAndRecord`, cursors, …) so consumers reach everything through one
|
|
10
|
+
// `Webhooks` namespace. The apps-layer poller stays private behind
|
|
11
|
+
// `createPoller` below.
|
|
12
|
+
export * from './internal/Webhooks.js'
|
|
13
|
+
|
|
14
|
+
// Re-export the webhook resource's zod schemas so consumers can validate and
|
|
15
|
+
// type delivery bodies: `Webhooks.schema.Envelope` (a discriminated union on
|
|
16
|
+
// `type`), plus the per-event `TransferEvent` / `TransactionEvent` / `PingEvent`
|
|
17
|
+
// payloads. Infer a type with `z.infer<typeof Webhooks.schema.Envelope>`.
|
|
18
|
+
export { eventTypes, schema } from './apps/webhooks.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds a scheduled webhook poller bound to a state store and chain config.
|
|
22
|
+
*
|
|
23
|
+
* This is the entry point a host runtime drives on a timer (a Cloudflare
|
|
24
|
+
* `scheduled` cron handler, a self-host `setInterval`, …). Each {@link
|
|
25
|
+
* Poller.tick} scans active subscriptions from their cursors up to `head -
|
|
26
|
+
* confirmations` and dispatches matching events.
|
|
27
|
+
*
|
|
28
|
+
* The monitoring event types (`log:emitted`, `block:created`,
|
|
29
|
+
* `transaction:included`) scan a confirmed window directly over JSON-RPC, so the
|
|
30
|
+
* poller builds memoized viem clients (via {@link Viem.getClient}); the legacy
|
|
31
|
+
* `token:transfer` type still reads from TIDX, so it
|
|
32
|
+
* also builds memoized TIDX clients (via {@link Tidx.createGetClient}) when a
|
|
33
|
+
* `tidx` config is supplied. The poller keeps `App.create` a pure Hono app: it
|
|
34
|
+
* constructs its own clients instead of reaching into app internals, and owns no
|
|
35
|
+
* request state.
|
|
36
|
+
*/
|
|
37
|
+
export function createPoller(options: createPoller.Options): createPoller.Poller {
|
|
38
|
+
const { store } = options.webhook
|
|
39
|
+
const getTidx = options.tidx
|
|
40
|
+
? Tidx.createGetClient({ defaultChainId: options.defaultChainId, tidx: options.tidx })
|
|
41
|
+
: undefined
|
|
42
|
+
const clients = new Map<number, Viem.getClient.ReturnType>()
|
|
43
|
+
const getClient = (chainId: Viem.ChainId) => {
|
|
44
|
+
let client = clients.get(chainId)
|
|
45
|
+
if (!client) {
|
|
46
|
+
client = Viem.getClient({ chainId, rpc: options.rpc })
|
|
47
|
+
clients.set(chainId, client)
|
|
48
|
+
}
|
|
49
|
+
return client
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const metrics = options.metrics ? MetricSink.webhooks(options.metrics) : undefined
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
async tick(input = {}) {
|
|
56
|
+
const chainId = input.chainId ?? options.defaultChainId ?? Viem.defaultChainId
|
|
57
|
+
|
|
58
|
+
// Wrap the transport so each enqueue (or inline delivery) emits a timed
|
|
59
|
+
// `webhook:enqueue` metric. The wrapper preserves the dispatch contract: it
|
|
60
|
+
// returns the underlying result unchanged and is a no-op when no metrics
|
|
61
|
+
// backend is configured.
|
|
62
|
+
const inner = input.dispatch ?? options.dispatch
|
|
63
|
+
const dispatch =
|
|
64
|
+
metrics && inner
|
|
65
|
+
? async (dispatchable: AppWebhooks.poll.Dispatchable) => {
|
|
66
|
+
const start = Date.now()
|
|
67
|
+
const result = await inner(dispatchable)
|
|
68
|
+
metrics.record({
|
|
69
|
+
chainId: dispatchable.envelope.chainId,
|
|
70
|
+
durationMs: Date.now() - start,
|
|
71
|
+
eventType: dispatchable.envelope.type,
|
|
72
|
+
ok: result.ok,
|
|
73
|
+
type: 'webhook:enqueue',
|
|
74
|
+
})
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
: inner
|
|
78
|
+
|
|
79
|
+
const start = Date.now()
|
|
80
|
+
try {
|
|
81
|
+
const result = await AppWebhooks.poll({
|
|
82
|
+
batchSize: input.batchSize ?? options.batchSize,
|
|
83
|
+
chainId,
|
|
84
|
+
client: getClient(chainId),
|
|
85
|
+
confirmations: input.confirmations ?? options.confirmations,
|
|
86
|
+
dispatch,
|
|
87
|
+
eventTypes: input.eventTypes ?? options.eventTypes,
|
|
88
|
+
...(input.headBlockNumber === undefined
|
|
89
|
+
? {}
|
|
90
|
+
: { headBlockNumber: input.headBlockNumber }),
|
|
91
|
+
maxBlocks: input.maxBlocks ?? options.maxBlocks,
|
|
92
|
+
now: input.now ?? options.now,
|
|
93
|
+
store,
|
|
94
|
+
...(getTidx ? { tidx: getTidx(chainId) } : {}),
|
|
95
|
+
})
|
|
96
|
+
metrics?.record({
|
|
97
|
+
meta: { durationMs: Date.now() - start, outcome: 'ok' },
|
|
98
|
+
result,
|
|
99
|
+
type: 'webhook:tick',
|
|
100
|
+
})
|
|
101
|
+
return result
|
|
102
|
+
} catch (error) {
|
|
103
|
+
metrics?.record({
|
|
104
|
+
meta: { chainId, durationMs: Date.now() - start, outcome: 'error' },
|
|
105
|
+
result: undefined,
|
|
106
|
+
type: 'webhook:tick',
|
|
107
|
+
})
|
|
108
|
+
throw error
|
|
109
|
+
} finally {
|
|
110
|
+
metrics?.flush()
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export declare namespace createPoller {
|
|
117
|
+
/** A row to dispatch, paired with its owning subscription. */
|
|
118
|
+
type Dispatchable = AppWebhooks.poll.Dispatchable
|
|
119
|
+
|
|
120
|
+
/** Delivery transport: hands a matched event to a queue or delivers inline. */
|
|
121
|
+
type Dispatch = NonNullable<AppWebhooks.poll.Options['dispatch']>
|
|
122
|
+
|
|
123
|
+
/** Summary counters returned by {@link Poller.tick}. */
|
|
124
|
+
type Result = AppWebhooks.poll.Result
|
|
125
|
+
|
|
126
|
+
/** Knobs shared by every {@link Poller.tick}; each is overridable per tick. */
|
|
127
|
+
type Defaults = {
|
|
128
|
+
/** Max matching rows scanned per subscription per tick (default 100). */
|
|
129
|
+
batchSize?: number | undefined
|
|
130
|
+
/** Reorg-safety window: only emit blocks this far behind head (default 2). */
|
|
131
|
+
confirmations?: number | undefined
|
|
132
|
+
/**
|
|
133
|
+
* Delivery transport. Defaults to inline `deliverAndRecord` (self-host); on
|
|
134
|
+
* Cloudflare pass a Queue producer so retry/backoff/DLQ are native.
|
|
135
|
+
*/
|
|
136
|
+
dispatch?: Dispatch | undefined
|
|
137
|
+
/** Event types to poll (default all). */
|
|
138
|
+
eventTypes?: AppWebhooks.poll.Options['eventTypes']
|
|
139
|
+
/** Backfill cap: most blocks an RPC scanner inspects per tick (default 1000). */
|
|
140
|
+
maxBlocks?: number | undefined
|
|
141
|
+
/** Clock for envelope timestamps; injectable for tests. */
|
|
142
|
+
now?: (() => Date) | undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Options for {@link createPoller}. */
|
|
146
|
+
type Options = Defaults & {
|
|
147
|
+
/** Tempo chain id polled when a tick omits one. */
|
|
148
|
+
defaultChainId?: Viem.ChainId | undefined
|
|
149
|
+
/** Metrics backend; emits `webhook:tick` per tick and `webhook:enqueue` per dispatch. */
|
|
150
|
+
metrics?: Metrics.Metrics | undefined
|
|
151
|
+
/** RPC options for the viem clients the monitoring scanners read from. */
|
|
152
|
+
rpc?: Viem.getClient.Rpc | undefined
|
|
153
|
+
/** TIDX query client options for the legacy `token:transfer` type. */
|
|
154
|
+
tidx?: Tidx.getClient.Tidx | undefined
|
|
155
|
+
/** Webhook state store (subscriptions + cursors). */
|
|
156
|
+
webhook: { store: Store.State }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Per-tick overrides; falls back to the {@link Options} defaults. */
|
|
160
|
+
type TickOptions = Defaults & {
|
|
161
|
+
/** Chain to poll this tick (default `options.defaultChainId`). */
|
|
162
|
+
chainId?: Viem.ChainId | undefined
|
|
163
|
+
/** Chain head to work back from; resolved from RPC when omitted. */
|
|
164
|
+
headBlockNumber?: bigint | number | undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A scheduled webhook poller. */
|
|
168
|
+
type Poller = {
|
|
169
|
+
/** Runs one poll tick, resolving the indexed head when not supplied. */
|
|
170
|
+
tick(options?: TickOptions): Promise<Result>
|
|
171
|
+
}
|
|
172
|
+
}
|