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