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.
Files changed (422) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/ApiKey.d.ts +65 -0
  4. package/dist/ApiKey.d.ts.map +1 -0
  5. package/dist/ApiKey.js +105 -0
  6. package/dist/ApiKey.js.map +1 -0
  7. package/dist/ApiKeySource.d.ts +166 -0
  8. package/dist/ApiKeySource.d.ts.map +1 -0
  9. package/dist/ApiKeySource.js +234 -0
  10. package/dist/ApiKeySource.js.map +1 -0
  11. package/dist/App.d.ts +19317 -0
  12. package/dist/App.d.ts.map +1 -0
  13. package/dist/App.js +612 -0
  14. package/dist/App.js.map +1 -0
  15. package/dist/Assets.d.ts +44 -0
  16. package/dist/Assets.d.ts.map +1 -0
  17. package/dist/Assets.js +23 -0
  18. package/dist/Assets.js.map +1 -0
  19. package/dist/Auth.d.ts +2 -0
  20. package/dist/Auth.d.ts.map +1 -0
  21. package/dist/Auth.js +2 -0
  22. package/dist/Auth.js.map +1 -0
  23. package/dist/Cli.d.ts +21 -0
  24. package/dist/Cli.d.ts.map +1 -0
  25. package/dist/Cli.js +13 -0
  26. package/dist/Cli.js.map +1 -0
  27. package/dist/Client.d.ts +20 -0
  28. package/dist/Client.d.ts.map +1 -0
  29. package/dist/Client.js +24 -0
  30. package/dist/Client.js.map +1 -0
  31. package/dist/Metrics.d.ts +70 -0
  32. package/dist/Metrics.d.ts.map +1 -0
  33. package/dist/Metrics.js +91 -0
  34. package/dist/Metrics.js.map +1 -0
  35. package/dist/Scope.d.ts +35 -0
  36. package/dist/Scope.d.ts.map +1 -0
  37. package/dist/Scope.js +38 -0
  38. package/dist/Scope.js.map +1 -0
  39. package/dist/Webhooks.d.ts +79 -0
  40. package/dist/Webhooks.d.ts.map +1 -0
  41. package/dist/Webhooks.js +107 -0
  42. package/dist/Webhooks.js.map +1 -0
  43. package/dist/admin/Access.d.ts +41 -0
  44. package/dist/admin/Access.d.ts.map +1 -0
  45. package/dist/admin/Access.js +75 -0
  46. package/dist/admin/Access.js.map +1 -0
  47. package/dist/admin/App.d.ts +1221 -0
  48. package/dist/admin/App.d.ts.map +1 -0
  49. package/dist/admin/App.js +113 -0
  50. package/dist/admin/App.js.map +1 -0
  51. package/dist/admin/apps/api-keys.d.ts +209 -0
  52. package/dist/admin/apps/api-keys.d.ts.map +1 -0
  53. package/dist/admin/apps/api-keys.js +129 -0
  54. package/dist/admin/apps/api-keys.js.map +1 -0
  55. package/dist/admin/apps/scopes.d.ts +46 -0
  56. package/dist/admin/apps/scopes.d.ts.map +1 -0
  57. package/dist/admin/apps/scopes.js +41 -0
  58. package/dist/admin/apps/scopes.js.map +1 -0
  59. package/dist/admin/apps/verified-tokens.d.ts +1206 -0
  60. package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
  61. package/dist/admin/apps/verified-tokens.js +485 -0
  62. package/dist/admin/apps/verified-tokens.js.map +1 -0
  63. package/dist/admin/index.d.ts +3 -0
  64. package/dist/admin/index.d.ts.map +1 -0
  65. package/dist/admin/index.js +3 -0
  66. package/dist/admin/index.js.map +1 -0
  67. package/dist/admin/ui.generated.d.ts +11 -0
  68. package/dist/admin/ui.generated.d.ts.map +1 -0
  69. package/dist/admin/ui.generated.js +43 -0
  70. package/dist/admin/ui.generated.js.map +1 -0
  71. package/dist/apps/activities.d.ts +18725 -0
  72. package/dist/apps/activities.d.ts.map +1 -0
  73. package/dist/apps/activities.js +3138 -0
  74. package/dist/apps/activities.js.map +1 -0
  75. package/dist/apps/addresses.d.ts +3662 -0
  76. package/dist/apps/addresses.d.ts.map +1 -0
  77. package/dist/apps/addresses.js +22 -0
  78. package/dist/apps/addresses.js.map +1 -0
  79. package/dist/apps/balances.d.ts +342 -0
  80. package/dist/apps/balances.d.ts.map +1 -0
  81. package/dist/apps/balances.js +393 -0
  82. package/dist/apps/balances.js.map +1 -0
  83. package/dist/apps/blocks.d.ts +931 -0
  84. package/dist/apps/blocks.d.ts.map +1 -0
  85. package/dist/apps/blocks.js +689 -0
  86. package/dist/apps/blocks.js.map +1 -0
  87. package/dist/apps/coingecko.d.ts +1053 -0
  88. package/dist/apps/coingecko.d.ts.map +1 -0
  89. package/dist/apps/coingecko.js +1015 -0
  90. package/dist/apps/coingecko.js.map +1 -0
  91. package/dist/apps/exchanges.d.ts +3000 -0
  92. package/dist/apps/exchanges.d.ts.map +1 -0
  93. package/dist/apps/exchanges.js +3570 -0
  94. package/dist/apps/exchanges.js.map +1 -0
  95. package/dist/apps/fee-amm.d.ts +640 -0
  96. package/dist/apps/fee-amm.d.ts.map +1 -0
  97. package/dist/apps/fee-amm.js +815 -0
  98. package/dist/apps/fee-amm.js.map +1 -0
  99. package/dist/apps/indexer.d.ts +116 -0
  100. package/dist/apps/indexer.d.ts.map +1 -0
  101. package/dist/apps/indexer.js +270 -0
  102. package/dist/apps/indexer.js.map +1 -0
  103. package/dist/apps/receipts.d.ts +1438 -0
  104. package/dist/apps/receipts.d.ts.map +1 -0
  105. package/dist/apps/receipts.js +874 -0
  106. package/dist/apps/receipts.js.map +1 -0
  107. package/dist/apps/rpc.d.ts +173 -0
  108. package/dist/apps/rpc.d.ts.map +1 -0
  109. package/dist/apps/rpc.js +191 -0
  110. package/dist/apps/rpc.js.map +1 -0
  111. package/dist/apps/tokenlist.d.ts +192 -0
  112. package/dist/apps/tokenlist.d.ts.map +1 -0
  113. package/dist/apps/tokenlist.js +153 -0
  114. package/dist/apps/tokenlist.js.map +1 -0
  115. package/dist/apps/tokens.d.ts +2246 -0
  116. package/dist/apps/tokens.d.ts.map +1 -0
  117. package/dist/apps/tokens.js +1789 -0
  118. package/dist/apps/tokens.js.map +1 -0
  119. package/dist/apps/transactions.d.ts +2327 -0
  120. package/dist/apps/transactions.d.ts.map +1 -0
  121. package/dist/apps/transactions.js +975 -0
  122. package/dist/apps/transactions.js.map +1 -0
  123. package/dist/apps/transfers.d.ts +589 -0
  124. package/dist/apps/transfers.d.ts.map +1 -0
  125. package/dist/apps/transfers.js +769 -0
  126. package/dist/apps/transfers.js.map +1 -0
  127. package/dist/apps/verified-tokens.d.ts +544 -0
  128. package/dist/apps/verified-tokens.d.ts.map +1 -0
  129. package/dist/apps/verified-tokens.js +234 -0
  130. package/dist/apps/verified-tokens.js.map +1 -0
  131. package/dist/apps/webhooks.d.ts +7684 -0
  132. package/dist/apps/webhooks.d.ts.map +1 -0
  133. package/dist/apps/webhooks.js +2112 -0
  134. package/dist/apps/webhooks.js.map +1 -0
  135. package/dist/cloudflare.d.ts +32 -0
  136. package/dist/cloudflare.d.ts.map +1 -0
  137. package/dist/cloudflare.js +45 -0
  138. package/dist/cloudflare.js.map +1 -0
  139. package/dist/index.d.ts +16 -0
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +16 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/internal/Auth.d.ts +298 -0
  144. package/dist/internal/Auth.d.ts.map +1 -0
  145. package/dist/internal/Auth.js +618 -0
  146. package/dist/internal/Auth.js.map +1 -0
  147. package/dist/internal/Cache.d.ts +74 -0
  148. package/dist/internal/Cache.d.ts.map +1 -0
  149. package/dist/internal/Cache.js +215 -0
  150. package/dist/internal/Cache.js.map +1 -0
  151. package/dist/internal/Cursor.d.ts +79 -0
  152. package/dist/internal/Cursor.d.ts.map +1 -0
  153. package/dist/internal/Cursor.js +116 -0
  154. package/dist/internal/Cursor.js.map +1 -0
  155. package/dist/internal/EdgeCache.d.ts +59 -0
  156. package/dist/internal/EdgeCache.d.ts.map +1 -0
  157. package/dist/internal/EdgeCache.js +174 -0
  158. package/dist/internal/EdgeCache.js.map +1 -0
  159. package/dist/internal/Log.d.ts +104 -0
  160. package/dist/internal/Log.d.ts.map +1 -0
  161. package/dist/internal/Log.js +118 -0
  162. package/dist/internal/Log.js.map +1 -0
  163. package/dist/internal/MetricSink.d.ts +65 -0
  164. package/dist/internal/MetricSink.d.ts.map +1 -0
  165. package/dist/internal/MetricSink.js +146 -0
  166. package/dist/internal/MetricSink.js.map +1 -0
  167. package/dist/internal/Mpp.d.ts +71 -0
  168. package/dist/internal/Mpp.d.ts.map +1 -0
  169. package/dist/internal/Mpp.js +105 -0
  170. package/dist/internal/Mpp.js.map +1 -0
  171. package/dist/internal/OpenApi.d.ts +313 -0
  172. package/dist/internal/OpenApi.d.ts.map +1 -0
  173. package/dist/internal/OpenApi.fonts.d.ts +2 -0
  174. package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
  175. package/dist/internal/OpenApi.fonts.js +39 -0
  176. package/dist/internal/OpenApi.fonts.js.map +1 -0
  177. package/dist/internal/OpenApi.js +335 -0
  178. package/dist/internal/OpenApi.js.map +1 -0
  179. package/dist/internal/OpenApi.logo.d.ts +6 -0
  180. package/dist/internal/OpenApi.logo.d.ts.map +1 -0
  181. package/dist/internal/OpenApi.logo.js +19 -0
  182. package/dist/internal/OpenApi.logo.js.map +1 -0
  183. package/dist/internal/Path.d.ts +5 -0
  184. package/dist/internal/Path.d.ts.map +1 -0
  185. package/dist/internal/Path.js +16 -0
  186. package/dist/internal/Path.js.map +1 -0
  187. package/dist/internal/RateLimit.d.ts +60 -0
  188. package/dist/internal/RateLimit.d.ts.map +1 -0
  189. package/dist/internal/RateLimit.js +48 -0
  190. package/dist/internal/RateLimit.js.map +1 -0
  191. package/dist/internal/Response.d.ts +132 -0
  192. package/dist/internal/Response.d.ts.map +1 -0
  193. package/dist/internal/Response.js +72 -0
  194. package/dist/internal/Response.js.map +1 -0
  195. package/dist/internal/Schema.d.ts +257 -0
  196. package/dist/internal/Schema.d.ts.map +1 -0
  197. package/dist/internal/Schema.js +360 -0
  198. package/dist/internal/Schema.js.map +1 -0
  199. package/dist/internal/Store.d.ts +204 -0
  200. package/dist/internal/Store.d.ts.map +1 -0
  201. package/dist/internal/Store.js +291 -0
  202. package/dist/internal/Store.js.map +1 -0
  203. package/dist/internal/Tidx.d.ts +120 -0
  204. package/dist/internal/Tidx.d.ts.map +1 -0
  205. package/dist/internal/Tidx.js +156 -0
  206. package/dist/internal/Tidx.js.map +1 -0
  207. package/dist/internal/Timing.d.ts +22 -0
  208. package/dist/internal/Timing.d.ts.map +1 -0
  209. package/dist/internal/Timing.js +32 -0
  210. package/dist/internal/Timing.js.map +1 -0
  211. package/dist/internal/Ttl.d.ts +25 -0
  212. package/dist/internal/Ttl.d.ts.map +1 -0
  213. package/dist/internal/Ttl.js +49 -0
  214. package/dist/internal/Ttl.js.map +1 -0
  215. package/dist/internal/Value.d.ts +47 -0
  216. package/dist/internal/Value.d.ts.map +1 -0
  217. package/dist/internal/Value.js +122 -0
  218. package/dist/internal/Value.js.map +1 -0
  219. package/dist/internal/VerifiedTokens.d.ts +228 -0
  220. package/dist/internal/VerifiedTokens.d.ts.map +1 -0
  221. package/dist/internal/VerifiedTokens.js +450 -0
  222. package/dist/internal/VerifiedTokens.js.map +1 -0
  223. package/dist/internal/Viem.d.ts +2082 -0
  224. package/dist/internal/Viem.d.ts.map +1 -0
  225. package/dist/internal/Viem.js +63 -0
  226. package/dist/internal/Viem.js.map +1 -0
  227. package/dist/internal/WebhookDestination.d.ts +198 -0
  228. package/dist/internal/WebhookDestination.d.ts.map +1 -0
  229. package/dist/internal/WebhookDestination.js +170 -0
  230. package/dist/internal/WebhookDestination.js.map +1 -0
  231. package/dist/internal/Webhooks.d.ts +433 -0
  232. package/dist/internal/Webhooks.d.ts.map +1 -0
  233. package/dist/internal/Webhooks.js +489 -0
  234. package/dist/internal/Webhooks.js.map +1 -0
  235. package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
  236. package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
  237. package/dist/internal/webhookDestinations/betterStack.js +76 -0
  238. package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
  239. package/dist/internal/webhookDestinations/slack.d.ts +59 -0
  240. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
  241. package/dist/internal/webhookDestinations/slack.js +422 -0
  242. package/dist/internal/webhookDestinations/slack.js.map +1 -0
  243. package/dist/internal/webhookDestinations/url.d.ts +17 -0
  244. package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
  245. package/dist/internal/webhookDestinations/url.js +130 -0
  246. package/dist/internal/webhookDestinations/url.js.map +1 -0
  247. package/dist/openrpc.json +5302 -0
  248. package/package.json +46 -4
  249. package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
  250. package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  251. package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  252. package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  253. package/seeds/31318/verified-tokens.json +30 -0
  254. package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  255. package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
  256. package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
  257. package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
  258. package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
  259. package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
  260. package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
  261. package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
  262. package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
  263. package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
  264. package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
  265. package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
  266. package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
  267. package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
  268. package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
  269. package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
  270. package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
  271. package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
  272. package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
  273. package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
  274. package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
  275. package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
  276. package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
  277. package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
  278. package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
  279. package/seeds/4217/verified-tokens.json +177 -0
  280. package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  281. package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  282. package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  283. package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  284. package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
  285. package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
  286. package/seeds/42431/verified-tokens.json +44 -0
  287. package/seeds/openrpc/README.md +27 -0
  288. package/seeds/openrpc/execution-apis.json +8989 -0
  289. package/src/ApiKey.test.ts +91 -0
  290. package/src/ApiKey.ts +122 -0
  291. package/src/ApiKeySource.test-d.ts +11 -0
  292. package/src/ApiKeySource.test.ts +295 -0
  293. package/src/ApiKeySource.ts +303 -0
  294. package/src/App.test-d.ts +38 -0
  295. package/src/App.test.ts +65927 -0
  296. package/src/App.ts +855 -0
  297. package/src/Assets.ts +58 -0
  298. package/src/Auth.ts +1 -0
  299. package/src/Cli.test.ts +81 -0
  300. package/src/Cli.ts +38 -0
  301. package/src/Client.test-d.ts +1438 -0
  302. package/src/Client.test.ts +134 -0
  303. package/src/Client.ts +45 -0
  304. package/src/Metrics.ts +154 -0
  305. package/src/Scope.ts +50 -0
  306. package/src/Webhooks.test.ts +49 -0
  307. package/src/Webhooks.ts +172 -0
  308. package/src/admin/Access.test.ts +130 -0
  309. package/src/admin/Access.ts +87 -0
  310. package/src/admin/App.test.ts +225 -0
  311. package/src/admin/App.ts +216 -0
  312. package/src/admin/apps/api-keys.test.ts +156 -0
  313. package/src/admin/apps/api-keys.ts +157 -0
  314. package/src/admin/apps/scopes.test.ts +52 -0
  315. package/src/admin/apps/scopes.ts +47 -0
  316. package/src/admin/apps/verified-tokens.test.ts +415 -0
  317. package/src/admin/apps/verified-tokens.ts +566 -0
  318. package/src/admin/index.ts +2 -0
  319. package/src/admin/ui/plugin.ts +135 -0
  320. package/src/admin/ui/src/components/Checkbox.tsx +22 -0
  321. package/src/admin/ui/src/components/Dialog.tsx +32 -0
  322. package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
  323. package/src/admin/ui/src/components/Page.tsx +53 -0
  324. package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
  325. package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
  326. package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
  327. package/src/admin/ui/src/lib/api-keys.ts +63 -0
  328. package/src/admin/ui/src/lib/client.ts +15 -0
  329. package/src/admin/ui/src/lib/config.ts +57 -0
  330. package/src/admin/ui/src/lib/query-client.ts +26 -0
  331. package/src/admin/ui/src/lib/scopes.ts +25 -0
  332. package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
  333. package/src/admin/ui/src/lib/wagmi.ts +18 -0
  334. package/src/admin/ui/src/routeTree.gen.ts +104 -0
  335. package/src/admin/ui/src/router.tsx +25 -0
  336. package/src/admin/ui/src/routes/__root.tsx +219 -0
  337. package/src/admin/ui/src/routes/api-keys.tsx +329 -0
  338. package/src/admin/ui/src/routes/index.tsx +7 -0
  339. package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
  340. package/src/admin/ui/src/styles.css +20 -0
  341. package/src/admin/ui/tsconfig.json +16 -0
  342. package/src/admin/ui/vite.config.ts +29 -0
  343. package/src/admin/ui.generated.ts +60 -0
  344. package/src/apps/activities.test.ts +1020 -0
  345. package/src/apps/activities.ts +3934 -0
  346. package/src/apps/addresses.ts +24 -0
  347. package/src/apps/balances.test.ts +274 -0
  348. package/src/apps/balances.ts +533 -0
  349. package/src/apps/blocks.test.ts +385 -0
  350. package/src/apps/blocks.ts +896 -0
  351. package/src/apps/coingecko.test.ts +218 -0
  352. package/src/apps/coingecko.ts +1383 -0
  353. package/src/apps/exchanges.test.ts +958 -0
  354. package/src/apps/exchanges.ts +4773 -0
  355. package/src/apps/fee-amm.test.ts +321 -0
  356. package/src/apps/fee-amm.ts +1121 -0
  357. package/src/apps/indexer.test.ts +153 -0
  358. package/src/apps/indexer.ts +330 -0
  359. package/src/apps/receipts.test.ts +285 -0
  360. package/src/apps/receipts.ts +1193 -0
  361. package/src/apps/rpc.test.ts +302 -0
  362. package/src/apps/rpc.ts +222 -0
  363. package/src/apps/tokenlist.test.ts +80 -0
  364. package/src/apps/tokenlist.ts +207 -0
  365. package/src/apps/tokens.test.ts +1062 -0
  366. package/src/apps/tokens.ts +2292 -0
  367. package/src/apps/transactions.test.ts +408 -0
  368. package/src/apps/transactions.ts +1363 -0
  369. package/src/apps/transfers.test.ts +371 -0
  370. package/src/apps/transfers.ts +1116 -0
  371. package/src/apps/verified-tokens.test.ts +114 -0
  372. package/src/apps/verified-tokens.ts +295 -0
  373. package/src/apps/webhooks.test.ts +1937 -0
  374. package/src/apps/webhooks.ts +2926 -0
  375. package/src/cloudflare.ts +55 -0
  376. package/src/index.ts +15 -0
  377. package/src/internal/Auth.test.ts +980 -0
  378. package/src/internal/Auth.ts +1012 -0
  379. package/src/internal/Cache.test.ts +240 -0
  380. package/src/internal/Cache.ts +262 -0
  381. package/src/internal/Cursor.test.ts +100 -0
  382. package/src/internal/Cursor.ts +149 -0
  383. package/src/internal/EdgeCache.test.ts +234 -0
  384. package/src/internal/EdgeCache.ts +224 -0
  385. package/src/internal/Errors.ts +14 -0
  386. package/src/internal/Log.test.ts +244 -0
  387. package/src/internal/Log.ts +216 -0
  388. package/src/internal/MetricSink.ts +220 -0
  389. package/src/internal/Mpp.test.ts +64 -0
  390. package/src/internal/Mpp.ts +141 -0
  391. package/src/internal/OpenApi.fonts.ts +38 -0
  392. package/src/internal/OpenApi.logo.ts +18 -0
  393. package/src/internal/OpenApi.ts +488 -0
  394. package/src/internal/Path.test.ts +14 -0
  395. package/src/internal/Path.ts +15 -0
  396. package/src/internal/RateLimit.test.ts +93 -0
  397. package/src/internal/RateLimit.ts +93 -0
  398. package/src/internal/Response.ts +131 -0
  399. package/src/internal/Schema.test.ts +34 -0
  400. package/src/internal/Schema.ts +523 -0
  401. package/src/internal/Store.test.ts +545 -0
  402. package/src/internal/Store.ts +526 -0
  403. package/src/internal/Tidx.local.test.ts +33 -0
  404. package/src/internal/Tidx.test.ts +294 -0
  405. package/src/internal/Tidx.ts +238 -0
  406. package/src/internal/Timing.test.ts +22 -0
  407. package/src/internal/Timing.ts +62 -0
  408. package/src/internal/Ttl.test.ts +18 -0
  409. package/src/internal/Ttl.ts +59 -0
  410. package/src/internal/Value.ts +114 -0
  411. package/src/internal/VerifiedTokens.test.ts +333 -0
  412. package/src/internal/VerifiedTokens.ts +645 -0
  413. package/src/internal/Viem.test.ts +115 -0
  414. package/src/internal/Viem.ts +165 -0
  415. package/src/internal/WebhookDestination.test.ts +648 -0
  416. package/src/internal/WebhookDestination.ts +331 -0
  417. package/src/internal/Webhooks.test.ts +668 -0
  418. package/src/internal/Webhooks.ts +940 -0
  419. package/src/internal/webhookDestinations/betterStack.ts +91 -0
  420. package/src/internal/webhookDestinations/slack.ts +496 -0
  421. package/src/internal/webhookDestinations/url.ts +128 -0
  422. package/src/openrpc.json +5302 -0
@@ -0,0 +1,244 @@
1
+ import { Hono } from 'hono'
2
+ import { requestId } from 'hono/request-id'
3
+
4
+ import * as Log from './Log.js'
5
+ import * as Response from './Response.js'
6
+ import * as Timing from './Timing.js'
7
+
8
+ function app(options: { emit: Log.Emit }) {
9
+ const app = new Hono<Log.Environment>()
10
+ app.use('*', requestId())
11
+ app.use('*', Log.middleware(options))
12
+ app.use('*', Timing.middleware())
13
+ return app
14
+ }
15
+
16
+ function stable(entry: Log.Entry) {
17
+ const { duration: _duration, requestId: _requestId, timings: _timings, ...rest } = entry
18
+ return rest
19
+ }
20
+
21
+ describe('middleware', () => {
22
+ test('emits one canonical entry per request', async () => {
23
+ const entries: Log.Entry[] = []
24
+ const api = app({ emit: (entry) => void entries.push(entry) })
25
+ api.get('/things/:id', async (c) => {
26
+ await Timing.time(c, 'rpc.example', () => Promise.resolve())
27
+ return c.json({ ok: true }, 200)
28
+ })
29
+
30
+ await api.request('/things/42?chainId=4217')
31
+
32
+ expect(entries.length).toMatchInlineSnapshot(`1`)
33
+ const entry = entries[0]!
34
+ expect(typeof entry.duration).toMatchInlineSnapshot(`"number"`)
35
+ expect(typeof entry.requestId).toMatchInlineSnapshot(`"string"`)
36
+ expect(Object.keys(entry.timings ?? {})).toMatchInlineSnapshot(`
37
+ [
38
+ "rpc.example",
39
+ ]
40
+ `)
41
+ expect(stable(entry)).toMatchInlineSnapshot(`
42
+ {
43
+ "level": "info",
44
+ "method": "GET",
45
+ "path": "/things/42",
46
+ "query": "chainId=4217",
47
+ "route": "/things/:id",
48
+ "status": 200,
49
+ }
50
+ `)
51
+ })
52
+
53
+ test('includes the caller principal, never the raw token', async () => {
54
+ const entries: Log.Entry[] = []
55
+ const api = app({ emit: (entry) => void entries.push(entry) })
56
+ api.use('*', async (c, next) => {
57
+ c.set('principal', {
58
+ apiKey: { environment: 'production', id: 'key_1', orgId: 'org_1', scopes: ['data:read'] },
59
+ environment: 'production',
60
+ id: 'key_1',
61
+ orgId: 'org_1',
62
+ type: 'api_key',
63
+ })
64
+ await next()
65
+ })
66
+ api.get('/secure', (c) => c.json({ ok: true }, 200))
67
+
68
+ const token = `tempo:sk:${'a'.repeat(48)}`
69
+ await api.request(`/secure?apiKey=${token}`)
70
+
71
+ expect(stable(entries[0]!)).toMatchInlineSnapshot(`
72
+ {
73
+ "level": "info",
74
+ "method": "GET",
75
+ "path": "/secure",
76
+ "principal": {
77
+ "environment": "production",
78
+ "id": "key_1",
79
+ "orgId": "org_1",
80
+ "type": "api_key",
81
+ },
82
+ "query": "apiKey=tempo:sk:…",
83
+ "route": "/secure",
84
+ "status": 200,
85
+ }
86
+ `)
87
+ })
88
+
89
+ test('includes the MPP payment reason for paid calls', async () => {
90
+ const entries: Log.Entry[] = []
91
+ const api = app({ emit: (entry) => void entries.push(entry) })
92
+ api.use('*', async (c, next) => {
93
+ c.set('principal', {
94
+ id: 'ip:203.0.113.7',
95
+ payment: { payer: '0xpayer', reason: 'public_over_quota', type: 'mpp' },
96
+ type: 'public',
97
+ })
98
+ await next()
99
+ })
100
+ api.get('/paid', (c) => c.json({ ok: true }, 200))
101
+
102
+ await api.request('/paid')
103
+
104
+ expect(stable(entries[0]!)).toMatchInlineSnapshot(`
105
+ {
106
+ "level": "info",
107
+ "method": "GET",
108
+ "path": "/paid",
109
+ "payment": "public_over_quota",
110
+ "principal": {
111
+ "id": "ip:203.0.113.7",
112
+ "type": "public",
113
+ },
114
+ "route": "/paid",
115
+ "status": 200,
116
+ }
117
+ `)
118
+ })
119
+
120
+ test('records the error code and warn level for error envelopes', async () => {
121
+ const entries: Log.Entry[] = []
122
+ const api = app({ emit: (entry) => void entries.push(entry) })
123
+ api.get('/missing', (c) =>
124
+ Response.error(c, { code: 'not_found', message: 'Route not found', status: 404 }),
125
+ )
126
+
127
+ await api.request('/missing')
128
+
129
+ expect(stable(entries[0]!)).toMatchInlineSnapshot(`
130
+ {
131
+ "errorCode": "not_found",
132
+ "level": "warn",
133
+ "method": "GET",
134
+ "path": "/missing",
135
+ "route": "/missing",
136
+ "status": 404,
137
+ }
138
+ `)
139
+ })
140
+
141
+ test('logs thrown errors at error level', async () => {
142
+ const entries: Log.Entry[] = []
143
+ const api = app({ emit: (entry) => void entries.push(entry) })
144
+ api.onError((_, c) => c.json({ error: true }, 500))
145
+ api.get('/boom', () => {
146
+ throw new Error('boom')
147
+ })
148
+
149
+ await api.request('/boom')
150
+
151
+ expect(stable(entries[0]!)).toMatchInlineSnapshot(`
152
+ {
153
+ "level": "error",
154
+ "method": "GET",
155
+ "path": "/boom",
156
+ "route": "/boom",
157
+ "status": 500,
158
+ }
159
+ `)
160
+ })
161
+
162
+ test('parses rate limit state from response headers', async () => {
163
+ const entries: Log.Entry[] = []
164
+ const api = app({ emit: (entry) => void entries.push(entry) })
165
+ api.get('/limited', (c) => {
166
+ c.header('RateLimit-Limit', '100')
167
+ c.header('RateLimit-Remaining', '37')
168
+ c.header('RateLimit-Reset', '1710000000')
169
+ c.header('RateLimit-Scope', 'data:read')
170
+ return c.json({ ok: true }, 200)
171
+ })
172
+
173
+ await api.request('/limited')
174
+
175
+ expect(stable(entries[0]!)).toMatchInlineSnapshot(`
176
+ {
177
+ "level": "info",
178
+ "method": "GET",
179
+ "path": "/limited",
180
+ "rateLimit": {
181
+ "limit": 100,
182
+ "remaining": 37,
183
+ "reset": 1710000000,
184
+ "scope": "data:read",
185
+ },
186
+ "route": "/limited",
187
+ "status": 200,
188
+ }
189
+ `)
190
+ })
191
+
192
+ test('marks edge-cache hits', async () => {
193
+ const entries: Log.Entry[] = []
194
+ const api = app({ emit: (entry) => void entries.push(entry) })
195
+ // Simulate the edge-cache layer answering before routing runs.
196
+ api.use('*', async (c) => {
197
+ c.set('edgeCacheStatus', 'hit')
198
+ return c.json({ cached: true }, 200)
199
+ })
200
+
201
+ await api.request('/anything')
202
+
203
+ expect(stable(entries[0]!)).toMatchInlineSnapshot(`
204
+ {
205
+ "cache": "hit",
206
+ "level": "info",
207
+ "method": "GET",
208
+ "path": "/anything",
209
+ "route": "/*",
210
+ "status": 200,
211
+ }
212
+ `)
213
+ })
214
+ })
215
+
216
+ describe('emit', () => {
217
+ test('routes entries to the level-matched console method', () => {
218
+ const error = vi.spyOn(console, 'error').mockImplementation(() => {})
219
+ const log = vi.spyOn(console, 'log').mockImplementation(() => {})
220
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
221
+ try {
222
+ const base = {
223
+ duration: 1,
224
+ method: 'GET',
225
+ path: '/',
226
+ requestId: 'req',
227
+ route: '/',
228
+ } as const
229
+
230
+ Log.emit({ ...base, level: 'info', status: 200 })
231
+ Log.emit({ ...base, level: 'warn', status: 404 })
232
+ Log.emit({ ...base, level: 'error', status: 500 })
233
+
234
+ expect(log.mock.calls.length).toMatchInlineSnapshot(`1`)
235
+ expect(warn.mock.calls.length).toMatchInlineSnapshot(`1`)
236
+ expect(error.mock.calls.length).toMatchInlineSnapshot(`1`)
237
+ expect(log.mock.calls[0]?.[0]).toMatchObject({ level: 'info', status: 200 })
238
+ } finally {
239
+ error.mockRestore()
240
+ log.mockRestore()
241
+ warn.mockRestore()
242
+ }
243
+ })
244
+ })
@@ -0,0 +1,216 @@
1
+ import type { MiddlewareHandler } from 'hono'
2
+ import type { RequestIdVariables } from 'hono/request-id'
3
+
4
+ import * as ApiKey from '../ApiKey.js'
5
+ import type * as Auth from './Auth.js'
6
+ import type * as EdgeCache from './EdgeCache.js'
7
+ import type * as Timing from './Timing.js'
8
+
9
+ /** Hono variables read by the request logger and written by other layers. */
10
+ export type Variables = {
11
+ /** Stable error code recorded by `Response.error` for the request log entry. */
12
+ errorCode?: string | undefined
13
+ }
14
+
15
+ /** Hono environment shape used by the request logger. */
16
+ export type Environment = {
17
+ Variables: Variables &
18
+ EdgeCache.Variables &
19
+ RequestIdVariables &
20
+ Timing.Variables & {
21
+ /** Authenticated or anonymous caller, set by auth middleware. */
22
+ principal?: Auth.Principal | undefined
23
+ }
24
+ }
25
+
26
+ /** Log level, derived from the response status. */
27
+ export type Level = 'error' | 'info' | 'warn'
28
+
29
+ /** One canonical request log entry. */
30
+ export type Entry = {
31
+ /** `'hit'` when the edge response cache served the request without reaching the origin handlers. */
32
+ cache?: 'hit' | undefined
33
+ /** Total request duration in milliseconds. */
34
+ duration: number
35
+ /** Stable error code when the response is a JSON error envelope (e.g. `not_found`, `api_key_invalid`). */
36
+ errorCode?: string | undefined
37
+ /** Log level derived from the response status: 5xx `error`, 4xx `warn`, else `info`. */
38
+ level: Level
39
+ /** Request method. */
40
+ method: string
41
+ /** Request pathname. */
42
+ path: string
43
+ /** MPP payment reason when the request was paid per call. */
44
+ payment?: Auth.Payment['reason'] | undefined
45
+ /** Caller identity. Raw API-key tokens are never logged. */
46
+ principal?:
47
+ | {
48
+ /** Key environment for API-key callers. */
49
+ environment?: ApiKey.ApiKey['environment'] | undefined
50
+ /** Principal id: the API key id, or the public quota identity. */
51
+ id: string
52
+ /** Owning organization id for API-key callers. */
53
+ orgId?: string | undefined
54
+ /** Principal kind. */
55
+ type: 'api_key' | 'public'
56
+ }
57
+ | undefined
58
+ /** Raw query string (no leading `?`) with API-key tokens redacted, when present. */
59
+ query?: string | undefined
60
+ /** Rate limit state from the `RateLimit-*` response headers, when enforced. */
61
+ rateLimit?:
62
+ | {
63
+ /** Quota for the current window. */
64
+ limit: number
65
+ /** Requests remaining in the current window. */
66
+ remaining: number
67
+ /** Reset timestamp in Unix seconds. */
68
+ reset?: number | undefined
69
+ /** Quota bucket scope, when available. */
70
+ scope?: string | undefined
71
+ }
72
+ | undefined
73
+ /** Request id, also echoed in the `tempo-request-id` response header. */
74
+ requestId: string
75
+ /** Matched handler path (bounded cardinality), e.g. `/v1/tokens/:address`; `/*` when a middleware (e.g. an edge-cache hit) short-circuited routing. */
76
+ route: string
77
+ /** Response status code. */
78
+ status: number
79
+ /** Upstream operation durations in milliseconds, from the request's Server-Timing metrics (e.g. RPC and TIDX calls). */
80
+ timings?: Record<string, number> | undefined
81
+ }
82
+
83
+ /** Sink receiving one {@link Entry} per request. */
84
+ export type Emit = (entry: Entry) => Promise<void> | void
85
+
86
+ /**
87
+ * Emits one canonical structured log entry per request, after the response is
88
+ * written. Register after `requestId()` and before the timing and edge-cache
89
+ * layers so the entry covers edge-cache hits and includes the Server-Timing
90
+ * metrics collected inside the request.
91
+ *
92
+ * The entry derives everything from state other layers already produce —
93
+ * `requestId`, the auth principal, `RateLimit-*` headers, Server-Timing
94
+ * metrics, the `errorCode` recorded by `Response.error`, and the edge-cache
95
+ * hit marker — so handlers never log per-request lines themselves.
96
+ */
97
+ export function middleware<environment extends Environment = Environment>(
98
+ options: middleware.Options = {},
99
+ ): MiddlewareHandler<environment> {
100
+ const fn = options.emit ?? emit
101
+ return async (c, next) => {
102
+ const start = performance.now()
103
+ await next()
104
+ const duration = performance.now() - start
105
+
106
+ const url = new URL(c.req.url)
107
+ const status = c.res.status
108
+ // Hono cannot resolve variable types through the generic `environment`,
109
+ // so shared-variable reads are cast from the environment contract above
110
+ // (the same pattern EdgeCache uses for its cross-layer reads).
111
+ const principal = c.get('principal' as never) as Auth.Principal | undefined
112
+ const errorCode = c.get('errorCode' as never) as string | undefined
113
+
114
+ // The `request` total metric duplicates `duration` (measured here, one
115
+ // layer further out), so only operation timings are kept.
116
+ const metrics = (c.get('serverTiming' as never) as Timing.Metric[] | undefined)?.filter(
117
+ (metric) => metric.name !== 'request',
118
+ )
119
+ const timings = metrics?.length
120
+ ? Object.fromEntries(metrics.map((metric) => [metric.name, round(metric.duration)]))
121
+ : undefined
122
+
123
+ const rateLimit = (() => {
124
+ const limit = c.res.headers.get('RateLimit-Limit')
125
+ const remaining = c.res.headers.get('RateLimit-Remaining')
126
+ const reset = c.res.headers.get('RateLimit-Reset')
127
+ const scope = c.res.headers.get('RateLimit-Scope')
128
+ if (limit === null || remaining === null) return undefined
129
+ return {
130
+ limit: Number(limit),
131
+ remaining: Number(remaining),
132
+ ...(reset === null ? {} : { reset: Number(reset) }),
133
+ ...(scope === null ? {} : { scope }),
134
+ }
135
+ })()
136
+
137
+ const result = fn({
138
+ ...(c.get('edgeCacheStatus' as never) === 'hit' ? { cache: 'hit' as const } : {}),
139
+ duration: round(duration),
140
+ ...(errorCode === undefined ? {} : { errorCode }),
141
+ level: status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info',
142
+ method: c.req.method,
143
+ path: url.pathname,
144
+ ...(principal?.payment === undefined ? {} : { payment: principal.payment.reason }),
145
+ ...(principal === undefined
146
+ ? {}
147
+ : {
148
+ principal: {
149
+ ...(principal.type === 'api_key' ? { environment: principal.environment } : {}),
150
+ id: principal.id,
151
+ ...(principal.type === 'api_key' ? { orgId: principal.orgId } : {}),
152
+ type: principal.type,
153
+ },
154
+ }),
155
+ // Keys belong in headers, but a confused client may pass one in the
156
+ // query string; redact defensively before it reaches a log store.
157
+ ...(url.search.length > 1 ? { query: ApiKey.redact(url.search.slice(1)) } : {}),
158
+ ...(rateLimit === undefined ? {} : { rateLimit }),
159
+ requestId: c.get('requestId' as never) as string,
160
+ route: c.req.routePath,
161
+ status,
162
+ ...(timings === undefined ? {} : { timings }),
163
+ })
164
+
165
+ if (result instanceof Promise) {
166
+ const promise = result.catch((error) => console.error('request log sink failed', error))
167
+ // Unit tests and non-Worker runtimes do not always provide an execution
168
+ // context. In Workers, waitUntil keeps async sinks alive without holding
169
+ // the response open; elsewhere the promise is deliberately fire-and-forget.
170
+ try {
171
+ c.executionCtx.waitUntil(promise)
172
+ } catch {
173
+ void promise
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ export declare namespace middleware {
180
+ /** Options for the request logger middleware. */
181
+ type Options = {
182
+ /** Log sink. Defaults to {@link emit}: the level-matched console method, passed the entry as a structured object. */
183
+ emit?: Emit | undefined
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Default log sink: writes the entry as a structured object through the
189
+ * console method matching its level. Passing the object (not a pre-serialized
190
+ * JSON string) lets Cloudflare Workers Logs index the fields for filtering
191
+ * and querying; Node prints the same object inline on stdout/stderr.
192
+ */
193
+ export function emit(entry: Entry) {
194
+ if (entry.level === 'error') console.error(entry)
195
+ else if (entry.level === 'warn') console.warn(entry)
196
+ else console.log(entry)
197
+ }
198
+
199
+ /** Severity ranking used to filter entries below a minimum level. */
200
+ const severity = { error: 2, info: 0, warn: 1 } as const
201
+
202
+ /**
203
+ * An {@link Emit} that drops entries below `min` and otherwise delegates to
204
+ * {@link emit}. `'warn'` keeps warn/error (e.g. production, where the request
205
+ * metrics sink carries successful-request volume instead of one info log each).
206
+ */
207
+ export function withMinLevel(min: Level): Emit {
208
+ return (entry) => {
209
+ if (severity[entry.level] < severity[min]) return
210
+ emit(entry)
211
+ }
212
+ }
213
+
214
+ function round(value: number) {
215
+ return Math.round(value * 10) / 10
216
+ }
@@ -0,0 +1,220 @@
1
+ import type { Metrics } from '../Metrics.js'
2
+ import type * as Webhooks from '../Webhooks.js'
3
+ import type * as Log from './Log.js'
4
+
5
+ /** Webhook event type, including the synthetic `ping` carried on test envelopes. */
6
+ export type EventType = Webhooks.Envelope['type']
7
+
8
+ /** Where a delivery attempt originated. */
9
+ export type Trigger = 'manual_retry' | 'ping' | 'queue'
10
+
11
+ /** Context for one poller tick that the {@link Webhooks.createPoller.Result} omits. */
12
+ export type TickMeta = {
13
+ /** Chain polled this tick; used when the tick errored before a result existed. */
14
+ chainId?: number | undefined
15
+ /** Wall-clock duration of the tick in ms. */
16
+ durationMs: number
17
+ /** Whether the tick completed. */
18
+ outcome: 'error' | 'ok'
19
+ }
20
+
21
+ /**
22
+ * A webhook metric event, recorded by the {@link webhooks} sink. The poller
23
+ * emits `webhook:tick`/`webhook:enqueue` and the delivery path emits
24
+ * `webhook:delivery`.
25
+ */
26
+ export type WebhookEvent =
27
+ | {
28
+ meta: TickMeta
29
+ result: Webhooks.createPoller.Result | undefined
30
+ type: 'webhook:tick'
31
+ }
32
+ | {
33
+ chainId: number
34
+ durationMs: number
35
+ eventType: EventType
36
+ ok: boolean
37
+ type: 'webhook:enqueue'
38
+ }
39
+ | {
40
+ envelope: Webhooks.Envelope
41
+ result: Webhooks.Result
42
+ subscription: Webhooks.Subscription
43
+ trigger: Trigger
44
+ type: 'webhook:delivery'
45
+ }
46
+
47
+ /** Records request (HTTP) metrics for one request-log entry. Built by {@link requests}. */
48
+ export type RequestSink = (entry: Log.Entry) => void
49
+
50
+ /** Records webhook reliability metrics. Built by {@link webhooks}. */
51
+ export type WebhookSink = {
52
+ /** Ships buffered metrics (call once per poller tick / delivery). */
53
+ flush(): void
54
+ /** Records one webhook event. */
55
+ record(event: WebhookEvent): void
56
+ }
57
+
58
+ /** Low-cardinality tags shared by every HTTP metric, derived from a request log entry. */
59
+ type RouteTags = {
60
+ cache: 'hit' | 'miss'
61
+ method: string
62
+ principal_environment: 'none' | 'production' | 'sandbox'
63
+ principal_type: 'api_key' | 'public' | 'unknown'
64
+ route: string
65
+ }
66
+
67
+ /**
68
+ * Builds a request (HTTP) metrics sink over `backend`: emits the bounded Worker
69
+ * metrics derived from one request-log entry, then flushes (one entry == one
70
+ * request).
71
+ *
72
+ * Metric tags deliberately exclude caller ids, organization ids, request ids,
73
+ * raw paths, and IP-derived identities — those belong in logs or analytics, not
74
+ * StatsD-style series.
75
+ */
76
+ export function requests(backend: Metrics): RequestSink {
77
+ return (entry) => {
78
+ const tags = routeTags(entry)
79
+
80
+ backend.count('http_response_count', 1, {
81
+ ...tags,
82
+ ...(entry.errorCode === undefined ? {} : { error_code: entry.errorCode }),
83
+ ...(entry.payment === undefined ? {} : { payment: entry.payment }),
84
+ status: entry.status,
85
+ })
86
+ backend.histogram('http_response_duration_ms', entry.duration, tags)
87
+
88
+ if (entry.status === 429 || entry.errorCode === 'rate_limit_exceeded')
89
+ backend.count('http_rate_limit_count', 1, {
90
+ ...tags,
91
+ rate_limit_scope: entry.rateLimit?.scope ?? 'unknown',
92
+ status: entry.status,
93
+ })
94
+
95
+ for (const [operation, duration] of Object.entries(entry.timings ?? {}))
96
+ backend.histogram('upstream_duration_ms', duration, { ...tags, operation })
97
+
98
+ backend.flush()
99
+ }
100
+ }
101
+
102
+ function routeTags(entry: Log.Entry): RouteTags {
103
+ return {
104
+ cache: entry.cache ?? 'miss',
105
+ method: entry.method,
106
+ principal_environment: entry.principal?.environment ?? 'none',
107
+ principal_type: entry.principal?.type ?? 'unknown',
108
+ route: entry.route,
109
+ }
110
+ }
111
+
112
+ /** Bounded attempt-count buckets so the attempt tag never explodes cardinality. */
113
+ type AttemptBucket = '1' | '2' | '3-5' | '6-10' | 'gt10'
114
+
115
+ /** Low-cardinality delivery destination kind, tagged on `webhook:delivery`. */
116
+ type DestinationType = Webhooks.Subscription['destination']['type']
117
+
118
+ /** Low-cardinality delivery outcome class derived from an HTTP status / error. */
119
+ type StatusClass = '2xx' | '4xx' | '5xx' | 'network' | 'timeout' | 'unknown' | 'url_rejected'
120
+
121
+ /**
122
+ * Builds a webhook delivery-reliability sink over `backend`. Tags stay
123
+ * low-cardinality (`chain_id`, `event_type`, `destination_type`, `status_class`,
124
+ * `attempt_bucket`, `outcome`) — never subscription/org/event ids, which belong
125
+ * in logs or analytics, not StatsD-style series.
126
+ */
127
+ export function webhooks(backend: Metrics): WebhookSink {
128
+ return {
129
+ flush() {
130
+ backend.flush()
131
+ },
132
+ record(event) {
133
+ switch (event.type) {
134
+ case 'webhook:tick': {
135
+ const { meta, result } = event
136
+ const chain_id = String(result?.chainId ?? meta.chainId ?? 'unknown')
137
+ backend.count('webhook_poller_tick_count', 1, { chain_id, outcome: meta.outcome })
138
+ backend.histogram('webhook_poller_tick_duration_ms', meta.durationMs, {
139
+ chain_id,
140
+ outcome: meta.outcome,
141
+ })
142
+ if (!result) return
143
+ backend.histogram('webhook_poller_subscription_count', result.subscriptions, { chain_id })
144
+ backend.count('webhook_poller_scanned_count', result.scanned, { chain_id })
145
+ backend.gauge('webhook_poller_safe_block', result.safeBlock, { chain_id })
146
+ backend.count('webhook_poller_dispatch_count', result.dispatched, {
147
+ chain_id,
148
+ outcome: 'dispatched',
149
+ })
150
+ backend.count('webhook_poller_dispatch_count', result.failed, {
151
+ chain_id,
152
+ outcome: 'failed',
153
+ })
154
+ return
155
+ }
156
+ case 'webhook:enqueue': {
157
+ const tags = { chain_id: String(event.chainId), event_type: event.eventType }
158
+ const outcome = event.ok ? 'ok' : 'error'
159
+ backend.count('webhook_dispatch_enqueue_count', 1, { ...tags, outcome })
160
+ backend.histogram('webhook_dispatch_enqueue_duration_ms', event.durationMs, {
161
+ ...tags,
162
+ outcome,
163
+ })
164
+ return
165
+ }
166
+ case 'webhook:delivery': {
167
+ const { envelope, result, subscription } = event
168
+ const tags = {
169
+ chain_id: String(envelope.chainId),
170
+ destination_type: subscription.destination.type satisfies DestinationType,
171
+ event_type: envelope.type,
172
+ }
173
+ const outcome = result.ok ? 'succeeded' : 'failed'
174
+ backend.count('webhook_delivery_attempt_count', 1, {
175
+ ...tags,
176
+ attempt_bucket: attemptBucket(subscription.failureCount + 1),
177
+ outcome,
178
+ status_class: statusClass(result),
179
+ trigger: event.trigger,
180
+ })
181
+ if (result.durationMs !== undefined)
182
+ backend.histogram('webhook_delivery_duration_ms', result.durationMs, {
183
+ ...tags,
184
+ outcome,
185
+ })
186
+ backend.histogram(
187
+ 'webhook_delivery_queue_latency_ms',
188
+ Math.max(0, Date.now() - Date.parse(envelope.createdAt)),
189
+ tags,
190
+ )
191
+ return
192
+ }
193
+ }
194
+ },
195
+ }
196
+ }
197
+
198
+ /** Buckets a 1-based attempt number into a bounded set of attempt labels. */
199
+ function attemptBucket(attempt: number): AttemptBucket {
200
+ if (attempt <= 1) return '1'
201
+ if (attempt === 2) return '2'
202
+ if (attempt <= 5) return '3-5'
203
+ if (attempt <= 10) return '6-10'
204
+ return 'gt10'
205
+ }
206
+
207
+ /** Maps a delivery result to a low-cardinality status class. */
208
+ function statusClass(result: Webhooks.Result): StatusClass {
209
+ if (result.ok) return '2xx'
210
+ const { error, status } = result
211
+ if (status !== undefined) {
212
+ if (status >= 300 && status < 400) return 'url_rejected'
213
+ if (status >= 400 && status < 500) return '4xx'
214
+ if (status >= 500) return '5xx'
215
+ }
216
+ if (error === undefined) return 'unknown'
217
+ if (/redirect/i.test(error)) return 'url_rejected'
218
+ if (/timeout|abort/i.test(error)) return 'timeout'
219
+ return 'network'
220
+ }