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
@@ -348,6 +348,41 @@ describe('recordSuccess / recordFailure', () => {
348
348
  expect(read?.lastDeliveryAt).toBe('2026-01-01T00:00:00.000Z')
349
349
  })
350
350
 
351
+ test('recordSuccess refreshes the stamp at most once per precision window', async () => {
352
+ const db = TestApp.database()
353
+ const created = await Webhooks.createSubscription(db, createInput())
354
+ const stamped = await Webhooks.recordSuccess(db, created, { now: fixedNow })
355
+ expect(stamped.lastDeliveryAt).toBe(fixedNow().toISOString())
356
+
357
+ // A success moments later has nothing to move: the counter is already zero
358
+ // and the stamp is inside its window, so the row is never rewritten.
359
+ const soon = new Date(fixedNow().getTime() + 1_000)
360
+ await Webhooks.recordSuccess(db, stamped, { now: () => soon })
361
+ const unmoved = await Webhooks.getSubscription(db, apiKeyOwner, created.id)
362
+ expect(unmoved?.lastDeliveryAt, 'the stored stamp must not move').toBe(fixedNow().toISOString())
363
+
364
+ // Past any plausible window; the exact bound is internal.
365
+ const later = new Date(fixedNow().getTime() + 3_600_000)
366
+ await Webhooks.recordSuccess(db, stamped, { now: () => later })
367
+ const moved = await Webhooks.getSubscription(db, apiKeyOwner, created.id)
368
+ expect(moved?.lastDeliveryAt).toBe(later.toISOString())
369
+ })
370
+
371
+ test('recordSuccess always resets a non-zero failure counter', async () => {
372
+ const db = TestApp.database()
373
+ const created = await Webhooks.createSubscription(db, createInput())
374
+ // Stamp first, so only the counter is out of date: the reset must not be
375
+ // suppressed by a fresh stamp.
376
+ const stamped = await Webhooks.recordSuccess(db, created, { now: fixedNow })
377
+ const failed = await Webhooks.recordFailure(db, stamped, { now: fixedNow })
378
+ expect(failed.failureCount).toBe(1)
379
+
380
+ await Webhooks.recordSuccess(db, failed, { now: fixedNow })
381
+
382
+ const read = await Webhooks.getSubscription(db, apiKeyOwner, created.id)
383
+ expect(read?.failureCount, 'a pending reset must never be skipped').toBe(0)
384
+ })
385
+
351
386
  test('recordFailure auto-disables at the threshold and drops from the active index', async () => {
352
387
  const db = TestApp.database()
353
388
  let subscription = await Webhooks.createSubscription(db, createInput())
@@ -375,6 +410,37 @@ describe('recordSuccess / recordFailure', () => {
375
410
  await Webhooks.listActiveForChain(db, { chainId: 4217, eventTypes: ['token:transfer'] }),
376
411
  ).toMatchInlineSnapshot(`[]`)
377
412
  })
413
+
414
+ test('recordFailure leaves an already-disabled subscription alone at the cap', async () => {
415
+ const db = TestApp.database()
416
+ let subscription = await Webhooks.createSubscription(db, createInput())
417
+ subscription = await Webhooks.recordFailure(db, subscription, { maxFailures: 2, now: fixedNow })
418
+ subscription = await Webhooks.recordFailure(db, subscription, { maxFailures: 2, now: fixedNow })
419
+ expect(subscription.status).toBe('disabled')
420
+
421
+ // A straggler failing after the disable has nothing to move: the row is
422
+ // never rewritten.
423
+ const later = new Date(fixedNow().getTime() + 1_000)
424
+ await Webhooks.recordFailure(db, subscription, { maxFailures: 2, now: () => later })
425
+
426
+ const read = await Webhooks.getSubscription(db, apiKeyOwner, subscription.id)
427
+ expect(read?.failureCount, 'the stored counter must not move').toBe(2)
428
+ expect(read?.updatedAt, 'the stored stamp must not move').toBe(fixedNow().toISOString())
429
+ })
430
+
431
+ test('recordFailure re-disables a re-enabled subscription still at the cap', async () => {
432
+ const db = TestApp.database()
433
+ let subscription = await Webhooks.createSubscription(db, createInput())
434
+ subscription = await Webhooks.recordFailure(db, subscription, { maxFailures: 1, now: fixedNow })
435
+ expect(subscription.status).toBe('disabled')
436
+
437
+ await Webhooks.updateSubscription(db, apiKeyOwner, subscription.id, { status: 'active' })
438
+
439
+ // The counter still sits at the cap, so this failure must write again.
440
+ await Webhooks.recordFailure(db, subscription, { maxFailures: 1, now: fixedNow })
441
+ const read = await Webhooks.getSubscription(db, apiKeyOwner, subscription.id)
442
+ expect(read?.status).toBe('disabled')
443
+ })
378
444
  })
379
445
 
380
446
  describe('sign / verify', () => {
@@ -1015,7 +1081,7 @@ describe('ensureQueueEvents', () => {
1015
1081
  expect(await Webhooks.getQueueEvent(db, reference)).toBeNull()
1016
1082
  })
1017
1083
 
1018
- test('replaces an expired terminal row so replays stay deliverable', async () => {
1084
+ test('stages a replay once its completion has expired', async () => {
1019
1085
  vi.useFakeTimers({ toFake: ['Date'] })
1020
1086
  vi.setSystemTime(fixedNow())
1021
1087
  try {
@@ -1031,9 +1097,9 @@ describe('ensureQueueEvents', () => {
1031
1097
  }),
1032
1098
  ).toBe(true)
1033
1099
 
1034
- // Past the dedupe window an unpruned terminal row must not suppress a
1100
+ // Past the dedupe window an unpruned completion must not suppress a
1035
1101
  // replay.
1036
- vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.deliveryRetentionMs + 1_000))
1102
+ vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.dedupeRetentionMs + 1_000))
1037
1103
  const replay = await Webhooks.ensureQueueEvents(db, [{ envelope, subscription }])
1038
1104
  expect(replay.created).toBe(1)
1039
1105
  await expect(Webhooks.claimQueueEvent(db, reference)).resolves.toMatchObject({
@@ -1044,7 +1110,7 @@ describe('ensureQueueEvents', () => {
1044
1110
  }
1045
1111
  })
1046
1112
 
1047
- test('refreshes the observation stamp when a replay replaces an expired row', async () => {
1113
+ test('stamps a fresh observation on a post-expiry replay', async () => {
1048
1114
  vi.useFakeTimers({ toFake: ['Date'] })
1049
1115
  vi.setSystemTime(fixedNow())
1050
1116
  try {
@@ -1057,7 +1123,7 @@ describe('ensureQueueEvents', () => {
1057
1123
  status: 'succeeded',
1058
1124
  })
1059
1125
 
1060
- vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.deliveryRetentionMs + 1_000))
1126
+ vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.dedupeRetentionMs + 1_000))
1061
1127
  const observedAt = Date.now() - 250
1062
1128
  await Webhooks.ensureQueueEvents(db, [{ envelope, subscription }], { observedAt })
1063
1129
 
@@ -1070,6 +1136,38 @@ describe('ensureQueueEvents', () => {
1070
1136
  vi.useRealTimers()
1071
1137
  }
1072
1138
  })
1139
+
1140
+ test('suppresses only the completed obligation when staging a mixed batch', async () => {
1141
+ const db = TestApp.database()
1142
+ const done = await stagedObligation(db, 4)
1143
+ const claim = await Webhooks.claimQueueEvent(db, done.reference)
1144
+ if (claim.type !== 'claimed') throw new Error(`Unexpected claim: ${claim.type}`)
1145
+ await Webhooks.completeQueueEvent(db, done.reference, {
1146
+ claimedAt: claim.record.attemptingAt!,
1147
+ status: 'failed',
1148
+ })
1149
+
1150
+ const fresh = Webhooks.buildEnvelope({
1151
+ blockNumber: 124,
1152
+ createdAt: new Date(),
1153
+ data: { amount: '1' },
1154
+ logIndex: 5,
1155
+ subscription: done.subscription,
1156
+ })
1157
+ const batch = await Webhooks.ensureQueueEvents(db, [
1158
+ { envelope: done.envelope, subscription: done.subscription },
1159
+ { envelope: fresh, subscription: done.subscription },
1160
+ ])
1161
+
1162
+ expect(batch.created).toBe(1)
1163
+ await expect(Webhooks.claimQueueEvent(db, done.reference)).resolves.toEqual({
1164
+ status: 'failed',
1165
+ type: 'terminal',
1166
+ })
1167
+ await expect(Webhooks.claimQueueEvent(db, batch.references[1]!)).resolves.toMatchObject({
1168
+ type: 'claimed',
1169
+ })
1170
+ })
1073
1171
  })
1074
1172
 
1075
1173
  describe('claimQueueEvent', () => {
@@ -1082,6 +1180,22 @@ describe('claimQueueEvent', () => {
1082
1180
  vi.useRealTimers()
1083
1181
  })
1084
1182
 
1183
+ test('reads an expired completion as missing', async () => {
1184
+ const db = TestApp.database()
1185
+ const { reference } = await stagedObligation(db)
1186
+ const claim = await Webhooks.claimQueueEvent(db, reference)
1187
+ if (claim.type !== 'claimed') throw new Error(`Unexpected claim: ${claim.type}`)
1188
+ await Webhooks.completeQueueEvent(db, reference, {
1189
+ claimedAt: claim.record.attemptingAt!,
1190
+ status: 'succeeded',
1191
+ })
1192
+
1193
+ // Matches a pruned terminal row: past the dedupe window the completion is
1194
+ // invisible everywhere, not only to staging.
1195
+ vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.dedupeRetentionMs + 1_000))
1196
+ await expect(Webhooks.claimQueueEvent(db, reference)).resolves.toEqual({ type: 'missing' })
1197
+ })
1198
+
1085
1199
  test('claims a due obligation once and reclaims after staleness', async () => {
1086
1200
  const db = TestApp.database()
1087
1201
  const { reference } = await stagedObligation(db)
@@ -1146,17 +1260,17 @@ describe('claimQueueEvent', () => {
1146
1260
 
1147
1261
  // Pending obligations never expire; only terminal transitions stamp a
1148
1262
  // dedupe deadline.
1149
- vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.deliveryRetentionMs * 3))
1263
+ vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.dedupeRetentionMs * 3))
1150
1264
  await expect(Webhooks.claimQueueEvent(db, reference)).resolves.toMatchObject({
1151
1265
  type: 'claimed',
1152
1266
  })
1153
1267
  })
1154
1268
 
1155
- test('never prunes pending work', async () => {
1269
+ test('leaves pending obligations claimable across a prune', async () => {
1156
1270
  const db = TestApp.database()
1157
1271
  const { reference } = await stagedObligation(db)
1158
1272
 
1159
- vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.deliveryRetentionMs + 1_000))
1273
+ vi.setSystemTime(new Date(fixedNow().getTime() + Webhooks.dedupeRetentionMs + 1_000))
1160
1274
  await Webhooks.pruneExpired(db)
1161
1275
  const claim = await Webhooks.claimQueueEvent(db, reference)
1162
1276
  expect(claim.type).toBe('claimed')
@@ -3,6 +3,7 @@ import { Hash, Hex } from 'ox'
3
3
  import type * as app_Webhooks from '../apps/data/routes/webhooks.js'
4
4
  import type * as Db from '../db/Db.js'
5
5
  import * as WebhookDeliveries from '../db/tables/webhookDeliveries.js'
6
+ import * as WebhookQueueCompletions from '../db/tables/webhookQueueCompletions.js'
6
7
  import * as WebhookQueueEvents from '../db/tables/webhookQueueEvents.js'
7
8
  import * as WebhookSubscriptions from '../db/tables/webhookSubscriptions.js'
8
9
  import type * as Metrics from '../Metrics.js'
@@ -95,7 +96,8 @@ export type Subscription = {
95
96
  filters: Record<string, unknown>
96
97
  /** Subscription id (`wh_…`). */
97
98
  id: string
98
- /** ISO timestamp of the last successful delivery, if any. */
99
+ /** ISO timestamp of the last successful delivery, if any. Display only:
100
+ * refreshed at most once per precision window. */
99
101
  lastDeliveryAt?: string | undefined
100
102
  /** Authenticated owner. */
101
103
  owner: Owner
@@ -105,7 +107,8 @@ export type Subscription = {
105
107
  secret: string
106
108
  /** Lifecycle status. */
107
109
  status: Status
108
- /** ISO timestamp of the last mutation. */
110
+ /** ISO timestamp of the last mutation. May lag repeat deliveries; see
111
+ * `lastDeliveryAt`. */
109
112
  updatedAt: string
110
113
  }
111
114
 
@@ -406,11 +409,18 @@ export async function getCursor(db: Db.Db, subscriptionId: string): Promise<stri
406
409
  * deliveries past retention. Reads already filter expiry at query time; this
407
410
  * is the janitor reclaiming space, run best-effort on a schedule.
408
411
  */
409
- export async function pruneExpired(db: Db.Db, options: pruneExpired.Options = {}): Promise<void> {
412
+ export async function pruneExpired(
413
+ db: Db.Db,
414
+ options: pruneExpired.Options = {},
415
+ ): Promise<pruneExpired.Result> {
410
416
  const iso = (options.now ?? (() => new Date()))().toISOString()
411
- await WebhookDeliveries.pruneExpired(db, iso)
412
- await WebhookQueueEvents.pruneExpired(db, iso)
413
- await WebhookSubscriptions.pruneExpired(db, iso)
417
+ // Sequential on purpose: the subscription delete cascades into the other
418
+ // tables, so overlapping it with their prunes risks deadlocks.
419
+ return {
420
+ completions: await WebhookQueueCompletions.pruneExpired(db, iso),
421
+ deliveries: await WebhookDeliveries.pruneExpired(db, iso),
422
+ subscriptions: await WebhookSubscriptions.pruneExpired(db, iso),
423
+ }
414
424
  }
415
425
 
416
426
  export declare namespace pruneExpired {
@@ -419,6 +429,16 @@ export declare namespace pruneExpired {
419
429
  /** Clock used for expiry evaluation; injectable for tests. */
420
430
  now?: (() => Date) | undefined
421
431
  }
432
+
433
+ /** Rows deleted per table; a count pinned at the batch bound means expiry is outpacing pruning. */
434
+ type Result = {
435
+ /** Deleted `webhook_queue_completions` rows. */
436
+ completions: number
437
+ /** Deleted `webhook_deliveries` rows. */
438
+ deliveries: number
439
+ /** Deleted `webhook_subscriptions` rows. */
440
+ subscriptions: number
441
+ }
422
442
  }
423
443
 
424
444
  /**
@@ -427,6 +447,13 @@ export declare namespace pruneExpired {
427
447
  */
428
448
  export const deliveryRetentionMs = 7 * 24 * 60 * 60 * 1_000
429
449
 
450
+ /**
451
+ * Completions dedupe window: replays of a completed obligation are suppressed
452
+ * this long. Deliberately its own literal so per-deployment log-retention
453
+ * tuning cannot move it.
454
+ */
455
+ export const dedupeRetentionMs = 7 * 24 * 60 * 60 * 1_000
456
+
430
457
  /** A compact Cloudflare Queue message referencing a durable staged envelope. */
431
458
  export type QueueReference = {
432
459
  /** Idempotent event id of the staged envelope. */
@@ -441,8 +468,6 @@ export type QueueEvent = QueueReference & {
441
468
  createdAt: string
442
469
  /** Full envelope loaded by the Queue consumer. */
443
470
  envelope: Envelope
444
- /** ISO timestamp after which an unconsumed envelope may be pruned. */
445
- expiresAt: string
446
471
  }
447
472
 
448
473
  /** A claim newer than this counts as an in-flight attempt; above the 10s destination timeout default. */
@@ -467,7 +492,6 @@ export async function ensureQueueEvents(
467
492
  options: ensureQueueEvents.Options = {},
468
493
  ): Promise<ensureQueueEvents.Result> {
469
494
  const now = (options.now ?? (() => new Date()))()
470
- const expiresAt = new Date(now.getTime() + (options.ttl ?? deliveryRetentionMs)).toISOString()
471
495
  // Carried so the delivery worker can measure observation-to-attempt without
472
496
  // re-reading the block; replays have no observation and stay null.
473
497
  const observedAt =
@@ -476,9 +500,6 @@ export async function ensureQueueEvents(
476
500
  createdAt: now.toISOString(),
477
501
  envelope,
478
502
  eventId: envelope.id,
479
- // Inert while pending; the meaningful dedupe deadline is stamped at
480
- // terminal transitions.
481
- expiresAt,
482
503
  // Due immediately; the value orders the sweep so it is never null here.
483
504
  nextAttemptAt: now.toISOString(),
484
505
  observedAt,
@@ -499,14 +520,12 @@ export declare namespace ensureQueueEvents {
499
520
  subscription: Subscription
500
521
  }
501
522
 
502
- /** Staged-envelope retention options. */
523
+ /** Staging options. */
503
524
  type Options = {
504
525
  /** Clock used for staging timestamps. */
505
526
  now?: (() => Date) | undefined
506
527
  /** Epoch ms the head owing these events was observed; absent on replays. */
507
528
  observedAt?: number | undefined
508
- /** Staged-envelope TTL in ms; defaults to {@link deliveryRetentionMs}. */
509
- ttl?: number | undefined
510
529
  }
511
530
 
512
531
  /** Created-row count plus references for every requested obligation. */
@@ -542,7 +561,7 @@ export function completeQueueEvent(
542
561
  ): Promise<boolean> {
543
562
  return WebhookQueueEvents.complete(db, reference, {
544
563
  ...options,
545
- expiresAt: new Date(Date.now() + deliveryRetentionMs).toISOString(),
564
+ expiresAt: new Date(Date.now() + dedupeRetentionMs).toISOString(),
546
565
  })
547
566
  }
548
567
 
@@ -552,7 +571,7 @@ export declare namespace completeQueueEvent {
552
571
  /** Claim marker from the claimed record's `attemptingAt`. */
553
572
  claimedAt: string
554
573
  /** Terminal obligation outcome. */
555
- status: WebhookQueueEvents.TerminalStatus
574
+ status: WebhookQueueCompletions.TerminalStatus
556
575
  }
557
576
  }
558
577
 
@@ -766,10 +785,17 @@ export async function getDelivery(
766
785
  */
767
786
  export const maxFailures = 10
768
787
 
788
+ /**
789
+ * How stale `lastDeliveryAt` may get before a success rewrites it. Read only
790
+ * for display, so exactness is traded for not rewriting the row per delivery.
791
+ */
792
+ const lastDeliveryPrecisionMs = 60_000
793
+
769
794
  /**
770
795
  * Records a successful delivery: resets the consecutive-failure counter and
771
- * stamps `lastDeliveryAt`. A column-targeted update, so a concurrent patch is
772
- * never clobbered by the stale copy carried through the delivery queue.
796
+ * refreshes `lastDeliveryAt` at most once per precision window. Returns the
797
+ * written row, or the caller's snapshot when the write was skipped or the row
798
+ * deleted.
773
799
  */
774
800
  export async function recordSuccess(
775
801
  db: Db.Db,
@@ -777,11 +803,13 @@ export async function recordSuccess(
777
803
  options: recordSuccess.Options = {},
778
804
  ): Promise<Subscription> {
779
805
  const now = (options.now ?? (() => new Date()))()
780
- const iso = now.toISOString()
781
- const next = await WebhookSubscriptions.recordSuccess(db, subscription.id, iso)
782
- // A concurrently deleted subscription is not resurrected; return the
783
- // optimistic next state so the caller still observes the outcome.
784
- return next ?? { ...subscription, failureCount: 0, lastDeliveryAt: iso, updatedAt: iso }
806
+ const next = await WebhookSubscriptions.recordSuccess(db, subscription.id, {
807
+ now: now.toISOString(),
808
+ staleBefore: new Date(now.getTime() - lastDeliveryPrecisionMs).toISOString(),
809
+ })
810
+ // No row means the write was skipped or the row was deleted; neither
811
+ // resurrects, so keep the caller's timestamps.
812
+ return next ?? { ...subscription, failureCount: 0 }
785
813
  }
786
814
 
787
815
  export declare namespace recordSuccess {
@@ -796,7 +824,9 @@ export declare namespace recordSuccess {
796
824
  * Records a failed delivery: atomically increments the consecutive-failure
797
825
  * counter and, once it reaches `maxFailures`, sets `status: 'disabled'` so the
798
826
  * subscription drops out of the partial active index (surfaced via the API).
799
- * Returns the updated subscription.
827
+ * Once disabled at the cap the row is left alone, so an outage's in-flight
828
+ * failures stop contending on its lock. Returns the written row, or the
829
+ * caller's snapshot advanced optimistically.
800
830
  */
801
831
  export async function recordFailure(
802
832
  db: Db.Db,
@@ -808,14 +838,14 @@ export async function recordFailure(
808
838
  const max = options.maxFailures ?? maxFailures
809
839
  const next = await WebhookSubscriptions.recordFailure(db, subscription.id, max, iso)
810
840
  if (next) return next
811
- // A concurrently deleted subscription is not resurrected; return the
812
- // optimistic next state so the caller still observes the outcome.
813
- const failureCount = subscription.failureCount + 1
841
+ // No row means skipped (already disabled at the cap) or deleted; neither
842
+ // resurrects, so keep the caller's timestamps. Capped so chained skips
843
+ // cannot grow the counter past what the store would hold.
844
+ const failureCount = Math.min(subscription.failureCount + 1, max)
814
845
  return {
815
846
  ...subscription,
816
847
  failureCount,
817
848
  ...(failureCount >= max ? { status: 'disabled' as const } : {}),
818
- updatedAt: iso,
819
849
  }
820
850
  }
821
851
 
@@ -132,7 +132,47 @@ describe('validateEvmCalls', () => {
132
132
  sourceTokenAddress: sourceToken,
133
133
  }),
134
134
  ).toThrowErrorMatchingInlineSnapshot(
135
- `[Funding.Action.InvalidActionError: Stablecoin funding calls must not carry native value.]`,
135
+ `[Funding.Action.InvalidActionError: Native value must match a validated provider fee.]`,
136
136
  )
137
137
  })
138
+
139
+ test('accepts an exact provider-validated native value', () => {
140
+ Action.validateEvmCalls({
141
+ action: {
142
+ calls: [approveCall(1_000_000n), { ...depositCall, value: '0x10' }],
143
+ type: 'evm:calls',
144
+ },
145
+ nativeValues: [{ to: spender, value: '0x10' }],
146
+ sourceAmount: '1000000',
147
+ sourceTokenAddress: sourceToken,
148
+ })
149
+ })
150
+
151
+ test('rejects malformed native value allowances', () => {
152
+ expect(() =>
153
+ Action.validateEvmCalls({
154
+ action: { calls: [approveCall(1_000_000n), depositCall], type: 'evm:calls' },
155
+ nativeValues: [{ to: spender, value: '10' }],
156
+ sourceAmount: '1000000',
157
+ sourceTokenAddress: sourceToken,
158
+ }),
159
+ ).toThrowErrorMatchingInlineSnapshot(
160
+ `[Funding.Action.InvalidActionError: Malformed native value allowance.]`,
161
+ )
162
+ })
163
+
164
+ test('rejects native values outside the exact provider allowance', () => {
165
+ for (const call of [
166
+ { ...depositCall, to: `0x${'99'.repeat(20)}`, value: '0x10' },
167
+ { ...depositCall, value: '0x11' },
168
+ ])
169
+ expect(() =>
170
+ Action.validateEvmCalls({
171
+ action: { calls: [approveCall(1_000_000n), call], type: 'evm:calls' },
172
+ nativeValues: [{ to: spender, value: '0x10' }],
173
+ sourceAmount: '1000000',
174
+ sourceTokenAddress: sourceToken,
175
+ }),
176
+ ).toThrow(Action.InvalidActionError)
177
+ })
138
178
  })
@@ -1,6 +1,8 @@
1
1
  import { AbiFunction, Address, type Hex, Solidity } from 'ox'
2
2
  import * as z from 'zod/mini'
3
3
 
4
+ import * as OpenApi from '../OpenApi.js'
5
+
4
6
  /**
5
7
  * Executable source-chain actions returned by funding transfer creation. A
6
8
  * strict discriminated union: transfer reads never include an action.
@@ -12,49 +14,58 @@ const approveSelector = AbiFunction.getSelector(approve)
12
14
  /** Zod schemas owned by funding actions. */
13
15
  export namespace schema {
14
16
  /** One unsigned EVM call, in the exchange quote API's call shape. */
15
- export const Call = z
16
- .object({
17
- data: z
18
- .string()
19
- .check(
20
- z.regex(/^0x(?:[0-9a-fA-F]{2})*$/),
21
- z.describe('ABI-encoded calldata.'),
22
- z.meta({ examples: ['0x095ea7b3'] }),
23
- ),
24
- to: z
25
- .string()
26
- .check(
27
- z.regex(/^0x[0-9a-fA-F]{40}$/),
28
- z.describe('Contract the call targets.'),
29
- z.meta({ examples: [`0x${'44'.repeat(20)}`] }),
30
- ),
31
- value: z
32
- .string()
33
- .check(
34
- z.regex(/^0x[0-9a-fA-F]+$/),
35
- z.describe('Native value to send, as hex.'),
36
- z.meta({ examples: ['0x0'] }),
37
- ),
38
- })
39
- .check(z.describe('One unsigned EVM call.'))
17
+ export const Call = OpenApi.component(
18
+ z
19
+ .object({
20
+ data: z
21
+ .string()
22
+ .check(
23
+ z.regex(/^0x(?:[0-9a-fA-F]{2})*$/),
24
+ z.describe('ABI-encoded calldata.'),
25
+ z.meta({ examples: ['0x095ea7b3'] }),
26
+ ),
27
+ to: z
28
+ .string()
29
+ .check(
30
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
31
+ z.describe('Contract the call targets.'),
32
+ z.meta({ examples: [`0x${'44'.repeat(20)}`] }),
33
+ ),
34
+ value: z
35
+ .string()
36
+ .check(
37
+ z.regex(/^0x[0-9a-fA-F]+$/),
38
+ z.describe('Native value to send, as hex.'),
39
+ z.meta({ examples: ['0x0'] }),
40
+ ),
41
+ })
42
+ .check(z.describe('One unsigned EVM call.')),
43
+ 'FundingActionCall',
44
+ )
40
45
 
41
46
  /** Ordered unsigned EVM calls the caller signs and submits on the source chain. */
42
- export const EvmCalls = z
43
- .object({
44
- calls: z
45
- .array(Call)
46
- .check(
47
- z.minLength(1),
48
- z.describe('Calls in submission order; they may require separate transactions.'),
49
- ),
50
- type: z.literal('evm:calls').check(z.describe('Action kind.')),
51
- })
52
- .check(z.describe('Ordered unsigned EVM calls to sign and submit on the source chain.'))
47
+ export const EvmCalls = OpenApi.component(
48
+ z
49
+ .object({
50
+ calls: z
51
+ .array(Call)
52
+ .check(
53
+ z.minLength(1),
54
+ z.describe('Calls in submission order; they may require separate transactions.'),
55
+ ),
56
+ type: z.literal('evm:calls').check(z.describe('Action kind.')),
57
+ })
58
+ .check(z.describe('Ordered unsigned EVM calls to sign and submit on the source chain.')),
59
+ 'FundingEvmCallsAction',
60
+ )
53
61
 
54
62
  /** Executable source-chain action attached to a creation response. */
55
- export const Action = z
56
- .discriminatedUnion('type', [EvmCalls])
57
- .check(z.describe('Executable source-chain action. Never returned by transfer reads.'))
63
+ export const Action = OpenApi.component(
64
+ z
65
+ .discriminatedUnion('type', [EvmCalls])
66
+ .check(z.describe('Executable source-chain action. Never returned by transfer reads.')),
67
+ 'FundingAction',
68
+ )
58
69
  }
59
70
 
60
71
  /** One unsigned EVM call (see {@link schema.Call}). */
@@ -66,6 +77,14 @@ export type EvmCalls = z.output<typeof schema.EvmCalls>
66
77
  /** Executable source-chain action (see {@link schema.Action}). */
67
78
  export type Action = z.output<typeof schema.Action>
68
79
 
80
+ /** One exact native-value allowance established by provider-specific validation. */
81
+ export type NativeValue = {
82
+ /** Contract that may receive the native value. */
83
+ to: string
84
+ /** Exact native value allowed, as a JSON-RPC quantity. */
85
+ value: string
86
+ }
87
+
69
88
  /**
70
89
  * Validates prepared EVM calls before persistence. Token calls, approval
71
90
  * targets, spenders, amounts, selectors, and native value must match the route.
@@ -73,9 +92,16 @@ export type Action = z.output<typeof schema.Action>
73
92
  export function validateEvmCalls(options: validateEvmCalls.Options): void {
74
93
  const action = schema.EvmCalls.safeParse(options.action)
75
94
  if (!action.success) throw new InvalidActionError('Malformed EVM calls.')
95
+ const nativeValues = new Set<string>()
96
+ for (const { to, value } of options.nativeValues ?? []) {
97
+ if (!Address.validate(to, { strict: false }) || !/^0x[0-9a-fA-F]+$/.test(value))
98
+ throw new InvalidActionError('Malformed native value allowance.')
99
+ nativeValues.add(`${to.toLowerCase()}:${BigInt(value).toString()}`)
100
+ }
76
101
  for (const [index, call] of action.data.calls.entries()) {
77
- if (BigInt(call.value) !== 0n)
78
- throw new InvalidActionError('Stablecoin funding calls must not carry native value.')
102
+ const callValue = BigInt(call.value)
103
+ if (callValue > 0n && !nativeValues.has(`${call.to.toLowerCase()}:${callValue.toString()}`))
104
+ throw new InvalidActionError('Native value must match a validated provider fee.')
79
105
  const targetsSourceToken = Address.isEqual(
80
106
  call.to as Address.Address,
81
107
  options.sourceTokenAddress as Address.Address,
@@ -104,6 +130,8 @@ export declare namespace validateEvmCalls {
104
130
  type Options = {
105
131
  /** The prepared action to validate. */
106
132
  action: EvmCalls
133
+ /** Exact native values established by provider-specific validation. */
134
+ nativeValues?: readonly NativeValue[] | undefined
107
135
  /** Source amount in base units. */
108
136
  sourceAmount: string
109
137
  /** Source token contract address. */
@@ -309,3 +309,37 @@ describe('route capabilities', () => {
309
309
  ).toThrow()
310
310
  })
311
311
  })
312
+
313
+ describe('route configuration', () => {
314
+ const configuration = { poolAddress: `0x${'11'.repeat(20)}` }
315
+
316
+ test('carries configuration into hydrated provider routes', () => {
317
+ const snapshot = Catalog.compile(rows({ routes: [{ ...data.routes[0]!, configuration }] }))
318
+ expect(snapshot.routes[0]?.configuration).toStrictEqual(configuration)
319
+ expect(snapshot.routesByProvider.get('relay')?.[0]?.configuration).toStrictEqual(configuration)
320
+ })
321
+
322
+ test('omits configuration when it is absent or null', () => {
323
+ const absent = Catalog.compile(rows())
324
+ const nulled = Catalog.compile(rows({ routes: [{ ...data.routes[0]!, configuration: null }] }))
325
+ expect('configuration' in absent.routes[0]!).toBe(false)
326
+ expect('configuration' in nulled.routes[0]!).toBe(false)
327
+ })
328
+
329
+ test('publishes configuration through the write boundary', async () => {
330
+ const db = TestApp.database()
331
+ const snapshot = await Catalog.publish(db, {
332
+ ...data,
333
+ routes: [{ ...data.routes[0]!, configuration }],
334
+ })
335
+ expect(snapshot.routes[0]?.configuration).toStrictEqual(configuration)
336
+ })
337
+
338
+ test('rejects non-object configuration', () => {
339
+ expect(() =>
340
+ Catalog.compile(
341
+ rows({ routes: [{ ...data.routes[0]!, configuration: 'invalid' as never }] }),
342
+ ),
343
+ ).toThrow()
344
+ })
345
+ })