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
@@ -14,7 +14,9 @@ import * as Timing from '../../../internal/Timing.js';
14
14
  import * as Ttl from '../../../internal/Ttl.js';
15
15
  import * as Value from '../../../internal/Value.js';
16
16
  import * as VerifiedTokens from '../../../internal/VerifiedTokens.js';
17
+ import * as FxOracle from '../FxOracle.js';
17
18
  import * as Tokens from './tokens.js';
19
+ import * as Valuation from './valuation.js';
18
20
  // TIP-20 tokens emit the standard ERC-20 Transfer event. Supplying the
19
21
  // signature exposes a decoded `Transfer` CTE (with `from`/`to`/`value`) to
20
22
  // the indexer SQL.
@@ -24,52 +26,44 @@ export const signature = 'event Transfer(address indexed from, address indexed t
24
26
  // text memo is decoded into `memo`; an MPP attribution fingerprint memo is
25
27
  // resolved into `attribution` instead.
26
28
  const memoSignature = 'event TransferWithMemo(address indexed from, address indexed to, uint256 amount, bytes32 indexed memo)';
29
+ const tokenFields = ['token.logoUri', 'token.verified'];
27
30
  /** Zod schemas owned by the transfer handlers. */
28
31
  export var schema;
29
32
  (function (schema) {
30
- /**
31
- * The token moved by a transfer: its contract address and the transferred
32
- * amount, plus the trimmed token reference spread in when requested via
33
- * `include=token`. Reference fields are absent on the base (no-`include`)
34
- * response and best-effort when requested (a row whose metadata is
35
- * unavailable keeps just `address`/`amount`). Shared by every per-row
36
- * transfer response across the API.
37
- *
38
- * The optional metadata fields are inlined here (rather than spread from
39
- * `Tokens.schema.Token` via `z.partial`) so this module avoids referencing
40
- * the `tokens` schema namespace at module-init time — `transfers` and
41
- * `tokens` import each other for runtime helpers (`Transfers.query`,
42
- * `Tokens.resolveToken`), and the schema-evaluation back-edge would
43
- * otherwise deadlock the namespace.
44
- */
33
+ /** A transfer token reference with RPC metadata. Inlined to avoid the `transfers`/`tokens` module cycle. */
45
34
  schema.SourceToken = z
46
35
  .object({
47
36
  address: Schema.TokenAddress.check(z.describe('The TIP-20 token contract address on Tempo.')),
48
- amount: z
49
- .string()
50
- .check(z.regex(/^\d+$/), z.describe('A non-negative whole number, given as a decimal string so very large token amounts keep full precision. Expressed in the smallest unit of the token.'), z.meta({ examples: ['1000000'] })),
51
37
  currency: z
52
- .optional(z.string())
38
+ .string()
53
39
  .check(z.describe('The currency label for this token, such as `USD` for USD-denominated stablecoins.'), z.meta({ examples: ['USD'] })),
54
40
  decimals: z
55
- .optional(z.number().check(z.int(), z.nonnegative()))
41
+ .number()
42
+ .check(z.int(), z.nonnegative())
56
43
  .check(z.describe('The number of decimal places the token uses; Tempo stablecoins typically use 6.'), z.meta({ examples: [6] })),
57
- logoUri: z.optional(z.string()).check(z.describe('A URL for the token’s logo image, when one is available.'), z.meta({
44
+ logoUri: z.optional(z.string()).check(z.describe('A URL for the token’s logo image, returned with `include=token.logoUri`.'), z.meta({
58
45
  examples: [
59
46
  'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
60
47
  ],
61
48
  })),
62
49
  name: z
63
- .optional(z.string())
50
+ .string()
64
51
  .check(z.describe('The token’s human-readable name.'), z.meta({ examples: ['USD Coin'] })),
65
52
  symbol: z
66
- .optional(z.string())
53
+ .string()
67
54
  .check(z.describe('The short ticker symbol wallets and apps show for this token.'), z.meta({ examples: ['USDC'] })),
68
55
  verified: z
69
56
  .optional(z.boolean())
70
- .check(z.describe('Whether this token is in Tempo’s curated verified token list.'), z.meta({ examples: [true] })),
57
+ .check(z.describe('Whether this token is in Tempo’s curated verified list, returned with `include=token.verified`.'), z.meta({ examples: [true] })),
71
58
  })
72
- .check(z.describe('The token and amount that moved in this transfer, with token metadata added when you request `include=token`.'));
59
+ .check(z.describe('The token that moved in this transfer, with RPC metadata and optional curated fields.'));
60
+ /** A token amount carrying its nominal value in the requested denomination. */
61
+ const ValuedAmount = z.extend(Schema.TokenAmount, {
62
+ valuation: z
63
+ .optional(z.nullable(Valuation.schema.Value))
64
+ .check(z.describe('The amount’s nominal value in the requested `denomination`. `null` when the token is ' +
65
+ 'unverified, its display currency has no rate, or rates were unavailable.')),
66
+ });
73
67
  /**
74
68
  * Filter predicates shared verbatim by the `GET /transfers` list query
75
69
  * ({@link getTransfers.Query}, which spreads `Predicates.shape`) and `transfer`
@@ -99,15 +93,14 @@ export var schema;
99
93
  (function (getTransfers) {
100
94
  /** Optional related resources that callers opt into via `include`. */
101
95
  getTransfers.Include = z
102
- .enum(['crossToken', 'memo', 'token', 'totalCount'])
96
+ .enum(['crossToken', 'memo', ...tokenFields, 'totalCount'])
103
97
  .check(z.describe('Related resources to include only when you request them with `include`.'));
104
98
  /**
105
99
  * Parses a comma-separated `include` query value into a list of optional
106
- * resources to embed. Extra lookups (token metadata, the `TransferWithMemo`
107
- * memo/attribution decode, the capped total count) each run only when their
108
- * resource is requested, keeping the base transfer page fast.
100
+ * resources to embed. Extra lookups run only when their resource is
101
+ * requested, keeping the base transfer page fast.
109
102
  */
110
- getTransfers.includeQuery = Schema.includeQuery(getTransfers.Include, 'Comma-separated related resources to include, such as `token,memo,crossToken,totalCount`.');
103
+ getTransfers.includeQuery = Schema.includeQuery(getTransfers.Include, 'Comma-separated related resources to include, such as `token.logoUri,token.verified,memo,crossToken,totalCount`.');
111
104
  /**
112
105
  * Query parameters for top-level transfer list requests. `address` is a
113
106
  * convenience for "either side" — passing it alongside an explicit
@@ -127,6 +120,7 @@ export var schema;
127
120
  'blockNumber.to': Schema.blockNumberBound('transfers', 'to'),
128
121
  chainId: Schema.ChainIdQuery,
129
122
  cursor: Schema.Cursor,
123
+ denomination: Schema.Denomination,
130
124
  include: getTransfers.includeQuery,
131
125
  limit: Schema.Limit,
132
126
  order: Schema.Order,
@@ -152,9 +146,12 @@ export var schema;
152
146
  blockNumber: z
153
147
  .number()
154
148
  .check(z.int(), z.nonnegative(), z.describe('The block number where this transfer was recorded.'), z.meta({ examples: [23456789] })),
149
+ destinationAmount: z
150
+ .optional(ValuedAmount)
151
+ .check(z.describe('Amount delivered to the recipient for a cross-token transfer. Omitted with `destinationToken` for a same-token transfer.')),
155
152
  destinationToken: z
156
153
  .optional(schema.SourceToken)
157
- .check(z.describe('The token and amount delivered to the recipient when it differs from `sourceToken` — a cross-token transfer routed through the DEX, where `sourceToken` is the token swapped in. Omitted for same-token transfers, and only returned when you request `include=crossToken`.')),
154
+ .check(z.describe('The token delivered to the recipient when it differs from `sourceToken`. Omitted for same-token transfers and returned only with `include=crossToken`.')),
158
155
  id: z.string().check(z.describe('A stable resource ID for this transfer, built from `${transactionHash}-${logIndex}`.'), z.meta({
159
156
  examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-0'],
160
157
  })),
@@ -166,30 +163,46 @@ export var schema;
166
163
  .check(z.describe('The decoded text memo from the matching `TransferWithMemo` event, when one is present. Returned when you request `include=memo`.'), z.meta({ examples: ['Payment for invoice 123'] })),
167
164
  recipient: Schema.Address.check(z.describe('The account address that received the tokens.')),
168
165
  sender: Schema.Address.check(z.describe('The account address that sent the tokens.')),
169
- sourceToken: schema.SourceToken.check(z.describe('The token and amount that moved.')),
166
+ sourceAmount: ValuedAmount.check(z.describe('Amount associated with `sourceToken`.')),
167
+ sourceToken: schema.SourceToken.check(z.describe('The token that moved.')),
170
168
  timestamp: z.iso
171
169
  .datetime()
172
170
  .check(z.describe('The block timestamp when this transfer was recorded.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
173
171
  transactionHash: Schema.Hash.check(z.describe('The hash of the transaction that emitted this transfer event.')),
174
172
  })
175
- .check(z.describe('One TIP-20 token transfer event, optionally including a payment memo for reconciliation or invoices.'));
173
+ .check(z.refine((data) => (data.destinationToken === undefined) === (data.destinationAmount === undefined), {
174
+ error: '`destinationAmount` and `destinationToken` must be returned together.',
175
+ path: ['destinationAmount'],
176
+ }), z.describe('One TIP-20 token transfer event, optionally including a payment memo for reconciliation or invoices.'));
177
+ /** Page-level resources: opt-in counts plus valuation rate provenance. */
178
+ getTransfers.Meta = z
179
+ .object({
180
+ pricing: z
181
+ .optional(Valuation.schema.Pricing)
182
+ .check(z.describe('Rate provenance for leg valuations. Present when conversion rates were ' +
183
+ 'consulted; absent when every value was identity-valued or rates were unavailable.')),
184
+ totalCount: z.optional(Schema.TotalCount),
185
+ totalCountCapped: z.optional(Schema.TotalCountCapped),
186
+ })
187
+ .check(z.describe('Page-level resources attached to this response.'));
176
188
  /** Page of TIP-20 token transfers across the chain, ordered by block then log index. */
177
189
  getTransfers.Response = z
178
190
  .object({
179
191
  data: z.array(getTransfers.Transfer).check(z.describe('The transfers in this page.')),
180
192
  // Response-wide `meta` carries the opt-in capped total count
181
- // (`include=totalCount`); per-row embeds (`memo`, `token`) attach to
182
- // each `data` item, not here.
193
+ // (`include=totalCount`) and valuation rate provenance; per-row embeds
194
+ // (`memo`, `token`) attach to each `data` item, not here.
183
195
  meta: z
184
- .optional(Schema.CountMeta)
185
- .check(z.describe('Page-level resources you requested with `include`.')),
196
+ .optional(getTransfers.Meta)
197
+ .check(z.describe('Page-level resources: opt-in counts and valuation rate provenance.')),
186
198
  nextCursor: Schema.NextCursor,
187
199
  })
188
200
  .check(z.describe('A page of TIP-20 token transfer events across the chain.'));
189
201
  })(getTransfers = schema.getTransfers || (schema.getTransfers = {}));
190
202
  })(schema || (schema = {}));
191
203
  /** Creates transfer handlers. */
192
- export function transfers() {
204
+ export function transfers(options = {}) {
205
+ const oracle = options.fx?.oracle ?? FxOracle.ecb();
193
206
  return new Hono().get('/v1/transfers', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getTransfers.Query, {
194
207
  code: 'query_invalid',
195
208
  message: 'Invalid query parameters',
@@ -197,7 +210,7 @@ export function transfers() {
197
210
  description: 'List token transfer events across Tempo.',
198
211
  operationId: 'getTransfers',
199
212
  responses: OpenApi.responses({
200
- errors: { 502: 'The upstream indexer could not serve this request.' },
213
+ errors: { 502: 'The indexer or Tempo RPC could not serve this request.' },
201
214
  success: {
202
215
  description: 'A page of token transfer events.',
203
216
  schema: schema.getTransfers.Response,
@@ -207,7 +220,7 @@ export function transfers() {
207
220
  tags: ['Transfers'],
208
221
  }), Cache.response({
209
222
  cacheControl: Cache.policies.feed,
210
- name: 'tempo-api:transfers:v1',
223
+ name: 'tempo-api:transfers:v2',
211
224
  key: (c) => Cache.urlKey(c, schema.getTransfers.Query),
212
225
  }), async (c) => {
213
226
  if (Auth.narrowAccess)
@@ -219,11 +232,10 @@ export function transfers() {
219
232
  });
220
233
  const options = c.req.valid('query');
221
234
  const chainId = options.chainId ?? c.get('chainId');
222
- // Cursor pages are anchored below the head and effectively immutable,
223
- // so cache them aggressively; the head page keeps the route's short
224
- // `feed` default.
235
+ const denomination = options.denomination ?? 'USD';
236
+ // Cursor pages carry current valuations and curated verification data.
225
237
  if (options.cursor !== undefined)
226
- Cache.setPolicy(c, Cache.policies.immutable);
238
+ Cache.setPolicy(c, Cache.policies.metadata);
227
239
  // Filters shared by the page query and the opt-in total count, so the
228
240
  // count always matches the page it annotates.
229
241
  const predicates = {
@@ -257,11 +269,6 @@ export function transfers() {
257
269
  page: options.page,
258
270
  timing: 'transfers',
259
271
  });
260
- // Token metadata is opt-in. When requested, resolve each page token's
261
- // metadata concurrently (sharing one verified-token fetch) and spread
262
- // it into the row's `sourceToken`. Resolution is best-effort: a token
263
- // whose metadata is unavailable keeps `sourceToken` as just
264
- // `address`/`amount` on that row rather than failing the whole page.
265
272
  let data = page.data.map((row) => ({
266
273
  blockNumber: row.blockNumber,
267
274
  id: `${row.transactionHash}-${row.logIndex}`,
@@ -315,55 +322,124 @@ export function transfers() {
315
322
  return suppress.has(key) ? [] : [transfer];
316
323
  });
317
324
  }
318
- if (options.include.includes('token') && data.length > 0) {
319
- const uniqueTokens = Array.from(new Set(data.flatMap((transfer) => [
325
+ // Verification is ancillary to the feed. Skip empty pages and degrade
326
+ // cold snapshot failures to null valuations.
327
+ const snapshot = data.length > 0
328
+ ? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
329
+ : undefined;
330
+ const includeLogoUri = options.include.includes('token.logoUri');
331
+ const includeVerified = options.include.includes('token.verified');
332
+ const uniqueTokens = Array.from(new Set(data.flatMap((transfer) => [
333
+ transfer.sourceToken.address,
334
+ ...(transfer.destinationToken ? [transfer.destinationToken.address] : []),
335
+ ])));
336
+ const tokensByAddress = new Map(await Timing.time(c, 'tokens', () => Promise.all(uniqueTokens.map(async (token) => {
337
+ const [logoUri, metadata] = await Promise.all([
338
+ includeLogoUri
339
+ ? Timing.time(c, 'token_logo', () => Tokens.getTokenLogo(c, { address: token, chainId })).catch(() => undefined)
340
+ : undefined,
341
+ Timing.time(c, 'token_metadata', () => Tokens.getTokenMetadata(c, { address: token, chainId })),
342
+ ]);
343
+ return [
344
+ token.toLowerCase(),
345
+ {
346
+ ...metadata,
347
+ logoUri: includeLogoUri
348
+ ? (logoUri ??
349
+ snapshot?.byAddress.get(token.toLowerCase())?.logoUri ??
350
+ metadata.logoUri)
351
+ : undefined,
352
+ },
353
+ ];
354
+ }))));
355
+ // Value each leg via its curated display currency, in the requested
356
+ // denomination.
357
+ const rates = await Valuation.ratesFor(c, {
358
+ currencies: data.flatMap((transfer) => [
320
359
  transfer.sourceToken.address,
321
360
  ...(transfer.destinationToken ? [transfer.destinationToken.address] : []),
322
- ])));
323
- const snapshot = await VerifiedTokens.snapshot(c, chainId);
324
- const resolved = await Timing.time(c, 'tokens', () => Promise.all(uniqueTokens.map((token) => Tokens.resolveToken(c, {
325
- address: token,
326
- chainId,
327
- snapshot,
328
- })
329
- .then((metadata) => [token, metadata])
330
- .catch(() => [token, undefined]))));
331
- const tokensByAddress = new Map(resolved);
332
- const withToken = (entry) => {
333
- const token = tokensByAddress.get(entry.address);
334
- return token ? { ...entry, ...token } : entry;
361
+ ].flatMap((address) => {
362
+ const held = snapshot?.byAddress.get(address.toLowerCase())?.currency;
363
+ return held === undefined ? [] : [held];
364
+ })),
365
+ denomination,
366
+ oracle,
367
+ });
368
+ const enrich = (leg) => {
369
+ const metadata = tokensByAddress.get(leg.address.toLowerCase());
370
+ if (!metadata)
371
+ throw new Error(`Token metadata unavailable for ${leg.address}`);
372
+ return {
373
+ amount: {
374
+ ...Value.tokenAmount({
375
+ baseUnits: leg.amount,
376
+ currency: metadata.currency,
377
+ decimals: metadata.decimals,
378
+ }),
379
+ valuation: Valuation.valuationFor({
380
+ amount: BigInt(leg.amount),
381
+ denomination,
382
+ rates,
383
+ token: snapshot?.byAddress.get(leg.address.toLowerCase()),
384
+ }),
385
+ },
386
+ token: {
387
+ address: leg.address,
388
+ currency: metadata.currency,
389
+ decimals: metadata.decimals,
390
+ ...(metadata.logoUri !== undefined ? { logoUri: metadata.logoUri } : {}),
391
+ name: metadata.name,
392
+ symbol: metadata.symbol,
393
+ ...(includeVerified && snapshot
394
+ ? { verified: snapshot.byAddress.has(leg.address.toLowerCase()) }
395
+ : {}),
396
+ },
335
397
  };
336
- data = data.map((transfer) => ({
398
+ };
399
+ const responseData = data.map(({ destinationToken, sourceToken, ...transfer }) => {
400
+ const source = enrich(sourceToken);
401
+ if (!destinationToken)
402
+ return {
403
+ ...transfer,
404
+ sourceAmount: source.amount,
405
+ sourceToken: source.token,
406
+ };
407
+ const destination = enrich(destinationToken);
408
+ return {
337
409
  ...transfer,
338
- sourceToken: withToken(transfer.sourceToken),
339
- ...(transfer.destinationToken
340
- ? { destinationToken: withToken(transfer.destinationToken) }
341
- : {}),
342
- }));
343
- }
344
- const meta = countPromise ? await countPromise : undefined;
410
+ destinationAmount: destination.amount,
411
+ destinationToken: destination.token,
412
+ sourceAmount: source.amount,
413
+ sourceToken: source.token,
414
+ };
415
+ });
416
+ const counts = countPromise ? await countPromise : undefined;
417
+ const meta = {
418
+ ...(rates ? { pricing: Valuation.pricing(rates, oracle) } : {}),
419
+ ...counts,
420
+ };
345
421
  return c.json(Response.validated(schema.getTransfers.Response, {
346
- data,
347
- ...(meta ? { meta } : {}),
422
+ data: responseData,
423
+ ...(Object.keys(meta).length > 0 ? { meta } : {}),
348
424
  nextCursor: page.nextCursor,
349
425
  }), 200);
350
426
  }
351
427
  catch (cause) {
428
+ if (cause instanceof Valuation.UnsupportedDenominationError)
429
+ return Response.error(c, { code: 'query_invalid', message: cause.message, status: 400 });
352
430
  return Response.upstream(c, cause);
353
431
  }
354
432
  });
355
433
  }
356
434
  /**
357
435
  * Shared TIP-20 `Transfer` page query. Used by `GET /transfers` and the
358
- * webhook poller. Builds the signature-decoded `Transfer` CTE query, applies
436
+ * webhook poller. Builds signature-decoded `Transfer` CTE queries, applies
359
437
  * opaque keyset pagination on `(block_num, log_idx)`, and decodes rows into a
360
438
  * strongly-typed intermediate shape that each caller maps to its own public
361
439
  * response.
362
440
  *
363
- * `eitherSide` (the subject address must appear on _either_ side of the
364
- * transfer) is exposed alongside the one-sided `sender`/`recipient` filters
365
- * because composing it with the keyset cursor pre-WHERE in SQL is awkward at
366
- * the call site; the helper takes the knob directly.
441
+ * `eitherSide` is exposed alongside one-sided `sender`/`recipient` filters
442
+ * because it requires two upstream scans. The helper owns their ordered merge.
367
443
  *
368
444
  * Memoization lives inside the helper under one canonical `transfers:v1:…`
369
445
  * key derived from every filter — so equivalent filter combinations share a
@@ -404,32 +480,48 @@ export async function scan(deps, options) {
404
480
  // the head page, so it shares the head's SQL and cache entries; deeper pages
405
481
  // translate to a SQL OFFSET of `(page - 1) * limit` rows.
406
482
  const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
407
- // Transfer events filtered by any combination of contract / subject /
408
- // sender / recipient / block / timestamp. Ordered by block then log
409
- // index so transfers within a block stay stable. The keyset cursor clause is
410
- // page-only and added on top of the shared filters below.
483
+ const address = options.eitherSide;
484
+ // Transfer events filtered by any combination of contract / sender /
485
+ // recipient / block / timestamp. The address shortcut is split below so
486
+ // each upstream query can use one indexed side.
411
487
  const filters = filterClauses(options);
412
488
  if (cursor !== undefined)
413
489
  filters.push(Cursor.keyset([
414
490
  { literal: Cursor.literal(cursor[0], 'int'), name: 'block_num', order },
415
491
  { literal: Cursor.literal(cursor[1], 'int'), name: 'log_idx', order },
416
492
  ]));
417
- const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
418
- // Fetch one extra row to detect `hasMore` without a separate count query.
419
- const result = await tidx.fetch({
420
- chainId,
421
- ...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
422
- query: `
423
- SELECT "from", "to", ${tokenScoped ? 'token AS address, amount AS value' : 'address, value'}, tx_hash, block_num, log_idx, block_timestamp
424
- FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
425
- ${where}
426
- ORDER BY block_num ${direction}, log_idx ${direction}
427
- LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
428
- `,
429
- });
493
+ const addressScoped = address !== undefined;
494
+ const window = addressScoped ? (offset ?? 0) + limit + 1 : limit + 1;
495
+ const fetchRows = async (side) => {
496
+ const sideFilters = side !== undefined && address !== undefined
497
+ ? [`"${side}" = '${address}'`, ...filters]
498
+ : filters;
499
+ const where = sideFilters.length > 0 ? `WHERE ${sideFilters.join(' AND ')}` : '';
500
+ const result = await tidx.fetch({
501
+ chainId,
502
+ ...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
503
+ query: `
504
+ SELECT "from", "to", ${tokenScoped ? 'token AS address, amount AS value' : 'address, value'}, tx_hash, block_num, log_idx, block_timestamp
505
+ FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
506
+ ${where}
507
+ ORDER BY block_num ${direction}, log_idx ${direction}
508
+ LIMIT ${window}${!addressScoped && offset !== undefined ? ` OFFSET ${offset}` : ''}
509
+ `,
510
+ });
511
+ return result.rows;
512
+ };
513
+ // TIDX cannot plan `(from = X OR to = X)` over the decoded Transfer table.
514
+ // Merge two bounded, server-filtered streams while preserving global order.
515
+ const rows = addressScoped
516
+ ? mergeTransferRows((await Promise.all([fetchRows('from'), fetchRows('to')])).flat(), {
517
+ limit: limit + 1,
518
+ offset: offset ?? 0,
519
+ order,
520
+ })
521
+ : await fetchRows();
430
522
  // The next page anchors below the last fetched row's `(block, log_idx)`.
431
523
  const page = Cursor.paginate({
432
- rows: result.rows,
524
+ rows,
433
525
  limit,
434
526
  key: (row) => {
435
527
  const block = Value.toNumber(row['block_num']);
@@ -471,17 +563,37 @@ export async function scan(deps, options) {
471
563
  }
472
564
  return { hasMore: page.hasMore, items, limit, nextCursor: page.nextCursor };
473
565
  }
566
+ /** Deduplicates and globally orders bounded `from`/`to` transfer streams. */
567
+ function mergeTransferRows(rows, options) {
568
+ const positioned = [];
569
+ const seen = new Set();
570
+ for (const row of rows) {
571
+ const blockNumber = Value.toNumber(row['block_num']);
572
+ const logIndex = Value.toNumber(row['log_idx']);
573
+ const transactionHash = Value.toText(row['tx_hash']);
574
+ if (blockNumber === undefined || logIndex === undefined || !transactionHash)
575
+ continue;
576
+ const key = eventKey(transactionHash, logIndex);
577
+ if (seen.has(key))
578
+ continue;
579
+ seen.add(key);
580
+ positioned.push({ blockNumber, logIndex, row });
581
+ }
582
+ positioned.sort((a, b) => {
583
+ const comparison = a.blockNumber - b.blockNumber || a.logIndex - b.logIndex;
584
+ return options.order === 'asc' ? comparison : -comparison;
585
+ });
586
+ return positioned.slice(options.offset, options.offset + options.limit).map(({ row }) => row);
587
+ }
474
588
  /**
475
589
  * Builds shared transfer filters. A token predicate targets `token_transfers`'s
476
590
  * physical `token` column; unscoped queries omit it and use the `Transfer` CTE.
477
591
  */
478
592
  function filterClauses(options) {
479
- const { eitherSide, fromBlock, fromTimestamp, recipient, sender, toBlock, toTimestamp, token } = options;
593
+ const { fromBlock, fromTimestamp, recipient, sender, toBlock, toTimestamp, token } = options;
480
594
  const filters = [];
481
595
  if (token !== undefined)
482
596
  filters.push(`token = '${token}'`);
483
- if (eitherSide !== undefined)
484
- filters.push(`("from" = '${eitherSide}' OR "to" = '${eitherSide}')`);
485
597
  if (sender !== undefined)
486
598
  filters.push(`"from" = '${sender}'`);
487
599
  if (recipient !== undefined)
@@ -515,54 +627,84 @@ export function count(c, options) {
515
627
  /**
516
628
  * Context-free core of {@link count}: counts token-scoped rows from
517
629
  * `token_transfers`, otherwise the decoded `Transfer` CTE. The capped inner
518
- * scan bounds work; the SQL aggregate runs on ClickHouse.
630
+ * scan bounds work while preserving each source's established query engine.
519
631
  */
520
632
  export async function countScan(deps, options) {
521
633
  const { chainId } = options;
522
634
  const { tidx } = deps;
523
635
  const filters = filterClauses(options);
524
636
  const tokenScoped = options.token !== undefined;
525
- const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
637
+ const address = options.eitherSide;
526
638
  // Folded count: cross-token transfers collapse several `Transfer` legs into
527
639
  // one row, so a raw `count(*)` would overcount. Scan the capped `Transfer`
528
640
  // rows, classify each transaction, and subtract the folded-away legs. Bounded
529
641
  // by `countCap` like the raw count, so it stays within the indexer's budget.
530
642
  if (options.crossToken) {
531
- const result = await tidx.fetch({
532
- chainId,
533
- ...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
534
- query: `
535
- SELECT tx_hash, log_idx, "from", "to", ${tokenScoped ? 'token AS address, amount AS value' : 'address, value'}
536
- FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
537
- ${where}
538
- ORDER BY block_num, log_idx
539
- LIMIT ${Schema.countCap}
540
- `,
541
- });
542
- const rawCount = result.rows.length;
643
+ const fetchRows = async (side) => {
644
+ const sideFilters = side !== undefined && address !== undefined
645
+ ? [`"${side}" = '${address}'`, ...filters]
646
+ : filters;
647
+ const sideWhere = sideFilters.length > 0 ? `WHERE ${sideFilters.join(' AND ')}` : '';
648
+ const result = await tidx.fetch({
649
+ chainId,
650
+ ...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
651
+ query: `
652
+ SELECT tx_hash, block_num, log_idx, "from", "to", ${tokenScoped ? 'token AS address, amount AS value' : 'address, value'}
653
+ FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
654
+ ${sideWhere}
655
+ ORDER BY block_num, log_idx
656
+ LIMIT ${Schema.countCap}
657
+ `,
658
+ });
659
+ return result.rows;
660
+ };
661
+ const rows = address !== undefined
662
+ ? mergeTransferRows((await Promise.all([fetchRows('from'), fetchRows('to')])).flat(), {
663
+ limit: Schema.countCap,
664
+ offset: 0,
665
+ order: 'asc',
666
+ })
667
+ : await fetchRows();
668
+ const rawCount = rows.length;
543
669
  let suppressed = 0;
544
- for (const legs of legsByTransaction(result.rows).values()) {
670
+ for (const legs of legsByTransaction(rows).values()) {
545
671
  const bundle = classifyCrossToken(legs);
546
672
  if (bundle)
547
673
  suppressed += bundle.suppressLogIndexes.length;
548
674
  }
549
675
  return { totalCountCapped: rawCount >= Schema.countCap, totalCount: rawCount - suppressed };
550
676
  }
551
- const result = await tidx.fetch({
552
- chainId,
553
- engine: 'clickhouse',
554
- ...(tokenScoped ? {} : { signatures: [signature] }),
555
- query: `
556
- SELECT count(*) AS total
557
- FROM (
558
- SELECT 1
559
- FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
560
- ${where}
561
- LIMIT ${Schema.countCap}
562
- ) AS capped
563
- `,
564
- });
565
- const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0;
677
+ const countRows = async (additionalFilters = []) => {
678
+ const countFilters = [...additionalFilters, ...filters];
679
+ const countWhere = countFilters.length > 0 ? `WHERE ${countFilters.join(' AND ')}` : '';
680
+ const result = await tidx.fetch({
681
+ chainId,
682
+ ...(tokenScoped ? { engine: 'clickhouse' } : { signatures: [signature] }),
683
+ query: `
684
+ SELECT count(*) AS total
685
+ FROM (
686
+ SELECT 1
687
+ FROM ${tokenScoped ? 'token_transfers' : 'Transfer'}
688
+ ${countWhere}
689
+ LIMIT ${Schema.countCap}
690
+ ) AS capped
691
+ `,
692
+ });
693
+ return Value.toNumber(result.rows[0]?.['total']) ?? 0;
694
+ };
695
+ if (address !== undefined) {
696
+ // Keep branches disjoint so self-transfers count once.
697
+ const [sent, received] = await Promise.all([
698
+ countRows([`"from" = '${address}'`]),
699
+ countRows([`"to" = '${address}'`, `"from" != '${address}'`]),
700
+ ]);
701
+ const rawCount = sent + received;
702
+ return {
703
+ totalCount: Math.min(rawCount, Schema.countCap),
704
+ totalCountCapped: sent >= Schema.countCap || received >= Schema.countCap || rawCount >= Schema.countCap,
705
+ };
706
+ }
707
+ const totalCount = await countRows();
566
708
  return { totalCountCapped: totalCount >= Schema.countCap, totalCount };
567
709
  }
568
710
  /** `'tx1', 'tx2', …` SQL `IN` list of the page's distinct transaction hashes. */