tapimo 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +10 -7
  2. package/dist/ApiKey.d.ts +3 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +10 -0
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +7 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +9 -2
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +20117 -19183
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +61 -4
  13. package/dist/App.js.map +1 -1
  14. package/dist/admin/App.d.ts +84 -74
  15. package/dist/admin/App.d.ts.map +1 -1
  16. package/dist/admin/apps/api-keys.d.ts +14 -1
  17. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  18. package/dist/admin/apps/api-keys.js +6 -2
  19. package/dist/admin/apps/api-keys.js.map +1 -1
  20. package/dist/admin/apps/organizations.d.ts +2 -0
  21. package/dist/admin/apps/organizations.d.ts.map +1 -1
  22. package/dist/admin/apps/verified-tokens.d.ts +11 -11
  23. package/dist/admin/ui.generated.d.ts +1 -1
  24. package/dist/admin/ui.generated.js +67 -67
  25. package/dist/admin/ui.generated.js.map +1 -1
  26. package/dist/analytics/Analytics.d.ts +2 -0
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +3 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/data/App.d.ts +11696 -13937
  31. package/dist/apps/data/App.d.ts.map +1 -1
  32. package/dist/apps/data/App.js +4 -8
  33. package/dist/apps/data/App.js.map +1 -1
  34. package/dist/apps/data/routes/activities.d.ts +271 -271
  35. package/dist/apps/data/routes/addresses.d.ts +398 -167
  36. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  37. package/dist/apps/data/routes/balances.d.ts +278 -8
  38. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  39. package/dist/apps/data/routes/balances.js +114 -2
  40. package/dist/apps/data/routes/balances.js.map +1 -1
  41. package/dist/apps/data/routes/blocks.d.ts +123 -123
  42. package/dist/apps/data/routes/coingecko.d.ts +170 -170
  43. package/dist/apps/data/routes/earn.d.ts +498 -6
  44. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  45. package/dist/apps/data/routes/earn.js +876 -4
  46. package/dist/apps/data/routes/earn.js.map +1 -1
  47. package/dist/apps/data/routes/exchanges.d.ts +10 -10
  48. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  49. package/dist/apps/data/routes/indexer.d.ts +6 -6
  50. package/dist/apps/data/routes/indexer.js +4 -1
  51. package/dist/apps/data/routes/indexer.js.map +1 -1
  52. package/dist/apps/data/routes/receipts.d.ts +235 -235
  53. package/dist/apps/data/routes/rpc.d.ts +31 -31
  54. package/dist/apps/data/routes/rpc.js +4 -1
  55. package/dist/apps/data/routes/rpc.js.map +1 -1
  56. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  57. package/dist/apps/data/routes/tokens.d.ts +449 -449
  58. package/dist/apps/data/routes/transactions.d.ts +2 -2
  59. package/dist/apps/data/routes/transfers.d.ts +81 -81
  60. package/dist/apps/data/routes/valuation.d.ts +1 -1
  61. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  62. package/dist/apps/data/routes/webhooks.d.ts +9 -9
  63. package/dist/apps/data/routes/zones.d.ts +14 -14
  64. package/dist/apps/funding/Access.d.ts +16 -0
  65. package/dist/apps/funding/Access.d.ts.map +1 -0
  66. package/dist/apps/funding/Access.js +22 -0
  67. package/dist/apps/funding/Access.js.map +1 -0
  68. package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1640 -3217
  69. package/dist/apps/funding/App.d.ts.map +1 -0
  70. package/dist/apps/funding/App.js +61 -0
  71. package/dist/apps/funding/App.js.map +1 -0
  72. package/dist/apps/funding/Inventory.d.ts +5 -0
  73. package/dist/apps/funding/Inventory.d.ts.map +1 -0
  74. package/dist/apps/funding/Inventory.js +25 -0
  75. package/dist/apps/funding/Inventory.js.map +1 -0
  76. package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
  77. package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
  78. package/dist/apps/funding/ProviderAttempts.js +24 -0
  79. package/dist/apps/funding/ProviderAttempts.js.map +1 -0
  80. package/dist/apps/funding/index.d.ts +2 -0
  81. package/dist/apps/funding/index.d.ts.map +1 -0
  82. package/dist/apps/funding/index.js +2 -0
  83. package/dist/apps/funding/index.js.map +1 -0
  84. package/dist/apps/funding/routes/chains.d.ts +169 -0
  85. package/dist/apps/funding/routes/chains.d.ts.map +1 -0
  86. package/dist/apps/funding/routes/chains.js +80 -0
  87. package/dist/apps/funding/routes/chains.js.map +1 -0
  88. package/dist/apps/funding/routes/deposit-addresses.d.ts +1997 -0
  89. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
  90. package/dist/apps/funding/routes/deposit-addresses.js +778 -0
  91. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
  92. package/dist/apps/funding/routes/providers.d.ts +91 -0
  93. package/dist/apps/funding/routes/providers.d.ts.map +1 -0
  94. package/dist/apps/funding/routes/providers.js +56 -0
  95. package/dist/apps/funding/routes/providers.js.map +1 -0
  96. package/dist/apps/funding/routes/quotes.d.ts +426 -0
  97. package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
  98. package/dist/apps/funding/routes/quotes.js +188 -0
  99. package/dist/apps/funding/routes/quotes.js.map +1 -0
  100. package/dist/apps/funding/routes/transfers.d.ts +1999 -0
  101. package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
  102. package/dist/apps/funding/routes/transfers.js +614 -0
  103. package/dist/apps/funding/routes/transfers.js.map +1 -0
  104. package/dist/apps/index.d.ts +1 -0
  105. package/dist/apps/index.d.ts.map +1 -1
  106. package/dist/apps/index.js +1 -0
  107. package/dist/apps/index.js.map +1 -1
  108. package/dist/apps/management/App.d.ts +430 -165
  109. package/dist/apps/management/App.d.ts.map +1 -1
  110. package/dist/apps/management/routes/api-keys.d.ts +298 -21
  111. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  112. package/dist/apps/management/routes/api-keys.js +75 -1
  113. package/dist/apps/management/routes/api-keys.js.map +1 -1
  114. package/dist/apps/management/routes/billing.d.ts +6 -6
  115. package/dist/apps/management/routes/faucet.d.ts +35 -35
  116. package/dist/apps/management/routes/invitations.d.ts +122 -122
  117. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  118. package/dist/apps/management/routes/members.d.ts +69 -69
  119. package/dist/apps/management/routes/orgs.d.ts +99 -99
  120. package/dist/apps/management/routes/projects.d.ts +25 -25
  121. package/dist/apps/management/routes/scopes.d.ts +1 -1
  122. package/dist/apps/management/routes/usage.d.ts +72 -72
  123. package/dist/apps/management/routes/verified-token-requests.d.ts +77 -77
  124. package/dist/apps/management/routes/webhooks.d.ts +128 -128
  125. package/dist/apps/mcp.d.ts +2 -3
  126. package/dist/apps/mcp.d.ts.map +1 -1
  127. package/dist/apps/mcp.js +16 -14
  128. package/dist/apps/mcp.js.map +1 -1
  129. package/dist/apps/metadata.d.ts +4 -0
  130. package/dist/apps/metadata.d.ts.map +1 -1
  131. package/dist/apps/metadata.js +10 -0
  132. package/dist/apps/metadata.js.map +1 -1
  133. package/dist/apps/mpp/App.d.ts +44 -44
  134. package/dist/cloudflare.d.ts +50 -0
  135. package/dist/cloudflare.d.ts.map +1 -1
  136. package/dist/cloudflare.js +178 -0
  137. package/dist/cloudflare.js.map +1 -1
  138. package/dist/db/Db.d.ts +2 -0
  139. package/dist/db/Db.d.ts.map +1 -1
  140. package/dist/db/Db.js +45 -0
  141. package/dist/db/Db.js.map +1 -1
  142. package/dist/db/Schema.d.ts +12 -0
  143. package/dist/db/Schema.d.ts.map +1 -1
  144. package/dist/db/Schema.js +20 -0
  145. package/dist/db/Schema.js.map +1 -1
  146. package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
  147. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  148. package/dist/db/tables/fundingDepositAddresses.js +12 -0
  149. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  150. package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
  151. package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
  152. package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
  153. package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
  154. package/dist/db/tables/fundingDeposits.d.ts +74 -1
  155. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  156. package/dist/db/tables/fundingDeposits.js +126 -0
  157. package/dist/db/tables/fundingDeposits.js.map +1 -1
  158. package/dist/internal/Auth.d.ts +15 -11
  159. package/dist/internal/Auth.d.ts.map +1 -1
  160. package/dist/internal/Auth.js +52 -17
  161. package/dist/internal/Auth.js.map +1 -1
  162. package/dist/internal/Cache.js +1 -1
  163. package/dist/internal/Cache.js.map +1 -1
  164. package/dist/internal/EdgeCache.d.ts +2 -2
  165. package/dist/internal/EdgeCache.d.ts.map +1 -1
  166. package/dist/internal/EdgeCache.js +10 -4
  167. package/dist/internal/EdgeCache.js.map +1 -1
  168. package/dist/internal/IpAllowlist.d.ts +15 -0
  169. package/dist/internal/IpAllowlist.d.ts.map +1 -0
  170. package/dist/internal/IpAllowlist.js +27 -0
  171. package/dist/internal/IpAllowlist.js.map +1 -0
  172. package/dist/internal/OpenApi.d.ts +1 -1
  173. package/dist/internal/OpenApi.js +1 -1
  174. package/dist/internal/OpenApi.js.map +1 -1
  175. package/dist/internal/Tidx.d.ts +6 -0
  176. package/dist/internal/Tidx.d.ts.map +1 -1
  177. package/dist/internal/Tidx.js +13 -3
  178. package/dist/internal/Tidx.js.map +1 -1
  179. package/dist/internal/Viem.d.ts +8 -0
  180. package/dist/internal/Viem.d.ts.map +1 -1
  181. package/dist/internal/Viem.js +12 -5
  182. package/dist/internal/Viem.js.map +1 -1
  183. package/dist/internal/funding/Deposit.d.ts +42 -0
  184. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  185. package/dist/internal/funding/Deposit.js +37 -5
  186. package/dist/internal/funding/Deposit.js.map +1 -1
  187. package/dist/internal/funding/Evidence.d.ts +1 -5
  188. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  189. package/dist/internal/funding/Evidence.js +4 -3
  190. package/dist/internal/funding/Evidence.js.map +1 -1
  191. package/dist/internal/funding/Provider.d.ts +102 -0
  192. package/dist/internal/funding/Provider.d.ts.map +1 -1
  193. package/dist/internal/funding/Provider.js +5 -0
  194. package/dist/internal/funding/Provider.js.map +1 -1
  195. package/dist/internal/funding/Reconciliation.d.ts +16 -2
  196. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  197. package/dist/internal/funding/Reconciliation.js +348 -54
  198. package/dist/internal/funding/Reconciliation.js.map +1 -1
  199. package/dist/internal/funding/SourceObservation.d.ts +108 -0
  200. package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
  201. package/dist/internal/funding/SourceObservation.js +143 -0
  202. package/dist/internal/funding/SourceObservation.js.map +1 -0
  203. package/dist/internal/funding/Subsidy.d.ts +2 -2
  204. package/dist/internal/funding/Subsidy.d.ts.map +1 -1
  205. package/dist/internal/funding/Subsidy.js +1 -1
  206. package/dist/internal/funding/Subsidy.js.map +1 -1
  207. package/dist/internal/funding/index.d.ts +1 -0
  208. package/dist/internal/funding/index.d.ts.map +1 -1
  209. package/dist/internal/funding/index.js +1 -0
  210. package/dist/internal/funding/index.js.map +1 -1
  211. package/dist/internal/funding/providers/relay.d.ts +43 -1
  212. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  213. package/dist/internal/funding/providers/relay.js +438 -27
  214. package/dist/internal/funding/providers/relay.js.map +1 -1
  215. package/package.json +3 -3
  216. package/src/ApiKey.test.ts +35 -0
  217. package/src/ApiKey.ts +27 -0
  218. package/src/ApiKeys.test.ts +29 -0
  219. package/src/ApiKeys.ts +11 -3
  220. package/src/App.test.ts +55 -5
  221. package/src/App.ts +79 -5
  222. package/src/Client.test-d.ts +203 -28
  223. package/src/admin/apps/api-keys.test.ts +25 -1
  224. package/src/admin/apps/api-keys.ts +6 -2
  225. package/src/admin/ui.generated.ts +67 -67
  226. package/src/analytics/Analytics.ts +4 -0
  227. package/src/apps/composition.test.ts +130 -3
  228. package/src/apps/data/App.test.ts +0 -12
  229. package/src/apps/data/App.ts +4 -8
  230. package/src/apps/data/routes/balances.test.ts +42 -0
  231. package/src/apps/data/routes/balances.ts +218 -85
  232. package/src/apps/data/routes/earn.test.ts +363 -0
  233. package/src/apps/data/routes/earn.ts +1141 -3
  234. package/src/apps/data/routes/indexer.ts +2 -1
  235. package/src/apps/data/routes/rpc.ts +2 -0
  236. package/src/apps/data/routes/tokens.test.ts +6 -8
  237. package/src/apps/funding/Access.ts +26 -0
  238. package/src/apps/funding/App.test.ts +78 -0
  239. package/src/apps/funding/App.ts +78 -0
  240. package/src/apps/funding/Inventory.ts +30 -0
  241. package/src/apps/funding/ProviderAttempts.ts +26 -0
  242. package/src/apps/funding/index.ts +1 -0
  243. package/src/apps/funding/routes/chains.test.ts +78 -0
  244. package/src/apps/funding/routes/chains.ts +101 -0
  245. package/src/apps/funding/routes/deposit-addresses.test.ts +1649 -0
  246. package/src/apps/funding/routes/deposit-addresses.ts +932 -0
  247. package/src/apps/funding/routes/providers.test.ts +165 -0
  248. package/src/apps/funding/routes/providers.ts +73 -0
  249. package/src/apps/funding/routes/quotes.test.ts +511 -0
  250. package/src/apps/funding/routes/quotes.ts +205 -0
  251. package/src/apps/funding/routes/transfers.test.ts +1236 -0
  252. package/src/apps/funding/routes/transfers.ts +704 -0
  253. package/src/apps/index.ts +1 -0
  254. package/src/apps/management/routes/api-keys.test.ts +98 -0
  255. package/src/apps/management/routes/api-keys.ts +96 -1
  256. package/src/apps/mcp.test.ts +17 -2
  257. package/src/apps/mcp.ts +18 -13
  258. package/src/apps/metadata.ts +12 -0
  259. package/src/apps/mpp/internal/Policy.test.ts +7 -1
  260. package/src/cloudflare.ts +279 -0
  261. package/src/db/Db.ts +71 -0
  262. package/src/db/Schema.ts +24 -0
  263. package/src/db/tables/fundingDepositAddresses.ts +28 -0
  264. package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
  265. package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
  266. package/src/db/tables/fundingDeposits.test.ts +239 -0
  267. package/src/db/tables/fundingDeposits.ts +214 -1
  268. package/src/internal/Auth.test.ts +205 -5
  269. package/src/internal/Auth.ts +63 -19
  270. package/src/internal/Cache.test.ts +37 -3
  271. package/src/internal/Cache.ts +1 -1
  272. package/src/internal/EdgeCache.test.ts +7 -7
  273. package/src/internal/EdgeCache.ts +13 -4
  274. package/src/internal/IpAllowlist.test.ts +52 -0
  275. package/src/internal/IpAllowlist.ts +39 -0
  276. package/src/internal/Log.test.ts +7 -1
  277. package/src/internal/OpenApi.ts +1 -1
  278. package/src/internal/Tidx.test.ts +28 -0
  279. package/src/internal/Tidx.ts +19 -3
  280. package/src/internal/Viem.test.ts +28 -0
  281. package/src/internal/Viem.ts +21 -5
  282. package/src/internal/funding/Deposit.ts +74 -5
  283. package/src/internal/funding/Evidence.test.ts +2 -2
  284. package/src/internal/funding/Evidence.ts +4 -9
  285. package/src/internal/funding/Provider.ts +115 -0
  286. package/src/internal/funding/Reconciliation.local.test.ts +7 -0
  287. package/src/internal/funding/Reconciliation.test.ts +1364 -109
  288. package/src/internal/funding/Reconciliation.ts +467 -88
  289. package/src/internal/funding/SourceObservation.test.ts +301 -0
  290. package/src/internal/funding/SourceObservation.ts +267 -0
  291. package/src/internal/funding/Subsidy.ts +2 -2
  292. package/src/internal/funding/index.test.ts +1 -0
  293. package/src/internal/funding/index.ts +1 -0
  294. package/src/internal/funding/providers/relay.test.ts +94 -4
  295. package/src/internal/funding/providers/relay.ts +533 -27
  296. package/dist/apps/data/routes/funding.d.ts.map +0 -1
  297. package/dist/apps/data/routes/funding.js +0 -1557
  298. package/dist/apps/data/routes/funding.js.map +0 -1
  299. package/src/apps/data/routes/funding.test.ts +0 -2994
  300. package/src/apps/data/routes/funding.ts +0 -1821
@@ -0,0 +1,932 @@
1
+ import { type Context, Hono } from 'hono'
2
+ import { etag } from 'hono/etag'
3
+ import * as z from 'zod/mini'
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 Auth from '../../../internal/Auth.js'
11
+ import * as Cache from '../../../internal/Cache.js'
12
+ import * as Cursor from '../../../internal/Cursor.js'
13
+ import * as FundingCatalog from '../../../internal/funding/Catalog.js'
14
+ import * as FundingDeposit from '../../../internal/funding/Deposit.js'
15
+ import * as FundingDepositAddress from '../../../internal/funding/DepositAddress.js'
16
+ import * as FundingProvider from '../../../internal/funding/Provider.js'
17
+ import * as FundingTransfer from '../../../internal/funding/Transfer.js'
18
+ import * as Idempotency from '../../../internal/Idempotency.js'
19
+ import * as OpenApi from '../../../internal/OpenApi.js'
20
+ import * as Response from '../../../internal/Response.js'
21
+ import * as Schema from '../../../internal/Schema.js'
22
+ import * as Value from '../../../internal/Value.js'
23
+ import * as Access from '../Access.js'
24
+ import * as ProviderAttempts from '../ProviderAttempts.js'
25
+
26
+ /** Zod schemas owned by funding deposit-address resources. */
27
+ export namespace schema {
28
+ /** One reusable funding deposit address. */
29
+ export const DepositAddress = FundingDepositAddress.schema.FundingDepositAddress
30
+
31
+ /** One detected funding deposit. */
32
+ export const Deposit = FundingDeposit.schema.FundingDeposit
33
+
34
+ /** Schemas for the createFundingDepositAddress operation. */
35
+ export namespace createFundingDepositAddress {
36
+ /** Funding deposit address creation request body. */
37
+ export const Body = z
38
+ .strictObject({
39
+ amount: FundingTransfer.schema.CreateRequest.shape.amount,
40
+ destinationToken: z
41
+ .string()
42
+ .check(
43
+ z.minLength(1),
44
+ z.describe('Tempo destination token symbol, contract address, or token key.'),
45
+ z.meta({ examples: ['usdt0'] }),
46
+ ),
47
+ recipient: z
48
+ .string()
49
+ .check(
50
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
51
+ z.describe('Tempo account that receives completed deposits.'),
52
+ z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
53
+ ),
54
+ refundAddress: z
55
+ .string()
56
+ .check(
57
+ z.minLength(1),
58
+ z.describe('Source-chain account that receives refunds.'),
59
+ z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
60
+ ),
61
+ sourceChain: z
62
+ .string()
63
+ .check(
64
+ z.minLength(1),
65
+ z.describe('Source chain CAIP-2 id, slug, or alias.'),
66
+ z.meta({ examples: ['tron', 'tron:0x2b6653dc'] }),
67
+ ),
68
+ sourceToken: z
69
+ .string()
70
+ .check(
71
+ z.minLength(1),
72
+ z.describe('Source token symbol, contract address, or token key.'),
73
+ z.meta({ examples: ['usdt'] }),
74
+ ),
75
+ subsidize: z
76
+ .optional(z.boolean())
77
+ .check(
78
+ z.describe('Guarantees normalized 1:1 delivery through a Tempo-funded subsidy.'),
79
+ z.meta({ examples: [false] }),
80
+ ),
81
+ })
82
+ .check(z.describe('Reusable funding deposit address creation request.'))
83
+
84
+ /** Required headers for funding deposit address creation. */
85
+ export const Headers = z.object({
86
+ 'idempotency-key': z
87
+ .string()
88
+ .check(
89
+ z.minLength(1),
90
+ z.describe(
91
+ '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.',
92
+ ),
93
+ z.meta({ examples: ['funding_01k1c5j8q8p0be6j5v9m6d1e4r'] }),
94
+ ),
95
+ })
96
+
97
+ /** The reusable address, with quoted terms only when the provider address was newly stored. */
98
+ export const Response = z
99
+ .union([
100
+ FundingDepositAddress.schema.CreatedFundingDepositAddress,
101
+ FundingDepositAddress.schema.FundingDepositAddress,
102
+ ])
103
+ .check(
104
+ z.describe(
105
+ 'A reusable funding deposit address, including terms sampled for a newly stored provider address.',
106
+ ),
107
+ )
108
+ }
109
+
110
+ /** Schemas for the getFundingDepositAddress operation. */
111
+ export namespace getFundingDepositAddress {
112
+ /** Path parameters for reading a funding deposit address. */
113
+ export const Params = z
114
+ .object({
115
+ id: z
116
+ .string()
117
+ .check(
118
+ z.regex(/^fda_[A-Za-z0-9_-]+$/),
119
+ z.describe('Funding deposit address ID (`fda_…`).'),
120
+ z.meta({ examples: ['fda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
121
+ ),
122
+ })
123
+ .check(z.describe('Path parameters for reading a funding deposit address.'))
124
+
125
+ /** One reusable funding deposit address. */
126
+ export const Response = DepositAddress
127
+ }
128
+
129
+ /** Schemas for the reconcileFundingDepositAddress operation. */
130
+ export namespace reconcileFundingDepositAddress {
131
+ /** Path parameters for reconciling a funding deposit address. */
132
+ export const Params = getFundingDepositAddress.Params
133
+ }
134
+
135
+ /** Schemas for the listFundingDeposits operation. */
136
+ export namespace listFundingDeposits {
137
+ /** Query parameters for listing funding deposits. */
138
+ export const Query = z
139
+ .object({
140
+ depositAddress: z
141
+ .optional(z.string().check(z.minLength(1)))
142
+ .check(
143
+ z.describe('Reusable source-chain deposit address that received the funds.'),
144
+ z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
145
+ ),
146
+ cursor: Schema.Cursor,
147
+ limit: Schema.Limit,
148
+ recipient: z
149
+ .optional(Schema.Address)
150
+ .check(
151
+ z.describe('Tempo account that received the deposits.'),
152
+ z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
153
+ ),
154
+ })
155
+ .check(
156
+ z.refine(
157
+ (query) => (query.depositAddress === undefined) !== (query.recipient === undefined),
158
+ {
159
+ error: 'Pass exactly one of `depositAddress` or `recipient`.',
160
+ path: ['depositAddress'],
161
+ },
162
+ ),
163
+ z.describe('Exactly one deposit filter and optional paging parameters.'),
164
+ )
165
+
166
+ /** One page of detected deposits. */
167
+ export const Response = z
168
+ .object({
169
+ data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
170
+ nextCursor: Schema.NextCursor,
171
+ })
172
+ .check(z.describe('A page of funding deposits matching the selected filter.'))
173
+ }
174
+
175
+ /** Schemas for the getFundingDeposit operation. */
176
+ export namespace getFundingDeposit {
177
+ /** Path parameters for reading a funding deposit. */
178
+ export const Params = z
179
+ .object({
180
+ id: z
181
+ .string()
182
+ .check(
183
+ z.regex(/^fdp_[A-Za-z0-9_-]+$/),
184
+ z.describe('Funding deposit id (`fdp_…`).'),
185
+ z.meta({ examples: ['fdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx'] }),
186
+ ),
187
+ })
188
+ .check(z.describe('Path parameters for reading a funding deposit.'))
189
+
190
+ /** One detected funding deposit. */
191
+ export const Response = Deposit
192
+ }
193
+ }
194
+
195
+ const tronChainExample = {
196
+ addressFormat: 'base58check',
197
+ id: 'tron:0x2b6653dc',
198
+ kind: 'tron',
199
+ name: 'Tron',
200
+ } as const
201
+
202
+ const tronUsdtExample = {
203
+ address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
204
+ currency: 'USD',
205
+ decimals: 6,
206
+ name: 'Tether USD',
207
+ standard: 'TRC-20',
208
+ symbol: 'USDT',
209
+ tokenKey: 'tron:0x2b6653dc/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
210
+ verified: true,
211
+ } as const
212
+
213
+ const tempoChainExample = {
214
+ addressFormat: 'hex',
215
+ id: 'eip155:4217',
216
+ kind: 'evm',
217
+ name: 'Tempo',
218
+ } as const
219
+
220
+ const tempoUsdt0Example = {
221
+ address: '0x20c00000000000000000000014f22ca97301eb73',
222
+ currency: 'USD',
223
+ decimals: 6,
224
+ name: 'USDT0',
225
+ standard: 'TIP-20',
226
+ symbol: 'USDT0',
227
+ tokenKey: 'eip155:4217/erc20:0x20c00000000000000000000014f22ca97301eb73',
228
+ verified: true,
229
+ } as const
230
+
231
+ const fundingDepositAddressExample = {
232
+ address: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
233
+ createdAt: '2026-08-04T00:00:00.000Z',
234
+ destinationChain: tempoChainExample,
235
+ destinationToken: tempoUsdt0Example,
236
+ id: 'fda_001785792000000_2ZPE2gvateYEQ0dQslgvkhjx',
237
+ provider: { id: 'relay', name: 'Relay' },
238
+ recipient: `0x${'11'.repeat(20)}`,
239
+ refundAddress: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
240
+ sourceChain: tronChainExample,
241
+ sourceToken: tronUsdtExample,
242
+ status: 'active',
243
+ subsidize: false,
244
+ updatedAt: '2026-08-04T00:00:00.000Z',
245
+ } satisfies z.output<typeof schema.DepositAddress>
246
+
247
+ const createdFundingDepositAddressExample = {
248
+ ...fundingDepositAddressExample,
249
+ destinationAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
250
+ destinationAmountMin: Value.tokenAmount({
251
+ baseUnits: '1000000',
252
+ currency: 'USD',
253
+ decimals: 6,
254
+ }),
255
+ fees: [],
256
+ quote: { sampledAt: '2026-08-04T00:00:00.000Z' },
257
+ sourceAmount: Value.tokenAmount({ baseUnits: '1000000', currency: 'USD', decimals: 6 }),
258
+ } satisfies z.output<typeof schema.createFundingDepositAddress.Response>
259
+
260
+ const fundingDepositExample = {
261
+ createdAt: '2026-08-04T00:01:00.000Z',
262
+ depositAddressId: fundingDepositAddressExample.id,
263
+ detectionTrigger: 'webhook',
264
+ destinationChain: tempoChainExample,
265
+ destinationToken: tempoUsdt0Example,
266
+ id: 'fdp_001785792060000_2ZPE2gvateYEQ0dQslgvkhjx',
267
+ provider: { id: 'relay', name: 'Relay' },
268
+ recipient: fundingDepositAddressExample.recipient,
269
+ refundAddress: fundingDepositAddressExample.refundAddress,
270
+ sourceChain: tronChainExample,
271
+ sourceToken: tronUsdtExample,
272
+ sourceTransactionHashes: ['9f2c4c80dbb468878baa8c07ff523e0464bf6437f2a486268508485d83f14d12'],
273
+ status: 'detected',
274
+ updatedAt: '2026-08-04T00:01:00.000Z',
275
+ } satisfies z.output<typeof schema.Deposit>
276
+
277
+ const fundingDepositsExample = {
278
+ data: [fundingDepositExample],
279
+ nextCursor: null,
280
+ } satisfies z.output<typeof schema.listFundingDeposits.Response>
281
+
282
+ const reconcileFundingDepositAddressResponses = (() => {
283
+ const responses = OpenApi.responses({
284
+ errors: {
285
+ 400: { codes: ['param_invalid'] },
286
+ 403: {
287
+ codes: ['api_key_forbidden'],
288
+ description: 'The API key does not grant funding write access.',
289
+ },
290
+ 404: {
291
+ codes: ['funding_deposit_address_not_found'],
292
+ description: 'No visible funding deposit address exists for the identifier.',
293
+ },
294
+ 409: {
295
+ codes: ['funding_deposit_address_not_active'],
296
+ description: 'The funding deposit address is not active.',
297
+ },
298
+ 501: {
299
+ codes: ['not_implemented'],
300
+ description: 'Manual funding reconciliation is not configured.',
301
+ },
302
+ 502: {
303
+ codes: ['upstream_error'],
304
+ description: 'Reconciliation could not be queued.',
305
+ },
306
+ },
307
+ success: {
308
+ description: 'Unused because reconciliation returns an empty `202` response.',
309
+ schema: z.never(),
310
+ },
311
+ })
312
+ const { 200: success, ...errors } = responses
313
+ void success
314
+ return {
315
+ 202: {
316
+ description: 'The funding deposit address was queued for reconciliation.',
317
+ headers: { ...OpenApi.successHeaders },
318
+ },
319
+ ...errors,
320
+ }
321
+ })()
322
+
323
+ // Replay the exact successful creation response for 24 hours.
324
+ const idempotencyReplayTtlMs = 86_400_000
325
+ // Pending claims only cover the bounded provider preparation window.
326
+ const idempotencyPendingTtlMs = 60_000
327
+
328
+ const depositAddressCheckpoint = Schema.describe(
329
+ z.object({
330
+ candidate: Schema.describe(
331
+ z.object({
332
+ destinationChain: FundingProvider.schema.ChainRef,
333
+ destinationToken: FundingProvider.schema.TokenRef,
334
+ sourceChain: FundingProvider.schema.ChainRef,
335
+ sourceToken: FundingProvider.schema.TokenRef,
336
+ }),
337
+ 'Normalized route retained after provider provisioning.',
338
+ ),
339
+ provider: FundingProvider.schema.ProviderRef,
340
+ recipient: z.string().check(z.describe('Normalized Tempo recipient.')),
341
+ refundAddress: z.string().check(z.describe('Normalized source-chain refund account.')),
342
+ result: FundingProvider.schema.DepositAddressResult,
343
+ }),
344
+ 'Provider result retained until deposit address creation completes.',
345
+ )
346
+
347
+ const fundingEtag = etag()
348
+
349
+ /** Mounts reusable deposit-address and detected-deposit routes. */
350
+ export function depositAddresses(options: depositAddresses.Options = {}) {
351
+ return new Hono<App.Environment>()
352
+ .post('/webhooks/funding/:providerId', async (c) => {
353
+ const provider = c
354
+ .get('providers')
355
+ .filter(FundingProvider.is)
356
+ .find((candidate) => candidate.id === c.req.param('providerId'))
357
+ if (!provider || !FundingProvider.canReceiveWebhook(provider)) return c.body(null, 404)
358
+ try {
359
+ await provider.webhook.receive({
360
+ body: await c.req.text(),
361
+ headers: c.req.raw.headers,
362
+ })
363
+ return c.body(null, 204)
364
+ } catch (cause) {
365
+ if (cause instanceof FundingProvider.WebhookAuthenticationError) return c.body(null, 401)
366
+ if (cause instanceof FundingProvider.WebhookPayloadError) return c.body(null, 400)
367
+ throw cause
368
+ }
369
+ })
370
+ .post(
371
+ '/v1/funding/deposit-addresses',
372
+ Auth.policy({ apiKey: { scopes: ['funding:write'] } }),
373
+ OpenApi.validate('header', schema.createFundingDepositAddress.Headers, {
374
+ code: 'idempotency_key_required',
375
+ message: 'Funding deposit address creation requires an `Idempotency-Key` header.',
376
+ }),
377
+ OpenApi.validate('json', schema.createFundingDepositAddress.Body, {
378
+ code: 'body_invalid',
379
+ message: 'Check the request body and try again.',
380
+ }),
381
+ OpenApi.describeRoute({
382
+ description: 'Creates or returns a reusable deposit address for funding a Tempo account.',
383
+ operationId: 'createFundingDepositAddress',
384
+ responses: OpenApi.responses({
385
+ errors: {
386
+ 400: { codes: ['body_invalid', 'idempotency_key_required'] },
387
+ 404: {
388
+ codes: ['deposit_address_not_available'],
389
+ description: 'No provider can provision the requested reusable address route.',
390
+ },
391
+ 409: {
392
+ codes: ['idempotency_conflict', 'idempotency_in_progress'],
393
+ description: 'The Idempotency-Key conflicts with an existing or in-flight request.',
394
+ },
395
+ 502: {
396
+ codes: ['upstream_error'],
397
+ description: 'The selected provider could not provision the address.',
398
+ },
399
+ },
400
+ success: {
401
+ description: 'The created or matching reusable funding deposit address and quote.',
402
+ example: createdFundingDepositAddressExample,
403
+ schema: schema.createFundingDepositAddress.Response,
404
+ },
405
+ }),
406
+ summary: 'Create deposit address',
407
+ tags: ['Deposit Addresses'],
408
+ }),
409
+ async (c) => {
410
+ if (Auth.narrowAccess) return Auth.accessError(c)
411
+ if (OpenApi.narrowValidation)
412
+ return OpenApi.validationError(c, {
413
+ code: 'body_invalid',
414
+ message: 'Check the request body and try again.',
415
+ })
416
+
417
+ const principal = Auth.getPrincipal(c)
418
+ const owner = Access.owner(c)
419
+ if (!owner || principal?.type !== 'api_key') return Access.unauthorized(c)
420
+ c.header('Cache-Control', 'no-store')
421
+
422
+ const body = c.req.valid('json')
423
+ const subsidize = body.subsidize ?? false
424
+ const db = Db.get(c.get('db'))
425
+ const identity = {
426
+ apiKeyId: principal.id,
427
+ keyHash: Idempotency.inputHash(c.req.valid('header')['idempotency-key']),
428
+ requestHash: Idempotency.inputHash({
429
+ body,
430
+ endpoint: 'POST /v1/funding/deposit-addresses',
431
+ }),
432
+ }
433
+ const claim = await FundingIdempotency.claim(db, {
434
+ ...identity,
435
+ ttlMs: idempotencyPendingTtlMs,
436
+ })
437
+ if (claim.type === 'mismatch')
438
+ return Response.error(c, {
439
+ code: 'idempotency_conflict',
440
+ message: 'This Idempotency-Key was already used with different input.',
441
+ status: 409,
442
+ })
443
+ if (claim.type === 'pending')
444
+ return Response.error(c, {
445
+ code: 'idempotency_in_progress',
446
+ message: 'An equivalent funding deposit address creation is still running.',
447
+ status: 409,
448
+ })
449
+ if (claim.type === 'replay') {
450
+ const response = JSON.parse(claim.response) as Record<string, unknown>
451
+ return c.json(
452
+ Response.validated(schema.createFundingDepositAddress.Response, {
453
+ ...response,
454
+ subsidize: response['subsidize'] ?? false,
455
+ }),
456
+ 200,
457
+ )
458
+ }
459
+
460
+ const release = async () => {
461
+ try {
462
+ await FundingIdempotency.release(db, {
463
+ apiKeyId: identity.apiKeyId,
464
+ createdAt: claim.createdAt,
465
+ keyHash: identity.keyHash,
466
+ })
467
+ } catch {
468
+ // The pending claim expires on its own; the response stays authoritative.
469
+ }
470
+ }
471
+ const complete = async (
472
+ database: Db.Db,
473
+ response: z.output<typeof schema.createFundingDepositAddress.Response>,
474
+ ) => {
475
+ const validated = Response.validated(
476
+ schema.createFundingDepositAddress.Response,
477
+ response,
478
+ )
479
+ const completion = await FundingIdempotency.complete(database, {
480
+ apiKeyId: identity.apiKeyId,
481
+ createdAt: claim.createdAt,
482
+ keyHash: identity.keyHash,
483
+ replayTtlMs: idempotencyReplayTtlMs,
484
+ response: JSON.stringify(validated),
485
+ })
486
+ if (!completion)
487
+ throw new IdempotencyLeaseLostError('Funding deposit address creation lease was lost.')
488
+ return validated
489
+ }
490
+
491
+ const attempts: FundingProvider.ProviderAttempt[] = []
492
+ const provisioning = { succeeded: claim.type === 'resume' }
493
+ try {
494
+ const selected = await (async () => {
495
+ if (claim.type === 'resume')
496
+ return depositAddressCheckpoint.parse(JSON.parse(claim.response))
497
+ const catalog = await FundingCatalog.snapshot(c)
498
+ const providers = c.get('providers').filter(FundingProvider.is)
499
+ const provisioned = await FundingProvider.createDepositAddress({
500
+ amount: body.amount,
501
+ catalog,
502
+ destinationChainId: c.get('chainId'),
503
+ destinationToken: body.destinationToken,
504
+ onAttempt: (attempt) => attempts.push(attempt),
505
+ providers,
506
+ recipient: body.recipient,
507
+ refundAddress: body.refundAddress,
508
+ sourceChain: body.sourceChain,
509
+ sourceToken: body.sourceToken,
510
+ subsidize,
511
+ })
512
+ if (!provisioned) return undefined
513
+ provisioning.succeeded = true
514
+ const checkpoint = depositAddressCheckpoint.parse({
515
+ candidate: provisioned.candidate,
516
+ provider: provisioned.provider,
517
+ recipient: provisioned.recipient,
518
+ refundAddress: provisioned.refundAddress,
519
+ result: provisioned.result,
520
+ })
521
+ const stored = await FundingIdempotency.checkpoint(db, {
522
+ apiKeyId: identity.apiKeyId,
523
+ createdAt: claim.createdAt,
524
+ keyHash: identity.keyHash,
525
+ replayTtlMs: idempotencyReplayTtlMs,
526
+ response: JSON.stringify(checkpoint),
527
+ })
528
+ if (!stored)
529
+ throw new IdempotencyLeaseLostError(
530
+ 'Funding deposit address creation lease was lost.',
531
+ )
532
+ return checkpoint
533
+ })()
534
+ if (attempts.length > 0)
535
+ c.set('fundingProviderAttempts', attempts.map(ProviderAttempts.format))
536
+ if (!selected) {
537
+ await release()
538
+ return depositAddressNotAvailable(c)
539
+ }
540
+
541
+ const {
542
+ candidate: selectedCandidate,
543
+ provider,
544
+ recipient,
545
+ refundAddress,
546
+ result,
547
+ } = selected
548
+ const feeToken = (side: 'destination' | 'source') =>
549
+ side === 'source' ? selectedCandidate.sourceToken : selectedCandidate.destinationToken
550
+ const created = await db.transaction(async (tx) => {
551
+ const stored = await FundingDepositAddress.createOrGet(tx, {
552
+ apiKeyId: principal.id,
553
+ deliveryStrategy: 'provider',
554
+ environment: owner.environment,
555
+ orgId: owner.orgId,
556
+ ...(owner.projectId === undefined ? {} : { projectId: owner.projectId }),
557
+ providerOutputToken: selectedCandidate.destinationToken,
558
+ providerRequestIds: [result.correlation.requestId],
559
+ providerState:
560
+ result.correlation.checkEndpoint === undefined
561
+ ? undefined
562
+ : { checkEndpoint: result.correlation.checkEndpoint },
563
+ snapshot: {
564
+ address: result.address,
565
+ destinationChain: selectedCandidate.destinationChain,
566
+ destinationToken: selectedCandidate.destinationToken,
567
+ provider,
568
+ recipient,
569
+ refundAddress,
570
+ sourceChain: selectedCandidate.sourceChain,
571
+ sourceToken: selectedCandidate.sourceToken,
572
+ subsidize,
573
+ },
574
+ })
575
+ const record = FundingDepositAddress.toPublic(stored.record)
576
+ if (stored.type === 'existing') return complete(tx, record)
577
+ return complete(tx, {
578
+ ...record,
579
+ destinationAmount: Value.tokenAmount({
580
+ baseUnits: result.destinationAmount,
581
+ currency: selectedCandidate.destinationToken.currency,
582
+ decimals: selectedCandidate.destinationToken.decimals,
583
+ }),
584
+ destinationAmountMin: Value.tokenAmount({
585
+ baseUnits: result.destinationAmountMin,
586
+ currency: selectedCandidate.destinationToken.currency,
587
+ decimals: selectedCandidate.destinationToken.decimals,
588
+ }),
589
+ fees: result.fees.map((fee) => ({
590
+ amount: Value.tokenAmount({
591
+ baseUnits: fee.amount,
592
+ currency: feeToken(fee.side).currency,
593
+ decimals: feeToken(fee.side).decimals,
594
+ }),
595
+ side: fee.side,
596
+ token: feeToken(fee.side),
597
+ type: 'provider',
598
+ })),
599
+ quote: {
600
+ ...(result.expiresAt === undefined ? {} : { expiresAt: result.expiresAt }),
601
+ sampledAt: result.sampledAt,
602
+ },
603
+ sourceAmount: Value.tokenAmount({
604
+ baseUnits: body.amount,
605
+ currency: selectedCandidate.sourceToken.currency,
606
+ decimals: selectedCandidate.sourceToken.decimals,
607
+ }),
608
+ })
609
+ })
610
+ if (options.registerDepositAddress)
611
+ await options.registerDepositAddress(
612
+ FundingDepositAddress.schema.FundingDepositAddress.parse(created),
613
+ )
614
+ return c.json(created, 200)
615
+ } catch (cause) {
616
+ if (!provisioning.succeeded) await release()
617
+ if (cause instanceof FundingProvider.InvalidRefundAddressError)
618
+ return Response.error(c, {
619
+ code: 'body_invalid',
620
+ message: 'Invalid `refundAddress`.',
621
+ status: 400,
622
+ })
623
+ if (cause instanceof FundingProvider.UnsupportedSourceChainError)
624
+ return depositAddressNotAvailable(c)
625
+ if (
626
+ cause instanceof FundingDepositAddress.ConflictError ||
627
+ cause instanceof FundingProvider.ProviderQuoteError
628
+ ) {
629
+ if (cause instanceof FundingDepositAddress.ConflictError)
630
+ return Response.error(c, {
631
+ code: 'upstream_error',
632
+ message: 'The funding provider returned a conflicting deposit address.',
633
+ status: 502,
634
+ })
635
+ if (cause.fundingProviderAttempts.length > 0)
636
+ c.set('fundingProviderAttempts', cause.fundingProviderAttempts)
637
+ if (cause.providerFailure) c.set('providerFailure', cause.providerFailure)
638
+ if (cause.providerFailures) c.set('providerFailures', cause.providerFailures)
639
+ return Response.error(c, {
640
+ code: 'upstream_error',
641
+ message: 'The funding provider could not provision the deposit address.',
642
+ status: 502,
643
+ })
644
+ }
645
+ throw cause
646
+ }
647
+ },
648
+ )
649
+ .get(
650
+ '/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}',
651
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
652
+ OpenApi.validate('param', schema.getFundingDepositAddress.Params, {
653
+ code: 'param_invalid',
654
+ message: 'Check the path parameters and try again.',
655
+ }),
656
+ OpenApi.describeRoute({
657
+ description: 'Returns one reusable funding deposit address.',
658
+ operationId: 'getFundingDepositAddress',
659
+ responses: OpenApi.responses({
660
+ errors: {
661
+ 400: { codes: ['param_invalid'] },
662
+ 404: {
663
+ codes: ['funding_deposit_address_not_found'],
664
+ description: 'No visible funding deposit address exists for the identifier.',
665
+ },
666
+ },
667
+ success: {
668
+ description: 'The requested funding deposit address.',
669
+ example: fundingDepositAddressExample,
670
+ schema: schema.getFundingDepositAddress.Response,
671
+ },
672
+ }),
673
+ summary: 'Get deposit address',
674
+ tags: ['Deposit Addresses'],
675
+ }),
676
+ async (c) => {
677
+ if (Auth.narrowAccess) return Auth.accessError(c)
678
+ if (OpenApi.narrowValidation)
679
+ return OpenApi.validationError(c, {
680
+ code: 'param_invalid',
681
+ message: 'Check the path parameters and try again.',
682
+ })
683
+
684
+ const owner = Access.owner(c)
685
+ if (!owner) return Access.unauthorized(c)
686
+
687
+ const db = Db.get(c.get('db'))
688
+ const { id } = c.req.valid('param')
689
+ try {
690
+ const record = await FundingDepositAddresses.getOwned(db, owner, id)
691
+ if (!record)
692
+ return Response.error(c, {
693
+ code: 'funding_deposit_address_not_found',
694
+ message: 'Funding deposit address not found',
695
+ status: 404,
696
+ })
697
+
698
+ c.header('Cache-Control', Cache.policies.noStore)
699
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key')
700
+ return c.json(
701
+ Response.validated(
702
+ schema.getFundingDepositAddress.Response,
703
+ FundingDepositAddress.toPublic(record),
704
+ ),
705
+ 200,
706
+ )
707
+ } catch (cause) {
708
+ return Response.upstream(c, cause)
709
+ }
710
+ },
711
+ )
712
+ .post(
713
+ '/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}/reconcile',
714
+ Auth.policy({
715
+ apiKey: { rateLimit: { perMinute: 10 }, scopes: ['funding:write'] },
716
+ }),
717
+ OpenApi.validate('param', schema.reconcileFundingDepositAddress.Params, {
718
+ code: 'param_invalid',
719
+ message: 'Check the path parameters and try again.',
720
+ }),
721
+ OpenApi.describeRoute({
722
+ description:
723
+ 'Queues an immediate provider reconciliation for one reusable funding deposit address.',
724
+ operationId: 'reconcileFundingDepositAddress',
725
+ responses: reconcileFundingDepositAddressResponses,
726
+ summary: 'Reconcile deposit address',
727
+ tags: ['Deposit Addresses'],
728
+ }),
729
+ async (c) => {
730
+ if (Auth.narrowAccess) return Auth.accessError(c)
731
+ if (OpenApi.narrowValidation)
732
+ return OpenApi.validationError(c, {
733
+ code: 'param_invalid',
734
+ message: 'Check the path parameters and try again.',
735
+ })
736
+
737
+ const owner = Access.owner(c)
738
+ if (!owner) return Access.unauthorized(c)
739
+
740
+ c.header('Cache-Control', Cache.policies.noStore)
741
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
742
+ const db = Db.get(c.get('db'))
743
+ const { id } = c.req.valid('param')
744
+ try {
745
+ const record = await FundingDepositAddresses.getOwned(db, owner, id)
746
+ if (!record)
747
+ return Response.error(c, {
748
+ code: 'funding_deposit_address_not_found',
749
+ message: 'Funding deposit address not found',
750
+ status: 404,
751
+ })
752
+ if (record.status !== 'active')
753
+ return Response.error(c, {
754
+ code: 'funding_deposit_address_not_active',
755
+ message: 'Funding deposit address is not active',
756
+ status: 409,
757
+ })
758
+ const dispatch = options.dispatchReconciliation
759
+ if (!dispatch)
760
+ return Response.error(c, {
761
+ code: 'not_implemented',
762
+ message: 'Manual funding reconciliation is not configured',
763
+ status: 501,
764
+ })
765
+
766
+ await dispatch({
767
+ addressId: record.id,
768
+ trigger: 'manual',
769
+ type: 'funding:deposit-address:reconcile',
770
+ })
771
+ return c.body(null, 202)
772
+ } catch (cause) {
773
+ return Response.upstream(c, cause)
774
+ }
775
+ },
776
+ )
777
+ .get(
778
+ '/v1/funding/deposits',
779
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
780
+ OpenApi.validate('query', schema.listFundingDeposits.Query, {
781
+ code: 'query_invalid',
782
+ message: 'Check the query parameters and try again.',
783
+ }),
784
+ OpenApi.describeRoute({
785
+ description: 'Lists deposits by deposit address or Tempo recipient, newest first.',
786
+ operationId: 'listFundingDeposits',
787
+ responses: OpenApi.responses({
788
+ errors: {
789
+ 400: { codes: ['query_invalid'] },
790
+ },
791
+ success: {
792
+ description: 'A page of funding deposits matching the selected filter.',
793
+ example: fundingDepositsExample,
794
+ schema: schema.listFundingDeposits.Response,
795
+ },
796
+ }),
797
+ summary: 'List deposits',
798
+ tags: ['Deposit Addresses'],
799
+ }),
800
+ async (c) => {
801
+ if (Auth.narrowAccess) return Auth.accessError(c)
802
+ if (OpenApi.narrowValidation)
803
+ return OpenApi.validationError(c, {
804
+ code: 'query_invalid',
805
+ message: 'Check the query parameters and try again.',
806
+ })
807
+
808
+ const owner = Access.owner(c)
809
+ if (!owner) return Access.unauthorized(c)
810
+
811
+ const db = Db.get(c.get('db'))
812
+ const { cursor, depositAddress, limit, recipient } = c.req.valid('query')
813
+ const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
814
+ try {
815
+ const paging = {
816
+ ...(decoded
817
+ ? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
818
+ : {}),
819
+ limit: limit + 1,
820
+ owner,
821
+ }
822
+ const rows =
823
+ depositAddress === undefined
824
+ ? await FundingDeposits.listByRecipient(db, { ...paging, recipient: recipient! })
825
+ : await FundingDeposits.listByDepositAddress(db, { ...paging, depositAddress })
826
+ const page = Cursor.paginate({
827
+ key: (record) => [record.createdAt, record.id],
828
+ limit,
829
+ rows,
830
+ })
831
+ const data = page.rows.map(FundingDeposit.toPublic)
832
+
833
+ c.header('Cache-Control', Cache.policies.noStore)
834
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
835
+ return c.json(
836
+ Response.validated(schema.listFundingDeposits.Response, {
837
+ data,
838
+ nextCursor: page.nextCursor,
839
+ }),
840
+ 200,
841
+ )
842
+ } catch (cause) {
843
+ return Response.upstream(c, cause)
844
+ }
845
+ },
846
+ )
847
+ .get(
848
+ '/v1/funding/deposits/:id{fdp_[A-Za-z0-9_-]+}',
849
+ Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
850
+ OpenApi.validate('param', schema.getFundingDeposit.Params, {
851
+ code: 'param_invalid',
852
+ message: 'Check the path parameters and try again.',
853
+ }),
854
+ OpenApi.describeRoute({
855
+ description: 'Returns one detected deposit and its delivery status.',
856
+ operationId: 'getFundingDeposit',
857
+ responses: OpenApi.responses({
858
+ errors: {
859
+ 400: { codes: ['param_invalid'] },
860
+ 404: {
861
+ codes: ['funding_deposit_not_found'],
862
+ description: 'No visible funding deposit exists for the identifier.',
863
+ },
864
+ },
865
+ success: {
866
+ description: 'The requested funding deposit.',
867
+ example: fundingDepositExample,
868
+ schema: schema.getFundingDeposit.Response,
869
+ },
870
+ }),
871
+ summary: 'Get deposit',
872
+ tags: ['Deposit Addresses'],
873
+ }),
874
+ fundingEtag,
875
+ async (c) => {
876
+ if (Auth.narrowAccess) return Auth.accessError(c)
877
+ if (OpenApi.narrowValidation)
878
+ return OpenApi.validationError(c, {
879
+ code: 'param_invalid',
880
+ message: 'Check the path parameters and try again.',
881
+ })
882
+
883
+ const owner = Access.owner(c)
884
+ if (!owner) return Access.unauthorized(c)
885
+
886
+ const db = Db.get(c.get('db'))
887
+ const { id } = c.req.valid('param')
888
+ try {
889
+ const record = await FundingDeposits.getOwned(db, owner, id)
890
+ if (!record)
891
+ return Response.error(c, {
892
+ code: 'funding_deposit_not_found',
893
+ message: 'Funding deposit not found',
894
+ status: 404,
895
+ })
896
+
897
+ c.header('Cache-Control', Cache.policies.state)
898
+ c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key')
899
+ return c.json(
900
+ Response.validated(schema.getFundingDeposit.Response, FundingDeposit.toPublic(record)),
901
+ 200,
902
+ )
903
+ } catch (cause) {
904
+ return Response.upstream(c, cause)
905
+ }
906
+ },
907
+ )
908
+ }
909
+
910
+ export declare namespace depositAddresses {
911
+ /** Deposit-address route dependencies. */
912
+ type Options = {
913
+ /** Enqueues one deposit address for asynchronous reconciliation. */
914
+ dispatchReconciliation?:
915
+ | ((message: FundingProvider.Webhook.receive.Dispatchable) => Promise<void>)
916
+ | undefined
917
+ /** Registers a created address with a source-chain observer. */
918
+ registerDepositAddress?: ((address: FundingDepositAddress.Public) => Promise<void>) | undefined
919
+ }
920
+ }
921
+
922
+ function depositAddressNotAvailable(c: Context<App.Environment>) {
923
+ return Response.error(c, {
924
+ code: 'deposit_address_not_available',
925
+ message: 'No provider can provision the requested reusable deposit address.',
926
+ status: 404,
927
+ })
928
+ }
929
+
930
+ class IdempotencyLeaseLostError extends Error {
931
+ override name = 'Funding.IdempotencyLeaseLostError'
932
+ }