tapimo 0.12.0 → 0.14.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 (398) hide show
  1. package/dist/App.d.ts +11333 -9563
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +39 -3
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +2 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +17 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/Webhooks.js +2 -2
  10. package/dist/Webhooks.js.map +1 -1
  11. package/dist/admin/App.d.ts +1064 -924
  12. package/dist/admin/App.d.ts.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +80 -0
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/api-keys.js +54 -0
  16. package/dist/admin/apps/api-keys.js.map +1 -1
  17. package/dist/admin/apps/earn-vaults.d.ts +63 -15
  18. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  19. package/dist/admin/apps/earn-vaults.js +2 -1
  20. package/dist/admin/apps/earn-vaults.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +92 -3
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/apps/organizations.js +69 -7
  24. package/dist/admin/apps/organizations.js.map +1 -1
  25. package/dist/admin/apps/verified-tokens.d.ts +7 -7
  26. package/dist/admin/ui.generated.d.ts +1 -1
  27. package/dist/admin/ui.generated.d.ts.map +1 -1
  28. package/dist/admin/ui.generated.js +151 -151
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +28 -28
  31. package/dist/apps/Zones.js +7 -7
  32. package/dist/apps/data/App.d.ts +2909 -2523
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/App.js +26 -21
  35. package/dist/apps/data/App.js.map +1 -1
  36. package/dist/apps/data/ZoneSelection.d.ts +8 -8
  37. package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
  38. package/dist/apps/data/ZoneSelection.js +1 -1
  39. package/dist/apps/data/ZoneSelection.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +588 -584
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +1561 -1239
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +22 -22
  45. package/dist/apps/data/routes/balances.d.ts +26 -26
  46. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  47. package/dist/apps/data/routes/balances.js +43 -16
  48. package/dist/apps/data/routes/balances.js.map +1 -1
  49. package/dist/apps/data/routes/blocks.d.ts +44 -44
  50. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  51. package/dist/apps/data/routes/blocks.js +21 -11
  52. package/dist/apps/data/routes/blocks.js.map +1 -1
  53. package/dist/apps/data/routes/coingecko.d.ts +3 -3
  54. package/dist/apps/data/routes/earn.d.ts +201 -25
  55. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  56. package/dist/apps/data/routes/earn.js +434 -75
  57. package/dist/apps/data/routes/earn.js.map +1 -1
  58. package/dist/apps/data/routes/exchanges.d.ts +160 -145
  59. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  60. package/dist/apps/data/routes/exchanges.js +180 -114
  61. package/dist/apps/data/routes/exchanges.js.map +1 -1
  62. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  63. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  64. package/dist/apps/data/routes/fee-amm.js +18 -19
  65. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  66. package/dist/apps/data/routes/receipts.d.ts +155 -55
  67. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  68. package/dist/apps/data/routes/receipts.js +28 -10
  69. package/dist/apps/data/routes/receipts.js.map +1 -1
  70. package/dist/apps/data/routes/tokenlist.d.ts +13 -13
  71. package/dist/apps/data/routes/tokens.d.ts +5 -5
  72. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  73. package/dist/apps/data/routes/tokens.js +51 -20
  74. package/dist/apps/data/routes/tokens.js.map +1 -1
  75. package/dist/apps/data/routes/transactions.d.ts +7 -2
  76. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  77. package/dist/apps/data/routes/transactions.js +37 -17
  78. package/dist/apps/data/routes/transactions.js.map +1 -1
  79. package/dist/apps/data/routes/transfers.d.ts +13 -13
  80. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  81. package/dist/apps/data/routes/transfers.js +18 -12
  82. package/dist/apps/data/routes/transfers.js.map +1 -1
  83. package/dist/apps/data/routes/valuation.d.ts +31 -31
  84. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  85. package/dist/apps/data/routes/valuation.js +12 -7
  86. package/dist/apps/data/routes/valuation.js.map +1 -1
  87. package/dist/apps/data/routes/verified-tokens.d.ts +8 -8
  88. package/dist/apps/data/routes/webhooks.d.ts +816 -113
  89. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  90. package/dist/apps/data/routes/webhooks.js +47 -5
  91. package/dist/apps/data/routes/webhooks.js.map +1 -1
  92. package/dist/apps/data/routes/zones.d.ts +14 -14
  93. package/dist/apps/data/routes/zones.js +1 -1
  94. package/dist/apps/funding/App.d.ts +466 -44
  95. package/dist/apps/funding/App.d.ts.map +1 -1
  96. package/dist/apps/funding/App.js +1 -1
  97. package/dist/apps/funding/App.js.map +1 -1
  98. package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
  99. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  100. package/dist/apps/funding/routes/deposit-addresses.js +17 -12
  101. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  102. package/dist/apps/funding/routes/transfers.d.ts +650 -57
  103. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  104. package/dist/apps/funding/routes/transfers.js +238 -24
  105. package/dist/apps/funding/routes/transfers.js.map +1 -1
  106. package/dist/apps/management/App.d.ts +5120 -4152
  107. package/dist/apps/management/App.d.ts.map +1 -1
  108. package/dist/apps/management/Billing.d.ts +1 -1
  109. package/dist/apps/management/Billing.d.ts.map +1 -1
  110. package/dist/apps/management/Billing.js +17 -1
  111. package/dist/apps/management/Billing.js.map +1 -1
  112. package/dist/apps/management/routes/api-keys.d.ts +632 -81
  113. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  114. package/dist/apps/management/routes/api-keys.js +236 -4
  115. package/dist/apps/management/routes/api-keys.js.map +1 -1
  116. package/dist/apps/management/routes/billing.d.ts +76 -76
  117. package/dist/apps/management/routes/faucet.d.ts +6 -6
  118. package/dist/apps/management/routes/invitations.d.ts +24 -24
  119. package/dist/apps/management/routes/invite-links.d.ts +4 -4
  120. package/dist/apps/management/routes/members.d.ts +16 -16
  121. package/dist/apps/management/routes/orgs.d.ts +20 -20
  122. package/dist/apps/management/routes/projects.d.ts +4 -4
  123. package/dist/apps/management/routes/usage.d.ts +8 -8
  124. package/dist/apps/management/routes/verified-token-requests.d.ts +17 -17
  125. package/dist/apps/management/routes/webhooks.d.ts +675 -25
  126. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  127. package/dist/apps/management/routes/webhooks.js +21 -21
  128. package/dist/apps/management/routes/webhooks.js.map +1 -1
  129. package/dist/apps/relay/App.d.ts.map +1 -1
  130. package/dist/apps/relay/App.js +115 -23
  131. package/dist/apps/relay/App.js.map +1 -1
  132. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  133. package/dist/apps/relay/Sponsorships.js +27 -1
  134. package/dist/apps/relay/Sponsorships.js.map +1 -1
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +34 -1
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +11 -5
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +21 -3
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/fundingCatalog.d.ts +4 -2
  143. package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
  144. package/dist/db/tables/fundingCatalog.js +5 -1
  145. package/dist/db/tables/fundingCatalog.js.map +1 -1
  146. package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
  147. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
  148. package/dist/db/tables/fundingTransferTransactions.js +44 -3
  149. package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
  150. package/dist/db/tables/fundingTransfers.d.ts +17 -0
  151. package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
  152. package/dist/db/tables/fundingTransfers.js +21 -0
  153. package/dist/db/tables/fundingTransfers.js.map +1 -1
  154. package/dist/db/tables/organizations.d.ts +24 -0
  155. package/dist/db/tables/organizations.d.ts.map +1 -1
  156. package/dist/db/tables/organizations.js +27 -0
  157. package/dist/db/tables/organizations.js.map +1 -1
  158. package/dist/db/tables/projects.d.ts +25 -1
  159. package/dist/db/tables/projects.d.ts.map +1 -1
  160. package/dist/db/tables/projects.js +26 -0
  161. package/dist/db/tables/projects.js.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
  163. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  164. package/dist/db/tables/sponsoredTransactions.js +146 -13
  165. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  166. package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
  167. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  168. package/dist/db/tables/webhookSubscriptions.js +9 -6
  169. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  170. package/dist/handlers/internal/utils.d.ts.map +1 -1
  171. package/dist/handlers/internal/utils.js +9 -0
  172. package/dist/handlers/internal/utils.js.map +1 -1
  173. package/dist/internal/Auth.d.ts +3 -1
  174. package/dist/internal/Auth.d.ts.map +1 -1
  175. package/dist/internal/Auth.js +6 -3
  176. package/dist/internal/Auth.js.map +1 -1
  177. package/dist/internal/EarnVaults.d.ts +3 -3
  178. package/dist/internal/EarnVaults.js +5 -5
  179. package/dist/internal/EarnVaults.js.map +1 -1
  180. package/dist/internal/Fetch.d.ts +23 -0
  181. package/dist/internal/Fetch.d.ts.map +1 -0
  182. package/dist/internal/Fetch.js +26 -0
  183. package/dist/internal/Fetch.js.map +1 -0
  184. package/dist/internal/Log.d.ts +4 -2
  185. package/dist/internal/Log.d.ts.map +1 -1
  186. package/dist/internal/Log.js +16 -3
  187. package/dist/internal/Log.js.map +1 -1
  188. package/dist/internal/OpenApi.d.ts +5 -3
  189. package/dist/internal/OpenApi.d.ts.map +1 -1
  190. package/dist/internal/OpenApi.js +13 -3
  191. package/dist/internal/OpenApi.js.map +1 -1
  192. package/dist/internal/Response.d.ts +1 -1
  193. package/dist/internal/Response.d.ts.map +1 -1
  194. package/dist/internal/Schema.d.ts +6 -3
  195. package/dist/internal/Schema.d.ts.map +1 -1
  196. package/dist/internal/Schema.js +8 -2
  197. package/dist/internal/Schema.js.map +1 -1
  198. package/dist/internal/Store.d.ts +16 -3
  199. package/dist/internal/Store.d.ts.map +1 -1
  200. package/dist/internal/Store.js +82 -16
  201. package/dist/internal/Store.js.map +1 -1
  202. package/dist/internal/Tidx.d.ts.map +1 -1
  203. package/dist/internal/Tidx.js +7 -2
  204. package/dist/internal/Tidx.js.map +1 -1
  205. package/dist/internal/Timeout.d.ts +36 -0
  206. package/dist/internal/Timeout.d.ts.map +1 -0
  207. package/dist/internal/Timeout.js +37 -0
  208. package/dist/internal/Timeout.js.map +1 -0
  209. package/dist/internal/Timing.d.ts +9 -0
  210. package/dist/internal/Timing.d.ts.map +1 -1
  211. package/dist/internal/Timing.js +7 -0
  212. package/dist/internal/Timing.js.map +1 -1
  213. package/dist/internal/Viem.d.ts +16 -1
  214. package/dist/internal/Viem.d.ts.map +1 -1
  215. package/dist/internal/Viem.js +12 -2
  216. package/dist/internal/Viem.js.map +1 -1
  217. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  218. package/dist/internal/WebhookDestination.js +3 -0
  219. package/dist/internal/WebhookDestination.js.map +1 -1
  220. package/dist/internal/funding/Action.d.ts +9 -0
  221. package/dist/internal/funding/Action.d.ts.map +1 -1
  222. package/dist/internal/funding/Action.js +16 -8
  223. package/dist/internal/funding/Action.js.map +1 -1
  224. package/dist/internal/funding/Catalog.d.ts +9 -2
  225. package/dist/internal/funding/Catalog.d.ts.map +1 -1
  226. package/dist/internal/funding/Catalog.js +19 -8
  227. package/dist/internal/funding/Catalog.js.map +1 -1
  228. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
  229. package/dist/internal/funding/DepositWebhook.js +2 -1
  230. package/dist/internal/funding/DepositWebhook.js.map +1 -1
  231. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  232. package/dist/internal/funding/Evidence.js +2 -10
  233. package/dist/internal/funding/Evidence.js.map +1 -1
  234. package/dist/internal/funding/Provider.d.ts +108 -13
  235. package/dist/internal/funding/Provider.d.ts.map +1 -1
  236. package/dist/internal/funding/Provider.js +34 -8
  237. package/dist/internal/funding/Provider.js.map +1 -1
  238. package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
  239. package/dist/internal/funding/SourceObservation.js +5 -7
  240. package/dist/internal/funding/SourceObservation.js.map +1 -1
  241. package/dist/internal/funding/Transfer.d.ts +114 -18
  242. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  243. package/dist/internal/funding/Transfer.js +185 -55
  244. package/dist/internal/funding/Transfer.js.map +1 -1
  245. package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
  246. package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
  247. package/dist/internal/funding/TransferReconciliation.js +192 -0
  248. package/dist/internal/funding/TransferReconciliation.js.map +1 -0
  249. package/dist/internal/funding/TransferWebhook.d.ts +21 -0
  250. package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
  251. package/dist/internal/funding/TransferWebhook.js +43 -0
  252. package/dist/internal/funding/TransferWebhook.js.map +1 -0
  253. package/dist/internal/funding/index.d.ts +1 -0
  254. package/dist/internal/funding/index.d.ts.map +1 -1
  255. package/dist/internal/funding/index.js +1 -0
  256. package/dist/internal/funding/index.js.map +1 -1
  257. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  258. package/dist/internal/funding/providers/relay.js +3 -12
  259. package/dist/internal/funding/providers/relay.js.map +1 -1
  260. package/dist/internal/funding/providers/stargate.d.ts +127 -9
  261. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  262. package/dist/internal/funding/providers/stargate.js +548 -112
  263. package/dist/internal/funding/providers/stargate.js.map +1 -1
  264. package/dist/internal/index.d.ts +1 -0
  265. package/dist/internal/index.d.ts.map +1 -1
  266. package/dist/internal/index.js +1 -0
  267. package/dist/internal/index.js.map +1 -1
  268. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  269. package/dist/internal/webhookDestinations/slack.js +16 -0
  270. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  271. package/package.json +2 -2
  272. package/src/App.test.ts +100 -0
  273. package/src/App.ts +43 -3
  274. package/src/Client.test-d.ts +129 -2
  275. package/src/Scope.test.ts +20 -0
  276. package/src/Scope.ts +16 -0
  277. package/src/Webhooks.ts +2 -2
  278. package/src/admin/apps/api-keys.test.ts +75 -0
  279. package/src/admin/apps/api-keys.ts +64 -0
  280. package/src/admin/apps/earn-vaults.test.ts +1 -1
  281. package/src/admin/apps/earn-vaults.ts +2 -1
  282. package/src/admin/apps/organizations.test.ts +53 -1
  283. package/src/admin/apps/organizations.ts +90 -5
  284. package/src/admin/ui/src/lib/api-keys.ts +19 -0
  285. package/src/admin/ui/src/lib/organizations.test.ts +112 -1
  286. package/src/admin/ui/src/lib/organizations.ts +55 -0
  287. package/src/admin/ui/src/routes/api-keys.tsx +89 -2
  288. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
  289. package/src/admin/ui.generated.ts +189 -151
  290. package/src/apps/Zones.test.ts +7 -7
  291. package/src/apps/Zones.ts +7 -7
  292. package/src/apps/composition.test.ts +2 -1
  293. package/src/apps/data/App.test.ts +41 -0
  294. package/src/apps/data/App.ts +30 -21
  295. package/src/apps/data/ZoneSelection.test.ts +2 -2
  296. package/src/apps/data/ZoneSelection.ts +8 -8
  297. package/src/apps/data/routes/activities.test.ts +1526 -12
  298. package/src/apps/data/routes/activities.ts +1774 -1356
  299. package/src/apps/data/routes/balances.ts +133 -97
  300. package/src/apps/data/routes/blocks.test.ts +533 -0
  301. package/src/apps/data/routes/blocks.ts +35 -16
  302. package/src/apps/data/routes/earn.test.ts +373 -16
  303. package/src/apps/data/routes/earn.ts +548 -79
  304. package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
  305. package/src/apps/data/routes/exchanges.test.ts +740 -15
  306. package/src/apps/data/routes/exchanges.ts +954 -753
  307. package/src/apps/data/routes/fee-amm.ts +17 -18
  308. package/src/apps/data/routes/receipts.ts +47 -16
  309. package/src/apps/data/routes/tokens.test.ts +578 -0
  310. package/src/apps/data/routes/tokens.ts +283 -225
  311. package/src/apps/data/routes/transactions.ts +68 -29
  312. package/src/apps/data/routes/transfers.test.ts +421 -0
  313. package/src/apps/data/routes/transfers.ts +45 -27
  314. package/src/apps/data/routes/valuation.ts +94 -78
  315. package/src/apps/data/routes/webhooks.test.ts +21 -5
  316. package/src/apps/data/routes/webhooks.ts +63 -8
  317. package/src/apps/data/routes/zones.test.ts +2 -2
  318. package/src/apps/data/routes/zones.ts +1 -1
  319. package/src/apps/funding/App.ts +12 -2
  320. package/src/apps/funding/routes/deposit-addresses.test.ts +29 -7
  321. package/src/apps/funding/routes/deposit-addresses.ts +19 -14
  322. package/src/apps/funding/routes/quotes.test.ts +5 -9
  323. package/src/apps/funding/routes/transfers.test.ts +901 -23
  324. package/src/apps/funding/routes/transfers.ts +347 -64
  325. package/src/apps/management/Billing.ts +18 -5
  326. package/src/apps/management/routes/api-keys.test.ts +220 -0
  327. package/src/apps/management/routes/api-keys.ts +280 -3
  328. package/src/apps/management/routes/webhooks.test.ts +3 -3
  329. package/src/apps/management/routes/webhooks.ts +24 -25
  330. package/src/apps/relay/App.test.ts +212 -1
  331. package/src/apps/relay/App.ts +158 -29
  332. package/src/apps/relay/Sponsorships.ts +24 -1
  333. package/src/db/Db.ts +48 -1
  334. package/src/db/Schema.ts +22 -4
  335. package/src/db/tables/earnVaults.test.ts +9 -9
  336. package/src/db/tables/fundingCatalog.test.ts +16 -2
  337. package/src/db/tables/fundingCatalog.ts +9 -3
  338. package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
  339. package/src/db/tables/fundingTransferTransactions.ts +79 -3
  340. package/src/db/tables/fundingTransfers.test.ts +26 -0
  341. package/src/db/tables/fundingTransfers.ts +36 -0
  342. package/src/db/tables/organizations.test.ts +41 -0
  343. package/src/db/tables/organizations.ts +49 -0
  344. package/src/db/tables/projects.test.ts +55 -0
  345. package/src/db/tables/projects.ts +46 -1
  346. package/src/db/tables/sponsoredTransactions.test.ts +338 -0
  347. package/src/db/tables/sponsoredTransactions.ts +197 -13
  348. package/src/db/tables/webhookSubscriptions.ts +18 -11
  349. package/src/handlers/internal/utils.test.ts +53 -0
  350. package/src/handlers/internal/utils.ts +15 -0
  351. package/src/internal/Auth.ts +12 -4
  352. package/src/internal/EarnVaults.capabilities.test.ts +2 -2
  353. package/src/internal/EarnVaults.test.ts +1 -1
  354. package/src/internal/EarnVaults.ts +5 -5
  355. package/src/internal/Fetch.test.ts +46 -0
  356. package/src/internal/Fetch.ts +56 -0
  357. package/src/internal/Log.test-d.ts +2 -0
  358. package/src/internal/Log.test.ts +21 -0
  359. package/src/internal/Log.ts +22 -5
  360. package/src/internal/OpenApi.ts +14 -4
  361. package/src/internal/Response.ts +1 -1
  362. package/src/internal/Schema.test.ts +2 -0
  363. package/src/internal/Schema.ts +12 -2
  364. package/src/internal/Store.test.ts +178 -0
  365. package/src/internal/Store.ts +104 -18
  366. package/src/internal/Tidx.test.ts +2 -1
  367. package/src/internal/Tidx.ts +7 -2
  368. package/src/internal/Timeout.test.ts +35 -0
  369. package/src/internal/Timeout.ts +73 -0
  370. package/src/internal/Timing.ts +16 -0
  371. package/src/internal/Viem.ts +39 -3
  372. package/src/internal/WebhookDestination.ts +3 -0
  373. package/src/internal/Webhooks.test.ts +45 -0
  374. package/src/internal/funding/Action.test.ts +41 -1
  375. package/src/internal/funding/Action.ts +69 -41
  376. package/src/internal/funding/Catalog.test.ts +34 -0
  377. package/src/internal/funding/Catalog.ts +28 -11
  378. package/src/internal/funding/DepositWebhook.ts +2 -1
  379. package/src/internal/funding/Evidence.ts +2 -13
  380. package/src/internal/funding/Provider.test.ts +73 -0
  381. package/src/internal/funding/Provider.ts +125 -28
  382. package/src/internal/funding/SourceObservation.ts +5 -9
  383. package/src/internal/funding/Transfer.test.ts +155 -0
  384. package/src/internal/funding/Transfer.ts +387 -147
  385. package/src/internal/funding/TransferReconciliation.test.ts +147 -0
  386. package/src/internal/funding/TransferReconciliation.ts +264 -0
  387. package/src/internal/funding/TransferWebhook.test.ts +128 -0
  388. package/src/internal/funding/TransferWebhook.ts +67 -0
  389. package/src/internal/funding/index.test.ts +1 -0
  390. package/src/internal/funding/index.ts +1 -0
  391. package/src/internal/funding/providers/index.test.ts +4 -167
  392. package/src/internal/funding/providers/relay.test.ts +2 -2
  393. package/src/internal/funding/providers/relay.ts +3 -15
  394. package/src/internal/funding/providers/stargate.test.ts +328 -0
  395. package/src/internal/funding/providers/stargate.ts +807 -135
  396. package/src/internal/index.test.ts +1 -0
  397. package/src/internal/index.ts +1 -0
  398. package/src/internal/webhookDestinations/slack.ts +15 -0
@@ -7,9 +7,11 @@ import { Addresses, TokenId, Transaction } from 'viem/tempo'
7
7
  import type * as App from '../../App.js'
8
8
  import * as BillingSettings from '../../db/tables/billingSettings.js'
9
9
  import * as Db from '../../db/Db.js'
10
+ import * as Organizations from '../../db/tables/organizations.js'
11
+ import * as Projects from '../../db/tables/projects.js'
12
+ import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
10
13
  import * as Fees from '../../internal/Fees.js'
11
14
  import * as Log from '../../internal/Log.js'
12
- import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
13
15
  import * as Billing from '../management/Billing.js'
14
16
  import * as Handler from '../../Handler.js'
15
17
  import * as Auth from '../../internal/Auth.js'
@@ -85,6 +87,7 @@ function middleware(options: middleware.Options) {
85
87
  : 0n
86
88
  return createMiddleware<App.Environment>(async (c) => {
87
89
  const principal = Auth.getPrincipal(c)
90
+ const requestedAt = new Date().toISOString()
88
91
  const verifiedTokens = c.get('verifiedTokens')
89
92
  const zoneToken_header = c.req.header(ZoneRpcAuthentication.headerName)
90
93
  const zoneToken = zoneToken_header?.trim() ? zoneToken_header : undefined
@@ -97,7 +100,11 @@ function middleware(options: middleware.Options) {
97
100
 
98
101
  const recordRejection = (options: middleware.Rejection) => {
99
102
  const id =
100
- Number.isSafeInteger(options.chainId) && options.chainId >= 0 ? options.chainId : null
103
+ options.chainId !== undefined &&
104
+ Number.isSafeInteger(options.chainId) &&
105
+ options.chainId >= 0
106
+ ? options.chainId
107
+ : null
101
108
  if (chainId !== null) chainId = chainId === undefined || chainId === id ? id : null
102
109
  if (options.reason === 'internal_error') internalErrors = Math.min(internalErrors + 1, 65_535)
103
110
  if (reason !== null)
@@ -121,22 +128,49 @@ function middleware(options: middleware.Options) {
121
128
  throw new Error('Internal error', { cause: new Error(options.message) })
122
129
  }
123
130
 
124
- const feePayer = (() => {
131
+ const feePayer = await (async () => {
125
132
  if (!options.feePayer) return undefined
126
- const attribution =
127
- principal?.type === 'api_key'
128
- ? {
129
- apiKeyId: principal.id,
130
- environment: principal.environment,
131
- orgId: principal.orgId,
132
- ...(principal.projectId === undefined ? {} : { projectId: principal.projectId }),
133
- }
134
- : undefined
133
+ const resolved = await resolveAttribution(Db.get(c.get('db')), {
134
+ principal,
135
+ projectId: c.req.header('tempo-project-id')?.trim(),
136
+ })
137
+ const attribution = resolved?.attribution
138
+ const attributionError = resolved?.error
135
139
  const sponsorable =
136
140
  attribution !== undefined &&
137
141
  principal?.type === 'api_key' &&
138
142
  (principal.apiKey.scopes.includes('rpc-relay:sponsor') ||
139
143
  principal.apiKey.scopes.includes(Scope.wildcard))
144
+ let promotionState_promise: Promise<middleware.PromotionState>
145
+ const promotionState = () =>
146
+ (promotionState_promise ??= (async () => {
147
+ if (!attribution) return { status: 'inactive' }
148
+ const db = Db.get(c.get('db'))
149
+ const organization = await Organizations.get(db, attribution.orgId)
150
+ if (!organization || organization.sponsorshipSubsidyDurationDays === null)
151
+ return { status: 'inactive' }
152
+ // Missing project attribution opts out of the subsidy but must not interrupt sponsorship.
153
+ if (!attribution.projectId) return { status: 'inactive' }
154
+ const project = await Projects.get(db, attribution.projectId)
155
+ if (!project) return { message: 'Attributed project disappeared.', status: 'error' }
156
+ if (project.orgId !== attribution.orgId)
157
+ return { message: 'Attributed project changed ownership.', status: 'error' }
158
+ if (
159
+ (project.sponsorshipSubsidyStartsAt === null) !==
160
+ (project.sponsorshipSubsidyEndsAt === null)
161
+ )
162
+ return { message: 'Project promotion window is inconsistent.', status: 'error' }
163
+ if (
164
+ project.sponsorshipSubsidyEndsAt !== null &&
165
+ Date.parse(requestedAt) >= Date.parse(project.sponsorshipSubsidyEndsAt)
166
+ )
167
+ return { status: 'inactive' }
168
+ return { status: 'active' }
169
+ })())
170
+ const validatePromotion = async (chainId: number) => {
171
+ const state = await promotionState()
172
+ if (state.status === 'error') failSponsorship({ chainId, message: state.message })
173
+ }
140
174
  // Lifted so validation and the sponsored-token default read one value.
141
175
  const feeToken_sponsor = options.feePayer.feeToken ?? Addresses.pathUsd
142
176
  return {
@@ -147,6 +181,12 @@ function middleware(options: middleware.Options) {
147
181
  // only mainnet: the relay resolves chainId from the request body, so
148
182
  // the query-string chain guard never sees it.
149
183
  const id = Number(transaction.chainId)
184
+ if (attributionError)
185
+ rejectSponsorship({
186
+ chainId: id,
187
+ message: 'Project attribution rejected.',
188
+ reason: attributionError,
189
+ })
150
190
  if (!attribution) {
151
191
  if (sponsorshipRequired)
152
192
  rejectSponsorship({
@@ -186,15 +226,9 @@ function middleware(options: middleware.Options) {
186
226
  })
187
227
  return false
188
228
  }
189
-
190
- // Enforcement reads hit the primary, never the cached replica: a
191
- // stale `past_due`, a lowered limit, or another isolate's pending
192
- // spend must not keep the gate open.
193
229
  const db = Db.get(c.get('db'))
194
- // Mainnet sponsorship requires an active billing source; deployments
195
- // without billing configured have no rows, so mainnet stays closed.
196
- // `past_due` gets its own reason: the caller must settle an invoice,
197
- // not attach a card.
230
+ // Enforcement reads hit the primary, never the cached replica: a
231
+ // stale `past_due` or another isolate's state must not keep the gate open.
198
232
  const billing = await Billing.status(db, attribution.orgId)
199
233
  if (billing === 'past_due') {
200
234
  if (sponsorshipRequired)
@@ -214,6 +248,7 @@ function middleware(options: middleware.Options) {
214
248
  })
215
249
  return 'billing_required'
216
250
  }
251
+ await validatePromotion(id)
217
252
 
218
253
  const feeToken = (() => {
219
254
  try {
@@ -286,7 +321,6 @@ function middleware(options: middleware.Options) {
286
321
  // reconciliation convention; pending rows charge it to spend limits.
287
322
  const feeMax = Fees.maxOf(Transaction.deserialize(event.transaction as `0x76${string}`))
288
323
  const db = Db.get(c.get('db'))
289
-
290
324
  const feeToken = (() => {
291
325
  try {
292
326
  return event.feeToken !== undefined ? TokenId.toAddress(event.feeToken) : undefined
@@ -318,13 +352,54 @@ function middleware(options: middleware.Options) {
318
352
  // Re-read settings on the authoritative path: the limit may have
319
353
  // changed since `validate`, and only mainnet spend is limited.
320
354
  const settings = mainnet ? await BillingSettings.get(db, attribution.orgId) : undefined
321
- if (settings?.spendLimit == null) return void (await SponsoredTransactions.upsert(db, input)) // prettier-ignore
355
+ const billingLimit =
356
+ settings?.spendLimit == null
357
+ ? undefined
358
+ : {
359
+ chainIds: [Viem.chainId.mainnet],
360
+ max: Billing.toBaseUnits(settings.spendLimit),
361
+ since: Billing.periodStart(settings.period),
362
+ }
363
+ if (
364
+ mainnet &&
365
+ attribution.environment === 'production' &&
366
+ attribution.projectId !== undefined
367
+ )
368
+ try {
369
+ const promotion = await SponsoredTransactions.reservePromotion(
370
+ db,
371
+ { ...input, billable: false },
372
+ {
373
+ at: new Date().toISOString(),
374
+ ...(billingLimit ? { billingLimit } : {}),
375
+ chainIds: [Viem.chainId.mainnet],
376
+ projectId: attribution.projectId,
377
+ },
378
+ )
379
+ if (promotion.status !== 'ineligible') return
380
+ } catch (error) {
381
+ if (error instanceof SponsoredTransactions.AttributionConflictError)
382
+ rejectSponsorship({
383
+ chainId: event.chainId,
384
+ message: 'Project attribution rejected.',
385
+ reason: 'project_id_invalid',
386
+ })
387
+ if (error instanceof SponsoredTransactions.PromotionActivationError)
388
+ failSponsorship({
389
+ chainId: event.chainId,
390
+ message: 'Project promotion activation failed.',
391
+ })
392
+ if (error instanceof SponsoredTransactions.PeriodSpendLimitError)
393
+ rejectSponsorship({
394
+ chainId: event.chainId,
395
+ message: 'Spend limit exceeded.',
396
+ reason: 'spend_limit_exceeded',
397
+ })
398
+ throw error
399
+ }
400
+ if (!billingLimit) return void (await SponsoredTransactions.upsert(db, input))
322
401
  try {
323
- await SponsoredTransactions.reserve(db, input, {
324
- chainIds: [Viem.chainId.mainnet],
325
- max: Billing.toBaseUnits(settings.spendLimit),
326
- since: Billing.periodStart(settings.period),
327
- })
402
+ await SponsoredTransactions.reserve(db, input, billingLimit)
328
403
  } catch (error) {
329
404
  if (error instanceof SponsoredTransactions.PeriodSpendLimitError)
330
405
  rejectSponsorship({
@@ -518,6 +593,57 @@ function sponsorshipMethod(value: unknown): Log.SponsorshipMethod | undefined {
518
593
  }
519
594
  }
520
595
 
596
+ /** Resolves optional request-level project attribution within the API key's organization. */
597
+ async function resolveAttribution(
598
+ db: Db.Db,
599
+ options: resolveAttribution.Options,
600
+ ): Promise<resolveAttribution.Result | undefined> {
601
+ if (options.principal?.type !== 'api_key') return undefined
602
+ const { principal } = options
603
+ const attribution = {
604
+ apiKeyId: principal.id,
605
+ environment: principal.environment,
606
+ orgId: principal.orgId,
607
+ ...(principal.projectId === undefined ? {} : { projectId: principal.projectId }),
608
+ }
609
+ if (!options.projectId) return { attribution }
610
+ if (principal.projectId !== undefined) return { attribution, error: 'project_id_invalid' }
611
+ const project = await Projects.get(db, options.projectId)
612
+ if (!project || project.orgId !== principal.orgId)
613
+ return { attribution, error: 'project_id_invalid' }
614
+ return { attribution: { ...attribution, projectId: project.id } }
615
+ }
616
+
617
+ declare namespace resolveAttribution {
618
+ /** Inputs for resolving API-key project attribution. */
619
+ type Options = {
620
+ /** Authenticated request principal. */
621
+ principal: Auth.Principal | null
622
+ /** Optional project id supplied by the caller. */
623
+ projectId?: string | undefined
624
+ }
625
+
626
+ /** Resolved API-key attribution. */
627
+ type Attribution = {
628
+ /** API key id. */
629
+ apiKeyId: string
630
+ /** API key environment. */
631
+ environment: 'production' | 'sandbox'
632
+ /** API key organization id. */
633
+ orgId: string
634
+ /** Attributed project id. */
635
+ projectId?: string | undefined
636
+ }
637
+
638
+ /** Attribution result with an optional policy error. */
639
+ type Result = {
640
+ /** Attribution retained for request diagnostics. */
641
+ attribution: Attribution
642
+ /** Project attribution error detected before transaction validation. */
643
+ error?: 'project_id_invalid' | undefined
644
+ }
645
+ }
646
+
521
647
  declare namespace middleware {
522
648
  /** One invariant sponsorship failure. */
523
649
  type Failure = {
@@ -535,10 +661,13 @@ declare namespace middleware {
535
661
  path: 'rpc/relay' | 'rpc/sponsor'
536
662
  }
537
663
 
664
+ /** Cached promotion validation state; request accounting is applied per batch item. */
665
+ type PromotionState = { status: 'active' | 'inactive' } | { message: string; status: 'error' }
666
+
538
667
  /** One sponsorship rejection recorded for request logging. */
539
668
  type Rejection = {
540
669
  /** Candidate transaction chain, validated before it enters the log entry. */
541
- chainId: number
670
+ chainId?: number | undefined
542
671
  /** Stable client error code. */
543
672
  reason: Log.SponsorshipReason
544
673
  }
@@ -546,7 +675,7 @@ declare namespace middleware {
546
675
  /** One client-visible sponsorship refusal. */
547
676
  type Refusal = {
548
677
  /** Candidate transaction chain. */
549
- chainId: number
678
+ chainId?: number | undefined
550
679
  /** Client-safe error message. */
551
680
  message: string
552
681
  /** Stable client error code. */
@@ -11,6 +11,7 @@ import { getTransactionReceipt } from 'viem/actions'
11
11
 
12
12
  import * as Db from '../../db/Db.js'
13
13
  import * as BillingSettings from '../../db/tables/billingSettings.js'
14
+ import * as Projects from '../../db/tables/projects.js'
14
15
  import * as Fees from '../../internal/Fees.js'
15
16
  import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
16
17
  import * as Tidx from '../../internal/Tidx.js'
@@ -292,7 +293,29 @@ export function createFinalizer(options: createFinalizer.Options): createFinaliz
292
293
  getClient,
293
294
  getTidx: (chainId) => getTidx(chainId as Viem.ChainId),
294
295
  limitFor: async (intent) => {
295
- if (!intent.billable || !Viem.isMainnet(intent.chainId)) return undefined
296
+ if (!Viem.isMainnet(intent.chainId)) return undefined
297
+ if (!intent.billable) {
298
+ if (!intent.projectId) return undefined
299
+ const project = await Projects.get(db, intent.projectId)
300
+ // Missing promotion state cannot prove the intent remains within Tempo's budget.
301
+ if (!project || project.sponsorshipSubsidyStartsAt === null)
302
+ return {
303
+ billable: false,
304
+ chainIds: [Viem.chainId.mainnet],
305
+ max: -1n,
306
+ projectId: intent.projectId,
307
+ since: intent.createdAt,
308
+ }
309
+ if (project.sponsorshipSpendLimit === null) return undefined
310
+ return {
311
+ billable: false,
312
+ chainIds: [Viem.chainId.mainnet],
313
+ max: BigInt(project.sponsorshipSpendLimit),
314
+ projectId: intent.projectId,
315
+ since: project.sponsorshipSubsidyStartsAt,
316
+ }
317
+ }
318
+ if (!intent.billable) return undefined
296
319
  const settings = await BillingSettings.get(db, intent.orgId)
297
320
  if (settings?.spendLimit == null) return undefined
298
321
  return {
package/src/db/Db.ts CHANGED
@@ -1483,6 +1483,46 @@ export const migrations: readonly Migration[] = [
1483
1483
  sql`ALTER TABLE earn_vaults ADD COLUMN IF NOT EXISTS zones jsonb NOT NULL DEFAULT '[]'::jsonb`,
1484
1484
  ),
1485
1485
  },
1486
+ {
1487
+ name: '0137_funding_routes_configuration',
1488
+ up: (db) =>
1489
+ raw(db, sql`ALTER TABLE funding_routes ADD COLUMN IF NOT EXISTS configuration jsonb`),
1490
+ },
1491
+ {
1492
+ name: '0138_organizations_sponsorship_subsidy',
1493
+ up: (db) =>
1494
+ raw(
1495
+ db,
1496
+ sql`ALTER TABLE organizations
1497
+ ADD COLUMN IF NOT EXISTS sponsorship_subsidy_duration_days integer,
1498
+ ADD COLUMN IF NOT EXISTS sponsorship_subsidy_project_spend_limit text`,
1499
+ ),
1500
+ },
1501
+ {
1502
+ name: '0139_projects_sponsorship_activation',
1503
+ up: (db) =>
1504
+ raw(
1505
+ db,
1506
+ sql`ALTER TABLE projects
1507
+ ADD COLUMN IF NOT EXISTS sponsorship_subsidy_starts_at text,
1508
+ ADD COLUMN IF NOT EXISTS sponsorship_subsidy_ends_at text,
1509
+ DROP CONSTRAINT IF EXISTS projects_sponsorship_subsidy_window_consistent,
1510
+ ADD CONSTRAINT projects_sponsorship_subsidy_window_consistent CHECK ((sponsorship_subsidy_starts_at IS NULL) = (sponsorship_subsidy_ends_at IS NULL))`,
1511
+ ),
1512
+ },
1513
+ {
1514
+ name: '0140_projects_sponsorship_spend_limit',
1515
+ up: (db) =>
1516
+ raw(db, sql`ALTER TABLE projects ADD COLUMN IF NOT EXISTS sponsorship_spend_limit text`),
1517
+ },
1518
+ {
1519
+ name: '0141_sponsored_transactions_promotion_spend_index',
1520
+ up: (db) =>
1521
+ raw(
1522
+ db,
1523
+ sql`CREATE INDEX IF NOT EXISTS sponsored_transactions_promotion_spend_idx ON sponsored_transactions (org_id, project_id, status, created_at) WHERE NOT billable`,
1524
+ ),
1525
+ },
1486
1526
  ]
1487
1527
 
1488
1528
  /**
@@ -1620,7 +1660,14 @@ declare namespace fromDialect {
1620
1660
  */
1621
1661
  export function postgres(options: postgres.Options): Db {
1622
1662
  const { connectionString, schema } = options
1623
- const pool = new pg.Pool(schema ? { connectionString, max: 1 } : { connectionString })
1663
+ // Bounded connect and pool-checkout wait: the pg default (0) waits forever,
1664
+ // so an unresponsive origin parks callers and their invocation memory
1665
+ // invisibly instead of failing into the retry paths.
1666
+ const pool = new pg.Pool({
1667
+ connectionString,
1668
+ connectionTimeoutMillis: 30_000,
1669
+ ...(schema ? { max: 1 } : {}),
1670
+ })
1624
1671
  return fromDialect(new PostgresDialect({ pool }), { schema })
1625
1672
  }
1626
1673
 
package/src/db/Schema.ts CHANGED
@@ -219,6 +219,12 @@ export const Organization = z.object({
219
219
  name: z
220
220
  .string()
221
221
  .check(z.describe('Human-readable organization name.'), z.meta({ examples: ['Acme, Inc.'] })),
222
+ sponsorshipSubsidyDurationDays: z
223
+ .nullable(z.number().check(z.int(), z.positive()))
224
+ .check(z.describe('Promotional sponsorship duration in days, or null when disabled.')),
225
+ sponsorshipSubsidyProjectSpendLimit: z
226
+ .nullable(z.string())
227
+ .check(z.describe('Per-project promotional spend cap in decimal USD, or null when disabled.')),
222
228
  updatedAt: z.string().check(z.describe('When the organization was last updated (ISO 8601).')),
223
229
  userId: z
224
230
  .nullable(z.string())
@@ -243,6 +249,15 @@ export const Project = z.object({
243
249
  .string()
244
250
  .check(z.describe('Human-readable project name.'), z.meta({ examples: ['Checkout'] })),
245
251
  orgId: z.string().check(z.describe('Owning organization id (`org_…`).')),
252
+ sponsorshipSubsidyStartsAt: z
253
+ .nullable(z.string())
254
+ .check(z.describe('When the project first activated promotional sponsorship, or null.')),
255
+ sponsorshipSubsidyEndsAt: z
256
+ .nullable(z.string())
257
+ .check(z.describe('When the project promotional sponsorship ends, or null.')),
258
+ sponsorshipSpendLimit: z
259
+ .nullable(z.string())
260
+ .check(z.describe('Promotional spend cap in fee-token base units, or null.')),
246
261
  updatedAt: z.string().check(z.describe('When the project was last updated (ISO 8601).')),
247
262
  })
248
263
 
@@ -453,11 +468,14 @@ export type FundingChainToken = z.output<typeof FundingChainToken>
453
468
 
454
469
  /** One source-to-destination route supported by a funding provider. */
455
470
  export const FundingRoute = z.object({
456
- destinationChainId: z.string().check(z.describe('Destination funding chain identifier.')),
457
- destinationTokenId: z.string().check(z.describe('Destination funding token identifier.')),
458
471
  capabilities: z
459
472
  .nullable(z.custom<funding_Catalog.Capabilities>())
460
473
  .check(z.describe('Executable capability (`jsonb`), or null for indicative-only routes.')),
474
+ configuration: z
475
+ .nullable(z.custom<funding_Catalog.Configuration>())
476
+ .check(z.describe('Provider-specific route configuration (`jsonb`), or null.')),
477
+ destinationChainId: z.string().check(z.describe('Destination funding chain identifier.')),
478
+ destinationTokenId: z.string().check(z.describe('Destination funding token identifier.')),
461
479
  providerId: z.string().check(z.describe('Funding provider identifier.')),
462
480
  sourceChainId: z.string().check(z.describe('Source funding chain identifier.')),
463
481
  sourceTokenId: z.string().check(z.describe('Source funding token identifier.')),
@@ -637,8 +655,8 @@ export const FundingTransfer = z.object({
637
655
  .check(z.describe('Attributed project id (`prj_…`), or null for organization-attributed keys.')), // prettier-ignore
638
656
  providerId: z.string().check(z.describe('Selected funding provider id.')),
639
657
  providerState: z
640
- .nullable(z.custom<funding_Provider.prepareTransfer.Correlation>())
641
- .check(z.describe('Private provider correlation (`jsonb`), or null. Never serialized publicly.')), // prettier-ignore
658
+ .nullable(z.custom<funding_Transfer.ProviderState>())
659
+ .check(z.describe('Private provider state (`jsonb`), or null. Never serialized publicly.')), // prettier-ignore
642
660
  quoteExpiresAt: z
643
661
  .string()
644
662
  .check(z.describe('When the quoted terms stop being executable (ISO 8601).')),
@@ -13,9 +13,9 @@ const input = {
13
13
  vaultAddress: '0x2f05f4d3625B2eFFb383eFbEDAFd2D8F04371636',
14
14
  zones: [
15
15
  {
16
- chainId: 1_424_310_001,
17
- deploymentBlock: 31_039_238,
18
- earnRouter: '0x9144086A79dea966d8bf1B52125eBED8919fC45c',
16
+ chainId: 1_424_310_003,
17
+ deploymentBlock: 31_730_087,
18
+ earnRouter: '0xeBEdC74FFeEAD186ACC6caC05Eb82fB9e093a7c7',
19
19
  },
20
20
  ],
21
21
  } as const satisfies EarnVaults.upsert.Options
@@ -45,9 +45,9 @@ describe('upsert', () => {
45
45
  "vaultAddress": "0x2f05f4d3625b2effb383efbedafd2d8f04371636",
46
46
  "zones": [
47
47
  {
48
- "chainId": 1424310001,
49
- "deploymentBlock": 31039238,
50
- "earnRouter": "0x9144086a79dea966d8bf1b52125ebed8919fc45c",
48
+ "chainId": 1424310003,
49
+ "deploymentBlock": 31730087,
50
+ "earnRouter": "0xebedc74ffeead186acc6cac05eb82fb9e093a7c7",
51
51
  },
52
52
  ],
53
53
  }
@@ -93,9 +93,9 @@ describe('upsert', () => {
93
93
  "updatedAt": "2026-01-02T00:00:00.000Z",
94
94
  "zones": [
95
95
  {
96
- "chainId": 1424310001,
97
- "deploymentBlock": 31039238,
98
- "earnRouter": "0x9144086a79dea966d8bf1b52125ebed8919fc45c",
96
+ "chainId": 1424310003,
97
+ "deploymentBlock": 31730087,
98
+ "earnRouter": "0xebedc74ffeead186acc6cac05eb82fb9e093a7c7",
99
99
  },
100
100
  ],
101
101
  }
@@ -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
 
@@ -67,3 +67,46 @@ describe('insert', () => {
67
67
  ])
68
68
  })
69
69
  })
70
+
71
+ describe('claimSource', () => {
72
+ test('returns claimed, replayed, and conflicting ownership outcomes', async () => {
73
+ const first = await createTransfer('ftr_claim_first')
74
+ const second = await createTransfer('ftr_claim_second')
75
+ const record = {
76
+ chainId: 'eip155:8453',
77
+ createdAt: '2026-01-01T00:00:01.000Z',
78
+ role: 'source' as const,
79
+ transactionRef: `0x${'cd'.repeat(32)}`,
80
+ transferId: first.id,
81
+ }
82
+ const claimed = await FundingTransferTransactions.claimSource(db, record)
83
+ const replayed = await FundingTransferTransactions.claimSource(db, record)
84
+ const conflict = await FundingTransferTransactions.claimSource(db, {
85
+ ...record,
86
+ transferId: second.id,
87
+ })
88
+ expect([claimed.type, replayed.type, conflict.type]).toEqual([
89
+ 'claimed',
90
+ 'replayed',
91
+ 'conflict',
92
+ ])
93
+ })
94
+ })
95
+
96
+ describe('getSource', () => {
97
+ test('resolves EVM hashes case-insensitively', async () => {
98
+ const transfer = await createTransfer('ftr_get_source')
99
+ await FundingTransferTransactions.claimSource(db, {
100
+ chainId: 'eip155:8453',
101
+ createdAt: '2026-01-01T00:00:01.000Z',
102
+ role: 'source',
103
+ transactionRef: `0x${'ce'.repeat(32)}`,
104
+ transferId: transfer.id,
105
+ })
106
+ const record = await FundingTransferTransactions.getSource(db, {
107
+ chainId: 'eip155:8453',
108
+ transactionRef: `0x${'CE'.repeat(32)}`,
109
+ })
110
+ expect(record?.transferId).toBe(transfer.id)
111
+ })
112
+ })