tapimo 0.15.1 → 0.18.1

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 (709) hide show
  1. package/README.md +137 -0
  2. package/dist/ApiKey.d.ts +1 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +4 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +37 -11
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +163 -10
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +13085 -11974
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +46 -15
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +1 -1
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +1 -1
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/admin/App.d.ts +893 -191
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/App.js +5 -1
  21. package/dist/admin/App.js.map +1 -1
  22. package/dist/admin/apps/api-keys.d.ts +70 -70
  23. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.js +8 -3
  25. package/dist/admin/apps/api-keys.js.map +1 -1
  26. package/dist/admin/apps/earn-vaults.d.ts +66 -27
  27. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  28. package/dist/admin/apps/earn-vaults.js +36 -7
  29. package/dist/admin/apps/earn-vaults.js.map +1 -1
  30. package/dist/admin/apps/organizations.d.ts +25 -25
  31. package/dist/admin/apps/organizations.d.ts.map +1 -1
  32. package/dist/admin/apps/organizations.js +5 -4
  33. package/dist/admin/apps/organizations.js.map +1 -1
  34. package/dist/admin/apps/route-policies.d.ts +632 -0
  35. package/dist/admin/apps/route-policies.d.ts.map +1 -0
  36. package/dist/admin/apps/route-policies.js +257 -0
  37. package/dist/admin/apps/route-policies.js.map +1 -0
  38. package/dist/admin/apps/routes-support.js +39 -3
  39. package/dist/admin/apps/routes-support.js.map +1 -1
  40. package/dist/admin/apps/routes.d.ts +529 -76
  41. package/dist/admin/apps/routes.d.ts.map +1 -1
  42. package/dist/admin/apps/routes.js +82 -1
  43. package/dist/admin/apps/routes.js.map +1 -1
  44. package/dist/admin/apps/verified-tokens.d.ts +157 -157
  45. package/dist/admin/index.d.ts +0 -1
  46. package/dist/admin/index.d.ts.map +1 -1
  47. package/dist/admin/index.js +0 -1
  48. package/dist/admin/index.js.map +1 -1
  49. package/dist/admin/ui.generated.d.ts +1 -1
  50. package/dist/admin/ui.generated.d.ts.map +1 -1
  51. package/dist/admin/ui.generated.js +205 -201
  52. package/dist/admin/ui.generated.js.map +1 -1
  53. package/dist/analytics/Analytics.d.ts.map +1 -1
  54. package/dist/analytics/Analytics.js +65 -1
  55. package/dist/analytics/Analytics.js.map +1 -1
  56. package/dist/analytics/tables/requestEvents.d.ts +1 -1
  57. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  58. package/dist/analytics/tables/requestEvents.js +58 -59
  59. package/dist/analytics/tables/requestEvents.js.map +1 -1
  60. package/dist/apps/data/App.d.ts +2709 -2670
  61. package/dist/apps/data/App.d.ts.map +1 -1
  62. package/dist/apps/data/routes/activities.d.ts +357 -357
  63. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  64. package/dist/apps/data/routes/activities.js +50 -0
  65. package/dist/apps/data/routes/activities.js.map +1 -1
  66. package/dist/apps/data/routes/addresses.d.ts +248 -248
  67. package/dist/apps/data/routes/balances.d.ts +22 -22
  68. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  69. package/dist/apps/data/routes/balances.js +5 -7
  70. package/dist/apps/data/routes/balances.js.map +1 -1
  71. package/dist/apps/data/routes/blocks.d.ts +125 -125
  72. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  73. package/dist/apps/data/routes/earn.d.ts +103 -12
  74. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  75. package/dist/apps/data/routes/earn.js +456 -220
  76. package/dist/apps/data/routes/earn.js.map +1 -1
  77. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  78. package/dist/apps/data/routes/exchanges.js +267 -97
  79. package/dist/apps/data/routes/exchanges.js.map +1 -1
  80. package/dist/apps/data/routes/fee-amm.d.ts +109 -127
  81. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  82. package/dist/apps/data/routes/fee-amm.js +83 -54
  83. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  84. package/dist/apps/data/routes/indexer.d.ts +43 -43
  85. package/dist/apps/data/routes/receipts.d.ts +235 -235
  86. package/dist/apps/data/routes/rpc.d.ts +30 -30
  87. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  88. package/dist/apps/data/routes/tokens.js +11 -6
  89. package/dist/apps/data/routes/tokens.js.map +1 -1
  90. package/dist/apps/data/routes/transactions.d.ts +2 -0
  91. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  92. package/dist/apps/data/routes/transactions.js +30 -22
  93. package/dist/apps/data/routes/transactions.js.map +1 -1
  94. package/dist/apps/data/routes/transfers.d.ts +77 -77
  95. package/dist/apps/data/routes/valuation.d.ts +32 -32
  96. package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
  97. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  98. package/dist/apps/data/routes/webhooks.js +1 -1
  99. package/dist/apps/data/routes/webhooks.js.map +1 -1
  100. package/dist/apps/data/routes/zones.d.ts +31 -31
  101. package/dist/apps/management/App.d.ts +2909 -2064
  102. package/dist/apps/management/App.d.ts.map +1 -1
  103. package/dist/apps/management/App.js +2 -0
  104. package/dist/apps/management/App.js.map +1 -1
  105. package/dist/apps/management/Billing.d.ts +2 -4
  106. package/dist/apps/management/Billing.d.ts.map +1 -1
  107. package/dist/apps/management/Billing.js +65 -26
  108. package/dist/apps/management/Billing.js.map +1 -1
  109. package/dist/apps/management/routes/api-keys.d.ts +257 -245
  110. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  111. package/dist/apps/management/routes/api-keys.js +70 -11
  112. package/dist/apps/management/routes/api-keys.js.map +1 -1
  113. package/dist/apps/management/routes/billing.d.ts +84 -84
  114. package/dist/apps/management/routes/cli.d.ts +912 -0
  115. package/dist/apps/management/routes/cli.d.ts.map +1 -0
  116. package/dist/apps/management/routes/cli.js +416 -0
  117. package/dist/apps/management/routes/cli.js.map +1 -0
  118. package/dist/apps/management/routes/faucet.d.ts +8 -8
  119. package/dist/apps/management/routes/invitations.d.ts +138 -138
  120. package/dist/apps/management/routes/invite-links.d.ts +172 -172
  121. package/dist/apps/management/routes/members.d.ts +16 -16
  122. package/dist/apps/management/routes/orgs.d.ts +104 -104
  123. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  124. package/dist/apps/management/routes/orgs.js +8 -2
  125. package/dist/apps/management/routes/orgs.js.map +1 -1
  126. package/dist/apps/management/routes/projects.d.ts +125 -125
  127. package/dist/apps/management/routes/usage.d.ts +8 -8
  128. package/dist/apps/management/routes/verified-token-requests.d.ts +86 -86
  129. package/dist/apps/management/routes/webhooks.d.ts +485 -485
  130. package/dist/apps/mpp/App.d.ts +44 -44
  131. package/dist/apps/mpp/internal/Policy.d.ts.map +1 -1
  132. package/dist/apps/mpp/internal/Policy.js +4 -2
  133. package/dist/apps/mpp/internal/Policy.js.map +1 -1
  134. package/dist/apps/mpp/internal/Sponsorship.d.ts.map +1 -1
  135. package/dist/apps/mpp/internal/Sponsorship.js +6 -4
  136. package/dist/apps/mpp/internal/Sponsorship.js.map +1 -1
  137. package/dist/apps/relay/App.d.ts +8 -0
  138. package/dist/apps/relay/App.d.ts.map +1 -1
  139. package/dist/apps/relay/App.js +16 -4
  140. package/dist/apps/relay/App.js.map +1 -1
  141. package/dist/apps/relay/Sponsorships.d.ts +4 -6
  142. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  143. package/dist/apps/relay/Sponsorships.js +71 -60
  144. package/dist/apps/relay/Sponsorships.js.map +1 -1
  145. package/dist/apps/routes/Access.d.ts +53 -0
  146. package/dist/apps/routes/Access.d.ts.map +1 -1
  147. package/dist/apps/routes/Access.js +121 -0
  148. package/dist/apps/routes/Access.js.map +1 -1
  149. package/dist/apps/routes/App.d.ts +397 -158
  150. package/dist/apps/routes/App.d.ts.map +1 -1
  151. package/dist/apps/routes/App.js.map +1 -1
  152. package/dist/apps/routes/routes/chains.d.ts +25 -25
  153. package/dist/apps/routes/routes/deposit-addresses.d.ts +169 -61
  154. package/dist/apps/routes/routes/deposit-addresses.d.ts.map +1 -1
  155. package/dist/apps/routes/routes/deposit-addresses.js +217 -32
  156. package/dist/apps/routes/routes/deposit-addresses.js.map +1 -1
  157. package/dist/apps/routes/routes/quotes.d.ts +171 -81
  158. package/dist/apps/routes/routes/quotes.d.ts.map +1 -1
  159. package/dist/apps/routes/routes/quotes.js +101 -27
  160. package/dist/apps/routes/routes/quotes.js.map +1 -1
  161. package/dist/apps/routes/routes/transfers.d.ts +532 -480
  162. package/dist/apps/routes/routes/transfers.d.ts.map +1 -1
  163. package/dist/apps/routes/routes/transfers.js +182 -37
  164. package/dist/apps/routes/routes/transfers.js.map +1 -1
  165. package/dist/cli/ApiKeys.d.ts +120 -0
  166. package/dist/cli/ApiKeys.d.ts.map +1 -0
  167. package/dist/cli/ApiKeys.js +138 -0
  168. package/dist/cli/ApiKeys.js.map +1 -0
  169. package/dist/cli/Cli.d.ts +92 -34
  170. package/dist/cli/Cli.d.ts.map +1 -1
  171. package/dist/cli/Cli.js +188 -11
  172. package/dist/cli/Cli.js.map +1 -1
  173. package/dist/cli/Credentials.d.ts +95 -0
  174. package/dist/cli/Credentials.d.ts.map +1 -0
  175. package/dist/cli/Credentials.js +110 -0
  176. package/dist/cli/Credentials.js.map +1 -0
  177. package/dist/cli/Spec.d.ts +59 -0
  178. package/dist/cli/Spec.d.ts.map +1 -0
  179. package/dist/cli/Spec.js +77 -0
  180. package/dist/cli/Spec.js.map +1 -0
  181. package/dist/cli/bin.js +5 -1
  182. package/dist/cli/bin.js.map +1 -1
  183. package/dist/cli/commands/admin.d.ts +5 -5
  184. package/dist/cli/commands/admin.d.ts.map +1 -1
  185. package/dist/cli/commands/auth.d.ts +73 -0
  186. package/dist/cli/commands/auth.d.ts.map +1 -0
  187. package/dist/cli/commands/auth.js +204 -0
  188. package/dist/cli/commands/auth.js.map +1 -0
  189. package/dist/cli/commands/keys.d.ts +17 -0
  190. package/dist/cli/commands/keys.d.ts.map +1 -0
  191. package/dist/cli/commands/keys.js +109 -0
  192. package/dist/cli/commands/keys.js.map +1 -0
  193. package/dist/cloudflare.d.ts +27 -2
  194. package/dist/cloudflare.d.ts.map +1 -1
  195. package/dist/cloudflare.js +144 -17
  196. package/dist/cloudflare.js.map +1 -1
  197. package/dist/db/Db.d.ts +23 -0
  198. package/dist/db/Db.d.ts.map +1 -1
  199. package/dist/db/Db.js +413 -1
  200. package/dist/db/Db.js.map +1 -1
  201. package/dist/db/Schema.d.ts +44 -1
  202. package/dist/db/Schema.d.ts.map +1 -1
  203. package/dist/db/Schema.js +56 -3
  204. package/dist/db/Schema.js.map +1 -1
  205. package/dist/db/tables/apiKeyAdmissions.d.ts +28 -2
  206. package/dist/db/tables/apiKeyAdmissions.d.ts.map +1 -1
  207. package/dist/db/tables/apiKeyAdmissions.js +65 -9
  208. package/dist/db/tables/apiKeyAdmissions.js.map +1 -1
  209. package/dist/db/tables/apiKeyOwnerTombstones.d.ts +2 -0
  210. package/dist/db/tables/apiKeyOwnerTombstones.d.ts.map +1 -1
  211. package/dist/db/tables/apiKeyOwnerTombstones.js +2 -2
  212. package/dist/db/tables/apiKeyOwnerTombstones.js.map +1 -1
  213. package/dist/db/tables/authAccounts.d.ts.map +1 -1
  214. package/dist/db/tables/authAccounts.js +1 -0
  215. package/dist/db/tables/authAccounts.js.map +1 -1
  216. package/dist/db/tables/earnVaults.d.ts +5 -1
  217. package/dist/db/tables/earnVaults.d.ts.map +1 -1
  218. package/dist/db/tables/earnVaults.js +12 -1
  219. package/dist/db/tables/earnVaults.js.map +1 -1
  220. package/dist/db/tables/organizations.d.ts +4 -2
  221. package/dist/db/tables/organizations.d.ts.map +1 -1
  222. package/dist/db/tables/organizations.js +8 -34
  223. package/dist/db/tables/organizations.js.map +1 -1
  224. package/dist/db/tables/requestUsage.d.ts +5 -1
  225. package/dist/db/tables/requestUsage.d.ts.map +1 -1
  226. package/dist/db/tables/requestUsage.js +10 -2
  227. package/dist/db/tables/requestUsage.js.map +1 -1
  228. package/dist/db/tables/rewardEligibilityAssociations.d.ts +13 -0
  229. package/dist/db/tables/rewardEligibilityAssociations.d.ts.map +1 -1
  230. package/dist/db/tables/rewardEligibilityAssociations.js +11 -0
  231. package/dist/db/tables/rewardEligibilityAssociations.js.map +1 -1
  232. package/dist/db/tables/rewardRuns.d.ts.map +1 -1
  233. package/dist/db/tables/rewardRuns.js +4 -0
  234. package/dist/db/tables/rewardRuns.js.map +1 -1
  235. package/dist/db/tables/rewardTransactionAttempts.d.ts.map +1 -1
  236. package/dist/db/tables/rewardTransactionAttempts.js +2 -0
  237. package/dist/db/tables/rewardTransactionAttempts.js.map +1 -1
  238. package/dist/db/tables/routesDepositAddresses.d.ts +29 -0
  239. package/dist/db/tables/routesDepositAddresses.d.ts.map +1 -1
  240. package/dist/db/tables/routesDepositAddresses.js +84 -31
  241. package/dist/db/tables/routesDepositAddresses.js.map +1 -1
  242. package/dist/db/tables/routesDeposits.d.ts.map +1 -1
  243. package/dist/db/tables/routesDeposits.js +31 -21
  244. package/dist/db/tables/routesDeposits.js.map +1 -1
  245. package/dist/db/tables/routesIdempotency.d.ts.map +1 -1
  246. package/dist/db/tables/routesIdempotency.js +2 -0
  247. package/dist/db/tables/routesIdempotency.js.map +1 -1
  248. package/dist/db/tables/routesPolicies.d.ts +58 -0
  249. package/dist/db/tables/routesPolicies.d.ts.map +1 -0
  250. package/dist/db/tables/routesPolicies.js +198 -0
  251. package/dist/db/tables/routesPolicies.js.map +1 -0
  252. package/dist/db/tables/routesTransferSubsidies.d.ts +22 -0
  253. package/dist/db/tables/routesTransferSubsidies.d.ts.map +1 -1
  254. package/dist/db/tables/routesTransferSubsidies.js +41 -0
  255. package/dist/db/tables/routesTransferSubsidies.js.map +1 -1
  256. package/dist/db/tables/routesTransferSubsidyNonces.d.ts.map +1 -1
  257. package/dist/db/tables/routesTransferSubsidyNonces.js +12 -4
  258. package/dist/db/tables/routesTransferSubsidyNonces.js.map +1 -1
  259. package/dist/db/tables/routesTransfers.d.ts +26 -1
  260. package/dist/db/tables/routesTransfers.d.ts.map +1 -1
  261. package/dist/db/tables/routesTransfers.js +36 -6
  262. package/dist/db/tables/routesTransfers.js.map +1 -1
  263. package/dist/db/tables/sponsoredTransactions.d.ts +45 -13
  264. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  265. package/dist/db/tables/sponsoredTransactions.js +144 -33
  266. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  267. package/dist/db/tables/users.d.ts +2 -0
  268. package/dist/db/tables/users.d.ts.map +1 -1
  269. package/dist/db/tables/users.js +11 -0
  270. package/dist/db/tables/users.js.map +1 -1
  271. package/dist/handlers/internal/multisig.d.ts +1 -1
  272. package/dist/handlers/internal/multisig.d.ts.map +1 -1
  273. package/dist/handlers/internal/multisig.js +3 -2
  274. package/dist/handlers/internal/multisig.js.map +1 -1
  275. package/dist/handlers/relay.d.ts +2 -0
  276. package/dist/handlers/relay.d.ts.map +1 -1
  277. package/dist/handlers/relay.js +34 -15
  278. package/dist/handlers/relay.js.map +1 -1
  279. package/dist/internal/Auth.d.ts +57 -6
  280. package/dist/internal/Auth.d.ts.map +1 -1
  281. package/dist/internal/Auth.js +182 -78
  282. package/dist/internal/Auth.js.map +1 -1
  283. package/dist/internal/BetterAuth.d.ts +22 -0
  284. package/dist/internal/BetterAuth.d.ts.map +1 -1
  285. package/dist/internal/BetterAuth.js +17 -14
  286. package/dist/internal/BetterAuth.js.map +1 -1
  287. package/dist/internal/Cache.d.ts +5 -0
  288. package/dist/internal/Cache.d.ts.map +1 -1
  289. package/dist/internal/Cache.js +3 -1
  290. package/dist/internal/Cache.js.map +1 -1
  291. package/dist/internal/CliAuth.d.ts +243 -0
  292. package/dist/internal/CliAuth.d.ts.map +1 -0
  293. package/dist/internal/CliAuth.js +249 -0
  294. package/dist/internal/CliAuth.js.map +1 -0
  295. package/dist/internal/EarnSharePrices.d.ts +1 -1
  296. package/dist/internal/EarnSharePrices.d.ts.map +1 -1
  297. package/dist/internal/EarnSharePrices.js +7 -3
  298. package/dist/internal/EarnSharePrices.js.map +1 -1
  299. package/dist/internal/EarnVaults.d.ts +47 -0
  300. package/dist/internal/EarnVaults.d.ts.map +1 -1
  301. package/dist/internal/EarnVaults.js +188 -4
  302. package/dist/internal/EarnVaults.js.map +1 -1
  303. package/dist/internal/EarnWithdrawals.d.ts +73 -0
  304. package/dist/internal/EarnWithdrawals.d.ts.map +1 -0
  305. package/dist/internal/EarnWithdrawals.js +189 -0
  306. package/dist/internal/EarnWithdrawals.js.map +1 -0
  307. package/dist/internal/ExecutionContext.d.ts +21 -0
  308. package/dist/internal/ExecutionContext.d.ts.map +1 -0
  309. package/dist/internal/ExecutionContext.js +16 -0
  310. package/dist/internal/ExecutionContext.js.map +1 -0
  311. package/dist/internal/Log.d.ts +7 -3
  312. package/dist/internal/Log.d.ts.map +1 -1
  313. package/dist/internal/Log.js +163 -130
  314. package/dist/internal/Log.js.map +1 -1
  315. package/dist/internal/MetricSink.d.ts.map +1 -1
  316. package/dist/internal/MetricSink.js +26 -1
  317. package/dist/internal/MetricSink.js.map +1 -1
  318. package/dist/internal/RateLimit.d.ts +67 -0
  319. package/dist/internal/RateLimit.d.ts.map +1 -1
  320. package/dist/internal/RateLimit.js +174 -27
  321. package/dist/internal/RateLimit.js.map +1 -1
  322. package/dist/internal/Store.d.ts +6 -0
  323. package/dist/internal/Store.d.ts.map +1 -1
  324. package/dist/internal/Store.js +4 -5
  325. package/dist/internal/Store.js.map +1 -1
  326. package/dist/internal/Timing.d.ts +6 -0
  327. package/dist/internal/Timing.d.ts.map +1 -1
  328. package/dist/internal/Timing.js +21 -1
  329. package/dist/internal/Timing.js.map +1 -1
  330. package/dist/internal/Tron.d.ts +124 -0
  331. package/dist/internal/Tron.d.ts.map +1 -0
  332. package/dist/internal/Tron.js +202 -0
  333. package/dist/internal/Tron.js.map +1 -0
  334. package/dist/internal/Viem.d.ts +4 -0
  335. package/dist/internal/Viem.d.ts.map +1 -1
  336. package/dist/internal/Viem.js +25 -6
  337. package/dist/internal/Viem.js.map +1 -1
  338. package/dist/internal/rewards/Read.d.ts +2 -2
  339. package/dist/internal/rewards/Read.d.ts.map +1 -1
  340. package/dist/internal/rewards/Read.js +11 -2
  341. package/dist/internal/rewards/Read.js.map +1 -1
  342. package/dist/internal/routes/Action.d.ts +27 -0
  343. package/dist/internal/routes/Action.d.ts.map +1 -1
  344. package/dist/internal/routes/Action.js +49 -1
  345. package/dist/internal/routes/Action.js.map +1 -1
  346. package/dist/internal/routes/Chain.d.ts +2 -0
  347. package/dist/internal/routes/Chain.d.ts.map +1 -1
  348. package/dist/internal/routes/Chain.js +4 -0
  349. package/dist/internal/routes/Chain.js.map +1 -1
  350. package/dist/internal/routes/DepositAddress.d.ts.map +1 -1
  351. package/dist/internal/routes/DepositAddress.js +5 -0
  352. package/dist/internal/routes/DepositAddress.js.map +1 -1
  353. package/dist/internal/routes/Evidence.d.ts.map +1 -1
  354. package/dist/internal/routes/Evidence.js +6 -2
  355. package/dist/internal/routes/Evidence.js.map +1 -1
  356. package/dist/internal/routes/EvmTransferSettlement.d.ts +17 -0
  357. package/dist/internal/routes/EvmTransferSettlement.d.ts.map +1 -0
  358. package/dist/internal/routes/EvmTransferSettlement.js +196 -0
  359. package/dist/internal/routes/EvmTransferSettlement.js.map +1 -0
  360. package/dist/internal/routes/Policy.d.ts +161 -0
  361. package/dist/internal/routes/Policy.d.ts.map +1 -0
  362. package/dist/internal/routes/Policy.js +249 -0
  363. package/dist/internal/routes/Policy.js.map +1 -0
  364. package/dist/internal/routes/Provider.d.ts +66 -3
  365. package/dist/internal/routes/Provider.d.ts.map +1 -1
  366. package/dist/internal/routes/Provider.js +106 -27
  367. package/dist/internal/routes/Provider.js.map +1 -1
  368. package/dist/internal/routes/Quota.d.ts +57 -0
  369. package/dist/internal/routes/Quota.d.ts.map +1 -0
  370. package/dist/internal/routes/Quota.js +144 -0
  371. package/dist/internal/routes/Quota.js.map +1 -0
  372. package/dist/internal/routes/Reconciliation.d.ts.map +1 -1
  373. package/dist/internal/routes/Reconciliation.js +163 -47
  374. package/dist/internal/routes/Reconciliation.js.map +1 -1
  375. package/dist/internal/routes/SolanaTransferSettlement.d.ts +12 -0
  376. package/dist/internal/routes/SolanaTransferSettlement.d.ts.map +1 -0
  377. package/dist/internal/routes/SolanaTransferSettlement.js +220 -0
  378. package/dist/internal/routes/SolanaTransferSettlement.js.map +1 -0
  379. package/dist/internal/routes/SolanaTransferSubsidy.d.ts +172 -0
  380. package/dist/internal/routes/SolanaTransferSubsidy.d.ts.map +1 -0
  381. package/dist/internal/routes/SolanaTransferSubsidy.js +564 -0
  382. package/dist/internal/routes/SolanaTransferSubsidy.js.map +1 -0
  383. package/dist/internal/routes/Subsidy.d.ts +2 -2
  384. package/dist/internal/routes/Subsidy.d.ts.map +1 -1
  385. package/dist/internal/routes/Subsidy.js +2 -2
  386. package/dist/internal/routes/Subsidy.js.map +1 -1
  387. package/dist/internal/routes/Transfer.d.ts +229 -1
  388. package/dist/internal/routes/Transfer.d.ts.map +1 -1
  389. package/dist/internal/routes/Transfer.js +644 -7
  390. package/dist/internal/routes/Transfer.js.map +1 -1
  391. package/dist/internal/routes/TransferReconciliation.d.ts +6 -0
  392. package/dist/internal/routes/TransferReconciliation.d.ts.map +1 -1
  393. package/dist/internal/routes/TransferReconciliation.js +83 -159
  394. package/dist/internal/routes/TransferReconciliation.js.map +1 -1
  395. package/dist/internal/routes/TransferSettlement.d.ts +45 -0
  396. package/dist/internal/routes/TransferSettlement.d.ts.map +1 -0
  397. package/dist/internal/routes/TransferSettlement.js +2 -0
  398. package/dist/internal/routes/TransferSettlement.js.map +1 -0
  399. package/dist/internal/routes/TransferSubsidy.d.ts +23 -8
  400. package/dist/internal/routes/TransferSubsidy.d.ts.map +1 -1
  401. package/dist/internal/routes/TransferSubsidy.js +64 -5
  402. package/dist/internal/routes/TransferSubsidy.js.map +1 -1
  403. package/dist/internal/routes/TronTransferSettlement.d.ts +12 -0
  404. package/dist/internal/routes/TronTransferSettlement.d.ts.map +1 -0
  405. package/dist/internal/routes/TronTransferSettlement.js +201 -0
  406. package/dist/internal/routes/TronTransferSettlement.js.map +1 -0
  407. package/dist/internal/routes/TronTransferSubsidy.d.ts +174 -0
  408. package/dist/internal/routes/TronTransferSubsidy.d.ts.map +1 -0
  409. package/dist/internal/routes/TronTransferSubsidy.js +439 -0
  410. package/dist/internal/routes/TronTransferSubsidy.js.map +1 -0
  411. package/dist/internal/routes/index.d.ts +3 -0
  412. package/dist/internal/routes/index.d.ts.map +1 -1
  413. package/dist/internal/routes/index.js +3 -0
  414. package/dist/internal/routes/index.js.map +1 -1
  415. package/dist/internal/routes/providers/across.d.ts +3 -0
  416. package/dist/internal/routes/providers/across.d.ts.map +1 -1
  417. package/dist/internal/routes/providers/across.js +11 -0
  418. package/dist/internal/routes/providers/across.js.map +1 -1
  419. package/dist/internal/routes/providers/legacyMesh.d.ts +29 -0
  420. package/dist/internal/routes/providers/legacyMesh.d.ts.map +1 -0
  421. package/dist/internal/routes/providers/legacyMesh.js +474 -0
  422. package/dist/internal/routes/providers/legacyMesh.js.map +1 -0
  423. package/dist/internal/routes/providers/legacyMeshDelivery.d.ts +59 -0
  424. package/dist/internal/routes/providers/legacyMeshDelivery.d.ts.map +1 -0
  425. package/dist/internal/routes/providers/legacyMeshDelivery.js +229 -0
  426. package/dist/internal/routes/providers/legacyMeshDelivery.js.map +1 -0
  427. package/dist/internal/routes/providers/legacyMeshOutbound.d.ts +19 -0
  428. package/dist/internal/routes/providers/legacyMeshOutbound.d.ts.map +1 -0
  429. package/dist/internal/routes/providers/legacyMeshOutbound.js +457 -0
  430. package/dist/internal/routes/providers/legacyMeshOutbound.js.map +1 -0
  431. package/dist/internal/routes/providers/legacyMeshOutboundDelivery.d.ts +123 -0
  432. package/dist/internal/routes/providers/legacyMeshOutboundDelivery.d.ts.map +1 -0
  433. package/dist/internal/routes/providers/legacyMeshOutboundDelivery.js +333 -0
  434. package/dist/internal/routes/providers/legacyMeshOutboundDelivery.js.map +1 -0
  435. package/dist/internal/routes/providers/propAmm.d.ts +70 -0
  436. package/dist/internal/routes/providers/propAmm.d.ts.map +1 -0
  437. package/dist/internal/routes/providers/propAmm.js +524 -0
  438. package/dist/internal/routes/providers/propAmm.js.map +1 -0
  439. package/dist/internal/routes/providers/relay.d.ts +3 -0
  440. package/dist/internal/routes/providers/relay.d.ts.map +1 -1
  441. package/dist/internal/routes/providers/relay.js +352 -41
  442. package/dist/internal/routes/providers/relay.js.map +1 -1
  443. package/dist/internal/routes/providers/rhino.d.ts +3 -0
  444. package/dist/internal/routes/providers/rhino.d.ts.map +1 -1
  445. package/dist/internal/routes/providers/rhino.js +11 -0
  446. package/dist/internal/routes/providers/rhino.js.map +1 -1
  447. package/dist/internal/routes/providers/squid.d.ts +3 -0
  448. package/dist/internal/routes/providers/squid.d.ts.map +1 -1
  449. package/dist/internal/routes/providers/squid.js +19 -0
  450. package/dist/internal/routes/providers/squid.js.map +1 -1
  451. package/dist/internal/routes/providers/stargate.d.ts.map +1 -1
  452. package/dist/internal/routes/providers/stargate.js +22 -9
  453. package/dist/internal/routes/providers/stargate.js.map +1 -1
  454. package/dist/internal/routes/providers/symbiosis.d.ts.map +1 -1
  455. package/dist/internal/routes/providers/symbiosis.js +13 -17
  456. package/dist/internal/routes/providers/symbiosis.js.map +1 -1
  457. package/dist/providers/index.d.ts +14 -0
  458. package/dist/providers/index.d.ts.map +1 -1
  459. package/dist/providers/index.js +6 -0
  460. package/dist/providers/index.js.map +1 -1
  461. package/package.json +16 -6
  462. package/src/ApiKey.ts +7 -3
  463. package/src/ApiKeys.test.ts +652 -8
  464. package/src/ApiKeys.ts +191 -11
  465. package/src/App.test.ts +405 -18
  466. package/src/App.ts +51 -15
  467. package/src/Client.test-d.ts +157 -10
  468. package/src/Client.test.ts +4 -4
  469. package/src/Scope.ts +1 -2
  470. package/src/admin/App.ts +11 -5
  471. package/src/admin/apps/api-keys.test.ts +98 -2
  472. package/src/admin/apps/api-keys.ts +21 -12
  473. package/src/admin/apps/earn-vaults.test.ts +76 -7
  474. package/src/admin/apps/earn-vaults.ts +47 -8
  475. package/src/admin/apps/organizations.test.ts +147 -31
  476. package/src/admin/apps/organizations.ts +5 -4
  477. package/src/admin/apps/route-policies.test.ts +220 -0
  478. package/src/admin/apps/route-policies.ts +322 -0
  479. package/src/admin/apps/routes-support.test.ts +107 -2
  480. package/src/admin/apps/routes-support.ts +43 -3
  481. package/src/admin/apps/routes.test.ts +150 -0
  482. package/src/admin/apps/routes.ts +97 -1
  483. package/src/admin/apps/scopes.test.ts +1 -1
  484. package/src/admin/index.ts +0 -1
  485. package/src/admin/ui/plugin.ts +2 -1
  486. package/src/admin/ui/src/components/Dialog.tsx +1 -1
  487. package/src/admin/ui/src/components/EarnVaultForm.tsx +45 -0
  488. package/src/admin/ui/src/components/RoutePicker.tsx +79 -0
  489. package/src/admin/ui/src/lib/earn-vaults.ts +1 -1
  490. package/src/admin/ui/src/lib/route-policies.test.ts +452 -0
  491. package/src/admin/ui/src/lib/route-policies.ts +232 -0
  492. package/src/admin/ui/src/lib/routes.ts +34 -0
  493. package/src/admin/ui/src/routeTree.gen.ts +21 -0
  494. package/src/admin/ui/src/routes/api-keys.tsx +21 -2
  495. package/src/admin/ui/src/routes/early-access.tsx +1 -0
  496. package/src/admin/ui/src/routes/earn.vaults.tsx +1 -0
  497. package/src/admin/ui/src/routes/earn.vaults_.$vaultId.edit.tsx +22 -0
  498. package/src/admin/ui/src/routes/earn.vaults_.new.tsx +10 -0
  499. package/src/admin/ui/src/routes/organizations.tsx +1 -0
  500. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +18 -3
  501. package/src/admin/ui/src/routes/route-policies.tsx +678 -0
  502. package/src/admin/ui/src/routes/routes.tsx +85 -2
  503. package/src/admin/ui/src/routes/routes_.support.tsx +6 -22
  504. package/src/admin/ui/src/routes/subsidies.tsx +1 -0
  505. package/src/admin/ui/src/routes/verified-tokens.tsx +1 -0
  506. package/src/admin/ui.generated.ts +258 -202
  507. package/src/analytics/Analytics.test.ts +4 -50
  508. package/src/analytics/Analytics.ts +65 -1
  509. package/src/analytics/tables/requestEvents.test.ts +402 -1
  510. package/src/analytics/tables/requestEvents.ts +62 -93
  511. package/src/apps/data/routes/activities.test.ts +315 -14
  512. package/src/apps/data/routes/activities.ts +60 -0
  513. package/src/apps/data/routes/balances.ts +5 -7
  514. package/src/apps/data/routes/earn.bench.ts +18 -0
  515. package/src/apps/data/routes/earn.local.test.ts +507 -0
  516. package/src/apps/data/routes/earn.test.ts +405 -127
  517. package/src/apps/data/routes/earn.ts +660 -270
  518. package/src/apps/data/routes/exchanges.bench.ts +3 -1
  519. package/src/apps/data/routes/exchanges.test.ts +166 -2
  520. package/src/apps/data/routes/exchanges.ts +308 -101
  521. package/src/apps/data/routes/fee-amm.test.ts +134 -2
  522. package/src/apps/data/routes/fee-amm.ts +122 -60
  523. package/src/apps/data/routes/receipts.test.ts +21 -18
  524. package/src/apps/data/routes/rpc.test.ts +2 -2
  525. package/src/apps/data/routes/tokens.provenance.test.ts +157 -0
  526. package/src/apps/data/routes/tokens.test.ts +4 -4
  527. package/src/apps/data/routes/tokens.ts +11 -6
  528. package/src/apps/data/routes/transactions.test.ts +29 -0
  529. package/src/apps/data/routes/transactions.ts +33 -22
  530. package/src/apps/data/routes/webhooks.test.ts +12 -3
  531. package/src/apps/data/routes/webhooks.ts +2 -1
  532. package/src/apps/management/App.ts +4 -0
  533. package/src/apps/management/Billing.test.ts +253 -27
  534. package/src/apps/management/Billing.ts +83 -29
  535. package/src/apps/management/routes/api-keys.test.ts +1 -1
  536. package/src/apps/management/routes/api-keys.ts +85 -11
  537. package/src/apps/management/routes/billing.test.ts +68 -4
  538. package/src/apps/management/routes/cli.test.ts +370 -0
  539. package/src/apps/management/routes/cli.ts +533 -0
  540. package/src/apps/management/routes/me.test.ts +14 -4
  541. package/src/apps/management/routes/orgs.test.ts +108 -4
  542. package/src/apps/management/routes/orgs.ts +7 -2
  543. package/src/apps/management/routes/usage.test.ts +2 -2
  544. package/src/apps/mpp/internal/Policy.test.ts +51 -4
  545. package/src/apps/mpp/internal/Policy.ts +6 -2
  546. package/src/apps/mpp/internal/Sponsorship.test.ts +29 -0
  547. package/src/apps/mpp/internal/Sponsorship.ts +6 -4
  548. package/src/apps/relay/App.test.ts +85 -1
  549. package/src/apps/relay/App.ts +28 -4
  550. package/src/apps/relay/Sponsorships.test.ts +97 -1
  551. package/src/apps/relay/Sponsorships.ts +77 -66
  552. package/src/apps/routes/Access.test.ts +319 -0
  553. package/src/apps/routes/Access.ts +147 -0
  554. package/src/apps/routes/App.ts +10 -0
  555. package/src/apps/routes/routes/chains.test.ts +3 -2
  556. package/src/apps/routes/routes/deposit-addresses.test.ts +978 -34
  557. package/src/apps/routes/routes/deposit-addresses.ts +243 -32
  558. package/src/apps/routes/routes/propAmm.local.test.ts +951 -0
  559. package/src/apps/routes/routes/quotes.test.ts +81 -8
  560. package/src/apps/routes/routes/quotes.ts +109 -27
  561. package/src/apps/routes/routes/transfers.test.ts +252 -15
  562. package/src/apps/routes/routes/transfers.ts +212 -33
  563. package/src/cli/ApiKeys.test.ts +265 -0
  564. package/src/cli/ApiKeys.ts +189 -0
  565. package/src/cli/Cli.test.ts +188 -0
  566. package/src/cli/Cli.ts +280 -12
  567. package/src/cli/Credentials.test.ts +122 -0
  568. package/src/cli/Credentials.ts +144 -0
  569. package/src/cli/Spec.test.ts +133 -0
  570. package/src/cli/Spec.ts +132 -0
  571. package/src/cli/bin.test.ts +49 -0
  572. package/src/cli/bin.ts +5 -1
  573. package/src/cli/commands/auth.test.ts +138 -0
  574. package/src/cli/commands/auth.ts +254 -0
  575. package/src/cli/commands/keys.test.ts +68 -0
  576. package/src/cli/commands/keys.ts +125 -0
  577. package/src/cloudflare.ts +184 -36
  578. package/src/db/Db.test.ts +877 -0
  579. package/src/db/Db.ts +498 -1
  580. package/src/db/Schema.ts +75 -4
  581. package/src/db/tables/apiKeyAdmissions.test.ts +57 -0
  582. package/src/db/tables/apiKeyAdmissions.ts +101 -11
  583. package/src/db/tables/apiKeyOwnerTombstones.ts +2 -2
  584. package/src/db/tables/authAccounts.test.ts +30 -0
  585. package/src/db/tables/authAccounts.ts +1 -0
  586. package/src/db/tables/earnVaults.test.ts +34 -0
  587. package/src/db/tables/earnVaults.ts +17 -2
  588. package/src/db/tables/organizations.test.ts +148 -84
  589. package/src/db/tables/organizations.ts +11 -43
  590. package/src/db/tables/requestUsage.ts +13 -2
  591. package/src/db/tables/rewardCampaigns.test.ts +163 -0
  592. package/src/db/tables/rewardEligibilityAssociations.test.ts +14 -0
  593. package/src/db/tables/rewardEligibilityAssociations.ts +24 -0
  594. package/src/db/tables/rewardRuns.ts +4 -0
  595. package/src/db/tables/rewardTransactionAttempts.ts +2 -0
  596. package/src/db/tables/routesDepositAddresses.test.ts +263 -0
  597. package/src/db/tables/routesDepositAddresses.ts +118 -34
  598. package/src/db/tables/routesDeposits.test.ts +66 -5
  599. package/src/db/tables/routesDeposits.ts +47 -26
  600. package/src/db/tables/routesIdempotency.test.ts +80 -0
  601. package/src/db/tables/routesIdempotency.ts +2 -0
  602. package/src/db/tables/routesPolicies.test.ts +143 -0
  603. package/src/db/tables/routesPolicies.ts +251 -0
  604. package/src/db/tables/routesTransferSubsidies.ts +64 -0
  605. package/src/db/tables/routesTransferSubsidyNonces.ts +16 -4
  606. package/src/db/tables/routesTransfers.test.ts +183 -2
  607. package/src/db/tables/routesTransfers.ts +65 -6
  608. package/src/db/tables/sponsoredTransactions.test.ts +351 -76
  609. package/src/db/tables/sponsoredTransactions.ts +182 -38
  610. package/src/db/tables/users.test.ts +51 -0
  611. package/src/db/tables/users.ts +12 -0
  612. package/src/handlers/internal/multisig.store.test.ts +120 -0
  613. package/src/handlers/internal/multisig.test.ts +389 -0
  614. package/src/handlers/internal/multisig.ts +13 -10
  615. package/src/handlers/relay.test.ts +259 -83
  616. package/src/handlers/relay.ts +36 -17
  617. package/src/internal/Auth.test.ts +291 -8
  618. package/src/internal/Auth.ts +227 -96
  619. package/src/internal/BetterAuth.test.ts +58 -22
  620. package/src/internal/BetterAuth.ts +23 -17
  621. package/src/internal/Cache.ts +3 -1
  622. package/src/internal/CliAuth.test.ts +279 -0
  623. package/src/internal/CliAuth.ts +319 -0
  624. package/src/internal/EarnSharePrices.test.ts +25 -0
  625. package/src/internal/EarnSharePrices.ts +9 -2
  626. package/src/internal/EarnVaults.test.ts +67 -0
  627. package/src/internal/EarnVaults.ts +299 -4
  628. package/src/internal/EarnWithdrawals.test.ts +315 -0
  629. package/src/internal/EarnWithdrawals.ts +266 -0
  630. package/src/internal/ExecutionContext.ts +31 -0
  631. package/src/internal/Log.test.ts +210 -0
  632. package/src/internal/Log.ts +180 -142
  633. package/src/internal/MetricSink.test.ts +190 -15
  634. package/src/internal/MetricSink.ts +27 -1
  635. package/src/internal/RateLimit.test.ts +144 -8
  636. package/src/internal/RateLimit.ts +254 -26
  637. package/src/internal/Store.ts +4 -5
  638. package/src/internal/Timing.test.ts +32 -0
  639. package/src/internal/Timing.ts +20 -1
  640. package/src/internal/Tron.test-d.ts +22 -0
  641. package/src/internal/Tron.test.ts +94 -0
  642. package/src/internal/Tron.ts +289 -0
  643. package/src/internal/Viem.rpc.test.ts +76 -0
  644. package/src/internal/Viem.ts +29 -6
  645. package/src/internal/rewards/Read.test.ts +109 -0
  646. package/src/internal/rewards/Read.ts +13 -4
  647. package/src/internal/routes/Action.test.ts +70 -0
  648. package/src/internal/routes/Action.ts +76 -1
  649. package/src/internal/routes/Chain.test.ts +18 -0
  650. package/src/internal/routes/Chain.ts +5 -0
  651. package/src/internal/routes/DepositAddress.ts +5 -0
  652. package/src/internal/routes/Evidence.test.ts +64 -0
  653. package/src/internal/routes/Evidence.ts +11 -3
  654. package/src/internal/routes/EvmTransferSettlement.ts +213 -0
  655. package/src/internal/routes/Policy.test.ts +316 -0
  656. package/src/internal/routes/Policy.ts +399 -0
  657. package/src/internal/routes/Provider.test.ts +103 -2
  658. package/src/internal/routes/Provider.ts +149 -27
  659. package/src/internal/routes/Quota.test.ts +149 -0
  660. package/src/internal/routes/Quota.ts +226 -0
  661. package/src/internal/routes/Reconciliation.local.test.ts +285 -0
  662. package/src/internal/routes/Reconciliation.test.ts +371 -13
  663. package/src/internal/routes/Reconciliation.ts +193 -42
  664. package/src/internal/routes/SolanaTransferSettlement.ts +232 -0
  665. package/src/internal/routes/SolanaTransferSubsidy.test.ts +471 -0
  666. package/src/internal/routes/SolanaTransferSubsidy.ts +760 -0
  667. package/src/internal/routes/SourceObservation.test.ts +221 -0
  668. package/src/internal/routes/Subsidy.local.test.ts +46 -0
  669. package/src/internal/routes/Subsidy.ts +4 -2
  670. package/src/internal/routes/Transfer.solana.test.ts +389 -0
  671. package/src/internal/routes/Transfer.test.ts +130 -1
  672. package/src/internal/routes/Transfer.tron.test.ts +172 -0
  673. package/src/internal/routes/Transfer.ts +866 -8
  674. package/src/internal/routes/TransferReconciliation.test.ts +26 -4
  675. package/src/internal/routes/TransferReconciliation.ts +91 -166
  676. package/src/internal/routes/TransferSettlement.ts +48 -0
  677. package/src/internal/routes/TransferSubsidy.local.test.ts +143 -0
  678. package/src/internal/routes/TransferSubsidy.test.ts +34 -4
  679. package/src/internal/routes/TransferSubsidy.ts +87 -12
  680. package/src/internal/routes/TronTransferSettlement.ts +212 -0
  681. package/src/internal/routes/TronTransferSubsidy.test.ts +109 -0
  682. package/src/internal/routes/TronTransferSubsidy.ts +619 -0
  683. package/src/internal/routes/index.test.ts +3 -0
  684. package/src/internal/routes/index.ts +3 -0
  685. package/src/internal/routes/providers/across.ts +13 -0
  686. package/src/internal/routes/providers/index.test.ts +141 -0
  687. package/src/internal/routes/providers/legacyMesh.test.ts +484 -0
  688. package/src/internal/routes/providers/legacyMesh.ts +633 -0
  689. package/src/internal/routes/providers/legacyMeshDelivery.test.ts +306 -0
  690. package/src/internal/routes/providers/legacyMeshDelivery.ts +320 -0
  691. package/src/internal/routes/providers/legacyMeshOutbound.test.ts +401 -0
  692. package/src/internal/routes/providers/legacyMeshOutbound.ts +566 -0
  693. package/src/internal/routes/providers/legacyMeshOutboundDelivery.ts +473 -0
  694. package/src/internal/routes/providers/propAmm.ts +640 -0
  695. package/src/internal/routes/providers/relay.test.ts +1263 -41
  696. package/src/internal/routes/providers/relay.ts +423 -38
  697. package/src/internal/routes/providers/rhino.ts +14 -1
  698. package/src/internal/routes/providers/squid.ts +21 -0
  699. package/src/internal/routes/providers/stargate.test.ts +105 -1
  700. package/src/internal/routes/providers/stargate.ts +22 -9
  701. package/src/internal/routes/providers/symbiosis.ts +13 -47
  702. package/src/providers/index.test.ts +2 -0
  703. package/src/providers/index.ts +18 -0
  704. package/dist/admin/Access.d.ts +0 -39
  705. package/dist/admin/Access.d.ts.map +0 -1
  706. package/dist/admin/Access.js +0 -73
  707. package/dist/admin/Access.js.map +0 -1
  708. package/src/admin/Access.test.ts +0 -130
  709. package/src/admin/Access.ts +0 -85
@@ -8,6 +8,8 @@ import * as RoutesIdempotencyTable from '../../../db/tables/routesIdempotency.js
8
8
 
9
9
  import * as Organizations from '../../../db/tables/organizations.js'
10
10
 
11
+ import * as RoutesPolicies from '../../../db/tables/routesPolicies.js'
12
+
11
13
  import * as RoutesSubsidies from '../../../db/tables/routesSubsidies.js'
12
14
 
13
15
  import * as StripeCustomers from '../../../db/tables/stripeCustomers.js'
@@ -20,6 +22,8 @@ import * as RoutesDeposit from '../../../internal/routes/Deposit.js'
20
22
 
21
23
  import * as RoutesDepositAddress from '../../../internal/routes/DepositAddress.js'
22
24
 
25
+ import * as RoutesCatalog from '../../../internal/routes/Catalog.js'
26
+
23
27
  import * as RoutesProvider from '../../../internal/routes/Provider.js'
24
28
 
25
29
  import * as Idempotency from '../../../internal/Idempotency.js'
@@ -40,6 +44,8 @@ import * as Metrics from '../../../Metrics.js'
40
44
 
41
45
  import * as TestApp from '../../../../test/App.js'
42
46
 
47
+ import * as TestRelay from '../../../../test/Relay.js'
48
+
43
49
  import * as TestRoutes from '../../../../test/Routes.js'
44
50
 
45
51
  import * as Routes from './deposit-addresses.js'
@@ -178,19 +184,21 @@ test('publishes generator-ready route deposit contracts', async () => {
178
184
  spec.components.schemas.RoutesDepositAddressNotActiveError.properties.error.properties.code
179
185
  .enum,
180
186
  addressNotAvailable:
181
- spec.components.schemas.DepositAddressNotAvailableError.properties.error.properties.code
182
- .enum,
187
+ spec.components.schemas.DepositAddressNotAvailableOrQuoteNotAvailableError.properties.error
188
+ .properties.code.enum,
183
189
  addressNotFound:
184
190
  spec.components.schemas.RoutesDepositAddressNotFoundError.properties.error.properties.code
185
191
  .enum,
186
192
  createInvalid:
187
- spec.components.schemas.ApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError.properties
188
- .error.properties.code.enum,
193
+ spec.components.schemas
194
+ .AmountTooLowOrApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError.properties.error
195
+ .properties.code.enum,
189
196
  depositNotFound:
190
197
  spec.components.schemas.RoutesDepositNotFoundError.properties.error.properties.code.enum,
191
198
  idempotencyConflict:
192
- spec.components.schemas.IdempotencyConflictOrIdempotencyInProgressError.properties.error
193
- .properties.code.enum,
199
+ spec.components.schemas
200
+ .IdempotencyConflictOrIdempotencyInProgressOrRoutePolicyChangedOrRoutePolicyIdentityConflictError
201
+ .properties.error.properties.code.enum,
194
202
  notImplemented:
195
203
  spec.components.schemas.NotImplemented501Error.properties.error.properties.code.enum,
196
204
  parametersInvalid:
@@ -287,7 +295,7 @@ test('publishes generator-ready route deposit contracts', async () => {
287
295
  "createAddress": {
288
296
  "errors": {
289
297
  "400": {
290
- "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError",
298
+ "$ref": "#/components/schemas/AmountTooLowOrApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError",
291
299
  },
292
300
  "401": {
293
301
  "$ref": "#/components/schemas/AuthenticationError",
@@ -296,10 +304,10 @@ test('publishes generator-ready route deposit contracts', async () => {
296
304
  "$ref": "#/components/schemas/CreateRoutesDepositAddressForbiddenError",
297
305
  },
298
306
  "404": {
299
- "$ref": "#/components/schemas/DepositAddressNotAvailableError",
307
+ "$ref": "#/components/schemas/DepositAddressNotAvailableOrQuoteNotAvailableError",
300
308
  },
301
309
  "409": {
302
- "$ref": "#/components/schemas/IdempotencyConflictOrIdempotencyInProgressError",
310
+ "$ref": "#/components/schemas/IdempotencyConflictOrIdempotencyInProgressOrRoutePolicyChangedOrRoutePolicyIdentityConflictError",
303
311
  },
304
312
  "429": "#/components/responses/RateLimited",
305
313
  "500": "#/components/responses/InternalError",
@@ -456,11 +464,13 @@ test('publishes generator-ready route deposit contracts', async () => {
456
464
  ],
457
465
  "addressNotAvailable": [
458
466
  "deposit_address_not_available",
467
+ "quote_not_available",
459
468
  ],
460
469
  "addressNotFound": [
461
470
  "routes_deposit_address_not_found",
462
471
  ],
463
472
  "createInvalid": [
473
+ "amount_too_low",
464
474
  "api_key_malformed",
465
475
  "body_invalid",
466
476
  "idempotency_key_required",
@@ -471,6 +481,8 @@ test('publishes generator-ready route deposit contracts', async () => {
471
481
  "idempotencyConflict": [
472
482
  "idempotency_conflict",
473
483
  "idempotency_in_progress",
484
+ "route_policy_changed",
485
+ "route_policy_identity_conflict",
474
486
  ],
475
487
  "notImplemented": [
476
488
  "not_implemented",
@@ -490,7 +502,10 @@ test('publishes generator-ready route deposit contracts', async () => {
490
502
  "billing_required",
491
503
  "forbidden",
492
504
  "routes_deposit_address_limit_exceeded",
505
+ "routes_not_allowed",
506
+ "routes_subsidy_not_allowed",
493
507
  "routes_subsidy_not_enabled",
508
+ "routes_suspended",
494
509
  ],
495
510
  },
496
511
  "statusReasons": [
@@ -574,6 +589,356 @@ describe('POST /routes/deposit-addresses', () => {
574
589
  })
575
590
  })
576
591
 
592
+ test.skipIf(!relayApiKey)(
593
+ 'creates a fresh Relay address after a cap change without changing wallets (live)',
594
+ async () => {
595
+ const key = {
596
+ id: 'key_relay_cap_replacement',
597
+ orgId: 'org_relay_cap_replacement',
598
+ scopes: ['routes:read', 'routes:write'],
599
+ token: 'secret_relay_cap_replacement',
600
+ }
601
+ await grantRoutes(key)
602
+ const scope = { environment: 'production', orgId: key.orgId } as const
603
+ // Leave room for live Relay fees while testing changes to pinned caps.
604
+ const policy = {
605
+ access: null,
606
+ maxAmount: '1',
607
+ subsidyEnabled: true,
608
+ subsidyPayer: 'tempo',
609
+ } as const
610
+ await RoutesPolicies.save(db, { policy, scope, version: 1 })
611
+ const app = create({
612
+ auth: { keys: [key] },
613
+ defaultChainId: tempoMainnet.id,
614
+ providers: [Relay.relay({ apiKey: relayApiKey, subsidies: { maxAmount: '100' } })],
615
+ routes: { depositSubsidyMaxAmount: '3' },
616
+ })
617
+ const body = {
618
+ ...depositAddressBody,
619
+ amount: '10000000',
620
+ refundAddress: `0x${'93'.repeat(20)}`,
621
+ sourceChain: 'base',
622
+ sourceToken: '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2',
623
+ subsidize: true,
624
+ }
625
+ const firstKey = nextDepositAddressKey()
626
+ const first = await TestApp.json(
627
+ await postDepositAddress(app, { body, idempotencyKey: firstKey, key }),
628
+ Routes.schema.createRoutesDepositAddress.Response,
629
+ )
630
+ const stored = await RoutesDepositAddresses.get(db, first.id)
631
+ expect(stored?.providerState).toMatchObject({
632
+ routePolicy: { maxAmount: '1', subsidyPayer: 'tempo' },
633
+ })
634
+ await RoutesPolicies.save(db, {
635
+ policy: { ...policy, maxAmount: '2' },
636
+ scope,
637
+ version: 2,
638
+ })
639
+ const replacement = await postDepositAddress(app, { body, key })
640
+ expect(replacement.status).toBe(200)
641
+ const second = await TestApp.json(
642
+ replacement,
643
+ Routes.schema.createRoutesDepositAddress.Response,
644
+ )
645
+ expect(second.address).not.toBe(first.address)
646
+ expect(second.recipient).toBe(first.recipient)
647
+ expect(second.refundAddress).toBe(first.refundAddress)
648
+ expect((await RoutesDepositAddresses.get(db, second.id))?.providerState).toMatchObject({
649
+ routePolicy: { maxAmount: '2', subsidyPayer: 'tempo' },
650
+ })
651
+ const reused = await TestApp.json(
652
+ await postDepositAddress(app, { body, key }),
653
+ Routes.schema.createRoutesDepositAddress.Response,
654
+ )
655
+ expect(reused.id).toBe(second.id)
656
+ const replay = await TestApp.json(
657
+ await postDepositAddress(app, { body, idempotencyKey: firstKey, key }),
658
+ Routes.schema.createRoutesDepositAddress.Response,
659
+ )
660
+ expect(replay.id).toBe(first.id)
661
+ expect(await RoutesDepositAddresses.get(db, first.id)).toEqual(stored)
662
+ await RoutesPolicies.save(db, { policy, scope, version: 3 })
663
+ const restored = await TestApp.json(
664
+ await postDepositAddress(app, { body, key }),
665
+ Routes.schema.createRoutesDepositAddress.Response,
666
+ )
667
+ expect(restored.id).toBe(first.id)
668
+ expect(await RoutesDepositAddresses.countByOrganization(db, key.orgId)).toBe(2)
669
+ },
670
+ )
671
+
672
+ test('keeps fixed-rate legacy Relay addresses separate from previous delivery terms', async () => {
673
+ const key = {
674
+ id: 'key_legacy_upgrade',
675
+ orgId: 'org_legacy_upgrade',
676
+ token: 'secret_legacy_upgrade',
677
+ scopes: ['routes:read', 'routes:write'],
678
+ }
679
+ const sibling = {
680
+ ...key,
681
+ id: 'key_legacy_upgrade_sibling',
682
+ token: 'secret_legacy_upgrade_sibling',
683
+ }
684
+ await enableRoutesSubsidy(key, 'active')
685
+ const scope = { orgId: key.orgId, environment: 'production' } as const
686
+ const inputs: RoutesProvider.createDepositAddress.Parameters[] = []
687
+ const counts: Parameters<Metrics.Metrics['count']>[] = []
688
+ const app = create({
689
+ auth: { keys: [key, sibling] },
690
+ defaultChainId: tempoMainnet.id,
691
+ metrics: Metrics.from({
692
+ count: (...args) => {
693
+ counts.push(args)
694
+ },
695
+ flush() {},
696
+ gauge() {},
697
+ histogram() {},
698
+ }),
699
+ providers: [
700
+ depositAddressProvider(`0x${'81'.repeat(20)}`, (input) => {
701
+ inputs.push(input)
702
+ }),
703
+ ],
704
+ routes: { depositSubsidyMaxAmount: '0.05' },
705
+ })
706
+ const body = {
707
+ ...depositAddressBody,
708
+ sourceChain: 'base',
709
+ sourceToken: '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2',
710
+ refundAddress: `0x${'82'.repeat(20)}`,
711
+ subsidize: true,
712
+ }
713
+ const idempotencyKey = nextDepositAddressKey()
714
+ const candidate = RoutesProvider.getDepositAddressCandidates({
715
+ catalog: TestRoutes.snapshot,
716
+ destinationChainId: tempoMainnet.id,
717
+ destinationToken: body.destinationToken,
718
+ providers,
719
+ sourceChain: body.sourceChain,
720
+ sourceToken: body.sourceToken,
721
+ })[0]?.candidate
722
+ if (!candidate) throw new Error('Expected a deposit-address candidate.')
723
+ const previous = await RoutesDepositAddress.create(db, {
724
+ deliveryStrategy: 'provider',
725
+ environment: 'production',
726
+ orgId: key.orgId,
727
+ providerOutputToken: candidate.destinationToken,
728
+ snapshot: {
729
+ address: `0x${'d4'.repeat(20)}`,
730
+ destinationChain: candidate.destinationChain,
731
+ destinationToken: candidate.destinationToken,
732
+ provider: { id: 'relay', name: 'Relay' },
733
+ recipient: body.recipient,
734
+ refundAddress: body.refundAddress,
735
+ sourceChain: candidate.sourceChain,
736
+ sourceToken: candidate.sourceToken,
737
+ subsidize: true,
738
+ },
739
+ })
740
+ const first = await TestApp.json(
741
+ await postDepositAddress(app, { body, key, idempotencyKey }),
742
+ Routes.schema.createRoutesDepositAddress.Response,
743
+ )
744
+ const claim = await RoutesDepositAddresses.claim(db, {
745
+ id: first.id,
746
+ leaseUntil: '2027-01-01T00:01:00.000Z',
747
+ now: '2027-01-01T00:00:00.000Z',
748
+ })
749
+ if (!claim) throw new Error('Expected a claimed address.')
750
+ await RoutesDepositAddresses.completePoll(db, {
751
+ id: first.id,
752
+ lastPolledAt: '2027-01-01T00:00:10.000Z',
753
+ nextPollAt: '2027-01-01T00:01:10.000Z',
754
+ pollLeaseVersion: claim.pollLeaseVersion,
755
+ providerRequestIds: claim.providerRequestIds,
756
+ providerState: claim.providerState,
757
+ })
758
+ const reused = await TestApp.json(
759
+ await postDepositAddress(app, { body, key: sibling }),
760
+ Routes.schema.createRoutesDepositAddress.Response,
761
+ )
762
+ expect(reused.id).toBe(first.id)
763
+ expect(first.id).not.toBe(previous.id)
764
+ expect(await RoutesDepositAddresses.get(db, previous.id)).toEqual(previous)
765
+ expect(inputs.map((input) => input.subsidize)).toEqual([true])
766
+ expect(await RoutesPolicies.get(db, scope)).toBeUndefined()
767
+ const stored = await RoutesDepositAddresses.get(db, first.id)
768
+ expect(stored?.providerState).toMatchObject({
769
+ fixedRate: '1:1',
770
+ routePolicy: {
771
+ maxAmount: '0.05',
772
+ scope: { ...scope, apiKeyId: key.id },
773
+ subsidyPayer: 'enterprise',
774
+ versions: [0, 0],
775
+ },
776
+ routePolicyKey: '["[\\"enterprise\\",\\"0.050000\\"]","1:1"]',
777
+ })
778
+ const reservation = await db.kysely
779
+ .selectFrom('routes_idempotency_requests')
780
+ .select('matchHash')
781
+ .where('apiKeyId', '=', key.id)
782
+ .where('keyHash', '=', Idempotency.inputHash(idempotencyKey))
783
+ .executeTakeFirstOrThrow()
784
+ expect(reservation.matchHash).toBe(
785
+ Idempotency.inputHash({
786
+ destinationTokenKey: 'eip155:4217/erc20:0x20c00000000000000000000014f22ca97301eb73',
787
+ environment: 'production',
788
+ orgId: key.orgId,
789
+ policyKey: '["[\\"enterprise\\",\\"0.050000\\"]","1:1"]',
790
+ providerId: 'relay',
791
+ recipient: body.recipient,
792
+ refundAddress: body.refundAddress,
793
+ sourceChainId: 'eip155:8453',
794
+ sourceTokenKey: 'eip155:8453/erc20:0xfde4c96c8593536e31f229ea8f37b2ada2699bb2',
795
+ subsidize: true,
796
+ }),
797
+ )
798
+
799
+ // A new commitment may provision, but cannot adopt the same physical address returned by this provider.
800
+ await RoutesPolicies.save(db, {
801
+ scope,
802
+ version: 0,
803
+ policy: { access: null, subsidyEnabled: true, subsidyPayer: 'tempo', maxAmount: '0.05' },
804
+ })
805
+ const conflict = await postDepositAddress(app, { body, key })
806
+ expect(conflict.status).toBe(409)
807
+ await expect(conflict.json()).resolves.toMatchObject({
808
+ error: { code: 'route_policy_identity_conflict' },
809
+ })
810
+ expect(counts).toContainEqual([
811
+ 'routes_policy_denial_count',
812
+ 1,
813
+ { reason: 'route_policy_identity_conflict' },
814
+ ])
815
+ expect(inputs.map((input) => input.subsidize)).toEqual([true, true])
816
+
817
+ await RoutesPolicies.save(db, { scope, version: 1, policy: { access: [], suspended: true } })
818
+ const replay = await postDepositAddress(app, { body, key, idempotencyKey })
819
+ expect(replay.status).toBe(200)
820
+ await expect(replay.json()).resolves.toMatchObject({ id: first.id })
821
+ expect(
822
+ (
823
+ await app.request(`/v1/routes/deposit-addresses/${first.id}`, {
824
+ headers: { authorization: `Bearer ${key.token}` },
825
+ })
826
+ ).status,
827
+ ).toBe(200)
828
+ // The accepted provider checkpoint still conflicts; suspension blocks genuinely fresh instructions.
829
+ const retry = await postDepositAddress(app, { body, key })
830
+ expect(retry.status).toBe(409)
831
+ await expect(retry.json()).resolves.toMatchObject({
832
+ error: { code: 'route_policy_identity_conflict' },
833
+ })
834
+ expect(
835
+ (
836
+ await postDepositAddress(app, {
837
+ body: { ...body, refundAddress: `0x${'83'.repeat(20)}` },
838
+ key,
839
+ })
840
+ ).status,
841
+ ).toBe(403)
842
+ expect((await RoutesDepositAddresses.get(db, first.id))?.providerState).toEqual(
843
+ stored?.providerState,
844
+ )
845
+ })
846
+
847
+ test('selects a financially eligible deposit provider and reuses its pinned terms', async () => {
848
+ const key = {
849
+ id: 'key_deposit_financial_fallback',
850
+ orgId: 'org_deposit_financial_fallback',
851
+ scopes: ['routes:read', 'routes:write'],
852
+ token: 'secret_deposit_financial_fallback',
853
+ }
854
+ await Organizations.create(db, { id: key.orgId, name: 'Financial fallback' })
855
+ const route = {
856
+ destinationChainId: 'eip155:4217',
857
+ destinationTokenId: 'usdce',
858
+ sourceChainId: 'eip155:8453',
859
+ sourceTokenId: 'usdc',
860
+ }
861
+ await RoutesCatalog.publish(db, {
862
+ ...TestRoutes.data,
863
+ routes: TestRoutes.data.routes.map((entry) =>
864
+ entry.providerId === 'rhino' &&
865
+ entry.sourceChainId === route.sourceChainId &&
866
+ entry.sourceTokenId === route.sourceTokenId &&
867
+ entry.destinationTokenId === route.destinationTokenId
868
+ ? { ...entry, capabilities: { depositAddress: true } }
869
+ : entry,
870
+ ),
871
+ })
872
+ try {
873
+ await RoutesPolicies.save(db, {
874
+ policy: {
875
+ access: null,
876
+ maxAmount: '0.04',
877
+ subsidyEnabled: true,
878
+ subsidyPayer: 'enterprise',
879
+ terms: [{ ...route, providerId: 'rhino', subsidyPayer: 'tempo' }],
880
+ },
881
+ scope: { environment: 'production', orgId: key.orgId },
882
+ version: 0,
883
+ })
884
+ const provisions: string[] = []
885
+ const app = create({
886
+ auth: { keys: [key] },
887
+ defaultChainId: tempoMainnet.id,
888
+ providers: ['relay', 'rhino'].map((id) =>
889
+ RoutesProvider.from({
890
+ ...depositAddressProvider(`0x${'86'.repeat(20)}`, () => {
891
+ provisions.push(id)
892
+ }),
893
+ id,
894
+ name: id,
895
+ }),
896
+ ),
897
+ routes: {
898
+ depositSubsidyAccount: `0x${'aa'.repeat(20)}`,
899
+ depositSubsidyMaxAmount: '0.05',
900
+ },
901
+ })
902
+ const body = {
903
+ ...depositAddressBody,
904
+ destinationToken: 'usdce',
905
+ refundAddress: `0x${'87'.repeat(20)}`,
906
+ sourceChain: 'base',
907
+ sourceToken: 'usdc',
908
+ subsidize: true,
909
+ }
910
+ const withoutSigner = create({
911
+ auth: { keys: [key] },
912
+ defaultChainId: tempoMainnet.id,
913
+ providers: ['relay', 'rhino'].map((id) =>
914
+ RoutesProvider.from({
915
+ ...depositAddressProvider(`0x${'86'.repeat(20)}`, () => {
916
+ provisions.push(id)
917
+ }),
918
+ id,
919
+ name: id,
920
+ }),
921
+ ),
922
+ routes: { depositSubsidyMaxAmount: '0.05' },
923
+ })
924
+ expect((await postDepositAddress(withoutSigner, { body, key })).status).toBe(403)
925
+ expect(provisions).toEqual([])
926
+ const response = await postDepositAddress(app, { body, key })
927
+ expect(response.status).toBe(200)
928
+ const first = await TestApp.json(response, Routes.schema.createRoutesDepositAddress.Response)
929
+ expect(first.provider.id).toBe('rhino')
930
+ const repeated = await postDepositAddress(app, { body, key })
931
+ expect(repeated.status).toBe(200)
932
+ await expect(repeated.json()).resolves.toMatchObject({ id: first.id })
933
+ expect(provisions).toEqual(['rhino'])
934
+ expect((await RoutesDepositAddresses.get(db, first.id))?.providerState).toMatchObject({
935
+ routePolicy: { maxAmount: '0.04', subsidyPayer: 'tempo' },
936
+ })
937
+ } finally {
938
+ await TestRoutes.publish(db)
939
+ }
940
+ })
941
+
577
942
  test('rejects a missing policy, records its bounded reason, and releases the claim', async () => {
578
943
  await Organizations.upsert(db, {
579
944
  id: routesSubsidyDisabledWriter.orgId,
@@ -598,6 +963,7 @@ describe('POST /routes/deposit-addresses', () => {
598
963
  body,
599
964
  idempotencyKey,
600
965
  key: routesSubsidyDisabledWriter,
966
+ policy: false,
601
967
  })
602
968
 
603
969
  expect({ body: await rejected.json(), status: rejected.status }).toMatchInlineSnapshot(
@@ -609,7 +975,7 @@ describe('POST /routes/deposit-addresses', () => {
609
975
  "body": {
610
976
  "error": {
611
977
  "code": "routes_subsidy_not_enabled",
612
- "message": "Routes subsidies are not enabled for this organization.",
978
+ "message": "Route subsidies are not enabled for this request. Contact Tempo to enable them, or use an API key from an organization with subsidies enabled.",
613
979
  },
614
980
  "requestId": Any<String>,
615
981
  },
@@ -620,10 +986,7 @@ describe('POST /routes/deposit-addresses', () => {
620
986
  expect(counts.find(([name]) => name === 'routes_subsidy_request_rejection_count')).toEqual([
621
987
  'routes_subsidy_request_rejection_count',
622
988
  1,
623
- {
624
- reason: 'routes_subsidy_not_enabled',
625
- routes_environment: 'production',
626
- },
989
+ { reason: 'routes_subsidy_not_enabled', routes_environment: 'production' },
627
990
  ])
628
991
 
629
992
  await enableRoutesSubsidy(routesSubsidyDisabledWriter, 'active')
@@ -663,7 +1026,7 @@ describe('POST /routes/deposit-addresses', () => {
663
1026
  "body": {
664
1027
  "error": {
665
1028
  "code": "billing_required",
666
- "message": "Billing required.",
1029
+ "message": "Active billing is required for enterprise-paid route subsidies. Set up billing for this organization, then retry.",
667
1030
  },
668
1031
  "requestId": Any<String>,
669
1032
  },
@@ -708,6 +1071,365 @@ describe('POST /routes/deposit-addresses', () => {
708
1071
  expect({ body: await replay.json(), status: replay.status }).toEqual({ body: firstBody, status: 200 }) // prettier-ignore
709
1072
  })
710
1073
 
1074
+ test.each(['99000000', '100000001'])(
1075
+ 'preserves the claim after Relay provisions invalid output %s',
1076
+ async (amount) => {
1077
+ const key = {
1078
+ id: `key_rejected_relay_${amount}`,
1079
+ orgId: `org_rejected_relay_${amount}`,
1080
+ scopes: ['routes:write'],
1081
+ token: `secret_rejected_relay_${amount}`,
1082
+ }
1083
+ await enableRoutesSubsidy(key, 'active')
1084
+ let provisions = 0
1085
+ const server = await TestRelay.createServer((_request, response) => {
1086
+ provisions++
1087
+ response.writeHead(200, { 'content-type': 'application/json' })
1088
+ response.end(
1089
+ JSON.stringify({
1090
+ details: {
1091
+ currencyIn: { amount: '100000000' },
1092
+ currencyOut: { amount, minimumAmount: '98000000' },
1093
+ isFixedRate: true,
1094
+ },
1095
+ steps: [
1096
+ { depositAddress: depositAddressBody.refundAddress, requestId: 'rejected-rate' },
1097
+ ],
1098
+ }),
1099
+ )
1100
+ })
1101
+ try {
1102
+ const app = create({
1103
+ auth: { keys: [key] },
1104
+ defaultChainId: tempoMainnet.id,
1105
+ providers: [Relay.relay({ baseUrl: server.url, subsidies: { maxAmount: '100' } })],
1106
+ routes: { depositSubsidyMaxAmount: '5' },
1107
+ })
1108
+ const body = { ...depositAddressBody, subsidize: true }
1109
+ const idempotencyKey = nextDepositAddressKey()
1110
+ const first = await postDepositAddress(app, { body, idempotencyKey, key })
1111
+ const retry = await postDepositAddress(app, { body, idempotencyKey, key })
1112
+ const sibling = await postDepositAddress(app, { body, key })
1113
+ expect({ provisions, statuses: [first.status, retry.status, sibling.status] })
1114
+ .toMatchInlineSnapshot(`
1115
+ {
1116
+ "provisions": 1,
1117
+ "statuses": [
1118
+ 502,
1119
+ 409,
1120
+ 409,
1121
+ ],
1122
+ }
1123
+ `)
1124
+ await expect(
1125
+ db.kysely
1126
+ .selectFrom('routes_idempotency_requests')
1127
+ .select(['irrevocable', 'status'])
1128
+ .where('apiKeyId', '=', key.id)
1129
+ .where('keyHash', '=', Idempotency.inputHash(idempotencyKey))
1130
+ .executeTakeFirstOrThrow(),
1131
+ ).resolves.toEqual({ irrevocable: true, status: 'pending' })
1132
+ } finally {
1133
+ await server.closeAsync()
1134
+ }
1135
+ },
1136
+ )
1137
+
1138
+ test('provisions subsidized Solana USDC to Tempo USDC.e at a fixed rate', async () => {
1139
+ const key = {
1140
+ id: 'key_solana_usdc_fixed_rate',
1141
+ orgId: 'org_solana_usdc_fixed_rate',
1142
+ scopes: ['routes:write'],
1143
+ token: 'secret_solana_usdc_fixed_rate',
1144
+ }
1145
+ await grantRoutes(key)
1146
+ await RoutesPolicies.save(db, {
1147
+ policy: {
1148
+ access: null,
1149
+ maxAmount: '0.05',
1150
+ subsidyEnabled: true,
1151
+ subsidyPayer: 'tempo',
1152
+ },
1153
+ scope: { environment: 'production', orgId: key.orgId },
1154
+ version: 1,
1155
+ })
1156
+ const requests: Record<string, unknown>[] = []
1157
+ const server = await TestRelay.createServer(async (request, response) => {
1158
+ request.setEncoding('utf8')
1159
+ let payload = ''
1160
+ for await (const chunk of request) payload += chunk
1161
+ requests.push(JSON.parse(payload))
1162
+ response.writeHead(200, { 'content-type': 'application/json' })
1163
+ response.end(
1164
+ JSON.stringify({
1165
+ details: {
1166
+ currencyIn: { amount: '100000000' },
1167
+ currencyOut: { amount: '100000000', minimumAmount: '98000000' },
1168
+ isFixedRate: false,
1169
+ },
1170
+ steps: [
1171
+ {
1172
+ depositAddress: 'So11111111111111111111111111111111111111112',
1173
+ requestId: 'solana-usdc',
1174
+ },
1175
+ ],
1176
+ }),
1177
+ )
1178
+ })
1179
+ try {
1180
+ const app = create({
1181
+ auth: { keys: [key] },
1182
+ defaultChainId: tempoMainnet.id,
1183
+ providers: [Relay.relay({ baseUrl: server.url, subsidies: { maxAmount: '100' } })],
1184
+ routes: { depositSubsidyMaxAmount: '5' },
1185
+ })
1186
+ const body = {
1187
+ ...depositAddressBody,
1188
+ destinationToken: 'USDC.e',
1189
+ refundAddress: 'So11111111111111111111111111111111111111112',
1190
+ sourceChain: 'solana',
1191
+ sourceToken: 'USDC',
1192
+ subsidize: true,
1193
+ }
1194
+ const first = await TestApp.json(
1195
+ await postDepositAddress(app, { body, key }),
1196
+ Routes.schema.createRoutesDepositAddress.Response,
1197
+ )
1198
+ const reused = await TestApp.json(
1199
+ await postDepositAddress(app, { body, key }),
1200
+ Routes.schema.createRoutesDepositAddress.Response,
1201
+ )
1202
+ expect(reused.id).toBe(first.id)
1203
+ expect(requests).toEqual([
1204
+ expect.objectContaining({
1205
+ amount: '100000000',
1206
+ destinationChainId: 4217,
1207
+ destinationCurrency: '0x20c000000000000000000000b9537d11c60e8b50',
1208
+ fixedRate: '1:1',
1209
+ maxSubsidizationAmount: '50000',
1210
+ originChainId: 792703809,
1211
+ originCurrency: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
1212
+ refundTo: body.refundAddress,
1213
+ subsidizeFees: true,
1214
+ tradeType: 'EXACT_INPUT',
1215
+ useDepositAddress: true,
1216
+ user: body.refundAddress,
1217
+ }),
1218
+ ])
1219
+ expect(first).toMatchObject({
1220
+ address: 'So11111111111111111111111111111111111111112',
1221
+ destinationAmount: { baseUnits: '100000000' },
1222
+ provider: { id: 'relay' },
1223
+ sourceChain: { kind: 'solana' },
1224
+ subsidize: true,
1225
+ })
1226
+ expect((await RoutesDepositAddresses.get(db, first.id))?.providerState).toMatchObject({
1227
+ fixedRate: '1:1',
1228
+ routePolicy: { maxAmount: '0.05', subsidyPayer: 'tempo' },
1229
+ })
1230
+ } finally {
1231
+ await server.closeAsync()
1232
+ }
1233
+ })
1234
+
1235
+ test.each([
1236
+ { name: 'organization', organization: '0.05', platform: '1' },
1237
+ { name: 'platform', organization: '1', platform: '0.05' },
1238
+ { name: 'missing_platform', organization: '1', platform: undefined },
1239
+ ])('caps legacy Relay sponsorship by $name', async (scenario) => {
1240
+ const key = {
1241
+ id: `key_legacy_cap_${scenario.name}`,
1242
+ orgId: `org_legacy_cap_${scenario.name}`,
1243
+ scopes: ['routes:write'],
1244
+ token: `secret_legacy_cap_${scenario.name}`,
1245
+ }
1246
+ await enableRoutesSubsidy(key, 'active')
1247
+ await RoutesSubsidies.upsertOrganization(db, {
1248
+ frequency: 'tx',
1249
+ maxAmount: scenario.organization,
1250
+ orgId: key.orgId,
1251
+ })
1252
+ const requests: Record<string, unknown>[] = []
1253
+ const server = await TestRelay.createServer(async (request, response) => {
1254
+ request.setEncoding('utf8')
1255
+ let payload = ''
1256
+ for await (const chunk of request) payload += chunk
1257
+ requests.push(JSON.parse(payload))
1258
+ response.writeHead(200, { 'content-type': 'application/json' })
1259
+ response.end(
1260
+ JSON.stringify({
1261
+ details: {
1262
+ currencyIn: { amount: '100000000' },
1263
+ currencyOut: { amount: '100000000', minimumAmount: '98000000' },
1264
+ isFixedRate: true,
1265
+ },
1266
+ steps: [
1267
+ {
1268
+ depositAddress: Hash.keccak256(Hex.fromString(`${key.id}_${requests.length}`)).slice(
1269
+ 0,
1270
+ 42,
1271
+ ),
1272
+ requestId: `capped-rate-${requests.length}`,
1273
+ },
1274
+ ],
1275
+ }),
1276
+ )
1277
+ })
1278
+ try {
1279
+ const options = {
1280
+ auth: { keys: [key] },
1281
+ defaultChainId: tempoMainnet.id,
1282
+ providers: [Relay.relay({ baseUrl: server.url, subsidies: { maxAmount: '100' } })],
1283
+ }
1284
+ const app = create({
1285
+ ...options,
1286
+ routes: { depositSubsidyMaxAmount: scenario.platform },
1287
+ })
1288
+ const body = {
1289
+ ...depositAddressBody,
1290
+ refundAddress: `0x${'93'.repeat(20)}`,
1291
+ sourceChain: 'base',
1292
+ sourceToken: '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2',
1293
+ subsidize: true,
1294
+ }
1295
+ const response = await postDepositAddress(app, { body, key })
1296
+ if (scenario.platform === undefined) {
1297
+ expect(response.status).toBe(404)
1298
+ expect(requests).toEqual([])
1299
+ return
1300
+ }
1301
+ expect(response.status).toBe(200)
1302
+ const first = await TestApp.json(response, Routes.schema.createRoutesDepositAddress.Response)
1303
+ const stored = await RoutesDepositAddresses.get(db, first.id)
1304
+ const reused = await TestApp.json(
1305
+ await postDepositAddress(app, { body, key }),
1306
+ Routes.schema.createRoutesDepositAddress.Response,
1307
+ )
1308
+ expect(reused.id).toBe(first.id)
1309
+ expect(requests).toHaveLength(1)
1310
+ expect(requests[0]).toMatchObject({
1311
+ fixedRate: '1:1',
1312
+ maxSubsidizationAmount: '50000',
1313
+ subsidizeFees: true,
1314
+ })
1315
+
1316
+ if (scenario.name === 'organization')
1317
+ await RoutesSubsidies.upsertOrganization(db, {
1318
+ frequency: 'tx',
1319
+ maxAmount: '0.025',
1320
+ orgId: key.orgId,
1321
+ })
1322
+ const capped = create({
1323
+ ...options,
1324
+ routes: {
1325
+ depositSubsidyMaxAmount: scenario.name === 'platform' ? '0.025' : scenario.platform,
1326
+ },
1327
+ })
1328
+ const replacement = await TestApp.json(
1329
+ await postDepositAddress(capped, { body, key }),
1330
+ Routes.schema.createRoutesDepositAddress.Response,
1331
+ )
1332
+ expect(replacement.id).not.toBe(first.id)
1333
+ expect(requests).toHaveLength(2)
1334
+ expect(requests[1]).toMatchObject({
1335
+ fixedRate: '1:1',
1336
+ maxSubsidizationAmount: '25000',
1337
+ subsidizeFees: true,
1338
+ })
1339
+ expect(await RoutesDepositAddresses.get(db, first.id)).toEqual(stored)
1340
+ if (scenario.name === 'organization')
1341
+ await RoutesSubsidies.upsertOrganization(db, {
1342
+ frequency: 'tx',
1343
+ maxAmount: scenario.organization,
1344
+ orgId: key.orgId,
1345
+ })
1346
+ const restored = await TestApp.json(
1347
+ await postDepositAddress(app, { body, key }),
1348
+ Routes.schema.createRoutesDepositAddress.Response,
1349
+ )
1350
+ expect(restored.id).toBe(first.id)
1351
+ expect(requests).toHaveLength(2)
1352
+ } finally {
1353
+ await server.closeAsync()
1354
+ }
1355
+ })
1356
+
1357
+ test.each(['AMOUNT_TOO_LOW', 'NO_ROUTE'])(
1358
+ 'releases subsidized claims after a definitive Relay rejection %s',
1359
+ async (errorCode) => {
1360
+ const key = {
1361
+ id: `key_relay_rejection_${errorCode}`,
1362
+ orgId: `org_relay_rejection_${errorCode}`,
1363
+ scopes: ['routes:write'],
1364
+ token: `secret_relay_rejection_${errorCode}`,
1365
+ }
1366
+ await enableRoutesSubsidy(key, 'active')
1367
+ let requests = 0
1368
+ const server = await TestRelay.createServer((request, response) => {
1369
+ request.resume()
1370
+ requests += 1
1371
+ response.writeHead(requests < 4 ? 400 : 200, { 'content-type': 'application/json' })
1372
+ response.end(
1373
+ JSON.stringify(
1374
+ requests < 4
1375
+ ? { errorCode }
1376
+ : {
1377
+ details: {
1378
+ currencyIn: { amount: '100000000' },
1379
+ currencyOut: { amount: '100000000', minimumAmount: '98000000' },
1380
+ isFixedRate: true,
1381
+ },
1382
+ steps: [
1383
+ {
1384
+ depositAddress: Hash.keccak256(Hex.fromString(key.id)).slice(0, 42),
1385
+ requestId: `recovered-${errorCode}`,
1386
+ },
1387
+ ],
1388
+ },
1389
+ ),
1390
+ )
1391
+ })
1392
+ try {
1393
+ const app = create({
1394
+ auth: { keys: [key] },
1395
+ defaultChainId: tempoMainnet.id,
1396
+ providers: [Relay.relay({ baseUrl: server.url, subsidies: { maxAmount: '5' } })],
1397
+ routes: { depositSubsidyMaxAmount: '5' },
1398
+ })
1399
+ const body = {
1400
+ ...depositAddressBody,
1401
+ refundAddress: `0x${'95'.repeat(20)}`,
1402
+ sourceChain: 'base',
1403
+ sourceToken: '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2',
1404
+ subsidize: true,
1405
+ }
1406
+ const idempotencyKey = nextDepositAddressKey()
1407
+ for (const requestKey of [idempotencyKey, idempotencyKey, nextDepositAddressKey()]) {
1408
+ const response = await postDepositAddress(app, { body, idempotencyKey: requestKey, key })
1409
+ expect(response.status).toBe(errorCode === 'AMOUNT_TOO_LOW' ? 400 : 404)
1410
+ expect((await response.json()).error.code).toBe(
1411
+ errorCode === 'AMOUNT_TOO_LOW' ? 'amount_too_low' : 'quote_not_available',
1412
+ )
1413
+ expect(
1414
+ await db.kysely
1415
+ .selectFrom('routes_idempotency_requests')
1416
+ .select(['irrevocable', 'status'])
1417
+ .where('apiKeyId', '=', key.id)
1418
+ .execute(),
1419
+ ).toMatchInlineSnapshot(`[]`)
1420
+ }
1421
+ const recovered = await postDepositAddress(app, { body, idempotencyKey, key })
1422
+ expect(recovered.status).toMatchInlineSnapshot(`200`)
1423
+ expect(requests).toMatchInlineSnapshot(`4`)
1424
+ expect(
1425
+ await TestApp.json(recovered, Routes.schema.createRoutesDepositAddress.Response),
1426
+ ).toMatchObject({ provider: { id: 'relay' }, subsidize: true })
1427
+ } finally {
1428
+ await server.closeAsync()
1429
+ }
1430
+ },
1431
+ )
1432
+
711
1433
  test('preserves a subsidized claim after an ambiguous provider failure', async () => {
712
1434
  await enableRoutesSubsidy(routesSubsidyReplayWriter, 'active')
713
1435
  const app = transfersApp({
@@ -745,7 +1467,7 @@ describe('POST /routes/deposit-addresses', () => {
745
1467
  ).resolves.toEqual({ irrevocable: true, status: 'pending' })
746
1468
  })
747
1469
 
748
- test('resumes an expired provisioned subsidy without replaying stale quote terms', async () => {
1470
+ test('resumes a legacy provisioned subsidy after policy adoption without replaying stale quote terms', async () => {
749
1471
  await Organizations.upsert(db, {
750
1472
  id: routesSubsidyResumeWriter.orgId,
751
1473
  name: 'Routes subsidy resume',
@@ -813,11 +1535,19 @@ describe('POST /routes/deposit-addresses', () => {
813
1535
  .where('keyHash', '=', identity.keyHash)
814
1536
  .execute()
815
1537
 
816
- const response = await postDepositAddress(transfersApp({ metrics, providers: [] }), {
817
- body,
818
- idempotencyKey,
819
- key: routesSubsidyResumeWriter,
1538
+ await RoutesPolicies.save(db, {
1539
+ scope: { orgId: routesSubsidyResumeWriter.orgId, environment: 'production' },
1540
+ version: 0,
1541
+ policy: { access: [], suspended: true, subsidyEnabled: false },
820
1542
  })
1543
+ const response = await postDepositAddress(
1544
+ transfersApp({ metrics, providers: [], routes: { depositSubsidyAccount: undefined } }),
1545
+ {
1546
+ body,
1547
+ idempotencyKey,
1548
+ key: routesSubsidyResumeWriter,
1549
+ },
1550
+ )
821
1551
 
822
1552
  expect(response.status).toBe(200)
823
1553
  const responseBody = await TestApp.json(
@@ -826,6 +1556,9 @@ describe('POST /routes/deposit-addresses', () => {
826
1556
  )
827
1557
  expect(responseBody.subsidize).toBe(true)
828
1558
  expect(responseBody).not.toHaveProperty('quote')
1559
+ const recovered = await RoutesDepositAddresses.get(db, responseBody.id)
1560
+ expect(recovered).toBeDefined()
1561
+ expect(recovered!.providerState).toBeNull()
829
1562
  expect(
830
1563
  histograms.filter(
831
1564
  ([name]) =>
@@ -836,6 +1569,95 @@ describe('POST /routes/deposit-addresses', () => {
836
1569
  ).toEqual([])
837
1570
  })
838
1571
 
1572
+ test('waits for a settler before persisting a resumed non-Relay subsidy', async () => {
1573
+ await Organizations.upsert(db, {
1574
+ id: routesSubsidyResumeWriter.orgId,
1575
+ name: 'Routes subsidy resume',
1576
+ })
1577
+ const body = {
1578
+ ...depositAddressBody,
1579
+ recipient: `0x${'7b'.repeat(20)}`,
1580
+ subsidize: true,
1581
+ }
1582
+ const idempotencyKey = nextDepositAddressKey()
1583
+ const identity = {
1584
+ apiKeyId: routesSubsidyResumeWriter.id,
1585
+ keyHash: Idempotency.inputHash(idempotencyKey),
1586
+ requestHash: Idempotency.inputHash({
1587
+ body,
1588
+ endpoint: 'POST /v1/routes/deposit-addresses',
1589
+ }),
1590
+ }
1591
+ const claim = await RoutesIdempotencyTable.claim(db, {
1592
+ ...identity,
1593
+ irrevocable: true,
1594
+ operation: RoutesIdempotencyTable.operations.depositAddress,
1595
+ orgId: routesSubsidyResumeWriter.orgId,
1596
+ ttlMs: 60_000,
1597
+ })
1598
+ if (claim.type !== 'claimed') throw new Error('Expected a fresh idempotency claim.')
1599
+ const snapshot = TestRoutes.depositAddressSnapshot({
1600
+ address: 'TG6rrQgTkf3e85cYzqtD2q8fYuJDdM99Rx',
1601
+ provider: { id: 'rhino', name: 'Rhino' },
1602
+ recipient: body.recipient,
1603
+ subsidize: true,
1604
+ })
1605
+ await RoutesIdempotencyTable.checkpoint(db, {
1606
+ apiKeyId: identity.apiKeyId,
1607
+ createdAt: claim.createdAt,
1608
+ keyHash: identity.keyHash,
1609
+ replayTtlMs: 86_400_000,
1610
+ response: JSON.stringify({
1611
+ candidate: {
1612
+ destinationChain: snapshot.destinationChain,
1613
+ destinationToken: snapshot.destinationToken,
1614
+ sourceChain: snapshot.sourceChain,
1615
+ sourceToken: snapshot.sourceToken,
1616
+ },
1617
+ provider: snapshot.provider,
1618
+ recipient: snapshot.recipient,
1619
+ refundAddress: snapshot.refundAddress,
1620
+ result: {
1621
+ address: snapshot.address,
1622
+ correlation: { requestId: 'rhino_resume_request' },
1623
+ destinationAmount: '1000000',
1624
+ destinationAmountMin: '1000000',
1625
+ fees: [],
1626
+ sampledAt: '2026-08-04T00:00:00.000Z',
1627
+ },
1628
+ }),
1629
+ })
1630
+
1631
+ const count = await RoutesDepositAddresses.countByOrganization(
1632
+ db,
1633
+ routesSubsidyResumeWriter.orgId,
1634
+ )
1635
+ const unavailable = await postDepositAddress(
1636
+ transfersApp({ providers: [], routes: { depositSubsidyAccount: undefined } }),
1637
+ { body, idempotencyKey, key: routesSubsidyResumeWriter },
1638
+ )
1639
+ expect(await unavailable.json()).toMatchObject({
1640
+ error: { code: 'deposit_address_not_available' },
1641
+ })
1642
+ expect(unavailable.status).toBe(404)
1643
+ expect(
1644
+ await RoutesDepositAddresses.countByOrganization(db, routesSubsidyResumeWriter.orgId),
1645
+ ).toBe(count)
1646
+
1647
+ const resumed = await postDepositAddress(transfersApp({ providers: [] }), {
1648
+ body,
1649
+ idempotencyKey,
1650
+ key: routesSubsidyResumeWriter,
1651
+ })
1652
+ expect(resumed.status).toBe(200)
1653
+ expect(
1654
+ await TestApp.json(resumed, Routes.schema.createRoutesDepositAddress.Response),
1655
+ ).toMatchObject({
1656
+ provider: { id: 'rhino' },
1657
+ subsidize: true,
1658
+ })
1659
+ })
1660
+
839
1661
  test('creates a reusable address and persists private provider correlation', async () => {
840
1662
  await Organizations.upsert(db, { id: routesWriter.orgId, name: 'Routes' })
841
1663
  let seen: RoutesProvider.createDepositAddress.Parameters | undefined
@@ -1052,6 +1874,7 @@ describe('POST /routes/deposit-addresses', () => {
1052
1874
 
1053
1875
  test('stores subsidized addresses as a separate reusable identity', async () => {
1054
1876
  await enableRoutesSubsidy(routesSubsidyWriter, 'active')
1877
+ await grantRoutes(routesSubsidyWriter)
1055
1878
  const inputs: RoutesProvider.createDepositAddress.Parameters[] = []
1056
1879
  const app = transfersApp({
1057
1880
  providers: [
@@ -1059,7 +1882,10 @@ describe('POST /routes/deposit-addresses', () => {
1059
1882
  inputs.push(input)
1060
1883
  return {
1061
1884
  address: inputs.length === 1 ? `0x${'31'.repeat(20)}` : `0x${'32'.repeat(20)}`,
1062
- correlation: { requestId: `relay_request_${inputs.length}` },
1885
+ correlation: {
1886
+ ...(input.subsidize ? { fixedRate: '1:1' as const } : {}),
1887
+ requestId: `relay_request_${inputs.length}`,
1888
+ },
1063
1889
  destinationAmount: '1000000',
1064
1890
  destinationAmountMin: '1000000',
1065
1891
  fees: [],
@@ -1085,26 +1911,45 @@ describe('POST /routes/deposit-addresses', () => {
1085
1911
  }),
1086
1912
  Routes.schema.createRoutesDepositAddress.Response,
1087
1913
  )
1914
+ const subsidizedRecord = await RoutesDepositAddresses.get(db, subsidized.id)
1088
1915
 
1089
1916
  expect({
1090
1917
  distinct: standard.id !== subsidized.id,
1091
- inputs: inputs.map(({ subsidize }) => subsidize),
1918
+ inputs: inputs.map(({ subsidize, subsidyMaxAmount }) => ({ subsidize, subsidyMaxAmount })),
1092
1919
  standard: standard.subsidize,
1093
1920
  subsidized: subsidized.subsidize,
1094
1921
  }).toMatchInlineSnapshot(`
1095
1922
  {
1096
1923
  "distinct": true,
1097
1924
  "inputs": [
1098
- false,
1099
- true,
1925
+ {
1926
+ "subsidize": false,
1927
+ "subsidyMaxAmount": undefined,
1928
+ },
1929
+ {
1930
+ "subsidize": true,
1931
+ "subsidyMaxAmount": "5",
1932
+ },
1100
1933
  ],
1101
1934
  "standard": false,
1102
1935
  "subsidized": true,
1103
1936
  }
1104
1937
  `)
1938
+ expect(subsidizedRecord?.providerState).toHaveProperty('routePolicy')
1939
+ expect(subsidizedRecord?.providerState).toHaveProperty('routePolicyKey')
1940
+ expect(subsidizedRecord?.providerState).toHaveProperty('fixedRate', '1:1')
1941
+ const reused = await TestApp.json(
1942
+ await postDepositAddress(app, {
1943
+ body: { ...body, subsidize: true },
1944
+ key: routesSubsidyWriter,
1945
+ }),
1946
+ Routes.schema.createRoutesDepositAddress.Response,
1947
+ )
1948
+ expect(reused.id).toBe(subsidized.id)
1949
+ expect(inputs).toHaveLength(2)
1105
1950
  })
1106
1951
 
1107
- test('reuses a subsidized address across API keys in one organization', async () => {
1952
+ test('checks policy before reusing a subsidized address under a fresh API key', async () => {
1108
1953
  await enableRoutesSubsidy(routesSubsidyWriter, 'active')
1109
1954
  let calls = 0
1110
1955
  const app = transfersApp({
@@ -1125,12 +1970,15 @@ describe('POST /routes/deposit-addresses', () => {
1125
1970
  await postDepositAddress(app, { body, key: routesSubsidyWriter }),
1126
1971
  Routes.schema.createRoutesDepositAddress.Response,
1127
1972
  )
1128
- const second = await TestApp.json(
1129
- await postDepositAddress(app, { body, key: routesSubsidyWriterSecond }),
1130
- Routes.schema.createRoutesDepositAddress.Response,
1131
- )
1132
-
1133
- expect({ calls, reused: first.id === second.id }).toEqual({ calls: 1, reused: true })
1973
+ await RoutesSubsidies.removeOrganization(db, routesSubsidyWriter.orgId)
1974
+ const second = await postDepositAddress(app, { body, key: routesSubsidyWriterSecond })
1975
+
1976
+ expect(first.id).toBeDefined()
1977
+ expect({ body: await second.json(), calls, status: second.status }).toMatchObject({
1978
+ body: { error: { code: 'routes_subsidy_not_enabled' } },
1979
+ calls: 1,
1980
+ status: 403,
1981
+ })
1134
1982
  })
1135
1983
 
1136
1984
  test('normalizes EVM accounts before provisioning and matching', async () => {
@@ -1253,6 +2101,79 @@ describe('POST /routes/deposit-addresses', () => {
1253
2101
  expect(calls).toBe(0)
1254
2102
  })
1255
2103
 
2104
+ test('returns 409 on initial completion and retry when different terms win during provisioning', async () => {
2105
+ const key: TestApp.kvStore.Key = {
2106
+ id: 'key_policy_insert_race',
2107
+ orgId: 'org_policy_insert_race',
2108
+ scopes: ['routes:read', 'routes:write'],
2109
+ token: 'secret_policy_insert_race',
2110
+ }
2111
+ await grantRoutes(key)
2112
+ const scope = { environment: 'production', orgId: key.orgId } as const
2113
+ const policy = {
2114
+ access: null,
2115
+ maxAmount: '0.04',
2116
+ subsidyEnabled: true,
2117
+ subsidyPayer: 'tempo',
2118
+ } as const
2119
+ await RoutesPolicies.save(db, { policy, scope, version: 1 })
2120
+ const entered = Promise.withResolvers<void>()
2121
+ const release = Promise.withResolvers<void>()
2122
+ let provisions = 0
2123
+ const app = create({
2124
+ auth: { keys: [key] },
2125
+ defaultChainId: tempoMainnet.id,
2126
+ providers: [
2127
+ depositAddressProvider(`0x${'84'.repeat(20)}`, async () => {
2128
+ provisions++
2129
+ if (provisions === 1) {
2130
+ entered.resolve()
2131
+ await release.promise
2132
+ }
2133
+ }),
2134
+ ],
2135
+ routes: { depositSubsidyMaxAmount: '0.05' },
2136
+ })
2137
+ const body = {
2138
+ ...depositAddressBody,
2139
+ refundAddress: `0x${'85'.repeat(20)}`,
2140
+ sourceChain: 'base',
2141
+ sourceToken: '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2',
2142
+ subsidize: true,
2143
+ }
2144
+ const idempotencyKey = nextDepositAddressKey()
2145
+ const pending = postDepositAddress(app, { body, idempotencyKey, key })
2146
+ await entered.promise
2147
+ try {
2148
+ await RoutesPolicies.save(db, {
2149
+ policy: { ...policy, maxAmount: '0.05' },
2150
+ scope,
2151
+ version: 2,
2152
+ })
2153
+ expect((await postDepositAddress(app, { body, key })).status).toBe(200)
2154
+ } finally {
2155
+ release.resolve()
2156
+ }
2157
+ for (const response of [
2158
+ await pending,
2159
+ await postDepositAddress(app, { body, idempotencyKey, key }),
2160
+ ]) {
2161
+ expect(response.status).toBe(409)
2162
+ await expect(response.json()).resolves.toMatchObject({
2163
+ error: { code: 'route_policy_identity_conflict' },
2164
+ })
2165
+ }
2166
+ expect(provisions).toBe(2)
2167
+ expect(await RoutesDepositAddresses.countByOrganization(db, key.orgId)).toBe(1)
2168
+ expect(
2169
+ await db.kysely
2170
+ .selectFrom('routes_deposit_addresses')
2171
+ .select('providerState')
2172
+ .where('orgId', '=', key.orgId)
2173
+ .executeTakeFirstOrThrow(),
2174
+ ).toMatchObject({ providerState: { routePolicy: { maxAmount: '0.05' } } })
2175
+ })
2176
+
1256
2177
  test('retains the provider checkpoint when address persistence fails', async () => {
1257
2178
  const address = 'TKJebW6SdoJhgkKU13BeKCbrMzT6HeQhJ6'
1258
2179
  const body = { ...depositAddressBody, recipient: `0x${'66'.repeat(20)}` }
@@ -2829,19 +3750,22 @@ function nextDepositAddressKey() {
2829
3750
  return `routes_deposit_address_${depositAddressIdempotencySequence++}`
2830
3751
  }
2831
3752
 
2832
- function postDepositAddress(
3753
+ async function postDepositAddress(
2833
3754
  app: ReturnType<typeof create>,
2834
3755
  options: {
2835
3756
  body?: unknown
2836
3757
  headers?: Record<string, string> | undefined
2837
3758
  idempotencyKey?: string | undefined
2838
- key?: { token: string } | undefined
3759
+ key?: TestApp.kvStore.Key | undefined
3760
+ policy?: boolean | undefined
2839
3761
  } = {},
2840
3762
  ) {
3763
+ const key = options.key ?? routesWriter
3764
+ if (options.policy === true && options.headers === undefined) await grantRoutes(key)
2841
3765
  return app.request('/v1/routes/deposit-addresses', {
2842
3766
  body: JSON.stringify(options.body ?? depositAddressBody),
2843
3767
  headers: options.headers ?? {
2844
- authorization: `Bearer ${(options.key ?? routesWriter).token}`,
3768
+ authorization: `Bearer ${key.token}`,
2845
3769
  'content-type': 'application/json',
2846
3770
  'idempotency-key': options.idempotencyKey ?? nextDepositAddressKey(),
2847
3771
  },
@@ -2870,6 +3794,9 @@ function depositAddressProvider(
2870
3794
  address,
2871
3795
  correlation: {
2872
3796
  checkEndpoint: '/intents/status/v3?requestId=relay_request_1',
3797
+ ...(input.subsidize && input.candidate.provider.id === 'relay'
3798
+ ? { fixedRate: '1:1' as const }
3799
+ : {}),
2873
3800
  requestId: 'relay_request_1',
2874
3801
  },
2875
3802
  destinationAmount: '1000000',
@@ -3078,6 +4005,11 @@ const routesSubsidyRaceWriter = {
3078
4005
  function transfersApp(options: TestApp.create.Options = {}) {
3079
4006
  return create({
3080
4007
  ...options,
4008
+ routes: {
4009
+ depositSubsidyAccount: `0x${'aa'.repeat(20)}`,
4010
+ depositSubsidyMaxAmount: '5',
4011
+ ...options.routes,
4012
+ },
3081
4013
  auth: {
3082
4014
  keys: [
3083
4015
  TestApp.key,
@@ -3136,6 +4068,18 @@ function as(key: { token: string }) {
3136
4068
  return { headers: { authorization: `Bearer ${key.token}` } } as const
3137
4069
  }
3138
4070
 
4071
+ async function grantRoutes(key: TestApp.kvStore.Key) {
4072
+ await Organizations.upsert(db, { id: key.orgId, name: key.orgId })
4073
+ const scope = { environment: 'production' as const, orgId: key.orgId }
4074
+ const current = await RoutesPolicies.get(db, scope)
4075
+ if (current?.policy.access === null) return
4076
+ await RoutesPolicies.save(db, {
4077
+ policy: { ...current?.policy, access: null },
4078
+ scope,
4079
+ version: current?.version ?? 0,
4080
+ })
4081
+ }
4082
+
3139
4083
  let seedDepositAddressSecond = 0
3140
4084
 
3141
4085
  function seedDepositAddress(input: Partial<RoutesDepositAddress.create.Input> = {}) {