tapimo 0.5.2 → 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 (299) 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 +643 -118
  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 +963 -342
  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/tokens.d.ts +166 -173
  64. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  65. package/dist/apps/data/routes/tokens.js +14 -12
  66. package/dist/apps/data/routes/tokens.js.map +1 -1
  67. package/dist/apps/data/routes/transactions.d.ts +277 -536
  68. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transactions.js +96 -77
  70. package/dist/apps/data/routes/transactions.js.map +1 -1
  71. package/dist/apps/data/routes/transfers.d.ts +82 -17
  72. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  73. package/dist/apps/data/routes/transfers.js +70 -13
  74. package/dist/apps/data/routes/transfers.js.map +1 -1
  75. package/dist/apps/data/routes/valuation.d.ts +307 -0
  76. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  77. package/dist/apps/data/routes/valuation.js +292 -0
  78. package/dist/apps/data/routes/valuation.js.map +1 -0
  79. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  80. package/dist/apps/data/routes/verified-tokens.js +7 -5
  81. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  82. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  83. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  84. package/dist/apps/data/routes/webhooks.js +124 -25
  85. package/dist/apps/data/routes/webhooks.js.map +1 -1
  86. package/dist/apps/index.d.ts +1 -1
  87. package/dist/apps/index.d.ts.map +1 -1
  88. package/dist/apps/index.js +1 -1
  89. package/dist/apps/index.js.map +1 -1
  90. package/dist/apps/management/App.d.ts +8606 -1681
  91. package/dist/apps/management/App.d.ts.map +1 -1
  92. package/dist/apps/management/App.js +12 -1
  93. package/dist/apps/management/App.js.map +1 -1
  94. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  95. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  96. package/dist/apps/management/routes/api-keys.js +5 -3
  97. package/dist/apps/management/routes/api-keys.js.map +1 -1
  98. package/dist/apps/management/routes/billing.d.ts +88 -48
  99. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  100. package/dist/apps/management/routes/billing.js +10 -2
  101. package/dist/apps/management/routes/billing.js.map +1 -1
  102. package/dist/apps/management/routes/faucet.d.ts +218 -0
  103. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  104. package/dist/apps/management/routes/faucet.js +139 -0
  105. package/dist/apps/management/routes/faucet.js.map +1 -0
  106. package/dist/apps/management/routes/invitations.d.ts +121 -121
  107. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  108. package/dist/apps/management/routes/invitations.js +3 -0
  109. package/dist/apps/management/routes/invitations.js.map +1 -1
  110. package/dist/apps/management/routes/invite-links.d.ts +197 -168
  111. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  112. package/dist/apps/management/routes/invite-links.js +26 -1
  113. package/dist/apps/management/routes/invite-links.js.map +1 -1
  114. package/dist/apps/management/routes/me.d.ts +2 -0
  115. package/dist/apps/management/routes/me.d.ts.map +1 -1
  116. package/dist/apps/management/routes/me.js +4 -0
  117. package/dist/apps/management/routes/me.js.map +1 -1
  118. package/dist/apps/management/routes/members.d.ts +49 -49
  119. package/dist/apps/management/routes/orgs.d.ts +91 -91
  120. package/dist/apps/management/routes/projects.d.ts +115 -115
  121. package/dist/apps/management/routes/usage.d.ts +35 -32
  122. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  123. package/dist/apps/management/routes/usage.js +4 -0
  124. package/dist/apps/management/routes/usage.js.map +1 -1
  125. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  126. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  127. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  128. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  129. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  130. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  131. package/dist/apps/management/routes/webhooks.js +573 -0
  132. package/dist/apps/management/routes/webhooks.js.map +1 -0
  133. package/dist/apps/mcp.d.ts +9 -2
  134. package/dist/apps/mcp.d.ts.map +1 -1
  135. package/dist/apps/mcp.js +9 -4
  136. package/dist/apps/mcp.js.map +1 -1
  137. package/dist/cli/Cli.d.ts.map +1 -1
  138. package/dist/cli/Cli.js +7 -2
  139. package/dist/cli/Cli.js.map +1 -1
  140. package/dist/db/Db.d.ts +4 -0
  141. package/dist/db/Db.d.ts.map +1 -1
  142. package/dist/db/Db.js +83 -0
  143. package/dist/db/Db.js.map +1 -1
  144. package/dist/db/Schema.d.ts +41 -0
  145. package/dist/db/Schema.d.ts.map +1 -1
  146. package/dist/db/Schema.js +45 -0
  147. package/dist/db/Schema.js.map +1 -1
  148. package/dist/db/tables/earlyAccess.d.ts +50 -0
  149. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  150. package/dist/db/tables/earlyAccess.js +63 -0
  151. package/dist/db/tables/earlyAccess.js.map +1 -0
  152. package/dist/db/tables/invitations.d.ts +12 -1
  153. package/dist/db/tables/invitations.d.ts.map +1 -1
  154. package/dist/db/tables/invitations.js +23 -0
  155. package/dist/db/tables/invitations.js.map +1 -1
  156. package/dist/db/tables/inviteLinks.d.ts +10 -2
  157. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  158. package/dist/db/tables/inviteLinks.js +10 -1
  159. package/dist/db/tables/inviteLinks.js.map +1 -1
  160. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  161. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.js +2 -0
  163. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  164. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  165. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  166. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  167. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  168. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  169. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  170. package/dist/db/tables/webhookSubscriptions.js +16 -0
  171. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  172. package/dist/internal/Auth.d.ts +43 -2
  173. package/dist/internal/Auth.d.ts.map +1 -1
  174. package/dist/internal/Auth.js +60 -45
  175. package/dist/internal/Auth.js.map +1 -1
  176. package/dist/internal/FundingQuotes.d.ts +69 -15
  177. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  178. package/dist/internal/FundingQuotes.js +74 -25
  179. package/dist/internal/FundingQuotes.js.map +1 -1
  180. package/dist/internal/Log.d.ts +19 -1
  181. package/dist/internal/Log.d.ts.map +1 -1
  182. package/dist/internal/Log.js +2 -0
  183. package/dist/internal/Log.js.map +1 -1
  184. package/dist/internal/MetricSink.d.ts.map +1 -1
  185. package/dist/internal/MetricSink.js +9 -0
  186. package/dist/internal/MetricSink.js.map +1 -1
  187. package/dist/internal/OpenApi.d.ts +2 -2
  188. package/dist/internal/OpenApi.d.ts.map +1 -1
  189. package/dist/internal/OpenApi.js +3 -0
  190. package/dist/internal/OpenApi.js.map +1 -1
  191. package/dist/internal/Schema.d.ts +14 -5
  192. package/dist/internal/Schema.d.ts.map +1 -1
  193. package/dist/internal/Schema.js +23 -5
  194. package/dist/internal/Schema.js.map +1 -1
  195. package/dist/internal/Value.d.ts +18 -0
  196. package/dist/internal/Value.d.ts.map +1 -1
  197. package/dist/internal/Value.js +11 -1
  198. package/dist/internal/Value.js.map +1 -1
  199. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  200. package/dist/internal/VerifiedTokens.js +10 -1
  201. package/dist/internal/VerifiedTokens.js.map +1 -1
  202. package/dist/internal/Viem.d.ts +1 -1
  203. package/dist/internal/Webhooks.d.ts +24 -3
  204. package/dist/internal/Webhooks.d.ts.map +1 -1
  205. package/dist/internal/Webhooks.js +34 -8
  206. package/dist/internal/Webhooks.js.map +1 -1
  207. package/package.json +2 -2
  208. package/src/App.ts +9 -1
  209. package/src/Client.test-d.ts +638 -6
  210. package/src/admin/App.ts +4 -0
  211. package/src/admin/apps/early-access.test.ts +125 -0
  212. package/src/admin/apps/early-access.ts +175 -0
  213. package/src/admin/apps/verified-tokens.test.ts +201 -0
  214. package/src/admin/apps/verified-tokens.ts +532 -286
  215. package/src/admin/ui/plugin.ts +2 -1
  216. package/src/admin/ui/src/lib/early-access.ts +63 -0
  217. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  218. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  219. package/src/admin/ui/src/routes/__root.tsx +2 -1
  220. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  221. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  222. package/src/admin/ui.generated.ts +48 -33
  223. package/src/apps/data/App.ts +22 -14
  224. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  225. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  226. package/src/apps/data/FxOracle.test.ts +118 -0
  227. package/src/apps/data/FxOracle.ts +153 -0
  228. package/src/apps/data/index.ts +1 -0
  229. package/src/apps/data/routes/activities.test.ts +173 -3
  230. package/src/apps/data/routes/activities.ts +403 -132
  231. package/src/apps/data/routes/addresses.ts +19 -9
  232. package/src/apps/data/routes/balances.test.ts +144 -11
  233. package/src/apps/data/routes/balances.ts +140 -51
  234. package/src/apps/data/routes/exchanges.test.ts +540 -9
  235. package/src/apps/data/routes/exchanges.ts +690 -103
  236. package/src/apps/data/routes/funding.test.ts +84 -0
  237. package/src/apps/data/routes/funding.ts +38 -6
  238. package/src/apps/data/routes/receipts.test.ts +125 -23
  239. package/src/apps/data/routes/receipts.ts +205 -100
  240. package/src/apps/data/routes/tokens.test.ts +43 -0
  241. package/src/apps/data/routes/tokens.ts +27 -12
  242. package/src/apps/data/routes/transactions.test.ts +72 -19
  243. package/src/apps/data/routes/transactions.ts +162 -92
  244. package/src/apps/data/routes/transfers.test.ts +146 -4
  245. package/src/apps/data/routes/transfers.ts +91 -13
  246. package/src/apps/data/routes/valuation.test.ts +422 -0
  247. package/src/apps/data/routes/valuation.ts +423 -0
  248. package/src/apps/data/routes/verified-tokens.ts +14 -2
  249. package/src/apps/data/routes/webhooks.test.ts +56 -8
  250. package/src/apps/data/routes/webhooks.ts +144 -27
  251. package/src/apps/index.ts +1 -1
  252. package/src/apps/management/App.ts +13 -0
  253. package/src/apps/management/routes/api-keys.test.ts +87 -0
  254. package/src/apps/management/routes/api-keys.ts +4 -3
  255. package/src/apps/management/routes/billing.test.ts +112 -1
  256. package/src/apps/management/routes/billing.ts +7 -2
  257. package/src/apps/management/routes/faucet.test.ts +79 -0
  258. package/src/apps/management/routes/faucet.ts +185 -0
  259. package/src/apps/management/routes/invitations.test.ts +172 -0
  260. package/src/apps/management/routes/invitations.ts +3 -0
  261. package/src/apps/management/routes/invite-links.test.ts +117 -0
  262. package/src/apps/management/routes/invite-links.ts +43 -1
  263. package/src/apps/management/routes/me.test.ts +140 -88
  264. package/src/apps/management/routes/me.ts +6 -0
  265. package/src/apps/management/routes/usage.test.ts +6 -0
  266. package/src/apps/management/routes/usage.ts +7 -0
  267. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  268. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  269. package/src/apps/management/routes/webhooks.test.ts +426 -0
  270. package/src/apps/management/routes/webhooks.ts +672 -0
  271. package/src/apps/mcp.test.ts +13 -0
  272. package/src/apps/mcp.ts +18 -5
  273. package/src/cli/Cli.test.ts +36 -0
  274. package/src/cli/Cli.ts +7 -2
  275. package/src/db/Db.test.ts +19 -0
  276. package/src/db/Db.ts +95 -0
  277. package/src/db/Schema.ts +53 -0
  278. package/src/db/tables/earlyAccess.ts +86 -0
  279. package/src/db/tables/invitations.ts +26 -1
  280. package/src/db/tables/inviteLinks.ts +22 -3
  281. package/src/db/tables/sponsoredTransactions.ts +4 -0
  282. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  283. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  284. package/src/db/tables/webhookSubscriptions.ts +31 -0
  285. package/src/internal/Auth.ts +73 -45
  286. package/src/internal/FundingQuotes.test.ts +143 -1
  287. package/src/internal/FundingQuotes.ts +95 -28
  288. package/src/internal/Log.test-d.ts +21 -0
  289. package/src/internal/Log.test.ts +50 -0
  290. package/src/internal/Log.ts +24 -1
  291. package/src/internal/MetricSink.test.ts +160 -0
  292. package/src/internal/MetricSink.ts +10 -0
  293. package/src/internal/OpenApi.ts +3 -0
  294. package/src/internal/Schema.ts +50 -5
  295. package/src/internal/Value.test.ts +20 -0
  296. package/src/internal/Value.ts +24 -1
  297. package/src/internal/VerifiedTokens.ts +9 -1
  298. package/src/internal/Webhooks.test.ts +140 -0
  299. package/src/internal/Webhooks.ts +53 -9
@@ -14,10 +14,14 @@ import * as Store from '../../../internal/Store.js';
14
14
  import * as Timing from '../../../internal/Timing.js';
15
15
  import * as Ttl from '../../../internal/Ttl.js';
16
16
  import * as Value from '../../../internal/Value.js';
17
+ import * as VerifiedTokens from '../../../internal/VerifiedTokens.js';
18
+ import * as FxOracle from '../FxOracle.js';
17
19
  import * as Tokens from './tokens.js';
18
20
  import * as Transfers from './transfers.js';
21
+ import * as Valuation from './valuation.js';
19
22
  /** Example transaction hash surfaced in OpenAPI docs. */
20
23
  const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474';
24
+ const tokenFields = ['token.logoUri', 'token.verified'];
21
25
  /** Zod schemas owned by the activity handlers. */
22
26
  export var schema;
23
27
  (function (schema) {
@@ -25,19 +29,31 @@ export var schema;
25
29
  const Signer = z
26
30
  .union([z.literal('self'), Schema.Address])
27
31
  .check(z.describe('Who signed the activity: `self`, or the access-key address used to sign it.'));
28
- /**
29
- * A token and the amount that moved, in the same shape as the API's transfer
30
- * resource (`Transfers.schema.SourceToken`): a contract `address` and a
31
- * decimal-integer `amount`, plus best-effort `decimals`/`symbol` metadata when
32
- * the token has been resolved.
33
- */
34
- schema.Token = Transfers.schema.SourceToken;
35
- /**
36
- * A token transfer as classified by the feed: the `/transfers` row shape
37
- * plus the feed-only, subject-relative fields (`direction`, `signer`) that
38
- * the unscoped resource cannot carry, with `memo`/`attribution` always
39
- * resolved (the feed already decodes the underlying events).
40
- */
32
+ /** A token with required RPC metadata and optional curated fields. */
33
+ schema.Token = Schema.describe(Tokens.schema.TokenReference, 'A token referenced by an activity item.');
34
+ /** A token amount carrying its nominal value in the requested denomination. */
35
+ const ValuedAmount = z.extend(Schema.TokenAmount, {
36
+ valuation: z
37
+ .optional(z.nullable(Valuation.schema.Value))
38
+ .check(z.describe('The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
39
+ 'unverified, its display currency has no rate, or rates were unavailable.')),
40
+ });
41
+ /** Self-contained amount for a destination token. */
42
+ const DestinationAmount = ValuedAmount.check(z.describe('Amount associated with `destinationToken`.'));
43
+ /** Self-contained amount for a refunded token. */
44
+ const RefundAmount = ValuedAmount.check(z.describe('Amount associated with `refund`.'));
45
+ /** Self-contained amount for a source token. */
46
+ const SourceAmount = ValuedAmount.check(z.describe('Amount associated with `sourceToken`.'));
47
+ /** Page-level resources: valuation rate provenance. */
48
+ schema.Meta = z
49
+ .object({
50
+ pricing: z
51
+ .optional(Valuation.schema.Pricing)
52
+ .check(z.describe('Rate provenance for amount valuations. Present when conversion rates were ' +
53
+ 'consulted; absent when every value was identity-valued or rates were unavailable.')),
54
+ })
55
+ .check(z.describe('Page-level resources attached to this response.'));
56
+ /** A token transfer with subject-relative direction and signer fields. */
41
57
  schema.Transfer = z
42
58
  .object({
43
59
  attribution: z
@@ -46,9 +62,12 @@ export var schema;
46
62
  blockNumber: z
47
63
  .number()
48
64
  .check(z.int(), z.nonnegative(), z.describe('Block number where this transfer was included.'), z.meta({ examples: [12345678] })),
65
+ destinationAmount: z
66
+ .optional(DestinationAmount)
67
+ .check(z.describe('Amount delivered to the recipient for a cross-token transfer. Omitted with `destinationToken` for a same-token transfer.')),
49
68
  destinationToken: z
50
69
  .optional(schema.Token)
51
- .check(z.describe('Token and amount delivered to the recipient when it differs from the source token — a cross-token transfer routed through the DEX. Omitted for a same-token transfer.')),
70
+ .check(z.describe('Token delivered to the recipient when it differs from the source token. Omitted for a same-token transfer.')),
52
71
  direction: z
53
72
  .enum(['in', 'out'])
54
73
  .check(z.describe('The address’s side of the transfer: `in` means received, and `out` means sent.')),
@@ -60,13 +79,17 @@ export var schema;
60
79
  signer: z
61
80
  .optional(Signer)
62
81
  .check(z.describe('For outbound transfers, who signed it: `self` or the access-key address. Omitted for inbound transfers.')),
63
- sourceToken: schema.Token.check(z.describe('Token and amount that moved in the transfer.')),
82
+ sourceAmount: SourceAmount,
83
+ sourceToken: schema.Token.check(z.describe('Token that moved in the transfer.')),
64
84
  timestamp: z
65
85
  .iso.datetime()
66
86
  .check(z.describe('Block timestamp for the transfer.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })), // prettier-ignore
67
87
  transactionHash: Schema.Hash.check(z.describe('Transaction hash that contains this transfer.')),
68
88
  })
69
- .check(z.describe('One token transfer involving the requested address.'));
89
+ .check(z.refine((data) => (data.destinationToken === undefined) === (data.destinationAmount === undefined), {
90
+ error: '`destinationAmount` and `destinationToken` must be returned together.',
91
+ path: ['destinationAmount'],
92
+ }), z.describe('One token transfer involving the requested address.'));
70
93
  /**
71
94
  * One transaction log, decoded into a known Tempo event when possible. The
72
95
  * raw fields (`address`/`data`/`logIndex`/`topics`) are always present so no
@@ -126,7 +149,7 @@ export var schema;
126
149
  z
127
150
  .object({
128
151
  ...base,
129
- data: schema.Transfer.check(z.describe('Transfer details, using the `/transfers` shape plus feed-only fields such as direction and signer.')),
152
+ data: schema.Transfer.check(z.describe('Transfer details with feed-relative direction and signer fields.')),
130
153
  type: z
131
154
  .literal('transfer')
132
155
  .check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
@@ -138,7 +161,8 @@ export var schema;
138
161
  data: z
139
162
  .object({
140
163
  recipient: Schema.Address.check(z.describe('Address that received the transfer.')),
141
- sourceToken: schema.Token.check(z.describe('Token and amount that were minted.')),
164
+ sourceAmount: SourceAmount,
165
+ sourceToken: schema.Token.check(z.describe('Token that was minted.')),
142
166
  })
143
167
  .check(z.describe('A token mint activity.')),
144
168
  type: z
@@ -153,7 +177,8 @@ export var schema;
153
177
  .object({
154
178
  sender: Schema.Address.check(z.describe('Address whose token balance was burned.')),
155
179
  signer: Signer,
156
- sourceToken: schema.Token.check(z.describe('Token and amount that were burned.')),
180
+ sourceAmount: SourceAmount,
181
+ sourceToken: schema.Token.check(z.describe('Token that was burned.')),
157
182
  })
158
183
  .check(z.describe('A token burn activity.')),
159
184
  type: z
@@ -166,8 +191,10 @@ export var schema;
166
191
  ...base,
167
192
  data: z
168
193
  .object({
194
+ destinationAmount: DestinationAmount,
169
195
  destinationToken: schema.Token.check(z.describe('Token the address received in the swap.')),
170
196
  signer: Signer,
197
+ sourceAmount: SourceAmount,
171
198
  sourceToken: schema.Token.check(z.describe('Token the address sent into the swap.')),
172
199
  })
173
200
  .check(z.describe('Swap inferred from token transfers through the DEX precompile. This feed entry is a summary, so per-fill details, route, and rate are not included.')),
@@ -182,7 +209,8 @@ export var schema;
182
209
  data: z
183
210
  .object({
184
211
  signer: Signer,
185
- sourceToken: schema.Token.check(z.describe('Token and allowance amount approved for spending.')),
212
+ sourceAmount: SourceAmount,
213
+ sourceToken: schema.Token.check(z.describe('Token approved for spending.')),
186
214
  spender: Schema.Address.check(z.describe('Address allowed to spend the approved amount.')),
187
215
  })
188
216
  .check(z.describe('A token allowance approval activity.')),
@@ -199,9 +227,15 @@ export var schema;
199
227
  refund: z
200
228
  .optional(schema.Token)
201
229
  .check(z.describe('Token refunded to the address when the session closed, if any.')),
230
+ refundAmount: z
231
+ .optional(RefundAmount)
232
+ .check(z.describe('Amount refunded to the address when the session closed, if any.')),
202
233
  signer: Signer,
203
234
  })
204
- .check(z.describe('A payment-channel session close activity.')),
235
+ .check(z.refine((data) => (data.refund === undefined) === (data.refundAmount === undefined), {
236
+ error: '`refundAmount` and `refund` must be returned together.',
237
+ path: ['refundAmount'],
238
+ }), z.describe('A payment-channel session close activity.')),
205
239
  type: z
206
240
  .literal('session-closed')
207
241
  .check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
@@ -266,7 +300,8 @@ export var schema;
266
300
  .object({
267
301
  sender: Schema.Address.check(z.describe('Address whose blocked balance was burned by policy.')),
268
302
  signer: Signer,
269
- sourceToken: schema.Token.check(z.describe('Token and amount that were burned.')),
303
+ sourceAmount: SourceAmount,
304
+ sourceToken: schema.Token.check(z.describe('Token that was burned.')),
270
305
  })
271
306
  .check(z.describe('A compliance burn of funds the receive policy blocked, distinct from a normal burn.')),
272
307
  type: z
@@ -283,7 +318,8 @@ export var schema;
283
318
  payee: Schema.Address.check(z.describe('Address that receives payments from the channel.')),
284
319
  payer: Schema.Address.check(z.describe('Address that funds the channel.')),
285
320
  signer: Signer,
286
- sourceToken: schema.Token.check(z.describe('Token and deposit amount locked into the channel.')),
321
+ sourceAmount: SourceAmount,
322
+ sourceToken: schema.Token.check(z.describe('Token locked into the channel.')),
287
323
  })
288
324
  .check(z.describe('A payment channel was opened and funded.')),
289
325
  type: z
@@ -298,7 +334,8 @@ export var schema;
298
334
  .object({
299
335
  channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
300
336
  signer: Signer,
301
- sourceToken: schema.Token.check(z.describe('Token and amount added to the channel deposit.')),
337
+ sourceAmount: SourceAmount,
338
+ sourceToken: schema.Token.check(z.describe('Token added to the channel deposit.')),
302
339
  })
303
340
  .check(z.describe('A payment channel deposit was topped up.')),
304
341
  type: z
@@ -315,7 +352,8 @@ export var schema;
315
352
  payee: Schema.Address.check(z.describe('Address that received the settled payment.')),
316
353
  payer: Schema.Address.check(z.describe('Address that funded the channel.')),
317
354
  signer: Signer,
318
- sourceToken: schema.Token.check(z.describe('Token and amount paid out in this settlement.')),
355
+ sourceAmount: SourceAmount,
356
+ sourceToken: schema.Token.check(z.describe('Token paid out in this settlement.')),
319
357
  })
320
358
  .check(z.describe('An incremental payment-channel settlement to the payee.')),
321
359
  type: z
@@ -333,13 +371,25 @@ export var schema;
333
371
  payer: Schema.Address.check(z.describe('Address that funded the channel.')),
334
372
  refund: z
335
373
  .optional(schema.Token)
336
- .check(z.describe('Token and amount refunded to the payer on close, if any.')),
374
+ .check(z.describe('Token refunded to the payer on close, if any.')),
375
+ refundAmount: z
376
+ .optional(RefundAmount)
377
+ .check(z.describe('Amount refunded to the payer on close, if any.')),
337
378
  signer: Signer,
379
+ sourceAmount: z
380
+ .optional(SourceAmount)
381
+ .check(z.describe('Final amount paid to the payee on close, if any.')),
338
382
  sourceToken: z
339
383
  .optional(schema.Token)
340
- .check(z.describe('Token and final amount paid out to the payee on close, if any.')),
384
+ .check(z.describe('Token paid out to the payee on close, if any.')),
341
385
  })
342
- .check(z.describe('A payment channel was closed, paying out the payee and refunding the payer.')),
386
+ .check(z.refine((data) => (data.refund === undefined) === (data.refundAmount === undefined), {
387
+ error: '`refundAmount` and `refund` must be returned together.',
388
+ path: ['refundAmount'],
389
+ }), z.refine((data) => (data.sourceToken === undefined) === (data.sourceAmount === undefined), {
390
+ error: '`sourceAmount` and `sourceToken` must be returned together.',
391
+ path: ['sourceAmount'],
392
+ }), z.describe('A payment channel was closed, paying out the payee and refunding the payer.')),
343
393
  type: z
344
394
  .literal('channel-closed')
345
395
  .check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
@@ -371,7 +421,8 @@ export var schema;
371
421
  .enum(['bid', 'ask'])
372
422
  .check(z.describe('Order side: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
373
423
  signer: Signer,
374
- sourceToken: schema.Token.check(z.describe('Token and amount escrowed for the order.')),
424
+ sourceAmount: SourceAmount,
425
+ sourceToken: schema.Token.check(z.describe('Token escrowed for the order.')),
375
426
  tick: z
376
427
  .number()
377
428
  .check(z.int(), z.describe('Price tick the order was placed at.'), z.meta({ examples: [0] })),
@@ -402,7 +453,8 @@ export var schema;
402
453
  data: z
403
454
  .object({
404
455
  signer: Signer,
405
- sourceToken: schema.Token.check(z.describe('Token and amount of fees distributed.')),
456
+ sourceAmount: SourceAmount,
457
+ sourceToken: schema.Token.check(z.describe('Token used for the distributed fees.')),
406
458
  validator: Schema.Address.check(z.describe('Validator that received the distributed fees.')),
407
459
  })
408
460
  .check(z.describe('Accrued validator fees were distributed.')),
@@ -416,9 +468,11 @@ export var schema;
416
468
  ...base,
417
469
  data: z
418
470
  .object({
419
- destinationToken: schema.Token.check(z.describe('Token and amount received from the fee AMM.')),
471
+ destinationAmount: DestinationAmount,
472
+ destinationToken: schema.Token.check(z.describe('Token received from the fee AMM.')),
420
473
  signer: Signer,
421
- sourceToken: schema.Token.check(z.describe('Token and amount sent into the fee AMM.')),
474
+ sourceAmount: SourceAmount,
475
+ sourceToken: schema.Token.check(z.describe('Token sent into the fee AMM.')),
422
476
  swapper: Schema.Address.check(z.describe('Address that performed the rebalance swap.')),
423
477
  })
424
478
  .check(z.describe('A fee-AMM rebalance swap between a validator and user token.')),
@@ -434,7 +488,8 @@ export var schema;
434
488
  .object({
435
489
  funder: Schema.Address.check(z.describe('Address that funded the reward pool.')),
436
490
  signer: Signer,
437
- sourceToken: schema.Token.check(z.describe('Token and amount added to the reward pool.')),
491
+ sourceAmount: SourceAmount,
492
+ sourceToken: schema.Token.check(z.describe('Token added to the reward pool.')),
438
493
  })
439
494
  .check(z.describe('A token reward pool was funded.')),
440
495
  type: z
@@ -465,7 +520,8 @@ export var schema;
465
520
  account: Schema.Address.check(z.describe('Account the access key belongs to.')),
466
521
  publicKey: Schema.Address.check(z.describe('Public key of the access key whose limit changed.')),
467
522
  signer: Signer,
468
- sourceToken: schema.Token.check(z.describe('Token and new spending limit set for the access key.')),
523
+ sourceAmount: SourceAmount,
524
+ sourceToken: schema.Token.check(z.describe('Token whose spending limit was updated.')),
469
525
  })
470
526
  .check(z.describe('An access key’s per-token spending limit was updated.')),
471
527
  type: z
@@ -507,7 +563,8 @@ export var schema;
507
563
  .enum(['bid', 'ask'])
508
564
  .check(z.describe('Side of the flipped order: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
509
565
  signer: Signer,
510
- sourceToken: schema.Token.check(z.describe('Token and amount the flipped order rests with.')),
566
+ sourceAmount: SourceAmount,
567
+ sourceToken: schema.Token.check(z.describe('Token the flipped order rests with.')),
511
568
  tick: z
512
569
  .number()
513
570
  .check(z.int(), z.describe('Price tick the original order filled at.'), z.meta({ examples: [0] })),
@@ -1085,6 +1142,12 @@ export var schema;
1085
1142
  schema.Entry = z
1086
1143
  .union([...schema.items, schema.Group])
1087
1144
  .check(z.describe('Either one activity item, or a grouped set of access-key activity items.'));
1145
+ /** Optional token fields that require sources beyond RPC metadata. */
1146
+ schema.Include = z
1147
+ .enum(tokenFields)
1148
+ .check(z.describe('Additional token fields to include in activity responses.'));
1149
+ /** Parses comma-separated optional token fields. */
1150
+ schema.includeQuery = Schema.includeQuery(schema.Include, 'Comma-separated token fields to include, such as `token.logoUri,token.verified`.');
1088
1151
  /** Schemas for the getAddressActivities operation. */
1089
1152
  let getAddressActivities;
1090
1153
  (function (getAddressActivities) {
@@ -1099,10 +1162,12 @@ export var schema;
1099
1162
  .strictObject({
1100
1163
  chainId: Schema.ChainIdQuery,
1101
1164
  cursor: Schema.Cursor,
1165
+ denomination: Schema.Denomination,
1102
1166
  // Folding is opt-in: by default every item is returned at the root.
1103
1167
  group: z
1104
1168
  ._default(Schema.booleanQuery(), false)
1105
1169
  .check(z.describe('Set to `true` to fold consecutive items signed by the same access key on the same UTC day into `group` entries. Defaults to `false`, which returns every item separately.'), z.meta({ examples: [true] })),
1170
+ include: schema.includeQuery,
1106
1171
  limit: Schema.Limit,
1107
1172
  // Raw-log capture is opt-in: it can add a large `events` array to every
1108
1173
  // item, so the default feed stays lean.
@@ -1120,6 +1185,9 @@ export var schema;
1120
1185
  getAddressActivities.Response = z
1121
1186
  .object({
1122
1187
  data: z.array(schema.Entry).check(z.describe('Activity feed entries for the address.')),
1188
+ meta: z
1189
+ .optional(schema.Meta)
1190
+ .check(z.describe('Page-level resources, such as valuation rate provenance.')),
1123
1191
  nextCursor: Schema.NextCursor,
1124
1192
  })
1125
1193
  .check(z.describe('A page of human-readable activity for the address.'));
@@ -1137,6 +1205,8 @@ export var schema;
1137
1205
  getTransactionActivities.Query = z
1138
1206
  .strictObject({
1139
1207
  chainId: Schema.ChainIdQuery,
1208
+ denomination: Schema.Denomination,
1209
+ include: schema.includeQuery,
1140
1210
  // Raw-log capture is opt-in: it can add a large `events` array to every
1141
1211
  // item, so the default response stays lean.
1142
1212
  logs: z
@@ -1153,6 +1223,9 @@ export var schema;
1153
1223
  getTransactionActivities.Response = z
1154
1224
  .object({
1155
1225
  data: z.array(schema.Item).check(z.describe('Activity items classified from the transaction.')),
1226
+ meta: z
1227
+ .optional(schema.Meta)
1228
+ .check(z.describe('Page-level resources, such as valuation rate provenance.')),
1156
1229
  })
1157
1230
  .check(z.describe('The human-readable activity that happened on a transaction.'));
1158
1231
  })(getTransactionActivities = schema.getTransactionActivities || (schema.getTransactionActivities = {}));
@@ -1308,6 +1381,11 @@ const minGroupEntries = 5;
1308
1381
  const maxGroupSize = 50;
1309
1382
  /** Token-bearing keys on an item's `data` payload, walked for enrichment. */
1310
1383
  const tokenKeys = ['destinationToken', 'refund', 'sourceToken'];
1384
+ const amountKeyByTokenKey = {
1385
+ destinationToken: 'destinationAmount',
1386
+ refund: 'refundAmount',
1387
+ sourceToken: 'sourceAmount',
1388
+ };
1311
1389
  /** Builds common public activity fields from the normalized transaction hash and representative log. */
1312
1390
  function baseFor(options) {
1313
1391
  return {
@@ -1336,7 +1414,8 @@ function baseFor(options) {
1336
1414
  * consecutive access-key-signed items into `group` entries. Pagination uses the
1337
1415
  * same `(block_num, tx_idx)` keyset cursor the pipeline emits.
1338
1416
  */
1339
- export function addresses() {
1417
+ export function addresses(options = {}) {
1418
+ const oracle = options.fx?.oracle ?? FxOracle.ecb();
1340
1419
  return new Hono().get('/v1/addresses/:address/activities', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getAddressActivities.Params, {
1341
1420
  code: 'address_invalid',
1342
1421
  message: 'Check the account address and try again.',
@@ -1347,7 +1426,7 @@ export function addresses() {
1347
1426
  description: 'Get a human-readable feed of what an address has been doing onchain, including payments sent and received, swaps, approvals, and more.',
1348
1427
  operationId: 'getAddressActivities',
1349
1428
  responses: OpenApi.responses({
1350
- errors: { 502: 'Tempo could not read activity data from the upstream indexer.' },
1429
+ errors: { 502: 'Tempo could not read activity or token data from an upstream service.' },
1351
1430
  success: {
1352
1431
  description: 'A page of address activity entries.',
1353
1432
  schema: schema.getAddressActivities.Response,
@@ -1374,15 +1453,13 @@ export function addresses() {
1374
1453
  });
1375
1454
  const { address } = c.req.valid('param');
1376
1455
  const query = c.req.valid('query');
1377
- // Cursor pages are anchored below the head and effectively immutable, so
1378
- // cache them aggressively; the head page keeps the route's short `feed`
1379
- // default.
1456
+ // Cursor pages carry current valuations and curated verification data.
1380
1457
  if (query.cursor !== undefined)
1381
- Cache.setPolicy(c, Cache.policies.immutable);
1458
+ Cache.setPolicy(c, Cache.policies.metadata);
1382
1459
  try {
1383
1460
  const chainId = query.chainId ?? c.get('chainId');
1384
1461
  const provider = tidx({ chainId, tidx: c.get('getTidx')(chainId) });
1385
- const { items, nextCursor } = await Timing.time(c, 'address_activities', () => Store.memoize(() => list(provider, {
1462
+ const { items: unenrichedItems, nextCursor } = await Timing.time(c, 'address_activities', () => Store.memoize(() => list(provider, {
1386
1463
  address: address,
1387
1464
  cursor: query.cursor,
1388
1465
  group: query.group,
@@ -1394,20 +1471,26 @@ export function addresses() {
1394
1471
  store: c.get('store'),
1395
1472
  ttl: Ttl.seconds(15),
1396
1473
  }));
1397
- // Resolve token metadata for every token referenced across the page and
1398
- // spread it into each item's stub tokens. Goes through the shared
1399
- // `Tokens.getTokenMetadata` path so it reuses the `token:v1:…` cache with
1400
- // `/tokens` and `/transfers` instead of a parallel metadata fetch.
1401
- await enrichTokens(c, { chainId, items });
1474
+ // Build immutable token and structured amount copies from RPC metadata.
1475
+ const { items, rates } = await enrichTokens(c, {
1476
+ chainId,
1477
+ denomination: query.denomination ?? 'USD',
1478
+ include: query.include,
1479
+ items: unenrichedItems,
1480
+ oracle,
1481
+ });
1402
1482
  // Fold consecutive access-key-signed items into `group` entries only
1403
1483
  // when requested; otherwise every item is returned at the root.
1404
1484
  const data = query.group ? groupByAccessKey(items) : items;
1405
1485
  return c.json(Response.validated(schema.getAddressActivities.Response, {
1406
1486
  data,
1487
+ ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
1407
1488
  nextCursor,
1408
1489
  }), 200);
1409
1490
  }
1410
1491
  catch (cause) {
1492
+ if (cause instanceof Valuation.UnsupportedDenominationError)
1493
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 });
1411
1494
  return Response.upstream(c, cause);
1412
1495
  }
1413
1496
  });
@@ -1422,7 +1505,8 @@ export function addresses() {
1422
1505
  * classified item(s) — usually one headline activity — with the full per-tx
1423
1506
  * event set available via `logs=true`.
1424
1507
  */
1425
- export function transactions() {
1508
+ export function transactions(options = {}) {
1509
+ const oracle = options.fx?.oracle ?? FxOracle.ecb();
1426
1510
  return new Hono().get('/v1/transactions/:transactionHash/activities', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('param', schema.getTransactionActivities.Params, {
1427
1511
  code: 'transaction_invalid',
1428
1512
  message: 'Check the transaction hash and try again.',
@@ -1438,7 +1522,7 @@ export function transactions() {
1438
1522
  description: 'No mined transaction was found for that hash.',
1439
1523
  codes: ['transaction_not_found'],
1440
1524
  },
1441
- 502: 'Tempo could not read activity data from the upstream indexer.',
1525
+ 502: 'Tempo could not read activity or token data from an upstream service.',
1442
1526
  },
1443
1527
  success: {
1444
1528
  description: 'The classified activity for the transaction.',
@@ -1448,10 +1532,8 @@ export function transactions() {
1448
1532
  summary: 'List transaction activities',
1449
1533
  tags: ['Activities'],
1450
1534
  }), Cache.response({
1451
- // Activity exists only once the transaction is mined; once mined it is
1452
- // immutable. Default to no-store so a 404 (unknown / not-yet-mined hash)
1453
- // is not cached with a long header that would mask the eventual result;
1454
- // the handler upgrades the 200 to `immutable` below.
1535
+ // Default to no-store so a 404 does not mask the eventual mined result.
1536
+ // Successful responses use the metadata policy for current valuations.
1455
1537
  cacheControl: Cache.policies.noStore,
1456
1538
  name: 'tempo-api:transactions:v1',
1457
1539
  key: (c) => Cache.urlKey(c, schema.getTransactionActivities.Query),
@@ -1472,7 +1554,7 @@ export function transactions() {
1472
1554
  const query = c.req.valid('query');
1473
1555
  const chainId = query.chainId ?? c.get('chainId');
1474
1556
  try {
1475
- const items = await Timing.time(c, 'transaction_activities', () => Store.memoize(() => getTransactionActivities({
1557
+ const unenrichedItems = await Timing.time(c, 'transaction_activities', () => Store.memoize(() => getTransactionActivities({
1476
1558
  chainId,
1477
1559
  logs: query.logs,
1478
1560
  store: c.get('store'),
@@ -1483,20 +1565,30 @@ export function transactions() {
1483
1565
  store: c.get('store'),
1484
1566
  ttl: Ttl.seconds(15),
1485
1567
  }));
1486
- if (items === null)
1568
+ if (unenrichedItems === null)
1487
1569
  return Response.error(c, {
1488
1570
  code: 'transaction_not_found',
1489
1571
  message: 'Transaction not found',
1490
1572
  status: 404,
1491
1573
  });
1492
- // A mined transaction's activity is final, so cache it aggressively.
1493
- Cache.setPolicy(c, Cache.policies.immutable);
1494
- // Resolve token metadata for every token referenced and spread it into
1495
- // each item's stub tokens (shared `token:v1:…` cache path).
1496
- await enrichTokens(c, { chainId, items });
1497
- return c.json(Response.validated(schema.getTransactionActivities.Response, { data: items }), 200);
1574
+ // Mined activity still carries current valuations and verification data.
1575
+ Cache.setPolicy(c, Cache.policies.metadata);
1576
+ // Build immutable token and structured amount copies from RPC metadata.
1577
+ const { items, rates } = await enrichTokens(c, {
1578
+ chainId,
1579
+ denomination: query.denomination ?? 'USD',
1580
+ include: query.include,
1581
+ items: unenrichedItems,
1582
+ oracle,
1583
+ });
1584
+ return c.json(Response.validated(schema.getTransactionActivities.Response, {
1585
+ data: items,
1586
+ ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
1587
+ }), 200);
1498
1588
  }
1499
1589
  catch (cause) {
1590
+ if (cause instanceof Valuation.UnsupportedDenominationError)
1591
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 });
1500
1592
  return Response.upstream(c, cause);
1501
1593
  }
1502
1594
  });
@@ -1595,7 +1687,7 @@ export async function list(provider, options) {
1595
1687
  allItems.push(...page.flatMap((txGroup) => classifyGroup(txGroup, viewer, fingerprintMap, logs).map(withTitle)));
1596
1688
  lastGroup = page[page.length - 1];
1597
1689
  const enough = group
1598
- ? groupByAccessKey(allItems).length >= minGroupEntries
1690
+ ? groupByAccessKeyItems(allItems).length >= minGroupEntries
1599
1691
  : allItems.length >= limit;
1600
1692
  if (enough || !hasMore)
1601
1693
  break;
@@ -1605,8 +1697,8 @@ export async function list(provider, options) {
1605
1697
  let nextCursor = null;
1606
1698
  if (hasMore && lastGroup)
1607
1699
  nextCursor = provider.cursorForGroup(lastGroup);
1608
- // Items carry stub tokens (`address`/`amount`); the caller enriches them with
1609
- // metadata via {@link enrichTokens}.
1700
+ // Items carry private `address`/`amount` stubs; the caller splits them into
1701
+ // public token references and structured amounts via {@link enrichTokens}.
1610
1702
  return { items: allItems, nextCursor };
1611
1703
  }
1612
1704
  /**
@@ -3014,7 +3106,7 @@ function lowerOrNull(value) {
3014
3106
  const text = Value.toText(value);
3015
3107
  return text ? text.toLowerCase() : null;
3016
3108
  }
3017
- /** Builds a stub token; `decimals`/`symbol` are filled by {@link enrichItem}. */
3109
+ /** Builds a token stub whose metadata is filled by {@link enrichItem}. */
3018
3110
  function token(amount, address) {
3019
3111
  return { address: address.toLowerCase(), amount: (amount ?? 0n).toString() };
3020
3112
  }
@@ -3027,48 +3119,94 @@ function collectTokens(item, set) {
3027
3119
  set.add(t.address.toLowerCase());
3028
3120
  }
3029
3121
  }
3030
- /**
3031
- * Resolves metadata for every token referenced across a page of items and
3032
- * spreads it into each item's stub tokens, in place. Routes through the shared
3033
- * {@link Tokens.getTokenMetadata} (one `token.getMetadata` RPC per token,
3034
- * memoized under the `token:v1:…` key shared with `/tokens` and `/transfers`)
3035
- * instead of a parallel metadata fetch. Resolution is best-effort: a token
3036
- * whose metadata is unavailable keeps just `address`/`amount`.
3037
- */
3122
+ /** Resolves core metadata into immutable items. Optional logo and verification sources are best-effort. */
3038
3123
  async function enrichTokens(c, options) {
3039
- const { chainId, items } = options;
3124
+ const { chainId, denomination, include, items, oracle } = options;
3040
3125
  const addresses = new Set();
3041
3126
  for (const item of items)
3042
3127
  collectTokens(item, addresses);
3128
+ // Valuation keys off the curated snapshot; a snapshot failure degrades the
3129
+ // values to `null` rather than failing the feed.
3130
+ const snapshot = addresses.size > 0
3131
+ ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
3132
+ : undefined;
3133
+ const rates = await Valuation.ratesFor(c, {
3134
+ currencies: snapshot
3135
+ ? [...addresses].flatMap((address) => {
3136
+ const held = snapshot.byAddress.get(address)?.currency;
3137
+ return held === undefined ? [] : [held];
3138
+ })
3139
+ : [],
3140
+ denomination,
3141
+ oracle,
3142
+ });
3043
3143
  if (addresses.size === 0)
3044
- return;
3045
- const resolved = await Timing.time(c, 'tokens', () => Promise.all([...addresses].map((address) => Tokens.getTokenMetadata(c, { address, chainId })
3046
- .then((metadata) => [address.toLowerCase(), metadata])
3047
- .catch(() => [address.toLowerCase(), undefined]))));
3144
+ return { items: items, rates };
3145
+ const includeLogoUri = include.includes('token.logoUri');
3146
+ const includeVerified = include.includes('token.verified');
3147
+ const resolved = await Timing.time(c, 'tokens', () => Promise.all([...addresses].map(async (address) => {
3148
+ const [logoUri, metadata] = await Promise.all([
3149
+ includeLogoUri
3150
+ ? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
3151
+ : undefined,
3152
+ Tokens.getTokenMetadata(c, { address, chainId }),
3153
+ ]);
3154
+ return [
3155
+ address.toLowerCase(),
3156
+ {
3157
+ currency: metadata.currency,
3158
+ decimals: metadata.decimals,
3159
+ logoUri: includeLogoUri
3160
+ ? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
3161
+ : undefined,
3162
+ name: metadata.name,
3163
+ symbol: metadata.symbol,
3164
+ verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
3165
+ },
3166
+ ];
3167
+ })));
3048
3168
  const byAddress = new Map(resolved);
3049
- for (const item of items)
3050
- enrichItem(item, byAddress);
3169
+ return {
3170
+ items: items.map((item) => enrichItem(item, byAddress, { denomination, rates, snapshot })),
3171
+ rates,
3172
+ };
3051
3173
  }
3052
- /** Spreads resolved token metadata into an item's stub tokens, in place. */
3053
- function enrichItem(item, byAddress) {
3054
- const data = item.data;
3174
+ /** Returns an enriched copy of an item without mutating its cached token stubs. */
3175
+ function enrichItem(item, byAddress, valuation) {
3176
+ const source = item.data;
3177
+ const data = { ...item.data };
3055
3178
  for (const key of tokenKeys) {
3056
- const t = data[key];
3179
+ const t = source[key];
3057
3180
  if (!t)
3058
3181
  continue;
3059
3182
  const meta = byAddress.get(t.address.toLowerCase());
3060
3183
  if (!meta)
3061
- continue;
3062
- // Carry only the per-row token fields the `Token` (SourceToken) shape
3063
- // declares; `address`/`amount` come from the stub.
3184
+ throw new Error(`Token metadata unavailable for ${t.address}`);
3185
+ data[amountKeyByTokenKey[key]] = {
3186
+ ...Value.tokenAmount({
3187
+ baseUnits: t.amount,
3188
+ currency: meta.currency,
3189
+ decimals: meta.decimals,
3190
+ }),
3191
+ // Valued via the curated display currency, never the on-chain metadata.
3192
+ valuation: Valuation.valuationFor({
3193
+ amount: BigInt(t.amount),
3194
+ denomination: valuation.denomination,
3195
+ rates: valuation.rates,
3196
+ token: valuation.snapshot?.byAddress.get(t.address.toLowerCase()),
3197
+ }),
3198
+ };
3064
3199
  data[key] = {
3065
- ...t,
3200
+ address: t.address,
3066
3201
  currency: meta.currency,
3067
3202
  decimals: meta.decimals,
3203
+ ...(meta.logoUri !== undefined ? { logoUri: meta.logoUri } : {}),
3068
3204
  name: meta.name,
3069
3205
  symbol: meta.symbol,
3206
+ ...(meta.verified !== undefined ? { verified: meta.verified } : {}),
3070
3207
  };
3071
3208
  }
3209
+ return { ...item, data };
3072
3210
  }
3073
3211
  /**
3074
3212
  * Folds adjacent items signed by the same access key on the same UTC calendar
@@ -3076,6 +3214,10 @@ function enrichItem(item, byAddress) {
3076
3214
  * Must be called once over the full, timestamp-sorted item list.
3077
3215
  */
3078
3216
  export function groupByAccessKey(items) {
3217
+ return groupByAccessKeyItems(items);
3218
+ }
3219
+ /** Groups items while preserving whether their token data has been enriched. */
3220
+ function groupByAccessKeyItems(items) {
3079
3221
  const entries = [];
3080
3222
  let currentGroup = null;
3081
3223
  for (const item of items) {