tapimo 0.5.3 → 0.7.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 (376) hide show
  1. package/dist/App.d.ts +15830 -7220
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +1011 -412
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/App.js +4 -0
  12. package/dist/admin/App.js.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +2 -1
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.d.ts +148 -0
  16. package/dist/admin/apps/early-access.d.ts.map +1 -0
  17. package/dist/admin/apps/early-access.js +133 -0
  18. package/dist/admin/apps/early-access.js.map +1 -0
  19. package/dist/admin/apps/organizations.d.ts +124 -1
  20. package/dist/admin/apps/organizations.d.ts.map +1 -1
  21. package/dist/admin/apps/organizations.js +97 -6
  22. package/dist/admin/apps/organizations.js.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +526 -1
  24. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  25. package/dist/admin/apps/verified-tokens.js +214 -1
  26. package/dist/admin/apps/verified-tokens.js.map +1 -1
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.d.ts.map +1 -1
  29. package/dist/admin/ui.generated.js +95 -75
  30. package/dist/admin/ui.generated.js.map +1 -1
  31. package/dist/analytics/Analytics.d.ts.map +1 -1
  32. package/dist/analytics/Analytics.js +20 -0
  33. package/dist/analytics/Analytics.js.map +1 -1
  34. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  36. package/dist/analytics/tables/requestEvents.js +36 -7
  37. package/dist/analytics/tables/requestEvents.js.map +1 -1
  38. package/dist/apps/data/App.d.ts +6260 -4924
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +11 -6
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  43. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  44. package/dist/apps/data/FundingQuoteProviders.js +194 -16
  45. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  46. package/dist/apps/data/FxOracle.d.ts +71 -0
  47. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  48. package/dist/apps/data/FxOracle.js +97 -0
  49. package/dist/apps/data/FxOracle.js.map +1 -0
  50. package/dist/apps/data/index.d.ts +1 -0
  51. package/dist/apps/data/index.d.ts.map +1 -1
  52. package/dist/apps/data/index.js +1 -0
  53. package/dist/apps/data/index.js.map +1 -1
  54. package/dist/apps/data/routes/activities.d.ts +3599 -1382
  55. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  56. package/dist/apps/data/routes/activities.js +250 -88
  57. package/dist/apps/data/routes/activities.js.map +1 -1
  58. package/dist/apps/data/routes/addresses.d.ts +872 -251
  59. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  60. package/dist/apps/data/routes/addresses.js +11 -9
  61. package/dist/apps/data/routes/addresses.js.map +1 -1
  62. package/dist/apps/data/routes/balances.d.ts +77 -26
  63. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  64. package/dist/apps/data/routes/balances.js +88 -45
  65. package/dist/apps/data/routes/balances.js.map +1 -1
  66. package/dist/apps/data/routes/blocks.d.ts +39 -39
  67. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  68. package/dist/apps/data/routes/exchanges.d.ts +856 -105
  69. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  70. package/dist/apps/data/routes/exchanges.js +518 -76
  71. package/dist/apps/data/routes/exchanges.js.map +1 -1
  72. package/dist/apps/data/routes/fee-amm.d.ts +81 -67
  73. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  74. package/dist/apps/data/routes/fee-amm.js +76 -91
  75. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  76. package/dist/apps/data/routes/funding.d.ts +80 -36
  77. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  78. package/dist/apps/data/routes/funding.js +65 -31
  79. package/dist/apps/data/routes/funding.js.map +1 -1
  80. package/dist/apps/data/routes/receipts.d.ts +392 -315
  81. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  82. package/dist/apps/data/routes/receipts.js +152 -77
  83. package/dist/apps/data/routes/receipts.js.map +1 -1
  84. package/dist/apps/data/routes/tokens.d.ts +167 -174
  85. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  86. package/dist/apps/data/routes/tokens.js +15 -13
  87. package/dist/apps/data/routes/tokens.js.map +1 -1
  88. package/dist/apps/data/routes/transactions.d.ts +277 -536
  89. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  90. package/dist/apps/data/routes/transactions.js +96 -77
  91. package/dist/apps/data/routes/transactions.js.map +1 -1
  92. package/dist/apps/data/routes/transfers.d.ts +157 -81
  93. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  94. package/dist/apps/data/routes/transfers.js +272 -130
  95. package/dist/apps/data/routes/transfers.js.map +1 -1
  96. package/dist/apps/data/routes/valuation.d.ts +307 -0
  97. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  98. package/dist/apps/data/routes/valuation.js +292 -0
  99. package/dist/apps/data/routes/valuation.js.map +1 -0
  100. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  101. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  102. package/dist/apps/data/routes/verified-tokens.js +7 -5
  103. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  104. package/dist/apps/data/routes/webhooks.d.ts +103 -123
  105. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  106. package/dist/apps/data/routes/webhooks.js +124 -25
  107. package/dist/apps/data/routes/webhooks.js.map +1 -1
  108. package/dist/apps/index.d.ts +2 -1
  109. package/dist/apps/index.d.ts.map +1 -1
  110. package/dist/apps/index.js +2 -1
  111. package/dist/apps/index.js.map +1 -1
  112. package/dist/apps/management/App.d.ts +8554 -1692
  113. package/dist/apps/management/App.d.ts.map +1 -1
  114. package/dist/apps/management/App.js +12 -1
  115. package/dist/apps/management/App.js.map +1 -1
  116. package/dist/apps/management/routes/api-keys.d.ts +7 -0
  117. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  118. package/dist/apps/management/routes/api-keys.js +17 -3
  119. package/dist/apps/management/routes/api-keys.js.map +1 -1
  120. package/dist/apps/management/routes/billing.d.ts +157 -162
  121. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  122. package/dist/apps/management/routes/billing.js +31 -23
  123. package/dist/apps/management/routes/billing.js.map +1 -1
  124. package/dist/apps/management/routes/faucet.d.ts +232 -0
  125. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  126. package/dist/apps/management/routes/faucet.js +154 -0
  127. package/dist/apps/management/routes/faucet.js.map +1 -0
  128. package/dist/apps/management/routes/invitations.d.ts +60 -60
  129. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  130. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  131. package/dist/apps/management/routes/invite-links.js +26 -1
  132. package/dist/apps/management/routes/invite-links.js.map +1 -1
  133. package/dist/apps/management/routes/me.d.ts +6 -6
  134. package/dist/apps/management/routes/members.d.ts +6 -6
  135. package/dist/apps/management/routes/orgs.d.ts +42 -42
  136. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  137. package/dist/apps/management/routes/orgs.js +5 -0
  138. package/dist/apps/management/routes/orgs.js.map +1 -1
  139. package/dist/apps/management/routes/projects.d.ts +44 -44
  140. package/dist/apps/management/routes/usage.d.ts +3 -0
  141. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  142. package/dist/apps/management/routes/usage.js +4 -0
  143. package/dist/apps/management/routes/usage.js.map +1 -1
  144. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  145. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  146. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  147. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  148. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  149. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  150. package/dist/apps/management/routes/webhooks.js +573 -0
  151. package/dist/apps/management/routes/webhooks.js.map +1 -0
  152. package/dist/apps/mpp/App.d.ts +363 -0
  153. package/dist/apps/mpp/App.d.ts.map +1 -0
  154. package/dist/apps/mpp/App.js +72 -0
  155. package/dist/apps/mpp/App.js.map +1 -0
  156. package/dist/apps/mpp/Mpp.d.ts +178 -0
  157. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  158. package/dist/apps/mpp/Mpp.js +183 -0
  159. package/dist/apps/mpp/Mpp.js.map +1 -0
  160. package/dist/apps/mpp/index.d.ts +3 -0
  161. package/dist/apps/mpp/index.d.ts.map +1 -0
  162. package/dist/apps/mpp/index.js +3 -0
  163. package/dist/apps/mpp/index.js.map +1 -0
  164. package/dist/apps/relay/App.d.ts.map +1 -1
  165. package/dist/apps/relay/App.js +56 -9
  166. package/dist/apps/relay/App.js.map +1 -1
  167. package/dist/db/Db.d.ts +6 -0
  168. package/dist/db/Db.d.ts.map +1 -1
  169. package/dist/db/Db.js +110 -0
  170. package/dist/db/Db.js.map +1 -1
  171. package/dist/db/Schema.d.ts +53 -0
  172. package/dist/db/Schema.d.ts.map +1 -1
  173. package/dist/db/Schema.js +56 -0
  174. package/dist/db/Schema.js.map +1 -1
  175. package/dist/db/tables/earlyAccess.d.ts +50 -0
  176. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  177. package/dist/db/tables/earlyAccess.js +63 -0
  178. package/dist/db/tables/earlyAccess.js.map +1 -0
  179. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  180. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  181. package/dist/db/tables/enabledBillingSources.js +69 -0
  182. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  183. package/dist/db/tables/invitations.d.ts.map +1 -1
  184. package/dist/db/tables/invitations.js +3 -0
  185. package/dist/db/tables/invitations.js.map +1 -1
  186. package/dist/db/tables/inviteLinks.d.ts +10 -2
  187. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  188. package/dist/db/tables/inviteLinks.js +10 -1
  189. package/dist/db/tables/inviteLinks.js.map +1 -1
  190. package/dist/db/tables/organizations.d.ts +5 -2
  191. package/dist/db/tables/organizations.d.ts.map +1 -1
  192. package/dist/db/tables/organizations.js +13 -2
  193. package/dist/db/tables/organizations.js.map +1 -1
  194. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  195. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  196. package/dist/db/tables/sponsoredTransactions.js +2 -0
  197. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  198. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  199. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  200. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  201. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  202. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  203. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  204. package/dist/db/tables/webhookSubscriptions.js +16 -0
  205. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  206. package/dist/handlers/internal/utils.d.ts +1 -16
  207. package/dist/handlers/internal/utils.d.ts.map +1 -1
  208. package/dist/handlers/internal/utils.js +17 -6
  209. package/dist/handlers/internal/utils.js.map +1 -1
  210. package/dist/handlers/relay.d.ts.map +1 -1
  211. package/dist/handlers/relay.js +37 -5
  212. package/dist/handlers/relay.js.map +1 -1
  213. package/dist/internal/Auth.d.ts +11 -2
  214. package/dist/internal/Auth.d.ts.map +1 -1
  215. package/dist/internal/Auth.js +14 -47
  216. package/dist/internal/Auth.js.map +1 -1
  217. package/dist/internal/FundingQuotes.d.ts +147 -47
  218. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  219. package/dist/internal/FundingQuotes.js +119 -33
  220. package/dist/internal/FundingQuotes.js.map +1 -1
  221. package/dist/internal/Log.d.ts +37 -5
  222. package/dist/internal/Log.d.ts.map +1 -1
  223. package/dist/internal/Log.js +52 -3
  224. package/dist/internal/Log.js.map +1 -1
  225. package/dist/internal/MetricSink.d.ts.map +1 -1
  226. package/dist/internal/MetricSink.js +24 -0
  227. package/dist/internal/MetricSink.js.map +1 -1
  228. package/dist/internal/OpenApi.d.ts +2 -2
  229. package/dist/internal/OpenApi.d.ts.map +1 -1
  230. package/dist/internal/OpenApi.js +3 -0
  231. package/dist/internal/OpenApi.js.map +1 -1
  232. package/dist/internal/Response.d.ts +2 -2
  233. package/dist/internal/Response.d.ts.map +1 -1
  234. package/dist/internal/Response.js +12 -4
  235. package/dist/internal/Response.js.map +1 -1
  236. package/dist/internal/Schema.d.ts +14 -5
  237. package/dist/internal/Schema.d.ts.map +1 -1
  238. package/dist/internal/Schema.js +23 -5
  239. package/dist/internal/Schema.js.map +1 -1
  240. package/dist/internal/Value.d.ts +18 -0
  241. package/dist/internal/Value.d.ts.map +1 -1
  242. package/dist/internal/Value.js +11 -1
  243. package/dist/internal/Value.js.map +1 -1
  244. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  245. package/dist/internal/VerifiedTokens.js +10 -1
  246. package/dist/internal/VerifiedTokens.js.map +1 -1
  247. package/dist/internal/Webhooks.d.ts +24 -3
  248. package/dist/internal/Webhooks.d.ts.map +1 -1
  249. package/dist/internal/Webhooks.js +34 -8
  250. package/dist/internal/Webhooks.js.map +1 -1
  251. package/package.json +7 -2
  252. package/src/App.test.ts +9 -4
  253. package/src/App.ts +23 -3
  254. package/src/Client.test-d.ts +733 -20
  255. package/src/Scope.ts +5 -0
  256. package/src/admin/App.test.ts +4 -0
  257. package/src/admin/App.ts +4 -0
  258. package/src/admin/apps/early-access.test.ts +125 -0
  259. package/src/admin/apps/early-access.ts +176 -0
  260. package/src/admin/apps/organizations.test.ts +86 -2
  261. package/src/admin/apps/organizations.ts +155 -31
  262. package/src/admin/apps/scopes.test.ts +5 -0
  263. package/src/admin/apps/verified-tokens.test.ts +201 -0
  264. package/src/admin/apps/verified-tokens.ts +532 -286
  265. package/src/admin/ui/plugin.ts +2 -1
  266. package/src/admin/ui/src/lib/early-access.ts +63 -0
  267. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  268. package/src/admin/ui/src/lib/organizations.ts +92 -0
  269. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  270. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  271. package/src/admin/ui/src/routes/__root.tsx +2 -1
  272. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  273. package/src/admin/ui/src/routes/early-access.tsx +162 -0
  274. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  275. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  276. package/src/admin/ui.generated.ts +95 -94
  277. package/src/analytics/Analytics.test.ts +83 -0
  278. package/src/analytics/Analytics.ts +20 -0
  279. package/src/analytics/tables/requestEvents.ts +60 -7
  280. package/src/apps/composition.test.ts +11 -2
  281. package/src/apps/data/App.test.ts +1 -1
  282. package/src/apps/data/App.ts +22 -14
  283. package/src/apps/data/FundingQuoteProviders.test.ts +297 -4
  284. package/src/apps/data/FundingQuoteProviders.ts +255 -29
  285. package/src/apps/data/FxOracle.test.ts +118 -0
  286. package/src/apps/data/FxOracle.ts +153 -0
  287. package/src/apps/data/index.ts +1 -0
  288. package/src/apps/data/routes/activities.test.ts +177 -3
  289. package/src/apps/data/routes/activities.ts +441 -132
  290. package/src/apps/data/routes/addresses.ts +19 -9
  291. package/src/apps/data/routes/balances.test.ts +148 -11
  292. package/src/apps/data/routes/balances.ts +140 -51
  293. package/src/apps/data/routes/exchanges.test.ts +544 -9
  294. package/src/apps/data/routes/exchanges.ts +690 -103
  295. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  296. package/src/apps/data/routes/fee-amm.ts +98 -119
  297. package/src/apps/data/routes/funding.test.ts +113 -8
  298. package/src/apps/data/routes/funding.ts +77 -51
  299. package/src/apps/data/routes/receipts.test.ts +129 -23
  300. package/src/apps/data/routes/receipts.ts +205 -100
  301. package/src/apps/data/routes/tokens.test.ts +43 -0
  302. package/src/apps/data/routes/tokens.ts +28 -13
  303. package/src/apps/data/routes/transactions.test.ts +72 -19
  304. package/src/apps/data/routes/transactions.ts +162 -92
  305. package/src/apps/data/routes/transfers.test.ts +341 -31
  306. package/src/apps/data/routes/transfers.ts +358 -158
  307. package/src/apps/data/routes/valuation.test.ts +437 -0
  308. package/src/apps/data/routes/valuation.ts +423 -0
  309. package/src/apps/data/routes/verified-tokens.ts +14 -2
  310. package/src/apps/data/routes/webhooks.test.ts +56 -8
  311. package/src/apps/data/routes/webhooks.ts +144 -27
  312. package/src/apps/index.ts +2 -1
  313. package/src/apps/management/App.ts +13 -0
  314. package/src/apps/management/routes/api-keys.test.ts +14 -0
  315. package/src/apps/management/routes/api-keys.ts +27 -3
  316. package/src/apps/management/routes/billing.test.ts +152 -20
  317. package/src/apps/management/routes/billing.ts +34 -18
  318. package/src/apps/management/routes/faucet.test.ts +87 -0
  319. package/src/apps/management/routes/faucet.ts +201 -0
  320. package/src/apps/management/routes/invite-links.test.ts +117 -0
  321. package/src/apps/management/routes/invite-links.ts +43 -1
  322. package/src/apps/management/routes/me.test.ts +9 -99
  323. package/src/apps/management/routes/orgs.test.ts +45 -2
  324. package/src/apps/management/routes/orgs.ts +6 -0
  325. package/src/apps/management/routes/scopes.test.ts +1 -0
  326. package/src/apps/management/routes/usage.test.ts +6 -0
  327. package/src/apps/management/routes/usage.ts +7 -0
  328. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  329. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  330. package/src/apps/management/routes/webhooks.test.ts +426 -0
  331. package/src/apps/management/routes/webhooks.ts +672 -0
  332. package/src/apps/mpp/App.ts +86 -0
  333. package/src/apps/mpp/Mpp.test.ts +193 -0
  334. package/src/apps/mpp/Mpp.ts +353 -0
  335. package/src/apps/mpp/index.ts +2 -0
  336. package/src/apps/mpp/internal/Adapter.ts +11 -0
  337. package/src/apps/mpp/internal/Error.ts +9 -0
  338. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  339. package/src/apps/mpp/internal/Screening.ts +10 -0
  340. package/src/apps/relay/App.test.ts +88 -6
  341. package/src/apps/relay/App.ts +61 -9
  342. package/src/db/Db.test.ts +19 -0
  343. package/src/db/Db.ts +125 -0
  344. package/src/db/Schema.ts +68 -0
  345. package/src/db/tables/earlyAccess.ts +86 -0
  346. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  347. package/src/db/tables/enabledBillingSources.ts +118 -0
  348. package/src/db/tables/invitations.ts +3 -0
  349. package/src/db/tables/inviteLinks.ts +22 -3
  350. package/src/db/tables/organizations.test.ts +103 -0
  351. package/src/db/tables/organizations.ts +18 -2
  352. package/src/db/tables/sponsoredTransactions.ts +4 -0
  353. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  354. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  355. package/src/db/tables/webhookSubscriptions.ts +31 -0
  356. package/src/handlers/internal/utils.ts +22 -6
  357. package/src/handlers/relay.errors.test.ts +153 -0
  358. package/src/handlers/relay.ts +42 -7
  359. package/src/internal/Auth.test.ts +3 -3
  360. package/src/internal/Auth.ts +15 -47
  361. package/src/internal/FundingQuotes.test.ts +161 -6
  362. package/src/internal/FundingQuotes.ts +141 -39
  363. package/src/internal/Log.test-d.ts +21 -0
  364. package/src/internal/Log.test.ts +141 -0
  365. package/src/internal/Log.ts +93 -6
  366. package/src/internal/MetricSink.test.ts +215 -0
  367. package/src/internal/MetricSink.ts +24 -0
  368. package/src/internal/OpenApi.ts +3 -0
  369. package/src/internal/Response.test.ts +28 -0
  370. package/src/internal/Response.ts +12 -4
  371. package/src/internal/Schema.ts +50 -5
  372. package/src/internal/Value.test.ts +20 -0
  373. package/src/internal/Value.ts +24 -1
  374. package/src/internal/VerifiedTokens.ts +9 -1
  375. package/src/internal/Webhooks.test.ts +140 -0
  376. package/src/internal/Webhooks.ts +53 -9
@@ -1,6 +1,7 @@
1
1
  import { Hono, type Context } from 'hono'
2
2
  import { Address, Hex } from 'ox'
3
3
  import { type Log, parseEventLogs, zeroAddress } from 'viem'
4
+ import { token as viem_Token } from 'viem/actions'
4
5
  import { Abis, Addresses } from 'viem/tempo'
5
6
  import * as z from 'zod/mini'
6
7
 
@@ -17,12 +18,21 @@ import type * as Tidx from '../../../internal/Tidx.js'
17
18
  import * as Timing from '../../../internal/Timing.js'
18
19
  import * as Ttl from '../../../internal/Ttl.js'
19
20
  import * as Value from '../../../internal/Value.js'
21
+ import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
22
+ import * as FxOracle from '../FxOracle.js'
20
23
  import * as Tokens from './tokens.js'
21
24
  import * as Transfers from './transfers.js'
25
+ import * as Valuation from './valuation.js'
22
26
 
23
27
  /** Example transaction hash surfaced in OpenAPI docs. */
24
28
  const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474'
25
29
 
30
+ const tokenFields = ['token.logoUri', 'token.verified'] as const
31
+
32
+ // TIP-20 tokens live at deterministic `0x20c0…` addresses. Other contracts
33
+ // can emit the same `Transfer` signature but do not expose TIP-20 metadata.
34
+ const tip20AddressPrefix = '0x20c0'
35
+
26
36
  /** Zod schemas owned by the activity handlers. */
27
37
  export namespace schema {
28
38
  /** A signer: the literal `self`, or the access-key address that signed. */
@@ -32,20 +42,50 @@ export namespace schema {
32
42
  z.describe('Who signed the activity: `self`, or the access-key address used to sign it.'),
33
43
  )
34
44
 
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
45
+ /** A token with required RPC metadata and optional curated fields. */
46
+ export const Token = Schema.describe(
47
+ Tokens.schema.TokenReference,
48
+ 'A token referenced by an activity item.',
49
+ )
42
50
 
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
- */
51
+ /** A token amount carrying its nominal value in the requested denomination. */
52
+ const ValuedAmount = z.extend(Schema.TokenAmount, {
53
+ valuation: z
54
+ .optional(z.nullable(Valuation.schema.Value))
55
+ .check(
56
+ z.describe(
57
+ 'The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
58
+ 'unverified, its display currency has no rate, or rates were unavailable.',
59
+ ),
60
+ ),
61
+ })
62
+
63
+ /** Self-contained amount for a destination token. */
64
+ const DestinationAmount = ValuedAmount.check(
65
+ z.describe('Amount associated with `destinationToken`.'),
66
+ )
67
+
68
+ /** Self-contained amount for a refunded token. */
69
+ const RefundAmount = ValuedAmount.check(z.describe('Amount associated with `refund`.'))
70
+
71
+ /** Self-contained amount for a source token. */
72
+ const SourceAmount = ValuedAmount.check(z.describe('Amount associated with `sourceToken`.'))
73
+
74
+ /** Page-level resources: valuation rate provenance. */
75
+ export const Meta = z
76
+ .object({
77
+ pricing: z
78
+ .optional(Valuation.schema.Pricing)
79
+ .check(
80
+ z.describe(
81
+ 'Rate provenance for amount valuations. Present when conversion rates were ' +
82
+ 'consulted; absent when every value was identity-valued or rates were unavailable.',
83
+ ),
84
+ ),
85
+ })
86
+ .check(z.describe('Page-level resources attached to this response.'))
87
+
88
+ /** A token transfer with subject-relative direction and signer fields. */
49
89
  export const Transfer = z
50
90
  .object({
51
91
  attribution: z
@@ -64,11 +104,18 @@ export namespace schema {
64
104
  z.describe('Block number where this transfer was included.'),
65
105
  z.meta({ examples: [12345678] }),
66
106
  ),
107
+ destinationAmount: z
108
+ .optional(DestinationAmount)
109
+ .check(
110
+ z.describe(
111
+ 'Amount delivered to the recipient for a cross-token transfer. Omitted with `destinationToken` for a same-token transfer.',
112
+ ),
113
+ ),
67
114
  destinationToken: z
68
115
  .optional(Token)
69
116
  .check(
70
117
  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.',
118
+ 'Token delivered to the recipient when it differs from the source token. Omitted for a same-token transfer.',
72
119
  ),
73
120
  ),
74
121
  direction: z
@@ -90,7 +137,8 @@ export namespace schema {
90
137
  'For outbound transfers, who signed it: `self` or the access-key address. Omitted for inbound transfers.',
91
138
  ),
92
139
  ),
93
- sourceToken: Token.check(z.describe('Token and amount that moved in the transfer.')),
140
+ sourceAmount: SourceAmount,
141
+ sourceToken: Token.check(z.describe('Token that moved in the transfer.')),
94
142
  timestamp: z
95
143
  .iso.datetime()
96
144
  .check(z.describe('Block timestamp for the transfer.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })), // prettier-ignore
@@ -98,7 +146,16 @@ export namespace schema {
98
146
  z.describe('Transaction hash that contains this transfer.'),
99
147
  ),
100
148
  })
101
- .check(z.describe('One token transfer involving the requested address.'))
149
+ .check(
150
+ z.refine(
151
+ (data) => (data.destinationToken === undefined) === (data.destinationAmount === undefined),
152
+ {
153
+ error: '`destinationAmount` and `destinationToken` must be returned together.',
154
+ path: ['destinationAmount'],
155
+ },
156
+ ),
157
+ z.describe('One token transfer involving the requested address.'),
158
+ )
102
159
 
103
160
  /**
104
161
  * One transaction log, decoded into a known Tempo event when possible. The
@@ -193,9 +250,7 @@ export namespace schema {
193
250
  .object({
194
251
  ...base,
195
252
  data: Transfer.check(
196
- z.describe(
197
- 'Transfer details, using the `/transfers` shape plus feed-only fields such as direction and signer.',
198
- ),
253
+ z.describe('Transfer details with feed-relative direction and signer fields.'),
199
254
  ),
200
255
  type: z
201
256
  .literal('transfer')
@@ -208,7 +263,8 @@ export namespace schema {
208
263
  data: z
209
264
  .object({
210
265
  recipient: Schema.Address.check(z.describe('Address that received the transfer.')),
211
- sourceToken: Token.check(z.describe('Token and amount that were minted.')),
266
+ sourceAmount: SourceAmount,
267
+ sourceToken: Token.check(z.describe('Token that was minted.')),
212
268
  })
213
269
  .check(z.describe('A token mint activity.')),
214
270
  type: z
@@ -223,7 +279,8 @@ export namespace schema {
223
279
  .object({
224
280
  sender: Schema.Address.check(z.describe('Address whose token balance was burned.')),
225
281
  signer: Signer,
226
- sourceToken: Token.check(z.describe('Token and amount that were burned.')),
282
+ sourceAmount: SourceAmount,
283
+ sourceToken: Token.check(z.describe('Token that was burned.')),
227
284
  })
228
285
  .check(z.describe('A token burn activity.')),
229
286
  type: z
@@ -236,8 +293,10 @@ export namespace schema {
236
293
  ...base,
237
294
  data: z
238
295
  .object({
296
+ destinationAmount: DestinationAmount,
239
297
  destinationToken: Token.check(z.describe('Token the address received in the swap.')),
240
298
  signer: Signer,
299
+ sourceAmount: SourceAmount,
241
300
  sourceToken: Token.check(z.describe('Token the address sent into the swap.')),
242
301
  })
243
302
  .check(
@@ -256,9 +315,8 @@ export namespace schema {
256
315
  data: z
257
316
  .object({
258
317
  signer: Signer,
259
- sourceToken: Token.check(
260
- z.describe('Token and allowance amount approved for spending.'),
261
- ),
318
+ sourceAmount: SourceAmount,
319
+ sourceToken: Token.check(z.describe('Token approved for spending.')),
262
320
  spender: Schema.Address.check(
263
321
  z.describe('Address allowed to spend the approved amount.'),
264
322
  ),
@@ -277,9 +335,18 @@ export namespace schema {
277
335
  refund: z
278
336
  .optional(Token)
279
337
  .check(z.describe('Token refunded to the address when the session closed, if any.')),
338
+ refundAmount: z
339
+ .optional(RefundAmount)
340
+ .check(z.describe('Amount refunded to the address when the session closed, if any.')),
280
341
  signer: Signer,
281
342
  })
282
- .check(z.describe('A payment-channel session close activity.')),
343
+ .check(
344
+ z.refine((data) => (data.refund === undefined) === (data.refundAmount === undefined), {
345
+ error: '`refundAmount` and `refund` must be returned together.',
346
+ path: ['refundAmount'],
347
+ }),
348
+ z.describe('A payment-channel session close activity.'),
349
+ ),
283
350
  type: z
284
351
  .literal('session-closed')
285
352
  .check(z.describe('Activity type that tells you which `data` shape this entry uses.')),
@@ -354,7 +421,8 @@ export namespace schema {
354
421
  z.describe('Address whose blocked balance was burned by policy.'),
355
422
  ),
356
423
  signer: Signer,
357
- sourceToken: Token.check(z.describe('Token and amount that were burned.')),
424
+ sourceAmount: SourceAmount,
425
+ sourceToken: Token.check(z.describe('Token that was burned.')),
358
426
  })
359
427
  .check(
360
428
  z.describe(
@@ -377,9 +445,8 @@ export namespace schema {
377
445
  ),
378
446
  payer: Schema.Address.check(z.describe('Address that funds the channel.')),
379
447
  signer: Signer,
380
- sourceToken: Token.check(
381
- z.describe('Token and deposit amount locked into the channel.'),
382
- ),
448
+ sourceAmount: SourceAmount,
449
+ sourceToken: Token.check(z.describe('Token locked into the channel.')),
383
450
  })
384
451
  .check(z.describe('A payment channel was opened and funded.')),
385
452
  type: z
@@ -394,7 +461,8 @@ export namespace schema {
394
461
  .object({
395
462
  channelId: Schema.Hex.check(z.describe('Identifier of the payment channel.')),
396
463
  signer: Signer,
397
- sourceToken: Token.check(z.describe('Token and amount added to the channel deposit.')),
464
+ sourceAmount: SourceAmount,
465
+ sourceToken: Token.check(z.describe('Token added to the channel deposit.')),
398
466
  })
399
467
  .check(z.describe('A payment channel deposit was topped up.')),
400
468
  type: z
@@ -411,7 +479,8 @@ export namespace schema {
411
479
  payee: Schema.Address.check(z.describe('Address that received the settled payment.')),
412
480
  payer: Schema.Address.check(z.describe('Address that funded the channel.')),
413
481
  signer: Signer,
414
- sourceToken: Token.check(z.describe('Token and amount paid out in this settlement.')),
482
+ sourceAmount: SourceAmount,
483
+ sourceToken: Token.check(z.describe('Token paid out in this settlement.')),
415
484
  })
416
485
  .check(z.describe('An incremental payment-channel settlement to the payee.')),
417
486
  type: z
@@ -431,13 +500,30 @@ export namespace schema {
431
500
  payer: Schema.Address.check(z.describe('Address that funded the channel.')),
432
501
  refund: z
433
502
  .optional(Token)
434
- .check(z.describe('Token and amount refunded to the payer on close, if any.')),
503
+ .check(z.describe('Token refunded to the payer on close, if any.')),
504
+ refundAmount: z
505
+ .optional(RefundAmount)
506
+ .check(z.describe('Amount refunded to the payer on close, if any.')),
435
507
  signer: Signer,
508
+ sourceAmount: z
509
+ .optional(SourceAmount)
510
+ .check(z.describe('Final amount paid to the payee on close, if any.')),
436
511
  sourceToken: z
437
512
  .optional(Token)
438
- .check(z.describe('Token and final amount paid out to the payee on close, if any.')),
513
+ .check(z.describe('Token paid out to the payee on close, if any.')),
439
514
  })
440
515
  .check(
516
+ z.refine((data) => (data.refund === undefined) === (data.refundAmount === undefined), {
517
+ error: '`refundAmount` and `refund` must be returned together.',
518
+ path: ['refundAmount'],
519
+ }),
520
+ z.refine(
521
+ (data) => (data.sourceToken === undefined) === (data.sourceAmount === undefined),
522
+ {
523
+ error: '`sourceAmount` and `sourceToken` must be returned together.',
524
+ path: ['sourceAmount'],
525
+ },
526
+ ),
441
527
  z.describe(
442
528
  'A payment channel was closed, paying out the payee and refunding the payer.',
443
529
  ),
@@ -475,7 +561,8 @@ export namespace schema {
475
561
  .enum(['bid', 'ask'])
476
562
  .check(z.describe('Order side: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
477
563
  signer: Signer,
478
- sourceToken: Token.check(z.describe('Token and amount escrowed for the order.')),
564
+ sourceAmount: SourceAmount,
565
+ sourceToken: Token.check(z.describe('Token escrowed for the order.')),
479
566
  tick: z
480
567
  .number()
481
568
  .check(
@@ -512,7 +599,8 @@ export namespace schema {
512
599
  data: z
513
600
  .object({
514
601
  signer: Signer,
515
- sourceToken: Token.check(z.describe('Token and amount of fees distributed.')),
602
+ sourceAmount: SourceAmount,
603
+ sourceToken: Token.check(z.describe('Token used for the distributed fees.')),
516
604
  validator: Schema.Address.check(
517
605
  z.describe('Validator that received the distributed fees.'),
518
606
  ),
@@ -528,11 +616,11 @@ export namespace schema {
528
616
  ...base,
529
617
  data: z
530
618
  .object({
531
- destinationToken: Token.check(
532
- z.describe('Token and amount received from the fee AMM.'),
533
- ),
619
+ destinationAmount: DestinationAmount,
620
+ destinationToken: Token.check(z.describe('Token received from the fee AMM.')),
534
621
  signer: Signer,
535
- sourceToken: Token.check(z.describe('Token and amount sent into the fee AMM.')),
622
+ sourceAmount: SourceAmount,
623
+ sourceToken: Token.check(z.describe('Token sent into the fee AMM.')),
536
624
  swapper: Schema.Address.check(z.describe('Address that performed the rebalance swap.')),
537
625
  })
538
626
  .check(z.describe('A fee-AMM rebalance swap between a validator and user token.')),
@@ -548,7 +636,8 @@ export namespace schema {
548
636
  .object({
549
637
  funder: Schema.Address.check(z.describe('Address that funded the reward pool.')),
550
638
  signer: Signer,
551
- sourceToken: Token.check(z.describe('Token and amount added to the reward pool.')),
639
+ sourceAmount: SourceAmount,
640
+ sourceToken: Token.check(z.describe('Token added to the reward pool.')),
552
641
  })
553
642
  .check(z.describe('A token reward pool was funded.')),
554
643
  type: z
@@ -583,9 +672,8 @@ export namespace schema {
583
672
  z.describe('Public key of the access key whose limit changed.'),
584
673
  ),
585
674
  signer: Signer,
586
- sourceToken: Token.check(
587
- z.describe('Token and new spending limit set for the access key.'),
588
- ),
675
+ sourceAmount: SourceAmount,
676
+ sourceToken: Token.check(z.describe('Token whose spending limit was updated.')),
589
677
  })
590
678
  .check(z.describe('An access key’s per-token spending limit was updated.')),
591
679
  type: z
@@ -636,7 +724,8 @@ export namespace schema {
636
724
  .enum(['bid', 'ask'])
637
725
  .check(z.describe('Side of the flipped order: `bid` buys the base token, `ask` sells it.'), z.meta({ examples: ['bid'] })), // prettier-ignore
638
726
  signer: Signer,
639
- sourceToken: Token.check(z.describe('Token and amount the flipped order rests with.')),
727
+ sourceAmount: SourceAmount,
728
+ sourceToken: Token.check(z.describe('Token the flipped order rests with.')),
640
729
  tick: z
641
730
  .number()
642
731
  .check(
@@ -1292,6 +1381,17 @@ export namespace schema {
1292
1381
  .union([...items, Group])
1293
1382
  .check(z.describe('Either one activity item, or a grouped set of access-key activity items.'))
1294
1383
 
1384
+ /** Optional token fields that require sources beyond RPC metadata. */
1385
+ export const Include = z
1386
+ .enum(tokenFields)
1387
+ .check(z.describe('Additional token fields to include in activity responses.'))
1388
+
1389
+ /** Parses comma-separated optional token fields. */
1390
+ export const includeQuery = Schema.includeQuery(
1391
+ Include,
1392
+ 'Comma-separated token fields to include, such as `token.logoUri,token.verified`.',
1393
+ )
1394
+
1295
1395
  /** Schemas for the getAddressActivities operation. */
1296
1396
  export namespace getAddressActivities {
1297
1397
  /** Path parameters for address activity requests. */
@@ -1308,6 +1408,7 @@ export namespace schema {
1308
1408
  .strictObject({
1309
1409
  chainId: Schema.ChainIdQuery,
1310
1410
  cursor: Schema.Cursor,
1411
+ denomination: Schema.Denomination,
1311
1412
  // Folding is opt-in: by default every item is returned at the root.
1312
1413
  group: z
1313
1414
  ._default(Schema.booleanQuery(), false)
@@ -1317,6 +1418,7 @@ export namespace schema {
1317
1418
  ),
1318
1419
  z.meta({ examples: [true] }),
1319
1420
  ),
1421
+ include: includeQuery,
1320
1422
  limit: Schema.Limit,
1321
1423
  // Raw-log capture is opt-in: it can add a large `events` array to every
1322
1424
  // item, so the default feed stays lean.
@@ -1344,6 +1446,9 @@ export namespace schema {
1344
1446
  export const Response = z
1345
1447
  .object({
1346
1448
  data: z.array(Entry).check(z.describe('Activity feed entries for the address.')),
1449
+ meta: z
1450
+ .optional(schema.Meta)
1451
+ .check(z.describe('Page-level resources, such as valuation rate provenance.')),
1347
1452
  nextCursor: Schema.NextCursor,
1348
1453
  })
1349
1454
  .check(z.describe('A page of human-readable activity for the address.'))
@@ -1366,6 +1471,8 @@ export namespace schema {
1366
1471
  export const Query = z
1367
1472
  .strictObject({
1368
1473
  chainId: Schema.ChainIdQuery,
1474
+ denomination: Schema.Denomination,
1475
+ include: includeQuery,
1369
1476
  // Raw-log capture is opt-in: it can add a large `events` array to every
1370
1477
  // item, so the default response stays lean.
1371
1478
  logs: z
@@ -1388,12 +1495,15 @@ export namespace schema {
1388
1495
  export const Response = z
1389
1496
  .object({
1390
1497
  data: z.array(Item).check(z.describe('Activity items classified from the transaction.')),
1498
+ meta: z
1499
+ .optional(schema.Meta)
1500
+ .check(z.describe('Page-level resources, such as valuation rate provenance.')),
1391
1501
  })
1392
1502
  .check(z.describe('The human-readable activity that happened on a transaction.'))
1393
1503
  }
1394
1504
  }
1395
1505
 
1396
- /** A token and amount in the public resource shape (derived from {@link schema.Token}). */
1506
+ /** A token in the public resource shape (derived from {@link schema.Token}). */
1397
1507
  export type Token = z.output<typeof schema.Token>
1398
1508
 
1399
1509
  /** A single classified activity item (derived from {@link schema.Item}). */
@@ -1402,14 +1512,36 @@ export type Item = z.output<typeof schema.Item>
1402
1512
  /** A single activity item, or a folded access-key group (derived from {@link schema.Entry}). */
1403
1513
  export type Entry = z.output<typeof schema.Entry>
1404
1514
 
1515
+ type AmountKey = 'destinationAmount' | 'refundAmount' | 'sourceAmount'
1516
+ type TokenKey = 'destinationToken' | 'refund' | 'sourceToken'
1517
+
1518
+ /** Token fields available before RPC metadata resolution. */
1519
+ type UnenrichedToken = {
1520
+ address: Token['address']
1521
+ amount: z.output<typeof Schema.DecimalString>
1522
+ }
1523
+
1524
+ type UnenrichedData<data> = {
1525
+ [key in keyof data as key extends AmountKey ? never : key]: key extends TokenKey
1526
+ ? UnenrichedToken | Extract<data[key], undefined>
1527
+ : data[key]
1528
+ }
1529
+
1530
+ /** A classified item whose token stubs have not been enriched with structured amounts. */
1531
+ type UnenrichedItem = {
1532
+ [type in Item['type']]: Omit<Extract<Item, { type: type }>, 'data'> & {
1533
+ data: UnenrichedData<Extract<Item, { type: type }>['data']>
1534
+ }
1535
+ }[Item['type']]
1536
+
1405
1537
  /**
1406
1538
  * A classified item before {@link list} stamps its display {@link Item.title}.
1407
1539
  * Internal classifiers build these; `list` derives `title` from `type` via
1408
1540
  * {@link titleByType}, so each branch never repeats the human label.
1409
1541
  */
1410
1542
  type RawItem = {
1411
- [type in Item['type']]: Omit<Extract<Item, { type: type }>, 'title'>
1412
- }[Item['type']]
1543
+ [type in UnenrichedItem['type']]: Omit<Extract<UnenrichedItem, { type: type }>, 'title'>
1544
+ }[UnenrichedItem['type']]
1413
1545
 
1414
1546
  /**
1415
1547
  * Human-readable label for each activity {@link Item.type}, mirroring the per-variant
@@ -1572,6 +1704,12 @@ const maxGroupSize = 50
1572
1704
  /** Token-bearing keys on an item's `data` payload, walked for enrichment. */
1573
1705
  const tokenKeys = ['destinationToken', 'refund', 'sourceToken'] as const
1574
1706
 
1707
+ const amountKeyByTokenKey = {
1708
+ destinationToken: 'destinationAmount',
1709
+ refund: 'refundAmount',
1710
+ sourceToken: 'sourceAmount',
1711
+ } as const
1712
+
1575
1713
  /** The base fields ({@link schema.base}) every activity item carries. */
1576
1714
  type Base = {
1577
1715
  id: string
@@ -1665,7 +1803,8 @@ type TxGroup = {
1665
1803
  * consecutive access-key-signed items into `group` entries. Pagination uses the
1666
1804
  * same `(block_num, tx_idx)` keyset cursor the pipeline emits.
1667
1805
  */
1668
- export function addresses() {
1806
+ export function addresses(options: addresses.Options = {}) {
1807
+ const oracle = options.fx?.oracle ?? FxOracle.ecb()
1669
1808
  return new Hono<App.Environment>().get(
1670
1809
  '/v1/addresses/:address/activities',
1671
1810
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
@@ -1682,7 +1821,7 @@ export function addresses() {
1682
1821
  'Get a human-readable feed of what an address has been doing onchain, including payments sent and received, swaps, approvals, and more.',
1683
1822
  operationId: 'getAddressActivities',
1684
1823
  responses: OpenApi.responses({
1685
- errors: { 502: 'Tempo could not read activity data from the upstream indexer.' },
1824
+ errors: { 502: 'Tempo could not read activity or token data from an upstream service.' },
1686
1825
  success: {
1687
1826
  description: 'A page of address activity entries.',
1688
1827
  schema: schema.getAddressActivities.Response,
@@ -1712,38 +1851,42 @@ export function addresses() {
1712
1851
  const { address } = c.req.valid('param')
1713
1852
  const query = c.req.valid('query')
1714
1853
 
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)
1854
+ // Cursor pages carry current valuations and curated verification data.
1855
+ if (query.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
1719
1856
 
1720
1857
  try {
1721
1858
  const chainId = query.chainId ?? c.get('chainId')
1722
1859
  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,
1860
+ const { items: unenrichedItems, nextCursor } = await Timing.time(
1861
+ c,
1862
+ 'address_activities',
1863
+ () =>
1864
+ Store.memoize(
1865
+ () =>
1866
+ list(provider, {
1867
+ address: address as Hex.Hex,
1868
+ cursor: query.cursor,
1869
+ group: query.group,
1870
+ limit: query.limit,
1871
+ logs: query.logs,
1872
+ store: c.get('store'),
1873
+ }),
1874
+ {
1875
+ key: `activities:v1:${chainId}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
1732
1876
  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
- ),
1877
+ ttl: Ttl.seconds(15),
1878
+ },
1879
+ ),
1740
1880
  )
1741
1881
 
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 })
1882
+ // Build immutable token and structured amount copies from RPC metadata.
1883
+ const { items, rates } = await enrichTokens(c, {
1884
+ chainId,
1885
+ denomination: query.denomination ?? 'USD',
1886
+ include: query.include,
1887
+ items: unenrichedItems,
1888
+ oracle,
1889
+ })
1747
1890
 
1748
1891
  // Fold consecutive access-key-signed items into `group` entries only
1749
1892
  // when requested; otherwise every item is returned at the root.
@@ -1752,17 +1895,28 @@ export function addresses() {
1752
1895
  return c.json(
1753
1896
  Response.validated(schema.getAddressActivities.Response, {
1754
1897
  data,
1898
+ ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
1755
1899
  nextCursor,
1756
1900
  }),
1757
1901
  200,
1758
1902
  )
1759
1903
  } catch (cause) {
1904
+ if (cause instanceof Valuation.UnsupportedDenominationError)
1905
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
1760
1906
  return Response.upstream(c, cause)
1761
1907
  }
1762
1908
  },
1763
1909
  )
1764
1910
  }
1765
1911
 
1912
+ export declare namespace addresses {
1913
+ /** Options for the address-scoped activity handlers. */
1914
+ type Options = {
1915
+ /** FX configuration backing amount valuation. */
1916
+ fx?: Valuation.addresses.Fx | undefined
1917
+ }
1918
+ }
1919
+
1766
1920
  /**
1767
1921
  * Creates transaction-scoped activity handlers, mounted under the
1768
1922
  * `/transactions` composer. Exposes `GET /:transactionHash/activities`.
@@ -1773,7 +1927,8 @@ export function addresses() {
1773
1927
  * classified item(s) — usually one headline activity — with the full per-tx
1774
1928
  * event set available via `logs=true`.
1775
1929
  */
1776
- export function transactions() {
1930
+ export function transactions(options: transactions.Options = {}) {
1931
+ const oracle = options.fx?.oracle ?? FxOracle.ecb()
1777
1932
  return new Hono<App.Environment>().get(
1778
1933
  '/v1/transactions/:transactionHash/activities',
1779
1934
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
@@ -1795,7 +1950,7 @@ export function transactions() {
1795
1950
  description: 'No mined transaction was found for that hash.',
1796
1951
  codes: ['transaction_not_found'],
1797
1952
  },
1798
- 502: 'Tempo could not read activity data from the upstream indexer.',
1953
+ 502: 'Tempo could not read activity or token data from an upstream service.',
1799
1954
  },
1800
1955
  success: {
1801
1956
  description: 'The classified activity for the transaction.',
@@ -1806,10 +1961,8 @@ export function transactions() {
1806
1961
  tags: ['Activities'],
1807
1962
  }),
1808
1963
  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.
1964
+ // Default to no-store so a 404 does not mask the eventual mined result.
1965
+ // Successful responses use the metadata policy for current valuations.
1813
1966
  cacheControl: Cache.policies.noStore,
1814
1967
  name: 'tempo-api:transactions:v1',
1815
1968
  key: (c) => Cache.urlKey(c, schema.getTransactionActivities.Query),
@@ -1832,7 +1985,7 @@ export function transactions() {
1832
1985
  const chainId = query.chainId ?? c.get('chainId')
1833
1986
 
1834
1987
  try {
1835
- const items = await Timing.time(c, 'transaction_activities', () =>
1988
+ const unenrichedItems = await Timing.time(c, 'transaction_activities', () =>
1836
1989
  Store.memoize(
1837
1990
  () =>
1838
1991
  getTransactionActivities({
@@ -1850,31 +2003,49 @@ export function transactions() {
1850
2003
  ),
1851
2004
  )
1852
2005
 
1853
- if (items === null)
2006
+ if (unenrichedItems === null)
1854
2007
  return Response.error(c, {
1855
2008
  code: 'transaction_not_found',
1856
2009
  message: 'Transaction not found',
1857
2010
  status: 404,
1858
2011
  })
1859
2012
 
1860
- // A mined transaction's activity is final, so cache it aggressively.
1861
- Cache.setPolicy(c, Cache.policies.immutable)
2013
+ // Mined activity still carries current valuations and verification data.
2014
+ Cache.setPolicy(c, Cache.policies.metadata)
1862
2015
 
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 })
2016
+ // Build immutable token and structured amount copies from RPC metadata.
2017
+ const { items, rates } = await enrichTokens(c, {
2018
+ chainId,
2019
+ denomination: query.denomination ?? 'USD',
2020
+ include: query.include,
2021
+ items: unenrichedItems,
2022
+ oracle,
2023
+ })
1866
2024
 
1867
2025
  return c.json(
1868
- Response.validated(schema.getTransactionActivities.Response, { data: items }),
2026
+ Response.validated(schema.getTransactionActivities.Response, {
2027
+ data: items,
2028
+ ...(rates ? { meta: { pricing: Valuation.pricing(rates, oracle) } } : {}),
2029
+ }),
1869
2030
  200,
1870
2031
  )
1871
2032
  } catch (cause) {
2033
+ if (cause instanceof Valuation.UnsupportedDenominationError)
2034
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 })
1872
2035
  return Response.upstream(c, cause)
1873
2036
  }
1874
2037
  },
1875
2038
  )
1876
2039
  }
1877
2040
 
2041
+ export declare namespace transactions {
2042
+ /** Options for the transaction-scoped activity handlers. */
2043
+ type Options = {
2044
+ /** FX configuration backing amount valuation. */
2045
+ fx?: Valuation.addresses.Fx | undefined
2046
+ }
2047
+ }
2048
+
1878
2049
  /**
1879
2050
  * Classifies the activity for a single transaction. Fetches the transaction's
1880
2051
  * receipt (for its block position, sender, and fee payer) and its logs, then
@@ -1887,7 +2058,7 @@ export function transactions() {
1887
2058
  */
1888
2059
  export async function getTransactionActivities(
1889
2060
  options: getTransactionActivities.Options,
1890
- ): Promise<Item[] | null> {
2061
+ ): Promise<UnenrichedItem[] | null> {
1891
2062
  const { chainId, logs = false, tidx: tidxClient } = options
1892
2063
  const hash = options.transactionHash.toLowerCase()
1893
2064
 
@@ -1979,7 +2150,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
1979
2150
  const fingerprintMap = await Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
1980
2151
 
1981
2152
  let currentCursor: string | undefined = cursor
1982
- const allItems: Item[] = []
2153
+ const allItems: UnenrichedItem[] = []
1983
2154
  let hasMore = false
1984
2155
  let lastGroup: TxGroup | undefined
1985
2156
 
@@ -2005,7 +2176,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
2005
2176
  lastGroup = page[page.length - 1]
2006
2177
 
2007
2178
  const enough = group
2008
- ? groupByAccessKey(allItems).length >= minGroupEntries
2179
+ ? groupByAccessKeyItems(allItems).length >= minGroupEntries
2009
2180
  : allItems.length >= limit
2010
2181
  if (enough || !hasMore) break
2011
2182
 
@@ -2015,8 +2186,8 @@ export async function list(provider: Provider, options: list.Options): Promise<l
2015
2186
  let nextCursor: string | null = null
2016
2187
  if (hasMore && lastGroup) nextCursor = provider.cursorForGroup(lastGroup)
2017
2188
 
2018
- // Items carry stub tokens (`address`/`amount`); the caller enriches them with
2019
- // metadata via {@link enrichTokens}.
2189
+ // Items carry private `address`/`amount` stubs; the caller splits them into
2190
+ // public token references and structured amounts via {@link enrichTokens}.
2020
2191
  return { items: allItems, nextCursor }
2021
2192
  }
2022
2193
 
@@ -2038,7 +2209,7 @@ export declare namespace list {
2038
2209
  }
2039
2210
  /** Result of {@link list}. */
2040
2211
  type ReturnType = {
2041
- items: Item[]
2212
+ items: UnenrichedItem[]
2042
2213
  nextCursor: string | null
2043
2214
  }
2044
2215
  }
@@ -3661,13 +3832,13 @@ function transfer(
3661
3832
  blockNumber: number,
3662
3833
  data: {
3663
3834
  attribution?: string | undefined
3664
- destinationToken?: Token | undefined
3835
+ destinationToken?: UnenrichedToken | undefined
3665
3836
  direction: 'in' | 'out'
3666
3837
  memo?: string | undefined
3667
3838
  recipient: Address.Address
3668
3839
  sender: Address.Address
3669
3840
  signer?: 'self' | Address.Address | undefined
3670
- sourceToken: Token
3841
+ sourceToken: UnenrichedToken
3671
3842
  },
3672
3843
  ): Extract<RawItem, { type: 'transfer' }> {
3673
3844
  return {
@@ -3792,75 +3963,187 @@ function lowerOrNull(value: unknown): Address.Address | null {
3792
3963
  return text ? (text.toLowerCase() as Address.Address) : null
3793
3964
  }
3794
3965
 
3795
- /** Builds a stub token; `decimals`/`symbol` are filled by {@link enrichItem}. */
3796
- function token(amount: bigint | undefined, address: Address.Address): Token {
3966
+ /** Builds a token stub whose metadata is filled by {@link enrichItem}. */
3967
+ function token(amount: bigint | undefined, address: Address.Address): UnenrichedToken {
3797
3968
  return { address: address.toLowerCase() as Address.Address, amount: (amount ?? 0n).toString() }
3798
3969
  }
3799
3970
 
3800
3971
  /** 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>>
3972
+ function collectTokens(item: UnenrichedItem, set: Set<Address.Address>) {
3973
+ const data = item.data as Partial<Record<(typeof tokenKeys)[number], UnenrichedToken>>
3803
3974
  for (const key of tokenKeys) {
3804
3975
  const t = data[key]
3805
3976
  if (t) set.add(t.address.toLowerCase() as Address.Address)
3806
3977
  }
3807
3978
  }
3808
3979
 
3809
- /** Resolved token metadata keyed by lowercased address. */
3810
- type ResolvedToken = Awaited<ReturnType<typeof Tokens.getTokenMetadata>>
3980
+ /** Resolved token fields keyed by lowercased address. */
3981
+ type ResolvedToken = {
3982
+ currency: string
3983
+ decimals: number
3984
+ logoUri?: string | undefined
3985
+ name: string
3986
+ symbol: string
3987
+ verified?: boolean | undefined
3988
+ }
3811
3989
 
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
3990
+ /** Resolves TIP-20 metadata or the standard ERC-20 subset used by activity transfers. */
3991
+ function getActivityTokenMetadata(
3992
+ c: Context<App.Environment>,
3993
+ options: getActivityTokenMetadata.Options,
3994
+ ): Promise<Omit<ResolvedToken, 'verified'>> {
3995
+ const { address, chainId } = options
3996
+ if (address.startsWith(tip20AddressPrefix))
3997
+ return Tokens.getTokenMetadata(c, { address, chainId })
3998
+
3999
+ return Store.memoize(
4000
+ async () => {
4001
+ const metadata = await viem_Token.getMetadata(c.get('getClient')(chainId), { token: address })
4002
+ // ERC-20 has no currency field; its symbol is the amount's unit label.
4003
+ return {
4004
+ currency: metadata.symbol,
4005
+ decimals: metadata.decimals,
4006
+ name: metadata.name,
4007
+ symbol: metadata.symbol,
4008
+ }
4009
+ },
4010
+ { key: `token:erc20:v1:${chainId}:${address}:metadata`, store: c.get('store'), ttl: Ttl.minutes(1) }, // prettier-ignore
4011
+ )
4012
+ }
4013
+
4014
+ declare namespace getActivityTokenMetadata {
4015
+ type Options = {
4016
+ /** Token contract address. */
4017
+ address: Address.Address
4018
+ /** Source chain id. */
4019
+ chainId: z.output<typeof Schema.ChainId>
4020
+ }
4021
+ }
4022
+
4023
+ /** Resolves core metadata into immutable items. Optional logo and verification sources are best-effort. */
4024
+ async function enrichTokens(
4025
+ c: Context<App.Environment>,
4026
+ options: enrichTokens.Options,
4027
+ ): Promise<{ items: Item[]; rates: FxOracle.RateSet | undefined }> {
4028
+ const { chainId, denomination, include, items, oracle } = options
3822
4029
  const addresses = new Set<Address.Address>()
3823
4030
  for (const item of items) collectTokens(item, addresses)
3824
- if (addresses.size === 0) return
4031
+ // Valuation keys off the curated snapshot; a snapshot failure degrades the
4032
+ // values to `null` rather than failing the feed.
4033
+ const snapshot =
4034
+ addresses.size > 0
4035
+ ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
4036
+ : undefined
4037
+ const rates = await Valuation.ratesFor(c, {
4038
+ currencies: snapshot
4039
+ ? [...addresses].flatMap((address) => {
4040
+ const held = snapshot.byAddress.get(address)?.currency
4041
+ return held === undefined ? [] : [held]
4042
+ })
4043
+ : [],
4044
+ denomination,
4045
+ oracle,
4046
+ })
4047
+ if (addresses.size === 0) return { items: items as Item[], rates }
4048
+
4049
+ const includeLogoUri = include.includes('token.logoUri')
4050
+ const includeVerified = include.includes('token.verified')
3825
4051
 
3826
4052
  const resolved = await Timing.time(c, 'tokens', () =>
3827
4053
  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
- ),
4054
+ [...addresses].map(async (address) => {
4055
+ const [logoUri, metadata] = await Promise.all([
4056
+ includeLogoUri
4057
+ ? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
4058
+ : undefined,
4059
+ getActivityTokenMetadata(c, { address, chainId }),
4060
+ ])
4061
+ return [
4062
+ address.toLowerCase(),
4063
+ {
4064
+ currency: metadata.currency,
4065
+ decimals: metadata.decimals,
4066
+ logoUri: includeLogoUri
4067
+ ? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
4068
+ : undefined,
4069
+ name: metadata.name,
4070
+ symbol: metadata.symbol,
4071
+ verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
4072
+ },
4073
+ ] as const
4074
+ }),
3833
4075
  ),
3834
4076
  )
3835
4077
  const byAddress = new Map(resolved)
3836
- for (const item of items) enrichItem(item, byAddress)
4078
+ return {
4079
+ items: items.map((item) => enrichItem(item, byAddress, { denomination, rates, snapshot })),
4080
+ rates,
4081
+ }
3837
4082
  }
3838
4083
 
3839
4084
  declare namespace enrichTokens {
3840
4085
  type Options = {
3841
4086
  chainId: z.output<typeof Schema.ChainId>
3842
- items: readonly Item[]
4087
+ /** Currency the amount valuations are denominated in (uppercase). */
4088
+ denomination: string
4089
+ include: readonly z.output<typeof schema.Include>[]
4090
+ items: readonly UnenrichedItem[]
4091
+ /** FX rate oracle backing amount valuation. */
4092
+ oracle: FxOracle.Oracle
3843
4093
  }
3844
4094
  }
3845
4095
 
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>>
4096
+ /** Returns an enriched copy of an item without mutating its cached token stubs. */
4097
+ function enrichItem(
4098
+ item: UnenrichedItem,
4099
+ byAddress: Map<string, ResolvedToken>,
4100
+ valuation: enrichItem.Valuation,
4101
+ ): Item {
4102
+ const source = item.data as Partial<Record<(typeof tokenKeys)[number], UnenrichedToken>>
4103
+ const data: Record<string, unknown> = { ...item.data }
3849
4104
  for (const key of tokenKeys) {
3850
- const t = data[key]
4105
+ const t = source[key]
3851
4106
  if (!t) continue
3852
4107
  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.
4108
+ if (!meta) throw new Error(`Token metadata unavailable for ${t.address}`)
4109
+
4110
+ data[amountKeyByTokenKey[key]] = {
4111
+ ...Value.tokenAmount({
4112
+ baseUnits: t.amount,
4113
+ currency: meta.currency,
4114
+ decimals: meta.decimals,
4115
+ }),
4116
+ // Valued via the curated display currency, never the on-chain metadata.
4117
+ valuation: Valuation.valuationFor({
4118
+ amount: BigInt(t.amount),
4119
+ denomination: valuation.denomination,
4120
+ rates: valuation.rates,
4121
+ token: valuation.snapshot?.byAddress.get(t.address.toLowerCase()),
4122
+ }),
4123
+ }
3856
4124
  data[key] = {
3857
- ...t,
4125
+ address: t.address,
3858
4126
  currency: meta.currency,
3859
4127
  decimals: meta.decimals,
4128
+ ...(meta.logoUri !== undefined ? { logoUri: meta.logoUri } : {}),
3860
4129
  name: meta.name,
3861
4130
  symbol: meta.symbol,
4131
+ ...(meta.verified !== undefined ? { verified: meta.verified } : {}),
3862
4132
  }
3863
4133
  }
4134
+ return { ...item, data } as Item
4135
+ }
4136
+
4137
+ declare namespace enrichItem {
4138
+ /** Valuation inputs threaded from {@link enrichTokens}. */
4139
+ type Valuation = {
4140
+ /** Currency the amount valuations are denominated in (uppercase). */
4141
+ denomination: string
4142
+ /** Oracle rate set, when loaded. */
4143
+ rates: FxOracle.RateSet | undefined
4144
+ /** Compiled verified-token snapshot, when available. */
4145
+ snapshot: VerifiedTokens.Snapshot | undefined
4146
+ }
3864
4147
  }
3865
4148
 
3866
4149
  /**
@@ -3869,8 +4152,31 @@ function enrichItem(item: Item, byAddress: Map<string, ResolvedToken | undefined
3869
4152
  * Must be called once over the full, timestamp-sorted item list.
3870
4153
  */
3871
4154
  export function groupByAccessKey(items: Item[]): Entry[] {
3872
- const entries: Entry[] = []
3873
- let currentGroup: { items: Item[]; signer: Address.Address } | null = null
4155
+ return groupByAccessKeyItems(items)
4156
+ }
4157
+
4158
+ type GroupableItem = {
4159
+ data: object
4160
+ id: string
4161
+ perspective: 'incoming' | 'outgoing'
4162
+ timestamp: string
4163
+ }
4164
+
4165
+ type GroupedItem<item extends GroupableItem> =
4166
+ | item
4167
+ | {
4168
+ data: { items: item[]; signer: Address.Address }
4169
+ id: string
4170
+ perspective: 'incoming' | 'outgoing'
4171
+ timestamp: string
4172
+ title: 'Group'
4173
+ type: 'group'
4174
+ }
4175
+
4176
+ /** Groups items while preserving whether their token data has been enriched. */
4177
+ function groupByAccessKeyItems<item extends GroupableItem>(items: item[]): GroupedItem<item>[] {
4178
+ const entries: GroupedItem<item>[] = []
4179
+ let currentGroup: { items: item[]; signer: Address.Address } | null = null
3874
4180
 
3875
4181
  for (const item of items) {
3876
4182
  const itemSigner = signerOf(item)
@@ -3903,12 +4209,15 @@ export function groupByAccessKey(items: Item[]): Entry[] {
3903
4209
  }
3904
4210
 
3905
4211
  /** The access-key signer of an item, or `self` when it has no access-key signer. */
3906
- function signerOf(item: Item): 'self' | Address.Address {
4212
+ function signerOf(item: GroupableItem): 'self' | Address.Address {
3907
4213
  return (item.data as { signer?: 'self' | Address.Address }).signer ?? 'self'
3908
4214
  }
3909
4215
 
3910
4216
  /** Unwraps single-item groups; otherwise wraps the items in a `group` entry. */
3911
- function flushGroup(group: { items: Item[]; signer: Address.Address }): Entry {
4217
+ function flushGroup<item extends GroupableItem>(group: {
4218
+ items: item[]
4219
+ signer: Address.Address
4220
+ }): GroupedItem<item> {
3912
4221
  if (group.items.length === 1) return group.items[0]!
3913
4222
  const firstItem = group.items[0]!
3914
4223
  const lastItem = group.items[group.items.length - 1]!