tapimo 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/App.d.ts +8641 -8263
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +112 -11
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +2 -1
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/early-access.d.ts +2 -2
  14. package/dist/admin/apps/early-access.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.js +5 -5
  16. package/dist/admin/apps/early-access.js.map +1 -1
  17. package/dist/admin/apps/organizations.d.ts +124 -1
  18. package/dist/admin/apps/organizations.d.ts.map +1 -1
  19. package/dist/admin/apps/organizations.js +97 -6
  20. package/dist/admin/apps/organizations.js.map +1 -1
  21. package/dist/admin/ui.generated.d.ts +1 -1
  22. package/dist/admin/ui.generated.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.js +47 -39
  24. package/dist/admin/ui.generated.js.map +1 -1
  25. package/dist/analytics/Analytics.d.ts.map +1 -1
  26. package/dist/analytics/Analytics.js +20 -0
  27. package/dist/analytics/Analytics.js.map +1 -1
  28. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  29. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  30. package/dist/analytics/tables/requestEvents.js +36 -7
  31. package/dist/analytics/tables/requestEvents.js.map +1 -1
  32. package/dist/apps/data/App.d.ts +8050 -8019
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +2 -15
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +1 -1
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +1 -1
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +27 -27
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +21 -1
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +58 -58
  45. package/dist/apps/data/routes/blocks.d.ts +39 -39
  46. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  47. package/dist/apps/data/routes/exchanges.d.ts +669 -669
  48. package/dist/apps/data/routes/fee-amm.d.ts +157 -143
  49. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  50. package/dist/apps/data/routes/fee-amm.js +76 -91
  51. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  52. package/dist/apps/data/routes/funding.d.ts +72 -34
  53. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  54. package/dist/apps/data/routes/funding.js +34 -26
  55. package/dist/apps/data/routes/funding.js.map +1 -1
  56. package/dist/apps/data/routes/indexer.d.ts +30 -30
  57. package/dist/apps/data/routes/receipts.d.ts +8 -8
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  60. package/dist/apps/data/routes/tokenlist.js +12 -3
  61. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  62. package/dist/apps/data/routes/tokens.d.ts +4 -4
  63. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  64. package/dist/apps/data/routes/tokens.js +5 -1
  65. package/dist/apps/data/routes/tokens.js.map +1 -1
  66. package/dist/apps/data/routes/transactions.d.ts +6 -6
  67. package/dist/apps/data/routes/transfers.d.ts +101 -90
  68. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transfers.js +226 -141
  70. package/dist/apps/data/routes/transfers.js.map +1 -1
  71. package/dist/apps/data/routes/valuation.d.ts +2 -2
  72. package/dist/apps/data/routes/valuation.js +3 -3
  73. package/dist/apps/data/routes/valuation.js.map +1 -1
  74. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  75. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  76. package/dist/apps/data/routes/verified-tokens.js +34 -9
  77. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  78. package/dist/apps/data/routes/webhooks.d.ts +2 -2
  79. package/dist/apps/data/routes/webhooks.js +14 -6
  80. package/dist/apps/data/routes/webhooks.js.map +1 -1
  81. package/dist/apps/index.d.ts +1 -0
  82. package/dist/apps/index.d.ts.map +1 -1
  83. package/dist/apps/index.js +1 -0
  84. package/dist/apps/index.js.map +1 -1
  85. package/dist/apps/management/App.d.ts +460 -523
  86. package/dist/apps/management/App.d.ts.map +1 -1
  87. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  88. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  89. package/dist/apps/management/routes/api-keys.js +20 -8
  90. package/dist/apps/management/routes/api-keys.js.map +1 -1
  91. package/dist/apps/management/routes/billing.d.ts +106 -151
  92. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  93. package/dist/apps/management/routes/billing.js +28 -28
  94. package/dist/apps/management/routes/billing.js.map +1 -1
  95. package/dist/apps/management/routes/faucet.d.ts +48 -34
  96. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  97. package/dist/apps/management/routes/faucet.js +33 -18
  98. package/dist/apps/management/routes/faucet.js.map +1 -1
  99. package/dist/apps/management/routes/invitations.d.ts +131 -131
  100. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  101. package/dist/apps/management/routes/invitations.js +0 -3
  102. package/dist/apps/management/routes/invitations.js.map +1 -1
  103. package/dist/apps/management/routes/me.d.ts +6 -8
  104. package/dist/apps/management/routes/me.d.ts.map +1 -1
  105. package/dist/apps/management/routes/me.js +0 -4
  106. package/dist/apps/management/routes/me.js.map +1 -1
  107. package/dist/apps/management/routes/members.d.ts +73 -73
  108. package/dist/apps/management/routes/orgs.d.ts +94 -94
  109. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  110. package/dist/apps/management/routes/orgs.js +5 -0
  111. package/dist/apps/management/routes/orgs.js.map +1 -1
  112. package/dist/apps/management/routes/usage.d.ts +72 -72
  113. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  114. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  115. package/dist/apps/mpp/App.d.ts +363 -0
  116. package/dist/apps/mpp/App.d.ts.map +1 -0
  117. package/dist/apps/mpp/App.js +72 -0
  118. package/dist/apps/mpp/App.js.map +1 -0
  119. package/dist/apps/mpp/Mpp.d.ts +178 -0
  120. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  121. package/dist/apps/mpp/Mpp.js +183 -0
  122. package/dist/apps/mpp/Mpp.js.map +1 -0
  123. package/dist/apps/mpp/index.d.ts +3 -0
  124. package/dist/apps/mpp/index.d.ts.map +1 -0
  125. package/dist/apps/mpp/index.js +3 -0
  126. package/dist/apps/mpp/index.js.map +1 -0
  127. package/dist/apps/relay/App.d.ts.map +1 -1
  128. package/dist/apps/relay/App.js +57 -10
  129. package/dist/apps/relay/App.js.map +1 -1
  130. package/dist/apps/relay/Sponsorships.js +1 -1
  131. package/dist/apps/relay/Sponsorships.js.map +1 -1
  132. package/dist/db/Db.d.ts +2 -0
  133. package/dist/db/Db.d.ts.map +1 -1
  134. package/dist/db/Db.js +27 -0
  135. package/dist/db/Db.js.map +1 -1
  136. package/dist/db/Schema.d.ts +12 -0
  137. package/dist/db/Schema.d.ts.map +1 -1
  138. package/dist/db/Schema.js +11 -0
  139. package/dist/db/Schema.js.map +1 -1
  140. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  141. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  142. package/dist/db/tables/enabledBillingSources.js +69 -0
  143. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  144. package/dist/db/tables/invitations.d.ts +1 -12
  145. package/dist/db/tables/invitations.d.ts.map +1 -1
  146. package/dist/db/tables/invitations.js +3 -23
  147. package/dist/db/tables/invitations.js.map +1 -1
  148. package/dist/db/tables/organizations.d.ts +5 -2
  149. package/dist/db/tables/organizations.d.ts.map +1 -1
  150. package/dist/db/tables/organizations.js +32 -2
  151. package/dist/db/tables/organizations.js.map +1 -1
  152. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  153. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.js +13 -3
  155. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  156. package/dist/handlers/internal/utils.d.ts +1 -16
  157. package/dist/handlers/internal/utils.d.ts.map +1 -1
  158. package/dist/handlers/internal/utils.js +17 -6
  159. package/dist/handlers/internal/utils.js.map +1 -1
  160. package/dist/handlers/relay.d.ts.map +1 -1
  161. package/dist/handlers/relay.js +37 -5
  162. package/dist/handlers/relay.js.map +1 -1
  163. package/dist/internal/Auth.d.ts +0 -32
  164. package/dist/internal/Auth.d.ts.map +1 -1
  165. package/dist/internal/Auth.js +3 -51
  166. package/dist/internal/Auth.js.map +1 -1
  167. package/dist/internal/EdgeCache.d.ts +1 -1
  168. package/dist/internal/EdgeCache.d.ts.map +1 -1
  169. package/dist/internal/EdgeCache.js +3 -2
  170. package/dist/internal/EdgeCache.js.map +1 -1
  171. package/dist/internal/FundingQuotes.d.ts +78 -32
  172. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  173. package/dist/internal/FundingQuotes.js +45 -8
  174. package/dist/internal/FundingQuotes.js.map +1 -1
  175. package/dist/internal/Log.d.ts +18 -4
  176. package/dist/internal/Log.d.ts.map +1 -1
  177. package/dist/internal/Log.js +50 -3
  178. package/dist/internal/Log.js.map +1 -1
  179. package/dist/internal/MetricSink.d.ts.map +1 -1
  180. package/dist/internal/MetricSink.js +15 -0
  181. package/dist/internal/MetricSink.js.map +1 -1
  182. package/dist/internal/Response.d.ts +2 -2
  183. package/dist/internal/Response.d.ts.map +1 -1
  184. package/dist/internal/Response.js +12 -4
  185. package/dist/internal/Response.js.map +1 -1
  186. package/dist/internal/Schema.d.ts +2 -2
  187. package/dist/internal/Schema.d.ts.map +1 -1
  188. package/dist/internal/Schema.js +4 -2
  189. package/dist/internal/Schema.js.map +1 -1
  190. package/dist/internal/Viem.d.ts +1 -1
  191. package/package.json +7 -2
  192. package/src/App.test.ts +9 -4
  193. package/src/App.ts +14 -2
  194. package/src/Client.test-d.ts +109 -28
  195. package/src/Scope.ts +5 -0
  196. package/src/admin/App.test.ts +4 -0
  197. package/src/admin/apps/early-access.ts +6 -5
  198. package/src/admin/apps/organizations.test.ts +86 -2
  199. package/src/admin/apps/organizations.ts +155 -31
  200. package/src/admin/apps/scopes.test.ts +5 -0
  201. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  202. package/src/admin/ui/src/lib/organizations.ts +92 -0
  203. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  204. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  205. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  206. package/src/admin/ui.generated.ts +49 -39
  207. package/src/analytics/Analytics.test.ts +83 -0
  208. package/src/analytics/Analytics.ts +20 -0
  209. package/src/analytics/tables/requestEvents.ts +60 -7
  210. package/src/apps/composition.test.ts +11 -2
  211. package/src/apps/data/App.test.ts +14 -2
  212. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  213. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  214. package/src/apps/data/FxOracle.test.ts +2 -2
  215. package/src/apps/data/FxOracle.ts +2 -2
  216. package/src/apps/data/routes/activities.test.ts +6 -2
  217. package/src/apps/data/routes/activities.ts +39 -1
  218. package/src/apps/data/routes/balances.test.ts +6 -2
  219. package/src/apps/data/routes/exchanges.test.ts +7 -3
  220. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  221. package/src/apps/data/routes/fee-amm.ts +98 -119
  222. package/src/apps/data/routes/funding.test.ts +29 -8
  223. package/src/apps/data/routes/funding.ts +42 -48
  224. package/src/apps/data/routes/receipts.test.ts +6 -2
  225. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  226. package/src/apps/data/routes/tokenlist.ts +18 -5
  227. package/src/apps/data/routes/tokens.ts +5 -1
  228. package/src/apps/data/routes/transfers.test.ts +203 -35
  229. package/src/apps/data/routes/transfers.ts +294 -172
  230. package/src/apps/data/routes/valuation.test.ts +18 -3
  231. package/src/apps/data/routes/valuation.ts +4 -4
  232. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  233. package/src/apps/data/routes/verified-tokens.ts +52 -15
  234. package/src/apps/data/routes/webhooks.test.ts +56 -0
  235. package/src/apps/data/routes/webhooks.ts +17 -8
  236. package/src/apps/index.ts +1 -0
  237. package/src/apps/management/routes/api-keys.test.ts +2 -75
  238. package/src/apps/management/routes/api-keys.ts +30 -7
  239. package/src/apps/management/routes/billing.test.ts +106 -85
  240. package/src/apps/management/routes/billing.ts +32 -21
  241. package/src/apps/management/routes/faucet.test.ts +11 -3
  242. package/src/apps/management/routes/faucet.ts +46 -30
  243. package/src/apps/management/routes/invitations.test.ts +0 -172
  244. package/src/apps/management/routes/invitations.ts +0 -3
  245. package/src/apps/management/routes/me.test.ts +3 -145
  246. package/src/apps/management/routes/me.ts +0 -6
  247. package/src/apps/management/routes/orgs.test.ts +45 -2
  248. package/src/apps/management/routes/orgs.ts +6 -0
  249. package/src/apps/management/routes/scopes.test.ts +1 -0
  250. package/src/apps/mpp/App.ts +86 -0
  251. package/src/apps/mpp/Mpp.test.ts +193 -0
  252. package/src/apps/mpp/Mpp.ts +353 -0
  253. package/src/apps/mpp/index.ts +2 -0
  254. package/src/apps/mpp/internal/Adapter.ts +11 -0
  255. package/src/apps/mpp/internal/Error.ts +9 -0
  256. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  257. package/src/apps/mpp/internal/Screening.ts +10 -0
  258. package/src/apps/relay/App.test.ts +90 -8
  259. package/src/apps/relay/App.ts +62 -10
  260. package/src/apps/relay/Sponsorships.test.ts +2 -0
  261. package/src/apps/relay/Sponsorships.ts +1 -1
  262. package/src/db/Db.test.ts +2 -2
  263. package/src/db/Db.ts +30 -0
  264. package/src/db/Schema.ts +15 -0
  265. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  266. package/src/db/tables/enabledBillingSources.ts +118 -0
  267. package/src/db/tables/invitations.ts +4 -26
  268. package/src/db/tables/organizations.test.ts +191 -0
  269. package/src/db/tables/organizations.ts +44 -3
  270. package/src/db/tables/sponsoredTransactions.test.ts +41 -2
  271. package/src/db/tables/sponsoredTransactions.ts +15 -3
  272. package/src/handlers/internal/utils.ts +22 -6
  273. package/src/handlers/relay.errors.test.ts +153 -0
  274. package/src/handlers/relay.ts +42 -7
  275. package/src/internal/Auth.test.ts +3 -3
  276. package/src/internal/Auth.ts +3 -63
  277. package/src/internal/EdgeCache.test.ts +18 -5
  278. package/src/internal/EdgeCache.ts +4 -2
  279. package/src/internal/FundingQuotes.test.ts +18 -5
  280. package/src/internal/FundingQuotes.ts +46 -11
  281. package/src/internal/Log.test.ts +91 -0
  282. package/src/internal/Log.ts +69 -5
  283. package/src/internal/MetricSink.test.ts +55 -0
  284. package/src/internal/MetricSink.ts +14 -0
  285. package/src/internal/Response.test.ts +28 -0
  286. package/src/internal/Response.ts +12 -4
  287. package/src/internal/Schema.test.ts +13 -0
  288. package/src/internal/Schema.ts +14 -7
@@ -9,6 +9,7 @@ import type * as Log from '../../../internal/Log.js'
9
9
  import * as OpenApi from '../../../internal/OpenApi.js'
10
10
  import * as Response from '../../../internal/Response.js'
11
11
  import * as Schema from '../../../internal/Schema.js'
12
+ import * as Value from '../../../internal/Value.js'
12
13
  import * as FundingQuoteProviders from '../FundingQuoteProviders.js'
13
14
 
14
15
  /** Zod schemas owned by funding quote resources. */
@@ -58,6 +59,12 @@ export namespace schema {
58
59
  z.describe('Contract address, mint, or issuer address on the token chain.'),
59
60
  z.meta({ examples: ['0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }),
60
61
  ),
62
+ currency: z
63
+ .string()
64
+ .check(
65
+ z.describe('Monetary denomination represented by this token.'),
66
+ z.meta({ examples: ['USD'] }),
67
+ ),
61
68
  decimals: z
62
69
  .number()
63
70
  .check(
@@ -97,22 +104,10 @@ export namespace schema {
97
104
  'A normalized token reference for funding quotes.',
98
105
  )
99
106
 
100
- /** Source amount echoed by a funding quote response. */
107
+ /** Self-contained source or destination amount in a funding quote. */
101
108
  export const TokenAmount = Schema.describe(
102
- z.object({
103
- amount: Schema.DecimalString.check(
104
- z.describe("The requested source amount in the token's smallest unit."),
105
- ),
106
- formatted: z
107
- .string()
108
- .check(
109
- z.describe(
110
- 'The same amount in human-readable decimal form, using the source token’s `decimals`.',
111
- ),
112
- z.meta({ examples: ['1'] }),
113
- ),
114
- }),
115
- 'The requested source amount in provider-safe base units and display form.',
109
+ Schema.TokenAmount,
110
+ 'A funding amount in base units and human-readable form.',
116
111
  )
117
112
 
118
113
  /** Provider summary embedded in a funding quote. */
@@ -171,7 +166,7 @@ export namespace schema {
171
166
  'Machine-readable liquidity, fee, and source signals for a successful quote.',
172
167
  )
173
168
 
174
- /** Provider-derived output, limits, and freshness data. */
169
+ /** Provider quote freshness data. */
175
170
  export const QuoteDetails = Schema.describe(
176
171
  z.object({
177
172
  expiresAt: z
@@ -180,31 +175,6 @@ export namespace schema {
180
175
  z.describe('Timestamp after which the provider quote is no longer valid.'),
181
176
  z.meta({ examples: ['2026-01-01T00:01:00Z'] }),
182
177
  ),
183
- inputAmount: Schema.DecimalString.check(
184
- z.describe('Source amount sent to the provider in base units.'),
185
- z.meta({ examples: ['1000000'] }),
186
- ),
187
- minimumOutputAmount: z
188
- .optional(Schema.DecimalString)
189
- .check(
190
- z.describe(
191
- 'Provider-derived minimum destination amount in base units. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.',
192
- ),
193
- z.meta({ examples: ['988000'] }),
194
- ),
195
- outputAmount: z
196
- .string()
197
- .check(z.regex(/^\d+$/))
198
- .check(
199
- z.describe('Expected destination amount in base units.'),
200
- z.meta({ examples: ['989102'] }),
201
- ),
202
- outputAmountFormatted: z
203
- .string()
204
- .check(
205
- z.describe('Expected destination amount rendered with destination token decimals.'),
206
- z.meta({ examples: ['0.989102'] }),
207
- ),
208
178
  sampledAt: z.iso
209
179
  .datetime()
210
180
  .check(
@@ -212,12 +182,20 @@ export namespace schema {
212
182
  z.meta({ examples: ['2026-01-01T00:00:00Z'] }),
213
183
  ),
214
184
  }),
215
- 'Provider-derived output, limits, and freshness data for a funding quote.',
185
+ 'Freshness data for a provider funding quote.',
216
186
  )
217
187
 
218
188
  /** Successful live funding quote. */
219
189
  export const FundingQuote = Schema.describe(
220
190
  z.object({
191
+ destinationAmount: TokenAmount.check(z.describe('Expected destination amount.')),
192
+ destinationAmountMin: z
193
+ .optional(TokenAmount)
194
+ .check(
195
+ z.describe(
196
+ 'Provider-derived minimum destination amount. Symbiosis and Squid quotes use a fixed 1% slippage tolerance.',
197
+ ),
198
+ ),
221
199
  destinationChain: Chain.check(z.describe('Destination chain, always Tempo mainnet in V1.')),
222
200
  destinationToken: Token.check(z.describe('Tempo token expected from this quote.')),
223
201
  id: z
@@ -377,7 +355,20 @@ const fundingQuoteResponseExample = (() => {
377
355
  return {
378
356
  data: [
379
357
  {
380
- ...candidate,
358
+ destinationAmount: Value.tokenAmount({
359
+ baseUnits: '979295',
360
+ currency: candidate.destinationToken.currency,
361
+ decimals: candidate.destinationToken.decimals,
362
+ }),
363
+ destinationAmountMin: Value.tokenAmount({
364
+ baseUnits: '939242',
365
+ currency: candidate.destinationToken.currency,
366
+ decimals: candidate.destinationToken.decimals,
367
+ }),
368
+ destinationChain: candidate.destinationChain,
369
+ destinationToken: candidate.destinationToken,
370
+ id: candidate.id,
371
+ provider: candidate.provider,
381
372
  quality: {
382
373
  estimatedSeconds: 1,
383
374
  liquiditySource: 'providerQuote',
@@ -385,12 +376,15 @@ const fundingQuoteResponseExample = (() => {
385
376
  tier: 'liquid',
386
377
  },
387
378
  quote: {
388
- inputAmount: '1000000',
389
- minimumOutputAmount: '939242',
390
- outputAmount: '979295',
391
- outputAmountFormatted: '0.979295',
392
379
  sampledAt: '2026-01-01T00:00:00.000Z',
393
380
  },
381
+ sourceAmount: Value.tokenAmount({
382
+ baseUnits: candidate.sourceAmount.amount,
383
+ currency: candidate.sourceToken.currency,
384
+ decimals: candidate.sourceToken.decimals,
385
+ }),
386
+ sourceChain: candidate.sourceChain,
387
+ sourceToken: candidate.sourceToken,
394
388
  },
395
389
  ],
396
390
  }
@@ -508,7 +502,7 @@ export function funding(options: funding.Options = {}) {
508
502
  }),
509
503
  Cache.response<App.Environment>({
510
504
  cacheControl: Cache.policies.stable,
511
- name: 'tempo-api:funding-chains:v1',
505
+ name: 'tempo-api:funding-chains:v2',
512
506
  key: (c) => c.req.url,
513
507
  }),
514
508
  (c) => {
@@ -11,7 +11,11 @@ import * as Receipts from './receipts.js'
11
11
  // Deterministic EUR-based rate set so conversion math is exact in assertions.
12
12
  const fixed = FxOracle.from({
13
13
  name: 'fixed',
14
- rates: async () => ({ asOf: '2026-01-01', base: 'EUR', rates: { AUD: '1.6', USD: '1.0' } }),
14
+ rates: async () => ({
15
+ asOf: '2026-01-01T00:00:00.000Z',
16
+ base: 'EUR',
17
+ rates: { AUD: '1.6', USD: '1.0' },
18
+ }),
15
19
  })
16
20
 
17
21
  /** The decoded `type` names a humanized receipt may report. */
@@ -217,7 +221,7 @@ describe('GET /transactions/receipts', () => {
217
221
  })
218
222
  expect(body.meta.pricing).toMatchInlineSnapshot(`
219
223
  {
220
- "asOf": "2026-01-01",
224
+ "asOf": "2026-01-01T00:00:00.000Z",
221
225
  "basis": "nominal",
222
226
  "source": "fixed",
223
227
  }
@@ -75,4 +75,19 @@ describe('GET /tokenlist', () => {
75
75
  expect(body.tokens).toEqual([])
76
76
  expect(body.version.patch).toBe(0)
77
77
  })
78
+
79
+ test('refreshes cached reads after a snapshot publish', async () => {
80
+ const db = TestApp.database()
81
+ await VerifiedTokens.replace(db, chainId, seedList)
82
+ const client = TestApp.client({ auth: { keys: [reader] }, db })
83
+
84
+ await client.v1.tokenlist.$get({ query: {} }, as(reader))
85
+ await VerifiedTokens.replace(db, chainId, seedList.slice(1))
86
+
87
+ const response = await client.v1.tokenlist.$get({ query: {} }, as(reader))
88
+ const body = await TestApp.json(response, Tokenlist.schema.getTokenList.Response)
89
+ expect(body.tokens.map((token) => token.address)).toEqual(
90
+ seedList.slice(1).map((token) => token.address),
91
+ )
92
+ })
78
93
  })
@@ -4,6 +4,7 @@ import * as z from 'zod/mini'
4
4
  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
+ import * as EdgeCache from '../../../internal/EdgeCache.js'
7
8
  import * as OpenApi from '../../../internal/OpenApi.js'
8
9
  import * as Response from '../../../internal/Response.js'
9
10
  import * as Schema from '../../../internal/Schema.js'
@@ -159,11 +160,23 @@ export function tokenlist() {
159
160
  summary: 'Get token list',
160
161
  tags: ['Verified Tokens'],
161
162
  }),
162
- Cache.response<App.Environment>({
163
- cacheControl: Cache.policies.stable,
164
- name: 'tempo-api:tokenlist:v1',
165
- key: (c) => Cache.urlKey(c, schema.getTokenList.Query),
166
- }),
163
+ // The shared edge cache is URL-keyed and cannot include the snapshot version.
164
+ EdgeCache.setEligibility(
165
+ Cache.response<App.Environment>({
166
+ cacheControl: Cache.policies.stable,
167
+ name: 'tempo-api:tokenlist:v1',
168
+ key: async (c) => {
169
+ const base = Cache.urlKey(c, schema.getTokenList.Query)
170
+ const query = schema.getTokenList.Query.parse(
171
+ Object.fromEntries(new URL(c.req.url).searchParams),
172
+ )
173
+ const chainId = query.chainId ?? c.get('chainId')
174
+ const { version } = await VerifiedTokens.snapshot(c, chainId)
175
+ return `${base}:v:${version}`
176
+ },
177
+ }),
178
+ () => false,
179
+ ),
167
180
  async (c) => {
168
181
  if (Auth.narrowAccess) return Auth.paidAccessError(c)
169
182
  if (OpenApi.narrowValidation)
@@ -1214,7 +1214,7 @@ declare namespace getTokenMetadata {
1214
1214
 
1215
1215
  /**
1216
1216
  * Resolves a token's curated R2 icon URI, memoized per `(chainId, address)`
1217
- * so page enrichers (balances, holders, transfers `include=token`) don't
1217
+ * so page enrichers (balances, holders, transfers `include=token.logoUri`) don't
1218
1218
  * re-fetch the asset per row on every request. Misses are memoized as `null`
1219
1219
  * too — most tokens have no curated icon, and the negative lookup is exactly
1220
1220
  * what page enrichment would otherwise repeat N times per page.
@@ -1821,6 +1821,8 @@ export async function getTokenCreatedAtByAddress(
1821
1821
  try {
1822
1822
  const result = await tidx.fetch({
1823
1823
  chainId: options.chainId,
1824
+ // TODO: Remove the explicit engine after TIDX 1.0 is released.
1825
+ engine: 'clickhouse',
1824
1826
  query: `
1825
1827
  SELECT token, block_timestamp
1826
1828
  FROM tokencreated
@@ -1916,6 +1918,8 @@ async function getTokenCreatedByAddress(
1916
1918
  // needs no event signature for the `logs` table.
1917
1919
  const result = await tidx.fetch({
1918
1920
  chainId: options.chainId,
1921
+ // TODO: Remove the explicit engine after TIDX 1.0 is released.
1922
+ engine: 'clickhouse',
1919
1923
  query: `
1920
1924
  SELECT topic1, data, block_timestamp
1921
1925
  FROM logs
@@ -10,10 +10,28 @@ import * as Transfers from './transfers.js'
10
10
  // Deterministic EUR-based rate set so conversion math is exact in assertions.
11
11
  const fixed = FxOracle.from({
12
12
  name: 'fixed',
13
- rates: async () => ({ asOf: '2026-01-01', base: 'EUR', rates: { AUD: '1.6', USD: '1.0' } }),
13
+ rates: async () => ({
14
+ asOf: '2026-01-01T00:00:00.000Z',
15
+ base: 'EUR',
16
+ rates: { AUD: '1.6', USD: '1.0' },
17
+ }),
14
18
  })
15
19
 
16
20
  describe('GET /transfers', () => {
21
+ test('parses granular token includes', () => {
22
+ expect(
23
+ Transfers.schema.getTransfers.Query.parse({
24
+ include: 'token.logoUri,token.verified',
25
+ }).include,
26
+ ).toMatchInlineSnapshot(`
27
+ [
28
+ "token.logoUri",
29
+ "token.verified",
30
+ ]
31
+ `)
32
+ expect(Transfers.schema.getTransfers.Query.safeParse({ include: 'token' }).success).toBe(false)
33
+ })
34
+
17
35
  test('degrades unavailable verification data to null valuations', async () => {
18
36
  const db = () =>
19
37
  Db.postgres({
@@ -29,7 +47,7 @@ describe('GET /transfers', () => {
29
47
 
30
48
  expect(response.status).toBe(200)
31
49
  expect(body.data.length).toBeGreaterThan(0)
32
- for (const entry of body.data) expect(entry.sourceToken.valuation).toBeNull()
50
+ for (const entry of body.data) expect(entry.sourceAmount.valuation).toBeNull()
33
51
 
34
52
  const emptyResponse = await client.v1.transfers.$get(
35
53
  { query: { limit: '5', token: `0x${'11'.repeat(20)}` } },
@@ -57,7 +75,7 @@ describe('GET /transfers', () => {
57
75
  expect(unsupported.error.code).toBe('query_invalid')
58
76
  })
59
77
 
60
- test('returns an unscoped transfer page, embedding tokens on demand', async () => {
78
+ test('returns an unscoped transfer page with token metadata', async () => {
61
79
  const client = TestApp.client()
62
80
  const baseResponse = await client.v1.transfers.$get({ query: { limit: '5' } }, TestApp.auth)
63
81
  const base = await TestApp.json(baseResponse, Transfers.schema.getTransfers.Response)
@@ -74,7 +92,14 @@ describe('GET /transfers', () => {
74
92
  base.data.every(
75
93
  (entry) =>
76
94
  /^0x[0-9a-f]{40}$/.test(entry.sourceToken.address) &&
77
- /^\d+$/.test(entry.sourceToken.amount) &&
95
+ /^\d+$/.test(entry.sourceAmount.baseUnits) &&
96
+ typeof entry.sourceAmount.currency === 'string' &&
97
+ Number.isInteger(entry.sourceAmount.decimals) &&
98
+ typeof entry.sourceAmount.formatted === 'string' &&
99
+ typeof entry.sourceToken.currency === 'string' &&
100
+ Number.isInteger(entry.sourceToken.decimals) &&
101
+ typeof entry.sourceToken.name === 'string' &&
102
+ typeof entry.sourceToken.symbol === 'string' &&
78
103
  Number.isInteger(entry.blockNumber) &&
79
104
  /^0x[0-9a-f]{40}$/.test(entry.sender) &&
80
105
  /^0x[0-9a-f]{40}$/.test(entry.recipient) &&
@@ -85,28 +110,35 @@ describe('GET /transfers', () => {
85
110
  // Transfers default to newest-first (descending block number).
86
111
  const blocks = base.data.map((entry) => entry.blockNumber)
87
112
  expect(blocks.every((block, index) => index === 0 || blocks[index - 1]! >= block)).toBe(true)
88
- // Token metadata is opt-in: the base page's `sourceToken` carries only
89
- // `address`/`amount`, with no metadata fields spread in.
90
- expect(base.data.every((entry) => entry.sourceToken.symbol === undefined)).toBe(true)
113
+ // Quantities live beside the token reference; curated fields remain opt-in.
114
+ expect(
115
+ base.data.every(
116
+ (entry) =>
117
+ !('amount' in entry.sourceToken) &&
118
+ !('valuation' in entry.sourceToken) &&
119
+ entry.sourceToken.logoUri === undefined &&
120
+ entry.sourceToken.verified === undefined,
121
+ ),
122
+ ).toBe(true)
91
123
 
92
124
  const includedResponse = await client.v1.transfers.$get(
93
- { query: { limit: '5', include: 'token' } },
125
+ { query: { limit: '5', include: 'token.logoUri,token.verified' } },
94
126
  TestApp.auth,
95
127
  )
96
128
  const included = await TestApp.json(includedResponse, Transfers.schema.getTransfers.Response)
97
129
 
98
130
  expect(includedResponse.status).toBe(200)
99
- // Each `sourceToken` keeps its `address`/`amount`; when metadata resolves
100
- // it is spread alongside. Resolution is best-effort, so a row whose
101
- // metadata is unavailable simply omits the metadata fields.
131
+ // Curated fields remain optional without moving quantities back into the token.
102
132
  expect(
103
133
  included.data.every(
104
134
  (entry) =>
105
135
  /^0x[0-9a-f]{40}$/.test(entry.sourceToken.address) &&
106
- /^\d+$/.test(entry.sourceToken.amount) &&
107
- (entry.sourceToken.symbol === undefined ||
108
- (typeof entry.sourceToken.symbol === 'string' &&
109
- Number.isInteger(entry.sourceToken.decimals))),
136
+ /^\d+$/.test(entry.sourceAmount.baseUnits) &&
137
+ !('amount' in entry.sourceToken) &&
138
+ !('valuation' in entry.sourceToken) &&
139
+ typeof entry.sourceToken.symbol === 'string' &&
140
+ Number.isInteger(entry.sourceToken.decimals) &&
141
+ typeof entry.sourceToken.verified === 'boolean',
110
142
  ),
111
143
  ).toBe(true)
112
144
  })
@@ -125,24 +157,156 @@ describe('GET /transfers', () => {
125
157
 
126
158
  test('narrows transfers by address (either side)', async () => {
127
159
  const client = TestApp.client()
128
- // Seed a real account from a recent transfer.
129
- const seedResponse = await client.v1.transfers.$get({ query: { limit: '5' } }, TestApp.auth)
130
- const seed = await TestApp.json(seedResponse, Transfers.schema.getTransfers.Response)
131
- expect(seedResponse.status).toBe(200)
132
- const account = seed.data[0]!.sender
133
- expect(account).toMatch(/^0x[0-9a-f]{40}$/)
134
-
135
- const filteredResponse = await client.v1.transfers.$get(
136
- { query: { limit: '5', address: account } },
160
+ const runtime = Runtime.get()
161
+ const address = Schema.Address.parse(
162
+ runtime.mode === 'testnet'
163
+ ? '0xb1f382bd6deed37895528a6387bae4cd9569ac3c'
164
+ : runtime.fixtures!.fundedAccounts[0]!,
165
+ )
166
+ const response = await client.v1.transfers.$get(
167
+ { query: { address, include: 'totalCount', limit: '10' } },
137
168
  TestApp.auth,
138
169
  )
139
- const filtered = await TestApp.json(filteredResponse, Transfers.schema.getTransfers.Response)
140
- expect(filteredResponse.status).toBe(200)
170
+ const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
171
+
172
+ expect(response.status).toBe(200)
141
173
  expect(
142
- filtered.data.every((entry) => entry.sender === account || entry.recipient === account),
174
+ body.data.every((entry) => entry.sender === address || entry.recipient === address),
143
175
  ).toBe(true)
176
+ expect(body.meta!.totalCount).toBeGreaterThanOrEqual(body.data.length)
177
+ expect(body.meta!.totalCountCapped).toBe(body.meta!.totalCount! >= Schema.countCap)
144
178
  })
145
179
 
180
+ test.runIf(Runtime.get().mode === 'testnet')(
181
+ 'merges both address sides before paginating',
182
+ async () => {
183
+ const client = TestApp.client()
184
+ const address = '0xabb0023644c54a20d0fe92becfa764dacaf59ba9'
185
+ const toBlock = '27608730'
186
+ const [senderResponse, recipientResponse, addressResponse] = await Promise.all([
187
+ client.v1.transfers.$get(
188
+ {
189
+ query: { 'blockNumber.to': toBlock, limit: '10', order: 'desc', sender: address },
190
+ },
191
+ TestApp.auth,
192
+ ),
193
+ client.v1.transfers.$get(
194
+ {
195
+ query: { 'blockNumber.to': toBlock, limit: '10', order: 'desc', recipient: address },
196
+ },
197
+ TestApp.auth,
198
+ ),
199
+ client.v1.transfers.$get(
200
+ {
201
+ query: { address, 'blockNumber.to': toBlock, limit: '10', order: 'desc' },
202
+ },
203
+ TestApp.auth,
204
+ ),
205
+ ])
206
+ const sender = await TestApp.json(senderResponse, Transfers.schema.getTransfers.Response)
207
+ const recipient = await TestApp.json(
208
+ recipientResponse,
209
+ Transfers.schema.getTransfers.Response,
210
+ )
211
+ const combined = await TestApp.json(addressResponse, Transfers.schema.getTransfers.Response)
212
+
213
+ expect([senderResponse.status, recipientResponse.status, addressResponse.status]).toEqual([
214
+ 200, 200, 200,
215
+ ])
216
+ const seen = new Set<string>()
217
+ const expected = [...sender.data, ...recipient.data]
218
+ .sort((a, b) => b.blockNumber - a.blockNumber || b.logIndex - a.logIndex)
219
+ .filter((entry) => {
220
+ if (seen.has(entry.id)) return false
221
+ seen.add(entry.id)
222
+ return true
223
+ })
224
+ .slice(0, 10)
225
+ expect(expected).toHaveLength(10)
226
+ expect(combined.data.map(({ id }) => id)).toEqual(expected.map(({ id }) => id))
227
+
228
+ const firstResponse = await client.v1.transfers.$get(
229
+ {
230
+ query: { address, 'blockNumber.to': toBlock, limit: '5', order: 'desc' },
231
+ },
232
+ TestApp.auth,
233
+ )
234
+ const first = await TestApp.json(firstResponse, Transfers.schema.getTransfers.Response)
235
+ expect(firstResponse.status).toBe(200)
236
+ expect(first.nextCursor).not.toBeNull()
237
+ if (!first.nextCursor) return
238
+
239
+ const [cursorResponse, positionalResponse] = await Promise.all([
240
+ client.v1.transfers.$get(
241
+ {
242
+ query: {
243
+ address,
244
+ 'blockNumber.to': toBlock,
245
+ cursor: first.nextCursor,
246
+ limit: '5',
247
+ order: 'desc',
248
+ },
249
+ },
250
+ TestApp.auth,
251
+ ),
252
+ client.v1.transfers.$get(
253
+ {
254
+ query: {
255
+ address,
256
+ 'blockNumber.to': toBlock,
257
+ limit: '5',
258
+ order: 'desc',
259
+ page: '2',
260
+ },
261
+ },
262
+ TestApp.auth,
263
+ ),
264
+ ])
265
+ const cursor = await TestApp.json(cursorResponse, Transfers.schema.getTransfers.Response)
266
+ const positional = await TestApp.json(
267
+ positionalResponse,
268
+ Transfers.schema.getTransfers.Response,
269
+ )
270
+ const expectedSecondPage = expected.slice(5, 10).map(({ id }) => id)
271
+
272
+ expect([cursorResponse.status, positionalResponse.status]).toEqual([200, 200])
273
+ expect([...first.data, ...cursor.data].map(({ id }) => id)).toEqual(
274
+ expected.map(({ id }) => id),
275
+ )
276
+ expect(positional.data.map(({ id }) => id)).toEqual(expectedSecondPage)
277
+ },
278
+ )
279
+
280
+ test.runIf(Runtime.get().mode === 'testnet')(
281
+ 'deduplicates self-transfers in address pages and counts',
282
+ async () => {
283
+ const client = TestApp.client()
284
+ const address = '0xabb0023644c54a20d0fe92becfa764dacaf59ba9'
285
+ const transactionHash = '0x915a11bcf4b5e667c631f9fb09ac8729f3b436f24eb0b552e54ec13a490ef57f'
286
+ const response = await client.v1.transfers.$get(
287
+ {
288
+ query: {
289
+ address,
290
+ 'blockNumber.from': '27608730',
291
+ 'blockNumber.to': '27608730',
292
+ include: 'totalCount',
293
+ limit: '50',
294
+ token: '0x20c0000000000000000000000000000000000003',
295
+ },
296
+ },
297
+ TestApp.auth,
298
+ )
299
+ const body = await TestApp.json(response, Transfers.schema.getTransfers.Response)
300
+
301
+ expect(response.status).toBe(200)
302
+ expect(body.data.filter(({ id }) => id === `${transactionHash}-233`)).toHaveLength(1)
303
+ expect(body.meta).toMatchObject({
304
+ totalCount: body.data.length,
305
+ totalCountCapped: false,
306
+ })
307
+ },
308
+ )
309
+
146
310
  test('narrows transfers by sender and recipient independently', async () => {
147
311
  const client = TestApp.client()
148
312
  const seedResponse = await client.v1.transfers.$get({ query: { limit: '5' } }, TestApp.auth)
@@ -208,7 +372,7 @@ describe('GET /transfers', () => {
208
372
  // `(txHash, blockNumber)` shortcut is not unique here.
209
373
  type Entry = z.output<typeof Transfers.schema.getTransfers.Transfer>
210
374
  const keyOf = (entry: Entry) =>
211
- `${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.sourceToken.amount}`
375
+ `${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.sourceAmount.baseUnits}`
212
376
  const firstKeys = new Set(first.data.map(keyOf))
213
377
  expect(second.data.every((entry) => !firstKeys.has(keyOf(entry)))).toBe(true)
214
378
  })
@@ -256,7 +420,7 @@ describe('GET /transfers', () => {
256
420
 
257
421
  type Entry = z.output<typeof Transfers.schema.getTransfers.Transfer>
258
422
  const keyOf = (entry: Entry) =>
259
- `${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.sourceToken.amount}`
423
+ `${entry.blockNumber}:${entry.transactionHash}:${entry.sourceToken.address}:${entry.sender}:${entry.recipient}:${entry.sourceAmount.baseUnits}`
260
424
  expect(page.data.map(keyOf)).toEqual(head.data.slice(5, 10).map(keyOf))
261
425
  // Offset pages still return a keyset cursor so clients can switch to deep
262
426
  // cursor traversal from a positional jump.
@@ -408,6 +572,10 @@ describe('GET /transfers', () => {
408
572
  expect(transfer.recipient).toBe(crossToken.recipient)
409
573
  expect(transfer.sourceToken.address).toBe(crossToken.sourceToken)
410
574
  expect(transfer.destinationToken?.address).toBe(crossToken.destinationToken)
575
+ expect(transfer.sourceAmount.baseUnits).toMatch(/^\d+$/)
576
+ expect(transfer.destinationAmount?.baseUnits).toMatch(/^\d+$/)
577
+ expect('amount' in transfer.sourceToken).toBe(false)
578
+ expect('amount' in transfer.destinationToken!).toBe(false)
411
579
 
412
580
  // Without `include=crossToken`, the transaction stays as its raw per-log
413
581
  // legs (more than one row) and no row carries `destinationToken`.
@@ -494,8 +662,8 @@ describe('GET /transfers', () => {
494
662
  expect(response.status).toBe(200)
495
663
  expect(body.data.length).toBeGreaterThan(0)
496
664
  for (const entry of body.data)
497
- expect(entry.sourceToken.valuation).toEqual({
498
- amount: core_Value.format(BigInt(entry.sourceToken.amount), 6),
665
+ expect(entry.sourceAmount.valuation).toEqual({
666
+ amount: core_Value.format(BigInt(entry.sourceAmount.baseUnits), 6),
499
667
  currency: 'USD',
500
668
  })
501
669
  expect(body.meta?.pricing).toBeUndefined()
@@ -517,13 +685,13 @@ describe('GET /transfers', () => {
517
685
  expect(body.data.length).toBeGreaterThan(0)
518
686
  // USD -> AUD at the fixed 1.6 cross rate, floored to 6 dp.
519
687
  for (const entry of body.data)
520
- expect(entry.sourceToken.valuation).toEqual({
521
- amount: core_Value.format((BigInt(entry.sourceToken.amount) * 16n) / 10n, 6),
688
+ expect(entry.sourceAmount.valuation).toEqual({
689
+ amount: core_Value.format((BigInt(entry.sourceAmount.baseUnits) * 16n) / 10n, 6),
522
690
  currency: 'AUD',
523
691
  })
524
692
  expect(body.meta?.pricing).toMatchInlineSnapshot(`
525
693
  {
526
- "asOf": "2026-01-01",
694
+ "asOf": "2026-01-01T00:00:00.000Z",
527
695
  "basis": "nominal",
528
696
  "source": "fixed",
529
697
  }
@@ -560,7 +728,7 @@ describe('GET /transfers', () => {
560
728
 
561
729
  expect(response.status).toBe(200)
562
730
  expect(body.data.length).toBeGreaterThan(0)
563
- for (const entry of body.data) expect(entry.sourceToken.valuation).toBeNull()
731
+ for (const entry of body.data) expect(entry.sourceAmount.valuation).toBeNull()
564
732
  },
565
733
  )
566
734