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,940 @@
1
+ import { nanoid } from 'nanoid'
2
+ import { Hash, Hex } from 'ox'
3
+
4
+ import type * as app_Webhooks from '../apps/webhooks.js'
5
+ import type * as Metrics from '../Metrics.js'
6
+ import * as MetricSink from './MetricSink.js'
7
+ import * as Store from './Store.js'
8
+ import * as WebhookDestination from './WebhookDestination.js'
9
+
10
+ // The destination layer lives in `./WebhookDestination.js`. Re-export only the
11
+ // pieces that belong on the public `Webhooks.*` surface: the `Destination` and
12
+ // `Result` types, the `InvalidUrlError` thrown by subscription writes, and the
13
+ // signature-verification API a consumer uses to authenticate a delivery
14
+ // (`verify` + `signatureHeader`, with `sign` as its pair). The transport-specific
15
+ // validators/log helpers stay internal to `WebhookDestination`.
16
+ export type { Destination, Result } from './WebhookDestination.js'
17
+ export { InvalidUrlError, sign, signatureHeader, verify } from './WebhookDestination.js'
18
+
19
+ /**
20
+ * Event types a subscription can listen to. The canonical set lives in the apps
21
+ * layer ({@link app_Webhooks.eventTypes}), co-located with the per-type schemas
22
+ * and descriptions that reference apps-layer read schemas; this storage layer
23
+ * only consumes the type.
24
+ */
25
+ export type EventType = app_Webhooks.EventType
26
+
27
+ /** Lifecycle status of a subscription. */
28
+ export type Status = 'active' | 'disabled' | 'paused'
29
+
30
+ /**
31
+ * Authenticated owner of a subscription. Derived from the request principal:
32
+ * API keys are org-scoped; MPP payers are identified by their stable DID.
33
+ */
34
+ export type Owner =
35
+ | {
36
+ /** Owning organization id (any key in the org manages its webhooks). */
37
+ orgId: string
38
+ /** Owner kind. */
39
+ type: 'api_key'
40
+ }
41
+ | {
42
+ /** Verified MPP payer identifier (e.g. `did:pkh:…`). */
43
+ payer: string
44
+ /** Owner kind. */
45
+ type: 'mpp'
46
+ }
47
+
48
+ /**
49
+ * Optional human context describing what a subscription is for. Carried onto
50
+ * every delivered {@link Envelope} and surfaced by destinations (Slack header +
51
+ * description, Better Stack log fields, and the raw `url` payload).
52
+ */
53
+ export type Context = {
54
+ /** Longer description of the subscription's purpose. */
55
+ description?: string | undefined
56
+ /** Arbitrary key/value labels echoed on every delivery and rendered by destinations. */
57
+ metadata?: Record<string, string> | undefined
58
+ /** Short label for the subscription (e.g. `Prod USDC large transfers`). */
59
+ title?: string | undefined
60
+ }
61
+
62
+ /** A durable webhook subscription record. */
63
+ export type Subscription = {
64
+ /** Chain the subscription listens on. */
65
+ chainId: number
66
+ /** Optional human context describing what the subscription is for. */
67
+ context?: Context | undefined
68
+ /** ISO timestamp of creation. */
69
+ createdAt: string
70
+ /** Where matched events are delivered (URL or Slack channel). */
71
+ destination: WebhookDestination.Destination
72
+ /** Event type the subscription listens to. */
73
+ eventType: EventType
74
+ /** ISO expiry timestamp; set for MPP-owned subscriptions (TTL-bounded). */
75
+ expiresAt?: string | undefined
76
+ /** Consecutive delivery failures; drives auto-disable. */
77
+ failureCount: number
78
+ /** Event-type-specific filter predicates. */
79
+ filters: Record<string, unknown>
80
+ /** Subscription id (`wh_…`). */
81
+ id: string
82
+ /** ISO timestamp of the last successful delivery, if any. */
83
+ lastDeliveryAt?: string | undefined
84
+ /** Authenticated owner. */
85
+ owner: Owner
86
+ /** HMAC signing secret. Returned once at creation; never exposed by reads. */
87
+ secret: string
88
+ /** Lifecycle status. */
89
+ status: Status
90
+ /** ISO timestamp of the last mutation. */
91
+ updatedAt: string
92
+ }
93
+
94
+ /** Fields accepted when creating a subscription. */
95
+ export type CreateInput = {
96
+ /** Chain to listen on. */
97
+ chainId: number
98
+ /** Optional human context describing what the subscription is for. */
99
+ context?: Context | undefined
100
+ /** Where matched events are delivered. `url` destinations are SSRF-validated. */
101
+ destination: WebhookDestination.Destination
102
+ /** Event type to listen to. */
103
+ eventType: EventType
104
+ /** Event-type-specific filter predicates. */
105
+ filters?: Record<string, unknown> | undefined
106
+ /** Owner derived from the request principal. */
107
+ owner: Owner
108
+ /** Time-to-live in milliseconds; set the subscription to expire (MPP TTL). */
109
+ ttl?: number | undefined
110
+ }
111
+
112
+ /** Mutable fields accepted when patching a subscription. */
113
+ export type PatchInput = {
114
+ /** New human context, or `null` to clear it. */
115
+ context?: Context | null | undefined
116
+ /** New delivery destination. `url` destinations are re-validated for SSRF. */
117
+ destination?: WebhookDestination.Destination | undefined
118
+ /** New filter predicates. */
119
+ filters?: Record<string, unknown> | undefined
120
+ /** New lifecycle status (`active`/`paused`/`disabled`). */
121
+ status?: Status | undefined
122
+ }
123
+
124
+ /** Outcome status of a delivery-log row. */
125
+ export type DeliveryStatus = 'failed' | 'pending' | 'succeeded'
126
+
127
+ /**
128
+ * A durable delivery-log row recording one delivery attempt. Persisted by
129
+ * {@link deliverAndRecord} so deliveries are observable and replayable (Phase
130
+ * C); the read endpoint returns these verbatim.
131
+ */
132
+ export type Delivery = {
133
+ /** Consecutive attempt number (1-based) at the time of this delivery. */
134
+ attempt: number
135
+ /** ISO timestamp the attempt was recorded. */
136
+ createdAt: string
137
+ /**
138
+ * The exact envelope that was sent, persisted so the delivery can be replayed
139
+ * verbatim (manual retry). Internal-only: the public delivery row omits it.
140
+ */
141
+ envelope: Envelope
142
+ /** Failure reason when `status` is `failed`. */
143
+ error?: string | undefined
144
+ /** Idempotent event id this delivery carried (`evt_…`). */
145
+ eventId: string
146
+ /** Delivery id (`whd_…`); time-sortable so keys order chronologically. */
147
+ id: string
148
+ /** Subscriber callback URL the attempt targeted. */
149
+ requestUrl: string
150
+ /** Wall-clock duration of the attempt in ms, when a request was made. */
151
+ responseMs?: number | undefined
152
+ /** HTTP response status, when a response was received. */
153
+ responseStatus?: number | undefined
154
+ /** Outcome status. */
155
+ status: DeliveryStatus
156
+ /** Owning subscription id. */
157
+ subscriptionId: string
158
+ }
159
+
160
+ /**
161
+ * Creates a subscription, validating its URL and (optionally) enforcing a
162
+ * per-owner cap, then persists it and indexes it for the poller.
163
+ */
164
+ export async function createSubscription(
165
+ store: Store.State,
166
+ input: CreateInput,
167
+ options: createSubscription.Options = {},
168
+ ): Promise<Subscription> {
169
+ WebhookDestination.assertDestination(input.destination)
170
+
171
+ if (options.maxPerOwner !== undefined) {
172
+ const existing = await listSubscriptions(store, input.owner)
173
+ if (existing.length >= options.maxPerOwner) throw new LimitExceededError(options.maxPerOwner)
174
+ }
175
+
176
+ const now = (options.now ?? (() => new Date()))()
177
+ const iso = now.toISOString()
178
+ const subscription: Subscription = {
179
+ chainId: input.chainId,
180
+ ...(input.context === undefined ? {} : { context: input.context }),
181
+ createdAt: iso,
182
+ destination: input.destination,
183
+ eventType: input.eventType,
184
+ failureCount: 0,
185
+ filters: input.filters ?? {},
186
+ id: generateId(now),
187
+ owner: input.owner,
188
+ secret: generateSecret(),
189
+ status: 'active',
190
+ updatedAt: iso,
191
+ ...(input.ttl === undefined
192
+ ? {}
193
+ : { expiresAt: new Date(now.getTime() + input.ttl).toISOString() }),
194
+ }
195
+
196
+ await writeSubscription(store, subscription)
197
+ return subscription
198
+ }
199
+
200
+ export declare namespace createSubscription {
201
+ /** Options for {@link createSubscription}. */
202
+ type Options = {
203
+ /** Reject creation once the owner has this many subscriptions. */
204
+ maxPerOwner?: number | undefined
205
+ /** Clock used for timestamps (injectable for tests). */
206
+ now?: (() => Date) | undefined
207
+ }
208
+ }
209
+
210
+ /** Reads a single subscription scoped to its owner. Returns null when absent. */
211
+ export async function getSubscription(
212
+ store: Store.State,
213
+ owner: Owner,
214
+ id: string,
215
+ ): Promise<Subscription | null> {
216
+ return readSubscription(store, subscriptionKey(owner, id))
217
+ }
218
+
219
+ /**
220
+ * Lists an owner's subscriptions, newest first, with optional keyset paging.
221
+ * Subscription ids embed a timestamp, so lexical key order is chronological.
222
+ */
223
+ export async function listSubscriptions(
224
+ store: Store.State,
225
+ owner: Owner,
226
+ options: listSubscriptions.Options = {},
227
+ ): Promise<Subscription[]> {
228
+ const prefix = subscriptionPrefix(owner)
229
+ const { keys } = await store.list({ prefix })
230
+
231
+ // Newest first: ids sort chronologically ascending, so reverse for descending.
232
+ let names = keys
233
+ .map((key) => key.name)
234
+ .sort()
235
+ .reverse()
236
+ if (options.cursor) names = names.filter((name) => name < `${prefix}${options.cursor}`)
237
+ if (options.offset) names = names.slice(options.offset)
238
+ if (options.limit !== undefined) names = names.slice(0, options.limit)
239
+
240
+ // Hydrate the page in parallel: the reads are independent, and a serial
241
+ // loop costs one store round trip per row (an N+1 against the production
242
+ // Durable Object).
243
+ const subscriptions = await Promise.all(names.map((name) => readSubscription(store, name)))
244
+ return subscriptions.filter((subscription) => subscription !== null)
245
+ }
246
+
247
+ export declare namespace listSubscriptions {
248
+ /** Options for {@link listSubscriptions}. */
249
+ type Options = {
250
+ /** Return subscriptions older than this id (keyset paging, newest first). */
251
+ cursor?: string | undefined
252
+ /** Maximum subscriptions to return. */
253
+ limit?: number | undefined
254
+ /** Rows to skip from the head (positional pagination; exclusive with `cursor`). */
255
+ offset?: number | undefined
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Counts an owner's subscriptions. Reads only the key set under the owner
261
+ * prefix (no row hydration), so it's a single store `list` — exact and cheap,
262
+ * since the per-owner set is bounded by `maxPerOwner`. Feeds the opt-in
263
+ * `meta.totalCount` on `GET /webhooks`.
264
+ */
265
+ export async function countSubscriptions(store: Store.State, owner: Owner): Promise<number> {
266
+ const { keys } = await store.list({ prefix: subscriptionPrefix(owner) })
267
+ return keys.length
268
+ }
269
+
270
+ /**
271
+ * Patches an owner's subscription, re-validating the URL when changed and
272
+ * re-indexing for the poller when the status changes. Returns null when absent.
273
+ */
274
+ export async function updateSubscription(
275
+ store: Store.State,
276
+ owner: Owner,
277
+ id: string,
278
+ patch: PatchInput,
279
+ options: updateSubscription.Options = {},
280
+ ): Promise<Subscription | null> {
281
+ const current = await getSubscription(store, owner, id)
282
+ if (!current) return null
283
+
284
+ if (patch.destination !== undefined) WebhookDestination.assertDestination(patch.destination)
285
+
286
+ const now = (options.now ?? (() => new Date()))()
287
+ const next: Subscription = {
288
+ ...current,
289
+ // `context: null` clears it (stored as undefined; drops from JSON + reads).
290
+ ...(patch.context === undefined
291
+ ? {}
292
+ : { context: patch.context === null ? undefined : patch.context }),
293
+ ...(patch.destination === undefined ? {} : { destination: patch.destination }),
294
+ ...(patch.filters === undefined ? {} : { filters: patch.filters }),
295
+ ...(patch.status === undefined ? {} : { status: patch.status }),
296
+ updatedAt: now.toISOString(),
297
+ }
298
+
299
+ await writeSubscription(store, next)
300
+ return next
301
+ }
302
+
303
+ export declare namespace updateSubscription {
304
+ /** Options for {@link updateSubscription}. */
305
+ type Options = {
306
+ /** Clock used for timestamps (injectable for tests). */
307
+ now?: (() => Date) | undefined
308
+ }
309
+ }
310
+
311
+ /** Deletes an owner's subscription and its index/cursor keys. */
312
+ export async function deleteSubscription(
313
+ store: Store.State,
314
+ owner: Owner,
315
+ id: string,
316
+ ): Promise<boolean> {
317
+ const current = await getSubscription(store, owner, id)
318
+ if (!current) return false
319
+
320
+ await store.delete(subscriptionKey(owner, id))
321
+ await store.delete(activeKey(current.chainId, current.eventType, id))
322
+ await store.delete(cursorKey(id))
323
+
324
+ // Drop the delivery log so a deleted subscription leaves no orphaned rows.
325
+ const { keys } = await store.list({ prefix: deliveryPrefix(id) })
326
+ for (const { name } of keys) await store.delete(name)
327
+ return true
328
+ }
329
+
330
+ /**
331
+ * Lists active subscriptions for a `(chainId, eventType)` stream. Used by the
332
+ * poller; reads the secondary index, then hydrates each subscription.
333
+ */
334
+ export async function listActive(
335
+ store: Store.State,
336
+ chainId: number,
337
+ eventType: EventType,
338
+ ): Promise<Subscription[]> {
339
+ const { keys } = await store.list({ prefix: activePrefix(chainId, eventType) })
340
+
341
+ const subscriptions: Subscription[] = []
342
+ for (const { name } of keys) {
343
+ const subKey = await store.get(name)
344
+ if (!subKey) continue
345
+ const subscription = await readSubscription(store, subKey)
346
+ if (subscription) subscriptions.push(subscription)
347
+ }
348
+ return subscriptions
349
+ }
350
+
351
+ /** Reads a subscription's keyset cursor. Returns null when unset. */
352
+ export async function getCursor(
353
+ store: Store.State,
354
+ subscriptionId: string,
355
+ ): Promise<string | null> {
356
+ return store.get(cursorKey(subscriptionId))
357
+ }
358
+
359
+ /** Writes a subscription's keyset cursor. */
360
+ export async function setCursor(
361
+ store: Store.State,
362
+ subscriptionId: string,
363
+ cursor: string,
364
+ ): Promise<void> {
365
+ await store.put(cursorKey(subscriptionId), cursor)
366
+ }
367
+
368
+ /**
369
+ * Default delivery-log retention. Rows are written with this TTL so a busy
370
+ * subscription's log doesn't grow unbounded; tune per deployment.
371
+ */
372
+ export const deliveryRetentionMs = 7 * 24 * 60 * 60 * 1_000
373
+
374
+ /** Records one delivery-log row (stateful, owner-agnostic; scoped per subscription). */
375
+ export async function recordDelivery(
376
+ store: Store.State,
377
+ delivery: Delivery,
378
+ options: recordDelivery.Options = {},
379
+ ): Promise<void> {
380
+ const ttl = options.ttl ?? deliveryRetentionMs
381
+ await store.put(deliveryKey(delivery.subscriptionId, delivery.id), JSON.stringify(delivery), {
382
+ ttl,
383
+ })
384
+ }
385
+
386
+ export declare namespace recordDelivery {
387
+ /** Options for {@link recordDelivery}. */
388
+ type Options = {
389
+ /** Row time-to-live in ms; defaults to {@link deliveryRetentionMs}. */
390
+ ttl?: number | undefined
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Lists a subscription's delivery log, newest first, with optional keyset
396
+ * paging. Delivery ids embed a timestamp, so lexical key order is chronological.
397
+ */
398
+ export async function listDeliveries(
399
+ store: Store.State,
400
+ subscriptionId: string,
401
+ options: listDeliveries.Options = {},
402
+ ): Promise<Delivery[]> {
403
+ const prefix = deliveryPrefix(subscriptionId)
404
+ const { keys } = await store.list({ prefix })
405
+
406
+ // Newest first: ids sort chronologically ascending, so reverse for descending.
407
+ let names = keys
408
+ .map((key) => key.name)
409
+ .sort()
410
+ .reverse()
411
+ if (options.cursor) names = names.filter((name) => name < `${prefix}${options.cursor}`)
412
+ if (options.offset) names = names.slice(options.offset)
413
+ if (options.limit !== undefined) names = names.slice(0, options.limit)
414
+
415
+ // Hydrate the page in parallel: the reads are independent, and a serial
416
+ // loop costs one store round trip per row (an N+1 against the production
417
+ // Durable Object).
418
+ const deliveries = await Promise.all(names.map((name) => readDelivery(store, name)))
419
+ return deliveries.filter((delivery) => delivery !== null)
420
+ }
421
+
422
+ export declare namespace listDeliveries {
423
+ /** Options for {@link listDeliveries}. */
424
+ type Options = {
425
+ /** Return deliveries whose id sorts before this one (older entries). */
426
+ cursor?: string | undefined
427
+ /** Maximum deliveries to return. */
428
+ limit?: number | undefined
429
+ /** Rows to skip from the head (positional pagination; exclusive with `cursor`). */
430
+ offset?: number | undefined
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Counts a subscription's delivery-log entries. Reads only the key set under
436
+ * the delivery prefix (no row hydration), so it's a single store `list` —
437
+ * exact, and bounded by the delivery retention TTL. Feeds the opt-in
438
+ * `meta.totalCount` on `GET /webhooks/:id/deliveries`.
439
+ */
440
+ export async function countDeliveries(store: Store.State, subscriptionId: string): Promise<number> {
441
+ const { keys } = await store.list({ prefix: deliveryPrefix(subscriptionId) })
442
+ return keys.length
443
+ }
444
+
445
+ /** Loads a single delivery-log row (`null` when absent), scoped to its subscription. */
446
+ export async function getDelivery(
447
+ store: Store.State,
448
+ subscriptionId: string,
449
+ deliveryId: string,
450
+ ): Promise<Delivery | null> {
451
+ return readDelivery(store, deliveryKey(subscriptionId, deliveryId))
452
+ }
453
+
454
+ /**
455
+ * Default number of consecutive delivery failures after which a subscription
456
+ * auto-disables (and is dropped from the poller's active index).
457
+ */
458
+ export const maxFailures = 10
459
+
460
+ /**
461
+ * Records a successful delivery: resets the consecutive-failure counter and
462
+ * stamps `lastDeliveryAt`. Returns the updated subscription (re-persisted so the
463
+ * poller's active index and TTL stay correct).
464
+ */
465
+ export async function recordSuccess(
466
+ store: Store.State,
467
+ subscription: Subscription,
468
+ options: recordSuccess.Options = {},
469
+ ): Promise<Subscription> {
470
+ const now = (options.now ?? (() => new Date()))()
471
+ const next: Subscription = {
472
+ ...subscription,
473
+ failureCount: 0,
474
+ lastDeliveryAt: now.toISOString(),
475
+ updatedAt: now.toISOString(),
476
+ }
477
+ await writeSubscription(store, next)
478
+ return next
479
+ }
480
+
481
+ export declare namespace recordSuccess {
482
+ /** Options for {@link recordSuccess}. */
483
+ type Options = {
484
+ /** Clock used for timestamps; injectable for tests. */
485
+ now?: (() => Date) | undefined
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Records a failed delivery: increments the consecutive-failure counter and,
491
+ * once it reaches `maxFailures`, sets `status: 'disabled'` so the subscription
492
+ * drops out of the poller's active index (surfaced via the API). Returns the
493
+ * updated subscription.
494
+ */
495
+ export async function recordFailure(
496
+ store: Store.State,
497
+ subscription: Subscription,
498
+ options: recordFailure.Options = {},
499
+ ): Promise<Subscription> {
500
+ const now = (options.now ?? (() => new Date()))()
501
+ const failureCount = subscription.failureCount + 1
502
+ const disabled = failureCount >= (options.maxFailures ?? maxFailures)
503
+ const next: Subscription = {
504
+ ...subscription,
505
+ failureCount,
506
+ ...(disabled ? { status: 'disabled' } : {}),
507
+ updatedAt: now.toISOString(),
508
+ }
509
+ await writeSubscription(store, next)
510
+ return next
511
+ }
512
+
513
+ export declare namespace recordFailure {
514
+ /** Options for {@link recordFailure}. */
515
+ type Options = {
516
+ /** Consecutive failures before auto-disabling (default {@link maxFailures}). */
517
+ maxFailures?: number | undefined
518
+ /** Clock used for timestamps; injectable for tests. */
519
+ now?: (() => Date) | undefined
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Delivers an envelope to its subscription and records the outcome: success
525
+ * resets the failure counter and stamps `lastDeliveryAt`; failure increments it
526
+ * and auto-disables after {@link maxFailures}. This is the single delivery entry
527
+ * point shared by the Cloudflare Queue consumer and the self-host inline path.
528
+ */
529
+ export async function deliverAndRecord(
530
+ store: Store.State,
531
+ subscription: Subscription,
532
+ envelope: Envelope,
533
+ options: deliverAndRecord.Options = {},
534
+ ): Promise<WebhookDestination.Result> {
535
+ // Snapshot the attempt before recordSuccess/recordFailure mutate the counter.
536
+ const attempt = subscription.failureCount + 1
537
+ const result = await WebhookDestination.from(subscription.destination).deliver({
538
+ envelope,
539
+ secret: subscription.secret,
540
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
541
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
542
+ })
543
+ if (result.ok) await recordSuccess(store, subscription, options)
544
+ else await recordFailure(store, subscription, options)
545
+
546
+ // Emit a `webhook:delivery` metric. recordSuccess/recordFailure are immutable
547
+ // (they spread into a new record), so `subscription.failureCount` here is still
548
+ // the pre-mutation snapshot the sink needs to derive the attempt bucket.
549
+ // Best-effort, like delivery logging.
550
+ const metrics = options.metrics ? MetricSink.webhooks(options.metrics) : undefined
551
+ if (metrics) {
552
+ metrics.record({
553
+ envelope,
554
+ result,
555
+ subscription,
556
+ trigger: options.trigger ?? 'queue',
557
+ type: 'webhook:delivery',
558
+ })
559
+ metrics.flush()
560
+ }
561
+
562
+ // Append a delivery-log row. Best-effort: a logging failure must never change
563
+ // the delivery outcome the caller (Queue consumer / inline path) acts on.
564
+ try {
565
+ await recordDelivery(
566
+ store,
567
+ buildDelivery({ attempt, envelope, result, subscription }, options),
568
+ options.deliveryRetentionMs === undefined ? {} : { ttl: options.deliveryRetentionMs },
569
+ )
570
+ } catch {
571
+ // delivery logging is best-effort
572
+ }
573
+
574
+ return result
575
+ }
576
+
577
+ export declare namespace deliverAndRecord {
578
+ /** Options for {@link deliverAndRecord}. */
579
+ type Options = {
580
+ /** Delivery-log row TTL in ms; defaults to {@link deliveryRetentionMs}. */
581
+ deliveryRetentionMs?: number | undefined
582
+ /** `fetch` implementation passed through to {@link deliver}. */
583
+ fetch?: typeof globalThis.fetch | undefined
584
+ /** Consecutive failures before auto-disabling (default {@link maxFailures}). */
585
+ maxFailures?: number | undefined
586
+ /** Metrics backend; emits one `webhook:delivery` event per attempt. */
587
+ metrics?: Metrics.Metrics | undefined
588
+ /** Clock used for timestamps; injectable for tests. */
589
+ now?: (() => Date) | undefined
590
+ /** Delivery timeout in ms passed through to {@link deliver}. */
591
+ timeoutMs?: number | undefined
592
+ /** Where this attempt originated; tags the `webhook:delivery` event (default `queue`). */
593
+ trigger?: MetricSink.Trigger | undefined
594
+ }
595
+ }
596
+
597
+ /** Builds a delivery-log row from an attempt's envelope and classified result. */
598
+ function buildDelivery(
599
+ input: {
600
+ attempt: number
601
+ envelope: Envelope
602
+ result: WebhookDestination.Result
603
+ subscription: Subscription
604
+ },
605
+ options: { now?: (() => Date) | undefined } = {},
606
+ ): Delivery {
607
+ const { attempt, envelope, result, subscription } = input
608
+ const now = (options.now ?? (() => new Date()))()
609
+ return {
610
+ attempt,
611
+ createdAt: now.toISOString(),
612
+ envelope,
613
+ eventId: envelope.id,
614
+ id: generateDeliveryId(now),
615
+ requestUrl: WebhookDestination.destinationLabel(subscription.destination),
616
+ status: result.ok ? 'succeeded' : 'failed',
617
+ subscriptionId: subscription.id,
618
+ ...(result.error === undefined ? {} : { error: result.error }),
619
+ ...(result.durationMs === undefined ? {} : { responseMs: result.durationMs }),
620
+ ...(result.status === undefined ? {} : { responseStatus: result.status }),
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Computes a stable, idempotent event id from its on-chain coordinates so
626
+ * re-deliveries are dedupable by the receiver.
627
+ */
628
+ export function eventId(options: eventId.Options): string {
629
+ const input = `${options.chainId}:${options.eventType}:${options.blockNumber}:${options.logIndex}`
630
+ return `evt_${Hash.sha256(Hex.fromString(input)).slice(2)}`
631
+ }
632
+
633
+ export declare namespace eventId {
634
+ /** Options for {@link eventId}. */
635
+ type Options = {
636
+ /** Block number the event was indexed in. */
637
+ blockNumber: bigint | number | string
638
+ /** Chain the event occurred on. */
639
+ chainId: number
640
+ /** Event type. */
641
+ eventType: EventType
642
+ /** Log index within the block (or transaction index for transaction events). */
643
+ logIndex: bigint | number | string
644
+ }
645
+ }
646
+
647
+ /** The JSON envelope delivered to subscribers. */
648
+ export type Envelope = {
649
+ /** Chain the event occurred on. */
650
+ chainId: number
651
+ /** Human context copied from the subscription, when set. */
652
+ context?: Context | undefined
653
+ /** ISO timestamp the envelope was built. */
654
+ createdAt: string
655
+ /** The same row shape the corresponding read endpoint returns. */
656
+ data: unknown
657
+ /** Stable, idempotent event id (`evt_…`). */
658
+ id: string
659
+ /** Originating subscription id. */
660
+ subscriptionId: string
661
+ /** Event type; `ping` marks a synthetic test delivery (see {@link buildPingEnvelope}). */
662
+ type: EventType | 'ping'
663
+ }
664
+
665
+ /** Builds a delivery envelope for an event matched against a subscription. */
666
+ export function buildEnvelope(options: buildEnvelope.Options): Envelope {
667
+ const { subscription } = options
668
+ return {
669
+ chainId: subscription.chainId,
670
+ ...(subscription.context === undefined ? {} : { context: subscription.context }),
671
+ createdAt: (options.createdAt ?? new Date()).toISOString(),
672
+ data: options.data,
673
+ id: eventId({
674
+ blockNumber: options.blockNumber,
675
+ chainId: subscription.chainId,
676
+ eventType: subscription.eventType,
677
+ logIndex: options.logIndex,
678
+ }),
679
+ subscriptionId: subscription.id,
680
+ type: subscription.eventType,
681
+ }
682
+ }
683
+
684
+ export declare namespace buildEnvelope {
685
+ /** Options for {@link buildEnvelope}. */
686
+ type Options = {
687
+ /** Block number the event was indexed in. */
688
+ blockNumber: bigint | number | string
689
+ /** Timestamp to stamp on the envelope; defaults to now. */
690
+ createdAt?: Date | undefined
691
+ /** Decoded event row (same shape the read endpoint returns). */
692
+ data: unknown
693
+ /** Log index within the block. */
694
+ logIndex: bigint | number | string
695
+ /** Subscription the event matched. */
696
+ subscription: Subscription
697
+ }
698
+ }
699
+
700
+ /**
701
+ * Builds a synthetic `ping` envelope so an owner can test a subscription's
702
+ * endpoint (connectivity, TLS, signature verification) without waiting for a real
703
+ * on-chain event. The id is randomized per call so repeated pings are never
704
+ * deduped by the receiver; `type` is `ping` and `data` is a recognizable
705
+ * `{ ping: true }` marker rather than an event row, so receivers can tell a test
706
+ * delivery apart from a real event.
707
+ */
708
+ export function buildPingEnvelope(
709
+ subscription: Subscription,
710
+ options: buildPingEnvelope.Options = {},
711
+ ): Envelope {
712
+ const now = options.createdAt ?? new Date()
713
+ const nonce = options.nonce ?? Hex.random(16).slice(2)
714
+ return {
715
+ chainId: subscription.chainId,
716
+ ...(subscription.context === undefined ? {} : { context: subscription.context }),
717
+ createdAt: now.toISOString(),
718
+ data: { ping: true },
719
+ id: `evt_${Hash.sha256(Hex.fromString(`ping:${subscription.id}:${nonce}`)).slice(2)}`,
720
+ subscriptionId: subscription.id,
721
+ type: 'ping',
722
+ }
723
+ }
724
+
725
+ export declare namespace buildPingEnvelope {
726
+ /** Options for {@link buildPingEnvelope}. */
727
+ type Options = {
728
+ /** Timestamp to stamp on the envelope; defaults to now. */
729
+ createdAt?: Date | undefined
730
+ /** Deterministic nonce for the event id; defaults to random (tests only). */
731
+ nonce?: string | undefined
732
+ }
733
+ }
734
+
735
+ /**
736
+ * Delivers a synthetic {@link buildPingEnvelope} to a subscription's endpoint and
737
+ * logs the attempt. Unlike {@link deliverAndRecord} it deliberately does NOT
738
+ * touch the subscription's `failureCount`/`status`: a test ping must never
739
+ * auto-disable a healthy subscription or reset a real failure streak. The
740
+ * delivery row is still appended (best-effort) so the ping is observable via the
741
+ * delivery log.
742
+ */
743
+ export async function ping(
744
+ store: Store.State,
745
+ subscription: Subscription,
746
+ options: ping.Options = {},
747
+ ): Promise<ping.Result> {
748
+ const now = options.now ?? (() => new Date())
749
+ const envelope = buildPingEnvelope(subscription, {
750
+ createdAt: now(),
751
+ ...(options.nonce === undefined ? {} : { nonce: options.nonce }),
752
+ })
753
+ const result = await WebhookDestination.from(subscription.destination).deliver({
754
+ envelope,
755
+ secret: subscription.secret,
756
+ ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
757
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
758
+ })
759
+
760
+ // Best-effort: a logging failure must never change the result the caller sees.
761
+ const delivery = buildDelivery({ attempt: 1, envelope, result, subscription }, { now })
762
+ try {
763
+ await recordDelivery(
764
+ store,
765
+ delivery,
766
+ options.deliveryRetentionMs === undefined ? {} : { ttl: options.deliveryRetentionMs },
767
+ )
768
+ } catch {
769
+ // delivery logging is best-effort
770
+ }
771
+
772
+ return { delivery, envelope, result }
773
+ }
774
+
775
+ export declare namespace ping {
776
+ /** Options for {@link ping}. */
777
+ type Options = {
778
+ /** Delivery-log row TTL in ms; defaults to {@link deliveryRetentionMs}. */
779
+ deliveryRetentionMs?: number | undefined
780
+ /** `fetch` implementation passed through to {@link deliver}. */
781
+ fetch?: typeof globalThis.fetch | undefined
782
+ /** Deterministic event-id nonce; defaults to random (tests only). */
783
+ nonce?: string | undefined
784
+ /** Clock used for timestamps; injectable for tests. */
785
+ now?: (() => Date) | undefined
786
+ /** Delivery timeout in ms passed through to {@link deliver}. */
787
+ timeoutMs?: number | undefined
788
+ }
789
+ /** Outcome of a ping: the classified delivery result plus the logged row. */
790
+ type Result = {
791
+ /** The delivery-log row that was recorded. */
792
+ delivery: Delivery
793
+ /** The synthetic envelope that was sent. */
794
+ envelope: Envelope
795
+ /** The classified transport result. */
796
+ result: WebhookDestination.Result
797
+ }
798
+ }
799
+
800
+ function ownerKey(owner: Owner): string {
801
+ return owner.type === 'api_key'
802
+ ? `api_key:${encodeURIComponent(owner.orgId)}`
803
+ : `mpp:${encodeURIComponent(owner.payer)}`
804
+ }
805
+
806
+ function subscriptionPrefix(owner: Owner): string {
807
+ return `webhook:sub:${ownerKey(owner)}:`
808
+ }
809
+
810
+ function subscriptionKey(owner: Owner, id: string): string {
811
+ return `${subscriptionPrefix(owner)}${id}`
812
+ }
813
+
814
+ function cursorKey(subscriptionId: string): string {
815
+ return `webhook:cursor:${subscriptionId}`
816
+ }
817
+
818
+ function deliveryPrefix(subscriptionId: string): string {
819
+ return `webhook:delivery:${subscriptionId}:`
820
+ }
821
+
822
+ function deliveryKey(subscriptionId: string, deliveryId: string): string {
823
+ return `${deliveryPrefix(subscriptionId)}${deliveryId}`
824
+ }
825
+
826
+ function activePrefix(chainId: number, eventType: EventType): string {
827
+ return `webhook:active:${chainId}:${eventType}:`
828
+ }
829
+
830
+ function activeKey(chainId: number, eventType: EventType, id: string): string {
831
+ return `${activePrefix(chainId, eventType)}${id}`
832
+ }
833
+
834
+ async function readSubscription(store: Store.State, key: string): Promise<Subscription | null> {
835
+ const raw = await store.get(key)
836
+ if (!raw) return null
837
+ try {
838
+ return JSON.parse(raw) as Subscription
839
+ } catch {
840
+ // Corrupt entries are treated as absent.
841
+ return null
842
+ }
843
+ }
844
+
845
+ async function readDelivery(store: Store.State, key: string): Promise<Delivery | null> {
846
+ const raw = await store.get(key)
847
+ if (!raw) return null
848
+ try {
849
+ return JSON.parse(raw) as Delivery
850
+ } catch {
851
+ // Corrupt entries are treated as absent.
852
+ return null
853
+ }
854
+ }
855
+
856
+ async function writeSubscription(store: Store.State, subscription: Subscription): Promise<void> {
857
+ const ttl = ttlFor(subscription)
858
+ const key = subscriptionKey(subscription.owner, subscription.id)
859
+ await store.put(key, JSON.stringify(subscription), { ttl })
860
+
861
+ // Mirror status into the poller's secondary index: only `active`
862
+ // subscriptions are polled. The index value is the subscription's storage key
863
+ // so the poller can hydrate without knowing the owner.
864
+ const indexKey = activeKey(subscription.chainId, subscription.eventType, subscription.id)
865
+ if (subscription.status === 'active') await store.put(indexKey, key, { ttl })
866
+ else await store.delete(indexKey)
867
+ }
868
+
869
+ function ttlFor(subscription: Subscription): number | undefined {
870
+ if (!subscription.expiresAt) return undefined
871
+ return Math.max(1, new Date(subscription.expiresAt).getTime() - Date.now())
872
+ }
873
+
874
+ function generateId(now: Date): string {
875
+ // Zero-pad the epoch-ms so ids sort chronologically by string comparison
876
+ // (mirroring delivery ids), letting listSubscriptions page newest-first; 15
877
+ // digits leaves headroom well past the year 5138.
878
+ const timestamp = now.getTime().toString().padStart(15, '0')
879
+ return `wh_${timestamp}_${nanoid()}`
880
+ }
881
+
882
+ function generateDeliveryId(now: Date): string {
883
+ // Zero-pad the epoch-ms so ids sort chronologically by string comparison; 15
884
+ // digits leaves headroom well past the year 5138.
885
+ const timestamp = now.getTime().toString().padStart(15, '0')
886
+ return `whd_${timestamp}_${nanoid()}`
887
+ }
888
+
889
+ function generateSecret(): string {
890
+ return `whsec_${Hex.random(32).slice(2)}`
891
+ }
892
+
893
+ export class LimitExceededError extends Error {
894
+ /** The cap that was reached. */
895
+ limit: number
896
+
897
+ constructor(limit: number) {
898
+ super(`Webhook subscription limit reached (${limit}).`)
899
+ this.name = 'Webhooks.LimitExceededError'
900
+ this.limit = limit
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Thrown when a subscription's `filters` fail to validate against the schema for
906
+ * its `eventType`. The detection path treats stored filters as **fail-closed**:
907
+ * an invalid filter never degrades to a firehose, so the API rejects bad filters
908
+ * at create/patch time rather than silently widening the match set later.
909
+ */
910
+ export class InvalidFilterError extends Error {
911
+ /** Event type whose filters failed validation. */
912
+ eventType: string
913
+ /** Per-field validation issues, suitable for an API error `details` array. */
914
+ details: readonly InvalidFilterError.Detail[]
915
+
916
+ constructor(eventType: string, details: readonly InvalidFilterError.Detail[] = []) {
917
+ super(`Invalid filters for "${eventType}" webhook subscription.`)
918
+ this.name = 'Webhooks.InvalidFilterError'
919
+ this.eventType = eventType
920
+ this.details = details
921
+ }
922
+ }
923
+
924
+ export declare namespace InvalidFilterError {
925
+ /** A single validation issue: a message plus the path to the invalid value. */
926
+ type Detail = {
927
+ /** Human-readable validation message. */
928
+ message: string
929
+ /** Path to the invalid value within the `filters` object. */
930
+ path?: readonly (string | number)[] | undefined
931
+ }
932
+ }
933
+
934
+ /** Thrown when webhook operations run without a configured state store. */
935
+ export class UnconfiguredError extends Error {
936
+ constructor() {
937
+ super('Webhooks are not configured for this deployment.')
938
+ this.name = 'Webhooks.UnconfiguredError'
939
+ }
940
+ }