tapimo 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/dist/App.d.ts +13987 -5755
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/admin/App.d.ts +910 -412
  5. package/dist/admin/App.d.ts.map +1 -1
  6. package/dist/admin/App.js +4 -0
  7. package/dist/admin/App.js.map +1 -1
  8. package/dist/admin/apps/early-access.d.ts +148 -0
  9. package/dist/admin/apps/early-access.d.ts.map +1 -0
  10. package/dist/admin/apps/early-access.js +133 -0
  11. package/dist/admin/apps/early-access.js.map +1 -0
  12. package/dist/admin/apps/verified-tokens.d.ts +526 -1
  13. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  14. package/dist/admin/apps/verified-tokens.js +214 -1
  15. package/dist/admin/apps/verified-tokens.js.map +1 -1
  16. package/dist/admin/ui.generated.d.ts +1 -1
  17. package/dist/admin/ui.generated.d.ts.map +1 -1
  18. package/dist/admin/ui.generated.js +45 -33
  19. package/dist/admin/ui.generated.js.map +1 -1
  20. package/dist/apps/data/App.d.ts +3733 -2428
  21. package/dist/apps/data/App.d.ts.map +1 -1
  22. package/dist/apps/data/App.js +11 -6
  23. package/dist/apps/data/App.js.map +1 -1
  24. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  25. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  26. package/dist/apps/data/FundingQuoteProviders.js +193 -2
  27. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  28. package/dist/apps/data/FxOracle.d.ts +71 -0
  29. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  30. package/dist/apps/data/FxOracle.js +97 -0
  31. package/dist/apps/data/FxOracle.js.map +1 -0
  32. package/dist/apps/data/index.d.ts +1 -0
  33. package/dist/apps/data/index.d.ts.map +1 -1
  34. package/dist/apps/data/index.js +1 -0
  35. package/dist/apps/data/index.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +3575 -1358
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +230 -88
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +872 -251
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/addresses.js +11 -9
  43. package/dist/apps/data/routes/addresses.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +108 -57
  45. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.js +88 -45
  47. package/dist/apps/data/routes/balances.js.map +1 -1
  48. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  49. package/dist/apps/data/routes/exchanges.d.ts +952 -201
  50. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  51. package/dist/apps/data/routes/exchanges.js +518 -76
  52. package/dist/apps/data/routes/exchanges.js.map +1 -1
  53. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  54. package/dist/apps/data/routes/funding.d.ts +8 -2
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  56. package/dist/apps/data/routes/funding.js +32 -6
  57. package/dist/apps/data/routes/funding.js.map +1 -1
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts +392 -315
  60. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  61. package/dist/apps/data/routes/receipts.js +152 -77
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/rpc.d.ts +31 -31
  64. package/dist/apps/data/routes/tokens.d.ts +166 -173
  65. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  66. package/dist/apps/data/routes/tokens.js +14 -12
  67. package/dist/apps/data/routes/tokens.js.map +1 -1
  68. package/dist/apps/data/routes/transactions.d.ts +277 -536
  69. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  70. package/dist/apps/data/routes/transactions.js +96 -77
  71. package/dist/apps/data/routes/transactions.js.map +1 -1
  72. package/dist/apps/data/routes/transfers.d.ts +82 -17
  73. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  74. package/dist/apps/data/routes/transfers.js +70 -13
  75. package/dist/apps/data/routes/transfers.js.map +1 -1
  76. package/dist/apps/data/routes/valuation.d.ts +307 -0
  77. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  78. package/dist/apps/data/routes/valuation.js +292 -0
  79. package/dist/apps/data/routes/valuation.js.map +1 -0
  80. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  81. package/dist/apps/data/routes/verified-tokens.js +7 -5
  82. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  83. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  84. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  85. package/dist/apps/data/routes/webhooks.js +124 -25
  86. package/dist/apps/data/routes/webhooks.js.map +1 -1
  87. package/dist/apps/index.d.ts +1 -1
  88. package/dist/apps/index.d.ts.map +1 -1
  89. package/dist/apps/index.js +1 -1
  90. package/dist/apps/index.js.map +1 -1
  91. package/dist/apps/management/App.d.ts +8606 -1681
  92. package/dist/apps/management/App.d.ts.map +1 -1
  93. package/dist/apps/management/App.js +12 -1
  94. package/dist/apps/management/App.js.map +1 -1
  95. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  96. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  97. package/dist/apps/management/routes/api-keys.js +5 -3
  98. package/dist/apps/management/routes/api-keys.js.map +1 -1
  99. package/dist/apps/management/routes/billing.d.ts +88 -48
  100. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  101. package/dist/apps/management/routes/billing.js +10 -2
  102. package/dist/apps/management/routes/billing.js.map +1 -1
  103. package/dist/apps/management/routes/faucet.d.ts +218 -0
  104. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  105. package/dist/apps/management/routes/faucet.js +139 -0
  106. package/dist/apps/management/routes/faucet.js.map +1 -0
  107. package/dist/apps/management/routes/invitations.d.ts +121 -121
  108. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  109. package/dist/apps/management/routes/invitations.js +3 -0
  110. package/dist/apps/management/routes/invitations.js.map +1 -1
  111. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  112. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  113. package/dist/apps/management/routes/invite-links.js +26 -1
  114. package/dist/apps/management/routes/invite-links.js.map +1 -1
  115. package/dist/apps/management/routes/me.d.ts +2 -0
  116. package/dist/apps/management/routes/me.d.ts.map +1 -1
  117. package/dist/apps/management/routes/me.js +4 -0
  118. package/dist/apps/management/routes/me.js.map +1 -1
  119. package/dist/apps/management/routes/members.d.ts +49 -49
  120. package/dist/apps/management/routes/orgs.d.ts +91 -91
  121. package/dist/apps/management/routes/projects.d.ts +44 -44
  122. package/dist/apps/management/routes/usage.d.ts +35 -32
  123. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  124. package/dist/apps/management/routes/usage.js +4 -0
  125. package/dist/apps/management/routes/usage.js.map +1 -1
  126. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  127. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  128. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  129. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  130. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  131. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  132. package/dist/apps/management/routes/webhooks.js +573 -0
  133. package/dist/apps/management/routes/webhooks.js.map +1 -0
  134. package/dist/db/Db.d.ts +4 -0
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +83 -0
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +41 -0
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +45 -0
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/earlyAccess.d.ts +50 -0
  143. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  144. package/dist/db/tables/earlyAccess.js +63 -0
  145. package/dist/db/tables/earlyAccess.js.map +1 -0
  146. package/dist/db/tables/invitations.d.ts +12 -1
  147. package/dist/db/tables/invitations.d.ts.map +1 -1
  148. package/dist/db/tables/invitations.js +23 -0
  149. package/dist/db/tables/invitations.js.map +1 -1
  150. package/dist/db/tables/inviteLinks.d.ts +10 -2
  151. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  152. package/dist/db/tables/inviteLinks.js +10 -1
  153. package/dist/db/tables/inviteLinks.js.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  155. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  156. package/dist/db/tables/sponsoredTransactions.js +2 -0
  157. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  158. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  159. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  160. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  161. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  162. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  163. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  164. package/dist/db/tables/webhookSubscriptions.js +16 -0
  165. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  166. package/dist/internal/Auth.d.ts +43 -2
  167. package/dist/internal/Auth.d.ts.map +1 -1
  168. package/dist/internal/Auth.js +60 -45
  169. package/dist/internal/Auth.js.map +1 -1
  170. package/dist/internal/FundingQuotes.d.ts +69 -15
  171. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  172. package/dist/internal/FundingQuotes.js +74 -25
  173. package/dist/internal/FundingQuotes.js.map +1 -1
  174. package/dist/internal/Log.d.ts +19 -1
  175. package/dist/internal/Log.d.ts.map +1 -1
  176. package/dist/internal/Log.js +2 -0
  177. package/dist/internal/Log.js.map +1 -1
  178. package/dist/internal/MetricSink.d.ts.map +1 -1
  179. package/dist/internal/MetricSink.js +9 -0
  180. package/dist/internal/MetricSink.js.map +1 -1
  181. package/dist/internal/OpenApi.d.ts +2 -2
  182. package/dist/internal/OpenApi.d.ts.map +1 -1
  183. package/dist/internal/OpenApi.js +3 -0
  184. package/dist/internal/OpenApi.js.map +1 -1
  185. package/dist/internal/Schema.d.ts +14 -5
  186. package/dist/internal/Schema.d.ts.map +1 -1
  187. package/dist/internal/Schema.js +23 -5
  188. package/dist/internal/Schema.js.map +1 -1
  189. package/dist/internal/Value.d.ts +18 -0
  190. package/dist/internal/Value.d.ts.map +1 -1
  191. package/dist/internal/Value.js +11 -1
  192. package/dist/internal/Value.js.map +1 -1
  193. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  194. package/dist/internal/VerifiedTokens.js +10 -1
  195. package/dist/internal/VerifiedTokens.js.map +1 -1
  196. package/dist/internal/Viem.d.ts +1 -1
  197. package/dist/internal/Webhooks.d.ts +24 -3
  198. package/dist/internal/Webhooks.d.ts.map +1 -1
  199. package/dist/internal/Webhooks.js +34 -8
  200. package/dist/internal/Webhooks.js.map +1 -1
  201. package/package.json +1 -1
  202. package/src/App.ts +9 -1
  203. package/src/Client.test-d.ts +638 -6
  204. package/src/admin/App.ts +4 -0
  205. package/src/admin/apps/early-access.test.ts +125 -0
  206. package/src/admin/apps/early-access.ts +175 -0
  207. package/src/admin/apps/verified-tokens.test.ts +201 -0
  208. package/src/admin/apps/verified-tokens.ts +532 -286
  209. package/src/admin/ui/plugin.ts +2 -1
  210. package/src/admin/ui/src/lib/early-access.ts +63 -0
  211. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  212. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  213. package/src/admin/ui/src/routes/__root.tsx +2 -1
  214. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  215. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  216. package/src/admin/ui.generated.ts +48 -33
  217. package/src/apps/data/App.ts +22 -14
  218. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  219. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  220. package/src/apps/data/FxOracle.test.ts +118 -0
  221. package/src/apps/data/FxOracle.ts +153 -0
  222. package/src/apps/data/index.ts +1 -0
  223. package/src/apps/data/routes/activities.test.ts +173 -3
  224. package/src/apps/data/routes/activities.ts +403 -132
  225. package/src/apps/data/routes/addresses.ts +19 -9
  226. package/src/apps/data/routes/balances.test.ts +144 -11
  227. package/src/apps/data/routes/balances.ts +140 -51
  228. package/src/apps/data/routes/exchanges.test.ts +540 -9
  229. package/src/apps/data/routes/exchanges.ts +690 -103
  230. package/src/apps/data/routes/funding.test.ts +84 -0
  231. package/src/apps/data/routes/funding.ts +38 -6
  232. package/src/apps/data/routes/receipts.test.ts +125 -23
  233. package/src/apps/data/routes/receipts.ts +205 -100
  234. package/src/apps/data/routes/tokens.test.ts +43 -0
  235. package/src/apps/data/routes/tokens.ts +27 -12
  236. package/src/apps/data/routes/transactions.test.ts +72 -19
  237. package/src/apps/data/routes/transactions.ts +162 -92
  238. package/src/apps/data/routes/transfers.test.ts +146 -4
  239. package/src/apps/data/routes/transfers.ts +91 -13
  240. package/src/apps/data/routes/valuation.test.ts +422 -0
  241. package/src/apps/data/routes/valuation.ts +423 -0
  242. package/src/apps/data/routes/verified-tokens.ts +14 -2
  243. package/src/apps/data/routes/webhooks.test.ts +56 -8
  244. package/src/apps/data/routes/webhooks.ts +144 -27
  245. package/src/apps/index.ts +1 -1
  246. package/src/apps/management/App.ts +13 -0
  247. package/src/apps/management/routes/api-keys.test.ts +87 -0
  248. package/src/apps/management/routes/api-keys.ts +4 -3
  249. package/src/apps/management/routes/billing.test.ts +112 -1
  250. package/src/apps/management/routes/billing.ts +7 -2
  251. package/src/apps/management/routes/faucet.test.ts +79 -0
  252. package/src/apps/management/routes/faucet.ts +185 -0
  253. package/src/apps/management/routes/invitations.test.ts +172 -0
  254. package/src/apps/management/routes/invitations.ts +3 -0
  255. package/src/apps/management/routes/invite-links.test.ts +117 -0
  256. package/src/apps/management/routes/invite-links.ts +43 -1
  257. package/src/apps/management/routes/me.test.ts +140 -88
  258. package/src/apps/management/routes/me.ts +6 -0
  259. package/src/apps/management/routes/usage.test.ts +6 -0
  260. package/src/apps/management/routes/usage.ts +7 -0
  261. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  262. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  263. package/src/apps/management/routes/webhooks.test.ts +426 -0
  264. package/src/apps/management/routes/webhooks.ts +672 -0
  265. package/src/db/Db.test.ts +19 -0
  266. package/src/db/Db.ts +95 -0
  267. package/src/db/Schema.ts +53 -0
  268. package/src/db/tables/earlyAccess.ts +86 -0
  269. package/src/db/tables/invitations.ts +26 -1
  270. package/src/db/tables/inviteLinks.ts +22 -3
  271. package/src/db/tables/sponsoredTransactions.ts +4 -0
  272. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  273. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  274. package/src/db/tables/webhookSubscriptions.ts +31 -0
  275. package/src/internal/Auth.ts +73 -45
  276. package/src/internal/FundingQuotes.test.ts +143 -1
  277. package/src/internal/FundingQuotes.ts +95 -28
  278. package/src/internal/Log.test-d.ts +21 -0
  279. package/src/internal/Log.test.ts +50 -0
  280. package/src/internal/Log.ts +24 -1
  281. package/src/internal/MetricSink.test.ts +160 -0
  282. package/src/internal/MetricSink.ts +10 -0
  283. package/src/internal/OpenApi.ts +3 -0
  284. package/src/internal/Schema.ts +50 -5
  285. package/src/internal/Value.test.ts +20 -0
  286. package/src/internal/Value.ts +24 -1
  287. package/src/internal/VerifiedTokens.ts +9 -1
  288. package/src/internal/Webhooks.test.ts +140 -0
  289. package/src/internal/Webhooks.ts +53 -9
@@ -93,6 +93,7 @@ describe('GET /funding/quotes', () => {
93
93
  })
94
94
 
95
95
  test('returns an empty list when the provider reports no quote', async () => {
96
+ const entries: Log.Entry[] = []
96
97
  const app = TestApp.create({
97
98
  funding: {
98
99
  adapters: {
@@ -103,11 +104,22 @@ describe('GET /funding/quotes', () => {
103
104
  },
104
105
  },
105
106
  },
107
+ logger: (entry) => {
108
+ entries.push(entry)
109
+ },
106
110
  })
107
111
  const response = await app.request(quoteUrl(), { headers: TestApp.auth.headers })
108
112
  expect(response.status).toBe(200)
109
113
  const body = await TestApp.json(response, Funding.schema.quoteFunding.Response)
110
114
  expect(body.data).toEqual([])
115
+ expect(entries[0]?.fundingProviderAttempts).toEqual([
116
+ {
117
+ durationMs: expect.any(Number),
118
+ id: 'relay',
119
+ operation: 'quote/v2',
120
+ outcome: 'unavailable',
121
+ },
122
+ ])
111
123
  })
112
124
 
113
125
  test('returns 502 when every matching provider fails', async () => {
@@ -156,6 +168,15 @@ describe('GET /funding/quotes', () => {
156
168
  id: 'across',
157
169
  operation: 'swap/approval',
158
170
  })
171
+ expect(entries[0]?.fundingProviderAttempts).toEqual([
172
+ {
173
+ durationMs: expect.any(Number),
174
+ failure: 'timeout',
175
+ id: 'across',
176
+ operation: 'swap/approval',
177
+ outcome: 'failed',
178
+ },
179
+ ])
159
180
  })
160
181
 
161
182
  test('logs every failure when multiple providers fail', async () => {
@@ -192,6 +213,62 @@ describe('GET /funding/quotes', () => {
192
213
  { failure: 'network', id: 'across', operation: 'swap/approval' },
193
214
  { failure: 'unknown', id: 'relay', operation: 'quote/v2' },
194
215
  ])
216
+ expect(entries[0]?.fundingProviderAttempts).toEqual([
217
+ {
218
+ durationMs: expect.any(Number),
219
+ failure: 'network',
220
+ id: 'across',
221
+ operation: 'swap/approval',
222
+ outcome: 'failed',
223
+ },
224
+ {
225
+ durationMs: expect.any(Number),
226
+ failure: 'unknown',
227
+ id: 'relay',
228
+ operation: 'quote/v2',
229
+ outcome: 'failed',
230
+ },
231
+ ])
232
+ })
233
+
234
+ test('logs failed providers when another provider returns a quote', async () => {
235
+ const entries: Log.Entry[] = []
236
+ const app = TestApp.create({
237
+ funding: {
238
+ adapters: {
239
+ across: {
240
+ async probeQuote() {
241
+ throw new TypeError(`wallet ${'0xaa'.repeat(20)} could not reach secret-provider`)
242
+ },
243
+ },
244
+ relay: relayAdapter(),
245
+ },
246
+ },
247
+ logger: (entry) => {
248
+ entries.push(entry)
249
+ },
250
+ })
251
+
252
+ const response = await app.request(quoteUrl(), { headers: TestApp.auth.headers })
253
+
254
+ expect(response.status).toBe(200)
255
+ expect(entries[0]?.fundingProviderAttempts).toEqual([
256
+ {
257
+ durationMs: expect.any(Number),
258
+ failure: 'network',
259
+ id: 'across',
260
+ operation: 'swap/approval',
261
+ outcome: 'failed',
262
+ },
263
+ {
264
+ durationMs: expect.any(Number),
265
+ id: 'relay',
266
+ operation: 'quote/v2',
267
+ outcome: 'available',
268
+ },
269
+ ])
270
+ expect(JSON.stringify(entries[0])).not.toContain('secret-provider')
271
+ expect(JSON.stringify(entries[0])).not.toContain('0xaa')
195
272
  })
196
273
 
197
274
  test('rejects successful provider results without output amounts', async () => {
@@ -306,6 +383,13 @@ describe('GET /funding/providers', () => {
306
383
  requiresApiKey: true,
307
384
  status: 'unconfigured',
308
385
  },
386
+ {
387
+ id: 'symbiosis',
388
+ kind: 'crossChainDex',
389
+ name: 'Symbiosis',
390
+ requiresApiKey: false,
391
+ status: 'unconfigured',
392
+ },
309
393
  ])
310
394
  })
311
395
 
@@ -5,6 +5,7 @@ import type * as App from '../../../App.js'
5
5
  import * as Auth from '../../../internal/Auth.js'
6
6
  import * as Cache from '../../../internal/Cache.js'
7
7
  import * as FundingQuotes from '../../../internal/FundingQuotes.js'
8
+ import type * as Log from '../../../internal/Log.js'
8
9
  import * as OpenApi from '../../../internal/OpenApi.js'
9
10
  import * as Response from '../../../internal/Response.js'
10
11
  import * as Schema from '../../../internal/Schema.js'
@@ -118,7 +119,7 @@ export namespace schema {
118
119
  export const ProviderRef = Schema.describe(
119
120
  z.object({
120
121
  id: z
121
- .enum(['across', 'relay', 'rhino', 'squid'])
122
+ .enum(['across', 'relay', 'rhino', 'squid', 'symbiosis'])
122
123
  .check(z.describe('Stable provider id.'), z.meta({ examples: ['relay'] })),
123
124
  kind: z
124
125
  .string()
@@ -186,7 +187,9 @@ export namespace schema {
186
187
  minimumOutputAmount: z
187
188
  .optional(Schema.DecimalString)
188
189
  .check(
189
- z.describe('Slippage-adjusted minimum destination amount in base units.'),
190
+ z.describe(
191
+ 'Provider-derived minimum destination amount in base units. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.',
192
+ ),
190
193
  z.meta({ examples: ['988000'] }),
191
194
  ),
192
195
  outputAmount: z
@@ -237,7 +240,7 @@ export namespace schema {
237
240
  export const Provider = Schema.describe(
238
241
  z.object({
239
242
  id: z
240
- .enum(['across', 'relay', 'rhino', 'squid'])
243
+ .enum(['across', 'relay', 'rhino', 'squid', 'symbiosis'])
241
244
  .check(z.describe('Stable provider id.'), z.meta({ examples: ['relay'] })),
242
245
  kind: z
243
246
  .string()
@@ -285,10 +288,10 @@ export namespace schema {
285
288
  z.meta({ examples: ['USDC.e'] }),
286
289
  ),
287
290
  provider: z
288
- .optional(z.enum(['across', 'relay', 'rhino', 'squid']))
291
+ .optional(z.enum(['across', 'relay', 'rhino', 'squid', 'symbiosis']))
289
292
  .check(
290
293
  z.describe(
291
- 'Only request a quote from this provider. Supported ids are `across`, `relay`, `rhino`, and `squid`; omit it to query every configured provider.',
294
+ 'Only request a quote from this provider. Supported ids are `across`, `relay`, `rhino`, `squid`, and `symbiosis`; omit it to query every configured provider.',
292
295
  ),
293
296
  z.meta({ examples: ['relay'] }),
294
297
  ),
@@ -416,7 +419,7 @@ export function funding(options: funding.Options = {}) {
416
419
  }),
417
420
  OpenApi.describeRoute({
418
421
  description:
419
- 'Returns live quotes for funding Tempo with a stablecoin. Omit `provider` to query all configured providers. This endpoint does not execute a transfer.',
422
+ 'Returns live quotes for funding Tempo with a stablecoin. Omit `provider` to query all configured providers. Provider minimums can use provider-specific slippage policies; Symbiosis and Squid use 1%. This endpoint does not execute a transfer.',
420
423
  operationId: 'quoteFunding',
421
424
  responses: OpenApi.responses({
422
425
  errors: {
@@ -445,6 +448,7 @@ export function funding(options: funding.Options = {}) {
445
448
  })
446
449
 
447
450
  const query = c.req.valid('query')
451
+ const attempts: FundingQuotes.ProviderAttempt[] = []
448
452
  try {
449
453
  c.header('Cache-Control', 'no-store')
450
454
  return c.json(
@@ -454,6 +458,7 @@ export function funding(options: funding.Options = {}) {
454
458
  adapterTimeoutMs: options.adapterTimeoutMs,
455
459
  cache: { bypass: cacheBypass(c.req.raw) },
456
460
  destinationToken: query.destinationToken,
461
+ onAttempt: (attempt) => attempts.push(attempt),
457
462
  provider: query.provider,
458
463
  sourceAmount: query.sourceAmount,
459
464
  sourceAmountUnits: query.sourceAmountUnits,
@@ -478,6 +483,9 @@ export function funding(options: funding.Options = {}) {
478
483
  })
479
484
  }
480
485
  throw cause
486
+ } finally {
487
+ if (attempts.length > 0)
488
+ c.set('fundingProviderAttempts', attempts.map(fundingProviderAttempt))
481
489
  }
482
490
  },
483
491
  )
@@ -557,6 +565,30 @@ export declare namespace funding {
557
565
  }
558
566
  }
559
567
 
568
+ function fundingProviderAttempt(
569
+ attempt: FundingQuotes.ProviderAttempt,
570
+ ): Log.FundingProviderAttempt {
571
+ const operation = FundingQuoteProviders.providerOperation(attempt.provider)
572
+ if (attempt.outcome !== 'failed')
573
+ return {
574
+ durationMs: attempt.durationMs,
575
+ id: attempt.provider,
576
+ operation,
577
+ outcome: attempt.outcome,
578
+ }
579
+
580
+ return {
581
+ durationMs: attempt.durationMs,
582
+ failure: FundingQuoteProviders.providerFailure({
583
+ cause: attempt.cause,
584
+ provider: attempt.provider,
585
+ }).failure,
586
+ id: attempt.provider,
587
+ operation,
588
+ outcome: attempt.outcome,
589
+ }
590
+ }
591
+
560
592
  // Maps caller-input errors to a specific 400 message; anything else rethrows.
561
593
  function requestErrorMessage(cause: unknown) {
562
594
  if (cause instanceof FundingQuotes.InvalidAmountError) return 'Invalid `sourceAmount`.'
@@ -1,5 +1,6 @@
1
1
  import { Schema } from 'tapimo'
2
- import { Hex } from 'ox'
2
+ import { FxOracle } from 'tapimo/apps'
3
+ import { Hex, Value as core_Value } from 'ox'
3
4
  import { zeroHash } from 'viem'
4
5
 
5
6
  import * as TestApp from '../../../../test/App.js'
@@ -7,6 +8,12 @@ import * as Runtime from '../../../../test/runtime.js'
7
8
  import * as Viem from '../../../../test/Viem.js'
8
9
  import * as Receipts from './receipts.js'
9
10
 
11
+ // Deterministic EUR-based rate set so conversion math is exact in assertions.
12
+ const fixed = FxOracle.from({
13
+ name: 'fixed',
14
+ rates: async () => ({ asOf: '2026-01-01', base: 'EUR', rates: { AUD: '1.6', USD: '1.0' } }),
15
+ })
16
+
10
17
  /** The decoded `type` names a humanized receipt may report. */
11
18
  const transactionTypes = ['legacy', 'eip2930', 'eip1559', 'eip4844', 'eip7702', 'tempo', 'unknown']
12
19
 
@@ -40,28 +47,44 @@ describe('GET /transactions/:transactionHash/receipt', () => {
40
47
  expect(typeof body.gasUsed).toBe('number')
41
48
  expect(Array.isArray(body.logs)).toBe(true)
42
49
  // `feeAmount` is the fee charged to the fee payer, derived as
43
- // ceil(gasUsed * effectiveGasPrice / 1e12), in the fee token base units.
44
- expect(body.feeAmount).toMatch(/^\d+$/)
50
+ // ceil(gasUsed * effectiveGasPrice / 1e12), in USD base units.
45
51
  const expectedFee =
46
52
  (BigInt(body.gasUsed) * BigInt(body.effectiveGasPrice) + 10n ** 12n - 1n) / 10n ** 12n
47
- expect(body.feeAmount).toBe(expectedFee.toString())
48
- // Fee-token metadata is opt-in: the base response must not embed it.
49
- expect(body.meta.feeToken).toBeUndefined()
53
+ expect(body.feeAmount.baseUnits).toBe(expectedFee.toString())
54
+ expect(body.feeAmount.currency).toBe('USD')
55
+ expect(body.feeAmount.decimals).toBe(6)
56
+ expect(body.feeAmount.formatted).toMatch(/^\d+(\.\d+)?$/)
57
+ // Core fee-token metadata is present by default while the raw address stays
58
+ // under `meta.rpc`. Curated fields remain opt-in.
59
+ if (body.meta.rpc.feeToken) {
60
+ expect(body.feeToken?.address).toBe(body.meta.rpc.feeToken)
61
+ expect(typeof body.feeToken?.currency).toBe('string')
62
+ expect(typeof body.feeToken?.decimals).toBe('number')
63
+ expect(typeof body.feeToken?.name).toBe('string')
64
+ expect(typeof body.feeToken?.symbol).toBe('string')
65
+ expect(body.feeToken?.logoUri).toBeUndefined()
66
+ expect(body.feeToken?.verified).toBeUndefined()
67
+ } else expect(body.feeToken).toBeUndefined()
68
+ expect(body.meta).not.toHaveProperty('feeToken')
50
69
  // The verbatim JSON-RPC receipt is always available under `meta.rpc`, where
51
70
  // `status` is the raw `0x1`/`0x0` flag and gas values stay hex-encoded.
52
71
  expect(['0x0', '0x1']).toContain(body.meta.rpc.status)
53
72
  expect(body.meta.rpc.gasUsed).toMatch(/^0x[0-9a-f]+$/)
54
73
 
55
- // include=feeToken: resolved metadata is embedded under meta (best-effort).
74
+ // Curated fee-token fields are opt-in on the top-level reference.
56
75
  const includedResponse = await client.v1.transactions[':transactionHash'].receipt.$get(
57
- { param: { transactionHash: transactionHash! }, query: { include: 'feeToken' } },
76
+ {
77
+ param: { transactionHash: transactionHash! },
78
+ query: { include: 'feeToken.logoUri,feeToken.verified' },
79
+ },
58
80
  TestApp.auth,
59
81
  )
60
82
  const includedBody = await TestApp.json(includedResponse, Receipts.schema.getReceipt.Response)
61
83
  expect(includedResponse.status).toBe(200)
62
- if (includedBody.feeToken && includedBody.meta?.feeToken) {
63
- expect(includedBody.meta.feeToken.address).toBe(includedBody.feeToken)
64
- expect(typeof includedBody.meta.feeToken.symbol).toBe('string')
84
+ if (includedBody.feeToken) {
85
+ expect(includedBody.feeToken.address).toBe(includedBody.meta.rpc.feeToken)
86
+ expect(typeof includedBody.feeToken.verified).toBe('boolean')
87
+ if (includedBody.feeToken.logoUri) expect(typeof includedBody.feeToken.logoUri).toBe('string')
65
88
  }
66
89
  })
67
90
 
@@ -90,7 +113,7 @@ describe('GET /transactions/:transactionHash/receipt', () => {
90
113
  })
91
114
 
92
115
  describe('GET /transactions/receipts', () => {
93
- test('returns a humanized receipt page, embedding fee tokens on demand', async () => {
116
+ test('returns a humanized receipt page with fee-token references', async () => {
94
117
  const client = TestApp.client()
95
118
  const response = await client.v1.transactions.receipts.$get(
96
119
  { query: { limit: '5' } },
@@ -105,6 +128,17 @@ describe('GET /transactions/receipts', () => {
105
128
  expect(body.nextCursor === null || typeof body.nextCursor === 'string').toBe(true)
106
129
  expect(body.data.length).toBeLessThanOrEqual(5)
107
130
 
131
+ if (body.nextCursor) {
132
+ const cursorResponse = await client.v1.transactions.receipts.$get(
133
+ { query: { cursor: body.nextCursor, limit: '5' } },
134
+ TestApp.auth,
135
+ )
136
+ expect(cursorResponse.status).toBe(200)
137
+ expect(cursorResponse.headers.get('cache-control')).toMatchInlineSnapshot(
138
+ `"private, max-age=60, stale-while-revalidate=300"`,
139
+ )
140
+ }
141
+
108
142
  if (body.data.length > 0) {
109
143
  const [receipt] = body.data
110
144
  // List rows reuse the detail endpoint's humanized shape: decoded
@@ -115,23 +149,35 @@ describe('GET /transactions/receipts', () => {
115
149
  expect(['success', 'reverted']).toContain(receipt!.status)
116
150
  expect(transactionTypes).toContain(receipt!.type)
117
151
  expect(typeof receipt!.gasUsed).toBe('number')
118
- expect(receipt!.feeAmount).toMatch(/^\d+$/)
152
+ expect(receipt!.feeAmount.baseUnits).toMatch(/^\d+$/)
153
+ expect(receipt!.feeAmount.currency).toBe('USD')
154
+ expect(receipt!.feeAmount.decimals).toBe(6)
155
+ expect(receipt!.feeAmount.formatted).toMatch(/^\d+(\.\d+)?$/)
119
156
  expect(receipt!.meta.rpc.status).toMatch(/^0x[0-9a-f]+$/)
120
- // Fee-token metadata is opt-in: the base response must not embed it.
121
- expect(receipt!.meta.feeToken).toBeUndefined()
157
+ if (receipt!.meta.rpc.feeToken) {
158
+ expect(receipt!.feeToken?.address).toBe(receipt!.meta.rpc.feeToken)
159
+ expect(typeof receipt!.feeToken?.currency).toBe('string')
160
+ expect(typeof receipt!.feeToken?.decimals).toBe('number')
161
+ expect(typeof receipt!.feeToken?.name).toBe('string')
162
+ expect(typeof receipt!.feeToken?.symbol).toBe('string')
163
+ expect(receipt!.feeToken?.logoUri).toBeUndefined()
164
+ expect(receipt!.feeToken?.verified).toBeUndefined()
165
+ } else expect(receipt!.feeToken).toBeUndefined()
166
+ expect(receipt!.meta).not.toHaveProperty('feeToken')
122
167
  }
123
168
 
124
169
  const includedResponse = await client.v1.transactions.receipts.$get(
125
- { query: { limit: '5', include: 'feeToken' } },
170
+ { query: { limit: '5', include: 'feeToken.logoUri,feeToken.verified' } },
126
171
  TestApp.auth,
127
172
  )
128
173
  const includedBody = await TestApp.json(includedResponse, Receipts.schema.getReceipts.Response)
129
174
  expect(includedResponse.status).toBe(200)
130
- const withFeeToken = includedBody.data.find((receipt) => receipt.meta?.feeToken)
175
+ const withFeeToken = includedBody.data.find((receipt) => receipt.feeToken)
131
176
  if (withFeeToken) {
132
- // When metadata resolves, it must describe the receipt's fee token.
133
- expect(withFeeToken.meta.feeToken!.address).toBe(withFeeToken.feeToken)
134
- expect(typeof withFeeToken.meta.feeToken!.symbol).toBe('string')
177
+ expect(withFeeToken.feeToken!.address).toBe(withFeeToken.meta.rpc.feeToken)
178
+ expect(typeof withFeeToken.feeToken!.verified).toBe('boolean')
179
+ if (withFeeToken.feeToken!.logoUri)
180
+ expect(typeof withFeeToken.feeToken!.logoUri).toBe('string')
135
181
  }
136
182
  })
137
183
 
@@ -149,10 +195,39 @@ describe('GET /transactions/receipts', () => {
149
195
  expect(body.error.code).toBe('query_invalid')
150
196
  })
151
197
 
198
+ test('values the fee in a requested denomination', async () => {
199
+ expect(transactionHash).toBeDefined()
200
+ const client = TestApp.client({ fx: { oracle: fixed } })
201
+
202
+ // Denominations are case-insensitive.
203
+ const response = await client.v1.transactions[':transactionHash'].receipt.$get(
204
+ { param: { transactionHash: transactionHash! }, query: { denomination: 'aud' } },
205
+ TestApp.auth,
206
+ )
207
+ const body = await TestApp.json(response, Receipts.schema.getReceipt.Response)
208
+
209
+ expect(response.status).toBe(200)
210
+ expect(response.headers.get('cache-control')).toMatchInlineSnapshot(
211
+ `"private, max-age=60, stale-while-revalidate=300"`,
212
+ )
213
+ // USD -> AUD at the fixed 1.6 cross rate, floored to 6 dp.
214
+ expect(body.feeAmount.valuation).toEqual({
215
+ amount: core_Value.format((BigInt(body.feeAmount.baseUnits) * 16n) / 10n, 6),
216
+ currency: 'AUD',
217
+ })
218
+ expect(body.meta.pricing).toMatchInlineSnapshot(`
219
+ {
220
+ "asOf": "2026-01-01",
221
+ "basis": "nominal",
222
+ "source": "fixed",
223
+ }
224
+ `)
225
+ })
226
+
152
227
  test('rejects the transactions-only include=receipt member', async () => {
153
228
  // `receipt` is a transactions-list embed; the receipts endpoints own their
154
- // own `include` enum (`feeToken` only) and must reject it. The typed client
155
- // would refuse the literal, so exercise the validator via `app.request`.
229
+ // own include enum and must reject it. The typed client would refuse the
230
+ // literal, so exercise the validator via `app.request`.
156
231
  const response = await TestApp.create().request('/v1/transactions/receipts?include=receipt', {
157
232
  headers: { authorization: `Bearer ${TestApp.key.token}` },
158
233
  })
@@ -161,6 +236,25 @@ describe('GET /transactions/receipts', () => {
161
236
  expect(body.error.code).toBe('query_invalid')
162
237
  })
163
238
 
239
+ test('rejects the legacy include=feeToken member', async () => {
240
+ const response = await TestApp.create().request('/v1/transactions/receipts?include=feeToken', {
241
+ headers: { authorization: `Bearer ${TestApp.key.token}` },
242
+ })
243
+ const body = await TestApp.json(response, Schema.ErrorResponse)
244
+ expect(response.status).toBe(400)
245
+ expect(body.error.code).toBe('query_invalid')
246
+ })
247
+
248
+ test('rejects generic token fields', async () => {
249
+ const response = await TestApp.create().request(
250
+ '/v1/transactions/receipts?include=token.logoUri',
251
+ { headers: { authorization: `Bearer ${TestApp.key.token}` } },
252
+ )
253
+ const body = await TestApp.json(response, Schema.ErrorResponse)
254
+ expect(response.status).toBe(400)
255
+ expect(body.error.code).toBe('query_invalid')
256
+ })
257
+
164
258
  test('narrows receipts by execution status', async () => {
165
259
  const client = TestApp.client()
166
260
  const response = await client.v1.transactions.receipts.$get(
@@ -193,7 +287,15 @@ describe('GET /transactions/receipts', () => {
193
287
  expect(Number.isInteger(body.meta?.totalCount)).toBe(true)
194
288
  expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
195
289
  expect(body.meta!.totalCount).toBeLessThanOrEqual(Schema.countCap)
196
- expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount >= Schema.countCap)
290
+ expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount! >= Schema.countCap)
291
+ // Fees are USD, so the default denomination values them at identity with
292
+ // no rate provenance.
293
+ for (const receipt of body.data)
294
+ expect(receipt.feeAmount.valuation).toEqual({
295
+ amount: receipt.feeAmount.formatted,
296
+ currency: 'USD',
297
+ })
298
+ expect(body.meta!.pricing).toBeUndefined()
197
299
 
198
300
  // Without `include=totalCount`, the same page omits `meta` and the count.
199
301
  const bare = await client.v1.transactions.receipts.$get(