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
@@ -17,12 +17,17 @@ import type * as Tidx from '../../../internal/Tidx.js'
17
17
  import * as Timing from '../../../internal/Timing.js'
18
18
  import * as Ttl from '../../../internal/Ttl.js'
19
19
  import * as Value from '../../../internal/Value.js'
20
+ import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
21
+ import * as FxOracle from '../FxOracle.js'
20
22
  import * as Tokens from './tokens.js'
21
23
  import * as Transfers from './transfers.js'
24
+ import * as Valuation from './valuation.js'
22
25
 
23
26
  /** Example transaction hash surfaced in OpenAPI docs. */
24
27
  const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474'
25
28
 
29
+ const tokenFields = ['token.logoUri', 'token.verified'] as const
30
+
26
31
  /** Zod schemas owned by the activity handlers. */
27
32
  export namespace schema {
28
33
  /** A signer: the literal `self`, or the access-key address that signed. */
@@ -32,20 +37,50 @@ export namespace schema {
32
37
  z.describe('Who signed the activity: `self`, or the access-key address used to sign it.'),
33
38
  )
34
39
 
35
- /**
36
- * A token and the amount that moved, in the same shape as the API's transfer
37
- * resource (`Transfers.schema.SourceToken`): a contract `address` and a
38
- * decimal-integer `amount`, plus best-effort `decimals`/`symbol` metadata when
39
- * the token has been resolved.
40
- */
41
- export const Token = Transfers.schema.SourceToken
40
+ /** A token with required RPC metadata and optional curated fields. */
41
+ export const Token = Schema.describe(
42
+ Tokens.schema.TokenReference,
43
+ 'A token referenced by an activity item.',
44
+ )
42
45
 
43
- /**
44
- * A token transfer as classified by the feed: the `/transfers` row shape
45
- * plus the feed-only, subject-relative fields (`direction`, `signer`) that
46
- * the unscoped resource cannot carry, with `memo`/`attribution` always
47
- * resolved (the feed already decodes the underlying events).
48
- */
46
+ /** A token amount carrying its nominal value in the requested denomination. */
47
+ const ValuedAmount = z.extend(Schema.TokenAmount, {
48
+ valuation: z
49
+ .optional(z.nullable(Valuation.schema.Value))
50
+ .check(
51
+ z.describe(
52
+ 'The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
53
+ 'unverified, its display currency has no rate, or rates were unavailable.',
54
+ ),
55
+ ),
56
+ })
57
+
58
+ /** Self-contained amount for a destination token. */
59
+ const DestinationAmount = ValuedAmount.check(
60
+ z.describe('Amount associated with `destinationToken`.'),
61
+ )
62
+
63
+ /** Self-contained amount for a refunded token. */
64
+ const RefundAmount = ValuedAmount.check(z.describe('Amount associated with `refund`.'))
65
+
66
+ /** Self-contained amount for a source token. */
67
+ const SourceAmount = ValuedAmount.check(z.describe('Amount associated with `sourceToken`.'))
68
+
69
+ /** Page-level resources: valuation rate provenance. */
70
+ export const Meta = z
71
+ .object({
72
+ pricing: z
73
+ .optional(Valuation.schema.Pricing)
74
+ .check(
75
+ z.describe(
76
+ 'Rate provenance for amount valuations. Present when conversion rates were ' +
77
+ 'consulted; absent when every value was identity-valued or rates were unavailable.',
78
+ ),
79
+ ),
80
+ })
81
+ .check(z.describe('Page-level resources attached to this response.'))
82
+
83
+ /** A token transfer with subject-relative direction and signer fields. */
49
84
  export const Transfer = z
50
85
  .object({
51
86
  attribution: z
@@ -64,11 +99,18 @@ export namespace schema {
64
99
  z.describe('Block number where this transfer was included.'),
65
100
  z.meta({ examples: [12345678] }),
66
101
  ),
102
+ destinationAmount: z
103
+ .optional(DestinationAmount)
104
+ .check(
105
+ z.describe(
106
+ 'Amount delivered to the recipient for a cross-token transfer. Omitted with `destinationToken` for a same-token transfer.',
107
+ ),
108
+ ),
67
109
  destinationToken: z
68
110
  .optional(Token)
69
111
  .check(
70
112
  z.describe(
71
- '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.',
113
+ 'Token delivered to the recipient when it differs from the source token. Omitted for a same-token transfer.',
72
114
  ),
73
115
  ),
74
116
  direction: z
@@ -90,7 +132,8 @@ export namespace schema {
90
132
  'For outbound transfers, who signed it: `self` or the access-key address. Omitted for inbound transfers.',
91
133
  ),
92
134
  ),
93
- sourceToken: Token.check(z.describe('Token and amount that moved in the transfer.')),
135
+ sourceAmount: SourceAmount,
136
+ sourceToken: Token.check(z.describe('Token that moved in the transfer.')),
94
137
  timestamp: z
95
138
  .iso.datetime()
96
139
  .check(z.describe('Block timestamp for the transfer.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })), // prettier-ignore
@@ -98,7 +141,16 @@ export namespace schema {
98
141
  z.describe('Transaction hash that contains this transfer.'),
99
142
  ),
100
143
  })
101
- .check(z.describe('One token transfer involving the requested address.'))
144
+ .check(
145
+ z.refine(
146
+ (data) => (data.destinationToken === undefined) === (data.destinationAmount === undefined),
147
+ {
148
+ error: '`destinationAmount` and `destinationToken` must be returned together.',
149
+ path: ['destinationAmount'],
150
+ },
151
+ ),
152
+ z.describe('One token transfer involving the requested address.'),
153
+ )
102
154
 
103
155
  /**
104
156
  * One transaction log, decoded into a known Tempo event when possible. The
@@ -193,9 +245,7 @@ export namespace schema {
193
245
  .object({
194
246
  ...base,
195
247
  data: Transfer.check(
196
- z.describe(
197
- 'Transfer details, using the `/transfers` shape plus feed-only fields such as direction and signer.',
198
- ),
248
+ z.describe('Transfer details with feed-relative direction and signer fields.'),
199
249
  ),
200
250
  type: z
201
251
  .literal('transfer')
@@ -208,7 +258,8 @@ export namespace schema {
208
258
  data: z
209
259
  .object({
210
260
  recipient: Schema.Address.check(z.describe('Address that received the transfer.')),
211
- sourceToken: Token.check(z.describe('Token and amount that were minted.')),
261
+ sourceAmount: SourceAmount,
262
+ sourceToken: Token.check(z.describe('Token that was minted.')),
212
263
  })
213
264
  .check(z.describe('A token mint activity.')),
214
265
  type: z
@@ -223,7 +274,8 @@ export namespace schema {
223
274
  .object({
224
275
  sender: Schema.Address.check(z.describe('Address whose token balance was burned.')),
225
276
  signer: Signer,
226
- sourceToken: Token.check(z.describe('Token and amount that were burned.')),
277
+ sourceAmount: SourceAmount,
278
+ sourceToken: Token.check(z.describe('Token that was burned.')),
227
279
  })
228
280
  .check(z.describe('A token burn activity.')),
229
281
  type: z
@@ -236,8 +288,10 @@ export namespace schema {
236
288
  ...base,
237
289
  data: z
238
290
  .object({
291
+ destinationAmount: DestinationAmount,
239
292
  destinationToken: Token.check(z.describe('Token the address received in the swap.')),
240
293
  signer: Signer,
294
+ sourceAmount: SourceAmount,
241
295
  sourceToken: Token.check(z.describe('Token the address sent into the swap.')),
242
296
  })
243
297
  .check(
@@ -256,9 +310,8 @@ export namespace schema {
256
310
  data: z
257
311
  .object({
258
312
  signer: Signer,
259
- sourceToken: Token.check(
260
- z.describe('Token and allowance amount approved for spending.'),
261
- ),
313
+ sourceAmount: SourceAmount,
314
+ sourceToken: Token.check(z.describe('Token approved for spending.')),
262
315
  spender: Schema.Address.check(
263
316
  z.describe('Address allowed to spend the approved amount.'),
264
317
  ),
@@ -277,9 +330,18 @@ export namespace schema {
277
330
  refund: z
278
331
  .optional(Token)
279
332
  .check(z.describe('Token refunded to the address when the session closed, if any.')),
333
+ refundAmount: z
334
+ .optional(RefundAmount)
335
+ .check(z.describe('Amount refunded to the address when the session closed, if any.')),
280
336
  signer: Signer,
281
337
  })
282
- .check(z.describe('A payment-channel session close activity.')),
338
+ .check(
339
+ z.refine((data) => (data.refund === undefined) === (data.refundAmount === undefined), {
340
+ error: '`refundAmount` and `refund` must be returned together.',
341
+ path: ['refundAmount'],
342
+ }),
343
+ z.describe('A payment-channel session close activity.'),
344
+ ),
283
345
  type: z
284
346
  .literal('session-closed')
285
347
  .check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
@@ -354,7 +416,8 @@ export namespace schema {
354
416
  z.describe('Address whose blocked balance was burned by policy.'),
355
417
  ),
356
418
  signer: Signer,
357
- sourceToken: Token.check(z.describe('Token and amount that were burned.')),
419
+ sourceAmount: SourceAmount,
420
+ sourceToken: Token.check(z.describe('Token that was burned.')),
358
421
  })
359
422
  .check(
360
423
  z.describe(
@@ -377,9 +440,8 @@ export namespace schema {
377
440
  ),
378
441
  payer: Schema.Address.check(z.describe('Address that funds the channel.')),
379
442
  signer: Signer,
380
- sourceToken: Token.check(
381
- z.describe('Token and deposit amount locked into the channel.'),
382
- ),
443
+ sourceAmount: SourceAmount,
444
+ sourceToken: Token.check(z.describe('Token locked into the channel.')),
383
445
  })
384
446
  .check(z.describe('A payment channel was opened and funded.')),
385
447
  type: z
@@ -394,7 +456,8 @@ export namespace schema {
394
456
  .object({
395
457
  channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
396
458
  signer: Signer,
397
- sourceToken: Token.check(z.describe('Token and amount added to the channel deposit.')),
459
+ sourceAmount: SourceAmount,
460
+ sourceToken: Token.check(z.describe('Token added to the channel deposit.')),
398
461
  })
399
462
  .check(z.describe('A payment channel deposit was topped up.')),
400
463
  type: z
@@ -411,7 +474,8 @@ export namespace schema {
411
474
  payee: Schema.Address.check(z.describe('Address that received the settled payment.')),
412
475
  payer: Schema.Address.check(z.describe('Address that funded the channel.')),
413
476
  signer: Signer,
414
- sourceToken: Token.check(z.describe('Token and amount paid out in this settlement.')),
477
+ sourceAmount: SourceAmount,
478
+ sourceToken: Token.check(z.describe('Token paid out in this settlement.')),
415
479
  })
416
480
  .check(z.describe('An incremental payment-channel settlement to the payee.')),
417
481
  type: z
@@ -431,13 +495,30 @@ export namespace schema {
431
495
  payer: Schema.Address.check(z.describe('Address that funded the channel.')),
432
496
  refund: z
433
497
  .optional(Token)
434
- .check(z.describe('Token and amount refunded to the payer on close, if any.')),
498
+ .check(z.describe('Token refunded to the payer on close, if any.')),
499
+ refundAmount: z
500
+ .optional(RefundAmount)
501
+ .check(z.describe('Amount refunded to the payer on close, if any.')),
435
502
  signer: Signer,
503
+ sourceAmount: z
504
+ .optional(SourceAmount)
505
+ .check(z.describe('Final amount paid to the payee on close, if any.')),
436
506
  sourceToken: z
437
507
  .optional(Token)
438
- .check(z.describe('Token and final amount paid out to the payee on close, if any.')),
508
+ .check(z.describe('Token paid out to the payee on close, if any.')),
439
509
  })
440
510
  .check(
511
+ z.refine((data) => (data.refund === undefined) === (data.refundAmount === undefined), {
512
+ error: '`refundAmount` and `refund` must be returned together.',
513
+ path: ['refundAmount'],
514
+ }),
515
+ z.refine(
516
+ (data) => (data.sourceToken === undefined) === (data.sourceAmount === undefined),
517
+ {
518
+ error: '`sourceAmount` and `sourceToken` must be returned together.',
519
+ path: ['sourceAmount'],
520
+ },
521
+ ),
441
522
  z.describe(
442
523
  'A payment channel was closed, paying out the payee and refunding the payer.',
443
524
  ),
@@ -475,7 +556,8 @@ export namespace schema {
475
556
  .enum(['bid', 'ask'])
476
557
  .check(z.describe('Order side: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
477
558
  signer: Signer,
478
- sourceToken: Token.check(z.describe('Token and amount escrowed for the order.')),
559
+ sourceAmount: SourceAmount,
560
+ sourceToken: Token.check(z.describe('Token escrowed for the order.')),
479
561
  tick: z
480
562
  .number()
481
563
  .check(
@@ -512,7 +594,8 @@ export namespace schema {
512
594
  data: z
513
595
  .object({
514
596
  signer: Signer,
515
- sourceToken: Token.check(z.describe('Token and amount of fees distributed.')),
597
+ sourceAmount: SourceAmount,
598
+ sourceToken: Token.check(z.describe('Token used for the distributed fees.')),
516
599
  validator: Schema.Address.check(
517
600
  z.describe('Validator that received the distributed fees.'),
518
601
  ),
@@ -528,11 +611,11 @@ export namespace schema {
528
611
  ...base,
529
612
  data: z
530
613
  .object({
531
- destinationToken: Token.check(
532
- z.describe('Token and amount received from the fee AMM.'),
533
- ),
614
+ destinationAmount: DestinationAmount,
615
+ destinationToken: Token.check(z.describe('Token received from the fee AMM.')),
534
616
  signer: Signer,
535
- sourceToken: Token.check(z.describe('Token and amount sent into the fee AMM.')),
617
+ sourceAmount: SourceAmount,
618
+ sourceToken: Token.check(z.describe('Token sent into the fee AMM.')),
536
619
  swapper: Schema.Address.check(z.describe('Address that performed the rebalance swap.')),
537
620
  })
538
621
  .check(z.describe('A fee-AMM rebalance swap between a validator and user token.')),
@@ -548,7 +631,8 @@ export namespace schema {
548
631
  .object({
549
632
  funder: Schema.Address.check(z.describe('Address that funded the reward pool.')),
550
633
  signer: Signer,
551
- sourceToken: Token.check(z.describe('Token and amount added to the reward pool.')),
634
+ sourceAmount: SourceAmount,
635
+ sourceToken: Token.check(z.describe('Token added to the reward pool.')),
552
636
  })
553
637
  .check(z.describe('A token reward pool was funded.')),
554
638
  type: z
@@ -583,9 +667,8 @@ export namespace schema {
583
667
  z.describe('Public key of the access key whose limit changed.'),
584
668
  ),
585
669
  signer: Signer,
586
- sourceToken: Token.check(
587
- z.describe('Token and new spending limit set for the access key.'),
588
- ),
670
+ sourceAmount: SourceAmount,
671
+ sourceToken: Token.check(z.describe('Token whose spending limit was updated.')),
589
672
  })
590
673
  .check(z.describe('An access key’s per-token spending limit was updated.')),
591
674
  type: z
@@ -636,7 +719,8 @@ export namespace schema {
636
719
  .enum(['bid', 'ask'])
637
720
  .check(z.describe('Side of the flipped order: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
638
721
  signer: Signer,
639
- sourceToken: Token.check(z.describe('Token and amount the flipped order rests with.')),
722
+ sourceAmount: SourceAmount,
723
+ sourceToken: Token.check(z.describe('Token the flipped order rests with.')),
640
724
  tick: z
641
725
  .number()
642
726
  .check(
@@ -1292,6 +1376,17 @@ export namespace schema {
1292
1376
  .union([...items, Group])
1293
1377
  .check(z.describe('Either one activity item, or a grouped set of access-key activity items.'))
1294
1378
 
1379
+ /** Optional token fields that require sources beyond RPC metadata. */
1380
+ export const Include = z
1381
+ .enum(tokenFields)
1382
+ .check(z.describe('Additional token fields to include in activity responses.'))
1383
+
1384
+ /** Parses comma-separated optional token fields. */
1385
+ export const includeQuery = Schema.includeQuery(
1386
+ Include,
1387
+ 'Comma-separated token fields to include, such as `token.logoUri,token.verified`.',
1388
+ )
1389
+
1295
1390
  /** Schemas for the getAddressActivities operation. */
1296
1391
  export namespace getAddressActivities {
1297
1392
  /** Path parameters for address activity requests. */
@@ -1308,6 +1403,7 @@ export namespace schema {
1308
1403
  .strictObject({
1309
1404
  chainId: Schema.ChainIdQuery,
1310
1405
  cursor: Schema.Cursor,
1406
+ denomination: Schema.Denomination,
1311
1407
  // Folding is opt-in: by default every item is returned at the root.
1312
1408
  group: z
1313
1409
  ._default(Schema.booleanQuery(), false)
@@ -1317,6 +1413,7 @@ export namespace schema {
1317
1413
  ),
1318
1414
  z.meta({ examples: [true] }),
1319
1415
  ),
1416
+ include: includeQuery,
1320
1417
  limit: Schema.Limit,
1321
1418
  // Raw-log capture is opt-in: it can add a large `events` array to every
1322
1419
  // item, so the default feed stays lean.
@@ -1344,6 +1441,9 @@ export namespace schema {
1344
1441
  export const Response = z
1345
1442
  .object({
1346
1443
  data: z.array(Entry).check(z.describe('Activity feed entries for the address.')),
1444
+ meta: z
1445
+ .optional(schema.Meta)
1446
+ .check(z.describe('Page-level resources, such as valuation rate provenance.')),
1347
1447
  nextCursor: Schema.NextCursor,
1348
1448
  })
1349
1449
  .check(z.describe('A page of human-readable activity for the address.'))
@@ -1366,6 +1466,8 @@ export namespace schema {
1366
1466
  export const Query = z
1367
1467
  .strictObject({
1368
1468
  chainId: Schema.ChainIdQuery,
1469
+ denomination: Schema.Denomination,
1470
+ include: includeQuery,
1369
1471
  // Raw-log capture is opt-in: it can add a large `events` array to every
1370
1472
  // item, so the default response stays lean.
1371
1473
  logs: z
@@ -1388,12 +1490,15 @@ export namespace schema {
1388
1490
  export const Response = z
1389
1491
  .object({
1390
1492
  data: z.array(Item).check(z.describe('Activity items classified from the transaction.')),
1493
+ meta: z
1494
+ .optional(schema.Meta)
1495
+ .check(z.describe('Page-level resources, such as valuation rate provenance.')),
1391
1496
  })
1392
1497
  .check(z.describe('The human-readable activity that happened on a transaction.'))
1393
1498
  }
1394
1499
  }
1395
1500
 
1396
- /** A token and amount in the public resource shape (derived from {@link schema.Token}). */
1501
+ /** A token in the public resource shape (derived from {@link schema.Token}). */
1397
1502
  export type Token = z.output<typeof schema.Token>
1398
1503
 
1399
1504
  /** A single classified activity item (derived from {@link schema.Item}). */
@@ -1402,14 +1507,36 @@ export type Item = z.output<typeof schema.Item>
1402
1507
  /** A single activity item, or a folded access-key group (derived from {@link schema.Entry}). */
1403
1508
  export type Entry = z.output<typeof schema.Entry>
1404
1509
 
1510
+ type AmountKey = 'destinationAmount' | 'refundAmount' | 'sourceAmount'
1511
+ type TokenKey = 'destinationToken' | 'refund' | 'sourceToken'
1512
+
1513
+ /** Token fields available before RPC metadata resolution. */
1514
+ type UnenrichedToken = {
1515
+ address: Token['address']
1516
+ amount: z.output<typeof Schema.DecimalString>
1517
+ }
1518
+
1519
+ type UnenrichedData<data> = {
1520
+ [key in keyof data as key extends AmountKey ? never : key]: key extends TokenKey
1521
+ ? UnenrichedToken | Extract<data[key], undefined>
1522
+ : data[key]
1523
+ }
1524
+
1525
+ /** A classified item whose token stubs have not been enriched with structured amounts. */
1526
+ type UnenrichedItem = {
1527
+ [type in Item['type']]: Omit<Extract<Item, { type: type }>, 'data'> & {
1528
+ data: UnenrichedData<Extract<Item, { type: type }>['data']>
1529
+ }
1530
+ }[Item['type']]
1531
+
1405
1532
  /**
1406
1533
  * A classified item before {@link list} stamps its display {@link Item.title}.
1407
1534
  * Internal classifiers build these; `list` derives `title` from `type` via
1408
1535
  * {@link titleByType}, so each branch never repeats the human label.
1409
1536
  */
1410
1537
  type RawItem = {
1411
- [type in Item['type']]: Omit<Extract<Item, { type: type }>, 'title'>
1412
- }[Item['type']]
1538
+ [type in UnenrichedItem['type']]: Omit<Extract<UnenrichedItem, { type: type }>, 'title'>
1539
+ }[UnenrichedItem['type']]
1413
1540
 
1414
1541
  /**
1415
1542
  * Human-readable label for each activity {@link Item.type}, mirroring the per-variant
@@ -1572,6 +1699,12 @@ const maxGroupSize = 50
1572
1699
  /** Token-bearing keys on an item's `data` payload, walked for enrichment. */
1573
1700
  const tokenKeys = ['destinationToken', 'refund', 'sourceToken'] as const
1574
1701
 
1702
+ const amountKeyByTokenKey = {
1703
+ destinationToken: 'destinationAmount',
1704
+ refund: 'refundAmount',
1705
+ sourceToken: 'sourceAmount',
1706
+ } as const
1707
+
1575
1708
  /** The base fields ({@link schema.base}) every activity item carries. */
1576
1709
  type Base = {
1577
1710
  id: string
@@ -1665,7 +1798,8 @@ type TxGroup = {
1665
1798
  * consecutive access-key-signed items into `group` entries. Pagination uses the
1666
1799
  * same `(block_num, tx_idx)` keyset cursor the pipeline emits.
1667
1800
  */
1668
- export function addresses() {
1801
+ export function addresses(options: addresses.Options = {}) {
1802
+ const oracle = options.fx?.oracle ?? FxOracle.ecb()
1669
1803
  return new Hono<App.Environment>().get(
1670
1804
  '/v1/addresses/:address/activities',
1671
1805
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
@@ -1682,7 +1816,7 @@ export function addresses() {
1682
1816
  'Get a human-readable feed of what an address has been doing onchain, including payments sent and received, swaps, approvals, and more.',
1683
1817
  operationId: 'getAddressActivities',
1684
1818
  responses: OpenApi.responses({
1685
- errors: { 502: 'Tempo could not read activity data from the upstream indexer.' },
1819
+ errors: { 502: 'Tempo could not read activity or token data from an upstream service.' },
1686
1820
  success: {
1687
1821
  description: 'A page of address activity entries.',
1688
1822
  schema: schema.getAddressActivities.Response,
@@ -1712,38 +1846,42 @@ export function addresses() {
1712
1846
  const { address } = c.req.valid('param')
1713
1847
  const query = c.req.valid('query')
1714
1848
 
1715
- // Cursor pages are anchored below the head and effectively immutable, so
1716
- // cache them aggressively; the head page keeps the route's short `feed`
1717
- // default.
1718
- if (query.cursor !== undefined) Cache.setPolicy(c, Cache.policies.immutable)
1849
+ // Cursor pages carry current valuations and curated verification data.
1850
+ if (query.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
1719
1851
 
1720
1852
  try {
1721
1853
  const chainId = query.chainId ?? c.get('chainId')
1722
1854
  const provider = tidx({ chainId, tidx: c.get('getTidx')(chainId) })
1723
- const { items, nextCursor } = await Timing.time(c, 'address_activities', () =>
1724
- Store.memoize(
1725
- () =>
1726
- list(provider, {
1727
- address: address as Hex.Hex,
1728
- cursor: query.cursor,
1729
- group: query.group,
1730
- limit: query.limit,
1731
- logs: query.logs,
1855
+ const { items: unenrichedItems, nextCursor } = await Timing.time(
1856
+ c,
1857
+ 'address_activities',
1858
+ () =>
1859
+ Store.memoize(
1860
+ () =>
1861
+ list(provider, {
1862
+ address: address as Hex.Hex,
1863
+ cursor: query.cursor,
1864
+ group: query.group,
1865
+ limit: query.limit,
1866
+ logs: query.logs,
1867
+ store: c.get('store'),
1868
+ }),
1869
+ {
1870
+ key: `activities:v1:${chainId}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
1732
1871
  store: c.get('store'),
1733
- }),
1734
- {
1735
- key: `activities:v1:${chainId}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
1736
- store: c.get('store'),
1737
- ttl: Ttl.seconds(15),
1738
- },
1739
- ),
1872
+ ttl: Ttl.seconds(15),
1873
+ },
1874
+ ),
1740
1875
  )
1741
1876
 
1742
- // Resolve token metadata for every token referenced across the page and
1743
- // spread it into each item's stub tokens. Goes through the shared
1744
- // `Tokens.getTokenMetadata` path so it reuses the `token:v1:…` cache with
1745
- // `/tokens` and `/transfers` instead of a parallel metadata fetch.
1746
- await enrichTokens(c, { chainId, items })
1877
+ // Build immutable token and structured amount copies from RPC metadata.
1878
+ const { items, rates } = await enrichTokens(c, {
1879
+ chainId,
1880
+ denomination: query.denomination ?? 'USD',
1881
+ include: query.include,
1882
+ items: unenrichedItems,
1883
+ oracle,
1884
+ })
1747
1885
 
1748
1886
  // Fold consecutive access-key-signed items into `group` entries only
1749
1887
  // when requested; otherwise every item is returned at the root.
@@ -1752,17 +1890,28 @@ export function addresses() {
1752
1890
  return c.json(
1753
1891
  Response.validated(schema.getAddressActivities.Response, {
1754
1892
  data,
1893
+ ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
1755
1894
  nextCursor,
1756
1895
  }),
1757
1896
  200,
1758
1897
  )
1759
1898
  } catch (cause) {
1899
+ if (cause instanceof Valuation.UnsupportedDenominationError)
1900
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
1760
1901
  return Response.upstream(c, cause)
1761
1902
  }
1762
1903
  },
1763
1904
  )
1764
1905
  }
1765
1906
 
1907
+ export declare namespace addresses {
1908
+ /** Options for the address-scoped activity handlers. */
1909
+ type Options = {
1910
+ /** FX configuration backing amount valuation. */
1911
+ fx?: Valuation.addresses.Fx | undefined
1912
+ }
1913
+ }
1914
+
1766
1915
  /**
1767
1916
  * Creates transaction-scoped activity handlers, mounted under the
1768
1917
  * `/transactions` composer. Exposes `GET /:transactionHash/activities`.
@@ -1773,7 +1922,8 @@ export function addresses() {
1773
1922
  * classified item(s) — usually one headline activity — with the full per-tx
1774
1923
  * event set available via `logs=true`.
1775
1924
  */
1776
- export function transactions() {
1925
+ export function transactions(options: transactions.Options = {}) {
1926
+ const oracle = options.fx?.oracle ?? FxOracle.ecb()
1777
1927
  return new Hono<App.Environment>().get(
1778
1928
  '/v1/transactions/:transactionHash/activities',
1779
1929
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
@@ -1795,7 +1945,7 @@ export function transactions() {
1795
1945
  description: 'No mined transaction was found for that hash.',
1796
1946
  codes: ['transaction_not_found'],
1797
1947
  },
1798
- 502: 'Tempo could not read activity data from the upstream indexer.',
1948
+ 502: 'Tempo could not read activity or token data from an upstream service.',
1799
1949
  },
1800
1950
  success: {
1801
1951
  description: 'The classified activity for the transaction.',
@@ -1806,10 +1956,8 @@ export function transactions() {
1806
1956
  tags: ['Activities'],
1807
1957
  }),
1808
1958
  Cache.response<App.Environment>({
1809
- // Activity exists only once the transaction is mined; once mined it is
1810
- // immutable. Default to no-store so a 404 (unknown / not-yet-mined hash)
1811
- // is not cached with a long header that would mask the eventual result;
1812
- // the handler upgrades the 200 to `immutable` below.
1959
+ // Default to no-store so a 404 does not mask the eventual mined result.
1960
+ // Successful responses use the metadata policy for current valuations.
1813
1961
  cacheControl: Cache.policies.noStore,
1814
1962
  name: 'tempo-api:transactions:v1',
1815
1963
  key: (c) => Cache.urlKey(c, schema.getTransactionActivities.Query),
@@ -1832,7 +1980,7 @@ export function transactions() {
1832
1980
  const chainId = query.chainId ?? c.get('chainId')
1833
1981
 
1834
1982
  try {
1835
- const items = await Timing.time(c, 'transaction_activities', () =>
1983
+ const unenrichedItems = await Timing.time(c, 'transaction_activities', () =>
1836
1984
  Store.memoize(
1837
1985
  () =>
1838
1986
  getTransactionActivities({
@@ -1850,31 +1998,49 @@ export function transactions() {
1850
1998
  ),
1851
1999
  )
1852
2000
 
1853
- if (items === null)
2001
+ if (unenrichedItems === null)
1854
2002
  return Response.error(c, {
1855
2003
  code: 'transaction_not_found',
1856
2004
  message: 'Transaction not found',
1857
2005
  status: 404,
1858
2006
  })
1859
2007
 
1860
- // A mined transaction's activity is final, so cache it aggressively.
1861
- Cache.setPolicy(c, Cache.policies.immutable)
2008
+ // Mined activity still carries current valuations and verification data.
2009
+ Cache.setPolicy(c, Cache.policies.metadata)
1862
2010
 
1863
- // Resolve token metadata for every token referenced and spread it into
1864
- // each item's stub tokens (shared `token:v1:…` cache path).
1865
- await enrichTokens(c, { chainId, items })
2011
+ // Build immutable token and structured amount copies from RPC metadata.
2012
+ const { items, rates } = await enrichTokens(c, {
2013
+ chainId,
2014
+ denomination: query.denomination ?? 'USD',
2015
+ include: query.include,
2016
+ items: unenrichedItems,
2017
+ oracle,
2018
+ })
1866
2019
 
1867
2020
  return c.json(
1868
- Response.validated(schema.getTransactionActivities.Response, { data: items }),
2021
+ Response.validated(schema.getTransactionActivities.Response, {
2022
+ data: items,
2023
+ ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
2024
+ }),
1869
2025
  200,
1870
2026
  )
1871
2027
  } catch (cause) {
2028
+ if (cause instanceof Valuation.UnsupportedDenominationError)
2029
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
1872
2030
  return Response.upstream(c, cause)
1873
2031
  }
1874
2032
  },
1875
2033
  )
1876
2034
  }
1877
2035
 
2036
+ export declare namespace transactions {
2037
+ /** Options for the transaction-scoped activity handlers. */
2038
+ type Options = {
2039
+ /** FX configuration backing amount valuation. */
2040
+ fx?: Valuation.addresses.Fx | undefined
2041
+ }
2042
+ }
2043
+
1878
2044
  /**
1879
2045
  * Classifies the activity for a single transaction. Fetches the transaction's
1880
2046
  * receipt (for its block position, sender, and fee payer) and its logs, then
@@ -1887,7 +2053,7 @@ export function transactions() {
1887
2053
  */
1888
2054
  export async function getTransactionActivities(
1889
2055
  options: getTransactionActivities.Options,
1890
- ): Promise<Item[] | null> {
2056
+ ): Promise<UnenrichedItem[] | null> {
1891
2057
  const { chainId, logs = false, tidx: tidxClient } = options
1892
2058
  const hash = options.transactionHash.toLowerCase()
1893
2059
 
@@ -1979,7 +2145,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
1979
2145
  const fingerprintMap = await Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
1980
2146
 
1981
2147
  let currentCursor: string | undefined = cursor
1982
- const allItems: Item[] = []
2148
+ const allItems: UnenrichedItem[] = []
1983
2149
  let hasMore = false
1984
2150
  let lastGroup: TxGroup | undefined
1985
2151
 
@@ -2005,7 +2171,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
2005
2171
  lastGroup = page[page.length - 1]
2006
2172
 
2007
2173
  const enough = group
2008
- ? groupByAccessKey(allItems).length >= minGroupEntries
2174
+ ? groupByAccessKeyItems(allItems).length >= minGroupEntries
2009
2175
  : allItems.length >= limit
2010
2176
  if (enough || !hasMore) break
2011
2177
 
@@ -2015,8 +2181,8 @@ export async function list(provider: Provider, options: list.Options): Promise<l
2015
2181
  let nextCursor: string | null = null
2016
2182
  if (hasMore && lastGroup) nextCursor = provider.cursorForGroup(lastGroup)
2017
2183
 
2018
- // Items carry stub tokens (`address`/`amount`); the caller enriches them with
2019
- // metadata via {@link enrichTokens}.
2184
+ // Items carry private `address`/`amount` stubs; the caller splits them into
2185
+ // public token references and structured amounts via {@link enrichTokens}.
2020
2186
  return { items: allItems, nextCursor }
2021
2187
  }
2022
2188
 
@@ -2038,7 +2204,7 @@ export declare namespace list {
2038
2204
  }
2039
2205
  /** Result of {@link list}. */
2040
2206
  type ReturnType = {
2041
- items: Item[]
2207
+ items: UnenrichedItem[]
2042
2208
  nextCursor: string | null
2043
2209
  }
2044
2210
  }
@@ -3661,13 +3827,13 @@ function transfer(
3661
3827
  blockNumber: number,
3662
3828
  data: {
3663
3829
  attribution?: string | undefined
3664
- destinationToken?: Token | undefined
3830
+ destinationToken?: UnenrichedToken | undefined
3665
3831
  direction: 'in' | 'out'
3666
3832
  memo?: string | undefined
3667
3833
  recipient: Address.Address
3668
3834
  sender: Address.Address
3669
3835
  signer?: 'self' | Address.Address | undefined
3670
- sourceToken: Token
3836
+ sourceToken: UnenrichedToken
3671
3837
  },
3672
3838
  ): Extract<RawItem, { type: 'transfer' }> {
3673
3839
  return {
@@ -3792,75 +3958,154 @@ function lowerOrNull(value: unknown): Address.Address | null {
3792
3958
  return text ? (text.toLowerCase() as Address.Address) : null
3793
3959
  }
3794
3960
 
3795
- /** Builds a stub token; `decimals`/`symbol` are filled by {@link enrichItem}. */
3796
- function token(amount: bigint | undefined, address: Address.Address): Token {
3961
+ /** Builds a token stub whose metadata is filled by {@link enrichItem}. */
3962
+ function token(amount: bigint | undefined, address: Address.Address): UnenrichedToken {
3797
3963
  return { address: address.toLowerCase() as Address.Address, amount: (amount ?? 0n).toString() }
3798
3964
  }
3799
3965
 
3800
3966
  /** Collects every token address referenced by an item's `data`. */
3801
- function collectTokens(item: Item, set: Set<Address.Address>) {
3802
- const data = item.data as Partial<Record<(typeof tokenKeys)[number], Token>>
3967
+ function collectTokens(item: UnenrichedItem, set: Set<Address.Address>) {
3968
+ const data = item.data as Partial<Record<(typeof tokenKeys)[number], UnenrichedToken>>
3803
3969
  for (const key of tokenKeys) {
3804
3970
  const t = data[key]
3805
3971
  if (t) set.add(t.address.toLowerCase() as Address.Address)
3806
3972
  }
3807
3973
  }
3808
3974
 
3809
- /** Resolved token metadata keyed by lowercased address. */
3810
- type ResolvedToken = Awaited<ReturnType<typeof Tokens.getTokenMetadata>>
3975
+ /** Resolved token fields keyed by lowercased address. */
3976
+ type ResolvedToken = {
3977
+ currency: string
3978
+ decimals: number
3979
+ logoUri?: string | undefined
3980
+ name: string
3981
+ symbol: string
3982
+ verified?: boolean | undefined
3983
+ }
3811
3984
 
3812
- /**
3813
- * Resolves metadata for every token referenced across a page of items and
3814
- * spreads it into each item's stub tokens, in place. Routes through the shared
3815
- * {@link Tokens.getTokenMetadata} (one `token.getMetadata` RPC per token,
3816
- * memoized under the `token:v1:…` key shared with `/tokens` and `/transfers`)
3817
- * instead of a parallel metadata fetch. Resolution is best-effort: a token
3818
- * whose metadata is unavailable keeps just `address`/`amount`.
3819
- */
3820
- async function enrichTokens(c: Context<App.Environment>, options: enrichTokens.Options) {
3821
- const { chainId, items } = options
3985
+ /** Resolves core metadata into immutable items. Optional logo and verification sources are best-effort. */
3986
+ async function enrichTokens(
3987
+ c: Context<App.Environment>,
3988
+ options: enrichTokens.Options,
3989
+ ): Promise<{ items: Item[]; rates: FxOracle.RateSet | undefined }> {
3990
+ const { chainId, denomination, include, items, oracle } = options
3822
3991
  const addresses = new Set<Address.Address>()
3823
3992
  for (const item of items) collectTokens(item, addresses)
3824
- if (addresses.size === 0) return
3993
+ // Valuation keys off the curated snapshot; a snapshot failure degrades the
3994
+ // values to `null` rather than failing the feed.
3995
+ const snapshot =
3996
+ addresses.size > 0
3997
+ ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
3998
+ : undefined
3999
+ const rates = await Valuation.ratesFor(c, {
4000
+ currencies: snapshot
4001
+ ? [...addresses].flatMap((address) => {
4002
+ const held = snapshot.byAddress.get(address)?.currency
4003
+ return held === undefined ? [] : [held]
4004
+ })
4005
+ : [],
4006
+ denomination,
4007
+ oracle,
4008
+ })
4009
+ if (addresses.size === 0) return { items: items as Item[], rates }
4010
+
4011
+ const includeLogoUri = include.includes('token.logoUri')
4012
+ const includeVerified = include.includes('token.verified')
3825
4013
 
3826
4014
  const resolved = await Timing.time(c, 'tokens', () =>
3827
4015
  Promise.all(
3828
- [...addresses].map((address) =>
3829
- Tokens.getTokenMetadata(c, { address, chainId })
3830
- .then((metadata) => [address.toLowerCase(), metadata] as const)
3831
- .catch(() => [address.toLowerCase(), undefined] as const),
3832
- ),
4016
+ [...addresses].map(async (address) => {
4017
+ const [logoUri, metadata] = await Promise.all([
4018
+ includeLogoUri
4019
+ ? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
4020
+ : undefined,
4021
+ Tokens.getTokenMetadata(c, { address, chainId }),
4022
+ ])
4023
+ return [
4024
+ address.toLowerCase(),
4025
+ {
4026
+ currency: metadata.currency,
4027
+ decimals: metadata.decimals,
4028
+ logoUri: includeLogoUri
4029
+ ? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
4030
+ : undefined,
4031
+ name: metadata.name,
4032
+ symbol: metadata.symbol,
4033
+ verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
4034
+ },
4035
+ ] as const
4036
+ }),
3833
4037
  ),
3834
4038
  )
3835
4039
  const byAddress = new Map(resolved)
3836
- for (const item of items) enrichItem(item, byAddress)
4040
+ return {
4041
+ items: items.map((item) => enrichItem(item, byAddress, { denomination, rates, snapshot })),
4042
+ rates,
4043
+ }
3837
4044
  }
3838
4045
 
3839
4046
  declare namespace enrichTokens {
3840
4047
  type Options = {
3841
4048
  chainId: z.output<typeof Schema.ChainId>
3842
- items: readonly Item[]
4049
+ /** Currency the amount valuations are denominated in (uppercase). */
4050
+ denomination: string
4051
+ include: readonly z.output<typeof schema.Include>[]
4052
+ items: readonly UnenrichedItem[]
4053
+ /** FX rate oracle backing amount valuation. */
4054
+ oracle: FxOracle.Oracle
3843
4055
  }
3844
4056
  }
3845
4057
 
3846
- /** Spreads resolved token metadata into an item's stub tokens, in place. */
3847
- function enrichItem(item: Item, byAddress: Map<string, ResolvedToken | undefined>) {
3848
- const data = item.data as Partial<Record<(typeof tokenKeys)[number], Token>>
4058
+ /** Returns an enriched copy of an item without mutating its cached token stubs. */
4059
+ function enrichItem(
4060
+ item: UnenrichedItem,
4061
+ byAddress: Map<string, ResolvedToken>,
4062
+ valuation: enrichItem.Valuation,
4063
+ ): Item {
4064
+ const source = item.data as Partial<Record<(typeof tokenKeys)[number], UnenrichedToken>>
4065
+ const data: Record<string, unknown> = { ...item.data }
3849
4066
  for (const key of tokenKeys) {
3850
- const t = data[key]
4067
+ const t = source[key]
3851
4068
  if (!t) continue
3852
4069
  const meta = byAddress.get(t.address.toLowerCase())
3853
- if (!meta) continue
3854
- // Carry only the per-row token fields the `Token` (SourceToken) shape
3855
- // declares; `address`/`amount` come from the stub.
4070
+ if (!meta) throw new Error(`Token metadata unavailable for ${t.address}`)
4071
+
4072
+ data[amountKeyByTokenKey[key]] = {
4073
+ ...Value.tokenAmount({
4074
+ baseUnits: t.amount,
4075
+ currency: meta.currency,
4076
+ decimals: meta.decimals,
4077
+ }),
4078
+ // Valued via the curated display currency, never the on-chain metadata.
4079
+ valuation: Valuation.valuationFor({
4080
+ amount: BigInt(t.amount),
4081
+ denomination: valuation.denomination,
4082
+ rates: valuation.rates,
4083
+ token: valuation.snapshot?.byAddress.get(t.address.toLowerCase()),
4084
+ }),
4085
+ }
3856
4086
  data[key] = {
3857
- ...t,
4087
+ address: t.address,
3858
4088
  currency: meta.currency,
3859
4089
  decimals: meta.decimals,
4090
+ ...(meta.logoUri !== undefined ? { logoUri: meta.logoUri } : {}),
3860
4091
  name: meta.name,
3861
4092
  symbol: meta.symbol,
4093
+ ...(meta.verified !== undefined ? { verified: meta.verified } : {}),
3862
4094
  }
3863
4095
  }
4096
+ return { ...item, data } as Item
4097
+ }
4098
+
4099
+ declare namespace enrichItem {
4100
+ /** Valuation inputs threaded from {@link enrichTokens}. */
4101
+ type Valuation = {
4102
+ /** Currency the amount valuations are denominated in (uppercase). */
4103
+ denomination: string
4104
+ /** Oracle rate set, when loaded. */
4105
+ rates: FxOracle.RateSet | undefined
4106
+ /** Compiled verified-token snapshot, when available. */
4107
+ snapshot: VerifiedTokens.Snapshot | undefined
4108
+ }
3864
4109
  }
3865
4110
 
3866
4111
  /**
@@ -3869,8 +4114,31 @@ function enrichItem(item: Item, byAddress: Map<string, ResolvedToken | undefined
3869
4114
  * Must be called once over the full, timestamp-sorted item list.
3870
4115
  */
3871
4116
  export function groupByAccessKey(items: Item[]): Entry[] {
3872
- const entries: Entry[] = []
3873
- let currentGroup: { items: Item[]; signer: Address.Address } | null = null
4117
+ return groupByAccessKeyItems(items)
4118
+ }
4119
+
4120
+ type GroupableItem = {
4121
+ data: object
4122
+ id: string
4123
+ perspective: 'incoming' | 'outgoing'
4124
+ timestamp: string
4125
+ }
4126
+
4127
+ type GroupedItem<item extends GroupableItem> =
4128
+ | item
4129
+ | {
4130
+ data: { items: item[]; signer: Address.Address }
4131
+ id: string
4132
+ perspective: 'incoming' | 'outgoing'
4133
+ timestamp: string
4134
+ title: 'Group'
4135
+ type: 'group'
4136
+ }
4137
+
4138
+ /** Groups items while preserving whether their token data has been enriched. */
4139
+ function groupByAccessKeyItems<item extends GroupableItem>(items: item[]): GroupedItem<item>[] {
4140
+ const entries: GroupedItem<item>[] = []
4141
+ let currentGroup: { items: item[]; signer: Address.Address } | null = null
3874
4142
 
3875
4143
  for (const item of items) {
3876
4144
  const itemSigner = signerOf(item)
@@ -3903,12 +4171,15 @@ export function groupByAccessKey(items: Item[]): Entry[] {
3903
4171
  }
3904
4172
 
3905
4173
  /** The access-key signer of an item, or `self` when it has no access-key signer. */
3906
- function signerOf(item: Item): 'self' | Address.Address {
4174
+ function signerOf(item: GroupableItem): 'self' | Address.Address {
3907
4175
  return (item.data as { signer?: 'self' | Address.Address }).signer ?? 'self'
3908
4176
  }
3909
4177
 
3910
4178
  /** Unwraps single-item groups; otherwise wraps the items in a `group` entry. */
3911
- function flushGroup(group: { items: Item[]; signer: Address.Address }): Entry {
4179
+ function flushGroup<item extends GroupableItem>(group: {
4180
+ items: item[]
4181
+ signer: Address.Address
4182
+ }): GroupedItem<item> {
3912
4183
  if (group.items.length === 1) return group.items[0]!
3913
4184
  const firstItem = group.items[0]!
3914
4185
  const lastItem = group.items[group.items.length - 1]!