tapimo 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (689) hide show
  1. package/dist/ApiKeys.d.ts +24 -7
  2. package/dist/ApiKeys.d.ts.map +1 -1
  3. package/dist/ApiKeys.js +17 -5
  4. package/dist/ApiKeys.js.map +1 -1
  5. package/dist/App.d.ts +12104 -7861
  6. package/dist/App.d.ts.map +1 -1
  7. package/dist/App.js +191 -128
  8. package/dist/App.js.map +1 -1
  9. package/dist/Assets.d.ts +10 -0
  10. package/dist/Assets.d.ts.map +1 -1
  11. package/dist/Assets.js +17 -4
  12. package/dist/Assets.js.map +1 -1
  13. package/dist/Scope.d.ts +19 -8
  14. package/dist/Scope.d.ts.map +1 -1
  15. package/dist/Scope.js +25 -9
  16. package/dist/Scope.js.map +1 -1
  17. package/dist/Webhooks.d.ts +1 -1
  18. package/dist/admin/App.d.ts +601 -20
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/App.js +12 -2
  21. package/dist/admin/App.js.map +1 -1
  22. package/dist/admin/apps/api-keys.d.ts +94 -11
  23. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.js +141 -32
  25. package/dist/admin/apps/api-keys.js.map +1 -1
  26. package/dist/admin/apps/earn-vaults.d.ts +1069 -0
  27. package/dist/admin/apps/earn-vaults.d.ts.map +1 -0
  28. package/dist/admin/apps/earn-vaults.js +409 -0
  29. package/dist/admin/apps/earn-vaults.js.map +1 -0
  30. package/dist/admin/apps/verified-tokens.d.ts +166 -166
  31. package/dist/admin/ui.generated.d.ts +1 -1
  32. package/dist/admin/ui.generated.d.ts.map +1 -1
  33. package/dist/admin/ui.generated.js +151 -95
  34. package/dist/admin/ui.generated.js.map +1 -1
  35. package/dist/analytics/Analytics.d.ts +2 -0
  36. package/dist/analytics/Analytics.d.ts.map +1 -1
  37. package/dist/analytics/Analytics.js +18 -2
  38. package/dist/analytics/Analytics.js.map +1 -1
  39. package/dist/analytics/tables/requestEvents.d.ts +22 -1
  40. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  41. package/dist/analytics/tables/requestEvents.js +59 -39
  42. package/dist/analytics/tables/requestEvents.js.map +1 -1
  43. package/dist/apps/Zones.d.ts +8335 -0
  44. package/dist/apps/Zones.d.ts.map +1 -0
  45. package/dist/apps/Zones.js +38 -0
  46. package/dist/apps/Zones.js.map +1 -0
  47. package/dist/apps/data/App.d.ts +14377 -10165
  48. package/dist/apps/data/App.d.ts.map +1 -1
  49. package/dist/apps/data/App.js +91 -19
  50. package/dist/apps/data/App.js.map +1 -1
  51. package/dist/apps/data/FxOracle.d.ts +1 -1
  52. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  53. package/dist/apps/data/index.d.ts +0 -1
  54. package/dist/apps/data/index.d.ts.map +1 -1
  55. package/dist/apps/data/index.js +0 -1
  56. package/dist/apps/data/index.js.map +1 -1
  57. package/dist/apps/data/routes/activities.d.ts +32 -32
  58. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  59. package/dist/apps/data/routes/activities.js +130 -65
  60. package/dist/apps/data/routes/activities.js.map +1 -1
  61. package/dist/apps/data/routes/addresses.d.ts +312 -312
  62. package/dist/apps/data/routes/balances.d.ts +18 -18
  63. package/dist/apps/data/routes/balances.js +5 -5
  64. package/dist/apps/data/routes/balances.js.map +1 -1
  65. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  66. package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
  67. package/dist/apps/data/routes/coingecko.js +62 -35
  68. package/dist/apps/data/routes/coingecko.js.map +1 -1
  69. package/dist/apps/data/routes/earn.d.ts +2144 -0
  70. package/dist/apps/data/routes/earn.d.ts.map +1 -0
  71. package/dist/apps/data/routes/earn.js +947 -0
  72. package/dist/apps/data/routes/earn.js.map +1 -0
  73. package/dist/apps/data/routes/exchanges.d.ts +1231 -324
  74. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  75. package/dist/apps/data/routes/exchanges.js +656 -193
  76. package/dist/apps/data/routes/exchanges.js.map +1 -1
  77. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  78. package/dist/apps/data/routes/fee-amm.js +59 -8
  79. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  80. package/dist/apps/data/routes/funding.d.ts +4092 -387
  81. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  82. package/dist/apps/data/routes/funding.js +1403 -256
  83. package/dist/apps/data/routes/funding.js.map +1 -1
  84. package/dist/apps/data/routes/indexer.d.ts +5 -5
  85. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  86. package/dist/apps/data/routes/indexer.js +19 -2
  87. package/dist/apps/data/routes/indexer.js.map +1 -1
  88. package/dist/apps/data/routes/receipts.d.ts +265 -265
  89. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  90. package/dist/apps/data/routes/receipts.js +11 -11
  91. package/dist/apps/data/routes/receipts.js.map +1 -1
  92. package/dist/apps/data/routes/rpc.d.ts +5 -5
  93. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  94. package/dist/apps/data/routes/rpc.js +211 -22
  95. package/dist/apps/data/routes/rpc.js.map +1 -1
  96. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  97. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  98. package/dist/apps/data/routes/tokenlist.js +49 -9
  99. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  100. package/dist/apps/data/routes/tokens.d.ts +116 -88
  101. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  102. package/dist/apps/data/routes/tokens.js +198 -91
  103. package/dist/apps/data/routes/tokens.js.map +1 -1
  104. package/dist/apps/data/routes/transactions.d.ts +55 -55
  105. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  106. package/dist/apps/data/routes/transactions.js +78 -39
  107. package/dist/apps/data/routes/transactions.js.map +1 -1
  108. package/dist/apps/data/routes/transfers.d.ts +78 -29
  109. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  110. package/dist/apps/data/routes/transfers.js +165 -67
  111. package/dist/apps/data/routes/transfers.js.map +1 -1
  112. package/dist/apps/data/routes/valuation.d.ts +10 -10
  113. package/dist/apps/data/routes/valuation.js +2 -2
  114. package/dist/apps/data/routes/valuation.js.map +1 -1
  115. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  116. package/dist/apps/data/routes/verified-tokens.js +34 -9
  117. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  118. package/dist/apps/data/routes/webhooks.d.ts +5 -3
  119. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  120. package/dist/apps/data/routes/webhooks.js +175 -65
  121. package/dist/apps/data/routes/webhooks.js.map +1 -1
  122. package/dist/apps/data/routes/zones.d.ts +281 -0
  123. package/dist/apps/data/routes/zones.d.ts.map +1 -0
  124. package/dist/apps/data/routes/zones.js +252 -0
  125. package/dist/apps/data/routes/zones.js.map +1 -0
  126. package/dist/apps/docs/reference.js +6 -5
  127. package/dist/apps/docs/reference.js.map +1 -1
  128. package/dist/apps/index.d.ts +2 -1
  129. package/dist/apps/index.d.ts.map +1 -1
  130. package/dist/apps/index.js +2 -1
  131. package/dist/apps/index.js.map +1 -1
  132. package/dist/apps/management/App.d.ts +670 -670
  133. package/dist/apps/management/Billing.d.ts +8 -0
  134. package/dist/apps/management/Billing.d.ts.map +1 -1
  135. package/dist/apps/management/Billing.js +12 -0
  136. package/dist/apps/management/Billing.js.map +1 -1
  137. package/dist/apps/management/Error.d.ts +12 -0
  138. package/dist/apps/management/Error.d.ts.map +1 -0
  139. package/dist/apps/management/Error.js +10 -0
  140. package/dist/apps/management/Error.js.map +1 -0
  141. package/dist/apps/management/routes/api-keys.d.ts +5 -5
  142. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  143. package/dist/apps/management/routes/api-keys.js +2 -8
  144. package/dist/apps/management/routes/api-keys.js.map +1 -1
  145. package/dist/apps/management/routes/billing.d.ts +119 -119
  146. package/dist/apps/management/routes/faucet.d.ts +35 -35
  147. package/dist/apps/management/routes/invitations.d.ts +62 -62
  148. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  149. package/dist/apps/management/routes/invitations.js +4 -10
  150. package/dist/apps/management/routes/invitations.js.map +1 -1
  151. package/dist/apps/management/routes/invite-links.d.ts +30 -30
  152. package/dist/apps/management/routes/me.d.ts +6 -6
  153. package/dist/apps/management/routes/members.d.ts +78 -78
  154. package/dist/apps/management/routes/orgs.d.ts +48 -48
  155. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  156. package/dist/apps/management/routes/orgs.js +2 -8
  157. package/dist/apps/management/routes/orgs.js.map +1 -1
  158. package/dist/apps/management/routes/projects.d.ts +9 -9
  159. package/dist/apps/management/routes/scopes.js +2 -2
  160. package/dist/apps/management/routes/scopes.js.map +1 -1
  161. package/dist/apps/management/routes/usage.d.ts +80 -80
  162. package/dist/apps/management/routes/verified-token-requests.d.ts +36 -36
  163. package/dist/apps/management/routes/webhooks.d.ts +50 -50
  164. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  165. package/dist/apps/management/routes/webhooks.js +15 -3
  166. package/dist/apps/management/routes/webhooks.js.map +1 -1
  167. package/dist/apps/mpp/App.d.ts +55 -56
  168. package/dist/apps/mpp/App.d.ts.map +1 -1
  169. package/dist/apps/mpp/App.js +192 -27
  170. package/dist/apps/mpp/App.js.map +1 -1
  171. package/dist/apps/mpp/Mpp.d.ts +0 -20
  172. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  173. package/dist/apps/mpp/Mpp.js +0 -24
  174. package/dist/apps/mpp/Mpp.js.map +1 -1
  175. package/dist/apps/mpp/internal/Adapter.d.ts +40 -0
  176. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -0
  177. package/dist/apps/mpp/internal/Adapter.js +66 -0
  178. package/dist/apps/mpp/internal/Adapter.js.map +1 -0
  179. package/dist/apps/mpp/internal/Error.d.ts +4 -0
  180. package/dist/apps/mpp/internal/Error.d.ts.map +1 -0
  181. package/dist/apps/mpp/internal/Error.js +41 -0
  182. package/dist/apps/mpp/internal/Error.js.map +1 -0
  183. package/dist/apps/mpp/internal/Idempotency.d.ts +33 -0
  184. package/dist/apps/mpp/internal/Idempotency.d.ts.map +1 -0
  185. package/dist/apps/mpp/internal/Idempotency.js +28 -0
  186. package/dist/apps/mpp/internal/Idempotency.js.map +1 -0
  187. package/dist/apps/mpp/internal/Policy.d.ts +46 -0
  188. package/dist/apps/mpp/internal/Policy.d.ts.map +1 -0
  189. package/dist/apps/mpp/internal/Policy.js +57 -0
  190. package/dist/apps/mpp/internal/Policy.js.map +1 -0
  191. package/dist/apps/mpp/internal/ScreenManager.d.ts +27 -0
  192. package/dist/apps/mpp/internal/ScreenManager.d.ts.map +1 -0
  193. package/dist/apps/mpp/internal/ScreenManager.js +18 -0
  194. package/dist/apps/mpp/internal/ScreenManager.js.map +1 -0
  195. package/dist/apps/mpp/internal/Screening.d.ts +35 -0
  196. package/dist/apps/mpp/internal/Screening.d.ts.map +1 -0
  197. package/dist/apps/mpp/internal/Screening.js +20 -0
  198. package/dist/apps/mpp/internal/Screening.js.map +1 -0
  199. package/dist/apps/mpp/internal/Sponsorship.d.ts +36 -0
  200. package/dist/apps/mpp/internal/Sponsorship.d.ts.map +1 -0
  201. package/dist/apps/mpp/internal/Sponsorship.js +103 -0
  202. package/dist/apps/mpp/internal/Sponsorship.js.map +1 -0
  203. package/dist/apps/mpp/internal/Utils.d.ts +7 -0
  204. package/dist/apps/mpp/internal/Utils.d.ts.map +1 -0
  205. package/dist/apps/mpp/internal/Utils.js +24 -0
  206. package/dist/apps/mpp/internal/Utils.js.map +1 -0
  207. package/dist/apps/relay/App.js +184 -24
  208. package/dist/apps/relay/App.js.map +1 -1
  209. package/dist/apps/relay/Sponsorships.d.ts +4 -0
  210. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  211. package/dist/apps/relay/Sponsorships.js +16 -7
  212. package/dist/apps/relay/Sponsorships.js.map +1 -1
  213. package/dist/db/Db.d.ts +20 -0
  214. package/dist/db/Db.d.ts.map +1 -1
  215. package/dist/db/Db.js +342 -0
  216. package/dist/db/Db.js.map +1 -1
  217. package/dist/db/Schema.d.ts +921 -1
  218. package/dist/db/Schema.d.ts.map +1 -1
  219. package/dist/db/Schema.js +269 -1
  220. package/dist/db/Schema.js.map +1 -1
  221. package/dist/db/tables/earnVaults.d.ts +68 -0
  222. package/dist/db/tables/earnVaults.d.ts.map +1 -0
  223. package/dist/db/tables/earnVaults.js +72 -0
  224. package/dist/db/tables/earnVaults.js.map +1 -0
  225. package/dist/db/tables/fundingCatalog.d.ts +56 -0
  226. package/dist/db/tables/fundingCatalog.d.ts.map +1 -0
  227. package/dist/db/tables/fundingCatalog.js +85 -0
  228. package/dist/db/tables/fundingCatalog.js.map +1 -0
  229. package/dist/db/tables/fundingDepositAddresses.d.ts +159 -0
  230. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -0
  231. package/dist/db/tables/fundingDepositAddresses.js +206 -0
  232. package/dist/db/tables/fundingDepositAddresses.js.map +1 -0
  233. package/dist/db/tables/fundingDeposits.d.ts +115 -0
  234. package/dist/db/tables/fundingDeposits.d.ts.map +1 -0
  235. package/dist/db/tables/fundingDeposits.js +100 -0
  236. package/dist/db/tables/fundingDeposits.js.map +1 -0
  237. package/dist/db/tables/fundingIdempotency.d.ts +91 -0
  238. package/dist/db/tables/fundingIdempotency.d.ts.map +1 -0
  239. package/dist/db/tables/fundingIdempotency.js +103 -0
  240. package/dist/db/tables/fundingIdempotency.js.map +1 -0
  241. package/dist/db/tables/fundingTransferEvents.d.ts +12 -0
  242. package/dist/db/tables/fundingTransferEvents.d.ts.map +1 -0
  243. package/dist/db/tables/fundingTransferEvents.js +18 -0
  244. package/dist/db/tables/fundingTransferEvents.js.map +1 -0
  245. package/dist/db/tables/fundingTransferTransactions.d.ts +15 -0
  246. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -0
  247. package/dist/db/tables/fundingTransferTransactions.js +27 -0
  248. package/dist/db/tables/fundingTransferTransactions.js.map +1 -0
  249. package/dist/db/tables/fundingTransfers.d.ts +82 -0
  250. package/dist/db/tables/fundingTransfers.d.ts.map +1 -0
  251. package/dist/db/tables/fundingTransfers.js +89 -0
  252. package/dist/db/tables/fundingTransfers.js.map +1 -0
  253. package/dist/db/tables/organizations.d.ts +8 -0
  254. package/dist/db/tables/organizations.d.ts.map +1 -1
  255. package/dist/db/tables/organizations.js +35 -0
  256. package/dist/db/tables/organizations.js.map +1 -1
  257. package/dist/db/tables/projects.d.ts +8 -0
  258. package/dist/db/tables/projects.d.ts.map +1 -1
  259. package/dist/db/tables/projects.js +16 -0
  260. package/dist/db/tables/projects.js.map +1 -1
  261. package/dist/db/tables/requestUsage.d.ts +13 -0
  262. package/dist/db/tables/requestUsage.d.ts.map +1 -1
  263. package/dist/db/tables/requestUsage.js +16 -0
  264. package/dist/db/tables/requestUsage.js.map +1 -1
  265. package/dist/db/tables/sponsoredTransactions.d.ts +4 -2
  266. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  267. package/dist/db/tables/sponsoredTransactions.js +14 -4
  268. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  269. package/dist/db/tables/users.d.ts +8 -0
  270. package/dist/db/tables/users.d.ts.map +1 -1
  271. package/dist/db/tables/users.js +16 -0
  272. package/dist/db/tables/users.js.map +1 -1
  273. package/dist/db/tables/verifiedTokens.d.ts +14 -7
  274. package/dist/db/tables/verifiedTokens.d.ts.map +1 -1
  275. package/dist/db/tables/verifiedTokens.js +9 -7
  276. package/dist/db/tables/verifiedTokens.js.map +1 -1
  277. package/dist/db/tables/webhookSubscriptions.d.ts +31 -1
  278. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  279. package/dist/db/tables/webhookSubscriptions.js +24 -3
  280. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  281. package/dist/handlers/relay.js +108 -108
  282. package/dist/handlers/relay.js.map +1 -1
  283. package/dist/internal/Auth.d.ts +26 -0
  284. package/dist/internal/Auth.d.ts.map +1 -1
  285. package/dist/internal/Auth.js +35 -2
  286. package/dist/internal/Auth.js.map +1 -1
  287. package/dist/internal/Cache.d.ts.map +1 -1
  288. package/dist/internal/Cache.js +16 -10
  289. package/dist/internal/Cache.js.map +1 -1
  290. package/dist/internal/Cursor.js +1 -1
  291. package/dist/internal/Cursor.js.map +1 -1
  292. package/dist/internal/EarnRates.d.ts +89 -0
  293. package/dist/internal/EarnRates.d.ts.map +1 -0
  294. package/dist/internal/EarnRates.js +266 -0
  295. package/dist/internal/EarnRates.js.map +1 -0
  296. package/dist/internal/EarnVaults.d.ts +171 -0
  297. package/dist/internal/EarnVaults.d.ts.map +1 -0
  298. package/dist/internal/EarnVaults.js +547 -0
  299. package/dist/internal/EarnVaults.js.map +1 -0
  300. package/dist/internal/EdgeCache.d.ts +9 -2
  301. package/dist/internal/EdgeCache.d.ts.map +1 -1
  302. package/dist/internal/EdgeCache.js +36 -9
  303. package/dist/internal/EdgeCache.js.map +1 -1
  304. package/dist/internal/Id.d.ts +11 -0
  305. package/dist/internal/Id.d.ts.map +1 -1
  306. package/dist/internal/Id.js +13 -0
  307. package/dist/internal/Id.js.map +1 -1
  308. package/dist/internal/Idempotency.d.ts +83 -0
  309. package/dist/internal/Idempotency.d.ts.map +1 -0
  310. package/dist/internal/Idempotency.js +113 -0
  311. package/dist/internal/Idempotency.js.map +1 -0
  312. package/dist/internal/Log.d.ts +64 -9
  313. package/dist/internal/Log.d.ts.map +1 -1
  314. package/dist/internal/Log.js +29 -7
  315. package/dist/internal/Log.js.map +1 -1
  316. package/dist/internal/MetricSink.d.ts.map +1 -1
  317. package/dist/internal/MetricSink.js +47 -1
  318. package/dist/internal/MetricSink.js.map +1 -1
  319. package/dist/internal/OpenApi.d.ts +1 -1
  320. package/dist/internal/OpenApi.d.ts.map +1 -1
  321. package/dist/internal/OpenApi.js +3 -0
  322. package/dist/internal/OpenApi.js.map +1 -1
  323. package/dist/internal/Provider.d.ts +14 -0
  324. package/dist/internal/Provider.d.ts.map +1 -0
  325. package/dist/internal/Provider.js +5 -0
  326. package/dist/internal/Provider.js.map +1 -0
  327. package/dist/internal/Schema.d.ts +8 -3
  328. package/dist/internal/Schema.d.ts.map +1 -1
  329. package/dist/internal/Schema.js +24 -13
  330. package/dist/internal/Schema.js.map +1 -1
  331. package/dist/internal/Store.d.ts +6 -0
  332. package/dist/internal/Store.d.ts.map +1 -1
  333. package/dist/internal/Store.js +35 -0
  334. package/dist/internal/Store.js.map +1 -1
  335. package/dist/internal/Tidx.d.ts +43 -24
  336. package/dist/internal/Tidx.d.ts.map +1 -1
  337. package/dist/internal/Tidx.js +79 -11
  338. package/dist/internal/Tidx.js.map +1 -1
  339. package/dist/internal/Viem.d.ts +82 -65
  340. package/dist/internal/Viem.d.ts.map +1 -1
  341. package/dist/internal/Viem.js +83 -12
  342. package/dist/internal/Viem.js.map +1 -1
  343. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  344. package/dist/internal/WebhookDestination.js +34 -28
  345. package/dist/internal/WebhookDestination.js.map +1 -1
  346. package/dist/internal/Webhooks.d.ts +9 -0
  347. package/dist/internal/Webhooks.d.ts.map +1 -1
  348. package/dist/internal/Webhooks.js +15 -10
  349. package/dist/internal/Webhooks.js.map +1 -1
  350. package/dist/internal/exchange/Provider.d.ts +219 -0
  351. package/dist/internal/exchange/Provider.d.ts.map +1 -0
  352. package/dist/internal/exchange/Provider.js +117 -0
  353. package/dist/internal/exchange/Provider.js.map +1 -0
  354. package/dist/internal/exchange/index.d.ts +2 -0
  355. package/dist/internal/exchange/index.d.ts.map +1 -0
  356. package/dist/internal/exchange/index.js +2 -0
  357. package/dist/internal/exchange/index.js.map +1 -0
  358. package/dist/internal/exchange/providers/uniswap.d.ts +57 -0
  359. package/dist/internal/exchange/providers/uniswap.d.ts.map +1 -0
  360. package/dist/internal/exchange/providers/uniswap.js +553 -0
  361. package/dist/internal/exchange/providers/uniswap.js.map +1 -0
  362. package/dist/internal/funding/Action.d.ts +55 -0
  363. package/dist/internal/funding/Action.d.ts.map +1 -0
  364. package/dist/internal/funding/Action.js +74 -0
  365. package/dist/internal/funding/Action.js.map +1 -0
  366. package/dist/internal/funding/Catalog.d.ts +107 -0
  367. package/dist/internal/funding/Catalog.d.ts.map +1 -0
  368. package/dist/internal/funding/Catalog.js +270 -0
  369. package/dist/internal/funding/Catalog.js.map +1 -0
  370. package/dist/internal/funding/Chain.d.ts +76 -0
  371. package/dist/internal/funding/Chain.d.ts.map +1 -0
  372. package/dist/internal/funding/Chain.js +38 -0
  373. package/dist/internal/funding/Chain.js.map +1 -0
  374. package/dist/internal/funding/Deposit.d.ts +366 -0
  375. package/dist/internal/funding/Deposit.d.ts.map +1 -0
  376. package/dist/internal/funding/Deposit.js +286 -0
  377. package/dist/internal/funding/Deposit.js.map +1 -0
  378. package/dist/internal/funding/DepositAddress.d.ts +345 -0
  379. package/dist/internal/funding/DepositAddress.d.ts.map +1 -0
  380. package/dist/internal/funding/DepositAddress.js +189 -0
  381. package/dist/internal/funding/DepositAddress.js.map +1 -0
  382. package/dist/internal/funding/Evidence.d.ts +52 -0
  383. package/dist/internal/funding/Evidence.d.ts.map +1 -0
  384. package/dist/internal/funding/Evidence.js +314 -0
  385. package/dist/internal/funding/Evidence.js.map +1 -0
  386. package/dist/internal/funding/Provider.d.ts +1186 -0
  387. package/dist/internal/funding/Provider.d.ts.map +1 -0
  388. package/dist/internal/funding/Provider.js +1081 -0
  389. package/dist/internal/funding/Provider.js.map +1 -0
  390. package/dist/internal/funding/Reconciliation.d.ts +113 -0
  391. package/dist/internal/funding/Reconciliation.d.ts.map +1 -0
  392. package/dist/internal/funding/Reconciliation.js +676 -0
  393. package/dist/internal/funding/Reconciliation.js.map +1 -0
  394. package/dist/internal/funding/Route.d.ts +21 -0
  395. package/dist/internal/funding/Route.d.ts.map +1 -0
  396. package/dist/internal/funding/Route.js +5 -0
  397. package/dist/internal/funding/Route.js.map +1 -0
  398. package/dist/internal/funding/Subsidy.d.ts +113 -0
  399. package/dist/internal/funding/Subsidy.d.ts.map +1 -0
  400. package/dist/internal/funding/Subsidy.js +138 -0
  401. package/dist/internal/funding/Subsidy.js.map +1 -0
  402. package/dist/internal/funding/Token.d.ts +41 -0
  403. package/dist/internal/funding/Token.d.ts.map +1 -0
  404. package/dist/internal/funding/Token.js +22 -0
  405. package/dist/internal/funding/Token.js.map +1 -0
  406. package/dist/internal/funding/Transfer.d.ts +643 -0
  407. package/dist/internal/funding/Transfer.d.ts.map +1 -0
  408. package/dist/internal/funding/Transfer.js +332 -0
  409. package/dist/internal/funding/Transfer.js.map +1 -0
  410. package/dist/internal/funding/index.d.ts +13 -0
  411. package/dist/internal/funding/index.d.ts.map +1 -0
  412. package/dist/internal/funding/index.js +13 -0
  413. package/dist/internal/funding/index.js.map +1 -0
  414. package/dist/internal/funding/providers/across.d.ts +21 -0
  415. package/dist/internal/funding/providers/across.d.ts.map +1 -0
  416. package/dist/internal/funding/providers/across.js +117 -0
  417. package/dist/internal/funding/providers/across.js.map +1 -0
  418. package/dist/internal/funding/providers/relay.d.ts +46 -0
  419. package/dist/internal/funding/providers/relay.d.ts.map +1 -0
  420. package/dist/internal/funding/providers/relay.js +464 -0
  421. package/dist/internal/funding/providers/relay.js.map +1 -0
  422. package/dist/internal/funding/providers/rhino.d.ts +15 -0
  423. package/dist/internal/funding/providers/rhino.d.ts.map +1 -0
  424. package/dist/internal/funding/providers/rhino.js +116 -0
  425. package/dist/internal/funding/providers/rhino.js.map +1 -0
  426. package/dist/internal/funding/providers/squid.d.ts +19 -0
  427. package/dist/internal/funding/providers/squid.d.ts.map +1 -0
  428. package/dist/internal/funding/providers/squid.js +120 -0
  429. package/dist/internal/funding/providers/squid.js.map +1 -0
  430. package/dist/internal/funding/providers/symbiosis.d.ts +20 -0
  431. package/dist/internal/funding/providers/symbiosis.d.ts.map +1 -0
  432. package/dist/internal/funding/providers/symbiosis.js +176 -0
  433. package/dist/internal/funding/providers/symbiosis.js.map +1 -0
  434. package/dist/internal/index.d.ts +5 -1
  435. package/dist/internal/index.d.ts.map +1 -1
  436. package/dist/internal/index.js +5 -1
  437. package/dist/internal/index.js.map +1 -1
  438. package/dist/internal/webhookDestinations/url.js +61 -57
  439. package/dist/internal/webhookDestinations/url.js.map +1 -1
  440. package/dist/providers/index.d.ts +43 -0
  441. package/dist/providers/index.d.ts.map +1 -0
  442. package/dist/providers/index.js +19 -0
  443. package/dist/providers/index.js.map +1 -0
  444. package/package.json +9 -4
  445. package/src/ApiKeys.test.ts +11 -7
  446. package/src/ApiKeys.ts +37 -9
  447. package/src/App.test.ts +85 -10
  448. package/src/App.ts +224 -137
  449. package/src/Assets.ts +29 -4
  450. package/src/Client.test-d.ts +650 -30
  451. package/src/Scope.test.ts +19 -0
  452. package/src/Scope.ts +28 -11
  453. package/src/Webhooks.test.ts +25 -0
  454. package/src/Webhooks.ts +1 -1
  455. package/src/admin/App.test.ts +9 -2
  456. package/src/admin/App.ts +32 -9
  457. package/src/admin/apps/api-keys.test.ts +167 -23
  458. package/src/admin/apps/api-keys.ts +166 -28
  459. package/src/admin/apps/earn-vaults.test.ts +338 -0
  460. package/src/admin/apps/earn-vaults.ts +572 -0
  461. package/src/admin/apps/scopes.test.ts +16 -1
  462. package/src/admin/ui/plugin.ts +2 -2
  463. package/src/admin/ui/src/components/EarnVaultForm.tsx +137 -0
  464. package/src/admin/ui/src/components/MobileProfile.tsx +2 -2
  465. package/src/admin/ui/src/components/Page.tsx +1 -1
  466. package/src/admin/ui/src/components/ThemeToggle.tsx +1 -1
  467. package/src/admin/ui/src/lib/api-keys.ts +37 -7
  468. package/src/admin/ui/src/lib/client.ts +1 -1
  469. package/src/admin/ui/src/lib/config.ts +1 -1
  470. package/src/admin/ui/src/lib/early-access.ts +8 -9
  471. package/src/admin/ui/src/lib/earn-vaults.ts +207 -0
  472. package/src/admin/ui/src/lib/format.ts +13 -0
  473. package/src/admin/ui/src/lib/organizations.test.ts +14 -4
  474. package/src/admin/ui/src/lib/organizations.ts +7 -1
  475. package/src/admin/ui/src/lib/response.ts +22 -0
  476. package/src/admin/ui/src/lib/scopes.test.ts +19 -11
  477. package/src/admin/ui/src/lib/scopes.ts +28 -7
  478. package/src/admin/ui/src/lib/verified-tokens.ts +10 -9
  479. package/src/admin/ui/src/routeTree.gen.ts +70 -7
  480. package/src/admin/ui/src/router.tsx +2 -2
  481. package/src/admin/ui/src/routes/__root.tsx +9 -8
  482. package/src/admin/ui/src/routes/api-keys.tsx +475 -64
  483. package/src/admin/ui/src/routes/early-access.tsx +5 -12
  484. package/src/admin/ui/src/routes/earn.vaults.tsx +348 -0
  485. package/src/admin/ui/src/routes/earn.vaults_.$vaultId.edit.tsx +154 -0
  486. package/src/admin/ui/src/routes/earn.vaults_.new.tsx +190 -0
  487. package/src/admin/ui/src/routes/organizations.tsx +4 -7
  488. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +56 -54
  489. package/src/admin/ui/src/routes/verified-tokens.tsx +4 -4
  490. package/src/admin/ui/vite.config.ts +4 -1
  491. package/src/admin/ui.generated.ts +189 -95
  492. package/src/analytics/Analytics.test.ts +69 -10
  493. package/src/analytics/Analytics.ts +20 -1
  494. package/src/analytics/tables/requestEvents.ts +79 -37
  495. package/src/apps/Zones.test.ts +33 -0
  496. package/src/apps/Zones.ts +40 -0
  497. package/src/apps/composition.test.ts +84 -1
  498. package/src/apps/data/App.test.ts +38 -9
  499. package/src/apps/data/App.ts +96 -33
  500. package/src/apps/data/FxOracle.ts +1 -1
  501. package/src/apps/data/index.ts +0 -1
  502. package/src/apps/data/routes/activities.test.ts +82 -6
  503. package/src/apps/data/routes/activities.ts +131 -71
  504. package/src/apps/data/routes/balances.test.ts +50 -37
  505. package/src/apps/data/routes/balances.ts +5 -5
  506. package/src/apps/data/routes/blocks.test.ts +2 -0
  507. package/src/apps/data/routes/coingecko.test.ts +22 -0
  508. package/src/apps/data/routes/coingecko.ts +84 -48
  509. package/src/apps/data/routes/earn.test.ts +1108 -0
  510. package/src/apps/data/routes/earn.ts +1391 -0
  511. package/src/apps/data/routes/exchanges.test.ts +82 -33
  512. package/src/apps/data/routes/exchanges.ts +842 -227
  513. package/src/apps/data/routes/fee-amm.test.ts +4 -6
  514. package/src/apps/data/routes/fee-amm.ts +69 -12
  515. package/src/apps/data/routes/funding.test.ts +2712 -221
  516. package/src/apps/data/routes/funding.ts +1604 -387
  517. package/src/apps/data/routes/indexer.test.ts +43 -34
  518. package/src/apps/data/routes/indexer.ts +19 -2
  519. package/src/apps/data/routes/receipts.test.ts +5 -3
  520. package/src/apps/data/routes/receipts.ts +13 -11
  521. package/src/apps/data/routes/rpc.test.ts +446 -28
  522. package/src/apps/data/routes/rpc.ts +243 -25
  523. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  524. package/src/apps/data/routes/tokenlist.ts +88 -22
  525. package/src/apps/data/routes/tokens.ts +262 -86
  526. package/src/apps/data/routes/transactions.test.ts +2 -0
  527. package/src/apps/data/routes/transactions.ts +77 -42
  528. package/src/apps/data/routes/transfers.test.ts +166 -4
  529. package/src/apps/data/routes/transfers.ts +216 -78
  530. package/src/apps/data/routes/valuation.test.ts +62 -9
  531. package/src/apps/data/routes/valuation.ts +2 -2
  532. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  533. package/src/apps/data/routes/verified-tokens.ts +52 -15
  534. package/src/apps/data/routes/webhooks.test.ts +432 -33
  535. package/src/apps/data/routes/webhooks.ts +189 -79
  536. package/src/apps/data/routes/zones.test.ts +473 -0
  537. package/src/apps/data/routes/zones.ts +338 -0
  538. package/src/apps/docs/reference.ts +8 -6
  539. package/src/apps/index.ts +2 -1
  540. package/src/apps/management/Billing.test.ts +29 -0
  541. package/src/apps/management/Billing.ts +23 -0
  542. package/src/apps/management/Error.ts +12 -0
  543. package/src/apps/management/routes/api-keys.test.ts +5 -3
  544. package/src/apps/management/routes/api-keys.ts +2 -9
  545. package/src/apps/management/routes/invitations.ts +4 -11
  546. package/src/apps/management/routes/orgs.ts +2 -9
  547. package/src/apps/management/routes/scopes.test.ts +7 -2
  548. package/src/apps/management/routes/scopes.ts +2 -2
  549. package/src/apps/management/routes/webhooks.test.ts +6 -1
  550. package/src/apps/management/routes/webhooks.ts +15 -3
  551. package/src/apps/mpp/App.ts +276 -29
  552. package/src/apps/mpp/Mpp.test.ts +631 -26
  553. package/src/apps/mpp/Mpp.ts +0 -42
  554. package/src/apps/mpp/internal/Adapter.ts +108 -3
  555. package/src/apps/mpp/internal/Error.test.ts +33 -0
  556. package/src/apps/mpp/internal/Error.ts +44 -6
  557. package/src/apps/mpp/internal/Idempotency.test.ts +213 -0
  558. package/src/apps/mpp/internal/Idempotency.ts +59 -5
  559. package/src/apps/mpp/internal/Policy.test.ts +122 -0
  560. package/src/apps/mpp/internal/Policy.ts +98 -0
  561. package/src/apps/mpp/internal/ScreenManager.test.ts +68 -0
  562. package/src/apps/mpp/internal/ScreenManager.ts +41 -0
  563. package/src/apps/mpp/internal/Screening.ts +40 -3
  564. package/src/apps/mpp/internal/Sponsorship.ts +144 -0
  565. package/src/apps/mpp/internal/Utils.ts +30 -0
  566. package/src/apps/relay/App.test.ts +495 -64
  567. package/src/apps/relay/App.ts +234 -24
  568. package/src/apps/relay/Sponsorships.test.ts +51 -0
  569. package/src/apps/relay/Sponsorships.ts +19 -7
  570. package/src/db/Db.ts +439 -0
  571. package/src/db/Schema.ts +325 -2
  572. package/src/db/tables/earnVaults.test.ts +185 -0
  573. package/src/db/tables/earnVaults.ts +151 -0
  574. package/src/db/tables/fundingCatalog.test.ts +108 -0
  575. package/src/db/tables/fundingCatalog.ts +157 -0
  576. package/src/db/tables/fundingDepositAddresses.test.ts +580 -0
  577. package/src/db/tables/fundingDepositAddresses.ts +376 -0
  578. package/src/db/tables/fundingDeposits.test.ts +261 -0
  579. package/src/db/tables/fundingDeposits.ts +229 -0
  580. package/src/db/tables/fundingIdempotency.test.ts +182 -0
  581. package/src/db/tables/fundingIdempotency.ts +180 -0
  582. package/src/db/tables/fundingTransferEvents.ts +28 -0
  583. package/src/db/tables/fundingTransferTransactions.test.ts +69 -0
  584. package/src/db/tables/fundingTransferTransactions.ts +37 -0
  585. package/src/db/tables/fundingTransfers.test.ts +89 -0
  586. package/src/db/tables/fundingTransfers.ts +153 -0
  587. package/src/db/tables/organizations.test.ts +88 -0
  588. package/src/db/tables/organizations.ts +42 -1
  589. package/src/db/tables/projects.ts +16 -0
  590. package/src/db/tables/requestUsage.ts +23 -0
  591. package/src/db/tables/sponsoredTransactions.test.ts +52 -2
  592. package/src/db/tables/sponsoredTransactions.ts +18 -6
  593. package/src/db/tables/users.ts +16 -0
  594. package/src/db/tables/verifiedTokens.ts +19 -14
  595. package/src/db/tables/webhookSubscriptions.ts +58 -3
  596. package/src/handlers/relay.test.ts +2 -0
  597. package/src/handlers/relay.ts +95 -99
  598. package/src/internal/Auth.test.ts +48 -0
  599. package/src/internal/Auth.ts +61 -2
  600. package/src/internal/Cache.ts +13 -9
  601. package/src/internal/Cursor.test.ts +2 -2
  602. package/src/internal/Cursor.ts +1 -1
  603. package/src/internal/EarnRates.test.ts +125 -0
  604. package/src/internal/EarnRates.ts +409 -0
  605. package/src/internal/EarnVaults.test.ts +195 -0
  606. package/src/internal/EarnVaults.ts +876 -0
  607. package/src/internal/EdgeCache.test.ts +37 -5
  608. package/src/internal/EdgeCache.ts +39 -7
  609. package/src/internal/Id.ts +14 -0
  610. package/src/internal/Idempotency.test.ts +109 -0
  611. package/src/internal/Idempotency.ts +237 -0
  612. package/src/internal/Log.test-d.ts +41 -8
  613. package/src/internal/Log.test.ts +189 -8
  614. package/src/internal/Log.ts +124 -17
  615. package/src/internal/MetricSink.test.ts +244 -4
  616. package/src/internal/MetricSink.ts +51 -1
  617. package/src/internal/OpenApi.ts +3 -0
  618. package/src/internal/Provider.test.ts +17 -0
  619. package/src/internal/Provider.ts +17 -0
  620. package/src/internal/Schema.test.ts +13 -0
  621. package/src/internal/Schema.ts +41 -24
  622. package/src/internal/Store.test.ts +25 -0
  623. package/src/internal/Store.ts +46 -0
  624. package/src/internal/Tidx.local.test.ts +78 -3
  625. package/src/internal/Tidx.test.ts +212 -18
  626. package/src/internal/Tidx.ts +112 -32
  627. package/src/internal/VerifiedTokens.test.ts +15 -0
  628. package/src/internal/Viem.test.ts +170 -17
  629. package/src/internal/Viem.ts +111 -28
  630. package/src/internal/WebhookDestination.ts +25 -23
  631. package/src/internal/Webhooks.test.ts +59 -0
  632. package/src/internal/Webhooks.ts +34 -10
  633. package/src/internal/exchange/Provider.test.ts +216 -0
  634. package/src/internal/exchange/Provider.ts +326 -0
  635. package/src/internal/exchange/index.test.ts +6 -0
  636. package/src/internal/exchange/index.ts +1 -0
  637. package/src/internal/exchange/providers/uniswap.test.ts +122 -0
  638. package/src/internal/exchange/providers/uniswap.ts +682 -0
  639. package/src/internal/funding/Action.test.ts +138 -0
  640. package/src/internal/funding/Action.ts +117 -0
  641. package/src/internal/funding/Catalog.test.ts +311 -0
  642. package/src/internal/funding/Catalog.ts +347 -0
  643. package/src/internal/funding/Chain.test.ts +79 -0
  644. package/src/internal/funding/Chain.ts +120 -0
  645. package/src/internal/funding/Deposit.test.ts +26 -0
  646. package/src/internal/funding/Deposit.ts +453 -0
  647. package/src/internal/funding/DepositAddress.test.ts +18 -0
  648. package/src/internal/funding/DepositAddress.ts +312 -0
  649. package/src/internal/funding/Evidence.test.ts +297 -0
  650. package/src/internal/funding/Evidence.ts +430 -0
  651. package/src/internal/funding/Provider.test.ts +1328 -0
  652. package/src/internal/funding/Provider.ts +1796 -0
  653. package/src/internal/funding/Reconciliation.local.test.ts +504 -0
  654. package/src/internal/funding/Reconciliation.test.ts +2020 -0
  655. package/src/internal/funding/Reconciliation.ts +930 -0
  656. package/src/internal/funding/Route.test.ts +66 -0
  657. package/src/internal/funding/Route.ts +29 -0
  658. package/src/internal/funding/Subsidy.local.test.ts +152 -0
  659. package/src/internal/funding/Subsidy.test.ts +75 -0
  660. package/src/internal/funding/Subsidy.ts +260 -0
  661. package/src/internal/funding/Token.test.ts +47 -0
  662. package/src/internal/funding/Token.ts +59 -0
  663. package/src/internal/funding/Transfer.test.ts +369 -0
  664. package/src/internal/funding/Transfer.ts +522 -0
  665. package/src/internal/funding/index.test.ts +20 -0
  666. package/src/internal/funding/index.ts +12 -0
  667. package/src/internal/funding/providers/across.ts +149 -0
  668. package/src/{apps/data/FundingQuoteProviders.test.ts → internal/funding/providers/index.test.ts} +178 -157
  669. package/src/internal/funding/providers/relay.test.ts +474 -0
  670. package/src/internal/funding/providers/relay.ts +559 -0
  671. package/src/internal/funding/providers/rhino.ts +139 -0
  672. package/src/internal/funding/providers/squid.ts +151 -0
  673. package/src/internal/funding/providers/symbiosis.ts +221 -0
  674. package/src/internal/index.test.ts +5 -1
  675. package/src/internal/index.ts +5 -1
  676. package/src/internal/webhookDestinations/url.ts +49 -45
  677. package/src/providers/index.test.ts +14 -0
  678. package/src/providers/index.ts +54 -0
  679. package/dist/apps/data/FundingQuoteProviders.d.ts +0 -174
  680. package/dist/apps/data/FundingQuoteProviders.d.ts.map +0 -1
  681. package/dist/apps/data/FundingQuoteProviders.js +0 -775
  682. package/dist/apps/data/FundingQuoteProviders.js.map +0 -1
  683. package/dist/internal/FundingQuotes.d.ts +0 -864
  684. package/dist/internal/FundingQuotes.d.ts.map +0 -1
  685. package/dist/internal/FundingQuotes.js +0 -672
  686. package/dist/internal/FundingQuotes.js.map +0 -1
  687. package/src/apps/data/FundingQuoteProviders.ts +0 -1043
  688. package/src/internal/FundingQuotes.test.ts +0 -456
  689. package/src/internal/FundingQuotes.ts +0 -944
@@ -6,6 +6,7 @@ import {
6
6
  ContractFunctionRevertedError,
7
7
  decodeFunctionData,
8
8
  parseAbi,
9
+ parseEventLogs,
9
10
  } from 'viem'
10
11
  import { Abis, Actions, Addresses, Tick } from 'viem/tempo'
11
12
  import * as z from 'zod/mini'
@@ -14,6 +15,7 @@ import type * as App from '../../../App.js'
14
15
  import * as Auth from '../../../internal/Auth.js'
15
16
  import * as Cache from '../../../internal/Cache.js'
16
17
  import * as Cursor from '../../../internal/Cursor.js'
18
+ import * as ExchangeProvider from '../../../internal/exchange/Provider.js'
17
19
  import * as OpenApi from '../../../internal/OpenApi.js'
18
20
  import * as Response from '../../../internal/Response.js'
19
21
  import * as Schema from '../../../internal/Schema.js'
@@ -22,6 +24,7 @@ import * as Timing from '../../../internal/Timing.js'
22
24
  import * as Ttl from '../../../internal/Ttl.js'
23
25
  import * as Value from '../../../internal/Value.js'
24
26
  import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
27
+ import type * as Viem from '../../../internal/Viem.js'
25
28
  import * as FxOracle from '../FxOracle.js'
26
29
  import * as Tokens from './tokens.js'
27
30
  import * as Valuation from './valuation.js'
@@ -32,6 +35,9 @@ const stablecoinDex = Addresses.stablecoinDex.toLowerCase()
32
35
 
33
36
  const contractPausedSelector = AbiError.getSelector(AbiError.fromAbi(Abis.tip20, 'ContractPaused'))
34
37
 
38
+ /** Largest amount accepted by TIP-20 and external uint256 provider contracts. */
39
+ const maxProviderAmount = 2n ** 256n - 1n
40
+
35
41
  // `OrderCancelled(uint128 orderId)` — emitted when a maker cancels a resting
36
42
  // order. Used by the orders endpoint to exclude cancelled orders from the
37
43
  // resting set.
@@ -241,25 +247,25 @@ export namespace schema {
241
247
  .optional(z.nullable(Valuation.schema.Value))
242
248
  .check(
243
249
  z.describe(
244
- 'The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
250
+ 'The amount’s nominal value in the requested `valuation.currency`. `null` when the token is ' +
245
251
  'unverified, its display currency has no rate, or rates were unavailable.',
246
252
  ),
247
253
  ),
248
254
  })
249
255
 
250
- /** Schemas for creating an executable native DEX exchange quote. */
256
+ /** Schemas for creating an executable exchange quote. */
251
257
  export namespace createQuote {
252
258
  const DestinationTokenAddress = Schema.tokenAddress(exampleBase)
253
259
  const SourceTokenAddress = Schema.tokenAddress(exampleSourceToken)
254
260
 
255
- /** Positive token amount accepted by the DEX's uint128 arguments. */
261
+ /** Positive token amount accepted by exchange providers. */
256
262
  export const Amount = Schema.DecimalString.check(
257
263
  z.refine(
258
264
  (value) => {
259
265
  const amount = BigInt(value)
260
- return amount > 0n && amount <= maxSwapAmount
266
+ return amount > 0n && amount <= maxProviderAmount
261
267
  },
262
- { error: 'Amount must be between 1 and the maximum uint128 value.' },
268
+ { error: 'Amount must be a positive uint256.' },
263
269
  ),
264
270
  z.describe(
265
271
  'Positive token amount in the token’s smallest unit, encoded as a decimal string.',
@@ -282,14 +288,20 @@ export namespace schema {
282
288
  export const Query = z
283
289
  .strictObject({
284
290
  chainId: Schema.ChainIdQuery,
285
- denomination: Schema.Denomination,
286
291
  include: includeQuery,
292
+ 'valuation.currency': Schema.Denomination,
287
293
  })
288
- .check(z.describe('Query parameters for creating a native DEX exchange quote.'))
294
+ .check(z.describe('Query parameters for creating an exchange quote.'))
289
295
 
290
296
  /** Quote request with one pinned side and caller-selected slippage. */
291
297
  export const Request = z
292
298
  .strictObject({
299
+ account: z
300
+ .optional(Schema.Address)
301
+ .check(
302
+ z.describe('Wallet that will sign and execute the swap.'),
303
+ z.meta({ examples: ['0x1111111111111111111111111111111111111111'] }),
304
+ ),
293
305
  amount: Amount.check(
294
306
  z.describe(
295
307
  'Pinned amount in base units: source amount for `exactSource`, destination amount for `exactDestination`.',
@@ -319,16 +331,22 @@ export namespace schema {
319
331
  z.refine((request) => request.sourceToken !== request.destinationToken, {
320
332
  error: 'Source and destination tokens must differ.',
321
333
  }),
322
- z.describe('Request for an executable quote on Tempo’s native stablecoin DEX.'),
334
+ z.describe('Request for an executable quote from the configured exchange providers.'),
323
335
  )
324
336
 
325
337
  /** One call in the unsigned Tempo transaction plan. */
326
338
  export const Call = z
327
339
  .object({
328
- data: Schema.Hex.check(z.describe('ABI-encoded call data.')),
329
- to: Schema.Address.check(z.describe('Contract or precompile that receives the call.')),
340
+ data: Schema.Hex.check(
341
+ z.describe('ABI-encoded call data.'),
342
+ z.meta({ examples: ['0x095ea7b3'] }),
343
+ ),
344
+ to: Schema.Address.check(
345
+ z.describe('Contract or precompile that receives the call.'),
346
+ z.meta({ examples: [Addresses.stablecoinDex] }),
347
+ ),
330
348
  value: Schema.Quantity.check(
331
- z.describe('Native token value sent with the call; native DEX swap calls use `0x0`.'),
349
+ z.describe('Native token value sent with the call.'),
332
350
  z.meta({ examples: ['0x0'] }),
333
351
  ),
334
352
  })
@@ -350,11 +368,11 @@ export namespace schema {
350
368
  address: SourceTokenAddress.check(z.describe('TIP-20 token the swap spends.')),
351
369
  })
352
370
 
353
- const Transaction = z
371
+ export const Transaction = z
354
372
  .object({
355
373
  calls: z.array(Call).check(
356
- z.length(2),
357
- z.describe('Approval followed by the native DEX swap call.'),
374
+ z.minLength(1),
375
+ z.describe('Calls to execute in order.'),
358
376
  z.meta({
359
377
  examples: [
360
378
  [
@@ -375,7 +393,7 @@ export namespace schema {
375
393
  /** Response-level valuation rate provenance. */
376
394
  export const Meta = z
377
395
  .object({
378
- pricing: z
396
+ valuation: z
379
397
  .optional(Valuation.schema.Pricing)
380
398
  .check(
381
399
  z.describe(
@@ -385,43 +403,225 @@ export namespace schema {
385
403
  })
386
404
  .check(z.describe('Response-level resources attached to this quote.'))
387
405
 
388
- /** Native DEX quote plus the approval and swap calls needed to execute it. */
389
- export const Response = z
390
- .discriminatedUnion('mode', [
391
- z.object({
392
- destinationAmount: ValuedAmount.check(z.describe('Quoted destination amount.')),
393
- destinationAmountMin: ValuedAmount.check(
394
- z.describe('Minimum destination amount after slippage.'),
395
- ),
396
- destinationToken: DestinationToken,
397
- meta: z.optional(Meta).check(z.describe('Valuation rate provenance for this quote.')),
398
- mode: z
399
- .literal('exactSource')
400
- .check(
401
- z.describe('The source amount is fixed and the destination has a minimum.'),
402
- z.meta({ examples: ['exactSource'] }),
403
- ),
404
- sourceAmount: ValuedAmount.check(z.describe('Exact source amount.')),
405
- sourceToken: SourceToken.check(z.describe('Token spent by the swap.')),
406
- transaction: Transaction,
407
- }),
408
- z.object({
409
- destinationAmount: ValuedAmount.check(z.describe('Exact destination amount.')),
410
- destinationToken: DestinationToken.check(z.describe('Token received by the swap.')),
411
- meta: z.optional(Meta).check(z.describe('Valuation rate provenance for this quote.')),
412
- mode: z
413
- .literal('exactDestination')
414
- .check(
415
- z.describe('The destination amount is fixed and the source has a maximum.'),
416
- z.meta({ examples: ['exactDestination'] }),
406
+ /** Provider-neutral EIP-712 payload to sign before finalizing a quote. */
407
+ export const TypedData = z
408
+ .object({
409
+ domain: z.record(z.string(), z.unknown()).check(
410
+ z.describe('EIP-712 signing domain.'),
411
+ z.meta({
412
+ examples: [
413
+ {
414
+ chainId: 4217,
415
+ name: 'Permit2',
416
+ verifyingContract: '0x000000000022d473030f116ddee9f6b43ac78ba3',
417
+ },
418
+ ],
419
+ }),
420
+ ),
421
+ message: z.record(z.string(), z.unknown()).check(
422
+ z.describe('Values encoded by the primary EIP-712 type.'),
423
+ z.meta({
424
+ examples: [
425
+ {
426
+ details: {
427
+ amount: '1000000',
428
+ expiration: '4102444800',
429
+ nonce: '0',
430
+ token: exampleSourceToken,
431
+ },
432
+ sigDeadline: '4102444800',
433
+ spender: '0x1febb76be10aaf3a1402f04e8e835f2c382f7914',
434
+ },
435
+ ],
436
+ }),
437
+ ),
438
+ primaryType: z
439
+ .string()
440
+ .check(z.minLength(1))
441
+ .check(z.describe('Root EIP-712 type.'), z.meta({ examples: ['PermitSingle'] })),
442
+ types: z
443
+ .record(
444
+ z.string(),
445
+ z.array(
446
+ z.strictObject({
447
+ name: z.string(),
448
+ type: z.string(),
449
+ }),
417
450
  ),
418
- sourceAmount: ValuedAmount.check(z.describe('Quoted source amount.')),
419
- sourceAmountMax: ValuedAmount.check(z.describe('Maximum source amount after slippage.')),
420
- sourceToken: SourceToken,
421
- transaction: Transaction,
422
- }),
451
+ )
452
+ .check(
453
+ z.describe('EIP-712 type definitions keyed by type name.'),
454
+ z.meta({
455
+ examples: [
456
+ {
457
+ PermitDetails: [
458
+ { name: 'token', type: 'address' },
459
+ { name: 'amount', type: 'uint160' },
460
+ { name: 'expiration', type: 'uint48' },
461
+ { name: 'nonce', type: 'uint48' },
462
+ ],
463
+ PermitSingle: [
464
+ { name: 'details', type: 'PermitDetails' },
465
+ { name: 'spender', type: 'address' },
466
+ { name: 'sigDeadline', type: 'uint256' },
467
+ ],
468
+ },
469
+ ],
470
+ }),
471
+ ),
472
+ })
473
+ .check(z.describe('EIP-712 payload the wallet must sign before execution can continue.'))
474
+
475
+ const Provider = z
476
+ .string()
477
+ .check(z.describe('Provider selected for this route.'), z.meta({ examples: ['nativeDex'] }))
478
+
479
+ const Quote = {
480
+ gasEstimate: Schema.DecimalString.check(
481
+ z.describe('Estimated network fee in the chain native token’s base unit.'),
482
+ z.meta({ examples: ['150000000000000'] }),
483
+ ),
484
+ provider: Provider,
485
+ }
486
+
487
+ const ApprovalRequired = {
488
+ approval: z.object({
489
+ calls: z.array(Call).check(
490
+ z.minLength(1),
491
+ z.describe('Approval calls to confirm before requesting a fresh quote.'),
492
+ z.meta({
493
+ examples: [
494
+ [
495
+ {
496
+ data: exampleApprovalCall.data,
497
+ to: exampleApprovalCall.to,
498
+ value: '0x0',
499
+ },
500
+ ],
501
+ ],
502
+ }),
503
+ ),
504
+ }),
505
+ status: z
506
+ .literal('approvalRequired')
507
+ .check(
508
+ z.describe('Confirm the approvals, then request a fresh quote.'),
509
+ z.meta({ examples: ['approvalRequired'] }),
510
+ ),
511
+ }
512
+
513
+ const Ready = {
514
+ status: z
515
+ .literal('ready')
516
+ .check(
517
+ z.describe('The returned calls can be executed now.'),
518
+ z.meta({ examples: ['ready'] }),
519
+ ),
520
+ transaction: Transaction,
521
+ }
522
+
523
+ const SignatureRequired = {
524
+ continuation: z
525
+ .string()
526
+ .check(
527
+ z.minLength(1),
528
+ z.describe('Opaque state supplied to the finalization endpoint within 30 seconds.'),
529
+ z.meta({ examples: ['eyJwcm92aWRlciI6InVuaXN3YXAifQ'] }),
530
+ ),
531
+ status: z
532
+ .literal('signatureRequired')
533
+ .check(
534
+ z.describe('Sign the typed data before final calldata can be built.'),
535
+ z.meta({ examples: ['signatureRequired'] }),
536
+ ),
537
+ typedData: TypedData,
538
+ }
539
+
540
+ const ExactSource = {
541
+ destinationAmount: ValuedAmount.check(z.describe('Quoted destination amount.')),
542
+ destinationAmountMin: ValuedAmount.check(
543
+ z.describe('Minimum destination amount after slippage.'),
544
+ ),
545
+ destinationToken: DestinationToken,
546
+ meta: z.optional(Meta).check(z.describe('Valuation rate provenance for this quote.')),
547
+ mode: z
548
+ .literal('exactSource')
549
+ .check(
550
+ z.describe('The source amount is fixed and the destination has a minimum.'),
551
+ z.meta({ examples: ['exactSource'] }),
552
+ ),
553
+ sourceAmount: ValuedAmount.check(z.describe('Exact source amount.')),
554
+ sourceToken: SourceToken.check(z.describe('Token spent by the swap.')),
555
+ }
556
+
557
+ const ExactDestination = {
558
+ destinationAmount: ValuedAmount.check(z.describe('Exact destination amount.')),
559
+ destinationToken: DestinationToken.check(z.describe('Token received by the swap.')),
560
+ meta: z.optional(Meta).check(z.describe('Valuation rate provenance for this quote.')),
561
+ mode: z
562
+ .literal('exactDestination')
563
+ .check(
564
+ z.describe('The destination amount is fixed and the source has a maximum.'),
565
+ z.meta({ examples: ['exactDestination'] }),
566
+ ),
567
+ sourceAmount: ValuedAmount.check(z.describe('Quoted source amount.')),
568
+ sourceAmountMax: ValuedAmount.check(z.describe('Maximum source amount after slippage.')),
569
+ sourceToken: SourceToken,
570
+ }
571
+
572
+ /** Provider-neutral quote and the action needed to execute it. */
573
+ export const Response = z
574
+ .union([
575
+ z.object({ ...ApprovalRequired, ...ExactSource, ...Quote }),
576
+ z.object({ ...ExactSource, ...Quote, ...Ready }),
577
+ z.object({ ...ExactSource, ...Quote, ...SignatureRequired }),
578
+ z.object({ ...ApprovalRequired, ...ExactDestination, ...Quote }),
579
+ z.object({ ...ExactDestination, ...Quote, ...Ready }),
580
+ z.object({ ...ExactDestination, ...Quote, ...SignatureRequired }),
423
581
  ])
424
- .check(z.describe('Executable native DEX quote with slippage-protected calls.'))
582
+ .check(z.describe('Provider-neutral exchange quote with its next execution action.'))
583
+ }
584
+
585
+ /** Schemas for finishing a quote after a provider-requested signature. */
586
+ export namespace executeQuote {
587
+ /** Signed continuation request. */
588
+ export const Request = z.strictObject({
589
+ account: Schema.Address.check(
590
+ z.describe('Wallet that signed and will execute the swap.'),
591
+ z.meta({ examples: ['0x1111111111111111111111111111111111111111'] }),
592
+ ),
593
+ continuation: z
594
+ .string()
595
+ .check(
596
+ z.minLength(1),
597
+ z.maxLength(100_000),
598
+ z.describe('Opaque state returned by the quote request.'),
599
+ z.meta({ examples: ['eyJwcm92aWRlciI6InVuaXN3YXAifQ'] }),
600
+ ),
601
+ provider: z
602
+ .string()
603
+ .check(
604
+ z.minLength(1),
605
+ z.describe('Provider that issued the continuation.'),
606
+ z.meta({ examples: ['uniswap'] }),
607
+ ),
608
+ signature: Schema.Hex.check(
609
+ z.refine((value) => value.length === 132, { error: 'Signature must be 65 bytes.' }),
610
+ z.describe('65-byte EIP-712 signature requested by the quote.'),
611
+ z.meta({ examples: [`0x${'11'.repeat(65)}`] }),
612
+ ),
613
+ })
614
+
615
+ /** Final transaction plan. */
616
+ export const Response = z.object({
617
+ provider: z
618
+ .string()
619
+ .check(
620
+ z.describe('Provider that built the transaction.'),
621
+ z.meta({ examples: ['uniswap'] }),
622
+ ),
623
+ transaction: createQuote.Transaction,
624
+ })
425
625
  }
426
626
 
427
627
  /** Schemas for the getPairs operation. */
@@ -599,7 +799,6 @@ export namespace schema {
599
799
  'blockNumber.to': Schema.blockNumberBound('swaps', 'to'),
600
800
  chainId: Schema.ChainIdQuery,
601
801
  cursor: Schema.Cursor,
602
- denomination: Schema.Denomination,
603
802
  include: includeQuery,
604
803
  limit: Schema.Limit,
605
804
  maker: z
@@ -625,6 +824,7 @@ export namespace schema {
625
824
  transactionHash: z
626
825
  .optional(Schema.Hash)
627
826
  .check(z.describe('Only include swaps included in this transaction hash.')),
827
+ 'valuation.currency': Schema.Denomination,
628
828
  })
629
829
  .check(
630
830
  z.describe(
@@ -774,7 +974,7 @@ export namespace schema {
774
974
  /** Page-level resources: valuation rate provenance. */
775
975
  export const Meta = z
776
976
  .object({
777
- pricing: z
977
+ valuation: z
778
978
  .optional(Valuation.schema.Pricing)
779
979
  .check(
780
980
  z.describe(
@@ -1532,19 +1732,20 @@ export function exchanges(options: exchanges.Options = {}) {
1532
1732
  }),
1533
1733
  OpenApi.describeRoute({
1534
1734
  description:
1535
- 'Creates a priced exchange preview on Tempo’s native stablecoin DEX with unsigned approval and swap calls. This endpoint does not submit a transaction.',
1735
+ 'Attempts exchange providers in priority order and returns the first executable quote. This endpoint does not submit a transaction.',
1536
1736
  operationId: 'createExchangeQuote',
1537
1737
  responses: OpenApi.responses({
1538
1738
  errors: {
1539
1739
  400: { codes: ['body_invalid', 'query_invalid', 'quote_amount_out_of_range'] },
1540
1740
  404: {
1541
1741
  codes: ['quote_not_available'],
1542
- description: 'The native DEX cannot price an executable exchange for these terms.',
1742
+ description:
1743
+ 'No configured provider can price an executable exchange for these terms.',
1543
1744
  },
1544
1745
  502: 'The Tempo RPC could not quote the swap or resolve token metadata.',
1545
1746
  },
1546
1747
  success: {
1547
- description: 'A native DEX quote and the unsigned calls needed to execute it.',
1748
+ description: 'A provider-neutral quote and its next execution action.',
1548
1749
  schema: schema.createQuote.Response,
1549
1750
  },
1550
1751
  }),
@@ -1565,8 +1766,8 @@ export function exchanges(options: exchanges.Options = {}) {
1565
1766
  })
1566
1767
 
1567
1768
  const request = c.req.valid('json')
1568
- const options = c.req.valid('query')
1569
- const chainId = options.chainId ?? c.get('chainId')
1769
+ const query = c.req.valid('query')
1770
+ const chainId = query.chainId ?? c.get('chainId')
1570
1771
  const client = c.get('getClient')(chainId)
1571
1772
  const amount = BigInt(request.amount)
1572
1773
  const slippageBps = BigInt(request.slippageBps)
@@ -1575,7 +1776,7 @@ export function exchanges(options: exchanges.Options = {}) {
1575
1776
  try {
1576
1777
  // Value quoted amounts via their curated display currency; snapshot
1577
1778
  // or rate failures degrade the values to `null`, never the quote.
1578
- const denomination = options.denomination ?? 'USD'
1779
+ const denomination = query['valuation.currency'] ?? 'USD'
1579
1780
  const snapshot = await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
1580
1781
  const rates = await Valuation.ratesFor(c, {
1581
1782
  currencies: snapshot
@@ -1605,13 +1806,16 @@ export function exchanges(options: exchanges.Options = {}) {
1605
1806
  }),
1606
1807
  })
1607
1808
 
1809
+ if (options.nativeDex === false || amount > maxSwapAmount)
1810
+ throw new ExchangeProvider.RouteUnavailableError()
1811
+
1608
1812
  if (request.mode === 'exactSource') {
1609
1813
  const destinationAmount = await Actions.dex.getSellQuote(client, {
1610
1814
  amountIn: amount,
1611
1815
  tokenIn: request.sourceToken,
1612
1816
  tokenOut: request.destinationToken,
1613
1817
  })
1614
- if (destinationAmount === 0n) return quoteNotAvailable(c)
1818
+ if (destinationAmount === 0n) throw new ExchangeProvider.RouteUnavailableError()
1615
1819
 
1616
1820
  const minimumAmount =
1617
1821
  (destinationAmount * (slippageDenominator - slippageBps) + slippageDenominator - 1n) /
@@ -1627,10 +1831,19 @@ export function exchanges(options: exchanges.Options = {}) {
1627
1831
  tokenIn: request.sourceToken,
1628
1832
  tokenOut: request.destinationToken,
1629
1833
  })
1630
- const tokensByAddress = await resolveTokensByAddress(c, {
1834
+ const calls = [
1835
+ { data: approve.data, to: approve.to, value: 0n },
1836
+ { data: swap.data, to: swap.to, value: 0n },
1837
+ ] as const
1838
+ const gasUnits = await client.estimateGas({
1839
+ ...(request.account ? { account: request.account } : {}),
1840
+ calls,
1841
+ })
1842
+ const gasPrice = await client.getGasPrice()
1843
+ const tokensByAddress = await Tokens.resolveTokens(c, {
1631
1844
  addresses: [request.destinationToken, request.sourceToken],
1632
1845
  chainId,
1633
- include: options.include,
1846
+ include: query.include,
1634
1847
  })
1635
1848
  const destinationToken = tokensByAddress.get(request.destinationToken)
1636
1849
  const sourceToken = tokensByAddress.get(request.sourceToken)
@@ -1653,18 +1866,18 @@ export function exchanges(options: exchanges.Options = {}) {
1653
1866
  ...destinationToken,
1654
1867
  address: request.destinationToken,
1655
1868
  },
1656
- ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
1869
+ ...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
1870
+ gasEstimate: gasCost({ gasPrice, gasUnits }),
1657
1871
  mode: request.mode,
1872
+ provider: 'nativeDex',
1658
1873
  sourceAmount: valued(request.amount, request.sourceToken, sourceToken),
1659
1874
  sourceToken: {
1660
1875
  ...sourceToken,
1661
1876
  address: request.sourceToken,
1662
1877
  },
1878
+ status: 'ready',
1663
1879
  transaction: {
1664
- calls: [
1665
- { data: approve.data, to: approve.to, value: '0x0' },
1666
- { data: swap.data, to: swap.to, value: '0x0' },
1667
- ],
1880
+ calls: calls.map((call) => ({ ...call, value: '0x0' as const })),
1668
1881
  chainId,
1669
1882
  },
1670
1883
  }),
@@ -1677,16 +1890,12 @@ export function exchanges(options: exchanges.Options = {}) {
1677
1890
  tokenIn: request.sourceToken,
1678
1891
  tokenOut: request.destinationToken,
1679
1892
  })
1680
- if (sourceAmount === 0n) return quoteNotAvailable(c)
1893
+ if (sourceAmount === 0n) throw new ExchangeProvider.RouteUnavailableError()
1681
1894
 
1682
1895
  const maximumAmount =
1683
- (sourceAmount * (slippageDenominator + slippageBps)) / slippageDenominator
1684
- if (maximumAmount > maxSwapAmount)
1685
- return Response.error(c, {
1686
- code: 'quote_amount_out_of_range',
1687
- message: 'Slippage-adjusted source amount exceeds the native DEX limit',
1688
- status: 400,
1689
- })
1896
+ (sourceAmount * (slippageDenominator + slippageBps) + slippageDenominator - 1n) /
1897
+ slippageDenominator
1898
+ if (maximumAmount > maxSwapAmount) throw new NativeDexAmountOutOfRangeError()
1690
1899
 
1691
1900
  const approve = Actions.token.approve.call(client, {
1692
1901
  amount: maximumAmount,
@@ -1699,10 +1908,19 @@ export function exchanges(options: exchanges.Options = {}) {
1699
1908
  tokenIn: request.sourceToken,
1700
1909
  tokenOut: request.destinationToken,
1701
1910
  })
1702
- const tokensByAddress = await resolveTokensByAddress(c, {
1911
+ const calls = [
1912
+ { data: approve.data, to: approve.to, value: 0n },
1913
+ { data: swap.data, to: swap.to, value: 0n },
1914
+ ] as const
1915
+ const gasUnits = await client.estimateGas({
1916
+ ...(request.account ? { account: request.account } : {}),
1917
+ calls,
1918
+ })
1919
+ const gasPrice = await client.getGasPrice()
1920
+ const tokensByAddress = await Tokens.resolveTokens(c, {
1703
1921
  addresses: [request.destinationToken, request.sourceToken],
1704
1922
  chainId,
1705
- include: options.include,
1923
+ include: query.include,
1706
1924
  })
1707
1925
  const destinationToken = tokensByAddress.get(request.destinationToken)
1708
1926
  const sourceToken = tokensByAddress.get(request.sourceToken)
@@ -1716,34 +1934,209 @@ export function exchanges(options: exchanges.Options = {}) {
1716
1934
  ...destinationToken,
1717
1935
  address: request.destinationToken,
1718
1936
  },
1719
- ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
1937
+ ...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
1938
+ gasEstimate: gasCost({ gasPrice, gasUnits }),
1720
1939
  mode: request.mode,
1940
+ provider: 'nativeDex',
1721
1941
  sourceAmount: valued(sourceAmount, request.sourceToken, sourceToken),
1722
1942
  sourceAmountMax: valued(maximumAmount, request.sourceToken, sourceToken),
1723
1943
  sourceToken: {
1724
1944
  ...sourceToken,
1725
1945
  address: request.sourceToken,
1726
1946
  },
1947
+ status: 'ready',
1727
1948
  transaction: {
1728
- calls: [
1729
- { data: approve.data, to: approve.to, value: '0x0' },
1730
- { data: swap.data, to: swap.to, value: '0x0' },
1731
- ],
1949
+ calls: calls.map((call) => ({ ...call, value: '0x0' as const })),
1732
1950
  chainId,
1733
1951
  },
1734
1952
  }),
1735
1953
  200,
1736
1954
  )
1737
1955
  } catch (cause) {
1738
- if (request.mode === 'exactSource' && isQuoteAmountOutOfRange(cause))
1956
+ if (cause instanceof Valuation.UnsupportedDenominationError)
1957
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
1958
+ const amountOutOfRange =
1959
+ cause instanceof NativeDexAmountOutOfRangeError ||
1960
+ (request.mode === 'exactSource' && isQuoteAmountOutOfRange(cause))
1961
+ const providers = c.get('providers').filter(ExchangeProvider.is)
1962
+ if (amountOutOfRange && providers.length === 0)
1739
1963
  return Response.error(c, {
1740
1964
  code: 'quote_amount_out_of_range',
1741
1965
  message: 'Quote amount exceeds the native DEX limit',
1742
1966
  status: 400,
1743
1967
  })
1744
- if (isQuoteUnavailable(cause)) return quoteNotAvailable(c)
1745
- if (cause instanceof Valuation.UnsupportedDenominationError)
1746
- return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
1968
+ if (
1969
+ !(cause instanceof ExchangeProvider.RouteUnavailableError) &&
1970
+ !isQuoteUnavailable(cause) &&
1971
+ !amountOutOfRange
1972
+ )
1973
+ return Response.upstream(c, cause)
1974
+
1975
+ if (!request.account) return quoteNotAvailable(c)
1976
+
1977
+ for (const provider of providers)
1978
+ try {
1979
+ const quote = await provider.quote(
1980
+ {
1981
+ account: request.account,
1982
+ amount: request.amount,
1983
+ chainId,
1984
+ destinationToken: request.destinationToken,
1985
+ mode: request.mode,
1986
+ slippageBps: request.slippageBps,
1987
+ sourceToken: request.sourceToken,
1988
+ },
1989
+ c.req.raw.signal,
1990
+ )
1991
+ if (quote.status === 'ready')
1992
+ await validateProviderCalls(client, {
1993
+ account: request.account,
1994
+ calls: quote.transaction.calls,
1995
+ execution: providerExecution({ quote, request }),
1996
+ })
1997
+ const [gasPrice, gasUnits] = await Promise.all([
1998
+ client.getGasPrice(),
1999
+ quote.status === 'approvalRequired'
2000
+ ? client.estimateGas({
2001
+ account: request.account,
2002
+ calls: quote.approval.calls.map((call) => ({
2003
+ data: call.data,
2004
+ to: call.to,
2005
+ value: BigInt(call.value),
2006
+ })),
2007
+ })
2008
+ : Promise.resolve(BigInt(quote.gasUnits)),
2009
+ ])
2010
+ return c.json(
2011
+ Response.validated(
2012
+ schema.createQuote.Response,
2013
+ await providerQuoteResponse(c, {
2014
+ chainId,
2015
+ gasPrice,
2016
+ gasUnits,
2017
+ include: query.include,
2018
+ oracle,
2019
+ query,
2020
+ quote,
2021
+ request,
2022
+ provider,
2023
+ }),
2024
+ ),
2025
+ 200,
2026
+ )
2027
+ } catch (error) {
2028
+ if (error instanceof ExchangeProvider.RouteUnavailableError) continue
2029
+ const failure = ExchangeProvider.failure(error, {
2030
+ chainId,
2031
+ operation: 'quote',
2032
+ provider,
2033
+ })
2034
+ if (failure) c.set('providerFailure', failure)
2035
+ return Response.upstream(c, error)
2036
+ }
2037
+
2038
+ return quoteNotAvailable(c)
2039
+ }
2040
+ },
2041
+ )
2042
+ .post(
2043
+ '/v1/exchange/quotes/execute',
2044
+ Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
2045
+ OpenApi.validate('query', schema.createQuote.Query, {
2046
+ code: 'query_invalid',
2047
+ message: 'Invalid query parameters',
2048
+ }),
2049
+ OpenApi.validate('json', schema.executeQuote.Request, {
2050
+ code: 'body_invalid',
2051
+ message: 'Invalid request body',
2052
+ }),
2053
+ OpenApi.describeRoute({
2054
+ description:
2055
+ 'Finishes a provider quote after the wallet supplies the requested typed-data signature. This endpoint does not submit a transaction.',
2056
+ operationId: 'finalizeExchangeQuote',
2057
+ responses: OpenApi.responses({
2058
+ errors: {
2059
+ 400: {
2060
+ codes: [
2061
+ 'body_invalid',
2062
+ 'query_invalid',
2063
+ 'swap_continuation_invalid',
2064
+ 'swap_provider_invalid',
2065
+ ],
2066
+ },
2067
+ 502: 'The selected provider could not build the swap right now.',
2068
+ },
2069
+ success: {
2070
+ description: 'The final unsigned calls needed to execute the swap.',
2071
+ schema: schema.executeQuote.Response,
2072
+ },
2073
+ }),
2074
+ summary: 'Finalize quote',
2075
+ tags: ['Exchange'],
2076
+ }),
2077
+ async (c) => {
2078
+ if (Auth.narrowAccess) return Auth.paidAccessError(c)
2079
+ if (OpenApi.narrowValidation)
2080
+ return OpenApi.validationError(c, {
2081
+ code: 'query_invalid',
2082
+ message: 'Invalid query parameters',
2083
+ })
2084
+ if (OpenApi.narrowValidation)
2085
+ return OpenApi.validationError(c, {
2086
+ code: 'body_invalid',
2087
+ message: 'Invalid request body',
2088
+ })
2089
+
2090
+ const request = c.req.valid('json')
2091
+ const chainId = c.req.valid('query').chainId ?? c.get('chainId')
2092
+ const provider = c
2093
+ .get('providers')
2094
+ .filter(ExchangeProvider.is)
2095
+ .find((candidate) => candidate.id === request.provider && candidate.finalizeQuote)
2096
+ if (!provider?.finalizeQuote)
2097
+ return Response.error(c, {
2098
+ code: 'swap_provider_invalid',
2099
+ message: 'The selected swap provider is not configured',
2100
+ status: 400,
2101
+ })
2102
+
2103
+ c.header('Cache-Control', 'no-store')
2104
+ try {
2105
+ const client = c.get('getClient')(chainId)
2106
+ const result = await provider.finalizeQuote(
2107
+ {
2108
+ account: request.account,
2109
+ chainId,
2110
+ continuation: request.continuation,
2111
+ signature: request.signature,
2112
+ },
2113
+ c.req.raw.signal,
2114
+ )
2115
+ await validateProviderCalls(client, {
2116
+ account: request.account,
2117
+ calls: result.calls,
2118
+ execution: result.execution,
2119
+ })
2120
+ return c.json(
2121
+ Response.validated(schema.executeQuote.Response, {
2122
+ provider: provider.id,
2123
+ transaction: { calls: result.calls, chainId },
2124
+ }),
2125
+ 200,
2126
+ )
2127
+ } catch (cause) {
2128
+ if (cause instanceof ExchangeProvider.ProviderContinuationError)
2129
+ return Response.error(c, {
2130
+ code: 'swap_continuation_invalid',
2131
+ message: 'Invalid swap continuation',
2132
+ status: 400,
2133
+ })
2134
+ const failure = ExchangeProvider.failure(cause, {
2135
+ chainId,
2136
+ operation: 'finalize',
2137
+ provider,
2138
+ })
2139
+ if (failure) c.set('providerFailure', failure)
1747
2140
  return Response.upstream(c, cause)
1748
2141
  }
1749
2142
  },
@@ -1793,11 +2186,6 @@ export function exchanges(options: exchanges.Options = {}) {
1793
2186
  if (options.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
1794
2187
 
1795
2188
  try {
1796
- // The pair index (base address → pair) resolves each fill's book
1797
- // orientation (the order events only carry the base-side `token`;
1798
- // the quote is intrinsic to it).
1799
- const pairIndex = await getPairIndex(c, { chainId })
1800
-
1801
2189
  const page = await getSwaps(c, {
1802
2190
  chainId,
1803
2191
  cursor: options.cursor,
@@ -1806,7 +2194,6 @@ export function exchanges(options: exchanges.Options = {}) {
1806
2194
  limit: options.limit,
1807
2195
  maker: options.maker,
1808
2196
  order: options.order,
1809
- pairIndex,
1810
2197
  participant: options.participant,
1811
2198
  taker: options.taker,
1812
2199
  toBlock: options['blockNumber.to'],
@@ -1816,14 +2203,14 @@ export function exchanges(options: exchanges.Options = {}) {
1816
2203
 
1817
2204
  // `route` spans every swap and fill side, so one lookup resolves each
1818
2205
  // token once for required RPC metadata and requested curated fields.
1819
- const tokensByAddress = await resolveTokensByAddress(c, {
2206
+ const tokensByAddress = await Tokens.resolveTokens(c, {
1820
2207
  addresses: page.data.flatMap((swap) => swap.route),
1821
2208
  chainId,
1822
2209
  include: options.include,
1823
2210
  })
1824
2211
  // Value amounts via their curated display currency; snapshot or rate
1825
2212
  // failures degrade the values to `null`, never the feed.
1826
- const denomination = options.denomination ?? 'USD'
2213
+ const denomination = options['valuation.currency'] ?? 'USD'
1827
2214
  const snapshot =
1828
2215
  page.data.length > 0
1829
2216
  ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
@@ -1894,7 +2281,7 @@ export function exchanges(options: exchanges.Options = {}) {
1894
2281
  return c.json(
1895
2282
  Response.validated(schema.getSwaps.Response, {
1896
2283
  data,
1897
- ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
2284
+ ...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
1898
2285
  nextCursor: page.nextCursor,
1899
2286
  }),
1900
2287
  200,
@@ -1982,7 +2369,7 @@ export function exchanges(options: exchanges.Options = {}) {
1982
2369
  transactionHash: pair.transactionHash,
1983
2370
  }))
1984
2371
  if (options.include.includes('tokens') && data.length > 0) {
1985
- const tokensByAddress = await resolveTokensByAddress(c, {
2372
+ const tokensByAddress = await Tokens.resolveTokens(c, {
1986
2373
  addresses: data.flatMap((pair) => [pair.base.address, pair.quote.address]),
1987
2374
  chainId,
1988
2375
  include: tokenFields,
@@ -2079,7 +2466,7 @@ export function exchanges(options: exchanges.Options = {}) {
2079
2466
  let baseToken: z.output<typeof schema.PairToken> = { address: target.base }
2080
2467
  let quoteToken: z.output<typeof schema.PairToken> = { address: target.quote }
2081
2468
  if (options.include.includes('tokens')) {
2082
- const tokensByAddress = await resolveTokensByAddress(c, {
2469
+ const tokensByAddress = await Tokens.resolveTokens(c, {
2083
2470
  addresses: [target.base, target.quote],
2084
2471
  chainId,
2085
2472
  include: tokenFields,
@@ -2154,18 +2541,21 @@ export function exchanges(options: exchanges.Options = {}) {
2154
2541
  const chainId = options.chainId ?? c.get('chainId')
2155
2542
 
2156
2543
  try {
2157
- // The pair index (base address → pair) resolves the `base` selector
2158
- // and each order's pair; the OrderPlaced scan only carries the
2159
- // base-side `token`.
2160
- const pairIndex = await getPairIndex(c, { chainId })
2161
-
2162
2544
  // `base` selects the single pair it identifies — a pair's quote is
2163
2545
  // intrinsic to its base token on-chain, so the base address alone
2164
2546
  // is a complete selector.
2165
- let pair: getPairs.Row | undefined
2547
+ let pair: getPairsByBase.Pair | undefined
2166
2548
  if (options.base !== undefined) {
2167
- const target = pairIndex.get(options.base)
2168
- if (!target) return pairNotFoundError(c, { base: options.base, pairIndex })
2549
+ const target = (await getPairsByBase(c, { bases: [options.base], chainId })).get(
2550
+ options.base,
2551
+ )
2552
+ // The 404's quote-token hint scans the full pair index; fetch it
2553
+ // only on this miss path so page requests never pay for it.
2554
+ if (!target)
2555
+ return pairNotFoundError(c, {
2556
+ base: options.base,
2557
+ pairIndex: await getPairIndex(c, { chainId }),
2558
+ })
2169
2559
  pair = target
2170
2560
  }
2171
2561
 
@@ -2177,7 +2567,6 @@ export function exchanges(options: exchanges.Options = {}) {
2177
2567
  order: options.order,
2178
2568
  page: options.page,
2179
2569
  pair,
2180
- pairIndex,
2181
2570
  side: options.side,
2182
2571
  sort: options.sort,
2183
2572
  })
@@ -2188,7 +2577,7 @@ export function exchanges(options: exchanges.Options = {}) {
2188
2577
  // unavailable keeps just `address`.
2189
2578
  let data = page.data
2190
2579
  if (options.include.includes('tokens') && data.length > 0) {
2191
- const tokensByAddress = await resolveTokensByAddress(c, {
2580
+ const tokensByAddress = await Tokens.resolveTokens(c, {
2192
2581
  addresses: data.flatMap((o) => [o.pair.base.address, o.pair.quote.address]),
2193
2582
  chainId,
2194
2583
  include: tokenFields,
@@ -2512,7 +2901,7 @@ export function exchanges(options: exchanges.Options = {}) {
2512
2901
  let base: z.output<typeof schema.PairToken> = { address: target.base }
2513
2902
  let quote: z.output<typeof schema.PairToken> = { address: target.quote }
2514
2903
  if (options.include.includes('tokens')) {
2515
- const tokensByAddress = await resolveTokensByAddress(c, {
2904
+ const tokensByAddress = await Tokens.resolveTokens(c, {
2516
2905
  addresses: [target.base, target.quote],
2517
2906
  chainId,
2518
2907
  include: tokenFields,
@@ -2614,7 +3003,7 @@ export function exchanges(options: exchanges.Options = {}) {
2614
3003
  let base: z.output<typeof schema.PairToken> = { address: target.base }
2615
3004
  let quote: z.output<typeof schema.PairToken> = { address: target.quote }
2616
3005
  if (options.include.includes('tokens')) {
2617
- const tokensByAddress = await resolveTokensByAddress(c, {
3006
+ const tokensByAddress = await Tokens.resolveTokens(c, {
2618
3007
  addresses: [target.base, target.quote],
2619
3008
  chainId,
2620
3009
  include: tokenFields,
@@ -2641,6 +3030,8 @@ export declare namespace exchanges {
2641
3030
  type Options = {
2642
3031
  /** FX configuration backing amount valuation. */
2643
3032
  fx?: Valuation.addresses.Fx | undefined
3033
+ /** Whether to try Tempo's native stablecoin DEX first. Enabled by default. */
3034
+ nativeDex?: boolean | undefined
2644
3035
  }
2645
3036
  }
2646
3037
 
@@ -2667,68 +3058,6 @@ function pairNotFoundError(
2667
3058
  return Response.error(c, { code: 'pair_not_found', message, status: 404 })
2668
3059
  }
2669
3060
 
2670
- /** Resolves RPC token metadata and explicitly requested enrichments. Unresolvable addresses are omitted. */
2671
- async function resolveTokensByAddress(
2672
- c: Context<App.Environment>,
2673
- options: {
2674
- addresses: readonly z.output<typeof Schema.Address>[]
2675
- chainId: z.output<typeof Schema.ChainId>
2676
- include: readonly (typeof tokenFields)[number][]
2677
- },
2678
- ) {
2679
- const { addresses, chainId, include } = options
2680
- const includeLogoUri = include.includes('token.logoUri')
2681
- const includeVerified = include.includes('token.verified')
2682
- type Token = Omit<z.output<typeof Tokens.schema.Token>, 'verified'> & {
2683
- verified?: boolean | undefined
2684
- }
2685
- const tokensByAddress = new Map<string, Token>()
2686
- const uniqueTokens = [...new Set(addresses)]
2687
- if (uniqueTokens.length === 0) return tokensByAddress
2688
- const snapshot =
2689
- includeLogoUri || includeVerified
2690
- ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
2691
- : undefined
2692
-
2693
- const resolved = await Timing.time(c, 'tokens', () =>
2694
- Promise.all(
2695
- uniqueTokens.map(async (token) => {
2696
- try {
2697
- const [logoUri, metadata] = await Promise.all([
2698
- includeLogoUri
2699
- ? Timing.time(c, 'token_logo', () =>
2700
- Tokens.getTokenLogo(c, { address: token, chainId }),
2701
- ).catch(() => undefined)
2702
- : undefined,
2703
- Timing.time(c, 'token_metadata', () =>
2704
- Tokens.getTokenMetadata(c, { address: token, chainId }),
2705
- ),
2706
- ])
2707
- return [
2708
- token,
2709
- {
2710
- address: token,
2711
- currency: metadata.currency,
2712
- decimals: metadata.decimals,
2713
- id: token,
2714
- logoUri: includeLogoUri
2715
- ? (logoUri ?? snapshot?.byAddress.get(token)?.logoUri ?? metadata.logoUri)
2716
- : undefined,
2717
- name: metadata.name,
2718
- symbol: metadata.symbol,
2719
- verified: includeVerified ? snapshot?.byAddress.has(token) : undefined,
2720
- },
2721
- ] as const
2722
- } catch {
2723
- return [token, undefined] as const
2724
- }
2725
- }),
2726
- ),
2727
- )
2728
- for (const [token, metadata] of resolved) if (metadata) tokensByAddress.set(token, metadata)
2729
- return tokensByAddress
2730
- }
2731
-
2732
3061
  async function getPairs(c: Context<App.Environment>, options: getPairs.Options) {
2733
3062
  if (options.sort === 'liquidity') return getPairsByLiquidity(c, options)
2734
3063
  return getPairsByCreation(c, options)
@@ -2974,6 +3303,110 @@ declare namespace getPairIndex {
2974
3303
  }
2975
3304
  }
2976
3305
 
3306
+ /**
3307
+ * Base-address chunk size for {@link getPairsByBase}. `tidx.fetch` transmits
3308
+ * queries as GET, so an unbounded `IN (...)` could overflow the request URI.
3309
+ */
3310
+ const pairLookupBatchSize = 200
3311
+
3312
+ /**
3313
+ * Resolves the pairs for a set of book base addresses with one targeted
3314
+ * `dex_pairs` lookup, avoiding the full pair-index fetch (1MB+ on large
3315
+ * chains).
3316
+ */
3317
+ async function getPairsByBase(c: Context<App.Environment>, options: getPairsByBase.Options) {
3318
+ const { bases, chainId } = options
3319
+ const pairs = new Map<string, getPairsByBase.Pair>()
3320
+ if (bases.length === 0) return pairs
3321
+ const tidx = c.get('getTidx')(chainId)
3322
+
3323
+ return Timing.time(c, 'pair_lookup', async () => {
3324
+ for (let i = 0; i < bases.length; i += pairLookupBatchSize) {
3325
+ const list = bases
3326
+ .slice(i, i + pairLookupBatchSize)
3327
+ .map((base) => `'${base}'`)
3328
+ .join(', ')
3329
+ // `LIMIT 1 BY base` with the ascending sort picks each base's oldest
3330
+ // row, the row the pair index's last-entry-wins `Map` build selects.
3331
+ const result = await tidx.fetch({
3332
+ chainId,
3333
+ engine: 'clickhouse',
3334
+ query: `
3335
+ SELECT key, base, quote
3336
+ FROM dex_pairs
3337
+ WHERE address = '${stablecoinDex}' AND base IN (${list})
3338
+ ORDER BY block_num ASC, log_idx ASC
3339
+ LIMIT 1 BY base
3340
+ ` as string,
3341
+ })
3342
+ // Drop malformed rows, mirroring `parsePairRow`.
3343
+ for (const row of result.rows) {
3344
+ const key = Schema.Hash.safeParse(row['key'])
3345
+ const base = Schema.TokenAddress.safeParse(row['base'])
3346
+ const quote = Schema.TokenAddress.safeParse(row['quote'])
3347
+ if (key.success && base.success && quote.success)
3348
+ pairs.set(base.data, { base: base.data, key: key.data, quote: quote.data })
3349
+ }
3350
+ }
3351
+ return pairs
3352
+ })
3353
+ }
3354
+
3355
+ declare namespace getPairsByBase {
3356
+ type Options = {
3357
+ /** Book base addresses to resolve (lowercase). */
3358
+ bases: readonly string[]
3359
+ chainId: z.output<typeof Schema.ChainId>
3360
+ }
3361
+ /** Pair fields consumed by the swap and order feeds. */
3362
+ type Pair = {
3363
+ base: z.output<typeof Schema.TokenAddress>
3364
+ key: z.output<typeof Schema.Hash>
3365
+ quote: z.output<typeof Schema.TokenAddress>
3366
+ }
3367
+ }
3368
+
3369
+ /**
3370
+ * Resolves one pair by its book key with a targeted `dex_pairs` lookup,
3371
+ * covering books the bounded pair index ages out of its newest window.
3372
+ */
3373
+ async function getPairByKey(c: Context<App.Environment>, options: getPairByKey.Options) {
3374
+ const { chainId, key } = options
3375
+ const tidx = c.get('getTidx')(chainId)
3376
+
3377
+ return Timing.time(c, 'pair_lookup', async () => {
3378
+ // The ascending sort picks the key's oldest row, matching the pair
3379
+ // index's last-entry-wins `Map` build.
3380
+ const result = await tidx.fetch({
3381
+ chainId,
3382
+ engine: 'clickhouse',
3383
+ query: `
3384
+ SELECT key, base, quote
3385
+ FROM dex_pairs
3386
+ WHERE address = '${stablecoinDex}' AND key = '${key}'
3387
+ ORDER BY block_num ASC, log_idx ASC
3388
+ LIMIT 1
3389
+ ` as string,
3390
+ })
3391
+ const row = result.rows[0]
3392
+ if (!row) return undefined
3393
+ // Drop malformed rows, mirroring `parsePairRow`.
3394
+ const parsedKey = Schema.Hash.safeParse(row['key'])
3395
+ const base = Schema.TokenAddress.safeParse(row['base'])
3396
+ const quote = Schema.TokenAddress.safeParse(row['quote'])
3397
+ if (!parsedKey.success || !base.success || !quote.success) return undefined
3398
+ return { base: base.data, key: parsedKey.data, quote: quote.data }
3399
+ })
3400
+ }
3401
+
3402
+ declare namespace getPairByKey {
3403
+ type Options = {
3404
+ chainId: z.output<typeof Schema.ChainId>
3405
+ /** Book key (lowercase). */
3406
+ key: string
3407
+ }
3408
+ }
3409
+
2977
3410
  /**
2978
3411
  * Validates and shapes one `PairCreated` row into the internal pair shape.
2979
3412
  * Returns `undefined` for malformed rows so the caller can drop them without
@@ -3075,7 +3508,6 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
3075
3508
  limit,
3076
3509
  maker,
3077
3510
  order,
3078
- pairIndex,
3079
3511
  participant,
3080
3512
  taker,
3081
3513
  toBlock,
@@ -3221,16 +3653,17 @@ async function getSwaps(c: Context<App.Environment>, options: getSwaps.Options)
3221
3653
  }),
3222
3654
  )
3223
3655
 
3224
- // Each fill's book quote: from the `dex_pairs` index when present, else
3225
- // the base token's on-chain `quoteToken()` — some books predate the
3226
- // indexer's `PairCreated` decode (genesis system books) and have no
3227
- // `dex_pairs` row.
3656
+ // Each fill's book quote: from a targeted `dex_pairs` lookup of this
3657
+ // batch's bases when indexed, else the base token's on-chain
3658
+ // `quoteToken()` some books predate the indexer's `PairCreated`
3659
+ // decode (genesis system books) and have no `dex_pairs` row.
3228
3660
  const bases = [
3229
3661
  ...new Set([...states.values()].flatMap((events) => events.map((event) => event.token))),
3230
3662
  ]
3663
+ const pairs = await getPairsByBase(c, { bases, chainId })
3231
3664
  const quoteByBase = new Map<string, z.output<typeof Schema.TokenAddress>>()
3232
3665
  for (const bookBase of bases) {
3233
- const indexed = pairIndex.get(bookBase)
3666
+ const indexed = pairs.get(bookBase)
3234
3667
  if (indexed) quoteByBase.set(bookBase, indexed.quote)
3235
3668
  }
3236
3669
  const fallback = await Timing.time(c, 'swap_quotes', () =>
@@ -3517,8 +3950,6 @@ declare namespace getSwaps {
3517
3950
  maker?: z.output<typeof Schema.Address> | undefined
3518
3951
  /** Sort direction over `(block_num, first fill log_idx)`. */
3519
3952
  order: 'asc' | 'desc'
3520
- /** Pair index (base address → pair) used to orient each fill's book. */
3521
- pairIndex: Map<string, getPairs.Row>
3522
3953
  /** Either-side filter — the taker or any fill's maker. */
3523
3954
  participant?: z.output<typeof Schema.Address> | undefined
3524
3955
  taker?: z.output<typeof Schema.Address> | undefined
@@ -4321,11 +4752,11 @@ const orderCursorTickBias = 32768
4321
4752
  * `remaining = OrderPlaced.amount - sum(OrderFilled.amountFilled)`; orders
4322
4753
  * with an `OrderCancelled` event are excluded outright. Each row is annotated
4323
4754
  * with its pair: the scoped feed reuses the resolved `pair`, the unscoped
4324
- * feed resolves per row from the supplied pair index (rows whose pair is
4325
- * unknown are dropped).
4755
+ * feed resolves the page's base tokens with one targeted `dex_pairs` lookup
4756
+ * (rows whose pair is unknown are dropped).
4326
4757
  */
4327
4758
  async function getOrders(c: Context<App.Environment>, options: getOrders.Options) {
4328
- const { chainId, cursor, limit, maker, order, page, pair, pairIndex, side, sort } = options
4759
+ const { chainId, cursor, limit, maker, order, page, pair, side, sort } = options
4329
4760
  const store = c.get('store')
4330
4761
 
4331
4762
  return Timing.time(c, 'orders', async () => {
@@ -4357,9 +4788,12 @@ async function getOrders(c: Context<App.Environment>, options: getOrders.Options
4357
4788
  sort,
4358
4789
  })
4359
4790
 
4791
+ const bases = pair ? [] : [...new Set(pageRows.map((row) => row.token))]
4792
+ const pairsByBase = await getPairsByBase(c, { bases, chainId })
4793
+
4360
4794
  const data: z.output<typeof schema.getOrders.Order>[] = []
4361
4795
  for (const row of pageRows) {
4362
- const rowPair = pair ?? pairIndex.get(row.token)
4796
+ const rowPair = pair ?? pairsByBase.get(row.token)
4363
4797
  if (!rowPair) continue
4364
4798
  const shaped = shapeRestingOrder(row)
4365
4799
  if (!shaped) continue
@@ -4391,9 +4825,7 @@ declare namespace getOrders {
4391
4825
  /** Sort direction applied to whatever `sort` selects. */
4392
4826
  order: 'asc' | 'desc'
4393
4827
  /** When set, scope the snapshot to this resolved pair. */
4394
- pair?: getPairs.Row | undefined
4395
- /** Pair index (base address → pair) used to resolve unscoped rows. */
4396
- pairIndex: Map<string, getPairs.Row>
4828
+ pair?: getPairsByBase.Pair | undefined
4397
4829
  /** Restrict to one side of the book. */
4398
4830
  side?: 'bid' | 'ask' | undefined
4399
4831
  /** Sort key. `tick` ranks by price; `time` orders by placement (block, log). */
@@ -4696,10 +5128,16 @@ async function loadRestingOrders(
4696
5128
 
4697
5129
  const truncated = result.rows.length > scanCap
4698
5130
  const placedRows = result.rows.slice(0, scanCap)
5131
+ // `dex_orders` can carry duplicate rows per placement; keep the first
5132
+ // (newest) copy so pages emit each order once and a duplicate straddling an
5133
+ // IN-batch boundary can't double-count fills.
4699
5134
  const placed: RestingOrder[] = []
5135
+ const seen = new Set<string>()
4700
5136
  for (const row of placedRows) {
4701
5137
  const parsed = parsePlacedRow(row)
4702
- if (parsed) placed.push(parsed)
5138
+ if (!parsed || seen.has(parsed.orderId)) continue
5139
+ seen.add(parsed.orderId)
5140
+ placed.push(parsed)
4703
5141
  }
4704
5142
  if (placed.length === 0) return { orders: [], truncated }
4705
5143
 
@@ -4765,12 +5203,14 @@ export async function resolveBookReserves(
4765
5203
  const tidx = c.get('getTidx')(chainId)
4766
5204
 
4767
5205
  // Scan the newest `scanCap` `OrderPlaced` rows per base. `LIMIT ... BY token`
4768
- // is the ClickHouse fair cap; the app-side `perToken` guard re-applies it
4769
- // across chunks (a base can't span chunks, but the guard also flips
4770
- // `truncated` consistently) and against the `scanCap + 1` probe row.
5206
+ // is the ClickHouse fair cap over raw rows; `rawPerToken` flips `truncated`
5207
+ // when a token's window overflows, since unfetched older rows may hold unique
5208
+ // placements even when duplicates keep the unique count under the cap.
4771
5209
  let truncated = false
4772
5210
  const placed: RestingOrder[] = []
4773
5211
  const perToken = new Map<string, number>()
5212
+ const rawPerToken = new Map<string, number>()
5213
+ const seen = new Set<string>()
4774
5214
  for (let i = 0; i < unique.length; i += reserveBaseInBatchSize) {
4775
5215
  const group = unique.slice(i, i + reserveBaseInBatchSize)
4776
5216
  const list = group.map((base) => `'${base}'`).join(', ')
@@ -4788,11 +5228,15 @@ export async function resolveBookReserves(
4788
5228
  for (const row of result.rows) {
4789
5229
  const parsed = parsePlacedRow(row)
4790
5230
  if (!parsed) continue
5231
+ const raw = (rawPerToken.get(parsed.token) ?? 0) + 1
5232
+ rawPerToken.set(parsed.token, raw)
5233
+ if (raw > scanCap) truncated = true
5234
+ // Duplicate placement rows (see `loadRestingOrders`) can't add the same
5235
+ // order's remaining liquidity twice; the raw probe above still counts them.
5236
+ if (seen.has(parsed.orderId)) continue
4791
5237
  const count = perToken.get(parsed.token) ?? 0
4792
- if (count >= scanCap) {
4793
- truncated = true
4794
- continue
4795
- }
5238
+ if (count >= scanCap) continue
5239
+ seen.add(parsed.orderId)
4796
5240
  perToken.set(parsed.token, count + 1)
4797
5241
  placed.push(parsed)
4798
5242
  }
@@ -4908,6 +5352,9 @@ async function fetchCancelledByIds(
4908
5352
  const cancelled = new Set<string>()
4909
5353
  if (orderIds.length === 0) return cancelled
4910
5354
  const tidx = c.get('getTidx')(chainId)
5355
+ // Chunks resolve serially: the managed indexer rejects bursts of concurrent
5356
+ // chunk queries (~2-3 in flight), and the cancel/fill helpers already
5357
+ // overlap via `Promise.all`.
4911
5358
  for (let i = 0; i < orderIds.length; i += orderInBatchSize) {
4912
5359
  const batch = orderIds.slice(i, i + orderInBatchSize)
4913
5360
  const result = await tidx.fetch({
@@ -4942,6 +5389,9 @@ async function fetchFillSumsByIds(
4942
5389
  const sums = new Map<string, bigint>()
4943
5390
  if (orderIds.length === 0) return sums
4944
5391
  const tidx = c.get('getTidx')(chainId)
5392
+ // Chunks resolve serially: the managed indexer rejects bursts of concurrent
5393
+ // chunk queries (~2-3 in flight), and the cancel/fill helpers already
5394
+ // overlap via `Promise.all`.
4945
5395
  for (let i = 0; i < orderIds.length; i += orderInBatchSize) {
4946
5396
  const batch = orderIds.slice(i, i + orderInBatchSize)
4947
5397
  const result = await tidx.fetch({
@@ -5055,31 +5505,28 @@ async function getOrder(c: Context<App.Environment>, options: getOrder.Options)
5055
5505
  Store.memoize(
5056
5506
  async () => {
5057
5507
  // The on-chain DEX exposes the full order struct (placement params
5058
- // plus current `remaining`) in one view call. We resolve the pair
5059
- // off the returned `bookKey` concurrently with the RPC; on a warm
5060
- // pair-index cache that's effectively free.
5061
- const [chainOrder, pairsByKey] = await Promise.all([
5062
- Timing.time(c, 'order_dex_get', () =>
5063
- // The DEX reverts with `OrderDoesNotExist()` for ids it no longer
5064
- // holds (never placed, or filled/cancelled and cleared), rather
5065
- // than returning the zero struct. Map that revert to not-found so
5066
- // the route answers 404 instead of bubbling up as a 502.
5067
- client.dex.getOrder({ orderId: BigInt(orderId) }).catch((cause) => {
5068
- if (isOrderDoesNotExist(cause)) return undefined
5069
- throw cause
5070
- }),
5071
- ),
5072
- getPairIndexByKey(c, { chainId }),
5073
- ])
5508
+ // plus current `remaining`) in one view call.
5509
+ const chainOrder = await Timing.time(c, 'order_dex_get', () =>
5510
+ // The DEX reverts with `OrderDoesNotExist()` for ids it no longer
5511
+ // holds (never placed, or filled/cancelled and cleared), rather
5512
+ // than returning the zero struct. Map that revert to not-found so
5513
+ // the route answers 404 instead of bubbling up as a 502.
5514
+ client.dex.getOrder({ orderId: BigInt(orderId) }).catch((cause) => {
5515
+ if (isOrderDoesNotExist(cause)) return undefined
5516
+ throw cause
5517
+ }),
5518
+ )
5074
5519
 
5075
5520
  // The DEX returns the zero struct (or reverts, handled above) when an
5076
5521
  // order id is unknown.
5077
5522
  if (!chainOrder || chainOrder.orderId === 0n) return undefined
5078
5523
 
5079
- // Pair index miss is a soft consistency floor (the index is bounded
5080
- // by `pairIndexLimit`); without `(base, quote)` we cannot frame the
5081
- // order, so surface as not-found.
5082
- const pair = pairsByKey.get(chainOrder.bookKey.toLowerCase())
5524
+ // Targeted lookup off the returned `bookKey`; the bounded pair index
5525
+ // misses books outside its newest window, 404ing live orders.
5526
+ const pair = await getPairByKey(c, {
5527
+ chainId,
5528
+ key: chainOrder.bookKey.toLowerCase(),
5529
+ })
5083
5530
  if (!pair) return undefined
5084
5531
 
5085
5532
  const tick = chainOrder.tick
@@ -5151,11 +5598,187 @@ function isOrderDoesNotExist(error: unknown): boolean {
5151
5598
  )
5152
5599
  }
5153
5600
 
5154
- /** Returns the native DEX 404 used when no executable quote is available. */
5601
+ async function providerQuoteResponse(
5602
+ c: Context<App.Environment>,
5603
+ options: {
5604
+ chainId: number
5605
+ gasPrice: bigint
5606
+ gasUnits: bigint
5607
+ include: readonly z.output<typeof schema.createQuote.Include>[]
5608
+ oracle: FxOracle.Oracle
5609
+ provider: ExchangeProvider.Provider
5610
+ query: z.output<typeof schema.createQuote.Query>
5611
+ quote: ExchangeProvider.QuoteResult
5612
+ request: z.output<typeof schema.createQuote.Request>
5613
+ },
5614
+ ) {
5615
+ const { chainId, gasPrice, gasUnits, oracle, provider, query, quote, request } = options
5616
+ const tokensByAddress = await Tokens.resolveTokens(c, {
5617
+ addresses: [request.destinationToken, request.sourceToken],
5618
+ chainId,
5619
+ include: options.include,
5620
+ })
5621
+ const destinationToken = tokensByAddress.get(request.destinationToken)
5622
+ const sourceToken = tokensByAddress.get(request.sourceToken)
5623
+ if (!destinationToken || !sourceToken)
5624
+ throw new Error('Unable to resolve exchange quote token metadata')
5625
+
5626
+ const denomination = query['valuation.currency'] ?? 'USD'
5627
+ const snapshot = await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
5628
+ const rates = await Valuation.ratesFor(c, {
5629
+ currencies: snapshot
5630
+ ? [request.destinationToken, request.sourceToken].flatMap((address) => {
5631
+ const held = snapshot.byAddress.get(address.toLowerCase())?.currency
5632
+ return held === undefined ? [] : [held]
5633
+ })
5634
+ : [],
5635
+ denomination,
5636
+ oracle,
5637
+ })
5638
+ const valued = (
5639
+ baseUnits: bigint | string,
5640
+ address: string,
5641
+ token: { currency: string; decimals: number },
5642
+ ) => ({
5643
+ ...Value.tokenAmount({ baseUnits, currency: token.currency, decimals: token.decimals }),
5644
+ valuation: Valuation.valuationFor({
5645
+ amount: BigInt(baseUnits),
5646
+ denomination,
5647
+ rates,
5648
+ token: snapshot?.byAddress.get(address.toLowerCase()),
5649
+ }),
5650
+ })
5651
+ const execution =
5652
+ quote.status === 'ready'
5653
+ ? {
5654
+ status: quote.status,
5655
+ transaction: { calls: [...quote.transaction.calls], chainId },
5656
+ }
5657
+ : quote.status === 'approvalRequired'
5658
+ ? { approval: { calls: [...quote.approval.calls] }, status: quote.status }
5659
+ : {
5660
+ continuation: quote.continuation,
5661
+ status: quote.status,
5662
+ typedData: quote.typedData,
5663
+ }
5664
+ const common = {
5665
+ destinationToken: { ...destinationToken, address: request.destinationToken },
5666
+ ...execution,
5667
+ ...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
5668
+ gasEstimate: gasCost({ gasPrice, gasUnits }),
5669
+ provider: provider.id,
5670
+ sourceToken: { ...sourceToken, address: request.sourceToken },
5671
+ }
5672
+
5673
+ if (request.mode === 'exactSource') {
5674
+ if (!quote.minimumDestinationAmount) throw new ExchangeProvider.ProviderPayloadError()
5675
+ return {
5676
+ ...common,
5677
+ destinationAmount: valued(
5678
+ quote.destinationAmount,
5679
+ request.destinationToken,
5680
+ destinationToken,
5681
+ ),
5682
+ destinationAmountMin: valued(
5683
+ quote.minimumDestinationAmount,
5684
+ request.destinationToken,
5685
+ destinationToken,
5686
+ ),
5687
+ mode: request.mode,
5688
+ sourceAmount: valued(quote.sourceAmount, request.sourceToken, sourceToken),
5689
+ }
5690
+ }
5691
+
5692
+ if (!quote.maximumSourceAmount) throw new ExchangeProvider.ProviderPayloadError()
5693
+ return {
5694
+ ...common,
5695
+ destinationAmount: valued(quote.destinationAmount, request.destinationToken, destinationToken),
5696
+ mode: request.mode,
5697
+ sourceAmount: valued(quote.sourceAmount, request.sourceToken, sourceToken),
5698
+ sourceAmountMax: valued(quote.maximumSourceAmount, request.sourceToken, sourceToken),
5699
+ }
5700
+ }
5701
+
5702
+ function providerExecution(options: providerExecution.Options): ExchangeProvider.Execution {
5703
+ const { quote, request } = options
5704
+ return {
5705
+ destinationAmount: quote.destinationAmount,
5706
+ destinationToken: request.destinationToken,
5707
+ gasUnits: quote.gasUnits,
5708
+ ...(quote.maximumSourceAmount ? { maximumSourceAmount: quote.maximumSourceAmount } : {}),
5709
+ ...(quote.minimumDestinationAmount
5710
+ ? { minimumDestinationAmount: quote.minimumDestinationAmount }
5711
+ : {}),
5712
+ mode: request.mode,
5713
+ sourceAmount: quote.sourceAmount,
5714
+ sourceToken: request.sourceToken,
5715
+ }
5716
+ }
5717
+
5718
+ declare namespace providerExecution {
5719
+ type Options = {
5720
+ quote: ExchangeProvider.QuoteResult
5721
+ request: z.output<typeof schema.createQuote.Request>
5722
+ }
5723
+ }
5724
+
5725
+ async function validateProviderCalls(
5726
+ client: Viem.getClient.ReturnType,
5727
+ options: validateProviderCalls.Options,
5728
+ ) {
5729
+ try {
5730
+ const { results } = await Actions.simulate.simulateCalls(client, {
5731
+ account: options.account,
5732
+ calls: options.calls.map((call) => ({
5733
+ data: call.data,
5734
+ to: call.to,
5735
+ value: BigInt(call.value),
5736
+ })),
5737
+ traceTransfers: true,
5738
+ })
5739
+ if (results.some((result) => result.status !== 'success'))
5740
+ throw new ExchangeProvider.ProviderPayloadError()
5741
+
5742
+ const logs = results.flatMap((result) => result.logs ?? [])
5743
+ const transfers = parseEventLogs({
5744
+ abi: [AbiEvent.fromAbi(Abis.tip20, 'Transfer')],
5745
+ eventName: 'Transfer',
5746
+ logs,
5747
+ }).map((log) => ({
5748
+ amount: log.args.amount,
5749
+ from: log.args.from,
5750
+ to: log.args.to,
5751
+ token: log.address,
5752
+ }))
5753
+ ExchangeProvider.validateExecution({
5754
+ account: options.account,
5755
+ execution: options.execution,
5756
+ transfers,
5757
+ })
5758
+ } catch (cause) {
5759
+ if (cause instanceof ExchangeProvider.ProviderPayloadError) throw cause
5760
+ throw new ExchangeProvider.ProviderPayloadError()
5761
+ }
5762
+ }
5763
+
5764
+ declare namespace validateProviderCalls {
5765
+ type Options = {
5766
+ account: `0x${string}`
5767
+ calls: readonly ExchangeProvider.Call[]
5768
+ execution: ExchangeProvider.Execution
5769
+ }
5770
+ }
5771
+
5772
+ /** Converts estimated gas units to a native-token base-unit fee. */
5773
+ function gasCost(options: { gasPrice: bigint; gasUnits: bigint }) {
5774
+ return (options.gasPrice * options.gasUnits).toString()
5775
+ }
5776
+
5777
+ /** Returns the 404 used when no executable quote is available. */
5155
5778
  function quoteNotAvailable(c: Context<App.Environment>) {
5156
5779
  return Response.error(c, {
5157
5780
  code: 'quote_not_available',
5158
- message: 'No executable quote is available on Tempo’s native DEX',
5781
+ message: 'No configured exchange provider has an executable quote',
5159
5782
  status: 404,
5160
5783
  })
5161
5784
  }
@@ -5309,18 +5932,6 @@ declare namespace countOrderFills {
5309
5932
  }
5310
5933
  }
5311
5934
 
5312
- /**
5313
- * Per-key pair index derived from the existing per-base index. Built once
5314
- * and memoized at the same cadence as the pair index itself so chained
5315
- * `getOrder` lookups stay an O(1) `Map.get`.
5316
- */
5317
- async function getPairIndexByKey(c: Context<App.Environment>, options: getPairIndex.Options) {
5318
- const pairsByBase = await getPairIndex(c, options)
5319
- const pairsByKey = new Map<string, getPairs.Row>()
5320
- for (const pair of pairsByBase.values()) pairsByKey.set(pair.key.toLowerCase(), pair)
5321
- return pairsByKey
5322
- }
5323
-
5324
5935
  /**
5325
5936
  * Validates and shapes one `OrderFilled` row into the public `Fill` shape.
5326
5937
  * Drops the row when any required column is malformed so the caller can omit
@@ -5358,3 +5969,7 @@ function parseOrderFillRow(
5358
5969
  transactionHash: transactionHash.data,
5359
5970
  }
5360
5971
  }
5972
+
5973
+ class NativeDexAmountOutOfRangeError extends Error {
5974
+ override name = 'Exchanges.NativeDexAmountOutOfRangeError'
5975
+ }