tapimo 0.5.3 → 0.7.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 (376) hide show
  1. package/dist/App.d.ts +15830 -7220
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +1011 -412
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/App.js +4 -0
  12. package/dist/admin/App.js.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +2 -1
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.d.ts +148 -0
  16. package/dist/admin/apps/early-access.d.ts.map +1 -0
  17. package/dist/admin/apps/early-access.js +133 -0
  18. package/dist/admin/apps/early-access.js.map +1 -0
  19. package/dist/admin/apps/organizations.d.ts +124 -1
  20. package/dist/admin/apps/organizations.d.ts.map +1 -1
  21. package/dist/admin/apps/organizations.js +97 -6
  22. package/dist/admin/apps/organizations.js.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +526 -1
  24. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  25. package/dist/admin/apps/verified-tokens.js +214 -1
  26. package/dist/admin/apps/verified-tokens.js.map +1 -1
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.d.ts.map +1 -1
  29. package/dist/admin/ui.generated.js +95 -75
  30. package/dist/admin/ui.generated.js.map +1 -1
  31. package/dist/analytics/Analytics.d.ts.map +1 -1
  32. package/dist/analytics/Analytics.js +20 -0
  33. package/dist/analytics/Analytics.js.map +1 -1
  34. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  36. package/dist/analytics/tables/requestEvents.js +36 -7
  37. package/dist/analytics/tables/requestEvents.js.map +1 -1
  38. package/dist/apps/data/App.d.ts +6260 -4924
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +11 -6
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  43. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  44. package/dist/apps/data/FundingQuoteProviders.js +194 -16
  45. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  46. package/dist/apps/data/FxOracle.d.ts +71 -0
  47. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  48. package/dist/apps/data/FxOracle.js +97 -0
  49. package/dist/apps/data/FxOracle.js.map +1 -0
  50. package/dist/apps/data/index.d.ts +1 -0
  51. package/dist/apps/data/index.d.ts.map +1 -1
  52. package/dist/apps/data/index.js +1 -0
  53. package/dist/apps/data/index.js.map +1 -1
  54. package/dist/apps/data/routes/activities.d.ts +3599 -1382
  55. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  56. package/dist/apps/data/routes/activities.js +250 -88
  57. package/dist/apps/data/routes/activities.js.map +1 -1
  58. package/dist/apps/data/routes/addresses.d.ts +872 -251
  59. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  60. package/dist/apps/data/routes/addresses.js +11 -9
  61. package/dist/apps/data/routes/addresses.js.map +1 -1
  62. package/dist/apps/data/routes/balances.d.ts +77 -26
  63. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  64. package/dist/apps/data/routes/balances.js +88 -45
  65. package/dist/apps/data/routes/balances.js.map +1 -1
  66. package/dist/apps/data/routes/blocks.d.ts +39 -39
  67. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  68. package/dist/apps/data/routes/exchanges.d.ts +856 -105
  69. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  70. package/dist/apps/data/routes/exchanges.js +518 -76
  71. package/dist/apps/data/routes/exchanges.js.map +1 -1
  72. package/dist/apps/data/routes/fee-amm.d.ts +81 -67
  73. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  74. package/dist/apps/data/routes/fee-amm.js +76 -91
  75. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  76. package/dist/apps/data/routes/funding.d.ts +80 -36
  77. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  78. package/dist/apps/data/routes/funding.js +65 -31
  79. package/dist/apps/data/routes/funding.js.map +1 -1
  80. package/dist/apps/data/routes/receipts.d.ts +392 -315
  81. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  82. package/dist/apps/data/routes/receipts.js +152 -77
  83. package/dist/apps/data/routes/receipts.js.map +1 -1
  84. package/dist/apps/data/routes/tokens.d.ts +167 -174
  85. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  86. package/dist/apps/data/routes/tokens.js +15 -13
  87. package/dist/apps/data/routes/tokens.js.map +1 -1
  88. package/dist/apps/data/routes/transactions.d.ts +277 -536
  89. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  90. package/dist/apps/data/routes/transactions.js +96 -77
  91. package/dist/apps/data/routes/transactions.js.map +1 -1
  92. package/dist/apps/data/routes/transfers.d.ts +157 -81
  93. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  94. package/dist/apps/data/routes/transfers.js +272 -130
  95. package/dist/apps/data/routes/transfers.js.map +1 -1
  96. package/dist/apps/data/routes/valuation.d.ts +307 -0
  97. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  98. package/dist/apps/data/routes/valuation.js +292 -0
  99. package/dist/apps/data/routes/valuation.js.map +1 -0
  100. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  101. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  102. package/dist/apps/data/routes/verified-tokens.js +7 -5
  103. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  104. package/dist/apps/data/routes/webhooks.d.ts +103 -123
  105. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  106. package/dist/apps/data/routes/webhooks.js +124 -25
  107. package/dist/apps/data/routes/webhooks.js.map +1 -1
  108. package/dist/apps/index.d.ts +2 -1
  109. package/dist/apps/index.d.ts.map +1 -1
  110. package/dist/apps/index.js +2 -1
  111. package/dist/apps/index.js.map +1 -1
  112. package/dist/apps/management/App.d.ts +8554 -1692
  113. package/dist/apps/management/App.d.ts.map +1 -1
  114. package/dist/apps/management/App.js +12 -1
  115. package/dist/apps/management/App.js.map +1 -1
  116. package/dist/apps/management/routes/api-keys.d.ts +7 -0
  117. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  118. package/dist/apps/management/routes/api-keys.js +17 -3
  119. package/dist/apps/management/routes/api-keys.js.map +1 -1
  120. package/dist/apps/management/routes/billing.d.ts +157 -162
  121. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  122. package/dist/apps/management/routes/billing.js +31 -23
  123. package/dist/apps/management/routes/billing.js.map +1 -1
  124. package/dist/apps/management/routes/faucet.d.ts +232 -0
  125. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  126. package/dist/apps/management/routes/faucet.js +154 -0
  127. package/dist/apps/management/routes/faucet.js.map +1 -0
  128. package/dist/apps/management/routes/invitations.d.ts +60 -60
  129. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  130. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  131. package/dist/apps/management/routes/invite-links.js +26 -1
  132. package/dist/apps/management/routes/invite-links.js.map +1 -1
  133. package/dist/apps/management/routes/me.d.ts +6 -6
  134. package/dist/apps/management/routes/members.d.ts +6 -6
  135. package/dist/apps/management/routes/orgs.d.ts +42 -42
  136. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  137. package/dist/apps/management/routes/orgs.js +5 -0
  138. package/dist/apps/management/routes/orgs.js.map +1 -1
  139. package/dist/apps/management/routes/projects.d.ts +44 -44
  140. package/dist/apps/management/routes/usage.d.ts +3 -0
  141. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  142. package/dist/apps/management/routes/usage.js +4 -0
  143. package/dist/apps/management/routes/usage.js.map +1 -1
  144. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  145. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  146. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  147. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  148. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  149. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  150. package/dist/apps/management/routes/webhooks.js +573 -0
  151. package/dist/apps/management/routes/webhooks.js.map +1 -0
  152. package/dist/apps/mpp/App.d.ts +363 -0
  153. package/dist/apps/mpp/App.d.ts.map +1 -0
  154. package/dist/apps/mpp/App.js +72 -0
  155. package/dist/apps/mpp/App.js.map +1 -0
  156. package/dist/apps/mpp/Mpp.d.ts +178 -0
  157. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  158. package/dist/apps/mpp/Mpp.js +183 -0
  159. package/dist/apps/mpp/Mpp.js.map +1 -0
  160. package/dist/apps/mpp/index.d.ts +3 -0
  161. package/dist/apps/mpp/index.d.ts.map +1 -0
  162. package/dist/apps/mpp/index.js +3 -0
  163. package/dist/apps/mpp/index.js.map +1 -0
  164. package/dist/apps/relay/App.d.ts.map +1 -1
  165. package/dist/apps/relay/App.js +56 -9
  166. package/dist/apps/relay/App.js.map +1 -1
  167. package/dist/db/Db.d.ts +6 -0
  168. package/dist/db/Db.d.ts.map +1 -1
  169. package/dist/db/Db.js +110 -0
  170. package/dist/db/Db.js.map +1 -1
  171. package/dist/db/Schema.d.ts +53 -0
  172. package/dist/db/Schema.d.ts.map +1 -1
  173. package/dist/db/Schema.js +56 -0
  174. package/dist/db/Schema.js.map +1 -1
  175. package/dist/db/tables/earlyAccess.d.ts +50 -0
  176. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  177. package/dist/db/tables/earlyAccess.js +63 -0
  178. package/dist/db/tables/earlyAccess.js.map +1 -0
  179. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  180. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  181. package/dist/db/tables/enabledBillingSources.js +69 -0
  182. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  183. package/dist/db/tables/invitations.d.ts.map +1 -1
  184. package/dist/db/tables/invitations.js +3 -0
  185. package/dist/db/tables/invitations.js.map +1 -1
  186. package/dist/db/tables/inviteLinks.d.ts +10 -2
  187. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  188. package/dist/db/tables/inviteLinks.js +10 -1
  189. package/dist/db/tables/inviteLinks.js.map +1 -1
  190. package/dist/db/tables/organizations.d.ts +5 -2
  191. package/dist/db/tables/organizations.d.ts.map +1 -1
  192. package/dist/db/tables/organizations.js +13 -2
  193. package/dist/db/tables/organizations.js.map +1 -1
  194. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  195. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  196. package/dist/db/tables/sponsoredTransactions.js +2 -0
  197. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  198. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  199. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  200. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  201. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  202. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  203. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  204. package/dist/db/tables/webhookSubscriptions.js +16 -0
  205. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  206. package/dist/handlers/internal/utils.d.ts +1 -16
  207. package/dist/handlers/internal/utils.d.ts.map +1 -1
  208. package/dist/handlers/internal/utils.js +17 -6
  209. package/dist/handlers/internal/utils.js.map +1 -1
  210. package/dist/handlers/relay.d.ts.map +1 -1
  211. package/dist/handlers/relay.js +37 -5
  212. package/dist/handlers/relay.js.map +1 -1
  213. package/dist/internal/Auth.d.ts +11 -2
  214. package/dist/internal/Auth.d.ts.map +1 -1
  215. package/dist/internal/Auth.js +14 -47
  216. package/dist/internal/Auth.js.map +1 -1
  217. package/dist/internal/FundingQuotes.d.ts +147 -47
  218. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  219. package/dist/internal/FundingQuotes.js +119 -33
  220. package/dist/internal/FundingQuotes.js.map +1 -1
  221. package/dist/internal/Log.d.ts +37 -5
  222. package/dist/internal/Log.d.ts.map +1 -1
  223. package/dist/internal/Log.js +52 -3
  224. package/dist/internal/Log.js.map +1 -1
  225. package/dist/internal/MetricSink.d.ts.map +1 -1
  226. package/dist/internal/MetricSink.js +24 -0
  227. package/dist/internal/MetricSink.js.map +1 -1
  228. package/dist/internal/OpenApi.d.ts +2 -2
  229. package/dist/internal/OpenApi.d.ts.map +1 -1
  230. package/dist/internal/OpenApi.js +3 -0
  231. package/dist/internal/OpenApi.js.map +1 -1
  232. package/dist/internal/Response.d.ts +2 -2
  233. package/dist/internal/Response.d.ts.map +1 -1
  234. package/dist/internal/Response.js +12 -4
  235. package/dist/internal/Response.js.map +1 -1
  236. package/dist/internal/Schema.d.ts +14 -5
  237. package/dist/internal/Schema.d.ts.map +1 -1
  238. package/dist/internal/Schema.js +23 -5
  239. package/dist/internal/Schema.js.map +1 -1
  240. package/dist/internal/Value.d.ts +18 -0
  241. package/dist/internal/Value.d.ts.map +1 -1
  242. package/dist/internal/Value.js +11 -1
  243. package/dist/internal/Value.js.map +1 -1
  244. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  245. package/dist/internal/VerifiedTokens.js +10 -1
  246. package/dist/internal/VerifiedTokens.js.map +1 -1
  247. package/dist/internal/Webhooks.d.ts +24 -3
  248. package/dist/internal/Webhooks.d.ts.map +1 -1
  249. package/dist/internal/Webhooks.js +34 -8
  250. package/dist/internal/Webhooks.js.map +1 -1
  251. package/package.json +7 -2
  252. package/src/App.test.ts +9 -4
  253. package/src/App.ts +23 -3
  254. package/src/Client.test-d.ts +733 -20
  255. package/src/Scope.ts +5 -0
  256. package/src/admin/App.test.ts +4 -0
  257. package/src/admin/App.ts +4 -0
  258. package/src/admin/apps/early-access.test.ts +125 -0
  259. package/src/admin/apps/early-access.ts +176 -0
  260. package/src/admin/apps/organizations.test.ts +86 -2
  261. package/src/admin/apps/organizations.ts +155 -31
  262. package/src/admin/apps/scopes.test.ts +5 -0
  263. package/src/admin/apps/verified-tokens.test.ts +201 -0
  264. package/src/admin/apps/verified-tokens.ts +532 -286
  265. package/src/admin/ui/plugin.ts +2 -1
  266. package/src/admin/ui/src/lib/early-access.ts +63 -0
  267. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  268. package/src/admin/ui/src/lib/organizations.ts +92 -0
  269. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  270. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  271. package/src/admin/ui/src/routes/__root.tsx +2 -1
  272. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  273. package/src/admin/ui/src/routes/early-access.tsx +162 -0
  274. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  275. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  276. package/src/admin/ui.generated.ts +95 -94
  277. package/src/analytics/Analytics.test.ts +83 -0
  278. package/src/analytics/Analytics.ts +20 -0
  279. package/src/analytics/tables/requestEvents.ts +60 -7
  280. package/src/apps/composition.test.ts +11 -2
  281. package/src/apps/data/App.test.ts +1 -1
  282. package/src/apps/data/App.ts +22 -14
  283. package/src/apps/data/FundingQuoteProviders.test.ts +297 -4
  284. package/src/apps/data/FundingQuoteProviders.ts +255 -29
  285. package/src/apps/data/FxOracle.test.ts +118 -0
  286. package/src/apps/data/FxOracle.ts +153 -0
  287. package/src/apps/data/index.ts +1 -0
  288. package/src/apps/data/routes/activities.test.ts +177 -3
  289. package/src/apps/data/routes/activities.ts +441 -132
  290. package/src/apps/data/routes/addresses.ts +19 -9
  291. package/src/apps/data/routes/balances.test.ts +148 -11
  292. package/src/apps/data/routes/balances.ts +140 -51
  293. package/src/apps/data/routes/exchanges.test.ts +544 -9
  294. package/src/apps/data/routes/exchanges.ts +690 -103
  295. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  296. package/src/apps/data/routes/fee-amm.ts +98 -119
  297. package/src/apps/data/routes/funding.test.ts +113 -8
  298. package/src/apps/data/routes/funding.ts +77 -51
  299. package/src/apps/data/routes/receipts.test.ts +129 -23
  300. package/src/apps/data/routes/receipts.ts +205 -100
  301. package/src/apps/data/routes/tokens.test.ts +43 -0
  302. package/src/apps/data/routes/tokens.ts +28 -13
  303. package/src/apps/data/routes/transactions.test.ts +72 -19
  304. package/src/apps/data/routes/transactions.ts +162 -92
  305. package/src/apps/data/routes/transfers.test.ts +341 -31
  306. package/src/apps/data/routes/transfers.ts +358 -158
  307. package/src/apps/data/routes/valuation.test.ts +437 -0
  308. package/src/apps/data/routes/valuation.ts +423 -0
  309. package/src/apps/data/routes/verified-tokens.ts +14 -2
  310. package/src/apps/data/routes/webhooks.test.ts +56 -8
  311. package/src/apps/data/routes/webhooks.ts +144 -27
  312. package/src/apps/index.ts +2 -1
  313. package/src/apps/management/App.ts +13 -0
  314. package/src/apps/management/routes/api-keys.test.ts +14 -0
  315. package/src/apps/management/routes/api-keys.ts +27 -3
  316. package/src/apps/management/routes/billing.test.ts +152 -20
  317. package/src/apps/management/routes/billing.ts +34 -18
  318. package/src/apps/management/routes/faucet.test.ts +87 -0
  319. package/src/apps/management/routes/faucet.ts +201 -0
  320. package/src/apps/management/routes/invite-links.test.ts +117 -0
  321. package/src/apps/management/routes/invite-links.ts +43 -1
  322. package/src/apps/management/routes/me.test.ts +9 -99
  323. package/src/apps/management/routes/orgs.test.ts +45 -2
  324. package/src/apps/management/routes/orgs.ts +6 -0
  325. package/src/apps/management/routes/scopes.test.ts +1 -0
  326. package/src/apps/management/routes/usage.test.ts +6 -0
  327. package/src/apps/management/routes/usage.ts +7 -0
  328. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  329. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  330. package/src/apps/management/routes/webhooks.test.ts +426 -0
  331. package/src/apps/management/routes/webhooks.ts +672 -0
  332. package/src/apps/mpp/App.ts +86 -0
  333. package/src/apps/mpp/Mpp.test.ts +193 -0
  334. package/src/apps/mpp/Mpp.ts +353 -0
  335. package/src/apps/mpp/index.ts +2 -0
  336. package/src/apps/mpp/internal/Adapter.ts +11 -0
  337. package/src/apps/mpp/internal/Error.ts +9 -0
  338. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  339. package/src/apps/mpp/internal/Screening.ts +10 -0
  340. package/src/apps/relay/App.test.ts +88 -6
  341. package/src/apps/relay/App.ts +61 -9
  342. package/src/db/Db.test.ts +19 -0
  343. package/src/db/Db.ts +125 -0
  344. package/src/db/Schema.ts +68 -0
  345. package/src/db/tables/earlyAccess.ts +86 -0
  346. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  347. package/src/db/tables/enabledBillingSources.ts +118 -0
  348. package/src/db/tables/invitations.ts +3 -0
  349. package/src/db/tables/inviteLinks.ts +22 -3
  350. package/src/db/tables/organizations.test.ts +103 -0
  351. package/src/db/tables/organizations.ts +18 -2
  352. package/src/db/tables/sponsoredTransactions.ts +4 -0
  353. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  354. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  355. package/src/db/tables/webhookSubscriptions.ts +31 -0
  356. package/src/handlers/internal/utils.ts +22 -6
  357. package/src/handlers/relay.errors.test.ts +153 -0
  358. package/src/handlers/relay.ts +42 -7
  359. package/src/internal/Auth.test.ts +3 -3
  360. package/src/internal/Auth.ts +15 -47
  361. package/src/internal/FundingQuotes.test.ts +161 -6
  362. package/src/internal/FundingQuotes.ts +141 -39
  363. package/src/internal/Log.test-d.ts +21 -0
  364. package/src/internal/Log.test.ts +141 -0
  365. package/src/internal/Log.ts +93 -6
  366. package/src/internal/MetricSink.test.ts +215 -0
  367. package/src/internal/MetricSink.ts +24 -0
  368. package/src/internal/OpenApi.ts +3 -0
  369. package/src/internal/Response.test.ts +28 -0
  370. package/src/internal/Response.ts +12 -4
  371. package/src/internal/Schema.ts +50 -5
  372. package/src/internal/Value.test.ts +20 -0
  373. package/src/internal/Value.ts +24 -1
  374. package/src/internal/VerifiedTokens.ts +9 -1
  375. package/src/internal/Webhooks.test.ts +140 -0
  376. package/src/internal/Webhooks.ts +53 -9
@@ -1,12 +1,45 @@
1
1
  import type * as z from 'zod/mini'
2
- import { Schema } from 'tapimo'
2
+ import { Address, Value as core_Value } from 'ox'
3
+ import { Db, Schema } from 'tapimo'
4
+ import { FxOracle } from 'tapimo/apps'
5
+ import { decodeFunctionData, zeroHash } from 'viem'
6
+ import { Abis, Actions, Addresses } from 'viem/tempo'
3
7
 
4
8
  import * as TestApp from '../../../../test/App.js'
9
+ import * as Relay from '../../../../test/Relay.js'
10
+ import * as Runtime from '../../../../test/runtime.js'
5
11
  import * as Exchanges from './exchanges.js'
6
12
 
13
+ type JsonSchema = {
14
+ examples?: readonly unknown[]
15
+ oneOf?: readonly JsonSchema[]
16
+ properties?: Record<string, JsonSchema>
17
+ required?: readonly string[]
18
+ }
7
19
  type OrdersResponse = z.output<typeof Exchanges.schema.getOrders.Response>
20
+ type OpenApiDocument = {
21
+ paths: Record<
22
+ string,
23
+ {
24
+ post?: {
25
+ requestBody?: { content?: Record<string, { schema?: JsonSchema }> }
26
+ responses?: Record<string, { content?: Record<string, { schema?: JsonSchema }> }>
27
+ }
28
+ }
29
+ >
30
+ }
8
31
  type PairsResponse = z.output<typeof Exchanges.schema.getPairs.Response>
9
32
 
33
+ // Deterministic EUR-based rate set so conversion math is exact in assertions.
34
+ const fixed = FxOracle.from({
35
+ name: 'fixed',
36
+ rates: async () => ({
37
+ asOf: '2026-01-01T00:00:00.000Z',
38
+ base: 'EUR',
39
+ rates: { AUD: '1.6', USD: '1.0' },
40
+ }),
41
+ })
42
+
10
43
  type Client = ReturnType<typeof TestApp.client>
11
44
  type OrdersQuery = {
12
45
  chainId?: string
@@ -569,6 +602,68 @@ describe('GET /exchange/pairs/:base/depth', () => {
569
602
  describe('GET /exchange/swaps', () => {
570
603
  type GlobalSwapsResponse = z.output<typeof Exchanges.schema.getSwaps.Response>
571
604
 
605
+ test('validates denominations without swap rows', async () => {
606
+ const client = TestApp.client({ fx: { oracle: fixed } })
607
+ const response = await client.v1.exchange.swaps.$get(
608
+ { query: { denomination: 'JPY', transactionHash: zeroHash } },
609
+ TestApp.auth,
610
+ )
611
+ expect(response.status).toBe(400)
612
+ })
613
+
614
+ test('parses granular token includes', () => {
615
+ expect(
616
+ Exchanges.schema.getSwaps.Query.parse({
617
+ include: 'token.logoUri,token.verified',
618
+ }).include,
619
+ ).toMatchInlineSnapshot(`
620
+ [
621
+ "token.logoUri",
622
+ "token.verified",
623
+ ]
624
+ `)
625
+ expect(Exchanges.schema.getSwaps.Query.safeParse({ include: 'tokens' }).success).toBe(false)
626
+ })
627
+
628
+ test.skipIf(Runtime.get().mode !== 'localnet')(
629
+ 'values swap legs in a requested denomination',
630
+ async () => {
631
+ const db = TestApp.database()
632
+ await TestApp.verifiedSeed(db, Runtime.get().chainId)
633
+ const client = TestApp.client({ db, fx: { oracle: fixed } })
634
+
635
+ const response = await client.v1.exchange.swaps.$get(
636
+ { query: { denomination: 'AUD', limit: '10' } },
637
+ TestApp.auth,
638
+ )
639
+ expect(response.status).toBe(200)
640
+ const body = await TestApp.json(response, Exchanges.schema.getSwaps.Response)
641
+ expect(body.data.length).toBeGreaterThan(0)
642
+
643
+ // Curated legs value at the fixed 1.6 USD -> AUD rate; uncurated
644
+ // created-token legs stay null.
645
+ const legs = body.data.flatMap((swap) => [
646
+ swap.sourceAmount,
647
+ swap.destinationAmount,
648
+ ...swap.fills.flatMap((fill) => [fill.sourceAmount, fill.destinationAmount]),
649
+ ])
650
+ const valued = legs.filter((leg) => leg.valuation)
651
+ expect(valued.length).toBeGreaterThan(0)
652
+ for (const leg of valued)
653
+ expect(leg.valuation).toEqual({
654
+ amount: core_Value.format((BigInt(leg.baseUnits) * 16n) / 10n, 6),
655
+ currency: 'AUD',
656
+ })
657
+ expect(body.meta?.pricing).toMatchInlineSnapshot(`
658
+ {
659
+ "asOf": "2026-01-01T00:00:00.000Z",
660
+ "basis": "nominal",
661
+ "source": "fixed",
662
+ }
663
+ `)
664
+ },
665
+ )
666
+
572
667
  test('returns a global, newest-first page of swaps across all pairs', async () => {
573
668
  const client = TestApp.client()
574
669
  const response = await client.v1.exchange.swaps.$get({ query: { limit: '10' } }, TestApp.auth)
@@ -582,9 +677,29 @@ describe('GET /exchange/swaps', () => {
582
677
  expect(swap.transactionHash).toMatch(/^0x[0-9a-f]{64}$/)
583
678
  expect(['exactSource', 'exactDestination', null]).toContain(swap.mode)
584
679
  expect(swap.sourceToken.address).toMatch(/^0x[0-9a-f]{40}$/)
585
- expect(swap.sourceToken.amount).toMatch(/^\d+$/)
680
+ expect(swap.sourceToken).not.toHaveProperty('amount')
681
+ expect(typeof swap.sourceToken.currency).toBe('string')
682
+ expect(typeof swap.sourceToken.decimals).toBe('number')
683
+ expect(typeof swap.sourceToken.name).toBe('string')
684
+ expect(typeof swap.sourceToken.symbol).toBe('string')
685
+ expect(swap.sourceToken.logoUri).toBeUndefined()
686
+ expect(swap.sourceToken.verified).toBeUndefined()
687
+ expect(swap.sourceAmount.baseUnits).toMatch(/^\d+$/)
688
+ expect(swap.sourceAmount.currency).toBe(swap.sourceToken.currency)
689
+ expect(swap.sourceAmount.decimals).toBe(swap.sourceToken.decimals)
690
+ expect(swap.sourceAmount.formatted).toMatch(/^\d+(\.\d+)?$/)
586
691
  expect(swap.destinationToken.address).toMatch(/^0x[0-9a-f]{40}$/)
587
- expect(swap.destinationToken.amount).toMatch(/^\d+$/)
692
+ expect(swap.destinationToken).not.toHaveProperty('amount')
693
+ expect(typeof swap.destinationToken.currency).toBe('string')
694
+ expect(typeof swap.destinationToken.decimals).toBe('number')
695
+ expect(typeof swap.destinationToken.name).toBe('string')
696
+ expect(typeof swap.destinationToken.symbol).toBe('string')
697
+ expect(swap.destinationToken.logoUri).toBeUndefined()
698
+ expect(swap.destinationToken.verified).toBeUndefined()
699
+ expect(swap.destinationAmount.baseUnits).toMatch(/^\d+$/)
700
+ expect(swap.destinationAmount.currency).toBe(swap.destinationToken.currency)
701
+ expect(swap.destinationAmount.decimals).toBe(swap.destinationToken.decimals)
702
+ expect(swap.destinationAmount.formatted).toMatch(/^\d+(\.\d+)?$/)
588
703
  expect(swap.rate).toMatch(/^\d+(\.\d+)?$/)
589
704
  expect(swap.filledAt).toMatch(/^\d{4}-\d{2}-\d{2}T/)
590
705
  // `route` spans source → destination; its ends mirror the side tokens.
@@ -600,6 +715,14 @@ describe('GET /exchange/swaps', () => {
600
715
  expect(fill.price).toMatch(/^\d+(\.\d+)?$/)
601
716
  expect(swap.route).toContain(fill.sourceToken.address)
602
717
  expect(swap.route).toContain(fill.destinationToken.address)
718
+ expect(fill.sourceToken).not.toHaveProperty('amount')
719
+ expect(fill.sourceAmount.baseUnits).toMatch(/^\d+$/)
720
+ expect(fill.sourceAmount.currency).toBe(fill.sourceToken.currency)
721
+ expect(fill.sourceAmount.decimals).toBe(fill.sourceToken.decimals)
722
+ expect(fill.destinationToken).not.toHaveProperty('amount')
723
+ expect(fill.destinationAmount.baseUnits).toMatch(/^\d+$/)
724
+ expect(fill.destinationAmount.currency).toBe(fill.destinationToken.currency)
725
+ expect(fill.destinationAmount.decimals).toBe(fill.destinationToken.decimals)
603
726
  }
604
727
  for (let i = 1; i < swap.fills.length; i++)
605
728
  expect(swap.fills[i]!.logIndex).toBeGreaterThan(swap.fills[i - 1]!.logIndex)
@@ -627,6 +750,9 @@ describe('GET /exchange/swaps', () => {
627
750
  TestApp.auth,
628
751
  )
629
752
  const secondBody = await TestApp.json(second, Exchanges.schema.getSwaps.Response)
753
+ expect(second.headers.get('cache-control')).toMatchInlineSnapshot(
754
+ `"private, max-age=60, stale-while-revalidate=300"`,
755
+ )
630
756
 
631
757
  // A swap row is unique on `(block, first fill logIndex)`.
632
758
  const key = (swap: GlobalSwapsResponse['data'][number]) =>
@@ -669,24 +795,433 @@ describe('GET /exchange/swaps', () => {
669
795
  for (const swap of body.data) expect(swap.fills.some((fill) => fill.maker === maker)).toBe(true)
670
796
  }, 60000)
671
797
 
672
- test('embeds token metadata on swap sides when requested via `include=tokens`', async () => {
798
+ test('includes curated token fields on swap sides and fills', async () => {
673
799
  const client = TestApp.client()
674
800
  const response = await client.v1.exchange.swaps.$get(
675
- { query: { include: 'tokens', limit: '10' } },
801
+ { query: { include: 'token.logoUri,token.verified', limit: '10' } },
676
802
  TestApp.auth,
677
803
  )
678
804
  expect(response.status).toBe(200)
679
805
  const body = await TestApp.json(response, Exchanges.schema.getSwaps.Response)
680
806
  if (body.data.length === 0) return
681
807
 
682
- const enriched = body.data.find((swap) => typeof swap.sourceToken.symbol === 'string')
683
- if (enriched) {
684
- expect(typeof enriched.sourceToken.symbol).toBe('string')
685
- expect(typeof enriched.sourceToken.decimals).toBe('number')
808
+ const enriched = body.data[0]!
809
+ expect(typeof enriched.sourceToken.verified).toBe('boolean')
810
+ expect(typeof enriched.destinationToken.verified).toBe('boolean')
811
+ for (const fill of enriched.fills) {
812
+ expect(typeof fill.sourceToken.verified).toBe('boolean')
813
+ expect(typeof fill.destinationToken.verified).toBe('boolean')
686
814
  }
687
815
  }, 60000)
688
816
  })
689
817
 
818
+ describe('POST /exchange/quotes', () => {
819
+ test('validates denominations without verification data', async () => {
820
+ const db = () =>
821
+ Db.postgres({ connectionString: 'postgresql://postgres:postgres@127.0.0.1:1/none' })
822
+ const client = TestApp.client({ db, fx: { oracle: fixed } })
823
+ const response = await client.v1.exchange.quotes.$post(
824
+ {
825
+ json: {
826
+ amount: '1',
827
+ destinationToken: Addresses.pathUsd,
828
+ mode: 'exactSource',
829
+ slippageBps: 50,
830
+ sourceToken: '0x20c0000000000000000000008f5425160ebe5525',
831
+ },
832
+ query: { denomination: 'JPY' },
833
+ },
834
+ TestApp.auth,
835
+ )
836
+ expect(response.status).toBe(400)
837
+ })
838
+
839
+ test('parses granular token includes', () => {
840
+ expect(
841
+ Exchanges.schema.createQuote.Query.parse({
842
+ include: 'token.logoUri,token.verified',
843
+ }).include,
844
+ ).toMatchInlineSnapshot(`
845
+ [
846
+ "token.logoUri",
847
+ "token.verified",
848
+ ]
849
+ `)
850
+ expect(Exchanges.schema.createQuote.Query.safeParse({ include: 'tokens' }).success).toBe(false)
851
+ })
852
+
853
+ test.each([
854
+ {
855
+ destinationToken: '0x20c0000000000000000000008f5425160ebe5525' as const,
856
+ label: 'identical tokens',
857
+ slippageBps: 50,
858
+ sourceToken: '0x20c0000000000000000000008f5425160ebe5525' as const,
859
+ },
860
+ {
861
+ destinationToken: '0x20c000000000000000000000b9537d11c60e8b50' as const,
862
+ label: 'invalid slippage',
863
+ slippageBps: 10_000,
864
+ sourceToken: '0x20c0000000000000000000008f5425160ebe5525' as const,
865
+ },
866
+ ])('rejects $label', async ({ destinationToken, slippageBps, sourceToken }) => {
867
+ const response = await TestApp.client().v1.exchange.quotes.$post(
868
+ {
869
+ json: {
870
+ amount: '1000000',
871
+ destinationToken,
872
+ mode: 'exactSource',
873
+ slippageBps,
874
+ sourceToken,
875
+ },
876
+ query: {},
877
+ },
878
+ TestApp.auth,
879
+ )
880
+ expect(response.status).toBe(400)
881
+ expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
882
+ })
883
+
884
+ test('publishes valid request and executable call examples', async () => {
885
+ const app = TestApp.create({ auth: false })
886
+ const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument
887
+ const operation = spec.paths['/v1/exchange/quotes']?.post
888
+ if (!operation) throw new Error('Missing create quote OpenAPI operation')
889
+
890
+ const requestSchema = operation.requestBody?.content?.['application/json']?.schema
891
+ const requestExample = Object.fromEntries(
892
+ ['amount', 'destinationToken', 'mode', 'slippageBps', 'sourceToken'].map((property) => [
893
+ property,
894
+ requestSchema?.properties?.[property]?.examples?.[0],
895
+ ]),
896
+ )
897
+ expect(requestExample).toEqual({
898
+ amount: '1000000',
899
+ destinationToken: '0x20c000000000000000000000b9537d11c60e8b50',
900
+ mode: 'exactSource',
901
+ slippageBps: 50,
902
+ sourceToken: '0x20c0000000000000000000008f5425160ebe5525',
903
+ })
904
+ expect(Exchanges.schema.createQuote.Request.safeParse(requestExample).success).toBe(true)
905
+
906
+ const responseSchema = operation.responses?.['200']?.content?.['application/json']?.schema
907
+ for (const [variant, names] of [
908
+ [responseSchema?.oneOf?.[0], ['destinationAmount', 'destinationAmountMin', 'sourceAmount']],
909
+ [responseSchema?.oneOf?.[1], ['destinationAmount', 'sourceAmount', 'sourceAmountMax']],
910
+ ] as const)
911
+ for (const name of names) {
912
+ const amount = variant?.properties?.[name]
913
+ expect(amount?.required).toEqual(['baseUnits', 'currency', 'decimals', 'formatted'])
914
+ expect(
915
+ Object.fromEntries(
916
+ ['baseUnits', 'currency', 'decimals', 'formatted'].map((property) => [
917
+ property,
918
+ amount?.properties?.[property]?.examples?.[0],
919
+ ]),
920
+ ),
921
+ ).toEqual({ baseUnits: '1000000', currency: 'USD', decimals: 6, formatted: '1' })
922
+ }
923
+
924
+ for (const variant of responseSchema?.oneOf ?? [])
925
+ for (const name of ['destinationToken', 'sourceToken']) {
926
+ const token = variant.properties?.[name]
927
+ expect(token?.required).toEqual(['address', 'currency', 'decimals', 'name', 'symbol'])
928
+ expect(token?.properties).not.toHaveProperty('amount')
929
+ expect(token?.properties).not.toHaveProperty('maximumAmount')
930
+ expect(token?.properties).not.toHaveProperty('minimumAmount')
931
+ expect(token?.required).not.toContain('logoUri')
932
+ expect(token?.required).not.toContain('verified')
933
+ }
934
+
935
+ const callsExample =
936
+ responseSchema?.oneOf?.[0]?.properties?.['transaction']?.properties?.['calls']?.examples?.[0]
937
+ if (!Array.isArray(callsExample)) throw new Error('Missing create quote call examples')
938
+ const calls = callsExample.map((call) => Exchanges.schema.createQuote.Call.parse(call))
939
+ expect(decodeFunctionData({ abi: Abis.tip20, data: calls[0]!.data })).toEqual({
940
+ args: [Address.checksum(Addresses.stablecoinDex), 1_000_000n],
941
+ functionName: 'approve',
942
+ })
943
+ expect(decodeFunctionData({ abi: Abis.stablecoinDex, data: calls[1]!.data })).toEqual({
944
+ args: [
945
+ Address.checksum('0x20c0000000000000000000008f5425160ebe5525'),
946
+ Address.checksum('0x20c000000000000000000000b9537d11c60e8b50'),
947
+ 1_000_000n,
948
+ 995_000n,
949
+ ],
950
+ functionName: 'swapExactAmountIn',
951
+ })
952
+ })
953
+
954
+ describe.skipIf(Runtime.get().mode !== 'localnet')('localnet quotes', () => {
955
+ const account = Relay.accounts[1]!
956
+ let baseToken: z.output<typeof Schema.TokenAddress>
957
+
958
+ beforeAll(async () => {
959
+ const rpc = Relay.getClient({ account })
960
+ const created = await Actions.token.createSync(rpc, {
961
+ currency: 'USD',
962
+ name: 'Quote Base',
963
+ symbol: 'QBASE',
964
+ })
965
+ baseToken = Schema.TokenAddress.parse(created.token)
966
+ await Actions.token.grantRolesSync(rpc, {
967
+ roles: ['issuer', 'pause', 'unpause'],
968
+ to: account.address,
969
+ token: baseToken,
970
+ })
971
+ await Actions.token.mintSync(rpc, {
972
+ amount: 2_000_000_000n,
973
+ to: account.address,
974
+ token: baseToken,
975
+ })
976
+ await Actions.token.approveSync(rpc, {
977
+ amount: 1_000_000_000n,
978
+ spender: Addresses.stablecoinDex,
979
+ token: baseToken,
980
+ })
981
+ await Actions.token.approveSync(rpc, {
982
+ amount: 1_000_000_000n,
983
+ spender: Addresses.stablecoinDex,
984
+ token: Addresses.pathUsd,
985
+ })
986
+ await Actions.dex.createPairSync(rpc, { base: baseToken })
987
+ await Actions.dex.placeSync(rpc, {
988
+ amount: 1_000_000_000n,
989
+ tick: 0,
990
+ token: baseToken,
991
+ type: 'buy',
992
+ })
993
+ await Actions.dex.placeSync(rpc, {
994
+ amount: 1_000_000_000n,
995
+ tick: -10,
996
+ token: baseToken,
997
+ type: 'sell',
998
+ })
999
+ }, 120_000)
1000
+
1001
+ test('quotes exact-source liquidity and returns approval then sell calls', async () => {
1002
+ // Pin an empty curated list so both legs deterministically value `null`.
1003
+ const db = TestApp.database()
1004
+ await TestApp.verifiedSeed(db, Runtime.get().chainId, { tokens: [] })
1005
+ const client = TestApp.client({ db })
1006
+ const request = {
1007
+ amount: '1',
1008
+ destinationToken: Addresses.pathUsd,
1009
+ mode: 'exactSource',
1010
+ slippageBps: 50,
1011
+ sourceToken: baseToken,
1012
+ } as const
1013
+ const response = await client.v1.exchange.quotes.$post(
1014
+ { json: request, query: { include: 'token.logoUri,token.verified' } },
1015
+ TestApp.auth,
1016
+ )
1017
+ expect(response.status).toBe(200)
1018
+ if (response.status !== 200) throw new Error(await response.text())
1019
+
1020
+ const body = await TestApp.json(response, Exchanges.schema.createQuote.Response)
1021
+ if (body.mode !== 'exactSource') throw new Error('Expected an exact-source exchange quote')
1022
+ expect(body.destinationAmount).toEqual({
1023
+ baseUnits: '1',
1024
+ currency: 'USD',
1025
+ decimals: 6,
1026
+ formatted: '0.000001',
1027
+ valuation: null,
1028
+ })
1029
+ expect(body.destinationToken).toMatchObject({
1030
+ address: Addresses.pathUsd,
1031
+ })
1032
+ expect(body.destinationToken).not.toHaveProperty('amount')
1033
+ expect(body.destinationToken).not.toHaveProperty('minimumAmount')
1034
+ expect(typeof body.destinationToken.symbol).toBe('string')
1035
+ expect(typeof body.destinationToken.verified).toBe('boolean')
1036
+ expect(body.destinationAmountMin).toEqual(body.destinationAmount)
1037
+ expect(body.sourceAmount).toEqual({
1038
+ baseUnits: '1',
1039
+ currency: 'USD',
1040
+ decimals: 6,
1041
+ formatted: '0.000001',
1042
+ valuation: null,
1043
+ })
1044
+ expect(body.sourceToken).toMatchObject({
1045
+ address: baseToken,
1046
+ name: 'Quote Base',
1047
+ symbol: 'QBASE',
1048
+ })
1049
+ expect(body.sourceToken).not.toHaveProperty('amount')
1050
+ expect(typeof body.sourceToken.verified).toBe('boolean')
1051
+ expect(body.transaction.calls).toHaveLength(2)
1052
+ expect(body.transaction.calls[0]!.to).toBe(baseToken)
1053
+ expect(body.transaction.calls[1]!.to).toBe(Addresses.stablecoinDex.toLowerCase())
1054
+ expect(
1055
+ decodeFunctionData({ abi: Abis.tip20, data: body.transaction.calls[0]!.data }),
1056
+ ).toEqual({
1057
+ args: [Address.checksum(Addresses.stablecoinDex), 1n],
1058
+ functionName: 'approve',
1059
+ })
1060
+ expect(
1061
+ decodeFunctionData({ abi: Abis.stablecoinDex, data: body.transaction.calls[1]!.data }),
1062
+ ).toEqual({
1063
+ args: [Address.checksum(baseToken), Address.checksum(Addresses.pathUsd), 1n, 1n],
1064
+ functionName: 'swapExactAmountIn',
1065
+ })
1066
+ }, 60_000)
1067
+
1068
+ test('quotes exact-destination liquidity and returns approval then buy calls', async () => {
1069
+ // Pin an empty curated list so both legs deterministically value `null`.
1070
+ const db = TestApp.database()
1071
+ await TestApp.verifiedSeed(db, Runtime.get().chainId, { tokens: [] })
1072
+ const client = TestApp.client({ db })
1073
+ const request = {
1074
+ amount: '1',
1075
+ destinationToken: baseToken,
1076
+ mode: 'exactDestination',
1077
+ slippageBps: 50,
1078
+ sourceToken: Addresses.pathUsd,
1079
+ } as const
1080
+ const response = await client.v1.exchange.quotes.$post(
1081
+ { json: request, query: {} },
1082
+ TestApp.auth,
1083
+ )
1084
+ expect(response.status).toBe(200)
1085
+ if (response.status !== 200) throw new Error(await response.text())
1086
+
1087
+ const body = await TestApp.json(response, Exchanges.schema.createQuote.Response)
1088
+ if (body.mode !== 'exactDestination')
1089
+ throw new Error('Expected an exact-destination exchange quote')
1090
+ expect(body.destinationAmount).toEqual({
1091
+ baseUnits: '1',
1092
+ currency: 'USD',
1093
+ decimals: 6,
1094
+ formatted: '0.000001',
1095
+ valuation: null,
1096
+ })
1097
+ expect(body.destinationToken).toMatchObject({
1098
+ address: baseToken,
1099
+ name: 'Quote Base',
1100
+ symbol: 'QBASE',
1101
+ })
1102
+ expect(body.destinationToken).not.toHaveProperty('amount')
1103
+ expect(body.destinationToken).not.toHaveProperty('logoUri')
1104
+ expect(body.destinationToken).not.toHaveProperty('verified')
1105
+ expect(body.sourceAmountMax).toEqual(body.destinationAmount)
1106
+ expect(body.sourceAmount).toEqual(body.destinationAmount)
1107
+ expect(body.sourceToken).toMatchObject({
1108
+ address: Addresses.pathUsd,
1109
+ })
1110
+ expect(body.sourceToken).not.toHaveProperty('amount')
1111
+ expect(body.sourceToken).not.toHaveProperty('maximumAmount')
1112
+ expect(typeof body.sourceToken.symbol).toBe('string')
1113
+ expect(body.sourceToken).not.toHaveProperty('logoUri')
1114
+ expect(body.sourceToken).not.toHaveProperty('verified')
1115
+ expect(body.transaction.calls).toHaveLength(2)
1116
+ expect(body.transaction.calls[0]!.to).toBe(Addresses.pathUsd)
1117
+ expect(body.transaction.calls[1]!.to).toBe(Addresses.stablecoinDex.toLowerCase())
1118
+ expect(
1119
+ decodeFunctionData({ abi: Abis.tip20, data: body.transaction.calls[0]!.data }),
1120
+ ).toEqual({
1121
+ args: [Address.checksum(Addresses.stablecoinDex), 1n],
1122
+ functionName: 'approve',
1123
+ })
1124
+ expect(
1125
+ decodeFunctionData({ abi: Abis.stablecoinDex, data: body.transaction.calls[1]!.data }),
1126
+ ).toEqual({
1127
+ args: [Address.checksum(Addresses.pathUsd), Address.checksum(baseToken), 1n, 1n],
1128
+ functionName: 'swapExactAmountOut',
1129
+ })
1130
+ }, 60_000)
1131
+
1132
+ test('values quoted amounts in a requested denomination', async () => {
1133
+ // Curate only pathUSD: its leg values at the fixed 1.6 USD -> AUD rate
1134
+ // while the uncurated base token leg stays null.
1135
+ const db = TestApp.database()
1136
+ await TestApp.verifiedSeed(db, Runtime.get().chainId, {
1137
+ tokens: [
1138
+ {
1139
+ address: Addresses.pathUsd,
1140
+ currency: 'USD',
1141
+ decimals: 6,
1142
+ name: 'PathUSD',
1143
+ symbol: 'pathUSD',
1144
+ },
1145
+ ],
1146
+ })
1147
+ const client = TestApp.client({ db, fx: { oracle: fixed } })
1148
+
1149
+ const response = await client.v1.exchange.quotes.$post(
1150
+ {
1151
+ json: {
1152
+ amount: '1000000',
1153
+ destinationToken: Addresses.pathUsd,
1154
+ mode: 'exactSource',
1155
+ slippageBps: 50,
1156
+ sourceToken: baseToken,
1157
+ },
1158
+ query: { denomination: 'aud' },
1159
+ },
1160
+ TestApp.auth,
1161
+ )
1162
+ expect(response.status).toBe(200)
1163
+ const body = await TestApp.json(response, Exchanges.schema.createQuote.Response)
1164
+
1165
+ expect(body.sourceAmount.valuation).toBeNull()
1166
+ expect(body.destinationAmount.valuation).toEqual({
1167
+ amount: core_Value.format((BigInt(body.destinationAmount.baseUnits) * 16n) / 10n, 6),
1168
+ currency: 'AUD',
1169
+ })
1170
+ expect(body.meta?.pricing).toMatchInlineSnapshot(`
1171
+ {
1172
+ "asOf": "2026-01-01T00:00:00.000Z",
1173
+ "basis": "nominal",
1174
+ "source": "fixed",
1175
+ }
1176
+ `)
1177
+ }, 60_000)
1178
+
1179
+ test('returns a client error when the quote amount overflows', async () => {
1180
+ const response = await TestApp.client().v1.exchange.quotes.$post(
1181
+ {
1182
+ json: {
1183
+ amount: (2n ** 128n - 1n).toString(),
1184
+ destinationToken: baseToken,
1185
+ mode: 'exactSource',
1186
+ slippageBps: 50,
1187
+ sourceToken: Addresses.pathUsd,
1188
+ },
1189
+ query: {},
1190
+ },
1191
+ TestApp.auth,
1192
+ )
1193
+ expect(response.status).toBe(400)
1194
+ expect(await response.json()).toMatchObject({
1195
+ error: { code: 'quote_amount_out_of_range' },
1196
+ })
1197
+ }, 60_000)
1198
+
1199
+ test('returns quote unavailable when a route token is paused', async () => {
1200
+ const rpc = Relay.getClient({ account })
1201
+ await Actions.token.pauseSync(rpc, { token: baseToken })
1202
+ try {
1203
+ const response = await TestApp.client().v1.exchange.quotes.$post(
1204
+ {
1205
+ json: {
1206
+ amount: '1',
1207
+ destinationToken: Addresses.pathUsd,
1208
+ mode: 'exactSource',
1209
+ slippageBps: 50,
1210
+ sourceToken: baseToken,
1211
+ },
1212
+ query: {},
1213
+ },
1214
+ TestApp.auth,
1215
+ )
1216
+ expect(response.status).toBe(404)
1217
+ expect(await response.json()).toMatchObject({ error: { code: 'quote_not_available' } })
1218
+ } finally {
1219
+ await Actions.token.unpauseSync(rpc, { token: baseToken })
1220
+ }
1221
+ }, 60_000)
1222
+ })
1223
+ })
1224
+
690
1225
  describe('GET /exchange/orders', () => {
691
1226
  type GlobalOrdersResponse = z.output<typeof Exchanges.schema.getOrders.Response>
692
1227