tapimo 0.9.0 → 0.10.1

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 (319) hide show
  1. package/dist/ApiKey.d.ts +2 -1
  2. package/dist/ApiKey.d.ts.map +1 -1
  3. package/dist/ApiKeys.d.ts +4 -2
  4. package/dist/ApiKeys.d.ts.map +1 -1
  5. package/dist/ApiKeys.js +26 -1
  6. package/dist/ApiKeys.js.map +1 -1
  7. package/dist/App.d.ts +4292 -3708
  8. package/dist/App.d.ts.map +1 -1
  9. package/dist/App.js +1 -0
  10. package/dist/App.js.map +1 -1
  11. package/dist/Webhooks.d.ts +21 -20
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +66 -23
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +91 -84
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +26 -17
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +6 -0
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +4 -2
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +168 -168
  24. package/dist/admin/ui.generated.d.ts +1 -1
  25. package/dist/admin/ui.generated.js +69 -69
  26. package/dist/admin/ui.generated.js.map +1 -1
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +26 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +4280 -84
  31. package/dist/apps/Zones.d.ts.map +1 -1
  32. package/dist/apps/Zones.js +30 -10
  33. package/dist/apps/Zones.js.map +1 -1
  34. package/dist/apps/data/App.d.ts +5799 -4929
  35. package/dist/apps/data/App.d.ts.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +4 -5
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +19 -4
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +3391 -492
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +372 -29
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +853 -339
  45. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.d.ts +10 -10
  47. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  48. package/dist/apps/data/routes/balances.js +145 -83
  49. package/dist/apps/data/routes/balances.js.map +1 -1
  50. package/dist/apps/data/routes/blocks.d.ts +123 -123
  51. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  52. package/dist/apps/data/routes/earn.d.ts +455 -1
  53. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  54. package/dist/apps/data/routes/earn.js +369 -45
  55. package/dist/apps/data/routes/earn.js.map +1 -1
  56. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  57. package/dist/apps/data/routes/exchanges.js +114 -118
  58. package/dist/apps/data/routes/exchanges.js.map +1 -1
  59. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  60. package/dist/apps/data/routes/indexer.d.ts +35 -34
  61. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  62. package/dist/apps/data/routes/indexer.js +3 -2
  63. package/dist/apps/data/routes/indexer.js.map +1 -1
  64. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  65. package/dist/apps/data/routes/receipts.js +68 -47
  66. package/dist/apps/data/routes/receipts.js.map +1 -1
  67. package/dist/apps/data/routes/rpc.d.ts +31 -31
  68. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  69. package/dist/apps/data/routes/rpc.js +5 -1
  70. package/dist/apps/data/routes/rpc.js.map +1 -1
  71. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  72. package/dist/apps/data/routes/tokens.d.ts +461 -459
  73. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  74. package/dist/apps/data/routes/transactions.d.ts +313 -305
  75. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  76. package/dist/apps/data/routes/transactions.js +25 -23
  77. package/dist/apps/data/routes/transactions.js.map +1 -1
  78. package/dist/apps/data/routes/transfers.d.ts +108 -86
  79. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  80. package/dist/apps/data/routes/transfers.js +618 -105
  81. package/dist/apps/data/routes/transfers.js.map +1 -1
  82. package/dist/apps/data/routes/valuation.d.ts +21 -21
  83. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  84. package/dist/apps/data/routes/valuation.js +2 -2
  85. package/dist/apps/data/routes/valuation.js.map +1 -1
  86. package/dist/apps/data/routes/webhooks.d.ts +519 -868
  87. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  88. package/dist/apps/data/routes/webhooks.js +642 -353
  89. package/dist/apps/data/routes/webhooks.js.map +1 -1
  90. package/dist/apps/data/routes/zones.d.ts +31 -31
  91. package/dist/apps/data/routes/zones.d.ts.map +1 -1
  92. package/dist/apps/data/routes/zones.js +4 -2
  93. package/dist/apps/data/routes/zones.js.map +1 -1
  94. package/dist/apps/funding/routes/deposit-addresses.js +1 -1
  95. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  96. package/dist/apps/funding/routes/providers.d.ts +13 -13
  97. package/dist/apps/funding/routes/quotes.d.ts +89 -89
  98. package/dist/apps/funding/routes/transfers.d.ts +36 -36
  99. package/dist/apps/funding/routes/transfers.js +1 -1
  100. package/dist/apps/funding/routes/transfers.js.map +1 -1
  101. package/dist/apps/management/App.d.ts +11 -309
  102. package/dist/apps/management/App.d.ts.map +1 -1
  103. package/dist/apps/management/routes/api-keys.d.ts +74 -74
  104. package/dist/apps/management/routes/billing.js +1 -1
  105. package/dist/apps/management/routes/billing.js.map +1 -1
  106. package/dist/apps/management/routes/faucet.d.ts +35 -35
  107. package/dist/apps/management/routes/faucet.js +2 -2
  108. package/dist/apps/management/routes/faucet.js.map +1 -1
  109. package/dist/apps/management/routes/invitations.d.ts +134 -134
  110. package/dist/apps/management/routes/invite-links.d.ts +80 -80
  111. package/dist/apps/management/routes/members.d.ts +69 -69
  112. package/dist/apps/management/routes/orgs.d.ts +98 -98
  113. package/dist/apps/management/routes/projects.d.ts +60 -60
  114. package/dist/apps/management/routes/usage.d.ts +72 -72
  115. package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
  116. package/dist/apps/management/routes/webhooks.d.ts +203 -618
  117. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  118. package/dist/apps/mcp.d.ts.map +1 -1
  119. package/dist/apps/mcp.js +4 -1
  120. package/dist/apps/mcp.js.map +1 -1
  121. package/dist/apps/mpp/App.d.ts +56 -44
  122. package/dist/apps/mpp/App.d.ts.map +1 -1
  123. package/dist/apps/mpp/Mpp.d.ts +3 -0
  124. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  125. package/dist/apps/mpp/Mpp.js +3 -0
  126. package/dist/apps/mpp/Mpp.js.map +1 -1
  127. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  128. package/dist/apps/mpp/internal/Adapter.js +9 -2
  129. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  130. package/dist/db/Db.d.ts +4 -2
  131. package/dist/db/Db.d.ts.map +1 -1
  132. package/dist/db/Db.js +75 -15
  133. package/dist/db/Db.js.map +1 -1
  134. package/dist/db/Schema.d.ts +19 -0
  135. package/dist/db/Schema.d.ts.map +1 -1
  136. package/dist/db/Schema.js +24 -0
  137. package/dist/db/Schema.js.map +1 -1
  138. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  139. package/dist/db/tables/sponsoredTransactions.js +4 -1
  140. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  141. package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
  142. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
  143. package/dist/db/tables/webhookQueueEvents.js +160 -0
  144. package/dist/db/tables/webhookQueueEvents.js.map +1 -0
  145. package/dist/db/tables/webhookSubscriptions.d.ts +58 -3
  146. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  147. package/dist/db/tables/webhookSubscriptions.js +86 -3
  148. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  149. package/dist/internal/Auth.d.ts +3 -0
  150. package/dist/internal/Auth.d.ts.map +1 -1
  151. package/dist/internal/Auth.js +6 -4
  152. package/dist/internal/Auth.js.map +1 -1
  153. package/dist/internal/MetricSink.d.ts +139 -3
  154. package/dist/internal/MetricSink.d.ts.map +1 -1
  155. package/dist/internal/MetricSink.js +144 -4
  156. package/dist/internal/MetricSink.js.map +1 -1
  157. package/dist/internal/RateLimit.d.ts +4 -6
  158. package/dist/internal/RateLimit.d.ts.map +1 -1
  159. package/dist/internal/RateLimit.js +15 -14
  160. package/dist/internal/RateLimit.js.map +1 -1
  161. package/dist/internal/Schema.js +1 -1
  162. package/dist/internal/Schema.js.map +1 -1
  163. package/dist/internal/Store.d.ts +2 -1
  164. package/dist/internal/Store.d.ts.map +1 -1
  165. package/dist/internal/Store.js +57 -18
  166. package/dist/internal/Store.js.map +1 -1
  167. package/dist/internal/Tidx.d.ts +1 -3
  168. package/dist/internal/Tidx.d.ts.map +1 -1
  169. package/dist/internal/Tidx.js +1 -3
  170. package/dist/internal/Tidx.js.map +1 -1
  171. package/dist/internal/Value.d.ts +2 -0
  172. package/dist/internal/Value.d.ts.map +1 -1
  173. package/dist/internal/Value.js +25 -0
  174. package/dist/internal/Value.js.map +1 -1
  175. package/dist/internal/Viem.d.ts.map +1 -1
  176. package/dist/internal/Viem.js +2 -1
  177. package/dist/internal/Viem.js.map +1 -1
  178. package/dist/internal/WebhookDestination.d.ts +2 -0
  179. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  180. package/dist/internal/WebhookDestination.js +11 -0
  181. package/dist/internal/WebhookDestination.js.map +1 -1
  182. package/dist/internal/WebhookTransfer.d.ts +75 -0
  183. package/dist/internal/WebhookTransfer.d.ts.map +1 -0
  184. package/dist/internal/WebhookTransfer.js +299 -0
  185. package/dist/internal/WebhookTransfer.js.map +1 -0
  186. package/dist/internal/Webhooks.d.ts +176 -0
  187. package/dist/internal/Webhooks.d.ts.map +1 -1
  188. package/dist/internal/Webhooks.js +216 -20
  189. package/dist/internal/Webhooks.js.map +1 -1
  190. package/dist/internal/funding/Provider.d.ts +4 -0
  191. package/dist/internal/funding/Provider.d.ts.map +1 -1
  192. package/dist/internal/funding/Provider.js +11 -7
  193. package/dist/internal/funding/Provider.js.map +1 -1
  194. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  195. package/dist/internal/funding/Reconciliation.js +128 -0
  196. package/dist/internal/funding/Reconciliation.js.map +1 -1
  197. package/dist/internal/funding/providers/relay.d.ts +6 -4
  198. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  199. package/dist/internal/funding/providers/relay.js +71 -9
  200. package/dist/internal/funding/providers/relay.js.map +1 -1
  201. package/package.json +4 -4
  202. package/src/ApiKeys.test.ts +20 -0
  203. package/src/ApiKeys.ts +28 -1
  204. package/src/App.test.ts +10 -8
  205. package/src/App.ts +1 -0
  206. package/src/Client.test-d.ts +61 -5
  207. package/src/Webhooks.bench.ts +714 -0
  208. package/src/Webhooks.memory.bench.ts +250 -0
  209. package/src/Webhooks.test.ts +77 -6
  210. package/src/Webhooks.ts +85 -29
  211. package/src/admin/apps/api-keys.test.ts +9 -0
  212. package/src/admin/apps/api-keys.ts +15 -0
  213. package/src/admin/ui/src/lib/query-client.ts +3 -2
  214. package/src/admin/ui/src/routes/api-keys.tsx +31 -9
  215. package/src/admin/ui.generated.ts +69 -69
  216. package/src/analytics/Analytics.test.ts +8 -0
  217. package/src/analytics/Analytics.ts +26 -0
  218. package/src/apps/Zones.test.ts +23 -5
  219. package/src/apps/Zones.ts +31 -10
  220. package/src/apps/composition.test.ts +1 -0
  221. package/src/apps/data/App.test.ts +2 -2
  222. package/src/apps/data/FxOracle.test.ts +18 -0
  223. package/src/apps/data/FxOracle.ts +28 -5
  224. package/src/apps/data/routes/activities.bench.ts +41 -0
  225. package/src/apps/data/routes/activities.test.ts +122 -2
  226. package/src/apps/data/routes/activities.ts +435 -32
  227. package/src/apps/data/routes/balances.bench.ts +21 -0
  228. package/src/apps/data/routes/balances.test.ts +119 -10
  229. package/src/apps/data/routes/balances.ts +164 -97
  230. package/src/apps/data/routes/blocks.bench.ts +34 -0
  231. package/src/apps/data/routes/blocks.test.ts +12 -6
  232. package/src/apps/data/routes/coingecko.bench.ts +53 -0
  233. package/src/apps/data/routes/earn.bench.ts +71 -0
  234. package/src/apps/data/routes/earn.test.ts +197 -0
  235. package/src/apps/data/routes/earn.ts +487 -45
  236. package/src/apps/data/routes/exchanges.bench.ts +85 -0
  237. package/src/apps/data/routes/exchanges.test.ts +7 -6
  238. package/src/apps/data/routes/exchanges.ts +124 -117
  239. package/src/apps/data/routes/fee-amm.bench.ts +13 -0
  240. package/src/apps/data/routes/indexer.bench.ts +8 -0
  241. package/src/apps/data/routes/indexer.test.ts +2 -1
  242. package/src/apps/data/routes/indexer.ts +3 -2
  243. package/src/apps/data/routes/receipts.bench.ts +39 -0
  244. package/src/apps/data/routes/receipts.test.ts +24 -7
  245. package/src/apps/data/routes/receipts.ts +67 -50
  246. package/src/apps/data/routes/rpc.bench.ts +14 -0
  247. package/src/apps/data/routes/rpc.test.ts +27 -0
  248. package/src/apps/data/routes/rpc.ts +5 -1
  249. package/src/apps/data/routes/tokenlist.bench.ts +3 -0
  250. package/src/apps/data/routes/tokens.bench.ts +76 -0
  251. package/src/apps/data/routes/tokens.test.ts +51 -0
  252. package/src/apps/data/routes/transactions.bench.ts +40 -0
  253. package/src/apps/data/routes/transactions.test.ts +42 -0
  254. package/src/apps/data/routes/transactions.ts +44 -39
  255. package/src/apps/data/routes/transfers.bench.ts +28 -0
  256. package/src/apps/data/routes/transfers.test.ts +402 -24
  257. package/src/apps/data/routes/transfers.ts +708 -101
  258. package/src/apps/data/routes/valuation.bench.ts +14 -0
  259. package/src/apps/data/routes/valuation.test.ts +26 -0
  260. package/src/apps/data/routes/valuation.ts +6 -2
  261. package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
  262. package/src/apps/data/routes/webhooks.bench.ts +26 -0
  263. package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
  264. package/src/apps/data/routes/webhooks.test.ts +466 -135
  265. package/src/apps/data/routes/webhooks.ts +820 -429
  266. package/src/apps/data/routes/zones.bench.ts +32 -0
  267. package/src/apps/data/routes/zones.test.ts +1 -1
  268. package/src/apps/data/routes/zones.ts +7 -2
  269. package/src/apps/funding/routes/chains.test.ts +1 -1
  270. package/src/apps/funding/routes/deposit-addresses.test.ts +1 -1
  271. package/src/apps/funding/routes/deposit-addresses.ts +1 -1
  272. package/src/apps/funding/routes/providers.test.ts +1 -1
  273. package/src/apps/funding/routes/quotes.test.ts +2 -2
  274. package/src/apps/funding/routes/transfers.test.ts +3 -6
  275. package/src/apps/funding/routes/transfers.ts +1 -1
  276. package/src/apps/management/routes/billing.ts +1 -1
  277. package/src/apps/management/routes/faucet.ts +2 -2
  278. package/src/apps/management/routes/me.test.ts +31 -0
  279. package/src/apps/mcp.ts +4 -1
  280. package/src/apps/mpp/Mpp.test.ts +13 -1
  281. package/src/apps/mpp/Mpp.ts +8 -0
  282. package/src/apps/mpp/internal/Adapter.ts +10 -2
  283. package/src/db/Db.test.ts +40 -6
  284. package/src/db/Db.ts +114 -19
  285. package/src/db/Schema.ts +32 -0
  286. package/src/db/tables/sponsoredTransactions.ts +9 -2
  287. package/src/db/tables/webhookQueueEvents.ts +285 -0
  288. package/src/db/tables/webhookSubscriptions.ts +131 -4
  289. package/src/internal/Auth.test.ts +61 -50
  290. package/src/internal/Auth.ts +9 -4
  291. package/src/internal/EarnVaults.test.ts +2 -2
  292. package/src/internal/MetricSink.test.ts +575 -0
  293. package/src/internal/MetricSink.ts +325 -10
  294. package/src/internal/RateLimit.test.ts +23 -18
  295. package/src/internal/RateLimit.ts +23 -15
  296. package/src/internal/Schema.ts +1 -1
  297. package/src/internal/Store.test.ts +99 -13
  298. package/src/internal/Store.ts +52 -17
  299. package/src/internal/Tidx.test.ts +4 -4
  300. package/src/internal/Tidx.ts +1 -3
  301. package/src/internal/Value.test.ts +25 -0
  302. package/src/internal/Value.ts +29 -0
  303. package/src/internal/Viem.test.ts +8 -0
  304. package/src/internal/Viem.ts +2 -1
  305. package/src/internal/WebhookDestination.test.ts +20 -0
  306. package/src/internal/WebhookDestination.ts +10 -0
  307. package/src/internal/WebhookTransfer.test.ts +89 -0
  308. package/src/internal/WebhookTransfer.ts +430 -0
  309. package/src/internal/Webhooks.test.ts +616 -0
  310. package/src/internal/Webhooks.ts +412 -24
  311. package/src/internal/funding/Provider.test.ts +48 -8
  312. package/src/internal/funding/Provider.ts +12 -7
  313. package/src/internal/funding/Reconciliation.local.test.ts +3 -1
  314. package/src/internal/funding/Reconciliation.test.ts +1240 -0
  315. package/src/internal/funding/Reconciliation.ts +136 -0
  316. package/src/internal/funding/SourceObservation.test.ts +1 -0
  317. package/src/internal/funding/providers/index.test.ts +73 -3
  318. package/src/internal/funding/providers/relay.test.ts +2 -9
  319. package/src/internal/funding/providers/relay.ts +72 -13
@@ -1,4 +1,5 @@
1
1
  import * as Metrics from '../Metrics.js'
2
+ import type * as Webhooks from '../Webhooks.js'
2
3
  import type * as Log from './Log.js'
3
4
  import * as MetricSink from './MetricSink.js'
4
5
 
@@ -453,3 +454,577 @@ describe('requests', () => {
453
454
  expect(JSON.stringify(mpp)).not.toMatch(/request_private|credential|payload|signature/)
454
455
  })
455
456
  })
457
+
458
+ describe('webhooks', () => {
459
+ test('records recovery, poll-cycle, poll-stage, and failed-tick telemetry with bounded tags', () => {
460
+ const events: MetricEvent[] = []
461
+ const metrics = Metrics.from({
462
+ count(name, value, tags) {
463
+ events.push({ name, tags, type: 'count', value })
464
+ },
465
+ flush() {},
466
+ gauge(name, value, tags) {
467
+ events.push({ name, tags, type: 'gauge', value })
468
+ },
469
+ histogram(name, value, tags) {
470
+ events.push({ name, tags, type: 'histogram', value })
471
+ },
472
+ })
473
+ const sink = MetricSink.webhooks(metrics)
474
+
475
+ sink.record({
476
+ chainId: 4217,
477
+ dequeuedAt: 1_300,
478
+ enqueuedAt: 1_100,
479
+ trigger: 'alarm',
480
+ type: 'webhook:head-recovery',
481
+ })
482
+ sink.record({
483
+ chainId: 4217,
484
+ lagBlocks: 2,
485
+ type: 'webhook:head-progress',
486
+ })
487
+ sink.record({
488
+ chainId: 4217,
489
+ delayMs: 200,
490
+ trigger: 'direct',
491
+ type: 'webhook:poll-start',
492
+ })
493
+ sink.record({
494
+ chainId: 4217,
495
+ outcome: 'ok',
496
+ requestedBoundaryAdvanceBlocks: 10,
497
+ trigger: 'direct',
498
+ type: 'webhook:poll-cycle',
499
+ })
500
+ sink.record({
501
+ chainId: 4217,
502
+ failure: 'remote',
503
+ stage: 'finish',
504
+ trigger: 'direct',
505
+ type: 'webhook:poll-failure',
506
+ })
507
+ sink.record({
508
+ chainId: 4217,
509
+ durationMs: 42,
510
+ eventType: 'token:transfer',
511
+ outcome: 'ok',
512
+ stage: 'scan',
513
+ type: 'webhook:poll',
514
+ })
515
+ sink.record({
516
+ cursorLagBlocks: 7,
517
+ meta: { chainId: 4217, durationMs: 50, outcome: 'error' },
518
+ result: undefined,
519
+ type: 'webhook:tick',
520
+ })
521
+
522
+ expect(events).toMatchInlineSnapshot(`
523
+ [
524
+ {
525
+ "name": "webhook_head_recovery_queue_count",
526
+ "tags": {
527
+ "chain_id": "4217",
528
+ "trigger": "alarm",
529
+ },
530
+ "type": "count",
531
+ "value": 1,
532
+ },
533
+ {
534
+ "name": "webhook_head_recovery_queue_wait_ms",
535
+ "tags": {
536
+ "chain_id": "4217",
537
+ "trigger": "alarm",
538
+ },
539
+ "type": "histogram",
540
+ "value": 200,
541
+ },
542
+ {
543
+ "name": "webhook_head_progress_lag_blocks",
544
+ "tags": {
545
+ "chain_id": "4217",
546
+ },
547
+ "type": "gauge",
548
+ "value": 2,
549
+ },
550
+ {
551
+ "name": "webhook_poll_start_count",
552
+ "tags": {
553
+ "chain_id": "4217",
554
+ "trigger": "direct",
555
+ },
556
+ "type": "count",
557
+ "value": 1,
558
+ },
559
+ {
560
+ "name": "webhook_poll_start_delay_ms",
561
+ "tags": {
562
+ "chain_id": "4217",
563
+ "trigger": "direct",
564
+ },
565
+ "type": "histogram",
566
+ "value": 200,
567
+ },
568
+ {
569
+ "name": "webhook_poll_cycle_count",
570
+ "tags": {
571
+ "chain_id": "4217",
572
+ "outcome": "ok",
573
+ "trigger": "direct",
574
+ },
575
+ "type": "count",
576
+ "value": 1,
577
+ },
578
+ {
579
+ "name": "webhook_poll_requested_boundary_advance_blocks",
580
+ "tags": {
581
+ "chain_id": "4217",
582
+ "outcome": "ok",
583
+ "trigger": "direct",
584
+ },
585
+ "type": "histogram",
586
+ "value": 10,
587
+ },
588
+ {
589
+ "name": "webhook_poll_failure_count",
590
+ "tags": {
591
+ "chain_id": "4217",
592
+ "failure": "remote",
593
+ "stage": "finish",
594
+ "trigger": "direct",
595
+ },
596
+ "type": "count",
597
+ "value": 1,
598
+ },
599
+ {
600
+ "name": "webhook_poller_stage_duration_ms",
601
+ "tags": {
602
+ "chain_id": "4217",
603
+ "event_type": "token:transfer",
604
+ "outcome": "ok",
605
+ "stage": "scan",
606
+ },
607
+ "type": "histogram",
608
+ "value": 42,
609
+ },
610
+ {
611
+ "name": "webhook_poller_tick_count",
612
+ "tags": {
613
+ "chain_id": "4217",
614
+ "outcome": "error",
615
+ },
616
+ "type": "count",
617
+ "value": 1,
618
+ },
619
+ {
620
+ "name": "webhook_poller_tick_duration_ms",
621
+ "tags": {
622
+ "chain_id": "4217",
623
+ "outcome": "error",
624
+ },
625
+ "type": "histogram",
626
+ "value": 50,
627
+ },
628
+ {
629
+ "name": "webhook_subscription_cursor_lag_blocks",
630
+ "tags": {
631
+ "chain_id": "4217",
632
+ },
633
+ "type": "gauge",
634
+ "value": 7,
635
+ },
636
+ ]
637
+ `)
638
+ })
639
+
640
+ test('records obligation staging and claim outcomes', () => {
641
+ const events: MetricEvent[] = []
642
+ const metrics = Metrics.from({
643
+ count(name, value, tags) {
644
+ events.push({ name, tags, type: 'count', value })
645
+ },
646
+ flush() {},
647
+ gauge() {},
648
+ histogram() {},
649
+ })
650
+ const sink = MetricSink.webhooks(metrics)
651
+
652
+ sink.record({ count: 3, outcome: 'created', type: 'webhook:delivery-job-ensure' })
653
+ sink.record({ count: 2, outcome: 'existing', type: 'webhook:delivery-job-ensure' })
654
+ sink.record({ outcome: 'claimed', type: 'webhook:delivery-job-claim' })
655
+ sink.record({ outcome: 'terminal', type: 'webhook:delivery-job-claim' })
656
+
657
+ expect(events).toMatchInlineSnapshot(`
658
+ [
659
+ {
660
+ "name": "webhook_delivery_job_ensure_count",
661
+ "tags": {
662
+ "outcome": "created",
663
+ },
664
+ "type": "count",
665
+ "value": 3,
666
+ },
667
+ {
668
+ "name": "webhook_delivery_job_ensure_count",
669
+ "tags": {
670
+ "outcome": "existing",
671
+ },
672
+ "type": "count",
673
+ "value": 2,
674
+ },
675
+ {
676
+ "name": "webhook_delivery_job_claim_count",
677
+ "tags": {
678
+ "outcome": "claimed",
679
+ },
680
+ "type": "count",
681
+ "value": 1,
682
+ },
683
+ {
684
+ "name": "webhook_delivery_job_claim_count",
685
+ "tags": {
686
+ "outcome": "terminal",
687
+ },
688
+ "type": "count",
689
+ "value": 1,
690
+ },
691
+ ]
692
+ `)
693
+ })
694
+
695
+ test('records scan boundaries with bounded workload dimensions', () => {
696
+ const events: MetricEvent[] = []
697
+ const metrics = Metrics.from({
698
+ count(name, value, tags) {
699
+ events.push({ name, tags, type: 'count', value })
700
+ },
701
+ flush() {},
702
+ gauge() {},
703
+ histogram(name, value, tags) {
704
+ events.push({ name, tags, type: 'histogram', value })
705
+ },
706
+ })
707
+ const sink = MetricSink.webhooks(metrics)
708
+
709
+ sink.record({
710
+ chainId: 4217,
711
+ concurrency: 8,
712
+ eventType: 'log:emitted',
713
+ maxWindowBlocks: 1_000,
714
+ phase: 'start',
715
+ rpcMethod: 'eth_getLogs',
716
+ subscriptionCount: 8,
717
+ subscriptionLimit: 256,
718
+ totalWindowBlocks: 8_000,
719
+ type: 'webhook:poll-scan',
720
+ })
721
+ sink.record({
722
+ chainId: 4217,
723
+ concurrency: 8,
724
+ durationMs: 250,
725
+ eventType: 'log:emitted',
726
+ failedSubscriptions: 1,
727
+ matchedItems: 700,
728
+ maxWindowBlocks: 1_000,
729
+ outcome: 'error',
730
+ phase: 'complete',
731
+ rpcMethod: 'eth_getLogs',
732
+ subscriptionCount: 8,
733
+ subscriptionLimit: 256,
734
+ totalWindowBlocks: 8_000,
735
+ type: 'webhook:poll-scan',
736
+ })
737
+
738
+ expect(events.map(({ name, tags, value }) => ({ name, tags, value }))).toMatchInlineSnapshot(`
739
+ [
740
+ {
741
+ "name": "webhook_poller_scan_checkpoint_count",
742
+ "tags": {
743
+ "chain_id": "4217",
744
+ "event_type": "log:emitted",
745
+ "phase": "start",
746
+ },
747
+ "value": 1,
748
+ },
749
+ {
750
+ "name": "webhook_poller_scan_concurrency",
751
+ "tags": {
752
+ "chain_id": "4217",
753
+ "event_type": "log:emitted",
754
+ "phase": "start",
755
+ },
756
+ "value": 8,
757
+ },
758
+ {
759
+ "name": "webhook_poller_scan_subscription_count",
760
+ "tags": {
761
+ "chain_id": "4217",
762
+ "event_type": "log:emitted",
763
+ "phase": "start",
764
+ },
765
+ "value": 8,
766
+ },
767
+ {
768
+ "name": "webhook_poller_scan_subscription_limit",
769
+ "tags": {
770
+ "chain_id": "4217",
771
+ "event_type": "log:emitted",
772
+ "phase": "start",
773
+ },
774
+ "value": 256,
775
+ },
776
+ {
777
+ "name": "webhook_poller_scan_max_window_blocks",
778
+ "tags": {
779
+ "chain_id": "4217",
780
+ "event_type": "log:emitted",
781
+ "phase": "start",
782
+ },
783
+ "value": 1000,
784
+ },
785
+ {
786
+ "name": "webhook_poller_scan_total_window_blocks",
787
+ "tags": {
788
+ "chain_id": "4217",
789
+ "event_type": "log:emitted",
790
+ "phase": "start",
791
+ },
792
+ "value": 8000,
793
+ },
794
+ {
795
+ "name": "webhook_poller_scan_checkpoint_count",
796
+ "tags": {
797
+ "chain_id": "4217",
798
+ "event_type": "log:emitted",
799
+ "phase": "complete",
800
+ },
801
+ "value": 1,
802
+ },
803
+ {
804
+ "name": "webhook_poller_scan_concurrency",
805
+ "tags": {
806
+ "chain_id": "4217",
807
+ "event_type": "log:emitted",
808
+ "phase": "complete",
809
+ },
810
+ "value": 8,
811
+ },
812
+ {
813
+ "name": "webhook_poller_scan_subscription_count",
814
+ "tags": {
815
+ "chain_id": "4217",
816
+ "event_type": "log:emitted",
817
+ "phase": "complete",
818
+ },
819
+ "value": 8,
820
+ },
821
+ {
822
+ "name": "webhook_poller_scan_subscription_limit",
823
+ "tags": {
824
+ "chain_id": "4217",
825
+ "event_type": "log:emitted",
826
+ "phase": "complete",
827
+ },
828
+ "value": 256,
829
+ },
830
+ {
831
+ "name": "webhook_poller_scan_max_window_blocks",
832
+ "tags": {
833
+ "chain_id": "4217",
834
+ "event_type": "log:emitted",
835
+ "phase": "complete",
836
+ },
837
+ "value": 1000,
838
+ },
839
+ {
840
+ "name": "webhook_poller_scan_total_window_blocks",
841
+ "tags": {
842
+ "chain_id": "4217",
843
+ "event_type": "log:emitted",
844
+ "phase": "complete",
845
+ },
846
+ "value": 8000,
847
+ },
848
+ {
849
+ "name": "webhook_poller_scan_duration_ms",
850
+ "tags": {
851
+ "chain_id": "4217",
852
+ "event_type": "log:emitted",
853
+ "phase": "complete",
854
+ },
855
+ "value": 250,
856
+ },
857
+ {
858
+ "name": "webhook_poller_scan_failed_subscription_count",
859
+ "tags": {
860
+ "chain_id": "4217",
861
+ "event_type": "log:emitted",
862
+ "phase": "complete",
863
+ },
864
+ "value": 1,
865
+ },
866
+ {
867
+ "name": "webhook_poller_scan_matched_item_count",
868
+ "tags": {
869
+ "chain_id": "4217",
870
+ "event_type": "log:emitted",
871
+ "phase": "complete",
872
+ },
873
+ "value": 700,
874
+ },
875
+ {
876
+ "name": "webhook_poller_rpc_scan_count",
877
+ "tags": {
878
+ "chain_id": "4217",
879
+ "event_type": "log:emitted",
880
+ "outcome": "error",
881
+ "rpc_method": "eth_getLogs",
882
+ },
883
+ "value": 1,
884
+ },
885
+ ]
886
+ `)
887
+ })
888
+
889
+ test('records delivery stages without subscription identity', () => {
890
+ const events: MetricEvent[] = []
891
+ const metrics = Metrics.from({
892
+ count(name, value, tags) {
893
+ events.push({ name, tags, type: 'count', value })
894
+ },
895
+ flush() {},
896
+ gauge() {},
897
+ histogram(name, value, tags) {
898
+ events.push({ name, tags, type: 'histogram', value })
899
+ },
900
+ })
901
+ const sink = MetricSink.webhooks(metrics)
902
+ const subscription = {
903
+ chainId: 4217,
904
+ createdAt: '2026-01-01T00:00:00.000Z',
905
+ destination: { type: 'url', url: 'https://hooks.example.com/endpoint' },
906
+ eventType: 'token:transfer',
907
+ failureCount: 0,
908
+ filters: {},
909
+ id: 'wh_private',
910
+ owner: { orgId: 'org_private', type: 'api_key' },
911
+ secret: 'whsec_private',
912
+ status: 'active',
913
+ updatedAt: '2026-01-01T00:00:00.000Z',
914
+ } satisfies Webhooks.Subscription
915
+ const envelope = {
916
+ chainId: 4217,
917
+ createdAt: '2026-01-01T00:00:00.000Z',
918
+ data: { ping: true },
919
+ id: 'evt_private',
920
+ subscriptionId: subscription.id,
921
+ type: 'ping',
922
+ } satisfies Webhooks.Envelope
923
+
924
+ sink.record({
925
+ attempt: 1,
926
+ envelope,
927
+ queueAttempt: 2,
928
+ result: { durationMs: 25, ok: true, status: 204 },
929
+ subscription,
930
+ timings: {
931
+ deliveryLogMs: 5,
932
+ envelopeToQueueMs: 3,
933
+ endToEndMs: 250,
934
+ eventToEnvelopeMs: 80,
935
+ processingMs: 67,
936
+ queueWaitMs: 100,
937
+ stateWriteMs: 10,
938
+ subscriptionReadMs: 4,
939
+ },
940
+ trigger: 'queue',
941
+ type: 'webhook:delivery',
942
+ })
943
+
944
+ expect(events.map(({ name, value }) => ({ name, value }))).toMatchInlineSnapshot(`
945
+ [
946
+ {
947
+ "name": "webhook_delivery_attempt_count",
948
+ "value": 1,
949
+ },
950
+ {
951
+ "name": "webhook_delivery_duration_ms",
952
+ "value": 25,
953
+ },
954
+ {
955
+ "name": "webhook_delivery_log_write_ms",
956
+ "value": 5,
957
+ },
958
+ {
959
+ "name": "webhook_delivery_envelope_to_queue_ms",
960
+ "value": 3,
961
+ },
962
+ {
963
+ "name": "webhook_delivery_end_to_end_ms",
964
+ "value": 250,
965
+ },
966
+ {
967
+ "name": "webhook_delivery_event_to_envelope_ms",
968
+ "value": 80,
969
+ },
970
+ {
971
+ "name": "webhook_delivery_processing_ms",
972
+ "value": 67,
973
+ },
974
+ {
975
+ "name": "webhook_delivery_queue_wait_ms",
976
+ "value": 100,
977
+ },
978
+ {
979
+ "name": "webhook_delivery_state_write_ms",
980
+ "value": 10,
981
+ },
982
+ {
983
+ "name": "webhook_delivery_subscription_read_ms",
984
+ "value": 4,
985
+ },
986
+ ]
987
+ `)
988
+ expect(events.find((event) => event.name === 'webhook_delivery_attempt_count')?.tags)
989
+ .toMatchInlineSnapshot(`
990
+ {
991
+ "attempt_bucket": "1",
992
+ "chain_id": "4217",
993
+ "destination_type": "url",
994
+ "event_type": "ping",
995
+ "outcome": "succeeded",
996
+ "queue_attempt_bucket": "2",
997
+ "queue_disposition": "acked",
998
+ "status_class": "2xx",
999
+ "trigger": "queue",
1000
+ }
1001
+ `)
1002
+ expect(events.find((event) => event.name === 'webhook_delivery_queue_wait_ms')?.tags)
1003
+ .toMatchInlineSnapshot(`
1004
+ {
1005
+ "chain_id": "4217",
1006
+ "destination_type": "url",
1007
+ "event_type": "ping",
1008
+ "outcome": "succeeded",
1009
+ "queue_attempt_bucket": "2",
1010
+ "queue_disposition": "acked",
1011
+ }
1012
+ `)
1013
+ expect(JSON.stringify(events)).not.toMatch(/evt_private|org_private|wh_private|whsec_private/)
1014
+
1015
+ sink.record({
1016
+ attempt: 3,
1017
+ envelope,
1018
+ queueAttempt: 3,
1019
+ result: { error: 'upstream unavailable', ok: false, status: 503 },
1020
+ subscription,
1021
+ trigger: 'queue',
1022
+ type: 'webhook:delivery',
1023
+ })
1024
+
1025
+ expect(events.at(-1)?.tags).toMatchObject({
1026
+ queue_attempt_bucket: '3-5',
1027
+ queue_disposition: 'scheduled_retry',
1028
+ })
1029
+ })
1030
+ })