tapimo 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (689) hide show
  1. package/dist/ApiKeys.d.ts +24 -7
  2. package/dist/ApiKeys.d.ts.map +1 -1
  3. package/dist/ApiKeys.js +17 -5
  4. package/dist/ApiKeys.js.map +1 -1
  5. package/dist/App.d.ts +12104 -7861
  6. package/dist/App.d.ts.map +1 -1
  7. package/dist/App.js +191 -128
  8. package/dist/App.js.map +1 -1
  9. package/dist/Assets.d.ts +10 -0
  10. package/dist/Assets.d.ts.map +1 -1
  11. package/dist/Assets.js +17 -4
  12. package/dist/Assets.js.map +1 -1
  13. package/dist/Scope.d.ts +19 -8
  14. package/dist/Scope.d.ts.map +1 -1
  15. package/dist/Scope.js +25 -9
  16. package/dist/Scope.js.map +1 -1
  17. package/dist/Webhooks.d.ts +1 -1
  18. package/dist/admin/App.d.ts +601 -20
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/App.js +12 -2
  21. package/dist/admin/App.js.map +1 -1
  22. package/dist/admin/apps/api-keys.d.ts +94 -11
  23. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.js +141 -32
  25. package/dist/admin/apps/api-keys.js.map +1 -1
  26. package/dist/admin/apps/earn-vaults.d.ts +1069 -0
  27. package/dist/admin/apps/earn-vaults.d.ts.map +1 -0
  28. package/dist/admin/apps/earn-vaults.js +409 -0
  29. package/dist/admin/apps/earn-vaults.js.map +1 -0
  30. package/dist/admin/apps/verified-tokens.d.ts +166 -166
  31. package/dist/admin/ui.generated.d.ts +1 -1
  32. package/dist/admin/ui.generated.d.ts.map +1 -1
  33. package/dist/admin/ui.generated.js +151 -95
  34. package/dist/admin/ui.generated.js.map +1 -1
  35. package/dist/analytics/Analytics.d.ts +2 -0
  36. package/dist/analytics/Analytics.d.ts.map +1 -1
  37. package/dist/analytics/Analytics.js +18 -2
  38. package/dist/analytics/Analytics.js.map +1 -1
  39. package/dist/analytics/tables/requestEvents.d.ts +22 -1
  40. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  41. package/dist/analytics/tables/requestEvents.js +59 -39
  42. package/dist/analytics/tables/requestEvents.js.map +1 -1
  43. package/dist/apps/Zones.d.ts +8335 -0
  44. package/dist/apps/Zones.d.ts.map +1 -0
  45. package/dist/apps/Zones.js +38 -0
  46. package/dist/apps/Zones.js.map +1 -0
  47. package/dist/apps/data/App.d.ts +14377 -10165
  48. package/dist/apps/data/App.d.ts.map +1 -1
  49. package/dist/apps/data/App.js +91 -19
  50. package/dist/apps/data/App.js.map +1 -1
  51. package/dist/apps/data/FxOracle.d.ts +1 -1
  52. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  53. package/dist/apps/data/index.d.ts +0 -1
  54. package/dist/apps/data/index.d.ts.map +1 -1
  55. package/dist/apps/data/index.js +0 -1
  56. package/dist/apps/data/index.js.map +1 -1
  57. package/dist/apps/data/routes/activities.d.ts +32 -32
  58. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  59. package/dist/apps/data/routes/activities.js +130 -65
  60. package/dist/apps/data/routes/activities.js.map +1 -1
  61. package/dist/apps/data/routes/addresses.d.ts +312 -312
  62. package/dist/apps/data/routes/balances.d.ts +18 -18
  63. package/dist/apps/data/routes/balances.js +5 -5
  64. package/dist/apps/data/routes/balances.js.map +1 -1
  65. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  66. package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
  67. package/dist/apps/data/routes/coingecko.js +62 -35
  68. package/dist/apps/data/routes/coingecko.js.map +1 -1
  69. package/dist/apps/data/routes/earn.d.ts +2144 -0
  70. package/dist/apps/data/routes/earn.d.ts.map +1 -0
  71. package/dist/apps/data/routes/earn.js +947 -0
  72. package/dist/apps/data/routes/earn.js.map +1 -0
  73. package/dist/apps/data/routes/exchanges.d.ts +1231 -324
  74. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  75. package/dist/apps/data/routes/exchanges.js +656 -193
  76. package/dist/apps/data/routes/exchanges.js.map +1 -1
  77. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  78. package/dist/apps/data/routes/fee-amm.js +59 -8
  79. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  80. package/dist/apps/data/routes/funding.d.ts +4092 -387
  81. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  82. package/dist/apps/data/routes/funding.js +1403 -256
  83. package/dist/apps/data/routes/funding.js.map +1 -1
  84. package/dist/apps/data/routes/indexer.d.ts +5 -5
  85. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  86. package/dist/apps/data/routes/indexer.js +19 -2
  87. package/dist/apps/data/routes/indexer.js.map +1 -1
  88. package/dist/apps/data/routes/receipts.d.ts +265 -265
  89. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  90. package/dist/apps/data/routes/receipts.js +11 -11
  91. package/dist/apps/data/routes/receipts.js.map +1 -1
  92. package/dist/apps/data/routes/rpc.d.ts +5 -5
  93. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  94. package/dist/apps/data/routes/rpc.js +211 -22
  95. package/dist/apps/data/routes/rpc.js.map +1 -1
  96. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  97. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  98. package/dist/apps/data/routes/tokenlist.js +49 -9
  99. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  100. package/dist/apps/data/routes/tokens.d.ts +116 -88
  101. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  102. package/dist/apps/data/routes/tokens.js +198 -91
  103. package/dist/apps/data/routes/tokens.js.map +1 -1
  104. package/dist/apps/data/routes/transactions.d.ts +55 -55
  105. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  106. package/dist/apps/data/routes/transactions.js +78 -39
  107. package/dist/apps/data/routes/transactions.js.map +1 -1
  108. package/dist/apps/data/routes/transfers.d.ts +78 -29
  109. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  110. package/dist/apps/data/routes/transfers.js +165 -67
  111. package/dist/apps/data/routes/transfers.js.map +1 -1
  112. package/dist/apps/data/routes/valuation.d.ts +10 -10
  113. package/dist/apps/data/routes/valuation.js +2 -2
  114. package/dist/apps/data/routes/valuation.js.map +1 -1
  115. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  116. package/dist/apps/data/routes/verified-tokens.js +34 -9
  117. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  118. package/dist/apps/data/routes/webhooks.d.ts +5 -3
  119. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  120. package/dist/apps/data/routes/webhooks.js +175 -65
  121. package/dist/apps/data/routes/webhooks.js.map +1 -1
  122. package/dist/apps/data/routes/zones.d.ts +281 -0
  123. package/dist/apps/data/routes/zones.d.ts.map +1 -0
  124. package/dist/apps/data/routes/zones.js +252 -0
  125. package/dist/apps/data/routes/zones.js.map +1 -0
  126. package/dist/apps/docs/reference.js +6 -5
  127. package/dist/apps/docs/reference.js.map +1 -1
  128. package/dist/apps/index.d.ts +2 -1
  129. package/dist/apps/index.d.ts.map +1 -1
  130. package/dist/apps/index.js +2 -1
  131. package/dist/apps/index.js.map +1 -1
  132. package/dist/apps/management/App.d.ts +670 -670
  133. package/dist/apps/management/Billing.d.ts +8 -0
  134. package/dist/apps/management/Billing.d.ts.map +1 -1
  135. package/dist/apps/management/Billing.js +12 -0
  136. package/dist/apps/management/Billing.js.map +1 -1
  137. package/dist/apps/management/Error.d.ts +12 -0
  138. package/dist/apps/management/Error.d.ts.map +1 -0
  139. package/dist/apps/management/Error.js +10 -0
  140. package/dist/apps/management/Error.js.map +1 -0
  141. package/dist/apps/management/routes/api-keys.d.ts +5 -5
  142. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  143. package/dist/apps/management/routes/api-keys.js +2 -8
  144. package/dist/apps/management/routes/api-keys.js.map +1 -1
  145. package/dist/apps/management/routes/billing.d.ts +119 -119
  146. package/dist/apps/management/routes/faucet.d.ts +35 -35
  147. package/dist/apps/management/routes/invitations.d.ts +62 -62
  148. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  149. package/dist/apps/management/routes/invitations.js +4 -10
  150. package/dist/apps/management/routes/invitations.js.map +1 -1
  151. package/dist/apps/management/routes/invite-links.d.ts +30 -30
  152. package/dist/apps/management/routes/me.d.ts +6 -6
  153. package/dist/apps/management/routes/members.d.ts +78 -78
  154. package/dist/apps/management/routes/orgs.d.ts +48 -48
  155. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  156. package/dist/apps/management/routes/orgs.js +2 -8
  157. package/dist/apps/management/routes/orgs.js.map +1 -1
  158. package/dist/apps/management/routes/projects.d.ts +9 -9
  159. package/dist/apps/management/routes/scopes.js +2 -2
  160. package/dist/apps/management/routes/scopes.js.map +1 -1
  161. package/dist/apps/management/routes/usage.d.ts +80 -80
  162. package/dist/apps/management/routes/verified-token-requests.d.ts +36 -36
  163. package/dist/apps/management/routes/webhooks.d.ts +50 -50
  164. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  165. package/dist/apps/management/routes/webhooks.js +15 -3
  166. package/dist/apps/management/routes/webhooks.js.map +1 -1
  167. package/dist/apps/mpp/App.d.ts +55 -56
  168. package/dist/apps/mpp/App.d.ts.map +1 -1
  169. package/dist/apps/mpp/App.js +192 -27
  170. package/dist/apps/mpp/App.js.map +1 -1
  171. package/dist/apps/mpp/Mpp.d.ts +0 -20
  172. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  173. package/dist/apps/mpp/Mpp.js +0 -24
  174. package/dist/apps/mpp/Mpp.js.map +1 -1
  175. package/dist/apps/mpp/internal/Adapter.d.ts +40 -0
  176. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -0
  177. package/dist/apps/mpp/internal/Adapter.js +66 -0
  178. package/dist/apps/mpp/internal/Adapter.js.map +1 -0
  179. package/dist/apps/mpp/internal/Error.d.ts +4 -0
  180. package/dist/apps/mpp/internal/Error.d.ts.map +1 -0
  181. package/dist/apps/mpp/internal/Error.js +41 -0
  182. package/dist/apps/mpp/internal/Error.js.map +1 -0
  183. package/dist/apps/mpp/internal/Idempotency.d.ts +33 -0
  184. package/dist/apps/mpp/internal/Idempotency.d.ts.map +1 -0
  185. package/dist/apps/mpp/internal/Idempotency.js +28 -0
  186. package/dist/apps/mpp/internal/Idempotency.js.map +1 -0
  187. package/dist/apps/mpp/internal/Policy.d.ts +46 -0
  188. package/dist/apps/mpp/internal/Policy.d.ts.map +1 -0
  189. package/dist/apps/mpp/internal/Policy.js +57 -0
  190. package/dist/apps/mpp/internal/Policy.js.map +1 -0
  191. package/dist/apps/mpp/internal/ScreenManager.d.ts +27 -0
  192. package/dist/apps/mpp/internal/ScreenManager.d.ts.map +1 -0
  193. package/dist/apps/mpp/internal/ScreenManager.js +18 -0
  194. package/dist/apps/mpp/internal/ScreenManager.js.map +1 -0
  195. package/dist/apps/mpp/internal/Screening.d.ts +35 -0
  196. package/dist/apps/mpp/internal/Screening.d.ts.map +1 -0
  197. package/dist/apps/mpp/internal/Screening.js +20 -0
  198. package/dist/apps/mpp/internal/Screening.js.map +1 -0
  199. package/dist/apps/mpp/internal/Sponsorship.d.ts +36 -0
  200. package/dist/apps/mpp/internal/Sponsorship.d.ts.map +1 -0
  201. package/dist/apps/mpp/internal/Sponsorship.js +103 -0
  202. package/dist/apps/mpp/internal/Sponsorship.js.map +1 -0
  203. package/dist/apps/mpp/internal/Utils.d.ts +7 -0
  204. package/dist/apps/mpp/internal/Utils.d.ts.map +1 -0
  205. package/dist/apps/mpp/internal/Utils.js +24 -0
  206. package/dist/apps/mpp/internal/Utils.js.map +1 -0
  207. package/dist/apps/relay/App.js +184 -24
  208. package/dist/apps/relay/App.js.map +1 -1
  209. package/dist/apps/relay/Sponsorships.d.ts +4 -0
  210. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  211. package/dist/apps/relay/Sponsorships.js +16 -7
  212. package/dist/apps/relay/Sponsorships.js.map +1 -1
  213. package/dist/db/Db.d.ts +20 -0
  214. package/dist/db/Db.d.ts.map +1 -1
  215. package/dist/db/Db.js +342 -0
  216. package/dist/db/Db.js.map +1 -1
  217. package/dist/db/Schema.d.ts +921 -1
  218. package/dist/db/Schema.d.ts.map +1 -1
  219. package/dist/db/Schema.js +269 -1
  220. package/dist/db/Schema.js.map +1 -1
  221. package/dist/db/tables/earnVaults.d.ts +68 -0
  222. package/dist/db/tables/earnVaults.d.ts.map +1 -0
  223. package/dist/db/tables/earnVaults.js +72 -0
  224. package/dist/db/tables/earnVaults.js.map +1 -0
  225. package/dist/db/tables/fundingCatalog.d.ts +56 -0
  226. package/dist/db/tables/fundingCatalog.d.ts.map +1 -0
  227. package/dist/db/tables/fundingCatalog.js +85 -0
  228. package/dist/db/tables/fundingCatalog.js.map +1 -0
  229. package/dist/db/tables/fundingDepositAddresses.d.ts +159 -0
  230. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -0
  231. package/dist/db/tables/fundingDepositAddresses.js +206 -0
  232. package/dist/db/tables/fundingDepositAddresses.js.map +1 -0
  233. package/dist/db/tables/fundingDeposits.d.ts +115 -0
  234. package/dist/db/tables/fundingDeposits.d.ts.map +1 -0
  235. package/dist/db/tables/fundingDeposits.js +100 -0
  236. package/dist/db/tables/fundingDeposits.js.map +1 -0
  237. package/dist/db/tables/fundingIdempotency.d.ts +91 -0
  238. package/dist/db/tables/fundingIdempotency.d.ts.map +1 -0
  239. package/dist/db/tables/fundingIdempotency.js +103 -0
  240. package/dist/db/tables/fundingIdempotency.js.map +1 -0
  241. package/dist/db/tables/fundingTransferEvents.d.ts +12 -0
  242. package/dist/db/tables/fundingTransferEvents.d.ts.map +1 -0
  243. package/dist/db/tables/fundingTransferEvents.js +18 -0
  244. package/dist/db/tables/fundingTransferEvents.js.map +1 -0
  245. package/dist/db/tables/fundingTransferTransactions.d.ts +15 -0
  246. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -0
  247. package/dist/db/tables/fundingTransferTransactions.js +27 -0
  248. package/dist/db/tables/fundingTransferTransactions.js.map +1 -0
  249. package/dist/db/tables/fundingTransfers.d.ts +82 -0
  250. package/dist/db/tables/fundingTransfers.d.ts.map +1 -0
  251. package/dist/db/tables/fundingTransfers.js +89 -0
  252. package/dist/db/tables/fundingTransfers.js.map +1 -0
  253. package/dist/db/tables/organizations.d.ts +8 -0
  254. package/dist/db/tables/organizations.d.ts.map +1 -1
  255. package/dist/db/tables/organizations.js +35 -0
  256. package/dist/db/tables/organizations.js.map +1 -1
  257. package/dist/db/tables/projects.d.ts +8 -0
  258. package/dist/db/tables/projects.d.ts.map +1 -1
  259. package/dist/db/tables/projects.js +16 -0
  260. package/dist/db/tables/projects.js.map +1 -1
  261. package/dist/db/tables/requestUsage.d.ts +13 -0
  262. package/dist/db/tables/requestUsage.d.ts.map +1 -1
  263. package/dist/db/tables/requestUsage.js +16 -0
  264. package/dist/db/tables/requestUsage.js.map +1 -1
  265. package/dist/db/tables/sponsoredTransactions.d.ts +4 -2
  266. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  267. package/dist/db/tables/sponsoredTransactions.js +14 -4
  268. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  269. package/dist/db/tables/users.d.ts +8 -0
  270. package/dist/db/tables/users.d.ts.map +1 -1
  271. package/dist/db/tables/users.js +16 -0
  272. package/dist/db/tables/users.js.map +1 -1
  273. package/dist/db/tables/verifiedTokens.d.ts +14 -7
  274. package/dist/db/tables/verifiedTokens.d.ts.map +1 -1
  275. package/dist/db/tables/verifiedTokens.js +9 -7
  276. package/dist/db/tables/verifiedTokens.js.map +1 -1
  277. package/dist/db/tables/webhookSubscriptions.d.ts +31 -1
  278. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  279. package/dist/db/tables/webhookSubscriptions.js +24 -3
  280. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  281. package/dist/handlers/relay.js +108 -108
  282. package/dist/handlers/relay.js.map +1 -1
  283. package/dist/internal/Auth.d.ts +26 -0
  284. package/dist/internal/Auth.d.ts.map +1 -1
  285. package/dist/internal/Auth.js +35 -2
  286. package/dist/internal/Auth.js.map +1 -1
  287. package/dist/internal/Cache.d.ts.map +1 -1
  288. package/dist/internal/Cache.js +16 -10
  289. package/dist/internal/Cache.js.map +1 -1
  290. package/dist/internal/Cursor.js +1 -1
  291. package/dist/internal/Cursor.js.map +1 -1
  292. package/dist/internal/EarnRates.d.ts +89 -0
  293. package/dist/internal/EarnRates.d.ts.map +1 -0
  294. package/dist/internal/EarnRates.js +266 -0
  295. package/dist/internal/EarnRates.js.map +1 -0
  296. package/dist/internal/EarnVaults.d.ts +171 -0
  297. package/dist/internal/EarnVaults.d.ts.map +1 -0
  298. package/dist/internal/EarnVaults.js +547 -0
  299. package/dist/internal/EarnVaults.js.map +1 -0
  300. package/dist/internal/EdgeCache.d.ts +9 -2
  301. package/dist/internal/EdgeCache.d.ts.map +1 -1
  302. package/dist/internal/EdgeCache.js +36 -9
  303. package/dist/internal/EdgeCache.js.map +1 -1
  304. package/dist/internal/Id.d.ts +11 -0
  305. package/dist/internal/Id.d.ts.map +1 -1
  306. package/dist/internal/Id.js +13 -0
  307. package/dist/internal/Id.js.map +1 -1
  308. package/dist/internal/Idempotency.d.ts +83 -0
  309. package/dist/internal/Idempotency.d.ts.map +1 -0
  310. package/dist/internal/Idempotency.js +113 -0
  311. package/dist/internal/Idempotency.js.map +1 -0
  312. package/dist/internal/Log.d.ts +64 -9
  313. package/dist/internal/Log.d.ts.map +1 -1
  314. package/dist/internal/Log.js +29 -7
  315. package/dist/internal/Log.js.map +1 -1
  316. package/dist/internal/MetricSink.d.ts.map +1 -1
  317. package/dist/internal/MetricSink.js +47 -1
  318. package/dist/internal/MetricSink.js.map +1 -1
  319. package/dist/internal/OpenApi.d.ts +1 -1
  320. package/dist/internal/OpenApi.d.ts.map +1 -1
  321. package/dist/internal/OpenApi.js +3 -0
  322. package/dist/internal/OpenApi.js.map +1 -1
  323. package/dist/internal/Provider.d.ts +14 -0
  324. package/dist/internal/Provider.d.ts.map +1 -0
  325. package/dist/internal/Provider.js +5 -0
  326. package/dist/internal/Provider.js.map +1 -0
  327. package/dist/internal/Schema.d.ts +8 -3
  328. package/dist/internal/Schema.d.ts.map +1 -1
  329. package/dist/internal/Schema.js +24 -13
  330. package/dist/internal/Schema.js.map +1 -1
  331. package/dist/internal/Store.d.ts +6 -0
  332. package/dist/internal/Store.d.ts.map +1 -1
  333. package/dist/internal/Store.js +35 -0
  334. package/dist/internal/Store.js.map +1 -1
  335. package/dist/internal/Tidx.d.ts +43 -24
  336. package/dist/internal/Tidx.d.ts.map +1 -1
  337. package/dist/internal/Tidx.js +79 -11
  338. package/dist/internal/Tidx.js.map +1 -1
  339. package/dist/internal/Viem.d.ts +82 -65
  340. package/dist/internal/Viem.d.ts.map +1 -1
  341. package/dist/internal/Viem.js +83 -12
  342. package/dist/internal/Viem.js.map +1 -1
  343. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  344. package/dist/internal/WebhookDestination.js +34 -28
  345. package/dist/internal/WebhookDestination.js.map +1 -1
  346. package/dist/internal/Webhooks.d.ts +9 -0
  347. package/dist/internal/Webhooks.d.ts.map +1 -1
  348. package/dist/internal/Webhooks.js +15 -10
  349. package/dist/internal/Webhooks.js.map +1 -1
  350. package/dist/internal/exchange/Provider.d.ts +219 -0
  351. package/dist/internal/exchange/Provider.d.ts.map +1 -0
  352. package/dist/internal/exchange/Provider.js +117 -0
  353. package/dist/internal/exchange/Provider.js.map +1 -0
  354. package/dist/internal/exchange/index.d.ts +2 -0
  355. package/dist/internal/exchange/index.d.ts.map +1 -0
  356. package/dist/internal/exchange/index.js +2 -0
  357. package/dist/internal/exchange/index.js.map +1 -0
  358. package/dist/internal/exchange/providers/uniswap.d.ts +57 -0
  359. package/dist/internal/exchange/providers/uniswap.d.ts.map +1 -0
  360. package/dist/internal/exchange/providers/uniswap.js +553 -0
  361. package/dist/internal/exchange/providers/uniswap.js.map +1 -0
  362. package/dist/internal/funding/Action.d.ts +55 -0
  363. package/dist/internal/funding/Action.d.ts.map +1 -0
  364. package/dist/internal/funding/Action.js +74 -0
  365. package/dist/internal/funding/Action.js.map +1 -0
  366. package/dist/internal/funding/Catalog.d.ts +107 -0
  367. package/dist/internal/funding/Catalog.d.ts.map +1 -0
  368. package/dist/internal/funding/Catalog.js +270 -0
  369. package/dist/internal/funding/Catalog.js.map +1 -0
  370. package/dist/internal/funding/Chain.d.ts +76 -0
  371. package/dist/internal/funding/Chain.d.ts.map +1 -0
  372. package/dist/internal/funding/Chain.js +38 -0
  373. package/dist/internal/funding/Chain.js.map +1 -0
  374. package/dist/internal/funding/Deposit.d.ts +366 -0
  375. package/dist/internal/funding/Deposit.d.ts.map +1 -0
  376. package/dist/internal/funding/Deposit.js +286 -0
  377. package/dist/internal/funding/Deposit.js.map +1 -0
  378. package/dist/internal/funding/DepositAddress.d.ts +345 -0
  379. package/dist/internal/funding/DepositAddress.d.ts.map +1 -0
  380. package/dist/internal/funding/DepositAddress.js +189 -0
  381. package/dist/internal/funding/DepositAddress.js.map +1 -0
  382. package/dist/internal/funding/Evidence.d.ts +52 -0
  383. package/dist/internal/funding/Evidence.d.ts.map +1 -0
  384. package/dist/internal/funding/Evidence.js +314 -0
  385. package/dist/internal/funding/Evidence.js.map +1 -0
  386. package/dist/internal/funding/Provider.d.ts +1186 -0
  387. package/dist/internal/funding/Provider.d.ts.map +1 -0
  388. package/dist/internal/funding/Provider.js +1081 -0
  389. package/dist/internal/funding/Provider.js.map +1 -0
  390. package/dist/internal/funding/Reconciliation.d.ts +113 -0
  391. package/dist/internal/funding/Reconciliation.d.ts.map +1 -0
  392. package/dist/internal/funding/Reconciliation.js +676 -0
  393. package/dist/internal/funding/Reconciliation.js.map +1 -0
  394. package/dist/internal/funding/Route.d.ts +21 -0
  395. package/dist/internal/funding/Route.d.ts.map +1 -0
  396. package/dist/internal/funding/Route.js +5 -0
  397. package/dist/internal/funding/Route.js.map +1 -0
  398. package/dist/internal/funding/Subsidy.d.ts +113 -0
  399. package/dist/internal/funding/Subsidy.d.ts.map +1 -0
  400. package/dist/internal/funding/Subsidy.js +138 -0
  401. package/dist/internal/funding/Subsidy.js.map +1 -0
  402. package/dist/internal/funding/Token.d.ts +41 -0
  403. package/dist/internal/funding/Token.d.ts.map +1 -0
  404. package/dist/internal/funding/Token.js +22 -0
  405. package/dist/internal/funding/Token.js.map +1 -0
  406. package/dist/internal/funding/Transfer.d.ts +643 -0
  407. package/dist/internal/funding/Transfer.d.ts.map +1 -0
  408. package/dist/internal/funding/Transfer.js +332 -0
  409. package/dist/internal/funding/Transfer.js.map +1 -0
  410. package/dist/internal/funding/index.d.ts +13 -0
  411. package/dist/internal/funding/index.d.ts.map +1 -0
  412. package/dist/internal/funding/index.js +13 -0
  413. package/dist/internal/funding/index.js.map +1 -0
  414. package/dist/internal/funding/providers/across.d.ts +21 -0
  415. package/dist/internal/funding/providers/across.d.ts.map +1 -0
  416. package/dist/internal/funding/providers/across.js +117 -0
  417. package/dist/internal/funding/providers/across.js.map +1 -0
  418. package/dist/internal/funding/providers/relay.d.ts +46 -0
  419. package/dist/internal/funding/providers/relay.d.ts.map +1 -0
  420. package/dist/internal/funding/providers/relay.js +464 -0
  421. package/dist/internal/funding/providers/relay.js.map +1 -0
  422. package/dist/internal/funding/providers/rhino.d.ts +15 -0
  423. package/dist/internal/funding/providers/rhino.d.ts.map +1 -0
  424. package/dist/internal/funding/providers/rhino.js +116 -0
  425. package/dist/internal/funding/providers/rhino.js.map +1 -0
  426. package/dist/internal/funding/providers/squid.d.ts +19 -0
  427. package/dist/internal/funding/providers/squid.d.ts.map +1 -0
  428. package/dist/internal/funding/providers/squid.js +120 -0
  429. package/dist/internal/funding/providers/squid.js.map +1 -0
  430. package/dist/internal/funding/providers/symbiosis.d.ts +20 -0
  431. package/dist/internal/funding/providers/symbiosis.d.ts.map +1 -0
  432. package/dist/internal/funding/providers/symbiosis.js +176 -0
  433. package/dist/internal/funding/providers/symbiosis.js.map +1 -0
  434. package/dist/internal/index.d.ts +5 -1
  435. package/dist/internal/index.d.ts.map +1 -1
  436. package/dist/internal/index.js +5 -1
  437. package/dist/internal/index.js.map +1 -1
  438. package/dist/internal/webhookDestinations/url.js +61 -57
  439. package/dist/internal/webhookDestinations/url.js.map +1 -1
  440. package/dist/providers/index.d.ts +43 -0
  441. package/dist/providers/index.d.ts.map +1 -0
  442. package/dist/providers/index.js +19 -0
  443. package/dist/providers/index.js.map +1 -0
  444. package/package.json +9 -4
  445. package/src/ApiKeys.test.ts +11 -7
  446. package/src/ApiKeys.ts +37 -9
  447. package/src/App.test.ts +85 -10
  448. package/src/App.ts +224 -137
  449. package/src/Assets.ts +29 -4
  450. package/src/Client.test-d.ts +650 -30
  451. package/src/Scope.test.ts +19 -0
  452. package/src/Scope.ts +28 -11
  453. package/src/Webhooks.test.ts +25 -0
  454. package/src/Webhooks.ts +1 -1
  455. package/src/admin/App.test.ts +9 -2
  456. package/src/admin/App.ts +32 -9
  457. package/src/admin/apps/api-keys.test.ts +167 -23
  458. package/src/admin/apps/api-keys.ts +166 -28
  459. package/src/admin/apps/earn-vaults.test.ts +338 -0
  460. package/src/admin/apps/earn-vaults.ts +572 -0
  461. package/src/admin/apps/scopes.test.ts +16 -1
  462. package/src/admin/ui/plugin.ts +2 -2
  463. package/src/admin/ui/src/components/EarnVaultForm.tsx +137 -0
  464. package/src/admin/ui/src/components/MobileProfile.tsx +2 -2
  465. package/src/admin/ui/src/components/Page.tsx +1 -1
  466. package/src/admin/ui/src/components/ThemeToggle.tsx +1 -1
  467. package/src/admin/ui/src/lib/api-keys.ts +37 -7
  468. package/src/admin/ui/src/lib/client.ts +1 -1
  469. package/src/admin/ui/src/lib/config.ts +1 -1
  470. package/src/admin/ui/src/lib/early-access.ts +8 -9
  471. package/src/admin/ui/src/lib/earn-vaults.ts +207 -0
  472. package/src/admin/ui/src/lib/format.ts +13 -0
  473. package/src/admin/ui/src/lib/organizations.test.ts +14 -4
  474. package/src/admin/ui/src/lib/organizations.ts +7 -1
  475. package/src/admin/ui/src/lib/response.ts +22 -0
  476. package/src/admin/ui/src/lib/scopes.test.ts +19 -11
  477. package/src/admin/ui/src/lib/scopes.ts +28 -7
  478. package/src/admin/ui/src/lib/verified-tokens.ts +10 -9
  479. package/src/admin/ui/src/routeTree.gen.ts +70 -7
  480. package/src/admin/ui/src/router.tsx +2 -2
  481. package/src/admin/ui/src/routes/__root.tsx +9 -8
  482. package/src/admin/ui/src/routes/api-keys.tsx +475 -64
  483. package/src/admin/ui/src/routes/early-access.tsx +5 -12
  484. package/src/admin/ui/src/routes/earn.vaults.tsx +348 -0
  485. package/src/admin/ui/src/routes/earn.vaults_.$vaultId.edit.tsx +154 -0
  486. package/src/admin/ui/src/routes/earn.vaults_.new.tsx +190 -0
  487. package/src/admin/ui/src/routes/organizations.tsx +4 -7
  488. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +56 -54
  489. package/src/admin/ui/src/routes/verified-tokens.tsx +4 -4
  490. package/src/admin/ui/vite.config.ts +4 -1
  491. package/src/admin/ui.generated.ts +189 -95
  492. package/src/analytics/Analytics.test.ts +69 -10
  493. package/src/analytics/Analytics.ts +20 -1
  494. package/src/analytics/tables/requestEvents.ts +79 -37
  495. package/src/apps/Zones.test.ts +33 -0
  496. package/src/apps/Zones.ts +40 -0
  497. package/src/apps/composition.test.ts +84 -1
  498. package/src/apps/data/App.test.ts +38 -9
  499. package/src/apps/data/App.ts +96 -33
  500. package/src/apps/data/FxOracle.ts +1 -1
  501. package/src/apps/data/index.ts +0 -1
  502. package/src/apps/data/routes/activities.test.ts +82 -6
  503. package/src/apps/data/routes/activities.ts +131 -71
  504. package/src/apps/data/routes/balances.test.ts +50 -37
  505. package/src/apps/data/routes/balances.ts +5 -5
  506. package/src/apps/data/routes/blocks.test.ts +2 -0
  507. package/src/apps/data/routes/coingecko.test.ts +22 -0
  508. package/src/apps/data/routes/coingecko.ts +84 -48
  509. package/src/apps/data/routes/earn.test.ts +1108 -0
  510. package/src/apps/data/routes/earn.ts +1391 -0
  511. package/src/apps/data/routes/exchanges.test.ts +82 -33
  512. package/src/apps/data/routes/exchanges.ts +842 -227
  513. package/src/apps/data/routes/fee-amm.test.ts +4 -6
  514. package/src/apps/data/routes/fee-amm.ts +69 -12
  515. package/src/apps/data/routes/funding.test.ts +2712 -221
  516. package/src/apps/data/routes/funding.ts +1604 -387
  517. package/src/apps/data/routes/indexer.test.ts +43 -34
  518. package/src/apps/data/routes/indexer.ts +19 -2
  519. package/src/apps/data/routes/receipts.test.ts +5 -3
  520. package/src/apps/data/routes/receipts.ts +13 -11
  521. package/src/apps/data/routes/rpc.test.ts +446 -28
  522. package/src/apps/data/routes/rpc.ts +243 -25
  523. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  524. package/src/apps/data/routes/tokenlist.ts +88 -22
  525. package/src/apps/data/routes/tokens.ts +262 -86
  526. package/src/apps/data/routes/transactions.test.ts +2 -0
  527. package/src/apps/data/routes/transactions.ts +77 -42
  528. package/src/apps/data/routes/transfers.test.ts +166 -4
  529. package/src/apps/data/routes/transfers.ts +216 -78
  530. package/src/apps/data/routes/valuation.test.ts +62 -9
  531. package/src/apps/data/routes/valuation.ts +2 -2
  532. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  533. package/src/apps/data/routes/verified-tokens.ts +52 -15
  534. package/src/apps/data/routes/webhooks.test.ts +432 -33
  535. package/src/apps/data/routes/webhooks.ts +189 -79
  536. package/src/apps/data/routes/zones.test.ts +473 -0
  537. package/src/apps/data/routes/zones.ts +338 -0
  538. package/src/apps/docs/reference.ts +8 -6
  539. package/src/apps/index.ts +2 -1
  540. package/src/apps/management/Billing.test.ts +29 -0
  541. package/src/apps/management/Billing.ts +23 -0
  542. package/src/apps/management/Error.ts +12 -0
  543. package/src/apps/management/routes/api-keys.test.ts +5 -3
  544. package/src/apps/management/routes/api-keys.ts +2 -9
  545. package/src/apps/management/routes/invitations.ts +4 -11
  546. package/src/apps/management/routes/orgs.ts +2 -9
  547. package/src/apps/management/routes/scopes.test.ts +7 -2
  548. package/src/apps/management/routes/scopes.ts +2 -2
  549. package/src/apps/management/routes/webhooks.test.ts +6 -1
  550. package/src/apps/management/routes/webhooks.ts +15 -3
  551. package/src/apps/mpp/App.ts +276 -29
  552. package/src/apps/mpp/Mpp.test.ts +631 -26
  553. package/src/apps/mpp/Mpp.ts +0 -42
  554. package/src/apps/mpp/internal/Adapter.ts +108 -3
  555. package/src/apps/mpp/internal/Error.test.ts +33 -0
  556. package/src/apps/mpp/internal/Error.ts +44 -6
  557. package/src/apps/mpp/internal/Idempotency.test.ts +213 -0
  558. package/src/apps/mpp/internal/Idempotency.ts +59 -5
  559. package/src/apps/mpp/internal/Policy.test.ts +122 -0
  560. package/src/apps/mpp/internal/Policy.ts +98 -0
  561. package/src/apps/mpp/internal/ScreenManager.test.ts +68 -0
  562. package/src/apps/mpp/internal/ScreenManager.ts +41 -0
  563. package/src/apps/mpp/internal/Screening.ts +40 -3
  564. package/src/apps/mpp/internal/Sponsorship.ts +144 -0
  565. package/src/apps/mpp/internal/Utils.ts +30 -0
  566. package/src/apps/relay/App.test.ts +495 -64
  567. package/src/apps/relay/App.ts +234 -24
  568. package/src/apps/relay/Sponsorships.test.ts +51 -0
  569. package/src/apps/relay/Sponsorships.ts +19 -7
  570. package/src/db/Db.ts +439 -0
  571. package/src/db/Schema.ts +325 -2
  572. package/src/db/tables/earnVaults.test.ts +185 -0
  573. package/src/db/tables/earnVaults.ts +151 -0
  574. package/src/db/tables/fundingCatalog.test.ts +108 -0
  575. package/src/db/tables/fundingCatalog.ts +157 -0
  576. package/src/db/tables/fundingDepositAddresses.test.ts +580 -0
  577. package/src/db/tables/fundingDepositAddresses.ts +376 -0
  578. package/src/db/tables/fundingDeposits.test.ts +261 -0
  579. package/src/db/tables/fundingDeposits.ts +229 -0
  580. package/src/db/tables/fundingIdempotency.test.ts +182 -0
  581. package/src/db/tables/fundingIdempotency.ts +180 -0
  582. package/src/db/tables/fundingTransferEvents.ts +28 -0
  583. package/src/db/tables/fundingTransferTransactions.test.ts +69 -0
  584. package/src/db/tables/fundingTransferTransactions.ts +37 -0
  585. package/src/db/tables/fundingTransfers.test.ts +89 -0
  586. package/src/db/tables/fundingTransfers.ts +153 -0
  587. package/src/db/tables/organizations.test.ts +88 -0
  588. package/src/db/tables/organizations.ts +42 -1
  589. package/src/db/tables/projects.ts +16 -0
  590. package/src/db/tables/requestUsage.ts +23 -0
  591. package/src/db/tables/sponsoredTransactions.test.ts +52 -2
  592. package/src/db/tables/sponsoredTransactions.ts +18 -6
  593. package/src/db/tables/users.ts +16 -0
  594. package/src/db/tables/verifiedTokens.ts +19 -14
  595. package/src/db/tables/webhookSubscriptions.ts +58 -3
  596. package/src/handlers/relay.test.ts +2 -0
  597. package/src/handlers/relay.ts +95 -99
  598. package/src/internal/Auth.test.ts +48 -0
  599. package/src/internal/Auth.ts +61 -2
  600. package/src/internal/Cache.ts +13 -9
  601. package/src/internal/Cursor.test.ts +2 -2
  602. package/src/internal/Cursor.ts +1 -1
  603. package/src/internal/EarnRates.test.ts +125 -0
  604. package/src/internal/EarnRates.ts +409 -0
  605. package/src/internal/EarnVaults.test.ts +195 -0
  606. package/src/internal/EarnVaults.ts +876 -0
  607. package/src/internal/EdgeCache.test.ts +37 -5
  608. package/src/internal/EdgeCache.ts +39 -7
  609. package/src/internal/Id.ts +14 -0
  610. package/src/internal/Idempotency.test.ts +109 -0
  611. package/src/internal/Idempotency.ts +237 -0
  612. package/src/internal/Log.test-d.ts +41 -8
  613. package/src/internal/Log.test.ts +189 -8
  614. package/src/internal/Log.ts +124 -17
  615. package/src/internal/MetricSink.test.ts +244 -4
  616. package/src/internal/MetricSink.ts +51 -1
  617. package/src/internal/OpenApi.ts +3 -0
  618. package/src/internal/Provider.test.ts +17 -0
  619. package/src/internal/Provider.ts +17 -0
  620. package/src/internal/Schema.test.ts +13 -0
  621. package/src/internal/Schema.ts +41 -24
  622. package/src/internal/Store.test.ts +25 -0
  623. package/src/internal/Store.ts +46 -0
  624. package/src/internal/Tidx.local.test.ts +78 -3
  625. package/src/internal/Tidx.test.ts +212 -18
  626. package/src/internal/Tidx.ts +112 -32
  627. package/src/internal/VerifiedTokens.test.ts +15 -0
  628. package/src/internal/Viem.test.ts +170 -17
  629. package/src/internal/Viem.ts +111 -28
  630. package/src/internal/WebhookDestination.ts +25 -23
  631. package/src/internal/Webhooks.test.ts +59 -0
  632. package/src/internal/Webhooks.ts +34 -10
  633. package/src/internal/exchange/Provider.test.ts +216 -0
  634. package/src/internal/exchange/Provider.ts +326 -0
  635. package/src/internal/exchange/index.test.ts +6 -0
  636. package/src/internal/exchange/index.ts +1 -0
  637. package/src/internal/exchange/providers/uniswap.test.ts +122 -0
  638. package/src/internal/exchange/providers/uniswap.ts +682 -0
  639. package/src/internal/funding/Action.test.ts +138 -0
  640. package/src/internal/funding/Action.ts +117 -0
  641. package/src/internal/funding/Catalog.test.ts +311 -0
  642. package/src/internal/funding/Catalog.ts +347 -0
  643. package/src/internal/funding/Chain.test.ts +79 -0
  644. package/src/internal/funding/Chain.ts +120 -0
  645. package/src/internal/funding/Deposit.test.ts +26 -0
  646. package/src/internal/funding/Deposit.ts +453 -0
  647. package/src/internal/funding/DepositAddress.test.ts +18 -0
  648. package/src/internal/funding/DepositAddress.ts +312 -0
  649. package/src/internal/funding/Evidence.test.ts +297 -0
  650. package/src/internal/funding/Evidence.ts +430 -0
  651. package/src/internal/funding/Provider.test.ts +1328 -0
  652. package/src/internal/funding/Provider.ts +1796 -0
  653. package/src/internal/funding/Reconciliation.local.test.ts +504 -0
  654. package/src/internal/funding/Reconciliation.test.ts +2020 -0
  655. package/src/internal/funding/Reconciliation.ts +930 -0
  656. package/src/internal/funding/Route.test.ts +66 -0
  657. package/src/internal/funding/Route.ts +29 -0
  658. package/src/internal/funding/Subsidy.local.test.ts +152 -0
  659. package/src/internal/funding/Subsidy.test.ts +75 -0
  660. package/src/internal/funding/Subsidy.ts +260 -0
  661. package/src/internal/funding/Token.test.ts +47 -0
  662. package/src/internal/funding/Token.ts +59 -0
  663. package/src/internal/funding/Transfer.test.ts +369 -0
  664. package/src/internal/funding/Transfer.ts +522 -0
  665. package/src/internal/funding/index.test.ts +20 -0
  666. package/src/internal/funding/index.ts +12 -0
  667. package/src/internal/funding/providers/across.ts +149 -0
  668. package/src/{apps/data/FundingQuoteProviders.test.ts → internal/funding/providers/index.test.ts} +178 -157
  669. package/src/internal/funding/providers/relay.test.ts +474 -0
  670. package/src/internal/funding/providers/relay.ts +559 -0
  671. package/src/internal/funding/providers/rhino.ts +139 -0
  672. package/src/internal/funding/providers/squid.ts +151 -0
  673. package/src/internal/funding/providers/symbiosis.ts +221 -0
  674. package/src/internal/index.test.ts +5 -1
  675. package/src/internal/index.ts +5 -1
  676. package/src/internal/webhookDestinations/url.ts +49 -45
  677. package/src/providers/index.test.ts +14 -0
  678. package/src/providers/index.ts +54 -0
  679. package/dist/apps/data/FundingQuoteProviders.d.ts +0 -174
  680. package/dist/apps/data/FundingQuoteProviders.d.ts.map +0 -1
  681. package/dist/apps/data/FundingQuoteProviders.js +0 -775
  682. package/dist/apps/data/FundingQuoteProviders.js.map +0 -1
  683. package/dist/internal/FundingQuotes.d.ts +0 -864
  684. package/dist/internal/FundingQuotes.d.ts.map +0 -1
  685. package/dist/internal/FundingQuotes.js +0 -672
  686. package/dist/internal/FundingQuotes.js.map +0 -1
  687. package/src/apps/data/FundingQuoteProviders.ts +0 -1043
  688. package/src/internal/FundingQuotes.test.ts +0 -456
  689. package/src/internal/FundingQuotes.ts +0 -944
@@ -1,108 +1,35 @@
1
- import { Hono } from 'hono'
1
+ import { type Context, Hono } from 'hono'
2
+ import { etag } from 'hono/etag'
2
3
  import * as z from 'zod/mini'
3
4
 
4
5
  import type * as App from '../../../App.js'
6
+ import * as Db from '../../../db/Db.js'
7
+ import * as FundingDepositAddresses from '../../../db/tables/fundingDepositAddresses.js'
8
+ import * as FundingDeposits from '../../../db/tables/fundingDeposits.js'
9
+ import * as FundingIdempotency from '../../../db/tables/fundingIdempotency.js'
10
+ import * as FundingTransfers from '../../../db/tables/fundingTransfers.js'
5
11
  import * as Auth from '../../../internal/Auth.js'
6
12
  import * as Cache from '../../../internal/Cache.js'
7
- import * as FundingQuotes from '../../../internal/FundingQuotes.js'
13
+ import * as Cursor from '../../../internal/Cursor.js'
14
+ import * as FundingCatalog from '../../../internal/funding/Catalog.js'
15
+ import * as FundingDeposit from '../../../internal/funding/Deposit.js'
16
+ import * as FundingDepositAddress from '../../../internal/funding/DepositAddress.js'
17
+ import * as FundingProvider from '../../../internal/funding/Provider.js'
18
+ import * as FundingTransfer from '../../../internal/funding/Transfer.js'
19
+ import * as Idempotency from '../../../internal/Idempotency.js'
8
20
  import type * as Log from '../../../internal/Log.js'
9
21
  import * as OpenApi from '../../../internal/OpenApi.js'
10
22
  import * as Response from '../../../internal/Response.js'
11
23
  import * as Schema from '../../../internal/Schema.js'
12
24
  import * as Value from '../../../internal/Value.js'
13
- import * as FundingQuoteProviders from '../FundingQuoteProviders.js'
14
25
 
15
26
  /** Zod schemas owned by funding quote resources. */
16
27
  export namespace schema {
17
28
  /** Source or destination chain reference. */
18
- export const Chain = Schema.describe(
19
- z.object({
20
- addressFormat: z
21
- .enum(['base58', 'base58check', 'evm'])
22
- .check(
23
- z.describe('Address family used by accounts and token identifiers on this chain.'),
24
- z.meta({ examples: ['evm'] }),
25
- ),
26
- id: z
27
- .string()
28
- .check(
29
- z.describe(
30
- 'Stable chain id. Uses CAIP-2 where standardized and a Tempo-defined compatible id otherwise.',
31
- ),
32
- z.meta({ examples: ['eip155:8453'] }),
33
- ),
34
- kind: z
35
- .enum(['evm', 'solana', 'tron'])
36
- .check(
37
- z.describe('Chain execution family used for provider routing.'),
38
- z.meta({ examples: ['evm'] }),
39
- ),
40
- name: z
41
- .string()
42
- .check(z.describe('Human-readable chain name.'), z.meta({ examples: ['Base'] })),
43
- providerIds: z
44
- .optional(z.record(z.string(), z.string()))
45
- .check(
46
- z.describe('Provider-specific chain ids Tempo uses when requesting quotes.'),
47
- z.meta({ examples: [{ across: '8453', relay: '8453', rhino: 'BASE' }] }),
48
- ),
49
- }),
50
- 'A normalized chain reference for funding quotes.',
51
- )
29
+ export const Chain = FundingProvider.schema.ChainRef
52
30
 
53
31
  /** Source or destination token reference. */
54
- export const Token = Schema.describe(
55
- z.object({
56
- address: z
57
- .string()
58
- .check(
59
- z.describe('Contract address, mint, or issuer address on the token chain.'),
60
- z.meta({ examples: ['0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }),
61
- ),
62
- currency: z
63
- .string()
64
- .check(
65
- z.describe('Monetary denomination represented by this token.'),
66
- z.meta({ examples: ['USD'] }),
67
- ),
68
- decimals: z
69
- .number()
70
- .check(
71
- z.int(),
72
- z.gte(0),
73
- z.describe('Number of decimal places this token uses.'),
74
- z.meta({ examples: [6] }),
75
- ),
76
- metadata: z
77
- .record(z.string(), z.string())
78
- .check(
79
- z.describe('Curated display or lookup metadata for this token.'),
80
- z.meta({ examples: [{ coingeckoId: 'usd-coin' }] }),
81
- ),
82
- name: z
83
- .string()
84
- .check(z.describe('Human-readable token name.'), z.meta({ examples: ['USD Coin'] })),
85
- standard: z
86
- .string()
87
- .check(z.describe('Token standard on the token chain.'), z.meta({ examples: ['ERC-20'] })),
88
- symbol: z
89
- .string()
90
- .check(z.describe('Short token ticker symbol.'), z.meta({ examples: ['USDC'] })),
91
- tokenKey: z.string().check(
92
- z.describe('Stable Tempo token key scoped to the token chain.'),
93
- z.meta({
94
- examples: ['eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'],
95
- }),
96
- ),
97
- verified: z
98
- .boolean()
99
- .check(
100
- z.describe('Whether Tempo recognizes this token in its funding quote inventory.'),
101
- z.meta({ examples: [true] }),
102
- ),
103
- }),
104
- 'A normalized token reference for funding quotes.',
105
- )
32
+ export const Token = FundingProvider.schema.TokenRef
106
33
 
107
34
  /** Self-contained source or destination amount in a funding quote. */
108
35
  export const TokenAmount = Schema.describe(
@@ -111,138 +38,19 @@ export namespace schema {
111
38
  )
112
39
 
113
40
  /** Provider summary embedded in a funding quote. */
114
- export const ProviderRef = Schema.describe(
115
- z.object({
116
- id: z
117
- .enum(['across', 'relay', 'rhino', 'squid', 'symbiosis'])
118
- .check(z.describe('Stable provider id.'), z.meta({ examples: ['relay'] })),
119
- kind: z
120
- .string()
121
- .check(
122
- z.describe('Provider category, such as a solver network or aggregator.'),
123
- z.meta({ examples: ['solverNetwork'] }),
124
- ),
125
- name: z
126
- .string()
127
- .check(z.describe('Human-readable provider name.'), z.meta({ examples: ['Relay'] })),
128
- requiresApiKey: z
129
- .boolean()
130
- .check(
131
- z.describe('Whether Tempo needs provider credentials to query or use this provider.'),
132
- z.meta({ examples: [false] }),
133
- ),
134
- }),
135
- 'Provider metadata embedded in a funding quote.',
136
- )
41
+ export const ProviderRef = FundingProvider.schema.ProviderRef
137
42
 
138
43
  /** Quote quality signals exposed directly instead of implied by list order. */
139
- export const Quality = Schema.describe(
140
- z.object({
141
- estimatedSeconds: z
142
- .optional(z.number().check(z.int(), z.nonnegative()))
143
- .check(
144
- z.describe('Estimated settlement time in seconds, when known.'),
145
- z.meta({ examples: [2] }),
146
- ),
147
- liquiditySource: z
148
- .string()
149
- .check(
150
- z.describe('Provider source for the live quote quality signal.'),
151
- z.meta({ examples: ['providerQuote'] }),
152
- ),
153
- sourceDetail: z
154
- .optional(z.string())
155
- .check(
156
- z.describe('Provider-specific source or endpoint detail for the quality signal.'),
157
- z.meta({ examples: ['providerEndpoint'] }),
158
- ),
159
- tier: z
160
- .enum(['liquid', 'thin'])
161
- .check(
162
- z.describe('Machine-readable quality tier for a successful quote.'),
163
- z.meta({ examples: ['liquid'] }),
164
- ),
165
- }),
166
- 'Machine-readable liquidity, fee, and source signals for a successful quote.',
167
- )
44
+ export const Quality = FundingProvider.schema.FundingQuoteQuality
168
45
 
169
46
  /** Provider quote freshness data. */
170
- export const QuoteDetails = Schema.describe(
171
- z.object({
172
- expiresAt: z
173
- .optional(z.iso.datetime())
174
- .check(
175
- z.describe('Timestamp after which the provider quote is no longer valid.'),
176
- z.meta({ examples: ['2026-01-01T00:01:00Z'] }),
177
- ),
178
- sampledAt: z.iso
179
- .datetime()
180
- .check(
181
- z.describe('Timestamp for the live quote probe.'),
182
- z.meta({ examples: ['2026-01-01T00:00:00Z'] }),
183
- ),
184
- }),
185
- 'Freshness data for a provider funding quote.',
186
- )
47
+ export const QuoteDetails = FundingProvider.schema.QuoteDetails
187
48
 
188
49
  /** Successful live funding quote. */
189
- export const FundingQuote = Schema.describe(
190
- z.object({
191
- destinationAmount: TokenAmount.check(z.describe('Expected destination amount.')),
192
- destinationAmountMin: z
193
- .optional(TokenAmount)
194
- .check(
195
- z.describe(
196
- 'Provider-derived minimum destination amount. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.',
197
- ),
198
- ),
199
- destinationChain: Chain.check(z.describe('Destination chain, always Tempo mainnet in V1.')),
200
- destinationToken: Token.check(z.describe('Tempo token expected from this quote.')),
201
- id: z
202
- .string()
203
- .check(
204
- z.describe('Stable deterministic funding quote id.'),
205
- z.meta({ examples: ['base-usdc-tempo-usdce-relay'] }),
206
- ),
207
- provider: ProviderRef,
208
- quality: Quality,
209
- quote: QuoteDetails,
210
- sourceAmount: TokenAmount,
211
- sourceChain: Chain.check(z.describe('Chain where the caller currently holds funds.')),
212
- sourceToken: Token.check(z.describe('Token the caller currently holds.')),
213
- }),
214
- 'One successful provider-specific funding quote.',
215
- )
50
+ export const FundingQuote = FundingProvider.schema.FundingQuote
216
51
 
217
52
  /** Funding quote provider catalog entry. */
218
- export const Provider = Schema.describe(
219
- z.object({
220
- id: z
221
- .enum(['across', 'relay', 'rhino', 'squid', 'symbiosis'])
222
- .check(z.describe('Stable provider id.'), z.meta({ examples: ['relay'] })),
223
- kind: z
224
- .string()
225
- .check(z.describe('Provider category.'), z.meta({ examples: ['solverNetwork'] })),
226
- name: z
227
- .string()
228
- .check(z.describe('Human-readable provider name.'), z.meta({ examples: ['Relay'] })),
229
- requiresApiKey: z
230
- .boolean()
231
- .check(
232
- z.describe('Whether Tempo needs provider credentials to query or use this provider.'),
233
- z.meta({ examples: [false] }),
234
- ),
235
- status: z
236
- .enum(['configured', 'unconfigured'])
237
- .check(
238
- z.describe(
239
- '`configured` means live quotes are enabled for this deployment. `unconfigured` means the provider is supported but unavailable here.',
240
- ),
241
- z.meta({ examples: ['configured', 'unconfigured'] }),
242
- ),
243
- }),
244
- 'One funding quote provider.',
245
- )
53
+ export const Provider = Schema.describe(ProviderRef, 'One funding quote provider.')
246
54
 
247
55
  /** Chain inventory entry returned by the funding chains endpoint. */
248
56
  export const ChainInventory = Schema.describe(
@@ -255,59 +63,7 @@ export namespace schema {
255
63
  /** Schemas for the quoteFunding operation. */
256
64
  export namespace quoteFunding {
257
65
  /** Query parameters for live funding quotes. */
258
- export const Query = z
259
- .strictObject({
260
- destinationToken: z
261
- .optional(z.string())
262
- .check(
263
- z.describe(
264
- 'Tempo destination token symbol, address, or token key. Defaults to USDC.e.',
265
- ),
266
- z.meta({ examples: ['USDC.e'] }),
267
- ),
268
- provider: z
269
- .optional(z.enum(['across', 'relay', 'rhino', 'squid', 'symbiosis']))
270
- .check(
271
- z.describe(
272
- 'Only request a quote from this provider. Supported ids are `across`, `relay`, `rhino`, `squid`, and `symbiosis`; omit it to query every configured provider.',
273
- ),
274
- z.meta({ examples: ['relay'] }),
275
- ),
276
- sourceAmount: z
277
- .string()
278
- .check(
279
- z.regex(/^(?=.*[1-9])\d+(\.\d+)?$/),
280
- z.describe(
281
- 'Positive source token amount. Base units by default, so 1 USDC is `1000000`.',
282
- ),
283
- z.meta({ examples: ['1000000'] }),
284
- ),
285
- sourceAmountUnits: z
286
- ._default(z.enum(['baseUnits', 'decimal']), 'baseUnits')
287
- .check(
288
- z.describe('Whether `sourceAmount` is base units or a human-readable decimal.'),
289
- z.meta({ examples: ['baseUnits', 'decimal'] }),
290
- ),
291
- sourceChain: z
292
- .string()
293
- .check(
294
- z.minLength(1),
295
- z.describe(
296
- 'Source chain alias or stable id, such as `base`, `solana`, or `eip155:8453`.',
297
- ),
298
- z.meta({ examples: ['base', 'eip155:8453'] }),
299
- ),
300
- sourceToken: z
301
- .string()
302
- .check(
303
- z.minLength(1),
304
- z.describe(
305
- 'Source token symbol, contract address, or Tempo token key. Lookup is scoped to `sourceChain`; non-EVM addresses are case-sensitive. An unknown token returns an empty list.',
306
- ),
307
- z.meta({ examples: ['USDC', '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }),
308
- ),
309
- })
310
- .check(z.describe('Query parameters for live funding quotes into Tempo.'))
66
+ export const Query = FundingProvider.schema.QuoteRequest
311
67
 
312
68
  /** Non-paginated list of successful funding quotes. */
313
69
  export const Response = Schema.describe(
@@ -318,8 +74,8 @@ export namespace schema {
318
74
  )
319
75
  }
320
76
 
321
- /** Schemas for the listFundingChains operation. */
322
- export namespace listFundingChains {
77
+ /** Schemas for the getFundingChains operation. */
78
+ export namespace getFundingChains {
323
79
  /** Non-paginated source-chain inventory. */
324
80
  export const Response = Schema.describe(
325
81
  z.object({
@@ -331,8 +87,8 @@ export namespace schema {
331
87
  )
332
88
  }
333
89
 
334
- /** Schemas for the listProviders operation. */
335
- export namespace listProviders {
90
+ /** Schemas for the getFundingProviders operation. */
91
+ export namespace getFundingProviders {
336
92
  /** Non-paginated funding quote provider catalog. */
337
93
  export const Response = Schema.describe(
338
94
  z.object({
@@ -341,69 +97,601 @@ export namespace schema {
341
97
  'A non-paginated list of funding quote providers.',
342
98
  )
343
99
  }
100
+
101
+ /** A durable funding transfer. Reads never include the executable `action`. */
102
+ export const Transfer = FundingTransfer.schema.FundingTransfer
103
+
104
+ /** One reusable funding deposit address. */
105
+ export const DepositAddress = FundingDepositAddress.schema.FundingDepositAddress
106
+
107
+ /** One detected funding deposit. */
108
+ export const Deposit = FundingDeposit.schema.FundingDeposit
109
+
110
+ /** Schemas for the createFundingDepositAddress operation. */
111
+ export namespace createFundingDepositAddress {
112
+ /** Funding deposit address creation request body. */
113
+ export const Body = z
114
+ .strictObject({
115
+ amount: FundingTransfer.schema.CreateRequest.shape.amount,
116
+ destinationToken: z
117
+ .string()
118
+ .check(
119
+ z.minLength(1),
120
+ z.describe('Tempo destination token symbol, contract address, or token key.'),
121
+ z.meta({ examples: ['usdt0'] }),
122
+ ),
123
+ recipient: z
124
+ .string()
125
+ .check(
126
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
127
+ z.describe('Tempo account that receives completed deposits.'),
128
+ z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
129
+ ),
130
+ refundAddress: z
131
+ .string()
132
+ .check(
133
+ z.minLength(1),
134
+ z.describe('Source-chain account that receives refunds.'),
135
+ z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
136
+ ),
137
+ sourceChain: z
138
+ .string()
139
+ .check(
140
+ z.minLength(1),
141
+ z.describe('Source chain CAIP-2 id, slug, or alias.'),
142
+ z.meta({ examples: ['tron', 'tron:0x2b6653dc'] }),
143
+ ),
144
+ sourceToken: z
145
+ .string()
146
+ .check(
147
+ z.minLength(1),
148
+ z.describe('Source token symbol, contract address, or token key.'),
149
+ z.meta({ examples: ['usdt'] }),
150
+ ),
151
+ subsidize: z
152
+ .optional(z.boolean())
153
+ .check(
154
+ z.describe('Guarantees normalized 1:1 delivery through a Tempo-funded subsidy.'),
155
+ z.meta({ examples: [false] }),
156
+ ),
157
+ })
158
+ .check(z.describe('Reusable funding deposit address creation request.'))
159
+
160
+ /** Required headers for funding deposit address creation. */
161
+ export const Headers = z.object({
162
+ 'idempotency-key': z
163
+ .string()
164
+ .check(
165
+ z.minLength(1),
166
+ z.describe(
167
+ 'Opaque retry key scoped to the API key. Matching requests replay successful responses for 24 hours; changed input conflicts, in-flight requests block, and failed attempts release the key.',
168
+ ),
169
+ z.meta({ examples: ['funding_01k1c5j8q8p0be6j5v9m6d1e4r'] }),
170
+ ),
171
+ })
172
+
173
+ /** The reusable address, with quoted terms only when the provider address was newly stored. */
174
+ export const Response = z
175
+ .union([
176
+ FundingDepositAddress.schema.CreatedFundingDepositAddress,
177
+ FundingDepositAddress.schema.FundingDepositAddress,
178
+ ])
179
+ .check(
180
+ z.describe(
181
+ 'A reusable funding deposit address, including terms sampled for a newly stored provider address.',
182
+ ),
183
+ )
184
+ }
185
+
186
+ /** Schemas for the getFundingDepositAddress operation. */
187
+ export namespace getFundingDepositAddress {
188
+ /** Path parameters for reading a funding deposit address. */
189
+ export const Params = z
190
+ .object({
191
+ id: z
192
+ .string()
193
+ .check(
194
+ z.regex(/^fda_[A-Za-z0-9_-]+$/),
195
+ z.describe('Funding deposit address ID (`fda_…`).'),
196
+ z.meta({ examples: ['fda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
197
+ ),
198
+ })
199
+ .check(z.describe('Path parameters for reading a funding deposit address.'))
200
+
201
+ /** One reusable funding deposit address. */
202
+ export const Response = DepositAddress
203
+ }
204
+
205
+ /** Schemas for the listFundingDepositAddressDeposits operation. */
206
+ export namespace listFundingDepositAddressDeposits {
207
+ /** Path parameters for listing one address's deposits. */
208
+ export const Params = getFundingDepositAddress.Params
209
+
210
+ /** Query parameters for listing one address's deposits. */
211
+ export const Query = z
212
+ .object({ cursor: Schema.Cursor, limit: Schema.Limit })
213
+ .check(z.describe('Paging parameters for one funding deposit address.'))
214
+
215
+ /** One page of detected deposits. */
216
+ export const Response = z
217
+ .object({
218
+ data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
219
+ nextCursor: Schema.NextCursor,
220
+ })
221
+ .check(z.describe('A page of deposits detected for one funding deposit address.'))
222
+ }
223
+
224
+ /** Schemas for the getFundingDeposit operation. */
225
+ export namespace getFundingDeposit {
226
+ /** Path parameters for reading a funding deposit. */
227
+ export const Params = z
228
+ .object({
229
+ id: z
230
+ .string()
231
+ .check(
232
+ z.regex(/^fdp_[A-Za-z0-9_-]+$/),
233
+ z.describe('Funding deposit id (`fdp_…`).'),
234
+ z.meta({ examples: ['fdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
235
+ ),
236
+ })
237
+ .check(z.describe('Path parameters for reading a funding deposit.'))
238
+
239
+ /** One detected funding deposit. */
240
+ export const Response = Deposit
241
+ }
242
+
243
+ /** Schemas for the listFundingTransfers operation. */
244
+ export namespace listFundingTransfers {
245
+ /** Query parameters for listing funding transfers. */
246
+ export const Query = z
247
+ .object({
248
+ cursor: Schema.Cursor,
249
+ include: Schema.totalCountInclude,
250
+ limit: Schema.Limit,
251
+ status: z.optional(FundingTransfer.schema.Status),
252
+ })
253
+ .check(z.describe('Query parameters for listing funding transfers.'))
254
+
255
+ /** Paginated funding transfer list. */
256
+ export const Response = z
257
+ .object({
258
+ data: z.array(Transfer).check(z.describe('Funding transfers on this page, newest first.')),
259
+ meta: z
260
+ .optional(Schema.CountMeta)
261
+ .check(z.describe('Extra response metadata requested with `include`.')),
262
+ nextCursor: Schema.NextCursor,
263
+ })
264
+ .check(z.describe('A page of funding transfers.'))
265
+ }
266
+
267
+ /** Schemas for the createFundingTransfer operation. */
268
+ export namespace createFundingTransfer {
269
+ /** Funding transfer creation request body. */
270
+ export const Body = FundingTransfer.schema.CreateRequest
271
+
272
+ /** Required headers for funding transfer creation. */
273
+ export const Headers = z.object({
274
+ 'idempotency-key': z
275
+ .string()
276
+ .check(
277
+ z.minLength(1),
278
+ z.describe(
279
+ 'Opaque retry key scoped to the API key. Matching requests replay successful responses for 24 hours; changed input conflicts, in-flight requests block, and failed attempts release the key.',
280
+ ),
281
+ z.meta({ examples: ['funding_01k1c5j8q8p0be6j5v9m6d1e4r'] }),
282
+ ),
283
+ })
284
+
285
+ /** The created transfer with its one-time executable action. */
286
+ export const Response = FundingTransfer.schema.CreatedFundingTransfer
287
+ }
288
+
289
+ const destinationCreateFields = {
290
+ amount: FundingTransfer.schema.CreateRequest.shape.amount,
291
+ mode: FundingTransfer.schema.CreateRequest.shape.mode,
292
+ recipient: FundingTransfer.schema.CreateRequest.shape.recipient,
293
+ sender: FundingTransfer.schema.CreateRequest.shape.sender,
294
+ slippageBps: FundingTransfer.schema.CreateRequest.shape.slippageBps,
295
+ sourceChain: FundingTransfer.schema.CreateRequest.shape.sourceChain,
296
+ sourceToken: FundingTransfer.schema.CreateRequest.shape.sourceToken,
297
+ }
298
+
299
+ /** Schemas for the createFundingTransferVault operation. */
300
+ export namespace createFundingTransferVault {
301
+ /** Funding transfer creation request for an Earn vault destination. */
302
+ export const Body = z
303
+ .strictObject({
304
+ ...destinationCreateFields,
305
+ vaultAddress: z
306
+ .string()
307
+ .check(
308
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
309
+ z.describe('Tempo Earn vault contract address.'),
310
+ z.meta({ examples: [`0x${'f4'.repeat(20)}`] }),
311
+ ),
312
+ })
313
+ .check(z.describe('Vault funding transfer creation request.'))
314
+
315
+ /** Required headers for vault funding transfer creation. */
316
+ export const Headers = createFundingTransfer.Headers
317
+ }
318
+
319
+ /** Schemas for the createFundingTransferZone operation. */
320
+ export namespace createFundingTransferZone {
321
+ /** Funding transfer creation request for a Zone destination. */
322
+ export const Body = z
323
+ .strictObject({
324
+ amount: destinationCreateFields.amount,
325
+ destinationChain: z
326
+ .string()
327
+ .check(
328
+ z.regex(/^eip155:\d+$/),
329
+ z.describe('Configured Zone CAIP-2 id.'),
330
+ z.meta({ examples: ['eip155:421700001'] }),
331
+ ),
332
+ destinationToken: FundingTransfer.schema.CreateRequest.shape.destinationToken,
333
+ mode: destinationCreateFields.mode,
334
+ recipient: destinationCreateFields.recipient,
335
+ recipientFallback: z
336
+ .string()
337
+ .check(
338
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
339
+ z.describe('Public Tempo recipient used if the Zone credit cannot complete.'),
340
+ z.meta({ examples: [`0x${'22'.repeat(20)}`] }),
341
+ ),
342
+ sender: destinationCreateFields.sender,
343
+ slippageBps: destinationCreateFields.slippageBps,
344
+ sourceChain: destinationCreateFields.sourceChain,
345
+ sourceToken: destinationCreateFields.sourceToken,
346
+ })
347
+ .check(z.describe('Zone funding transfer creation request.'))
348
+
349
+ /** Required headers for Zone funding transfer creation. */
350
+ export const Headers = createFundingTransfer.Headers
351
+ }
352
+
353
+ /** Schemas for the getFundingTransfer operation. */
354
+ export namespace getFundingTransfer {
355
+ /** Path parameters for a funding transfer read. */
356
+ export const Params = z
357
+ .object({
358
+ id: z
359
+ .string()
360
+ .check(
361
+ z.describe('Funding transfer ID (`ftr_…`).'),
362
+ z.meta({ examples: ['ftr_001785729600000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
363
+ ),
364
+ })
365
+ .check(z.describe('Path parameters for a funding transfer request.'))
366
+
367
+ /** The funding transfer resource, returned directly. */
368
+ export const Response = Transfer
369
+ }
344
370
  }
345
371
 
346
- const fundingQuoteResponseExample = (() => {
347
- const [candidate] = FundingQuotes.listQuoteCandidates({
348
- provider: 'relay',
349
- sourceAmount: '1000000',
350
- sourceAmountUnits: 'baseUnits',
351
- sourceChain: 'base',
352
- sourceToken: 'USDC',
353
- })
354
- if (!candidate) throw new Error('Missing Relay funding quote example candidate')
355
- return {
356
- data: [
357
- {
358
- destinationAmount: Value.tokenAmount({
359
- baseUnits: '979295',
360
- currency: candidate.destinationToken.currency,
361
- decimals: candidate.destinationToken.decimals,
362
- }),
363
- destinationAmountMin: Value.tokenAmount({
364
- baseUnits: '939242',
365
- currency: candidate.destinationToken.currency,
366
- decimals: candidate.destinationToken.decimals,
367
- }),
368
- destinationChain: candidate.destinationChain,
369
- destinationToken: candidate.destinationToken,
370
- id: candidate.id,
371
- provider: candidate.provider,
372
- quality: {
373
- estimatedSeconds: 1,
374
- liquiditySource: 'providerQuote',
375
- sourceDetail: 'relay:quote-v2',
376
- tier: 'liquid',
377
- },
378
- quote: {
379
- sampledAt: '2026-01-01T00:00:00.000Z',
380
- },
381
- sourceAmount: Value.tokenAmount({
382
- baseUnits: candidate.sourceAmount.amount,
383
- currency: candidate.sourceToken.currency,
384
- decimals: candidate.sourceToken.decimals,
385
- }),
386
- sourceChain: candidate.sourceChain,
387
- sourceToken: candidate.sourceToken,
372
+ const baseChainExample = {
373
+ addressFormat: 'hex',
374
+ id: 'eip155:8453',
375
+ kind: 'evm',
376
+ name: 'Base',
377
+ } as const
378
+
379
+ const baseUsdcExample = {
380
+ address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
381
+ currency: 'USD',
382
+ decimals: 6,
383
+ name: 'USD Coin',
384
+ standard: 'ERC-20',
385
+ symbol: 'USDC',
386
+ tokenKey: 'eip155:8453/erc20:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
387
+ verified: true,
388
+ } as const
389
+
390
+ const fundingQuoteResponseExample = {
391
+ data: [
392
+ {
393
+ destinationAmount: Value.tokenAmount({
394
+ baseUnits: '979295',
395
+ currency: 'USD',
396
+ decimals: 6,
397
+ }),
398
+ destinationAmountMin: Value.tokenAmount({
399
+ baseUnits: '939242',
400
+ currency: 'USD',
401
+ decimals: 6,
402
+ }),
403
+ destinationChain: {
404
+ addressFormat: 'hex',
405
+ id: 'eip155:4217',
406
+ kind: 'evm',
407
+ name: 'Tempo',
388
408
  },
389
- ],
390
- }
391
- })() satisfies z.output<typeof schema.quoteFunding.Response>
409
+ destinationToken: {
410
+ address: '0x20c000000000000000000000b9537d11c60e8b50',
411
+ currency: 'USD',
412
+ decimals: 6,
413
+ name: 'Bridged USDC (Stargate)',
414
+ standard: 'TIP-20',
415
+ symbol: 'USDC.e',
416
+ tokenKey: 'eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50',
417
+ verified: true,
418
+ },
419
+ id: 'base-usdc-tempo-usdce-relay',
420
+ provider: { id: 'relay', name: 'Relay' },
421
+ quality: {
422
+ estimatedSeconds: 1,
423
+ liquiditySource: 'providerQuote',
424
+ sourceDetail: 'relay:quote-v2',
425
+ tier: 'liquid',
426
+ },
427
+ quote: {
428
+ sampledAt: '2026-01-01T00:00:00.000Z',
429
+ },
430
+ sourceAmount: Value.tokenAmount({
431
+ baseUnits: '1000000',
432
+ currency: 'USD',
433
+ decimals: 6,
434
+ }),
435
+ sourceChain: baseChainExample,
436
+ sourceToken: baseUsdcExample,
437
+ },
438
+ ],
439
+ } satisfies z.output<typeof schema.quoteFunding.Response>
392
440
 
393
441
  const fundingChainsResponseExample = {
394
- data: FundingQuotes.listChains(),
395
- } satisfies z.output<typeof schema.listFundingChains.Response>
442
+ data: [{ ...baseChainExample, tokens: [baseUsdcExample] }],
443
+ } satisfies z.output<typeof schema.getFundingChains.Response>
396
444
 
397
445
  const providersResponseExample = {
398
- data: FundingQuotes.listProviders().map((provider) => ({
399
- ...provider,
400
- status: 'configured' as const,
401
- })),
402
- } satisfies z.output<typeof schema.listProviders.Response>
446
+ data: [{ id: 'relay', name: 'Relay' }],
447
+ } satisfies z.output<typeof schema.getFundingProviders.Response>
448
+
449
+ const tronChainExample = {
450
+ addressFormat: 'base58check',
451
+ id: 'tron:0x2b6653dc',
452
+ kind: 'tron',
453
+ name: 'Tron',
454
+ } as const
455
+
456
+ const tronUsdtExample = {
457
+ address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
458
+ currency: 'USD',
459
+ decimals: 6,
460
+ name: 'Tether USD',
461
+ standard: 'TRC-20',
462
+ symbol: 'USDT',
463
+ tokenKey: 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
464
+ verified: true,
465
+ } as const
466
+
467
+ const tempoChainExample = {
468
+ addressFormat: 'hex',
469
+ id: 'eip155:4217',
470
+ kind: 'evm',
471
+ name: 'Tempo',
472
+ } as const
473
+
474
+ const tempoUsdt0Example = {
475
+ address: '0x20c00000000000000000000014f22ca97301eb73',
476
+ currency: 'USD',
477
+ decimals: 6,
478
+ name: 'USDT0',
479
+ standard: 'TIP-20',
480
+ symbol: 'USDT0',
481
+ tokenKey: 'eip155:4217/erc20:0x20c00000000000000000000014f22ca97301eb73',
482
+ verified: true,
483
+ } as const
484
+
485
+ const fundingDepositAddressExample = {
486
+ address: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
487
+ createdAt: '2026-08-04T00:00:00.000Z',
488
+ destinationChain: tempoChainExample,
489
+ destinationToken: tempoUsdt0Example,
490
+ id: 'fda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx',
491
+ provider: { id: 'relay', name: 'Relay' },
492
+ recipient: `0x${'11'.repeat(20)}`,
493
+ refundAddress: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
494
+ sourceChain: tronChainExample,
495
+ sourceToken: tronUsdtExample,
496
+ status: 'active',
497
+ subsidize: false,
498
+ updatedAt: '2026-08-04T00:00:00.000Z',
499
+ } satisfies z.output<typeof schema.DepositAddress>
500
+
501
+ const createdFundingDepositAddressExample = {
502
+ ...fundingDepositAddressExample,
503
+ destinationAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
504
+ destinationAmountMin: Value.tokenAmount({
505
+ baseUnits: '1000000',
506
+ currency: 'USD',
507
+ decimals: 6,
508
+ }),
509
+ fees: [],
510
+ quote: { sampledAt: '2026-08-04T00:00:00.000Z' },
511
+ sourceAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
512
+ } satisfies z.output<typeof schema.createFundingDepositAddress.Response>
513
+
514
+ const fundingDepositExample = {
515
+ createdAt: '2026-08-04T00:01:00.000Z',
516
+ depositAddressId: fundingDepositAddressExample.id,
517
+ destinationChain: tempoChainExample,
518
+ destinationToken: tempoUsdt0Example,
519
+ id: 'fdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx',
520
+ provider: { id: 'relay', name: 'Relay' },
521
+ recipient: fundingDepositAddressExample.recipient,
522
+ refundAddress: fundingDepositAddressExample.refundAddress,
523
+ sourceChain: tronChainExample,
524
+ sourceToken: tronUsdtExample,
525
+ sourceTransactionHashes: ['9f2c4c80dbb468878baa8c07ff523e0464bf6437f2a486268508485d83f14d12'],
526
+ status: 'detected',
527
+ updatedAt: '2026-08-04T00:01:00.000Z',
528
+ } satisfies z.output<typeof schema.Deposit>
529
+
530
+ const fundingDepositAddressDepositsExample = {
531
+ data: [fundingDepositExample],
532
+ nextCursor: null,
533
+ } satisfies z.output<typeof schema.listFundingDepositAddressDeposits.Response>
534
+
535
+ const fundingTransferExample = {
536
+ createdAt: '2026-01-01T00:00:00.000Z',
537
+ destinationAmount: Value.tokenAmount({ baseUnits: '979295', currency: 'USD', decimals: 6 }),
538
+ destinationAmountMin: Value.tokenAmount({ baseUnits: '939242', currency: 'USD', decimals: 6 }),
539
+ destinationChain: {
540
+ addressFormat: 'hex',
541
+ id: 'eip155:4217',
542
+ kind: 'evm',
543
+ name: 'Tempo',
544
+ },
545
+ destinationToken: {
546
+ address: '0x20c0000000000000000000000000000000000000',
547
+ currency: 'USD',
548
+ decimals: 6,
549
+ name: 'PathUSD',
550
+ standard: 'TIP-20',
551
+ symbol: 'pathUSD',
552
+ tokenKey: 'eip155:4217/erc20:0x20c0000000000000000000000000000000000000',
553
+ verified: true,
554
+ },
555
+ fees: [
556
+ {
557
+ amount: Value.tokenAmount({ baseUnits: '20705', currency: 'USD', decimals: 6 }),
558
+ side: 'source',
559
+ token: baseUsdcExample,
560
+ type: 'provider',
561
+ },
562
+ ],
563
+ id: 'ftr_001767225600000_2ZPE2gvateYEQ0dQslgvkhjx',
564
+ method: 'transaction',
565
+ mode: 'exactSource',
566
+ provider: { id: 'relay', name: 'Relay' },
567
+ quote: {
568
+ expiresAt: '2026-01-01T00:01:00.000Z',
569
+ sampledAt: '2026-01-01T00:00:00.000Z',
570
+ },
571
+ recipient: `0x${'11'.repeat(20)}`,
572
+ sender: `0x${'22'.repeat(20)}`,
573
+ sourceAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
574
+ sourceChain: baseChainExample,
575
+ sourceToken: baseUsdcExample,
576
+ status: 'awaiting-source',
577
+ updatedAt: '2026-01-01T00:00:00.000Z',
578
+ version: 1,
579
+ } satisfies z.output<typeof schema.Transfer>
580
+
581
+ const listFundingTransfersResponseExample = {
582
+ data: [fundingTransferExample],
583
+ nextCursor: null,
584
+ } satisfies z.output<typeof schema.listFundingTransfers.Response>
585
+
586
+ const createdFundingTransferExample = {
587
+ action: {
588
+ calls: [
589
+ {
590
+ data: `0x095ea7b3000000000000000000000000${'44'.repeat(20)}00000000000000000000000000000000000000000000000000000000000f4240`,
591
+ to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
592
+ value: '0x0',
593
+ },
594
+ {
595
+ data: '0xe8017952',
596
+ to: `0x${'44'.repeat(20)}`,
597
+ value: '0x0',
598
+ },
599
+ ],
600
+ type: 'evm:calls',
601
+ },
602
+ ...fundingTransferExample,
603
+ } satisfies z.output<typeof schema.createFundingTransfer.Response>
604
+
605
+ const unimplementedFundingTransferResponses = (() => {
606
+ const responses = OpenApi.responses({
607
+ errors: {
608
+ 400: { codes: ['body_invalid', 'idempotency_key_required'] },
609
+ 501: {
610
+ codes: ['not_implemented'],
611
+ description: 'This funding transfer destination is not implemented.',
612
+ },
613
+ },
614
+ success: {
615
+ description: 'Unused until this destination is implemented.',
616
+ schema: z.never(),
617
+ },
618
+ })
619
+ const { 200: success, 502: upstream, ...errors } = responses
620
+ void success
621
+ void upstream
622
+ return errors
623
+ })()
624
+
625
+ // Replay the exact successful creation response for 24 hours.
626
+ const idempotencyReplayTtlMs = 86_400_000
627
+ // Pending claims only cover the bounded provider preparation window.
628
+ const idempotencyPendingTtlMs = 60_000
629
+
630
+ const depositAddressCheckpoint = Schema.describe(
631
+ z.object({
632
+ candidate: Schema.describe(
633
+ z.object({
634
+ destinationChain: FundingProvider.schema.ChainRef,
635
+ destinationToken: FundingProvider.schema.TokenRef,
636
+ sourceChain: FundingProvider.schema.ChainRef,
637
+ sourceToken: FundingProvider.schema.TokenRef,
638
+ }),
639
+ 'Normalized route retained after provider provisioning.',
640
+ ),
641
+ provider: FundingProvider.schema.ProviderRef,
642
+ recipient: z.string().check(z.describe('Normalized Tempo recipient.')),
643
+ refundAddress: z.string().check(z.describe('Normalized source-chain refund account.')),
644
+ result: FundingProvider.schema.DepositAddressResult,
645
+ }),
646
+ 'Provider result retained until deposit address creation completes.',
647
+ )
648
+
649
+ const inventorySourceScopes = new WeakMap<Db.Source, number>()
650
+ let nextInventorySourceScope = 0
651
+ const fundingEtag = etag()
652
+
653
+ function inventorySourceScope(source: Db.Source) {
654
+ const current = inventorySourceScopes.get(source)
655
+ if (current !== undefined) return current
656
+ // A production isolate's source is consistently zero, while multi-app runtimes receive distinct scopes.
657
+ const scope = nextInventorySourceScope++
658
+ inventorySourceScopes.set(source, scope)
659
+ return scope
660
+ }
403
661
 
404
662
  /** Mounts live funding quotes and their supporting inventory endpoints. */
405
- export function funding(options: funding.Options = {}) {
663
+ export function funding() {
664
+ const inventoryKey = (c: Context<App.Environment>) => {
665
+ const url = new URL(c.req.url)
666
+ const providers = c
667
+ .get('providers')
668
+ .filter(FundingProvider.is)
669
+ .map((provider) => provider.id)
670
+ .sort()
671
+ url.searchParams.set('__tempo_database', String(inventorySourceScope(c.get('dbCached'))))
672
+ url.searchParams.set('__tempo_providers', providers.join(','))
673
+ return url.toString()
674
+ }
675
+
406
676
  return new Hono<App.Environment>()
677
+ .post('/webhooks/funding/:providerId', async (c) => {
678
+ const provider = c
679
+ .get('providers')
680
+ .filter(FundingProvider.is)
681
+ .find((candidate) => candidate.id === c.req.param('providerId'))
682
+ if (!provider || !FundingProvider.canReceiveWebhook(provider)) return c.body(null, 404)
683
+ try {
684
+ await provider.webhook.receive({
685
+ body: await c.req.text(),
686
+ headers: c.req.raw.headers,
687
+ })
688
+ return c.body(null, 204)
689
+ } catch (cause) {
690
+ if (cause instanceof FundingProvider.WebhookAuthenticationError) return c.body(null, 401)
691
+ if (cause instanceof FundingProvider.WebhookPayloadError) return c.body(null, 400)
692
+ throw cause
693
+ }
694
+ })
407
695
  .get(
408
696
  '/v1/funding/quotes',
409
697
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
@@ -412,8 +700,7 @@ export function funding(options: funding.Options = {}) {
412
700
  message: 'Invalid query parameters',
413
701
  }),
414
702
  OpenApi.describeRoute({
415
- description:
416
- 'Returns live quotes for funding Tempo with a stablecoin. Omit `provider` to query all configured providers. Provider minimums can use provider-specific slippage policies; Symbiosis and Squid use 1%. This endpoint does not execute a transfer.',
703
+ description: 'Returns live quotes for transferring stablecoins to Tempo.',
417
704
  operationId: 'quoteFunding',
418
705
  responses: OpenApi.responses({
419
706
  errors: {
@@ -430,7 +717,7 @@ export function funding(options: funding.Options = {}) {
430
717
  schema: schema.quoteFunding.Response,
431
718
  },
432
719
  }),
433
- summary: 'Get funding quotes',
720
+ summary: 'Get quotes',
434
721
  tags: ['Funding'],
435
722
  }),
436
723
  async (c) => {
@@ -442,34 +729,38 @@ export function funding(options: funding.Options = {}) {
442
729
  })
443
730
 
444
731
  const query = c.req.valid('query')
445
- const attempts: FundingQuotes.ProviderAttempt[] = []
732
+ const attempts: FundingProvider.ProviderAttempt[] = []
733
+ const catalog = await FundingCatalog.snapshot(c)
734
+ const providers = c.get('providers').filter(FundingProvider.is)
446
735
  try {
447
736
  c.header('Cache-Control', 'no-store')
737
+ const data = await FundingProvider.getQuotes({
738
+ cache: { bypass: cacheBypass(c.req.raw) },
739
+ catalog,
740
+ destinationToken: query.destinationToken,
741
+ onAttempt: (attempt) => attempts.push(attempt),
742
+ provider: query.provider,
743
+ providers,
744
+ sourceAmount: query.sourceAmount,
745
+ sourceAmountUnits: query.sourceAmountUnits,
746
+ sourceChain: query.sourceChain,
747
+ sourceToken: query.sourceToken,
748
+ })
749
+ if (attempts.length > 0) c.set('fundingProviderAttempts', attempts.map(formatAttempt))
448
750
  return c.json(
449
751
  Response.validated(schema.quoteFunding.Response, {
450
- data: await FundingQuotes.listQuotes({
451
- adapters: options.adapters,
452
- adapterTimeoutMs: options.adapterTimeoutMs,
453
- cache: { bypass: cacheBypass(c.req.raw) },
454
- destinationToken: query.destinationToken,
455
- onAttempt: (attempt) => attempts.push(attempt),
456
- provider: query.provider,
457
- sourceAmount: query.sourceAmount,
458
- sourceAmountUnits: query.sourceAmountUnits,
459
- sourceChain: query.sourceChain,
460
- sourceToken: query.sourceToken,
461
- }),
752
+ data,
462
753
  }),
463
754
  200,
464
755
  )
465
756
  } catch (cause) {
466
757
  const message = requestErrorMessage(cause)
467
758
  if (message) return Response.error(c, { code: 'query_invalid', message, status: 400 })
468
- if (cause instanceof FundingQuotes.ProviderQuoteError) {
469
- const failures = cause.failures.map(FundingQuoteProviders.providerFailure)
470
- const failure = failures[0]
471
- if (failure) c.set('providerFailure', failure)
472
- if (failures.length > 1) c.set('providerFailures', failures)
759
+ if (cause instanceof FundingProvider.ProviderQuoteError) {
760
+ if (cause.fundingProviderAttempts.length > 0)
761
+ c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
762
+ if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
763
+ if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
473
764
  return Response.error(c, {
474
765
  code: 'upstream_error',
475
766
  message: 'Quote providers could not complete the request.',
@@ -477,9 +768,6 @@ export function funding(options: funding.Options = {}) {
477
768
  })
478
769
  }
479
770
  throw cause
480
- } finally {
481
- if (attempts.length > 0)
482
- c.set('fundingProviderAttempts', attempts.map(fundingProviderAttempt))
483
771
  }
484
772
  },
485
773
  )
@@ -487,29 +775,30 @@ export function funding(options: funding.Options = {}) {
487
775
  '/v1/funding/chains',
488
776
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
489
777
  OpenApi.describeRoute({
490
- description:
491
- 'Lists the source chains and stablecoins you can use when requesting a funding quote.',
492
- operationId: 'listFundingChains',
778
+ description: 'Lists supported source chains and tokens.',
779
+ operationId: 'getFundingChains',
493
780
  responses: OpenApi.responses({
494
781
  success: {
495
782
  description: 'Supported source chains and stablecoins.',
496
783
  example: fundingChainsResponseExample,
497
- schema: schema.listFundingChains.Response,
784
+ schema: schema.getFundingChains.Response,
498
785
  },
499
786
  }),
500
- summary: 'List funding chains',
787
+ summary: 'Get chains',
501
788
  tags: ['Funding'],
502
789
  }),
503
790
  Cache.response<App.Environment>({
504
791
  cacheControl: Cache.policies.stable,
505
- name: 'tempo-api:funding-chains:v2',
506
- key: (c) => c.req.url,
792
+ key: inventoryKey,
793
+ name: 'tempo-api:funding-chains:v5',
507
794
  }),
508
- (c) => {
795
+ async (c) => {
509
796
  if (Auth.narrowAccess) return Auth.paidAccessError(c)
797
+ const catalog = await FundingCatalog.snapshot(c)
798
+ const providers = c.get('providers').filter(FundingProvider.is)
510
799
  return c.json(
511
- Response.validated(schema.listFundingChains.Response, {
512
- data: FundingQuotes.listChains(),
800
+ Response.validated(schema.getFundingChains.Response, {
801
+ data: FundingProvider.getChains({ catalog, providers }),
513
802
  }),
514
803
  200,
515
804
  )
@@ -519,77 +808,1001 @@ export function funding(options: funding.Options = {}) {
519
808
  '/v1/funding/providers',
520
809
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
521
810
  OpenApi.describeRoute({
522
- description:
523
- 'Lists funding quote providers and whether live quoting is configured for this deployment.',
524
- operationId: 'listFundingProviders',
811
+ description: 'Lists available transfer providers.',
812
+ operationId: 'getFundingProviders',
525
813
  responses: OpenApi.responses({
526
814
  success: {
527
815
  description: 'Funding quote providers supported by Tempo.',
528
816
  example: providersResponseExample,
529
- schema: schema.listProviders.Response,
817
+ schema: schema.getFundingProviders.Response,
530
818
  },
531
819
  }),
532
- summary: 'List funding providers',
820
+ summary: 'Get providers',
533
821
  tags: ['Funding'],
534
822
  }),
535
823
  Cache.response<App.Environment>({
536
824
  cacheControl: Cache.policies.stable,
537
- name: 'tempo-api:funding-providers:v1',
538
- key: (c) => c.req.url,
825
+ key: inventoryKey,
826
+ name: 'tempo-api:funding-providers:v4',
539
827
  }),
540
- (c) => {
828
+ async (c) => {
541
829
  if (Auth.narrowAccess) return Auth.paidAccessError(c)
830
+ const catalog = await FundingCatalog.snapshot(c)
831
+ const providers = c.get('providers').filter(FundingProvider.is)
542
832
  return c.json(
543
- Response.validated(schema.listProviders.Response, {
544
- data: FundingQuotes.listProviders({ adapters: options.adapters }),
833
+ Response.validated(schema.getFundingProviders.Response, {
834
+ data: FundingProvider.getProviders({ catalog, providers }),
545
835
  }),
546
836
  200,
547
837
  )
548
838
  },
549
839
  )
840
+ .post(
841
+ '/v1/funding/deposit-addresses',
842
+ Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
843
+ OpenApi.validate('header', schema.createFundingDepositAddress.Headers, {
844
+ code: 'idempotency_key_required',
845
+ message: 'Funding deposit address creation requires an `Idempotency-Key` header.',
846
+ }),
847
+ OpenApi.validate('json', schema.createFundingDepositAddress.Body, {
848
+ code: 'body_invalid',
849
+ message: 'Check the request body and try again.',
850
+ }),
851
+ OpenApi.describeRoute({
852
+ description: 'Creates or returns a reusable deposit address for funding a Tempo account.',
853
+ operationId: 'createFundingDepositAddress',
854
+ responses: OpenApi.responses({
855
+ errors: {
856
+ 400: { codes: ['body_invalid', 'idempotency_key_required'] },
857
+ 404: {
858
+ codes: ['deposit_address_not_available'],
859
+ description: 'No provider can provision the requested reusable address route.',
860
+ },
861
+ 409: {
862
+ codes: ['idempotency_conflict', 'idempotency_in_progress'],
863
+ description: 'The Idempotency-Key conflicts with an existing or in-flight request.',
864
+ },
865
+ 502: {
866
+ codes: ['upstream_error'],
867
+ description: 'The selected provider could not provision the address.',
868
+ },
869
+ },
870
+ success: {
871
+ description: 'The created or matching reusable funding deposit address and quote.',
872
+ example: createdFundingDepositAddressExample,
873
+ schema: schema.createFundingDepositAddress.Response,
874
+ },
875
+ }),
876
+ summary: 'Create deposit address',
877
+ tags: ['Funding'],
878
+ }),
879
+ async (c) => {
880
+ if (Auth.narrowAccess) return Auth.accessError(c)
881
+ if (OpenApi.narrowValidation)
882
+ return OpenApi.validationError(c, {
883
+ code: 'body_invalid',
884
+ message: 'Check the request body and try again.',
885
+ })
886
+
887
+ const principal = Auth.getPrincipal(c)
888
+ const owner = fundingOwner(c)
889
+ if (!owner || principal?.type !== 'api_key') return fundingUnauthorized(c)
890
+ c.header('Cache-Control', 'no-store')
891
+
892
+ const body = c.req.valid('json')
893
+ const subsidize = body.subsidize ?? false
894
+ const db = Db.get(c.get('db'))
895
+ const identity = {
896
+ apiKeyId: principal.id,
897
+ keyHash: Idempotency.inputHash(c.req.valid('header')['idempotency-key']),
898
+ requestHash: Idempotency.inputHash({
899
+ body,
900
+ endpoint: 'POST /v1/funding/deposit-addresses',
901
+ }),
902
+ }
903
+ const claim = await FundingIdempotency.claim(db, {
904
+ ...identity,
905
+ ttlMs: idempotencyPendingTtlMs,
906
+ })
907
+ if (claim.type === 'mismatch')
908
+ return Response.error(c, {
909
+ code: 'idempotency_conflict',
910
+ message: 'This Idempotency-Key was already used with different input.',
911
+ status: 409,
912
+ })
913
+ if (claim.type === 'pending')
914
+ return Response.error(c, {
915
+ code: 'idempotency_in_progress',
916
+ message: 'An equivalent funding deposit address creation is still running.',
917
+ status: 409,
918
+ })
919
+ if (claim.type === 'replay') {
920
+ const response = JSON.parse(claim.response) as Record<string, unknown>
921
+ return c.json(
922
+ Response.validated(schema.createFundingDepositAddress.Response, {
923
+ ...response,
924
+ subsidize: response['subsidize'] ?? false,
925
+ }),
926
+ 200,
927
+ )
928
+ }
929
+
930
+ const release = async () => {
931
+ try {
932
+ await FundingIdempotency.release(db, {
933
+ apiKeyId: identity.apiKeyId,
934
+ createdAt: claim.createdAt,
935
+ keyHash: identity.keyHash,
936
+ })
937
+ } catch {
938
+ // The pending claim expires on its own; the response stays authoritative.
939
+ }
940
+ }
941
+ const complete = async (
942
+ database: Db.Db,
943
+ response: z.output<typeof schema.createFundingDepositAddress.Response>,
944
+ ) => {
945
+ const validated = Response.validated(
946
+ schema.createFundingDepositAddress.Response,
947
+ response,
948
+ )
949
+ const completion = await FundingIdempotency.complete(database, {
950
+ apiKeyId: identity.apiKeyId,
951
+ createdAt: claim.createdAt,
952
+ keyHash: identity.keyHash,
953
+ replayTtlMs: idempotencyReplayTtlMs,
954
+ response: JSON.stringify(validated),
955
+ })
956
+ if (!completion)
957
+ throw new IdempotencyLeaseLostError('Funding deposit address creation lease was lost.')
958
+ return validated
959
+ }
960
+
961
+ const attempts: FundingProvider.ProviderAttempt[] = []
962
+ const provisioning = { succeeded: claim.type === 'resume' }
963
+ try {
964
+ const selected = await (async () => {
965
+ if (claim.type === 'resume')
966
+ return depositAddressCheckpoint.parse(JSON.parse(claim.response))
967
+ const catalog = await FundingCatalog.snapshot(c)
968
+ const providers = c.get('providers').filter(FundingProvider.is)
969
+ const provisioned = await FundingProvider.createDepositAddress({
970
+ amount: body.amount,
971
+ catalog,
972
+ destinationChainId: c.get('chainId'),
973
+ destinationToken: body.destinationToken,
974
+ onAttempt: (attempt) => attempts.push(attempt),
975
+ providers,
976
+ recipient: body.recipient,
977
+ refundAddress: body.refundAddress,
978
+ sourceChain: body.sourceChain,
979
+ sourceToken: body.sourceToken,
980
+ subsidize,
981
+ })
982
+ if (!provisioned) return undefined
983
+ provisioning.succeeded = true
984
+ const checkpoint = depositAddressCheckpoint.parse({
985
+ candidate: provisioned.candidate,
986
+ provider: provisioned.provider,
987
+ recipient: provisioned.recipient,
988
+ refundAddress: provisioned.refundAddress,
989
+ result: provisioned.result,
990
+ })
991
+ const stored = await FundingIdempotency.checkpoint(db, {
992
+ apiKeyId: identity.apiKeyId,
993
+ createdAt: claim.createdAt,
994
+ keyHash: identity.keyHash,
995
+ replayTtlMs: idempotencyReplayTtlMs,
996
+ response: JSON.stringify(checkpoint),
997
+ })
998
+ if (!stored)
999
+ throw new IdempotencyLeaseLostError(
1000
+ 'Funding deposit address creation lease was lost.',
1001
+ )
1002
+ return checkpoint
1003
+ })()
1004
+ if (attempts.length > 0) c.set('fundingProviderAttempts', attempts.map(formatAttempt))
1005
+ if (!selected) {
1006
+ await release()
1007
+ return depositAddressNotAvailable(c)
1008
+ }
1009
+
1010
+ const {
1011
+ candidate: selectedCandidate,
1012
+ provider,
1013
+ recipient,
1014
+ refundAddress,
1015
+ result,
1016
+ } = selected
1017
+ const feeToken = (side: 'destination' | 'source') =>
1018
+ side === 'source' ? selectedCandidate.sourceToken : selectedCandidate.destinationToken
1019
+ const created = await db.transaction(async (tx) => {
1020
+ const stored = await FundingDepositAddress.createOrGet(tx, {
1021
+ apiKeyId: principal.id,
1022
+ deliveryStrategy: 'provider',
1023
+ environment: owner.environment,
1024
+ orgId: owner.orgId,
1025
+ ...(owner.projectId === undefined ? {} : { projectId: owner.projectId }),
1026
+ providerOutputToken: selectedCandidate.destinationToken,
1027
+ providerRequestIds: [result.correlation.requestId],
1028
+ providerState:
1029
+ result.correlation.checkEndpoint === undefined
1030
+ ? undefined
1031
+ : { checkEndpoint: result.correlation.checkEndpoint },
1032
+ snapshot: {
1033
+ address: result.address,
1034
+ destinationChain: selectedCandidate.destinationChain,
1035
+ destinationToken: selectedCandidate.destinationToken,
1036
+ provider,
1037
+ recipient,
1038
+ refundAddress,
1039
+ sourceChain: selectedCandidate.sourceChain,
1040
+ sourceToken: selectedCandidate.sourceToken,
1041
+ subsidize,
1042
+ },
1043
+ })
1044
+ const record = FundingDepositAddress.toPublic(stored.record)
1045
+ if (stored.type === 'existing') return complete(tx, record)
1046
+ return complete(tx, {
1047
+ ...record,
1048
+ destinationAmount: Value.tokenAmount({
1049
+ baseUnits: result.destinationAmount,
1050
+ currency: selectedCandidate.destinationToken.currency,
1051
+ decimals: selectedCandidate.destinationToken.decimals,
1052
+ }),
1053
+ destinationAmountMin: Value.tokenAmount({
1054
+ baseUnits: result.destinationAmountMin,
1055
+ currency: selectedCandidate.destinationToken.currency,
1056
+ decimals: selectedCandidate.destinationToken.decimals,
1057
+ }),
1058
+ fees: result.fees.map((fee) => ({
1059
+ amount: Value.tokenAmount({
1060
+ baseUnits: fee.amount,
1061
+ currency: feeToken(fee.side).currency,
1062
+ decimals: feeToken(fee.side).decimals,
1063
+ }),
1064
+ side: fee.side,
1065
+ token: feeToken(fee.side),
1066
+ type: 'provider',
1067
+ })),
1068
+ quote: {
1069
+ ...(result.expiresAt === undefined ? {} : { expiresAt: result.expiresAt }),
1070
+ sampledAt: result.sampledAt,
1071
+ },
1072
+ sourceAmount: Value.tokenAmount({
1073
+ baseUnits: body.amount,
1074
+ currency: selectedCandidate.sourceToken.currency,
1075
+ decimals: selectedCandidate.sourceToken.decimals,
1076
+ }),
1077
+ })
1078
+ })
1079
+ return c.json(created, 200)
1080
+ } catch (cause) {
1081
+ if (!provisioning.succeeded) await release()
1082
+ if (cause instanceof FundingProvider.InvalidRefundAddressError)
1083
+ return Response.error(c, {
1084
+ code: 'body_invalid',
1085
+ message: 'Invalid `refundAddress`.',
1086
+ status: 400,
1087
+ })
1088
+ if (cause instanceof FundingProvider.UnsupportedSourceChainError)
1089
+ return depositAddressNotAvailable(c)
1090
+ if (
1091
+ cause instanceof FundingDepositAddress.ConflictError ||
1092
+ cause instanceof FundingProvider.ProviderQuoteError
1093
+ ) {
1094
+ if (cause instanceof FundingDepositAddress.ConflictError)
1095
+ return Response.error(c, {
1096
+ code: 'upstream_error',
1097
+ message: 'The funding provider returned a conflicting deposit address.',
1098
+ status: 502,
1099
+ })
1100
+ if (cause.fundingProviderAttempts.length > 0)
1101
+ c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
1102
+ if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
1103
+ if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
1104
+ return Response.error(c, {
1105
+ code: 'upstream_error',
1106
+ message: 'The funding provider could not provision the deposit address.',
1107
+ status: 502,
1108
+ })
1109
+ }
1110
+ throw cause
1111
+ }
1112
+ },
1113
+ )
1114
+ .get(
1115
+ '/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}/deposits',
1116
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
1117
+ OpenApi.validate('param', schema.listFundingDepositAddressDeposits.Params, {
1118
+ code: 'param_invalid',
1119
+ message: 'Check the path parameters and try again.',
1120
+ }),
1121
+ OpenApi.validate('query', schema.listFundingDepositAddressDeposits.Query, {
1122
+ code: 'query_invalid',
1123
+ message: 'Check the query parameters and try again.',
1124
+ }),
1125
+ OpenApi.describeRoute({
1126
+ description: 'Lists deposits detected for one funding deposit address, newest first.',
1127
+ operationId: 'listFundingDepositAddressDeposits',
1128
+ responses: OpenApi.responses({
1129
+ errors: {
1130
+ 400: { codes: ['param_invalid', 'query_invalid'] },
1131
+ 404: {
1132
+ codes: ['funding_deposit_address_not_found'],
1133
+ description: 'No visible funding deposit address exists for the identifier.',
1134
+ },
1135
+ },
1136
+ success: {
1137
+ description: 'A page of deposits detected for the address.',
1138
+ example: fundingDepositAddressDepositsExample,
1139
+ schema: schema.listFundingDepositAddressDeposits.Response,
1140
+ },
1141
+ }),
1142
+ summary: 'List deposits',
1143
+ tags: ['Funding'],
1144
+ }),
1145
+ async (c) => {
1146
+ if (Auth.narrowAccess) return Auth.accessError(c)
1147
+ if (OpenApi.narrowValidation)
1148
+ return OpenApi.validationError(c, {
1149
+ code: 'query_invalid',
1150
+ message: 'Check the request parameters and try again.',
1151
+ })
1152
+
1153
+ const owner = fundingOwner(c)
1154
+ if (!owner) return fundingUnauthorized(c)
1155
+
1156
+ const db = Db.get(c.get('db'))
1157
+ const { id } = c.req.valid('param')
1158
+ const { cursor, limit } = c.req.valid('query')
1159
+ const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
1160
+ try {
1161
+ const address = await FundingDepositAddresses.getOwned(db, owner, id)
1162
+ if (!address)
1163
+ return Response.error(c, {
1164
+ code: 'funding_deposit_address_not_found',
1165
+ message: 'Funding deposit address not found',
1166
+ status: 404,
1167
+ })
1168
+
1169
+ const rows = await FundingDeposits.listByAddress(db, owner, id, {
1170
+ ...(decoded
1171
+ ? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
1172
+ : {}),
1173
+ limit: limit + 1,
1174
+ })
1175
+ const page = Cursor.paginate({
1176
+ key: (record) => [record.createdAt, record.id],
1177
+ limit,
1178
+ rows,
1179
+ })
1180
+ const data = page.rows.map(FundingDeposit.toPublic)
1181
+
1182
+ c.header('Cache-Control', Cache.policies.noStore)
1183
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
1184
+ return c.json(
1185
+ Response.validated(schema.listFundingDepositAddressDeposits.Response, {
1186
+ data,
1187
+ nextCursor: page.nextCursor,
1188
+ }),
1189
+ 200,
1190
+ )
1191
+ } catch (cause) {
1192
+ return Response.upstream(c, cause)
1193
+ }
1194
+ },
1195
+ )
1196
+ .get(
1197
+ '/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}',
1198
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
1199
+ OpenApi.validate('param', schema.getFundingDepositAddress.Params, {
1200
+ code: 'param_invalid',
1201
+ message: 'Check the path parameters and try again.',
1202
+ }),
1203
+ OpenApi.describeRoute({
1204
+ description: 'Returns one reusable funding deposit address.',
1205
+ operationId: 'getFundingDepositAddress',
1206
+ responses: OpenApi.responses({
1207
+ errors: {
1208
+ 400: { codes: ['param_invalid'] },
1209
+ 404: {
1210
+ codes: ['funding_deposit_address_not_found'],
1211
+ description: 'No visible funding deposit address exists for the identifier.',
1212
+ },
1213
+ },
1214
+ success: {
1215
+ description: 'The requested funding deposit address.',
1216
+ example: fundingDepositAddressExample,
1217
+ schema: schema.getFundingDepositAddress.Response,
1218
+ },
1219
+ }),
1220
+ summary: 'Get deposit address',
1221
+ tags: ['Funding'],
1222
+ }),
1223
+ async (c) => {
1224
+ if (Auth.narrowAccess) return Auth.accessError(c)
1225
+ if (OpenApi.narrowValidation)
1226
+ return OpenApi.validationError(c, {
1227
+ code: 'param_invalid',
1228
+ message: 'Check the path parameters and try again.',
1229
+ })
1230
+
1231
+ const owner = fundingOwner(c)
1232
+ if (!owner) return fundingUnauthorized(c)
1233
+
1234
+ const db = Db.get(c.get('db'))
1235
+ const { id } = c.req.valid('param')
1236
+ try {
1237
+ const record = await FundingDepositAddresses.getOwned(db, owner, id)
1238
+ if (!record)
1239
+ return Response.error(c, {
1240
+ code: 'funding_deposit_address_not_found',
1241
+ message: 'Funding deposit address not found',
1242
+ status: 404,
1243
+ })
1244
+
1245
+ c.header('Cache-Control', Cache.policies.noStore)
1246
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
1247
+ return c.json(
1248
+ Response.validated(
1249
+ schema.getFundingDepositAddress.Response,
1250
+ FundingDepositAddress.toPublic(record),
1251
+ ),
1252
+ 200,
1253
+ )
1254
+ } catch (cause) {
1255
+ return Response.upstream(c, cause)
1256
+ }
1257
+ },
1258
+ )
1259
+ .get(
1260
+ '/v1/funding/deposits/:id{fdp_[A-Za-z0-9_-]+}',
1261
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
1262
+ OpenApi.validate('param', schema.getFundingDeposit.Params, {
1263
+ code: 'param_invalid',
1264
+ message: 'Check the path parameters and try again.',
1265
+ }),
1266
+ OpenApi.describeRoute({
1267
+ description: 'Returns one detected deposit and its delivery status.',
1268
+ operationId: 'getFundingDeposit',
1269
+ responses: OpenApi.responses({
1270
+ errors: {
1271
+ 400: { codes: ['param_invalid'] },
1272
+ 404: {
1273
+ codes: ['funding_deposit_not_found'],
1274
+ description: 'No visible funding deposit exists for the identifier.',
1275
+ },
1276
+ },
1277
+ success: {
1278
+ description: 'The requested funding deposit.',
1279
+ example: fundingDepositExample,
1280
+ schema: schema.getFundingDeposit.Response,
1281
+ },
1282
+ }),
1283
+ summary: 'Get deposit',
1284
+ tags: ['Funding'],
1285
+ }),
1286
+ fundingEtag,
1287
+ async (c) => {
1288
+ if (Auth.narrowAccess) return Auth.accessError(c)
1289
+ if (OpenApi.narrowValidation)
1290
+ return OpenApi.validationError(c, {
1291
+ code: 'param_invalid',
1292
+ message: 'Check the path parameters and try again.',
1293
+ })
1294
+
1295
+ const owner = fundingOwner(c)
1296
+ if (!owner) return fundingUnauthorized(c)
1297
+
1298
+ const db = Db.get(c.get('db'))
1299
+ const { id } = c.req.valid('param')
1300
+ try {
1301
+ const record = await FundingDeposits.getOwned(db, owner, id)
1302
+ if (!record)
1303
+ return Response.error(c, {
1304
+ code: 'funding_deposit_not_found',
1305
+ message: 'Funding deposit not found',
1306
+ status: 404,
1307
+ })
1308
+
1309
+ c.header('Cache-Control', Cache.policies.state)
1310
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
1311
+ return c.json(
1312
+ Response.validated(schema.getFundingDeposit.Response, FundingDeposit.toPublic(record)),
1313
+ 200,
1314
+ )
1315
+ } catch (cause) {
1316
+ return Response.upstream(c, cause)
1317
+ }
1318
+ },
1319
+ )
1320
+ .get(
1321
+ '/v1/funding/transfers',
1322
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
1323
+ OpenApi.validate('query', schema.listFundingTransfers.Query, {
1324
+ code: 'query_invalid',
1325
+ message: 'Check the query parameters and try again.',
1326
+ }),
1327
+ OpenApi.describeRoute({
1328
+ description: 'Lists transfers from newest to oldest.',
1329
+ operationId: 'listFundingTransfers',
1330
+ responses: OpenApi.responses({
1331
+ errors: {
1332
+ 400: { codes: ['query_invalid'] },
1333
+ },
1334
+ success: {
1335
+ description: 'A page of funding transfers.',
1336
+ example: listFundingTransfersResponseExample,
1337
+ schema: schema.listFundingTransfers.Response,
1338
+ },
1339
+ }),
1340
+ summary: 'List transfers',
1341
+ tags: ['Funding'],
1342
+ }),
1343
+ fundingEtag,
1344
+ async (c) => {
1345
+ if (Auth.narrowAccess) return Auth.accessError(c)
1346
+ if (OpenApi.narrowValidation)
1347
+ return OpenApi.validationError(c, {
1348
+ code: 'query_invalid',
1349
+ message: 'Check the query parameters and try again.',
1350
+ })
1351
+
1352
+ const owner = fundingOwner(c)
1353
+ if (!owner) return fundingUnauthorized(c)
1354
+
1355
+ const db = Db.get(c.get('db'))
1356
+ const { cursor, include, limit, status } = c.req.valid('query')
1357
+ try {
1358
+ // `totalCount` is opt-in and capped (a capped subquery shares the
1359
+ // list filter); run it concurrently with the page list. Best-effort:
1360
+ // a failure omits `meta` rather than failing the page.
1361
+ const countPromise = include.includes('totalCount')
1362
+ ? FundingTransfers.count(db, owner, { cap: Schema.countCap, status }).catch(() => {
1363
+ c.set('fundingTransferCountFailed', true)
1364
+ return undefined
1365
+ })
1366
+ : undefined
1367
+
1368
+ // Fetch one extra to detect a further page without a second round-trip.
1369
+ const rows = await FundingTransfers.list(db, owner, { cursor, limit: limit + 1, status })
1370
+ const hasMore = rows.length > limit
1371
+ const data = (hasMore ? rows.slice(0, limit) : rows).map(FundingTransfer.toPublic)
1372
+ const nextCursor = hasMore ? (data[data.length - 1]?.id ?? null) : null
1373
+
1374
+ const count = countPromise ? await countPromise : undefined
1375
+
1376
+ c.header('Cache-Control', Cache.policies.feed)
1377
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
1378
+
1379
+ return c.json(
1380
+ Response.validated(schema.listFundingTransfers.Response, {
1381
+ data,
1382
+ ...(count !== undefined
1383
+ ? {
1384
+ meta: {
1385
+ totalCountCapped: count > Schema.countCap,
1386
+ totalCount: Math.min(count, Schema.countCap),
1387
+ },
1388
+ }
1389
+ : {}),
1390
+ nextCursor,
1391
+ }),
1392
+ 200,
1393
+ )
1394
+ } catch (cause) {
1395
+ return Response.upstream(c, cause)
1396
+ }
1397
+ },
1398
+ )
1399
+ .get(
1400
+ '/v1/funding/transfers/:id{ftr_[A-Za-z0-9_-]+}',
1401
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
1402
+ OpenApi.validate('param', schema.getFundingTransfer.Params, {
1403
+ code: 'param_invalid',
1404
+ message: 'Check the path parameters and try again.',
1405
+ }),
1406
+ OpenApi.describeRoute({
1407
+ description: 'Returns a transfer by ID.',
1408
+ operationId: 'getFundingTransfer',
1409
+ responses: OpenApi.responses({
1410
+ errors: {
1411
+ 400: { codes: ['param_invalid'] },
1412
+ 404: {
1413
+ codes: ['funding_transfer_not_found'],
1414
+ description: 'No visible funding transfer exists for the identifier.',
1415
+ },
1416
+ },
1417
+ success: {
1418
+ description: 'The requested funding transfer.',
1419
+ example: fundingTransferExample,
1420
+ schema: schema.getFundingTransfer.Response,
1421
+ },
1422
+ }),
1423
+ summary: 'Get transfer',
1424
+ tags: ['Funding'],
1425
+ }),
1426
+ async (c) => {
1427
+ if (Auth.narrowAccess) return Auth.accessError(c)
1428
+ if (OpenApi.narrowValidation)
1429
+ return OpenApi.validationError(c, {
1430
+ code: 'param_invalid',
1431
+ message: 'Check the path parameters and try again.',
1432
+ })
1433
+
1434
+ const owner = fundingOwner(c)
1435
+ if (!owner) return fundingUnauthorized(c)
1436
+
1437
+ const db = Db.get(c.get('db'))
1438
+ const { id } = c.req.valid('param')
1439
+ try {
1440
+ // Ownership filters in SQL, so absent and foreign rows are the same 404.
1441
+ const record = await FundingTransfers.getOwned(db, owner, id)
1442
+ if (!record)
1443
+ return Response.error(c, {
1444
+ code: 'funding_transfer_not_found',
1445
+ message: 'Funding transfer not found',
1446
+ status: 404,
1447
+ })
1448
+ c.header('Cache-Control', Cache.policies.noStore)
1449
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
1450
+ return c.json(
1451
+ Response.validated(
1452
+ schema.getFundingTransfer.Response,
1453
+ FundingTransfer.toPublic(record),
1454
+ ),
1455
+ 200,
1456
+ )
1457
+ } catch (cause) {
1458
+ return Response.upstream(c, cause)
1459
+ }
1460
+ },
1461
+ )
1462
+ .post(
1463
+ '/v1/funding/transfers',
1464
+ Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
1465
+ OpenApi.validate('header', schema.createFundingTransfer.Headers, {
1466
+ code: 'idempotency_key_required',
1467
+ message: 'Funding transfer creation requires an `Idempotency-Key` header.',
1468
+ }),
1469
+ OpenApi.validate('json', schema.createFundingTransfer.Body, {
1470
+ code: 'body_invalid',
1471
+ message: 'Check the request body and try again.',
1472
+ }),
1473
+ OpenApi.describeRoute({
1474
+ description: 'Creates a transfer to a Tempo account.',
1475
+ operationId: 'createFundingTransfer',
1476
+ responses: OpenApi.responses({
1477
+ errors: {
1478
+ 400: { codes: ['body_invalid', 'idempotency_key_required'] },
1479
+ 404: {
1480
+ codes: ['quote_not_available'],
1481
+ description: 'No provider can prepare the requested route.',
1482
+ },
1483
+ 409: {
1484
+ codes: ['idempotency_conflict', 'idempotency_in_progress'],
1485
+ description: 'The Idempotency-Key conflicts with an existing or in-flight request.',
1486
+ },
1487
+ 502: {
1488
+ codes: ['upstream_error'],
1489
+ description: 'No provider succeeded and at least one failed unexpectedly.',
1490
+ },
1491
+ },
1492
+ success: {
1493
+ description: 'The created funding transfer with its one-time executable action.',
1494
+ example: createdFundingTransferExample,
1495
+ schema: schema.createFundingTransfer.Response,
1496
+ },
1497
+ }),
1498
+ summary: 'Create transfer',
1499
+ tags: ['Funding'],
1500
+ }),
1501
+ async (c) => {
1502
+ if (Auth.narrowAccess) return Auth.accessError(c)
1503
+ if (OpenApi.narrowValidation)
1504
+ return OpenApi.validationError(c, {
1505
+ code: 'body_invalid',
1506
+ message: 'Check the request body and try again.',
1507
+ })
1508
+
1509
+ const principal = Auth.getPrincipal(c)
1510
+ const owner = fundingOwner(c)
1511
+ if (!owner || principal?.type !== 'api_key') return fundingUnauthorized(c)
1512
+ c.header('Cache-Control', 'no-store')
1513
+
1514
+ const idempotencyKey = c.req.valid('header')['idempotency-key']
1515
+
1516
+ const body = c.req.valid('json')
1517
+ const db = Db.get(c.get('db'))
1518
+ const identity = {
1519
+ apiKeyId: principal.id,
1520
+ keyHash: Idempotency.inputHash(idempotencyKey),
1521
+ requestHash: Idempotency.inputHash(body),
1522
+ }
1523
+ const claim = await FundingIdempotency.claim(db, {
1524
+ ...identity,
1525
+ ttlMs: idempotencyPendingTtlMs,
1526
+ })
1527
+ if (claim.type === 'mismatch')
1528
+ return Response.error(c, {
1529
+ code: 'idempotency_conflict',
1530
+ message: 'This Idempotency-Key was already used with different input.',
1531
+ status: 409,
1532
+ })
1533
+ if (claim.type === 'pending')
1534
+ return Response.error(c, {
1535
+ code: 'idempotency_in_progress',
1536
+ message: 'An equivalent funding transfer creation is still running.',
1537
+ status: 409,
1538
+ })
1539
+ if (claim.type === 'replay')
1540
+ return c.json(
1541
+ JSON.parse(claim.response) as z.output<typeof schema.createFundingTransfer.Response>,
1542
+ 200,
1543
+ )
1544
+
1545
+ const release = async () => {
1546
+ try {
1547
+ await FundingIdempotency.release(db, {
1548
+ apiKeyId: identity.apiKeyId,
1549
+ createdAt: claim.createdAt,
1550
+ keyHash: identity.keyHash,
1551
+ })
1552
+ } catch {
1553
+ // The pending claim expires on its own; the response stays authoritative.
1554
+ }
1555
+ }
1556
+ try {
1557
+ const attempts: FundingProvider.ProviderAttempt[] = []
1558
+ const catalog = await FundingCatalog.snapshot(c)
1559
+ const providers = c.get('providers').filter(FundingProvider.is)
1560
+ const selected = await FundingProvider.prepareTransfer({
1561
+ catalog,
1562
+ destinationChainId: c.get('chainId'),
1563
+ destinationToken: body.destinationToken,
1564
+ method: 'transaction',
1565
+ mode: body.mode,
1566
+ onAttempt: (attempt) => attempts.push(attempt),
1567
+ providers,
1568
+ recipient: body.recipient,
1569
+ sender: body.sender,
1570
+ slippageBps: body.slippageBps,
1571
+ sourceAmount: body.amount,
1572
+ sourceChain: body.sourceChain,
1573
+ sourceToken: body.sourceToken,
1574
+ })
1575
+ if (attempts.length > 0) c.set('fundingProviderAttempts', attempts.map(formatAttempt))
1576
+ if (!selected) {
1577
+ await release()
1578
+ return quoteNotAvailable(c)
1579
+ }
1580
+
1581
+ const { candidate, result } = selected
1582
+ const feeToken = (side: 'destination' | 'source') =>
1583
+ side === 'source' ? candidate.sourceToken : candidate.destinationToken
1584
+ const snapshot: FundingTransfer.Snapshot = {
1585
+ destinationAmount: Value.tokenAmount({
1586
+ baseUnits: result.destinationAmount,
1587
+ currency: candidate.destinationToken.currency,
1588
+ decimals: candidate.destinationToken.decimals,
1589
+ }),
1590
+ destinationAmountMin: Value.tokenAmount({
1591
+ baseUnits: result.destinationAmountMin,
1592
+ currency: candidate.destinationToken.currency,
1593
+ decimals: candidate.destinationToken.decimals,
1594
+ }),
1595
+ destinationChain: candidate.destinationChain,
1596
+ destinationToken: candidate.destinationToken,
1597
+ fees: result.fees.map((fee) => ({
1598
+ amount: Value.tokenAmount({
1599
+ baseUnits: fee.amount,
1600
+ currency: feeToken(fee.side).currency,
1601
+ decimals: feeToken(fee.side).decimals,
1602
+ }),
1603
+ side: fee.side,
1604
+ token: feeToken(fee.side),
1605
+ type: 'provider',
1606
+ })),
1607
+ method: 'transaction',
1608
+ mode: body.mode,
1609
+ provider: candidate.provider,
1610
+ quote: { expiresAt: result.expiresAt, sampledAt: result.sampledAt },
1611
+ recipient: body.recipient,
1612
+ sender: body.sender,
1613
+ sourceAmount: Value.tokenAmount({
1614
+ baseUnits: candidate.sourceAmount.amount,
1615
+ currency: candidate.sourceToken.currency,
1616
+ decimals: candidate.sourceToken.decimals,
1617
+ }),
1618
+ sourceChain: candidate.sourceChain,
1619
+ sourceToken: candidate.sourceToken,
1620
+ }
1621
+
1622
+ // The transfer row, its version-1 event, and the replay body commit
1623
+ // atomically so a crash cannot leave a claimed key without a replay.
1624
+ const created = await db.transaction(async (tx) => {
1625
+ const record = await FundingTransfer.createIn(tx, {
1626
+ apiKeyId: principal.id,
1627
+ environment: owner.environment,
1628
+ orgId: owner.orgId,
1629
+ projectId: owner.projectId,
1630
+ providerState: result.correlation,
1631
+ snapshot,
1632
+ })
1633
+ const validated = Response.validated(
1634
+ schema.createFundingTransfer.Response,
1635
+ FundingTransfer.toCreated(record, result.action),
1636
+ )
1637
+ const completion = await FundingIdempotency.complete(tx, {
1638
+ apiKeyId: identity.apiKeyId,
1639
+ createdAt: claim.createdAt,
1640
+ keyHash: identity.keyHash,
1641
+ replayTtlMs: idempotencyReplayTtlMs,
1642
+ response: JSON.stringify(validated),
1643
+ transferId: record.id,
1644
+ })
1645
+ if (!completion)
1646
+ throw new IdempotencyLeaseLostError('Funding transfer creation lease was lost.')
1647
+ return validated
1648
+ })
1649
+ return c.json(created, 200)
1650
+ } catch (cause) {
1651
+ await release()
1652
+ if (cause instanceof FundingProvider.InvalidAmountError)
1653
+ return Response.error(c, { code: 'body_invalid', message: 'Invalid `amount`.', status: 400 }) // prettier-ignore
1654
+ if (cause instanceof FundingProvider.UnsupportedSourceChainError)
1655
+ return quoteNotAvailable(c)
1656
+ if (cause instanceof FundingProvider.ProviderQuoteError) {
1657
+ if (cause.fundingProviderAttempts.length > 0)
1658
+ c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
1659
+ if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
1660
+ if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
1661
+ return Response.error(c, {
1662
+ code: 'upstream_error',
1663
+ message: 'No funding provider could prepare the transfer.',
1664
+ status: 502,
1665
+ })
1666
+ }
1667
+ throw cause
1668
+ }
1669
+ },
1670
+ )
1671
+ .post(
1672
+ '/v1/funding/transfers/vault',
1673
+ Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
1674
+ OpenApi.validate('header', schema.createFundingTransferVault.Headers, {
1675
+ code: 'idempotency_key_required',
1676
+ message: 'Funding transfer creation requires an `Idempotency-Key` header.',
1677
+ }),
1678
+ OpenApi.validate('json', schema.createFundingTransferVault.Body, {
1679
+ code: 'body_invalid',
1680
+ message: 'Check the request body and try again.',
1681
+ }),
1682
+ OpenApi.describeRoute({
1683
+ description: 'Creates a transfer into a Tempo Earn vault.',
1684
+ operationId: 'createFundingTransferVault',
1685
+ responses: unimplementedFundingTransferResponses,
1686
+ summary: 'Create transfer into vault',
1687
+ tags: ['Funding'],
1688
+ }),
1689
+ (c) => {
1690
+ if (Auth.narrowAccess) return Auth.accessError(c)
1691
+ if (OpenApi.narrowValidation)
1692
+ return OpenApi.validationError(c, {
1693
+ code: 'body_invalid',
1694
+ message: 'Check the request body and try again.',
1695
+ })
1696
+
1697
+ c.header('Cache-Control', 'no-store')
1698
+ return Response.error(c, {
1699
+ code: 'not_implemented',
1700
+ message: 'Vault funding transfers are not implemented.',
1701
+ status: 501,
1702
+ })
1703
+ },
1704
+ )
1705
+ .post(
1706
+ '/v1/funding/transfers/zone',
1707
+ Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
1708
+ OpenApi.validate('header', schema.createFundingTransferZone.Headers, {
1709
+ code: 'idempotency_key_required',
1710
+ message: 'Funding transfer creation requires an `Idempotency-Key` header.',
1711
+ }),
1712
+ OpenApi.validate('json', schema.createFundingTransferZone.Body, {
1713
+ code: 'body_invalid',
1714
+ message: 'Check the request body and try again.',
1715
+ }),
1716
+ OpenApi.describeRoute({
1717
+ description: 'Creates a transfer into a Tempo Zone.',
1718
+ operationId: 'createFundingTransferZone',
1719
+ responses: unimplementedFundingTransferResponses,
1720
+ summary: 'Create transfer into zone',
1721
+ tags: ['Funding'],
1722
+ }),
1723
+ (c) => {
1724
+ if (Auth.narrowAccess) return Auth.accessError(c)
1725
+ if (OpenApi.narrowValidation)
1726
+ return OpenApi.validationError(c, {
1727
+ code: 'body_invalid',
1728
+ message: 'Check the request body and try again.',
1729
+ })
1730
+
1731
+ c.header('Cache-Control', 'no-store')
1732
+ return Response.error(c, {
1733
+ code: 'not_implemented',
1734
+ message: 'Zone funding transfers are not implemented.',
1735
+ status: 501,
1736
+ })
1737
+ },
1738
+ )
1739
+ }
1740
+
1741
+ function quoteNotAvailable(c: Context<App.Environment>) {
1742
+ return Response.error(c, {
1743
+ code: 'quote_not_available',
1744
+ message: 'No provider can prepare the requested funding route.',
1745
+ status: 404,
1746
+ })
550
1747
  }
551
1748
 
552
- export declare namespace funding {
553
- /** Options for live funding quotes. */
554
- type Options = {
555
- /** Provider adapters used to request live quotes. */
556
- adapters?: FundingQuotes.ProviderAdapters | undefined
557
- /** Per-provider quote timeout in milliseconds. */
558
- adapterTimeoutMs?: number | undefined
1749
+ function depositAddressNotAvailable(c: Context<App.Environment>) {
1750
+ return Response.error(c, {
1751
+ code: 'deposit_address_not_available',
1752
+ message: 'No provider can provision the requested reusable deposit address.',
1753
+ status: 404,
1754
+ })
1755
+ }
1756
+
1757
+ /** Ownership filter from the API-key principal; other principals get no funding access. */
1758
+ function fundingOwner(c: Context<App.Environment>): FundingTransfers.Owner | null {
1759
+ const principal = Auth.getPrincipal(c)
1760
+ if (principal?.type !== 'api_key') return null
1761
+ return {
1762
+ environment: principal.environment,
1763
+ orgId: principal.orgId,
1764
+ projectId: principal.projectId,
559
1765
  }
560
1766
  }
561
1767
 
562
- function fundingProviderAttempt(
563
- attempt: FundingQuotes.ProviderAttempt,
564
- ): Log.FundingProviderAttempt {
565
- const operation = FundingQuoteProviders.providerOperation(attempt.provider)
1768
+ function fundingUnauthorized(c: Context<App.Environment>) {
1769
+ return Response.error(c, {
1770
+ code: 'unauthorized',
1771
+ message: 'Tempo could not determine which organization owns this funding resource.',
1772
+ status: 401,
1773
+ })
1774
+ }
1775
+
1776
+ function formatAttempt(attempt: FundingProvider.ProviderAttempt): Log.FundingProviderAttempt {
1777
+ const { id } = attempt.provider
566
1778
  if (attempt.outcome !== 'failed')
567
1779
  return {
568
1780
  durationMs: attempt.durationMs,
569
- id: attempt.provider,
570
- operation,
1781
+ id,
1782
+ operation: attempt.operation,
571
1783
  outcome: attempt.outcome,
572
1784
  }
573
1785
 
574
1786
  return {
575
1787
  durationMs: attempt.durationMs,
576
- failure: FundingQuoteProviders.providerFailure({
1788
+ failure: FundingProvider.failure({
577
1789
  cause: attempt.cause,
1790
+ operation: attempt.operation,
578
1791
  provider: attempt.provider,
579
1792
  }).failure,
580
- id: attempt.provider,
581
- operation,
1793
+ id,
1794
+ operation: attempt.operation,
582
1795
  outcome: attempt.outcome,
583
1796
  }
584
1797
  }
585
1798
 
586
1799
  // Maps caller-input errors to a specific 400 message; anything else rethrows.
587
1800
  function requestErrorMessage(cause: unknown) {
588
- if (cause instanceof FundingQuotes.InvalidAmountError) return 'Invalid `sourceAmount`.'
589
- if (cause instanceof FundingQuotes.UnsupportedDestinationTokenError)
1801
+ if (cause instanceof FundingProvider.InvalidAmountError) return 'Invalid `sourceAmount`.'
1802
+ if (cause instanceof FundingProvider.UnsupportedDestinationTokenError)
590
1803
  return 'Unsupported `destinationToken`.'
591
- if (cause instanceof FundingQuotes.UnsupportedProviderError) return 'Unsupported `provider`.'
592
- if (cause instanceof FundingQuotes.UnsupportedSourceChainError)
1804
+ if (cause instanceof FundingProvider.UnsupportedProviderError) return 'Unsupported `provider`.'
1805
+ if (cause instanceof FundingProvider.UnsupportedSourceChainError)
593
1806
  return 'Unsupported `sourceChain`.'
594
1807
  return undefined
595
1808
  }
@@ -602,3 +1815,7 @@ function cacheBypass(request: Request) {
602
1815
  ).toLowerCase()
603
1816
  return directives.includes('no-cache') || directives.includes('no-store')
604
1817
  }
1818
+
1819
+ class IdempotencyLeaseLostError extends Error {
1820
+ override name = 'Funding.IdempotencyLeaseLostError'
1821
+ }