tapimo 0.10.2 → 0.12.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 (386) hide show
  1. package/dist/App.d.ts +4896 -2166
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +2 -2
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.d.ts +15 -0
  6. package/dist/Assets.d.ts.map +1 -1
  7. package/dist/Assets.js +7 -0
  8. package/dist/Assets.js.map +1 -1
  9. package/dist/Webhooks.d.ts +0 -75
  10. package/dist/Webhooks.d.ts.map +1 -1
  11. package/dist/Webhooks.js +1 -143
  12. package/dist/Webhooks.js.map +1 -1
  13. package/dist/admin/App.d.ts +127 -78
  14. package/dist/admin/App.d.ts.map +1 -1
  15. package/dist/admin/apps/earn-vaults.d.ts +115 -1
  16. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  17. package/dist/admin/apps/earn-vaults.js +59 -6
  18. package/dist/admin/apps/earn-vaults.js.map +1 -1
  19. package/dist/admin/apps/organizations.d.ts +3 -3
  20. package/dist/admin/apps/organizations.d.ts.map +1 -1
  21. package/dist/admin/apps/organizations.js +4 -2
  22. package/dist/admin/apps/organizations.js.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +83 -83
  24. package/dist/admin/ui.generated.d.ts +1 -1
  25. package/dist/admin/ui.generated.d.ts.map +1 -1
  26. package/dist/admin/ui.generated.js +151 -151
  27. package/dist/admin/ui.generated.js.map +1 -1
  28. package/dist/apps/data/App.d.ts +5924 -4310
  29. package/dist/apps/data/App.d.ts.map +1 -1
  30. package/dist/apps/data/App.js +61 -9
  31. package/dist/apps/data/App.js.map +1 -1
  32. package/dist/apps/data/ZoneSelection.d.ts +62 -0
  33. package/dist/apps/data/ZoneSelection.d.ts.map +1 -0
  34. package/dist/apps/data/ZoneSelection.js +44 -0
  35. package/dist/apps/data/ZoneSelection.js.map +1 -0
  36. package/dist/apps/data/routes/activities.d.ts +2368 -5
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +423 -114
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +131 -0
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/balances.d.ts +25 -25
  43. package/dist/apps/data/routes/balances.js +1 -1
  44. package/dist/apps/data/routes/balances.js.map +1 -1
  45. package/dist/apps/data/routes/blocks.d.ts +1 -1
  46. package/dist/apps/data/routes/blocks.js +1 -1
  47. package/dist/apps/data/routes/blocks.js.map +1 -1
  48. package/dist/apps/data/routes/earn.d.ts +135 -10
  49. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  50. package/dist/apps/data/routes/earn.js +157 -25
  51. package/dist/apps/data/routes/earn.js.map +1 -1
  52. package/dist/apps/data/routes/exchanges.d.ts +829 -123
  53. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  54. package/dist/apps/data/routes/exchanges.js +16 -13
  55. package/dist/apps/data/routes/exchanges.js.map +1 -1
  56. package/dist/apps/data/routes/fee-amm.d.ts +26 -26
  57. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  58. package/dist/apps/data/routes/fee-amm.js +9 -11
  59. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  60. package/dist/apps/data/routes/receipts.d.ts +54 -54
  61. package/dist/apps/data/routes/receipts.js +1 -1
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/rpc.js +24 -10
  64. package/dist/apps/data/routes/rpc.js.map +1 -1
  65. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  66. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  67. package/dist/apps/data/routes/tokenlist.js +16 -15
  68. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  69. package/dist/apps/data/routes/tokens.d.ts +17 -3
  70. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  71. package/dist/apps/data/routes/tokens.js +34 -15
  72. package/dist/apps/data/routes/tokens.js.map +1 -1
  73. package/dist/apps/data/routes/transactions.d.ts +1 -1
  74. package/dist/apps/data/routes/transactions.js +1 -1
  75. package/dist/apps/data/routes/transactions.js.map +1 -1
  76. package/dist/apps/data/routes/transfers.d.ts +13 -13
  77. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  78. package/dist/apps/data/routes/transfers.js +7 -9
  79. package/dist/apps/data/routes/transfers.js.map +1 -1
  80. package/dist/apps/data/routes/valuation.d.ts +21 -21
  81. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  82. package/dist/apps/data/routes/webhooks.d.ts +1011 -151
  83. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  84. package/dist/apps/data/routes/webhooks.js +126 -739
  85. package/dist/apps/data/routes/webhooks.js.map +1 -1
  86. package/dist/apps/data/routes/zones.d.ts +15 -15
  87. package/dist/apps/funding/App.d.ts +27 -27
  88. package/dist/apps/funding/routes/chains.d.ts.map +1 -1
  89. package/dist/apps/funding/routes/chains.js +5 -4
  90. package/dist/apps/funding/routes/chains.js.map +1 -1
  91. package/dist/apps/funding/routes/deposit-addresses.d.ts +18 -18
  92. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  93. package/dist/apps/funding/routes/deposit-addresses.js +8 -8
  94. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  95. package/dist/apps/funding/routes/providers.d.ts.map +1 -1
  96. package/dist/apps/funding/routes/providers.js +3 -2
  97. package/dist/apps/funding/routes/providers.js.map +1 -1
  98. package/dist/apps/funding/routes/quotes.d.ts.map +1 -1
  99. package/dist/apps/funding/routes/quotes.js +2 -2
  100. package/dist/apps/funding/routes/quotes.js.map +1 -1
  101. package/dist/apps/funding/routes/transfers.d.ts +33 -33
  102. package/dist/apps/management/App.d.ts +1433 -322
  103. package/dist/apps/management/App.d.ts.map +1 -1
  104. package/dist/apps/management/Billing.d.ts.map +1 -1
  105. package/dist/apps/management/Billing.js +19 -6
  106. package/dist/apps/management/Billing.js.map +1 -1
  107. package/dist/apps/management/routes/api-keys.d.ts +154 -94
  108. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  109. package/dist/apps/management/routes/api-keys.js +76 -30
  110. package/dist/apps/management/routes/api-keys.js.map +1 -1
  111. package/dist/apps/management/routes/billing.d.ts +103 -30
  112. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  113. package/dist/apps/management/routes/billing.js +91 -32
  114. package/dist/apps/management/routes/billing.js.map +1 -1
  115. package/dist/apps/management/routes/faucet.d.ts +12 -12
  116. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  117. package/dist/apps/management/routes/faucet.js +23 -12
  118. package/dist/apps/management/routes/faucet.js.map +1 -1
  119. package/dist/apps/management/routes/invitations.d.ts +46 -13
  120. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  121. package/dist/apps/management/routes/invitations.js +79 -29
  122. package/dist/apps/management/routes/invitations.js.map +1 -1
  123. package/dist/apps/management/routes/invite-links.d.ts +128 -83
  124. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  125. package/dist/apps/management/routes/invite-links.js +100 -41
  126. package/dist/apps/management/routes/invite-links.js.map +1 -1
  127. package/dist/apps/management/routes/me.d.ts +5 -5
  128. package/dist/apps/management/routes/me.d.ts.map +1 -1
  129. package/dist/apps/management/routes/me.js +14 -10
  130. package/dist/apps/management/routes/me.js.map +1 -1
  131. package/dist/apps/management/routes/members.d.ts +68 -47
  132. package/dist/apps/management/routes/members.d.ts.map +1 -1
  133. package/dist/apps/management/routes/members.js +37 -18
  134. package/dist/apps/management/routes/members.js.map +1 -1
  135. package/dist/apps/management/routes/orgs.d.ts +48 -8
  136. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  137. package/dist/apps/management/routes/orgs.js +48 -19
  138. package/dist/apps/management/routes/orgs.js.map +1 -1
  139. package/dist/apps/management/routes/projects.d.ts +130 -68
  140. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  141. package/dist/apps/management/routes/projects.js +72 -21
  142. package/dist/apps/management/routes/projects.js.map +1 -1
  143. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  144. package/dist/apps/management/routes/scopes.js +8 -5
  145. package/dist/apps/management/routes/scopes.js.map +1 -1
  146. package/dist/apps/management/routes/usage.d.ts +38 -38
  147. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  148. package/dist/apps/management/routes/usage.js +41 -31
  149. package/dist/apps/management/routes/usage.js.map +1 -1
  150. package/dist/apps/management/routes/verified-token-requests.d.ts +34 -6
  151. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
  152. package/dist/apps/management/routes/verified-token-requests.js +36 -17
  153. package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
  154. package/dist/apps/management/routes/webhooks.d.ts +1186 -197
  155. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  156. package/dist/apps/management/routes/webhooks.js +106 -38
  157. package/dist/apps/management/routes/webhooks.js.map +1 -1
  158. package/dist/apps/mpp/internal/Adapter.d.ts +1 -1
  159. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  160. package/dist/apps/mpp/internal/Adapter.js +12 -0
  161. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  162. package/dist/db/Db.d.ts +2 -0
  163. package/dist/db/Db.d.ts.map +1 -1
  164. package/dist/db/Db.js +61 -0
  165. package/dist/db/Db.js.map +1 -1
  166. package/dist/db/Schema.d.ts +30 -2
  167. package/dist/db/Schema.d.ts.map +1 -1
  168. package/dist/db/Schema.js +28 -3
  169. package/dist/db/Schema.js.map +1 -1
  170. package/dist/db/tables/earnVaults.d.ts +5 -1
  171. package/dist/db/tables/earnVaults.d.ts.map +1 -1
  172. package/dist/db/tables/earnVaults.js +5 -0
  173. package/dist/db/tables/earnVaults.js.map +1 -1
  174. package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
  175. package/dist/db/tables/webhookDeliveries.js +10 -0
  176. package/dist/db/tables/webhookDeliveries.js.map +1 -1
  177. package/dist/db/tables/webhookQueueCompletions.d.ts +15 -0
  178. package/dist/db/tables/webhookQueueCompletions.d.ts.map +1 -0
  179. package/dist/db/tables/webhookQueueCompletions.js +32 -0
  180. package/dist/db/tables/webhookQueueCompletions.js.map +1 -0
  181. package/dist/db/tables/webhookQueueEvents.d.ts +16 -20
  182. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
  183. package/dist/db/tables/webhookQueueEvents.js +68 -43
  184. package/dist/db/tables/webhookQueueEvents.js.map +1 -1
  185. package/dist/db/tables/webhookSubscriptions.d.ts +47 -72
  186. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  187. package/dist/db/tables/webhookSubscriptions.js +92 -105
  188. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  189. package/dist/internal/Auth.d.ts +8 -1
  190. package/dist/internal/Auth.d.ts.map +1 -1
  191. package/dist/internal/Auth.js +13 -3
  192. package/dist/internal/Auth.js.map +1 -1
  193. package/dist/internal/Cache.d.ts.map +1 -1
  194. package/dist/internal/Cache.js +11 -7
  195. package/dist/internal/Cache.js.map +1 -1
  196. package/dist/internal/EarnVaults.d.ts +41 -2
  197. package/dist/internal/EarnVaults.d.ts.map +1 -1
  198. package/dist/internal/EarnVaults.js +167 -0
  199. package/dist/internal/EarnVaults.js.map +1 -1
  200. package/dist/internal/MetricSink.d.ts +35 -125
  201. package/dist/internal/MetricSink.d.ts.map +1 -1
  202. package/dist/internal/MetricSink.js +50 -121
  203. package/dist/internal/MetricSink.js.map +1 -1
  204. package/dist/internal/OpenApi.d.ts +26 -22
  205. package/dist/internal/OpenApi.d.ts.map +1 -1
  206. package/dist/internal/OpenApi.js +128 -39
  207. package/dist/internal/OpenApi.js.map +1 -1
  208. package/dist/internal/Response.d.ts +13 -8
  209. package/dist/internal/Response.d.ts.map +1 -1
  210. package/dist/internal/Response.js +16 -5
  211. package/dist/internal/Response.js.map +1 -1
  212. package/dist/internal/Schema.d.ts +8 -8
  213. package/dist/internal/Schema.d.ts.map +1 -1
  214. package/dist/internal/Schema.js +10 -9
  215. package/dist/internal/Schema.js.map +1 -1
  216. package/dist/internal/Store.d.ts +15 -6
  217. package/dist/internal/Store.d.ts.map +1 -1
  218. package/dist/internal/Store.js +45 -15
  219. package/dist/internal/Store.js.map +1 -1
  220. package/dist/internal/Tidx.d.ts +2 -0
  221. package/dist/internal/Tidx.d.ts.map +1 -1
  222. package/dist/internal/Tidx.js +16 -4
  223. package/dist/internal/Tidx.js.map +1 -1
  224. package/dist/internal/WebhookTransfer.d.ts +1 -55
  225. package/dist/internal/WebhookTransfer.d.ts.map +1 -1
  226. package/dist/internal/WebhookTransfer.js +1 -239
  227. package/dist/internal/WebhookTransfer.js.map +1 -1
  228. package/dist/internal/Webhooks.d.ts +112 -56
  229. package/dist/internal/Webhooks.d.ts.map +1 -1
  230. package/dist/internal/Webhooks.js +124 -93
  231. package/dist/internal/Webhooks.js.map +1 -1
  232. package/dist/internal/funding/Deposit.d.ts +2 -2
  233. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  234. package/dist/internal/funding/Deposit.js +9 -8
  235. package/dist/internal/funding/Deposit.js.map +1 -1
  236. package/dist/internal/funding/DepositAddress.d.ts +4 -4
  237. package/dist/internal/funding/DepositAddress.d.ts.map +1 -1
  238. package/dist/internal/funding/DepositAddress.js +9 -8
  239. package/dist/internal/funding/DepositAddress.js.map +1 -1
  240. package/dist/internal/funding/DepositWebhook.d.ts +25 -0
  241. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
  242. package/dist/internal/funding/DepositWebhook.js +74 -0
  243. package/dist/internal/funding/DepositWebhook.js.map +1 -0
  244. package/dist/internal/funding/Provider.d.ts.map +1 -1
  245. package/dist/internal/funding/Provider.js +13 -12
  246. package/dist/internal/funding/Provider.js.map +1 -1
  247. package/dist/internal/funding/Reconciliation.d.ts +6 -0
  248. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  249. package/dist/internal/funding/Reconciliation.js +42 -10
  250. package/dist/internal/funding/Reconciliation.js.map +1 -1
  251. package/dist/internal/funding/index.d.ts +1 -0
  252. package/dist/internal/funding/index.d.ts.map +1 -1
  253. package/dist/internal/funding/index.js +1 -0
  254. package/dist/internal/funding/index.js.map +1 -1
  255. package/dist/internal/funding/providers/stargate.d.ts +19 -0
  256. package/dist/internal/funding/providers/stargate.d.ts.map +1 -0
  257. package/dist/internal/funding/providers/stargate.js +143 -0
  258. package/dist/internal/funding/providers/stargate.js.map +1 -0
  259. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  260. package/dist/internal/webhookDestinations/slack.js +16 -0
  261. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  262. package/dist/providers/index.d.ts +7 -0
  263. package/dist/providers/index.d.ts.map +1 -1
  264. package/dist/providers/index.js +3 -0
  265. package/dist/providers/index.js.map +1 -1
  266. package/package.json +2 -2
  267. package/src/App.test.ts +48 -0
  268. package/src/App.ts +7 -2
  269. package/src/Assets.test.ts +18 -0
  270. package/src/Assets.ts +22 -0
  271. package/src/Client.test-d.ts +287 -17
  272. package/src/Webhooks.ts +1 -220
  273. package/src/admin/apps/earn-vaults.test.ts +143 -0
  274. package/src/admin/apps/earn-vaults.ts +83 -6
  275. package/src/admin/apps/organizations.ts +6 -2
  276. package/src/admin/ui/src/routes/api-keys.tsx +36 -0
  277. package/src/admin/ui.generated.ts +151 -189
  278. package/src/apps/data/App.test.ts +31 -0
  279. package/src/apps/data/App.ts +64 -10
  280. package/src/apps/data/ZoneSelection.test.ts +196 -0
  281. package/src/apps/data/ZoneSelection.ts +108 -0
  282. package/src/apps/data/routes/activities.test.ts +435 -12
  283. package/src/apps/data/routes/activities.ts +562 -125
  284. package/src/apps/data/routes/balances.ts +1 -1
  285. package/src/apps/data/routes/blocks.ts +1 -1
  286. package/src/apps/data/routes/earn.test.ts +183 -23
  287. package/src/apps/data/routes/earn.ts +225 -29
  288. package/src/apps/data/routes/earn.zone-access.test.ts +46 -0
  289. package/src/apps/data/routes/exchanges.test.ts +31 -1
  290. package/src/apps/data/routes/exchanges.ts +19 -15
  291. package/src/apps/data/routes/fee-amm.ts +18 -11
  292. package/src/apps/data/routes/receipts.ts +1 -1
  293. package/src/apps/data/routes/rpc.test.ts +75 -0
  294. package/src/apps/data/routes/rpc.ts +23 -9
  295. package/src/apps/data/routes/tokenlist.ts +21 -18
  296. package/src/apps/data/routes/tokens.test.ts +29 -0
  297. package/src/apps/data/routes/tokens.ts +38 -15
  298. package/src/apps/data/routes/transactions.ts +1 -1
  299. package/src/apps/data/routes/transfers.test.ts +3 -3
  300. package/src/apps/data/routes/transfers.ts +12 -9
  301. package/src/apps/data/routes/webhooks.test.ts +179 -1497
  302. package/src/apps/data/routes/webhooks.ts +156 -976
  303. package/src/apps/data/routes/zones.test.ts +19 -13
  304. package/src/apps/funding/routes/chains.test.ts +211 -7
  305. package/src/apps/funding/routes/chains.ts +19 -12
  306. package/src/apps/funding/routes/deposit-addresses.test.ts +394 -0
  307. package/src/apps/funding/routes/deposit-addresses.ts +84 -72
  308. package/src/apps/funding/routes/providers.ts +9 -5
  309. package/src/apps/funding/routes/quotes.test.ts +46 -0
  310. package/src/apps/funding/routes/quotes.ts +8 -5
  311. package/src/apps/funding/routes/transfers.test.ts +35 -32
  312. package/src/apps/management/Billing.test.ts +26 -0
  313. package/src/apps/management/Billing.ts +24 -7
  314. package/src/apps/management/routes/api-keys.test.ts +178 -0
  315. package/src/apps/management/routes/api-keys.ts +203 -144
  316. package/src/apps/management/routes/billing.test.ts +209 -0
  317. package/src/apps/management/routes/billing.ts +254 -177
  318. package/src/apps/management/routes/faucet.test.ts +39 -0
  319. package/src/apps/management/routes/faucet.ts +75 -41
  320. package/src/apps/management/routes/invitations.test.ts +216 -0
  321. package/src/apps/management/routes/invitations.ts +186 -109
  322. package/src/apps/management/routes/invite-links.test.ts +263 -1
  323. package/src/apps/management/routes/invite-links.ts +295 -214
  324. package/src/apps/management/routes/me.test.ts +49 -0
  325. package/src/apps/management/routes/me.ts +42 -32
  326. package/src/apps/management/routes/members.test.ts +124 -1
  327. package/src/apps/management/routes/members.ts +81 -51
  328. package/src/apps/management/routes/orgs.test.ts +176 -0
  329. package/src/apps/management/routes/orgs.ts +101 -59
  330. package/src/apps/management/routes/projects.test.ts +183 -0
  331. package/src/apps/management/routes/projects.ts +124 -60
  332. package/src/apps/management/routes/scopes.test.ts +44 -0
  333. package/src/apps/management/routes/scopes.ts +37 -28
  334. package/src/apps/management/routes/usage.test.ts +92 -0
  335. package/src/apps/management/routes/usage.ts +186 -151
  336. package/src/apps/management/routes/verified-token-requests.test.ts +114 -0
  337. package/src/apps/management/routes/verified-token-requests.ts +166 -133
  338. package/src/apps/management/routes/webhooks.test.ts +313 -1
  339. package/src/apps/management/routes/webhooks.ts +220 -71
  340. package/src/apps/mpp/internal/Adapter.ts +13 -1
  341. package/src/db/Db.test.ts +42 -5
  342. package/src/db/Db.ts +85 -0
  343. package/src/db/Schema.ts +41 -3
  344. package/src/db/tables/earnVaults.test.ts +22 -0
  345. package/src/db/tables/earnVaults.ts +11 -1
  346. package/src/db/tables/organizations.test.ts +5 -2
  347. package/src/db/tables/webhookDeliveries.ts +13 -0
  348. package/src/db/tables/webhookQueueCompletions.ts +55 -0
  349. package/src/db/tables/webhookQueueEvents.ts +97 -57
  350. package/src/db/tables/webhookSubscriptions.ts +134 -144
  351. package/src/internal/Auth.ts +23 -5
  352. package/src/internal/Cache.ts +14 -7
  353. package/src/internal/EarnVaults.capabilities.test.ts +33 -0
  354. package/src/internal/EarnVaults.test.ts +79 -0
  355. package/src/internal/EarnVaults.ts +277 -0
  356. package/src/internal/MetricSink.test.ts +41 -373
  357. package/src/internal/MetricSink.ts +92 -273
  358. package/src/internal/OpenApi.ts +165 -49
  359. package/src/internal/Response.test.ts +95 -0
  360. package/src/internal/Response.ts +40 -2
  361. package/src/internal/Schema.test.ts +34 -0
  362. package/src/internal/Schema.ts +20 -9
  363. package/src/internal/Store.test.ts +186 -0
  364. package/src/internal/Store.ts +57 -17
  365. package/src/internal/Tidx.test.ts +19 -0
  366. package/src/internal/Tidx.ts +16 -4
  367. package/src/internal/WebhookTransfer.ts +1 -351
  368. package/src/internal/Webhooks.test.ts +222 -145
  369. package/src/internal/Webhooks.ts +234 -135
  370. package/src/internal/funding/Deposit.ts +71 -64
  371. package/src/internal/funding/DepositAddress.ts +50 -43
  372. package/src/internal/funding/DepositWebhook.test.ts +131 -0
  373. package/src/internal/funding/DepositWebhook.ts +101 -0
  374. package/src/internal/funding/Provider.ts +164 -142
  375. package/src/internal/funding/Reconciliation.test.ts +14 -0
  376. package/src/internal/funding/Reconciliation.ts +161 -86
  377. package/src/internal/funding/index.test.ts +1 -0
  378. package/src/internal/funding/index.ts +1 -0
  379. package/src/internal/funding/providers/index.test.ts +185 -0
  380. package/src/internal/funding/providers/stargate.ts +184 -0
  381. package/src/internal/webhookDestinations/slack.ts +15 -0
  382. package/src/providers/index.test.ts +1 -0
  383. package/src/providers/index.ts +9 -0
  384. package/src/Webhooks.bench.ts +0 -714
  385. package/src/Webhooks.memory.bench.ts +0 -250
  386. package/src/Webhooks.test.ts +0 -146
@@ -22,6 +22,7 @@ import * as Ttl from '../../../internal/Ttl.js'
22
22
  import * as Value from '../../../internal/Value.js'
23
23
  import * as VerifiedTokens from '../../../internal/VerifiedTokens.js'
24
24
  import * as FxOracle from '../FxOracle.js'
25
+ import * as ZoneSelection from '../ZoneSelection.js'
25
26
  import * as Tokens from './tokens.js'
26
27
  import * as Transfers from './transfers.js'
27
28
  import * as Valuation from './valuation.js'
@@ -29,7 +30,7 @@ import * as Valuation from './valuation.js'
29
30
  /** Example transaction hash surfaced in OpenAPI docs. */
30
31
  const exampleHash = '0x4845ae2098724ab26a5d89370dce5124d044be6a5acc164afc348662de67d474'
31
32
 
32
- const tokenFields = ['token.logoUri', 'token.verified'] as const
33
+ const includeFields = ['token.logoUri', 'token.verified', 'zones'] as const
33
34
 
34
35
  // TIP-20 tokens live at deterministic `0x20c0…` addresses. Other contracts
35
36
  // can emit the same `Transfer` signature but do not expose TIP-20 metadata.
@@ -202,6 +203,12 @@ export namespace schema {
202
203
  * transfer's `direction`/`memo`/`attribution`/`signer`) the resource lacks.
203
204
  */
204
205
  const base = {
206
+ chainId: z
207
+ .optional(Schema.ChainId)
208
+ .check(
209
+ z.describe('Chain id containing this activity. Returned for multi-Zone requests.'),
210
+ z.meta({ examples: [421700001] }),
211
+ ),
205
212
  perspective: z
206
213
  .enum(['incoming', 'outgoing'])
207
214
  .check(
@@ -989,9 +996,7 @@ export namespace schema {
989
996
  logoUri: z.string().check(
990
997
  z.describe('New logo URL for the token.'),
991
998
  z.meta({
992
- examples: [
993
- 'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
994
- ],
999
+ examples: [Tokens.tokenExample.logoUri],
995
1000
  }),
996
1001
  ),
997
1002
  signer: Signer,
@@ -1482,6 +1487,12 @@ export namespace schema {
1482
1487
  /** A folded access-key group: consecutive same-signer items on one UTC day. */
1483
1488
  export const Group = z
1484
1489
  .object({
1490
+ chainId: z
1491
+ .optional(Schema.ChainId)
1492
+ .check(
1493
+ z.describe('Chain id containing this activity group. Returned for multi-Zone requests.'),
1494
+ z.meta({ examples: [421700001] }),
1495
+ ),
1485
1496
  data: z
1486
1497
  .object({
1487
1498
  items: z.array(Item).check(z.describe('Activity items included in this group.')),
@@ -1523,15 +1534,15 @@ export namespace schema {
1523
1534
  .union([...items, Group])
1524
1535
  .check(z.describe('Either one activity item, or a grouped set of access-key activity items.'))
1525
1536
 
1526
- /** Optional token fields that require sources beyond RPC metadata. */
1537
+ /** Optional activity resources and selection modes. */
1527
1538
  export const Include = z
1528
- .enum(tokenFields)
1529
- .check(z.describe('Additional token fields to include in activity responses.'))
1539
+ .enum(includeFields)
1540
+ .check(z.describe('Additional activity resources and selection modes to include.'))
1530
1541
 
1531
- /** Parses comma-separated optional token fields. */
1542
+ /** Parses comma-separated optional activity resources and selection modes. */
1532
1543
  export const includeQuery = Schema.includeQuery(
1533
1544
  Include,
1534
- 'Comma-separated token fields to include, such as `token.logoUri,token.verified`.',
1545
+ 'Comma-separated activity options, such as `zones,token.logoUri,token.verified`.',
1535
1546
  )
1536
1547
 
1537
1548
  /** Schemas for the getAddressActivities operation. */
@@ -1636,6 +1647,10 @@ export namespace schema {
1636
1647
  */
1637
1648
  export const Response = z
1638
1649
  .object({
1650
+ chainId: Schema.ChainId.check(
1651
+ z.describe('Chain id containing the transaction.'),
1652
+ z.meta({ examples: [4217] }),
1653
+ ),
1639
1654
  data: z.array(Item).check(z.describe('Activity items classified from the transaction.')),
1640
1655
  meta: z
1641
1656
  .optional(schema.Meta)
@@ -1991,7 +2006,7 @@ export function addresses(options: addresses.Options = {}) {
1991
2006
  tags: ['Activities'],
1992
2007
  }),
1993
2008
  Cache.response<App.Environment>({
1994
- cacheControl: Cache.policies.feed,
2009
+ cacheControl: Cache.policies.noStore,
1995
2010
  name: 'tempo-api:addresses:v1',
1996
2011
  key: (c) => Cache.urlKey(c, schema.getAddressActivities.Query),
1997
2012
  }),
@@ -2011,19 +2026,19 @@ export function addresses(options: addresses.Options = {}) {
2011
2026
  const { address } = c.req.valid('param')
2012
2027
  const query = c.req.valid('query')
2013
2028
 
2014
- // Cursor pages can carry current valuations and curated verification data.
2015
- if (query.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
2029
+ if (!query.include.includes('zones'))
2030
+ Cache.setPolicy(
2031
+ c,
2032
+ query.cursor === undefined ? Cache.policies.feed : Cache.policies.metadata,
2033
+ )
2016
2034
 
2017
2035
  try {
2018
2036
  const chainId = query.chainId ?? c.get('chainId')
2019
- const provider = tidx({ chainId, tidx: c.get('getTidx')(chainId) })
2020
- const { items: unenrichedItems, nextCursor } = await Timing.time(
2021
- c,
2022
- 'address_activities',
2023
- () =>
2024
- Store.memoize(
2037
+ const listed = await Timing.time(c, 'address_activities', async () => {
2038
+ if (!query.include.includes('zones')) {
2039
+ const result = await Store.memoize(
2025
2040
  () =>
2026
- list(provider, {
2041
+ list(tidx({ chainId, tidx: c.get('getTidx')(chainId) }), {
2027
2042
  address: address as Hex.Hex,
2028
2043
  cursor: query.cursor,
2029
2044
  group: query.group,
@@ -2036,18 +2051,54 @@ export function addresses(options: addresses.Options = {}) {
2036
2051
  store: c.get('store'),
2037
2052
  ttl: Ttl.seconds(15),
2038
2053
  },
2039
- ),
2040
- )
2054
+ )
2055
+ return { chainId, kind: 'chain' as const, ...result }
2056
+ }
2041
2057
 
2042
- // Build immutable token and structured amount copies from RPC metadata.
2043
- const { items, rates } = await enrichTokens(c, {
2044
- chainId,
2045
- denomination: query['valuation.currency'],
2046
- include: query.include,
2047
- items: unenrichedItems,
2048
- oracle,
2058
+ const zoneChainIds = c.get('zoneChainIds')
2059
+ if (!zoneChainIds) throw new Error('Inferred Zone selection was not resolved.')
2060
+ const result = await Store.memoize(
2061
+ () =>
2062
+ listZones({
2063
+ address: address as Hex.Hex,
2064
+ cursor: query.cursor,
2065
+ group: query.group,
2066
+ limit: query.limit,
2067
+ logs: query.logs,
2068
+ providers: zoneChainIds.map((zoneChainId) => ({
2069
+ chainId: zoneChainId,
2070
+ provider: tidx({
2071
+ chainId: zoneChainId,
2072
+ tidx: c.get('getTidx')(zoneChainId),
2073
+ }),
2074
+ })),
2075
+ store: c.get('store'),
2076
+ }),
2077
+ {
2078
+ key: `activities:zones:v1:${zoneChainIds.join(',')}:${address.toLowerCase()}:${query.group ?? false}:${query.logs ?? false}:${query.cursor ?? 'head'}:${query.limit}`,
2079
+ store: c.get('store'),
2080
+ ttl: Ttl.seconds(15),
2081
+ },
2082
+ )
2083
+ return { kind: 'zones' as const, ...result }
2049
2084
  })
2050
2085
 
2086
+ const { items, rates } =
2087
+ listed.kind === 'zones'
2088
+ ? await enrichZoneTokens(c, {
2089
+ denomination: query['valuation.currency'],
2090
+ include: query.include,
2091
+ items: listed.items,
2092
+ oracle,
2093
+ })
2094
+ : await enrichTokens(c, {
2095
+ chainId: listed.chainId,
2096
+ denomination: query['valuation.currency'],
2097
+ include: query.include,
2098
+ items: listed.items,
2099
+ oracle,
2100
+ })
2101
+
2051
2102
  // Fold consecutive access-key-signed items into `group` entries only
2052
2103
  // when requested; otherwise every item is returned at the root.
2053
2104
  const data = query.group ? groupByAccessKey(items) : items
@@ -2056,7 +2107,7 @@ export function addresses(options: addresses.Options = {}) {
2056
2107
  Response.validated(schema.getAddressActivities.Response, {
2057
2108
  data,
2058
2109
  ...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
2059
- nextCursor,
2110
+ nextCursor: listed.nextCursor,
2060
2111
  }),
2061
2112
  200,
2062
2113
  )
@@ -2110,6 +2161,10 @@ export function transactions(options: transactions.Options = {}) {
2110
2161
  description: 'No mined transaction was found for that hash.',
2111
2162
  codes: ['transaction_not_found'],
2112
2163
  },
2164
+ 409: {
2165
+ description: 'More than one readable Zone contains that transaction hash.',
2166
+ codes: ['transaction_ambiguous'],
2167
+ },
2113
2168
  502: 'Tempo could not read activity or token data from an upstream service.',
2114
2169
  },
2115
2170
  success: {
@@ -2142,48 +2197,123 @@ export function transactions(options: transactions.Options = {}) {
2142
2197
 
2143
2198
  const { transactionHash } = c.req.valid('param')
2144
2199
  const query = c.req.valid('query')
2145
- const chainId = query.chainId ?? c.get('chainId')
2146
2200
 
2147
2201
  try {
2148
- const unenrichedItems = await Timing.time(c, 'transaction_activities', () =>
2149
- Store.memoize(
2150
- () =>
2151
- getTransactionActivities({
2152
- chainId,
2153
- logs: query.logs,
2202
+ const result = await Timing.time(c, 'transaction_activities', async () => {
2203
+ const selected = await (async () => {
2204
+ if (!query.include.includes('zones'))
2205
+ return {
2206
+ chainId: query.chainId ?? c.get('chainId'),
2207
+ kind: 'selected' as const,
2208
+ receipt: null,
2209
+ }
2210
+
2211
+ const zoneChainIds = c.get('zoneChainIds')
2212
+ if (!zoneChainIds) throw new Error('Inferred Zone selection was not resolved.')
2213
+ // Cache only positive resolution. Missing transactions must remain
2214
+ // visible as soon as their Zone indexer catches up.
2215
+ const match = await Store.memoize(
2216
+ async () => {
2217
+ const probes: readonly ZoneSelection.uniqueMatch.Probe<TransactionReceipt>[] =
2218
+ await Promise.all(
2219
+ zoneChainIds.map(async (chainId) => {
2220
+ try {
2221
+ return {
2222
+ chainId,
2223
+ value: await getTransactionReceipt({
2224
+ chainId,
2225
+ tidx: c.get('getTidx')(chainId),
2226
+ transactionHash,
2227
+ }),
2228
+ }
2229
+ } catch (cause) {
2230
+ return { cause, chainId }
2231
+ }
2232
+ }),
2233
+ )
2234
+ return ZoneSelection.uniqueMatch<TransactionReceipt>(probes)
2235
+ },
2236
+ {
2237
+ key: `activities:zones:v1:${zoneChainIds.join(',')}:tx:${transactionHash.toLowerCase()}`,
2238
+ shouldCache: (match) => match.kind === 'selected',
2154
2239
  store: c.get('store'),
2155
- tidx: c.get('getTidx')(chainId),
2156
- transactionHash,
2157
- }),
2240
+ ttl: Ttl.seconds(15),
2241
+ },
2242
+ )
2243
+ if (match.kind === 'failed') {
2244
+ c.set(
2245
+ 'providerFailures',
2246
+ match.failures.map(({ cause, chainId }) => ({
2247
+ ...Tidx.providerFailure(cause),
2248
+ chainId,
2249
+ })),
2250
+ )
2251
+ throw match.failures[0]!.cause
2252
+ }
2253
+ if (match.kind !== 'selected') return match
2254
+ return { chainId: match.chainId, kind: 'selected' as const, receipt: match.value }
2255
+ })()
2256
+
2257
+ if (selected.kind !== 'selected') return selected
2258
+ const { chainId } = selected
2259
+ const unenrichedItems = await Store.memoize(
2260
+ () =>
2261
+ selected.receipt === null
2262
+ ? getTransactionActivities({
2263
+ chainId,
2264
+ logs: query.logs,
2265
+ store: c.get('store'),
2266
+ tidx: c.get('getTidx')(chainId),
2267
+ transactionHash,
2268
+ })
2269
+ : classifyTransactionActivities({
2270
+ chainId,
2271
+ logs: query.logs,
2272
+ receipt: selected.receipt,
2273
+ store: c.get('store'),
2274
+ tidx: c.get('getTidx')(chainId),
2275
+ transactionHash,
2276
+ }),
2158
2277
  {
2159
2278
  key: `activities:v1:${chainId}:tx:${transactionHash.toLowerCase()}:${query.logs ?? false}`,
2160
2279
  store: c.get('store'),
2161
2280
  ttl: Ttl.seconds(15),
2162
2281
  },
2163
- ),
2164
- )
2282
+ )
2283
+ return unenrichedItems === null
2284
+ ? ({ kind: 'not_found' } as const)
2285
+ : ({ chainId, items: unenrichedItems, kind: 'found' } as const)
2286
+ })
2165
2287
 
2166
- if (unenrichedItems === null)
2288
+ if (result.kind === 'not_found')
2167
2289
  return Response.error(c, {
2168
2290
  code: 'transaction_not_found',
2169
2291
  message: 'Transaction not found',
2170
2292
  status: 404,
2171
2293
  })
2294
+ if (result.kind === 'ambiguous')
2295
+ return Response.error(c, {
2296
+ code: 'transaction_ambiguous',
2297
+ message: 'Transaction hash matched more than one readable Zone',
2298
+ status: 409,
2299
+ })
2172
2300
 
2173
2301
  // Mined activity can carry current valuations and verification data.
2174
- Cache.setPolicy(c, Cache.policies.metadata)
2302
+ if (!query.include.includes('zones')) Cache.setPolicy(c, Cache.policies.metadata)
2303
+ c.set('chainId', result.chainId)
2175
2304
 
2176
2305
  // Build immutable token and structured amount copies from RPC metadata.
2177
2306
  const { items, rates } = await enrichTokens(c, {
2178
- chainId,
2307
+ chainId: result.chainId,
2179
2308
  denomination: query['valuation.currency'],
2180
2309
  include: query.include,
2181
- items: unenrichedItems,
2310
+ items: result.items,
2182
2311
  oracle,
2183
2312
  })
2184
2313
 
2185
2314
  return c.json(
2186
2315
  Response.validated(schema.getTransactionActivities.Response, {
2316
+ chainId: result.chainId,
2187
2317
  data: items,
2188
2318
  ...(rates ? { meta: { valuation: Valuation.pricing(rates, oracle) } } : {}),
2189
2319
  }),
@@ -2219,7 +2349,15 @@ export declare namespace transactions {
2219
2349
  export async function getTransactionActivities(
2220
2350
  options: getTransactionActivities.Options,
2221
2351
  ): Promise<UnenrichedItem[] | null> {
2222
- const { chainId, logs = false, tidx: tidxClient } = options
2352
+ const receipt = await getTransactionReceipt(options)
2353
+ if (!receipt) return null
2354
+ return classifyTransactionActivities({ ...options, receipt })
2355
+ }
2356
+
2357
+ async function getTransactionReceipt(
2358
+ options: getTransactionReceipt.Options,
2359
+ ): Promise<TransactionReceipt | null> {
2360
+ const { chainId, tidx: tidxClient } = options
2223
2361
  const hash = options.transactionHash.toLowerCase()
2224
2362
 
2225
2363
  // The `receipts` table is indexed by `tx_hash`, so this point lookup needs no
@@ -2234,9 +2372,33 @@ export async function getTransactionActivities(
2234
2372
  const receipt = receiptResult.rows[0]
2235
2373
  if (!receipt) return null
2236
2374
 
2237
- const blockNum = Number(Value.toNumber(receipt['block_num']))
2238
- const feePayer = lowerOrNull(receipt['fee_payer'])
2239
- const txSender = lowerOrNull(receipt['from'])
2375
+ return {
2376
+ blockNum: Number(Value.toNumber(receipt['block_num'])),
2377
+ feePayer: lowerOrNull(receipt['fee_payer']),
2378
+ txSender: lowerOrNull(receipt['from']),
2379
+ }
2380
+ }
2381
+
2382
+ declare namespace getTransactionReceipt {
2383
+ type Options = Pick<getTransactionActivities.Options, 'chainId' | 'tidx' | 'transactionHash'>
2384
+ }
2385
+
2386
+ type TransactionReceipt = {
2387
+ /** Block number containing the transaction. */
2388
+ blockNum: number
2389
+ /** Account that paid the transaction fee, when indexed. */
2390
+ feePayer: Address.Address | null
2391
+ /** Account that sent the transaction, when indexed. */
2392
+ txSender: Address.Address | null
2393
+ }
2394
+
2395
+ async function classifyTransactionActivities(
2396
+ options: classifyTransactionActivities.Options,
2397
+ ): Promise<UnenrichedItem[]> {
2398
+ const { chainId, logs = false, receipt, tidx: tidxClient } = options
2399
+ const hash = options.transactionHash.toLowerCase()
2400
+ const { blockNum, feePayer, txSender } = receipt
2401
+ const fingerprintMap_promise = Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
2240
2402
 
2241
2403
  // `logs` is sorted by block position, so a bare `tx_hash =` scans to genesis;
2242
2404
  // pair it with the receipt's block number (see `embedReceipts` in receipts.ts).
@@ -2269,7 +2431,7 @@ export async function getTransactionActivities(
2269
2431
 
2270
2432
  // Best-effort MPP service directory: a fetch failure yields an empty map, so
2271
2433
  // enrichment never blocks classification.
2272
- const fingerprintMap = await Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
2434
+ const fingerprintMap = await fingerprintMap_promise
2273
2435
 
2274
2436
  // Anchor the viewer on the transaction sender: the sender initiated every
2275
2437
  // activity in the transaction, so `perspective` is `outgoing` and `signer`
@@ -2279,6 +2441,13 @@ export async function getTransactionActivities(
2279
2441
  return classifyGroup(group, viewer, fingerprintMap, logs).map(withTitle)
2280
2442
  }
2281
2443
 
2444
+ declare namespace classifyTransactionActivities {
2445
+ type Options = getTransactionActivities.Options & {
2446
+ /** Indexed receipt fields used to bound the log lookup. */
2447
+ receipt: TransactionReceipt
2448
+ }
2449
+ }
2450
+
2282
2451
  export declare namespace getTransactionActivities {
2283
2452
  /** Options for {@link getTransactionActivities}. */
2284
2453
  type Options = {
@@ -2298,8 +2467,8 @@ export declare namespace getTransactionActivities {
2298
2467
  /**
2299
2468
  * Lists classified activity for an address by running the shared pipeline
2300
2469
  * (`groupByTx` → `classifyGroup` → `enrichItem`) over rows from a
2301
- * {@link Provider}. Returns enriched, chain-tagged items plus the next cursor;
2302
- * callers run {@link groupByAccessKey} over the result.
2470
+ * {@link Provider}. Returns classified items plus the next cursor; callers run
2471
+ * {@link groupByAccessKey} over the result.
2303
2472
  */
2304
2473
  export async function list(provider: Provider, options: list.Options): Promise<list.ReturnType> {
2305
2474
  const { address, cursor, group = false, limit = 20, logs = false } = options
@@ -2307,7 +2476,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
2307
2476
 
2308
2477
  // Best-effort MPP service directory: a fetch failure yields an empty map, so
2309
2478
  // enrichment never blocks the feed.
2310
- const fingerprintMap = await Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
2479
+ const fingerprintMap_promise = Mpp.fingerprintMap({ store: options.store }).catch(() => ({}))
2311
2480
 
2312
2481
  let currentCursor: string | undefined = cursor
2313
2482
  const allItems: UnenrichedItem[] = []
@@ -2326,6 +2495,7 @@ export async function list(provider: Provider, options: list.Options): Promise<l
2326
2495
  hasMore = txGroups.length > limit
2327
2496
  const page = txGroups.slice(0, limit)
2328
2497
 
2498
+ const fingerprintMap = await fingerprintMap_promise
2329
2499
  if (page.length === 0) break
2330
2500
 
2331
2501
  allItems.push(
@@ -2374,6 +2544,175 @@ export declare namespace list {
2374
2544
  }
2375
2545
  }
2376
2546
 
2547
+ const zoneCursorVersion = 1
2548
+
2549
+ type ZonePageState = {
2550
+ /** Zone chain id backing this page state. */
2551
+ chainId: number
2552
+ /** Whether the current buffer reaches the end of this Zone feed. */
2553
+ exhausted: boolean
2554
+ /** Unconsumed transaction groups already fetched from this Zone. */
2555
+ groups: TxGroup[]
2556
+ /** Activity data source for this Zone. */
2557
+ provider: Provider
2558
+ }
2559
+
2560
+ /**
2561
+ * Lists address activity across multiple Zones in one stable timestamp-ordered
2562
+ * feed. Its opaque cursor retains each Zone provider's native continuation.
2563
+ */
2564
+ export async function listZones(options: listZones.Options): Promise<listZones.ReturnType> {
2565
+ const { address, cursor, limit = 20, logs = false, providers, store } = options
2566
+ const group = options.group ?? false
2567
+ const orderedProviders = [...providers].sort((a, b) => a.chainId - b.chainId)
2568
+ const chainIds = orderedProviders.map(({ chainId }) => chainId)
2569
+ const cursors = decodeZoneCursor(cursor, chainIds)
2570
+ const viewer = address.toLowerCase() as Address.Address
2571
+ const fingerprintMap_promise = Mpp.fingerprintMap({ store }).catch(() => ({}))
2572
+ const states: ZonePageState[] = orderedProviders.map(({ chainId, provider }) => ({
2573
+ chainId,
2574
+ exhausted: false,
2575
+ groups: [],
2576
+ provider,
2577
+ }))
2578
+ const allItems: listZones.Item[] = []
2579
+ let hasMore = false
2580
+
2581
+ for (let attempt = 0; attempt < 3; attempt++) {
2582
+ await Promise.all(
2583
+ states.map(async (state) => {
2584
+ if (state.exhausted || state.groups.length > 0) return
2585
+ const { rows } = await state.provider.fetchRows({
2586
+ address: viewer,
2587
+ cursor: cursors.get(state.chainId),
2588
+ limit,
2589
+ })
2590
+ const groups = groupByTx(rows).slice(0, limit + 1)
2591
+ state.exhausted = groups.length <= limit
2592
+ state.groups.push(...groups)
2593
+ }),
2594
+ )
2595
+ const candidates = states
2596
+ .flatMap((state) => state.groups.map((txGroup) => ({ state, txGroup })))
2597
+ .sort((a, b) => {
2598
+ if (a.txGroup.blockTimestamp !== b.txGroup.blockTimestamp)
2599
+ return b.txGroup.blockTimestamp - a.txGroup.blockTimestamp
2600
+ if (a.state.chainId !== b.state.chainId) return a.state.chainId - b.state.chainId
2601
+ if (a.txGroup.blockNum !== b.txGroup.blockNum)
2602
+ return b.txGroup.blockNum - a.txGroup.blockNum
2603
+ return b.txGroup.txIdx - a.txGroup.txIdx
2604
+ })
2605
+ const page = candidates.slice(0, limit)
2606
+ const fingerprintMap = await fingerprintMap_promise
2607
+ if (page.length === 0) break
2608
+
2609
+ for (const { state, txGroup } of page) {
2610
+ state.groups.splice(state.groups.indexOf(txGroup), 1)
2611
+ cursors.set(state.chainId, state.provider.cursorForGroup(txGroup))
2612
+ allItems.push(
2613
+ ...classifyGroup(txGroup, viewer, fingerprintMap, logs).map((item) => ({
2614
+ ...withTitle(item),
2615
+ chainId: state.chainId,
2616
+ })),
2617
+ )
2618
+ }
2619
+
2620
+ hasMore = states.some((state) => !state.exhausted || state.groups.length > 0)
2621
+ const enough = group
2622
+ ? groupByAccessKeyItems(allItems).length >= minGroupEntries
2623
+ : allItems.length >= limit
2624
+ if (enough || !hasMore) break
2625
+ }
2626
+
2627
+ // A consumed sentinel proves only that the provider may have more rows.
2628
+ // Probe drained buffers so `nextCursor` remains the end-of-list signal.
2629
+ await Promise.all(
2630
+ states.map(async (state) => {
2631
+ if (state.exhausted || state.groups.length > 0) return
2632
+ const { rows } = await state.provider.fetchRows({
2633
+ address: viewer,
2634
+ cursor: cursors.get(state.chainId),
2635
+ limit: 1,
2636
+ })
2637
+ const group = groupByTx(rows)[0]
2638
+ if (group) state.groups.push(group)
2639
+ else state.exhausted = true
2640
+ }),
2641
+ )
2642
+ hasMore = states.some((state) => !state.exhausted || state.groups.length > 0)
2643
+
2644
+ return {
2645
+ items: allItems,
2646
+ nextCursor: hasMore ? encodeZoneCursor(cursors, chainIds) : null,
2647
+ }
2648
+ }
2649
+
2650
+ export declare namespace listZones {
2651
+ /** One Zone activity provider. */
2652
+ type ChainProvider = {
2653
+ /** Zone chain id. */
2654
+ chainId: number
2655
+ /** Activity data source for the Zone. */
2656
+ provider: Provider
2657
+ }
2658
+
2659
+ /** A classified item tagged with its Zone chain id. */
2660
+ type Item = UnenrichedItem & {
2661
+ /** Zone chain id containing the activity. */
2662
+ chainId: number
2663
+ }
2664
+
2665
+ /** Options for {@link listZones}. */
2666
+ type Options = {
2667
+ /** Subject account address. */
2668
+ address: Address.Address
2669
+ /** Opaque multi-Zone cursor from a previous page. */
2670
+ cursor?: string | undefined
2671
+ /** Whether the caller will fold access-key items. */
2672
+ group?: boolean | undefined
2673
+ /** Target number of transaction groups per page. */
2674
+ limit?: number | undefined
2675
+ /** Attach each item's full transaction event capture. */
2676
+ logs?: boolean | undefined
2677
+ /** Zone providers participating in the merged feed. */
2678
+ providers: readonly ChainProvider[]
2679
+ /** Cache store for the MPP fingerprint-directory lookup. */
2680
+ store: Store.Store
2681
+ }
2682
+
2683
+ /** Multi-Zone classified page before token enrichment. */
2684
+ type ReturnType = {
2685
+ /** Timestamp-ordered, chain-tagged activity items. */
2686
+ items: Item[]
2687
+ /** Opaque continuation for every Zone, or null at the end. */
2688
+ nextCursor: string | null
2689
+ }
2690
+ }
2691
+
2692
+ function decodeZoneCursor(cursor: string | undefined, chainIds: readonly number[]) {
2693
+ const fields: Cursor.Field[] = ['int']
2694
+ for (const _chainId of chainIds) fields.push('int', 'id')
2695
+ const decoded = cursor ? Cursor.decode(cursor, fields) : undefined
2696
+ const cursors = new Map<number, string>()
2697
+ if (!decoded || decoded[0] !== zoneCursorVersion) return cursors
2698
+
2699
+ for (let index = 0; index < chainIds.length; index++) {
2700
+ const chainId = chainIds[index]!
2701
+ const offset = index * 2 + 1
2702
+ if (decoded[offset] !== chainId) return new Map()
2703
+ const value = decoded[offset + 1] as string
2704
+ if (value !== 'head') cursors.set(chainId, value)
2705
+ }
2706
+ return cursors
2707
+ }
2708
+
2709
+ function encodeZoneCursor(cursors: ReadonlyMap<number, string>, chainIds: readonly number[]) {
2710
+ return Cursor.encode([
2711
+ zoneCursorVersion,
2712
+ ...chainIds.flatMap((chainId) => [chainId, cursors.get(chainId) ?? 'head']),
2713
+ ])
2714
+ }
2715
+
2377
2716
  /**
2378
2717
  * Creates a TIDX-backed {@link Provider} that runs three sub-queries in
2379
2718
  * parallel and merges them:
@@ -2727,15 +3066,46 @@ function classifyFromLogs(
2727
3066
  viewer: Address.Address
2728
3067
  },
2729
3068
  ): RawItem[] {
2730
- const items = classifyFromLogsCore(rows, ctx)
3069
+ const decoded = decodeRows(rows)
3070
+ const items = (() => {
3071
+ try {
3072
+ return classifyFromLogsCore(rows, ctx, decoded)
3073
+ } catch {
3074
+ // Indexed logs can match a known signature without satisfying that event's expected shape.
3075
+ return [
3076
+ unknown(ctx.itemBase(Math.min(...rows.map((row) => row.logIdx))), {
3077
+ events: buildTxEvents(rows, decoded),
3078
+ signer: signerForRows(rows, { fallback: ctx.signer, viewer: ctx.viewer }),
3079
+ }),
3080
+ ]
3081
+ }
3082
+ })()
2731
3083
  // When the caller requested raw logs, attach the full per-transaction log set
2732
3084
  // (decoded when possible) to every classified item so no co-located log is
2733
3085
  // hidden behind the headline. `unknown` items already carry it.
2734
3086
  if (!ctx.includeLogs) return items
2735
- const events = buildTxEvents(rows, decodeRows(rows))
3087
+ const events = buildTxEvents(rows, decoded)
2736
3088
  return items.map((item) => (item.type === 'unknown' ? item : { ...item, events }))
2737
3089
  }
2738
3090
 
3091
+ /** Resolves an access-key signer from raw rows, falling back to the transaction signer. */
3092
+ function signerForRows(
3093
+ rows: readonly Row[],
3094
+ options: { fallback: 'self' | Address.Address; viewer: Address.Address },
3095
+ ) {
3096
+ const accessKeySpendRow = rows.find(
3097
+ (row) =>
3098
+ row.selector === accessKeySpendSelector &&
3099
+ Address.isEqual(row.logAddress, Addresses.accountKeychain),
3100
+ )
3101
+ const accessKeySigner = accessKeySpendRow?.topic2
3102
+ ? (`0x${accessKeySpendRow.topic2.slice(-40)}`.toLowerCase() as Address.Address)
3103
+ : null
3104
+ return accessKeySigner && !Address.isEqual(accessKeySigner, options.viewer)
3105
+ ? accessKeySigner
3106
+ : options.fallback
3107
+ }
3108
+
2739
3109
  /** Classifies a Q1 (log-bearing) group via `parseEventLogs`, picking one headline item. */
2740
3110
  function classifyFromLogsCore(
2741
3111
  rows: Row[],
@@ -2746,24 +3116,11 @@ function classifyFromLogsCore(
2746
3116
  signer: 'self' | Address.Address
2747
3117
  viewer: Address.Address
2748
3118
  },
3119
+ events = decodeRows(rows),
2749
3120
  ): RawItem[] {
2750
3121
  const { blockNum, fingerprintMap, itemBase, viewer } = ctx
2751
3122
 
2752
- // An access-key-signed tx emits AccessKeySpend at the accountKeychain; topic2
2753
- // is the access-key publicKey. Use it as the signer so successive access-key
2754
- // actions group together.
2755
- const accessKeySpendRow = rows.find(
2756
- (r) =>
2757
- r.selector === accessKeySpendSelector &&
2758
- Address.isEqual(r.logAddress, Addresses.accountKeychain),
2759
- )
2760
- const accessKeySigner = accessKeySpendRow?.topic2
2761
- ? (`0x${accessKeySpendRow.topic2.slice(-40)}`.toLowerCase() as Address.Address)
2762
- : null
2763
- const signer: 'self' | Address.Address =
2764
- accessKeySigner && !Address.isEqual(accessKeySigner, viewer) ? accessKeySigner : ctx.signer
2765
-
2766
- const events = decodeRows(rows)
3123
+ const signer = signerForRows(rows, { fallback: ctx.signer, viewer })
2767
3124
 
2768
3125
  const minLogIndex = Math.min(...rows.map((r) => r.logIdx))
2769
3126
 
@@ -3251,6 +3608,14 @@ function classifyFromLogsCore(
3251
3608
  const approvalEvent = finalEvents.find(
3252
3609
  (e) =>
3253
3610
  e.eventName === 'Approval' &&
3611
+ // ERC-721 uses the same selector but indexes its token ID as topic3. In
3612
+ // non-strict mode viem otherwise decodes the missing ERC-20 amount as zero.
3613
+ rows.some(
3614
+ (row) =>
3615
+ row.logIdx === e.logIndex &&
3616
+ row.topic3 === null &&
3617
+ /^0x[0-9a-fA-F]{64}$/.test(row.data ?? ''),
3618
+ ) &&
3254
3619
  !Address.isEqual((e.args as { spender: Address.Address }).spender, Addresses.zoneOutbox),
3255
3620
  )
3256
3621
  if (approvalEvent) {
@@ -4338,21 +4703,27 @@ function unknown(
4338
4703
 
4339
4704
  /** Decodes a group's raw rows into `viem/tempo` events via `parseEventLogs` (non-strict). */
4340
4705
  function decodeRows(rows: readonly Row[]) {
4341
- const viemLogs: Log[] = rows.map((r) => ({
4342
- address: r.logAddress as `0x${string}`,
4343
- blockHash: '0x0' as Hex.Hex,
4344
- blockNumber: BigInt(r.blockNum),
4345
- data: (r.data ?? '0x') as Hex.Hex,
4346
- logIndex: r.logIdx,
4347
- removed: false,
4348
- topics: [r.selector, r.topic1, r.topic2, r.topic3].filter(Boolean) as [Hex.Hex, ...Hex.Hex[]],
4349
- transactionHash: r.txHash as `0x${string}`,
4350
- transactionIndex: r.txIdx,
4351
- }))
4352
- return parseEventLogs({
4353
- abi: [...Abis.abis, ...Abis.earnRouter, ...Abis.earnVault],
4354
- logs: viemLogs,
4355
- strict: false,
4706
+ return rows.flatMap((r) => {
4707
+ const log: Log = {
4708
+ address: r.logAddress as `0x${string}`,
4709
+ blockHash: '0x0' as Hex.Hex,
4710
+ blockNumber: BigInt(r.blockNum),
4711
+ data: (r.data ?? '0x') as Hex.Hex,
4712
+ logIndex: r.logIdx,
4713
+ removed: false,
4714
+ topics: [r.selector, r.topic1, r.topic2, r.topic3].filter(Boolean) as [Hex.Hex, ...Hex.Hex[]],
4715
+ transactionHash: r.txHash as `0x${string}`,
4716
+ transactionIndex: r.txIdx,
4717
+ }
4718
+ try {
4719
+ return parseEventLogs({
4720
+ abi: [...Abis.abis, ...Abis.earnRouter, ...Abis.earnVault],
4721
+ logs: [log],
4722
+ strict: false,
4723
+ })
4724
+ } catch {
4725
+ return []
4726
+ }
4356
4727
  })
4357
4728
  }
4358
4729
 
@@ -4489,49 +4860,63 @@ async function enrichTokens(
4489
4860
  for (const item of items) collectTokens(item, addresses)
4490
4861
  const includeLogoUri = include.includes('token.logoUri')
4491
4862
  const includeVerified = include.includes('token.verified')
4863
+ const resolved_promise =
4864
+ addresses.size > 0
4865
+ ? Timing.time(c, 'tokens', () =>
4866
+ Promise.all(
4867
+ [...addresses].map(async (address) => {
4868
+ const [logoUri, metadata] = await Promise.all([
4869
+ includeLogoUri
4870
+ ? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
4871
+ : undefined,
4872
+ getActivityTokenMetadata(c, { address, chainId }),
4873
+ ])
4874
+ return { address: address.toLowerCase(), logoUri, metadata }
4875
+ }),
4876
+ ),
4877
+ )
4878
+ : undefined
4492
4879
  // Curated data is needed only for requested valuation or token fields.
4493
- const snapshot =
4880
+ const snapshot_promise =
4494
4881
  addresses.size > 0 && (denomination !== undefined || includeLogoUri || includeVerified)
4495
- ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
4496
- : undefined
4497
- const rates = denomination
4498
- ? await Valuation.ratesFor(c, {
4499
- currencies: snapshot
4500
- ? [...addresses].flatMap((address) => {
4501
- const held = snapshot.byAddress.get(address)?.currency
4502
- return held === undefined ? [] : [held]
4503
- })
4504
- : [],
4505
- denomination,
4506
- oracle,
4507
- })
4508
- : undefined
4509
- if (addresses.size === 0) return { items: items as Item[], rates }
4510
-
4511
- const resolved = await Timing.time(c, 'tokens', () =>
4512
- Promise.all(
4513
- [...addresses].map(async (address) => {
4514
- const [logoUri, metadata] = await Promise.all([
4515
- includeLogoUri
4516
- ? Tokens.getTokenLogo(c, { address, chainId }).catch(() => undefined)
4882
+ ? VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
4883
+ : Promise.resolve(undefined)
4884
+ const rates_promise = (async () => {
4885
+ if (!denomination) return undefined
4886
+ const snapshot = await snapshot_promise
4887
+ return Valuation.ratesFor(c, {
4888
+ currencies: snapshot
4889
+ ? [...addresses].flatMap((address) => {
4890
+ const held = snapshot.byAddress.get(address)?.currency
4891
+ return held === undefined ? [] : [held]
4892
+ })
4893
+ : [],
4894
+ denomination,
4895
+ oracle,
4896
+ })
4897
+ })()
4898
+ if (!resolved_promise) return { items: items as Item[], rates: await rates_promise }
4899
+
4900
+ const [resolved_raw, rates, snapshot] = await Promise.all([
4901
+ resolved_promise,
4902
+ rates_promise,
4903
+ snapshot_promise,
4904
+ ])
4905
+ const resolved = resolved_raw.map(
4906
+ ({ address, logoUri, metadata }) =>
4907
+ [
4908
+ address,
4909
+ {
4910
+ currency: metadata.currency,
4911
+ decimals: metadata.decimals,
4912
+ logoUri: includeLogoUri
4913
+ ? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
4517
4914
  : undefined,
4518
- getActivityTokenMetadata(c, { address, chainId }),
4519
- ])
4520
- return [
4521
- address.toLowerCase(),
4522
- {
4523
- currency: metadata.currency,
4524
- decimals: metadata.decimals,
4525
- logoUri: includeLogoUri
4526
- ? (logoUri ?? snapshot?.byAddress.get(address)?.logoUri ?? metadata.logoUri)
4527
- : undefined,
4528
- name: metadata.name,
4529
- symbol: metadata.symbol,
4530
- verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
4531
- },
4532
- ] as const
4533
- }),
4534
- ),
4915
+ name: metadata.name,
4916
+ symbol: metadata.symbol,
4917
+ verified: includeVerified && snapshot ? snapshot.byAddress.has(address) : undefined,
4918
+ },
4919
+ ] as const,
4535
4920
  )
4536
4921
  const byAddress = new Map(resolved)
4537
4922
  return {
@@ -4552,6 +4937,54 @@ declare namespace enrichTokens {
4552
4937
  }
4553
4938
  }
4554
4939
 
4940
+ /** Resolves token metadata on each item's source Zone without changing feed order. */
4941
+ async function enrichZoneTokens(
4942
+ c: Context<App.Environment>,
4943
+ options: enrichZoneTokens.Options,
4944
+ ): Promise<{ items: Item[]; rates: FxOracle.RateSet | undefined }> {
4945
+ const byChain = new Map<number, listZones.Item[]>()
4946
+ for (const item of options.items) {
4947
+ const items = byChain.get(item.chainId) ?? []
4948
+ items.push(item)
4949
+ byChain.set(item.chainId, items)
4950
+ }
4951
+ const results = await Promise.all(
4952
+ [...byChain].map(async ([chainId, items]) => ({
4953
+ chainId,
4954
+ ...(await enrichTokens(c, {
4955
+ chainId,
4956
+ denomination: options.denomination,
4957
+ include: options.include,
4958
+ items,
4959
+ oracle: options.oracle,
4960
+ })),
4961
+ })),
4962
+ )
4963
+ type Bucket = { index: number; items: Item[] }
4964
+ const buckets = new Map<number, Bucket>()
4965
+ for (const result of results) buckets.set(result.chainId, { index: 0, items: result.items })
4966
+ return {
4967
+ items: options.items.map((item) => {
4968
+ const bucket = buckets.get(item.chainId)!
4969
+ return bucket.items[bucket.index++]!
4970
+ }),
4971
+ rates: results.find((result) => result.rates !== undefined)?.rates,
4972
+ }
4973
+ }
4974
+
4975
+ declare namespace enrichZoneTokens {
4976
+ type Options = {
4977
+ /** Currency the amount valuations are denominated in. */
4978
+ denomination?: string | undefined
4979
+ /** Optional token fields to resolve. */
4980
+ include: readonly z.output<typeof schema.Include>[]
4981
+ /** Chain-tagged items awaiting token enrichment. */
4982
+ items: readonly listZones.Item[]
4983
+ /** FX rate oracle backing amount valuation. */
4984
+ oracle: FxOracle.Oracle
4985
+ }
4986
+ }
4987
+
4555
4988
  /** Returns an enriched copy of an item without mutating its cached token stubs. */
4556
4989
  function enrichItem(
4557
4990
  item: UnenrichedItem,
@@ -4619,6 +5052,7 @@ export function groupByAccessKey(items: Item[]): Entry[] {
4619
5052
  }
4620
5053
 
4621
5054
  type GroupableItem = {
5055
+ chainId?: number | undefined
4622
5056
  data: object
4623
5057
  id: string
4624
5058
  perspective: 'incoming' | 'outgoing'
@@ -4628,6 +5062,7 @@ type GroupableItem = {
4628
5062
  type GroupedItem<item extends GroupableItem> =
4629
5063
  | item
4630
5064
  | {
5065
+ chainId?: number | undefined
4631
5066
  data: { items: item[]; signer: Address.Address }
4632
5067
  id: string
4633
5068
  perspective: 'incoming' | 'outgoing'
@@ -4646,6 +5081,7 @@ function groupByAccessKeyItems<item extends GroupableItem>(items: item[]): Group
4646
5081
  if (itemSigner !== 'self') {
4647
5082
  if (
4648
5083
  currentGroup &&
5084
+ currentGroup.items[0]!.chainId === item.chainId &&
4649
5085
  Address.isEqual(currentGroup.signer, itemSigner) &&
4650
5086
  sameCalendarDay(currentGroup.items[0]!.timestamp, item.timestamp)
4651
5087
  ) {
@@ -4685,6 +5121,7 @@ function flushGroup<item extends GroupableItem>(group: {
4685
5121
  const firstItem = group.items[0]!
4686
5122
  const lastItem = group.items[group.items.length - 1]!
4687
5123
  return {
5124
+ ...(firstItem.chainId === undefined ? {} : { chainId: firstItem.chainId }),
4688
5125
  data: { items: group.items, signer: group.signer },
4689
5126
  perspective: firstItem.perspective,
4690
5127
  id: `${firstItem.id}..${lastItem.id}:${group.items.length}`,