tapimo 0.11.0 → 0.13.0

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 (443) hide show
  1. package/dist/App.d.ts +6396 -4575
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +39 -4
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.d.ts +15 -0
  6. package/dist/Assets.d.ts.map +1 -1
  7. package/dist/Assets.js +7 -0
  8. package/dist/Assets.js.map +1 -1
  9. package/dist/Scope.d.ts +2 -0
  10. package/dist/Scope.d.ts.map +1 -1
  11. package/dist/Scope.js +17 -0
  12. package/dist/Scope.js.map +1 -1
  13. package/dist/admin/App.d.ts +176 -78
  14. package/dist/admin/App.d.ts.map +1 -1
  15. package/dist/admin/apps/api-keys.d.ts +84 -4
  16. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.js +54 -0
  18. package/dist/admin/apps/api-keys.js.map +1 -1
  19. package/dist/admin/apps/earn-vaults.d.ts +123 -9
  20. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  21. package/dist/admin/apps/earn-vaults.js +60 -7
  22. package/dist/admin/apps/earn-vaults.js.map +1 -1
  23. package/dist/admin/apps/organizations.d.ts.map +1 -1
  24. package/dist/admin/apps/organizations.js +4 -2
  25. package/dist/admin/apps/organizations.js.map +1 -1
  26. package/dist/admin/apps/verified-tokens.d.ts +90 -90
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.js +71 -71
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +16 -16
  31. package/dist/apps/Zones.js +4 -4
  32. package/dist/apps/data/App.d.ts +1699 -749
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/App.js +58 -9
  35. package/dist/apps/data/App.js.map +1 -1
  36. package/dist/apps/data/ZoneSelection.d.ts +62 -0
  37. package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
  38. package/dist/apps/data/ZoneSelection.js +44 -0
  39. package/dist/apps/data/ZoneSelection.js.map +1 -0
  40. package/dist/apps/data/routes/activities.d.ts +2417 -50
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +1896 -1293
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +471 -340
  45. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.d.ts +12 -12
  47. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  48. package/dist/apps/data/routes/balances.js +44 -17
  49. package/dist/apps/data/routes/balances.js.map +1 -1
  50. package/dist/apps/data/routes/blocks.d.ts +42 -42
  51. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  52. package/dist/apps/data/routes/blocks.js +22 -12
  53. package/dist/apps/data/routes/blocks.js.map +1 -1
  54. package/dist/apps/data/routes/coingecko.d.ts +179 -179
  55. package/dist/apps/data/routes/earn.d.ts +144 -19
  56. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  57. package/dist/apps/data/routes/earn.js +160 -28
  58. package/dist/apps/data/routes/earn.js.map +1 -1
  59. package/dist/apps/data/routes/exchanges.d.ts +719 -13
  60. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  61. package/dist/apps/data/routes/exchanges.js +16 -13
  62. package/dist/apps/data/routes/exchanges.js.map +1 -1
  63. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  64. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  65. package/dist/apps/data/routes/fee-amm.js +9 -11
  66. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  67. package/dist/apps/data/routes/indexer.d.ts +34 -34
  68. package/dist/apps/data/routes/receipts.d.ts +363 -263
  69. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  70. package/dist/apps/data/routes/receipts.js +29 -11
  71. package/dist/apps/data/routes/receipts.js.map +1 -1
  72. package/dist/apps/data/routes/rpc.js +24 -10
  73. package/dist/apps/data/routes/rpc.js.map +1 -1
  74. package/dist/apps/data/routes/tokenlist.d.ts +13 -13
  75. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  76. package/dist/apps/data/routes/tokenlist.js +16 -15
  77. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  78. package/dist/apps/data/routes/tokens.d.ts +112 -98
  79. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  80. package/dist/apps/data/routes/tokens.js +85 -35
  81. package/dist/apps/data/routes/tokens.js.map +1 -1
  82. package/dist/apps/data/routes/transactions.d.ts +7 -2
  83. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  84. package/dist/apps/data/routes/transactions.js +38 -18
  85. package/dist/apps/data/routes/transactions.js.map +1 -1
  86. package/dist/apps/data/routes/transfers.d.ts +1 -1
  87. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  88. package/dist/apps/data/routes/transfers.js +25 -21
  89. package/dist/apps/data/routes/transfers.js.map +1 -1
  90. package/dist/apps/data/routes/valuation.d.ts +1 -1
  91. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  92. package/dist/apps/data/routes/valuation.js +12 -7
  93. package/dist/apps/data/routes/valuation.js.map +1 -1
  94. package/dist/apps/data/routes/verified-tokens.d.ts +3 -3
  95. package/dist/apps/data/routes/webhooks.d.ts +15 -15
  96. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  97. package/dist/apps/data/routes/webhooks.js +2 -2
  98. package/dist/apps/data/routes/webhooks.js.map +1 -1
  99. package/dist/apps/data/routes/zones.d.ts +32 -32
  100. package/dist/apps/data/routes/zones.js +1 -1
  101. package/dist/apps/funding/App.d.ts +38 -27
  102. package/dist/apps/funding/App.d.ts.map +1 -1
  103. package/dist/apps/funding/routes/chains.d.ts.map +1 -1
  104. package/dist/apps/funding/routes/chains.js +5 -4
  105. package/dist/apps/funding/routes/chains.js.map +1 -1
  106. package/dist/apps/funding/routes/deposit-addresses.d.ts +16 -16
  107. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  108. package/dist/apps/funding/routes/deposit-addresses.js +24 -19
  109. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  110. package/dist/apps/funding/routes/providers.d.ts +13 -13
  111. package/dist/apps/funding/routes/providers.d.ts.map +1 -1
  112. package/dist/apps/funding/routes/providers.js +3 -2
  113. package/dist/apps/funding/routes/providers.js.map +1 -1
  114. package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
  115. package/dist/apps/funding/routes/quotes.js +2 -2
  116. package/dist/apps/funding/routes/quotes.js.map +1 -1
  117. package/dist/apps/funding/routes/transfers.d.ts +60 -48
  118. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  119. package/dist/apps/funding/routes/transfers.js +27 -19
  120. package/dist/apps/funding/routes/transfers.js.map +1 -1
  121. package/dist/apps/management/App.d.ts +4558 -3700
  122. package/dist/apps/management/App.d.ts.map +1 -1
  123. package/dist/apps/management/Billing.d.ts.map +1 -1
  124. package/dist/apps/management/Billing.js +19 -6
  125. package/dist/apps/management/Billing.js.map +1 -1
  126. package/dist/apps/management/routes/api-keys.d.ts +786 -175
  127. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  128. package/dist/apps/management/routes/api-keys.js +312 -34
  129. package/dist/apps/management/routes/api-keys.js.map +1 -1
  130. package/dist/apps/management/routes/billing.d.ts +103 -30
  131. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  132. package/dist/apps/management/routes/billing.js +91 -32
  133. package/dist/apps/management/routes/billing.js.map +1 -1
  134. package/dist/apps/management/routes/faucet.d.ts +35 -35
  135. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  136. package/dist/apps/management/routes/faucet.js +23 -12
  137. package/dist/apps/management/routes/faucet.js.map +1 -1
  138. package/dist/apps/management/routes/invitations.d.ts +46 -13
  139. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  140. package/dist/apps/management/routes/invitations.js +79 -29
  141. package/dist/apps/management/routes/invitations.js.map +1 -1
  142. package/dist/apps/management/routes/invite-links.d.ts +120 -75
  143. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  144. package/dist/apps/management/routes/invite-links.js +100 -41
  145. package/dist/apps/management/routes/invite-links.js.map +1 -1
  146. package/dist/apps/management/routes/me.d.ts +5 -5
  147. package/dist/apps/management/routes/me.d.ts.map +1 -1
  148. package/dist/apps/management/routes/me.js +14 -10
  149. package/dist/apps/management/routes/me.js.map +1 -1
  150. package/dist/apps/management/routes/members.d.ts +99 -78
  151. package/dist/apps/management/routes/members.d.ts.map +1 -1
  152. package/dist/apps/management/routes/members.js +37 -18
  153. package/dist/apps/management/routes/members.js.map +1 -1
  154. package/dist/apps/management/routes/orgs.d.ts +48 -8
  155. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  156. package/dist/apps/management/routes/orgs.js +48 -19
  157. package/dist/apps/management/routes/orgs.js.map +1 -1
  158. package/dist/apps/management/routes/projects.d.ts +108 -46
  159. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  160. package/dist/apps/management/routes/projects.js +72 -21
  161. package/dist/apps/management/routes/projects.js.map +1 -1
  162. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  163. package/dist/apps/management/routes/scopes.js +8 -5
  164. package/dist/apps/management/routes/scopes.js.map +1 -1
  165. package/dist/apps/management/routes/usage.d.ts +82 -82
  166. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  167. package/dist/apps/management/routes/usage.js +41 -31
  168. package/dist/apps/management/routes/usage.js.map +1 -1
  169. package/dist/apps/management/routes/verified-token-requests.d.ts +35 -7
  170. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
  171. package/dist/apps/management/routes/verified-token-requests.js +36 -17
  172. package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
  173. package/dist/apps/management/routes/webhooks.d.ts +200 -183
  174. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  175. package/dist/apps/management/routes/webhooks.js +80 -35
  176. package/dist/apps/management/routes/webhooks.js.map +1 -1
  177. package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
  178. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  179. package/dist/apps/mpp/internal/Adapter.js +12 -0
  180. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  181. package/dist/db/Db.d.ts +2 -0
  182. package/dist/db/Db.d.ts.map +1 -1
  183. package/dist/db/Db.js +52 -1
  184. package/dist/db/Db.js.map +1 -1
  185. package/dist/db/Schema.d.ts +26 -3
  186. package/dist/db/Schema.d.ts.map +1 -1
  187. package/dist/db/Schema.js +24 -5
  188. package/dist/db/Schema.js.map +1 -1
  189. package/dist/db/tables/earnVaults.d.ts +5 -1
  190. package/dist/db/tables/earnVaults.d.ts.map +1 -1
  191. package/dist/db/tables/earnVaults.js +5 -0
  192. package/dist/db/tables/earnVaults.js.map +1 -1
  193. package/dist/db/tables/fundingCatalog.d.ts +4 -2
  194. package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
  195. package/dist/db/tables/fundingCatalog.js +5 -1
  196. package/dist/db/tables/fundingCatalog.js.map +1 -1
  197. package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
  198. package/dist/db/tables/webhookDeliveries.js +10 -0
  199. package/dist/db/tables/webhookDeliveries.js.map +1 -1
  200. package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
  201. package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
  202. package/dist/db/tables/webhookQueueCompletions.js +32 -0
  203. package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
  204. package/dist/db/tables/webhookQueueEvents.d.ts +14 -20
  205. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
  206. package/dist/db/tables/webhookQueueEvents.js +68 -47
  207. package/dist/db/tables/webhookQueueEvents.js.map +1 -1
  208. package/dist/db/tables/webhookSubscriptions.d.ts +16 -5
  209. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  210. package/dist/db/tables/webhookSubscriptions.js +29 -5
  211. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  212. package/dist/handlers/internal/utils.d.ts.map +1 -1
  213. package/dist/handlers/internal/utils.js +9 -0
  214. package/dist/handlers/internal/utils.js.map +1 -1
  215. package/dist/internal/Auth.d.ts +11 -2
  216. package/dist/internal/Auth.d.ts.map +1 -1
  217. package/dist/internal/Auth.js +19 -6
  218. package/dist/internal/Auth.js.map +1 -1
  219. package/dist/internal/Cache.d.ts.map +1 -1
  220. package/dist/internal/Cache.js +11 -7
  221. package/dist/internal/Cache.js.map +1 -1
  222. package/dist/internal/EarnVaults.d.ts +43 -4
  223. package/dist/internal/EarnVaults.d.ts.map +1 -1
  224. package/dist/internal/EarnVaults.js +167 -0
  225. package/dist/internal/EarnVaults.js.map +1 -1
  226. package/dist/internal/Fetch.d.ts +23 -0
  227. package/dist/internal/Fetch.d.ts.map +1 -0
  228. package/dist/internal/Fetch.js +26 -0
  229. package/dist/internal/Fetch.js.map +1 -0
  230. package/dist/internal/Log.d.ts +3 -1
  231. package/dist/internal/Log.d.ts.map +1 -1
  232. package/dist/internal/Log.js +16 -3
  233. package/dist/internal/Log.js.map +1 -1
  234. package/dist/internal/OpenApi.d.ts +30 -24
  235. package/dist/internal/OpenApi.d.ts.map +1 -1
  236. package/dist/internal/OpenApi.js +140 -41
  237. package/dist/internal/OpenApi.js.map +1 -1
  238. package/dist/internal/Response.d.ts +14 -9
  239. package/dist/internal/Response.d.ts.map +1 -1
  240. package/dist/internal/Response.js +16 -5
  241. package/dist/internal/Response.js.map +1 -1
  242. package/dist/internal/Schema.d.ts +11 -10
  243. package/dist/internal/Schema.d.ts.map +1 -1
  244. package/dist/internal/Schema.js +14 -9
  245. package/dist/internal/Schema.js.map +1 -1
  246. package/dist/internal/Store.d.ts +28 -6
  247. package/dist/internal/Store.d.ts.map +1 -1
  248. package/dist/internal/Store.js +121 -25
  249. package/dist/internal/Store.js.map +1 -1
  250. package/dist/internal/Tidx.d.ts +2 -0
  251. package/dist/internal/Tidx.d.ts.map +1 -1
  252. package/dist/internal/Tidx.js +23 -6
  253. package/dist/internal/Tidx.js.map +1 -1
  254. package/dist/internal/Timeout.d.ts +36 -0
  255. package/dist/internal/Timeout.d.ts.map +1 -0
  256. package/dist/internal/Timeout.js +37 -0
  257. package/dist/internal/Timeout.js.map +1 -0
  258. package/dist/internal/Timing.d.ts +9 -0
  259. package/dist/internal/Timing.d.ts.map +1 -1
  260. package/dist/internal/Timing.js +7 -0
  261. package/dist/internal/Timing.js.map +1 -1
  262. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  263. package/dist/internal/WebhookDestination.js +3 -0
  264. package/dist/internal/WebhookDestination.js.map +1 -1
  265. package/dist/internal/Webhooks.d.ts +29 -12
  266. package/dist/internal/Webhooks.d.ts.map +1 -1
  267. package/dist/internal/Webhooks.js +37 -20
  268. package/dist/internal/Webhooks.js.map +1 -1
  269. package/dist/internal/funding/Action.d.ts +9 -0
  270. package/dist/internal/funding/Action.d.ts.map +1 -1
  271. package/dist/internal/funding/Action.js +16 -8
  272. package/dist/internal/funding/Action.js.map +1 -1
  273. package/dist/internal/funding/Catalog.d.ts +9 -2
  274. package/dist/internal/funding/Catalog.d.ts.map +1 -1
  275. package/dist/internal/funding/Catalog.js +19 -8
  276. package/dist/internal/funding/Catalog.js.map +1 -1
  277. package/dist/internal/funding/Deposit.d.ts +2 -2
  278. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  279. package/dist/internal/funding/Deposit.js +9 -8
  280. package/dist/internal/funding/Deposit.js.map +1 -1
  281. package/dist/internal/funding/DepositAddress.d.ts +4 -4
  282. package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
  283. package/dist/internal/funding/DepositAddress.js +9 -8
  284. package/dist/internal/funding/DepositAddress.js.map +1 -1
  285. package/dist/internal/funding/Provider.d.ts +68 -13
  286. package/dist/internal/funding/Provider.d.ts.map +1 -1
  287. package/dist/internal/funding/Provider.js +42 -20
  288. package/dist/internal/funding/Provider.js.map +1 -1
  289. package/dist/internal/funding/Transfer.d.ts +13 -12
  290. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  291. package/dist/internal/funding/Transfer.js +23 -21
  292. package/dist/internal/funding/Transfer.js.map +1 -1
  293. package/dist/internal/funding/providers/stargate.d.ts +38 -0
  294. package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
  295. package/dist/internal/funding/providers/stargate.js +277 -0
  296. package/dist/internal/funding/providers/stargate.js.map +1 -0
  297. package/dist/internal/index.d.ts +1 -0
  298. package/dist/internal/index.d.ts.map +1 -1
  299. package/dist/internal/index.js +1 -0
  300. package/dist/internal/index.js.map +1 -1
  301. package/dist/providers/index.d.ts +7 -0
  302. package/dist/providers/index.d.ts.map +1 -1
  303. package/dist/providers/index.js +3 -0
  304. package/dist/providers/index.js.map +1 -1
  305. package/package.json +3 -3
  306. package/src/App.test.ts +148 -0
  307. package/src/App.ts +45 -4
  308. package/src/Assets.test.ts +18 -0
  309. package/src/Assets.ts +22 -0
  310. package/src/Client.test-d.ts +311 -16
  311. package/src/Scope.test.ts +20 -0
  312. package/src/Scope.ts +16 -0
  313. package/src/admin/apps/api-keys.test.ts +75 -0
  314. package/src/admin/apps/api-keys.ts +64 -0
  315. package/src/admin/apps/earn-vaults.test.ts +143 -0
  316. package/src/admin/apps/earn-vaults.ts +84 -7
  317. package/src/admin/apps/organizations.ts +6 -2
  318. package/src/admin/ui/src/lib/api-keys.ts +19 -0
  319. package/src/admin/ui/src/routes/api-keys.tsx +125 -2
  320. package/src/admin/ui.generated.ts +71 -71
  321. package/src/apps/Zones.test.ts +4 -4
  322. package/src/apps/Zones.ts +4 -4
  323. package/src/apps/composition.test.ts +1 -1
  324. package/src/apps/data/App.test.ts +72 -0
  325. package/src/apps/data/App.ts +58 -10
  326. package/src/apps/data/ZoneSelection.test.ts +196 -0
  327. package/src/apps/data/ZoneSelection.ts +108 -0
  328. package/src/apps/data/routes/activities.test.ts +1843 -2
  329. package/src/apps/data/routes/activities.ts +2211 -1388
  330. package/src/apps/data/routes/balances.ts +134 -98
  331. package/src/apps/data/routes/blocks.test.ts +533 -0
  332. package/src/apps/data/routes/blocks.ts +36 -17
  333. package/src/apps/data/routes/earn.test.ts +187 -25
  334. package/src/apps/data/routes/earn.ts +228 -32
  335. package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
  336. package/src/apps/data/routes/exchanges.test.ts +31 -1
  337. package/src/apps/data/routes/exchanges.ts +19 -15
  338. package/src/apps/data/routes/fee-amm.ts +18 -11
  339. package/src/apps/data/routes/receipts.ts +48 -17
  340. package/src/apps/data/routes/rpc.test.ts +75 -0
  341. package/src/apps/data/routes/rpc.ts +23 -9
  342. package/src/apps/data/routes/tokenlist.ts +21 -18
  343. package/src/apps/data/routes/tokens.test.ts +607 -0
  344. package/src/apps/data/routes/tokens.ts +312 -231
  345. package/src/apps/data/routes/transactions.ts +69 -30
  346. package/src/apps/data/routes/transfers.test.ts +424 -3
  347. package/src/apps/data/routes/transfers.ts +57 -36
  348. package/src/apps/data/routes/valuation.ts +94 -78
  349. package/src/apps/data/routes/webhooks.ts +6 -2
  350. package/src/apps/data/routes/zones.test.ts +21 -15
  351. package/src/apps/data/routes/zones.ts +1 -1
  352. package/src/apps/funding/routes/chains.test.ts +211 -7
  353. package/src/apps/funding/routes/chains.ts +19 -12
  354. package/src/apps/funding/routes/deposit-addresses.test.ts +416 -0
  355. package/src/apps/funding/routes/deposit-addresses.ts +101 -84
  356. package/src/apps/funding/routes/providers.ts +9 -5
  357. package/src/apps/funding/routes/quotes.test.ts +42 -0
  358. package/src/apps/funding/routes/quotes.ts +8 -5
  359. package/src/apps/funding/routes/transfers.test.ts +430 -40
  360. package/src/apps/funding/routes/transfers.ts +76 -59
  361. package/src/apps/management/Billing.test.ts +26 -0
  362. package/src/apps/management/Billing.ts +24 -7
  363. package/src/apps/management/routes/api-keys.test.ts +398 -0
  364. package/src/apps/management/routes/api-keys.ts +481 -145
  365. package/src/apps/management/routes/billing.test.ts +209 -0
  366. package/src/apps/management/routes/billing.ts +254 -177
  367. package/src/apps/management/routes/faucet.test.ts +39 -0
  368. package/src/apps/management/routes/faucet.ts +75 -41
  369. package/src/apps/management/routes/invitations.test.ts +216 -0
  370. package/src/apps/management/routes/invitations.ts +186 -109
  371. package/src/apps/management/routes/invite-links.test.ts +263 -1
  372. package/src/apps/management/routes/invite-links.ts +295 -214
  373. package/src/apps/management/routes/me.test.ts +49 -0
  374. package/src/apps/management/routes/me.ts +42 -32
  375. package/src/apps/management/routes/members.test.ts +124 -1
  376. package/src/apps/management/routes/members.ts +81 -51
  377. package/src/apps/management/routes/orgs.test.ts +176 -0
  378. package/src/apps/management/routes/orgs.ts +101 -59
  379. package/src/apps/management/routes/projects.test.ts +183 -0
  380. package/src/apps/management/routes/projects.ts +124 -60
  381. package/src/apps/management/routes/scopes.test.ts +44 -0
  382. package/src/apps/management/routes/scopes.ts +37 -28
  383. package/src/apps/management/routes/usage.test.ts +92 -0
  384. package/src/apps/management/routes/usage.ts +186 -151
  385. package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
  386. package/src/apps/management/routes/verified-token-requests.ts +166 -133
  387. package/src/apps/management/routes/webhooks.test.ts +234 -0
  388. package/src/apps/management/routes/webhooks.ts +191 -78
  389. package/src/apps/mpp/internal/Adapter.ts +13 -1
  390. package/src/db/Db.test.ts +41 -0
  391. package/src/db/Db.ts +75 -1
  392. package/src/db/Schema.ts +33 -5
  393. package/src/db/tables/earnVaults.test.ts +22 -0
  394. package/src/db/tables/earnVaults.ts +11 -1
  395. package/src/db/tables/fundingCatalog.test.ts +16 -2
  396. package/src/db/tables/fundingCatalog.ts +9 -3
  397. package/src/db/tables/webhookDeliveries.ts +13 -0
  398. package/src/db/tables/webhookQueueCompletions.ts +55 -0
  399. package/src/db/tables/webhookQueueEvents.ts +95 -61
  400. package/src/db/tables/webhookSubscriptions.ts +45 -6
  401. package/src/handlers/internal/utils.test.ts +53 -0
  402. package/src/handlers/internal/utils.ts +15 -0
  403. package/src/internal/Auth.ts +35 -9
  404. package/src/internal/Cache.ts +14 -7
  405. package/src/internal/EarnVaults.capabilities.test.ts +33 -0
  406. package/src/internal/EarnVaults.test.ts +79 -0
  407. package/src/internal/EarnVaults.ts +277 -0
  408. package/src/internal/Fetch.test.ts +46 -0
  409. package/src/internal/Fetch.ts +56 -0
  410. package/src/internal/Log.test.ts +21 -0
  411. package/src/internal/Log.ts +20 -5
  412. package/src/internal/OpenApi.ts +177 -51
  413. package/src/internal/Response.test.ts +95 -0
  414. package/src/internal/Response.ts +41 -3
  415. package/src/internal/Schema.test.ts +18 -0
  416. package/src/internal/Schema.ts +19 -9
  417. package/src/internal/Store.test.ts +364 -0
  418. package/src/internal/Store.ts +153 -27
  419. package/src/internal/Tidx.test.ts +21 -1
  420. package/src/internal/Tidx.ts +23 -6
  421. package/src/internal/Timeout.test.ts +35 -0
  422. package/src/internal/Timeout.ts +73 -0
  423. package/src/internal/Timing.ts +16 -0
  424. package/src/internal/WebhookDestination.ts +3 -0
  425. package/src/internal/Webhooks.test.ts +122 -8
  426. package/src/internal/Webhooks.ts +59 -29
  427. package/src/internal/funding/Action.test.ts +41 -1
  428. package/src/internal/funding/Action.ts +69 -41
  429. package/src/internal/funding/Catalog.test.ts +34 -0
  430. package/src/internal/funding/Catalog.ts +28 -11
  431. package/src/internal/funding/Deposit.ts +71 -64
  432. package/src/internal/funding/DepositAddress.ts +50 -43
  433. package/src/internal/funding/Provider.test.ts +73 -0
  434. package/src/internal/funding/Provider.ts +246 -170
  435. package/src/internal/funding/Transfer.ts +133 -110
  436. package/src/internal/funding/providers/index.test.ts +22 -0
  437. package/src/internal/funding/providers/relay.test.ts +2 -2
  438. package/src/internal/funding/providers/stargate.test.ts +163 -0
  439. package/src/internal/funding/providers/stargate.ts +409 -0
  440. package/src/internal/index.test.ts +1 -0
  441. package/src/internal/index.ts +1 -0
  442. package/src/providers/index.test.ts +1 -0
  443. package/src/providers/index.ts +9 -0
@@ -10,7 +10,7 @@ const slugSuffix = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8)
10
10
  /** Columns of the `earn_vaults` table. */
11
11
  export type Table = Omit<
12
12
  db_Schema.EarnVault,
13
- 'chainId' | 'privateInputTokens' | 'privateOutputTokens'
13
+ 'chainId' | 'privateInputTokens' | 'privateOutputTokens' | 'zones'
14
14
  > & {
15
15
  /** Bigint chain id, read as a string from pg and written as a number. */
16
16
  chainId: ColumnType<string, number, number>
@@ -18,6 +18,8 @@ export type Table = Omit<
18
18
  privateInputTokens: JSONColumnType<db_Schema.EarnVault['privateInputTokens'], string, string>
19
19
  /** Tokens supported for private redemptions. */
20
20
  privateOutputTokens: JSONColumnType<db_Schema.EarnVault['privateOutputTokens'], string, string>
21
+ /** Curated private Zone routes. */
22
+ zones: JSONColumnType<db_Schema.EarnVault['zones'], string, string>
21
23
  }
22
24
 
23
25
  /** A stored curated Earn vault. */
@@ -76,6 +78,7 @@ export async function upsert(db: Db.Db, options: upsert.Options): Promise<Record
76
78
  slug,
77
79
  updatedAt: now,
78
80
  vaultAddress: normalize(options.vaultAddress),
81
+ zones: JSON.stringify(normalizeZones(options.zones)),
79
82
  })
80
83
  .onConflict((oc) =>
81
84
  oc.columns(['chainId', 'vaultAddress']).doUpdateSet({
@@ -85,6 +88,7 @@ export async function upsert(db: Db.Db, options: upsert.Options): Promise<Record
85
88
  privateOutputTokens: JSON.stringify(options.privateOutputTokens),
86
89
  ...(options.slug !== undefined ? { slug } : {}),
87
90
  updatedAt: now,
91
+ zones: JSON.stringify(normalizeZones(options.zones)),
88
92
  }),
89
93
  )
90
94
  .returningAll()
@@ -109,6 +113,8 @@ export declare namespace upsert {
109
113
  slug?: string | undefined
110
114
  /** Earn vault address. */
111
115
  vaultAddress: Address
116
+ /** Curated private Zone routes. */
117
+ zones: db_Schema.EarnVault['zones']
112
118
  }
113
119
  }
114
120
 
@@ -149,3 +155,7 @@ function toRecord(row: Selectable<Table>): Record {
149
155
  function normalize(address: string): Address {
150
156
  return address.toLowerCase() as Address
151
157
  }
158
+
159
+ function normalizeZones(zones: db_Schema.EarnVault['zones']): db_Schema.EarnVault['zones'] {
160
+ return zones.map((zone) => ({ ...zone, earnRouter: normalize(zone.earnRouter) }))
161
+ }
@@ -71,8 +71,12 @@ describe('publish', () => {
71
71
  catalog: head,
72
72
  chainTokens: catalog.chainTokens,
73
73
  chains: catalog.chains,
74
- // Absent `capabilities` persists as null (indicative-only).
75
- routes: catalog.routes.map((route) => ({ ...route, capabilities: null })),
74
+ // Optional route JSON persists as null.
75
+ routes: catalog.routes.map((route) => ({
76
+ ...route,
77
+ capabilities: null,
78
+ configuration: null,
79
+ })),
76
80
  tokens: catalog.tokens,
77
81
  })
78
82
  })
@@ -87,6 +91,16 @@ describe('publish', () => {
87
91
  expect((await FundingCatalog.read(db)).routes[0]?.capabilities).toStrictEqual(capabilities)
88
92
  })
89
93
 
94
+ test('round-trips route configuration', async () => {
95
+ const db = TestApp.database()
96
+ const configuration = { poolAddress: `0x${'11'.repeat(20)}` }
97
+ await FundingCatalog.publish(db, {
98
+ ...catalog,
99
+ routes: [{ ...catalog.routes[0]!, configuration }],
100
+ })
101
+ expect((await FundingCatalog.read(db)).routes[0]?.configuration).toStrictEqual(configuration)
102
+ })
103
+
90
104
  test('replaces rows and advances the catalog version', async () => {
91
105
  const db = TestApp.database()
92
106
  const first = await FundingCatalog.publish(db, catalog)
@@ -28,10 +28,12 @@ export type ChainTokenTable = db_Schema.FundingChainToken
28
28
  /** Columns of the `funding_routes` table. */
29
29
  export type RouteTable = db_Schema.FundingRoute
30
30
 
31
- /** A route row whose `capabilities` may be absent (validated input, fixtures). */
32
- export type RouteRow = Omit<db_Schema.FundingRoute, 'capabilities'> & {
31
+ /** A route row whose optional JSON fields may be absent in validated input. */
32
+ export type RouteRow = Omit<db_Schema.FundingRoute, 'capabilities' | 'configuration'> & {
33
33
  /** Executable capability; absent and null both mean indicative-only. */
34
34
  capabilities?: db_Schema.FundingRoute['capabilities'] | undefined
35
+ /** Provider-specific route configuration; absent and null are equivalent. */
36
+ configuration?: db_Schema.FundingRoute['configuration'] | undefined
35
37
  }
36
38
 
37
39
  /** Stored funding catalog rows and version metadata. */
@@ -117,7 +119,11 @@ export function publish(db: Db.Db, input: publish.Input): Promise<db_Schema.Fund
117
119
  await tx.kysely
118
120
  .insertInto('funding_routes')
119
121
  .values(
120
- input.routes.map((route) => ({ ...route, capabilities: route.capabilities ?? null })),
122
+ input.routes.map((route) => ({
123
+ ...route,
124
+ capabilities: route.capabilities ?? null,
125
+ configuration: route.configuration ?? null,
126
+ })),
121
127
  )
122
128
  .execute()
123
129
 
@@ -225,6 +225,10 @@ export async function get(
225
225
  return row ? toDelivery(row) : null
226
226
  }
227
227
 
228
+ // Per-statement delete bound: prune runs every minute, so a backlog drains in
229
+ // a few ticks instead of one unbounded DELETE churning the origin.
230
+ const pruneBatchLimit = 50_000
231
+
228
232
  /**
229
233
  * Deletes deliveries past their retention deadline. Called best-effort from
230
234
  * the poller tick.
@@ -236,7 +240,16 @@ export async function get(
236
240
  export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
237
241
  const result = await db.kysely
238
242
  .deleteFrom('webhook_deliveries')
243
+ // Repeated on the outer delete: the row recheck under concurrency re-runs
244
+ // only this predicate, so a row renewed mid-statement survives.
239
245
  .where('expiresAt', '<=', now)
246
+ .where('id', 'in', (eb) =>
247
+ eb
248
+ .selectFrom('webhook_deliveries')
249
+ .select('id')
250
+ .where('expiresAt', '<=', now)
251
+ .limit(pruneBatchLimit),
252
+ )
240
253
  .executeTakeFirst()
241
254
  return Number(result.numDeletedRows)
242
255
  }
@@ -0,0 +1,55 @@
1
+ import type * as Webhooks from '../../internal/Webhooks.js'
2
+ import type * as Db from '../Db.js'
3
+ import type * as db_Schema from '../Schema.js'
4
+
5
+ /** Columns of the `webhook_queue_completions` table, derived from `Schema.WebhookQueueCompletion`. */
6
+ export type Table = db_Schema.WebhookQueueCompletion
7
+
8
+ /** A terminal delivery-obligation outcome. */
9
+ export type TerminalStatus = db_Schema.WebhookQueueCompletion['status']
10
+
11
+ /**
12
+ * Reads the unexpired completion for a reference, if any. Rows are written by
13
+ * the queue-event terminal transition and only ever read for dedupe.
14
+ */
15
+ export async function get(
16
+ db: Db.Db,
17
+ reference: Webhooks.QueueReference,
18
+ now: string,
19
+ ): Promise<Table | null> {
20
+ return (
21
+ (await db.kysely
22
+ .selectFrom('webhook_queue_completions')
23
+ .selectAll()
24
+ .where('eventId', '=', reference.eventId)
25
+ .where('subscriptionId', '=', reference.subscriptionId)
26
+ .where('expiresAt', '>', now)
27
+ .executeTakeFirst()) ?? null
28
+ )
29
+ }
30
+
31
+ // Per-statement delete bound: prune runs every minute, so a backlog drains in
32
+ // a few ticks instead of one unbounded DELETE churning the origin.
33
+ const pruneBatchLimit = 50_000
34
+
35
+ /** Deletes expired completions, bounded per call; expiry is filtered at query time, so pruning is space-only. */
36
+ export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
37
+ const result = await db.kysely
38
+ .deleteFrom('webhook_queue_completions')
39
+ // Repeated on the outer delete: the row recheck under concurrency re-runs
40
+ // only this predicate, so a completion renewed mid-statement survives.
41
+ .where('expiresAt', '<=', now)
42
+ .where(({ eb, refTuple, selectFrom }) =>
43
+ eb(
44
+ refTuple('subscriptionId', 'eventId'),
45
+ 'in',
46
+ selectFrom('webhook_queue_completions')
47
+ .select(['subscriptionId', 'eventId'])
48
+ .where('expiresAt', '<=', now)
49
+ .limit(pruneBatchLimit)
50
+ .$asTuple('subscriptionId', 'eventId'),
51
+ ),
52
+ )
53
+ .executeTakeFirst()
54
+ return Number(result.numDeletedRows)
55
+ }
@@ -2,17 +2,20 @@ import { type ExpressionBuilder, type Generated, type Insertable, type Selectabl
2
2
 
3
3
  import type * as Webhooks from '../../internal/Webhooks.js'
4
4
  import type * as Db from '../Db.js'
5
+ import * as WebhookQueueCompletions from './webhookQueueCompletions.js'
5
6
  import type * as db_Schema from '../Schema.js'
6
7
 
7
8
  /** Columns of the `webhook_queue_events` table, derived from `Schema.WebhookQueueEvent`. */
8
9
  export type Table = Omit<
9
10
  db_Schema.WebhookQueueEvent,
10
- 'attemptCount' | 'attemptingAt' | 'nextAttemptAt' | 'observedAt' | 'status'
11
+ 'attemptCount' | 'attemptingAt' | 'expiresAt' | 'nextAttemptAt' | 'observedAt' | 'status'
11
12
  > & {
12
13
  /** Delivery attempts claimed so far; database default 0. */
13
14
  attemptCount: Generated<number>
14
15
  /** ISO timestamp of the current claim; database default null. */
15
16
  attemptingAt: Generated<string | null>
17
+ /** Legacy dedupe deadline; unwritten since completions took over. */
18
+ expiresAt: Generated<string | null>
16
19
  /** ISO retry due time; database default null (due immediately). */
17
20
  nextAttemptAt: Generated<string | null>
18
21
  /** ISO head-observation stamp; database default null (replays carry none). */
@@ -24,12 +27,9 @@ export type Table = Omit<
24
27
  /** A stored webhook Queue event. */
25
28
  export type Record = Selectable<Table>
26
29
 
27
- /** A terminal delivery-obligation outcome. */
28
- export type TerminalStatus = Exclude<db_Schema.WebhookQueueEvent['status'], 'pending'>
29
-
30
30
  /**
31
- * Reads one pending envelope. Terminal rows read as absent so no consumer
32
- * can redeliver a completed obligation or disturb its dedupe row.
31
+ * Reads one pending envelope. The status filter only ever excludes rows
32
+ * completed before the completions split; completions now leave the table.
33
33
  */
34
34
  export async function get(
35
35
  db: Db.Db,
@@ -47,42 +47,56 @@ export async function get(
47
47
  }
48
48
 
49
49
  /**
50
- * Stages envelopes as fresh obligations. Pending conflicts keep their first
51
- * envelope and backoff position; terminal conflicts inside the dedupe window
52
- * suppress the replay; terminal conflicts past it are replaced, so an expired
53
- * dedupe row pruning has not yet reclaimed never blocks a replay.
50
+ * Stages envelopes as fresh obligations. Unexpired completions and any
51
+ * existing row suppress the replay; a replay stages only when neither exists,
52
+ * which a completion past its dedupe window guarantees for completed work.
54
53
  */
55
54
  export async function ensure(db: Db.Db, rows: readonly Insertable<Table>[]): Promise<number> {
56
55
  if (rows.length === 0) return 0
56
+ const values = sql.join(
57
+ rows.map(
58
+ (row) =>
59
+ sql`(${row.subscriptionId}::text, ${row.eventId}::text, ${JSON.stringify(row.envelope)}::jsonb, ${row.createdAt}::text, ${row.nextAttemptAt ?? null}::text, ${row.observedAt ?? null}::text)`,
60
+ ),
61
+ )
62
+ // The VALUES source is raw because kysely has no table constructor for it,
63
+ // but both real tables stay in the builder so WithSchemaPlugin qualifies
64
+ // them (previews isolate by schema; raw identifiers would escape it). The
65
+ // completion check must ride the insert: a separate read would let a
66
+ // completion commit between the statements and re-stage just-finished work.
67
+ // `column1..6` are Postgres's default VALUES names, in the row order above;
68
+ // the window comparison uses each row's own staging time, not a shared clock.
69
+ // Conflicts only ever hit pending rows (or pre-split terminal rows during
70
+ // the drain), and both suppress the replay; a legitimate post-expiry replay
71
+ // meets no conflict because its completed row was deleted.
57
72
  const result = await db.kysely
58
73
  .insertInto('webhook_queue_events')
59
- .values([...rows])
60
- .onConflict((oc) =>
61
- oc
62
- .columns(['subscriptionId', 'eventId'])
63
- .doUpdateSet((eb) => ({
64
- attemptCount: 0,
65
- attemptingAt: null,
66
- createdAt: eb.ref('excluded.createdAt'),
67
- envelope: eb.ref('excluded.envelope'),
68
- expiresAt: eb.ref('excluded.expiresAt'),
69
- nextAttemptAt: eb.ref('excluded.nextAttemptAt'),
70
- // Refreshed with the rest: a replacement keeping the replaced row's
71
- // stamp would measure observation-to-attempt from an observation
72
- // that belongs to a delivery made days earlier.
73
- observedAt: eb.ref('excluded.observedAt'),
74
- status: 'pending',
75
- }))
76
- .where((eb) =>
77
- eb.and([
78
- eb('webhook_queue_events.status', '!=', 'pending'),
79
- eb('webhook_queue_events.expiresAt', '<=', eb.ref('excluded.createdAt')),
80
- ]),
74
+ .columns(['subscriptionId', 'eventId', 'envelope', 'createdAt', 'nextAttemptAt', 'observedAt'])
75
+ .expression((eb) =>
76
+ eb
77
+ .selectFrom(sql`(VALUES ${values})`.as('v'))
78
+ .select([
79
+ sql`v.column1`.as('subscriptionId'),
80
+ sql`v.column2`.as('eventId'),
81
+ sql`v.column3`.as('envelope'),
82
+ sql`v.column4`.as('createdAt'),
83
+ sql`v.column5`.as('nextAttemptAt'),
84
+ sql`v.column6`.as('observedAt'),
85
+ ])
86
+ .where(({ exists, not, selectFrom }) =>
87
+ not(
88
+ exists(
89
+ selectFrom('webhook_queue_completions')
90
+ .select('eventId')
91
+ .where('subscriptionId', '=', sql<string>`v.column1`)
92
+ .where('eventId', '=', sql<string>`v.column2`)
93
+ .where('expiresAt', '>', sql<string>`v.column4`),
94
+ ),
95
+ ),
81
96
  ),
82
97
  )
98
+ .onConflict((oc) => oc.columns(['subscriptionId', 'eventId']).doNothing())
83
99
  .executeTakeFirst()
84
- // The command tag counts inserted rows plus replaced expired rows; live
85
- // conflicts fall out of the conditional update and are not counted.
86
100
  return Number(result.numInsertedOrUpdatedRows ?? 0n)
87
101
  }
88
102
 
@@ -114,13 +128,23 @@ export async function claim(
114
128
  .executeTakeFirst()
115
129
  if (claimed) return { record: claimed, type: 'claimed' }
116
130
 
131
+ // Queue first, completions second: a complete() landing between the reads
132
+ // then still surfaces its completion, so a finished obligation never reads
133
+ // as missing, the one claim outcome alerts treat as a fault.
117
134
  const existing = await db.kysely
118
135
  .selectFrom('webhook_queue_events')
119
136
  .select(['attemptingAt', 'nextAttemptAt', 'status'])
120
137
  .where('subscriptionId', '=', reference.subscriptionId)
121
138
  .where('eventId', '=', reference.eventId)
122
139
  .executeTakeFirst()
123
- if (!existing) return { type: 'missing' }
140
+ if (!existing) {
141
+ // An unexpired completion reads as terminal so claim-outcome metrics keep
142
+ // their meaning; an expired one reads as missing, like a pruned row.
143
+ const completed = await WebhookQueueCompletions.get(db, reference, options.now)
144
+ if (completed) return { status: completed.status, type: 'terminal' }
145
+ return { type: 'missing' }
146
+ }
147
+ // Reachable only for rows completed before the completions split.
124
148
  if (existing.status !== 'pending') return { status: existing.status, type: 'terminal' }
125
149
  if (existing.attemptingAt !== null && existing.attemptingAt > options.staleBefore)
126
150
  return { type: 'attempting' }
@@ -142,12 +166,12 @@ export declare namespace claim {
142
166
  | { record: Record; type: 'claimed' }
143
167
  | { type: 'missing' }
144
168
  | { nextAttemptAt: string | null; type: 'scheduled' }
145
- | { status: TerminalStatus; type: 'terminal' }
169
+ | { status: WebhookQueueCompletions.TerminalStatus; type: 'terminal' }
146
170
  }
147
171
 
148
172
  /**
149
- * Marks a claimed obligation terminal, starting its dedupe retention window;
150
- * pending rows never expire, so this stamp is the only expiry that matters.
173
+ * Marks a claimed obligation terminal: one atomic statement deletes the queue
174
+ * row and writes the insert-only completion that starts the dedupe window.
151
175
  * The `attemptingAt` predicate fences stale claimants.
152
176
  */
153
177
  export async function complete(
@@ -156,14 +180,38 @@ export async function complete(
156
180
  options: complete.Options,
157
181
  ): Promise<boolean> {
158
182
  const result = await db.kysely
159
- .updateTable('webhook_queue_events')
160
- .set({ attemptingAt: null, expiresAt: options.expiresAt, status: options.status })
161
- .where('subscriptionId', '=', reference.subscriptionId)
162
- .where('eventId', '=', reference.eventId)
163
- .where('status', '=', 'pending')
164
- .where('attemptingAt', '=', options.claimedAt)
183
+ .with('done', (qb) =>
184
+ qb
185
+ .deleteFrom('webhook_queue_events')
186
+ .where('subscriptionId', '=', reference.subscriptionId)
187
+ .where('eventId', '=', reference.eventId)
188
+ .where('status', '=', 'pending')
189
+ .where('attemptingAt', '=', options.claimedAt)
190
+ .returning(['subscriptionId', 'eventId']),
191
+ )
192
+ .insertInto('webhook_queue_completions')
193
+ .columns(['subscriptionId', 'eventId', 'status', 'expiresAt'])
194
+ .expression((eb) =>
195
+ eb
196
+ .selectFrom('done')
197
+ .select((s) => [
198
+ 'subscriptionId',
199
+ 'eventId',
200
+ s.val(options.status).as('status'),
201
+ s.val(options.expiresAt).as('expiresAt'),
202
+ ]),
203
+ )
204
+ // Re-completing a replayed obligation: the latest delivery outcome wins
205
+ // and retention restarts, matching pre-split replay semantics; doNothing
206
+ // would also report zero rows and fail a fence that won.
207
+ .onConflict((oc) =>
208
+ oc.columns(['subscriptionId', 'eventId']).doUpdateSet((eb) => ({
209
+ expiresAt: eb.ref('excluded.expiresAt'),
210
+ status: eb.ref('excluded.status'),
211
+ })),
212
+ )
165
213
  .executeTakeFirst()
166
- return result.numUpdatedRows === 1n
214
+ return result.numInsertedOrUpdatedRows === 1n
167
215
  }
168
216
 
169
217
  export declare namespace complete {
@@ -171,10 +219,10 @@ export declare namespace complete {
171
219
  type Options = {
172
220
  /** Claim marker returned by {@link claim}; fences stale claimants. */
173
221
  claimedAt: string
174
- /** Replacement ISO retention deadline for the terminal dedupe row. */
222
+ /** ISO retention deadline for the dedupe completion. */
175
223
  expiresAt: string
176
224
  /** Terminal outcome. */
177
- status: TerminalStatus
225
+ status: WebhookQueueCompletions.TerminalStatus
178
226
  }
179
227
  }
180
228
 
@@ -275,17 +323,3 @@ export declare namespace pending {
275
323
  oldestDueAt: string | null
276
324
  }
277
325
  }
278
-
279
- /**
280
- * Deletes terminal dedupe rows past their retention deadline. Pending rows
281
- * never expire: owed work drains only through terminal transitions
282
- * (delivery, permanent failure, or the skip after auto-disable).
283
- */
284
- export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
285
- const result = await db.kysely
286
- .deleteFrom('webhook_queue_events')
287
- .where('expiresAt', '<=', now)
288
- .where('status', '!=', 'pending')
289
- .executeTakeFirst()
290
- return Number(result.numDeletedRows)
291
- }
@@ -447,37 +447,57 @@ export async function getCursor(db: Db.Db, id: string): Promise<string | null> {
447
447
 
448
448
  /**
449
449
  * Records a successful delivery: resets the consecutive-failure counter and
450
- * stamps `lastDeliveryAt`.
450
+ * stamps `lastDeliveryAt`. Skipped when neither field would move, so
451
+ * deliveries stop contending on the row's lock.
451
452
  *
452
453
  * @param db - The database.
453
454
  * @param id - The subscription id (`wh_…`).
454
- * @param now - Delivery timestamp (ISO).
455
- * @returns The updated subscription, or `undefined` when the row is absent.
455
+ * @param options - Delivery timestamp and stamp-staleness bound.
456
+ * @returns The updated subscription, or `undefined` when nothing was written.
456
457
  */
457
458
  export async function recordSuccess(
458
459
  db: Db.Db,
459
460
  id: string,
460
- now: string,
461
+ options: recordSuccess.Options,
461
462
  ): Promise<Webhooks.Subscription | undefined> {
463
+ const { now, staleBefore } = options
462
464
  const row = await db.kysely
463
465
  .updateTable('webhook_subscriptions')
464
466
  .set({ failureCount: 0, lastDeliveryAt: now, updatedAt: now })
465
467
  .where('id', '=', id)
468
+ .where((eb) =>
469
+ eb.or([
470
+ eb('failureCount', '>', 0),
471
+ eb('lastDeliveryAt', 'is', null),
472
+ eb('lastDeliveryAt', '<', staleBefore),
473
+ ]),
474
+ )
466
475
  .returningAll()
467
476
  .executeTakeFirst()
468
477
  return row ? toSubscription(row) : undefined
469
478
  }
470
479
 
480
+ export declare namespace recordSuccess {
481
+ /** Clock bounds for one success record. */
482
+ type Options = {
483
+ /** Delivery timestamp (ISO). */
484
+ now: string
485
+ /** Rewrite the stamp only when it predates this (ISO). */
486
+ staleBefore: string
487
+ }
488
+ }
489
+
471
490
  /**
472
491
  * Records a failed delivery: atomically increments the consecutive-failure
473
492
  * counter and disables the subscription once it reaches `maxFailures` (raw
474
493
  * fragments use storage column names — they bypass the camelCase mapping).
494
+ * Skipped once disabled at the cap, when nothing would move.
475
495
  *
476
496
  * @param db - The database.
477
497
  * @param id - The subscription id (`wh_…`).
478
498
  * @param maxFailures - Consecutive failures before auto-disabling.
479
499
  * @param now - Failure timestamp (ISO).
480
- * @returns The updated subscription, or `undefined` when the row is absent.
500
+ * @returns The updated subscription, or `undefined` when nothing was written.
481
501
  */
482
502
  export async function recordFailure(
483
503
  db: Db.Db,
@@ -493,11 +513,19 @@ export async function recordFailure(
493
513
  updatedAt: now,
494
514
  })
495
515
  .where('id', '=', id)
516
+ // In-flight failures race the disable and would queue on the row lock. A
517
+ // re-enabled subscription keeps its capped counter; the status arm lets
518
+ // its next failure re-disable it.
519
+ .where((eb) => eb.or([eb('failureCount', '<', maxFailures), eb('status', '!=', 'disabled')]))
496
520
  .returningAll()
497
521
  .executeTakeFirst()
498
522
  return row ? toSubscription(row) : undefined
499
523
  }
500
524
 
525
+ // Smaller than the ledger-table bounds: each subscription delete cascades
526
+ // into deliveries, queue events, and completions, fanning the statement out.
527
+ const pruneBatchLimit = 1_000
528
+
501
529
  /**
502
530
  * Deletes expired subscriptions (their delivery rows cascade). Called
503
531
  * best-effort on a schedule.
@@ -507,10 +535,21 @@ export async function recordFailure(
507
535
  * @returns The number of rows deleted.
508
536
  */
509
537
  export async function pruneExpired(db: Db.Db, now: string): Promise<number> {
538
+ // Bounded like the ledger prunes: this delete cascades into deliveries,
539
+ // queue events, and completions, so it fans out the widest of the four.
510
540
  const result = await db.kysely
511
541
  .deleteFrom('webhook_subscriptions')
512
- .where('expiresAt', 'is not', null)
542
+ // Repeated on the outer delete: the row recheck under concurrency re-runs
543
+ // only this predicate, so a subscription renewed mid-statement survives.
513
544
  .where('expiresAt', '<=', now)
545
+ .where('id', 'in', (eb) =>
546
+ eb
547
+ .selectFrom('webhook_subscriptions')
548
+ .select('id')
549
+ .where('expiresAt', 'is not', null)
550
+ .where('expiresAt', '<=', now)
551
+ .limit(pruneBatchLimit),
552
+ )
514
553
  .executeTakeFirst()
515
554
  return Number(result.numDeletedRows)
516
555
  }
@@ -0,0 +1,53 @@
1
+ import { RpcRequest } from 'ox'
2
+
3
+ import * as Utils from './utils.js'
4
+
5
+ const request = RpcRequest.from({ id: 1, method: 'eth_sendRawTransaction', params: ['0x'] })
6
+
7
+ describe('rpcErrorJson', () => {
8
+ test('maps upstream expired transactions to a transaction-rejected error', () => {
9
+ const error = {
10
+ cause: {
11
+ code: -32603,
12
+ message:
13
+ 'Revm error: transaction expired: current block timestamp 1755616740 >= validBefore 1755616739',
14
+ },
15
+ }
16
+
17
+ expect(Utils.rpcErrorJson(request, error)).toMatchInlineSnapshot(`
18
+ {
19
+ "error": {
20
+ "code": -32003,
21
+ "data": {
22
+ "code": "transaction_expired",
23
+ },
24
+ "message": "Transaction expired.",
25
+ },
26
+ "id": 1,
27
+ "jsonrpc": "2.0",
28
+ }
29
+ `)
30
+ })
31
+
32
+ test('preserves unrelated upstream internal errors', () => {
33
+ const error = {
34
+ code: -32603,
35
+ data: { reason: 'database unavailable' },
36
+ message: 'Revm error: database unavailable',
37
+ }
38
+
39
+ expect(Utils.rpcErrorJson(request, error)).toMatchInlineSnapshot(`
40
+ {
41
+ "error": {
42
+ "code": -32603,
43
+ "data": {
44
+ "reason": "database unavailable",
45
+ },
46
+ "message": "Revm error: database unavailable",
47
+ },
48
+ "id": 1,
49
+ "jsonrpc": "2.0",
50
+ }
51
+ `)
52
+ })
53
+ })
@@ -127,6 +127,21 @@ export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
127
127
  { request },
128
128
  )
129
129
 
130
+ if (
131
+ inner.code === RpcResponse.InternalError.code &&
132
+ /^Revm error: transaction expired(?:\.|: .+)?$/.test(inner.message)
133
+ )
134
+ return RpcResponse.from(
135
+ {
136
+ error: {
137
+ code: RpcResponse.TransactionRejectedError.code,
138
+ data: { code: 'transaction_expired' },
139
+ message: 'Transaction expired.',
140
+ },
141
+ },
142
+ { request },
143
+ )
144
+
130
145
  const { code, data, message } = inner
131
146
  return RpcResponse.from(
132
147
  {