tapimo 0.0.0 → 0.0.2

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 (422) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/ApiKey.d.ts +65 -0
  4. package/dist/ApiKey.d.ts.map +1 -0
  5. package/dist/ApiKey.js +105 -0
  6. package/dist/ApiKey.js.map +1 -0
  7. package/dist/ApiKeySource.d.ts +166 -0
  8. package/dist/ApiKeySource.d.ts.map +1 -0
  9. package/dist/ApiKeySource.js +234 -0
  10. package/dist/ApiKeySource.js.map +1 -0
  11. package/dist/App.d.ts +19317 -0
  12. package/dist/App.d.ts.map +1 -0
  13. package/dist/App.js +612 -0
  14. package/dist/App.js.map +1 -0
  15. package/dist/Assets.d.ts +44 -0
  16. package/dist/Assets.d.ts.map +1 -0
  17. package/dist/Assets.js +23 -0
  18. package/dist/Assets.js.map +1 -0
  19. package/dist/Auth.d.ts +2 -0
  20. package/dist/Auth.d.ts.map +1 -0
  21. package/dist/Auth.js +2 -0
  22. package/dist/Auth.js.map +1 -0
  23. package/dist/Cli.d.ts +21 -0
  24. package/dist/Cli.d.ts.map +1 -0
  25. package/dist/Cli.js +13 -0
  26. package/dist/Cli.js.map +1 -0
  27. package/dist/Client.d.ts +20 -0
  28. package/dist/Client.d.ts.map +1 -0
  29. package/dist/Client.js +24 -0
  30. package/dist/Client.js.map +1 -0
  31. package/dist/Metrics.d.ts +70 -0
  32. package/dist/Metrics.d.ts.map +1 -0
  33. package/dist/Metrics.js +91 -0
  34. package/dist/Metrics.js.map +1 -0
  35. package/dist/Scope.d.ts +35 -0
  36. package/dist/Scope.d.ts.map +1 -0
  37. package/dist/Scope.js +38 -0
  38. package/dist/Scope.js.map +1 -0
  39. package/dist/Webhooks.d.ts +79 -0
  40. package/dist/Webhooks.d.ts.map +1 -0
  41. package/dist/Webhooks.js +107 -0
  42. package/dist/Webhooks.js.map +1 -0
  43. package/dist/admin/Access.d.ts +41 -0
  44. package/dist/admin/Access.d.ts.map +1 -0
  45. package/dist/admin/Access.js +75 -0
  46. package/dist/admin/Access.js.map +1 -0
  47. package/dist/admin/App.d.ts +1221 -0
  48. package/dist/admin/App.d.ts.map +1 -0
  49. package/dist/admin/App.js +113 -0
  50. package/dist/admin/App.js.map +1 -0
  51. package/dist/admin/apps/api-keys.d.ts +209 -0
  52. package/dist/admin/apps/api-keys.d.ts.map +1 -0
  53. package/dist/admin/apps/api-keys.js +129 -0
  54. package/dist/admin/apps/api-keys.js.map +1 -0
  55. package/dist/admin/apps/scopes.d.ts +46 -0
  56. package/dist/admin/apps/scopes.d.ts.map +1 -0
  57. package/dist/admin/apps/scopes.js +41 -0
  58. package/dist/admin/apps/scopes.js.map +1 -0
  59. package/dist/admin/apps/verified-tokens.d.ts +1206 -0
  60. package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
  61. package/dist/admin/apps/verified-tokens.js +485 -0
  62. package/dist/admin/apps/verified-tokens.js.map +1 -0
  63. package/dist/admin/index.d.ts +3 -0
  64. package/dist/admin/index.d.ts.map +1 -0
  65. package/dist/admin/index.js +3 -0
  66. package/dist/admin/index.js.map +1 -0
  67. package/dist/admin/ui.generated.d.ts +11 -0
  68. package/dist/admin/ui.generated.d.ts.map +1 -0
  69. package/dist/admin/ui.generated.js +43 -0
  70. package/dist/admin/ui.generated.js.map +1 -0
  71. package/dist/apps/activities.d.ts +18725 -0
  72. package/dist/apps/activities.d.ts.map +1 -0
  73. package/dist/apps/activities.js +3138 -0
  74. package/dist/apps/activities.js.map +1 -0
  75. package/dist/apps/addresses.d.ts +3662 -0
  76. package/dist/apps/addresses.d.ts.map +1 -0
  77. package/dist/apps/addresses.js +22 -0
  78. package/dist/apps/addresses.js.map +1 -0
  79. package/dist/apps/balances.d.ts +342 -0
  80. package/dist/apps/balances.d.ts.map +1 -0
  81. package/dist/apps/balances.js +393 -0
  82. package/dist/apps/balances.js.map +1 -0
  83. package/dist/apps/blocks.d.ts +931 -0
  84. package/dist/apps/blocks.d.ts.map +1 -0
  85. package/dist/apps/blocks.js +689 -0
  86. package/dist/apps/blocks.js.map +1 -0
  87. package/dist/apps/coingecko.d.ts +1053 -0
  88. package/dist/apps/coingecko.d.ts.map +1 -0
  89. package/dist/apps/coingecko.js +1015 -0
  90. package/dist/apps/coingecko.js.map +1 -0
  91. package/dist/apps/exchanges.d.ts +3000 -0
  92. package/dist/apps/exchanges.d.ts.map +1 -0
  93. package/dist/apps/exchanges.js +3570 -0
  94. package/dist/apps/exchanges.js.map +1 -0
  95. package/dist/apps/fee-amm.d.ts +640 -0
  96. package/dist/apps/fee-amm.d.ts.map +1 -0
  97. package/dist/apps/fee-amm.js +815 -0
  98. package/dist/apps/fee-amm.js.map +1 -0
  99. package/dist/apps/indexer.d.ts +116 -0
  100. package/dist/apps/indexer.d.ts.map +1 -0
  101. package/dist/apps/indexer.js +270 -0
  102. package/dist/apps/indexer.js.map +1 -0
  103. package/dist/apps/receipts.d.ts +1438 -0
  104. package/dist/apps/receipts.d.ts.map +1 -0
  105. package/dist/apps/receipts.js +874 -0
  106. package/dist/apps/receipts.js.map +1 -0
  107. package/dist/apps/rpc.d.ts +173 -0
  108. package/dist/apps/rpc.d.ts.map +1 -0
  109. package/dist/apps/rpc.js +191 -0
  110. package/dist/apps/rpc.js.map +1 -0
  111. package/dist/apps/tokenlist.d.ts +192 -0
  112. package/dist/apps/tokenlist.d.ts.map +1 -0
  113. package/dist/apps/tokenlist.js +153 -0
  114. package/dist/apps/tokenlist.js.map +1 -0
  115. package/dist/apps/tokens.d.ts +2246 -0
  116. package/dist/apps/tokens.d.ts.map +1 -0
  117. package/dist/apps/tokens.js +1789 -0
  118. package/dist/apps/tokens.js.map +1 -0
  119. package/dist/apps/transactions.d.ts +2327 -0
  120. package/dist/apps/transactions.d.ts.map +1 -0
  121. package/dist/apps/transactions.js +975 -0
  122. package/dist/apps/transactions.js.map +1 -0
  123. package/dist/apps/transfers.d.ts +589 -0
  124. package/dist/apps/transfers.d.ts.map +1 -0
  125. package/dist/apps/transfers.js +769 -0
  126. package/dist/apps/transfers.js.map +1 -0
  127. package/dist/apps/verified-tokens.d.ts +544 -0
  128. package/dist/apps/verified-tokens.d.ts.map +1 -0
  129. package/dist/apps/verified-tokens.js +234 -0
  130. package/dist/apps/verified-tokens.js.map +1 -0
  131. package/dist/apps/webhooks.d.ts +7684 -0
  132. package/dist/apps/webhooks.d.ts.map +1 -0
  133. package/dist/apps/webhooks.js +2112 -0
  134. package/dist/apps/webhooks.js.map +1 -0
  135. package/dist/cloudflare.d.ts +32 -0
  136. package/dist/cloudflare.d.ts.map +1 -0
  137. package/dist/cloudflare.js +45 -0
  138. package/dist/cloudflare.js.map +1 -0
  139. package/dist/index.d.ts +16 -0
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +16 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/internal/Auth.d.ts +298 -0
  144. package/dist/internal/Auth.d.ts.map +1 -0
  145. package/dist/internal/Auth.js +618 -0
  146. package/dist/internal/Auth.js.map +1 -0
  147. package/dist/internal/Cache.d.ts +74 -0
  148. package/dist/internal/Cache.d.ts.map +1 -0
  149. package/dist/internal/Cache.js +215 -0
  150. package/dist/internal/Cache.js.map +1 -0
  151. package/dist/internal/Cursor.d.ts +79 -0
  152. package/dist/internal/Cursor.d.ts.map +1 -0
  153. package/dist/internal/Cursor.js +116 -0
  154. package/dist/internal/Cursor.js.map +1 -0
  155. package/dist/internal/EdgeCache.d.ts +59 -0
  156. package/dist/internal/EdgeCache.d.ts.map +1 -0
  157. package/dist/internal/EdgeCache.js +174 -0
  158. package/dist/internal/EdgeCache.js.map +1 -0
  159. package/dist/internal/Log.d.ts +104 -0
  160. package/dist/internal/Log.d.ts.map +1 -0
  161. package/dist/internal/Log.js +118 -0
  162. package/dist/internal/Log.js.map +1 -0
  163. package/dist/internal/MetricSink.d.ts +65 -0
  164. package/dist/internal/MetricSink.d.ts.map +1 -0
  165. package/dist/internal/MetricSink.js +146 -0
  166. package/dist/internal/MetricSink.js.map +1 -0
  167. package/dist/internal/Mpp.d.ts +71 -0
  168. package/dist/internal/Mpp.d.ts.map +1 -0
  169. package/dist/internal/Mpp.js +105 -0
  170. package/dist/internal/Mpp.js.map +1 -0
  171. package/dist/internal/OpenApi.d.ts +313 -0
  172. package/dist/internal/OpenApi.d.ts.map +1 -0
  173. package/dist/internal/OpenApi.fonts.d.ts +2 -0
  174. package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
  175. package/dist/internal/OpenApi.fonts.js +39 -0
  176. package/dist/internal/OpenApi.fonts.js.map +1 -0
  177. package/dist/internal/OpenApi.js +335 -0
  178. package/dist/internal/OpenApi.js.map +1 -0
  179. package/dist/internal/OpenApi.logo.d.ts +6 -0
  180. package/dist/internal/OpenApi.logo.d.ts.map +1 -0
  181. package/dist/internal/OpenApi.logo.js +19 -0
  182. package/dist/internal/OpenApi.logo.js.map +1 -0
  183. package/dist/internal/Path.d.ts +5 -0
  184. package/dist/internal/Path.d.ts.map +1 -0
  185. package/dist/internal/Path.js +16 -0
  186. package/dist/internal/Path.js.map +1 -0
  187. package/dist/internal/RateLimit.d.ts +60 -0
  188. package/dist/internal/RateLimit.d.ts.map +1 -0
  189. package/dist/internal/RateLimit.js +48 -0
  190. package/dist/internal/RateLimit.js.map +1 -0
  191. package/dist/internal/Response.d.ts +132 -0
  192. package/dist/internal/Response.d.ts.map +1 -0
  193. package/dist/internal/Response.js +72 -0
  194. package/dist/internal/Response.js.map +1 -0
  195. package/dist/internal/Schema.d.ts +257 -0
  196. package/dist/internal/Schema.d.ts.map +1 -0
  197. package/dist/internal/Schema.js +360 -0
  198. package/dist/internal/Schema.js.map +1 -0
  199. package/dist/internal/Store.d.ts +204 -0
  200. package/dist/internal/Store.d.ts.map +1 -0
  201. package/dist/internal/Store.js +291 -0
  202. package/dist/internal/Store.js.map +1 -0
  203. package/dist/internal/Tidx.d.ts +120 -0
  204. package/dist/internal/Tidx.d.ts.map +1 -0
  205. package/dist/internal/Tidx.js +156 -0
  206. package/dist/internal/Tidx.js.map +1 -0
  207. package/dist/internal/Timing.d.ts +22 -0
  208. package/dist/internal/Timing.d.ts.map +1 -0
  209. package/dist/internal/Timing.js +32 -0
  210. package/dist/internal/Timing.js.map +1 -0
  211. package/dist/internal/Ttl.d.ts +25 -0
  212. package/dist/internal/Ttl.d.ts.map +1 -0
  213. package/dist/internal/Ttl.js +49 -0
  214. package/dist/internal/Ttl.js.map +1 -0
  215. package/dist/internal/Value.d.ts +47 -0
  216. package/dist/internal/Value.d.ts.map +1 -0
  217. package/dist/internal/Value.js +122 -0
  218. package/dist/internal/Value.js.map +1 -0
  219. package/dist/internal/VerifiedTokens.d.ts +228 -0
  220. package/dist/internal/VerifiedTokens.d.ts.map +1 -0
  221. package/dist/internal/VerifiedTokens.js +450 -0
  222. package/dist/internal/VerifiedTokens.js.map +1 -0
  223. package/dist/internal/Viem.d.ts +2082 -0
  224. package/dist/internal/Viem.d.ts.map +1 -0
  225. package/dist/internal/Viem.js +63 -0
  226. package/dist/internal/Viem.js.map +1 -0
  227. package/dist/internal/WebhookDestination.d.ts +198 -0
  228. package/dist/internal/WebhookDestination.d.ts.map +1 -0
  229. package/dist/internal/WebhookDestination.js +170 -0
  230. package/dist/internal/WebhookDestination.js.map +1 -0
  231. package/dist/internal/Webhooks.d.ts +433 -0
  232. package/dist/internal/Webhooks.d.ts.map +1 -0
  233. package/dist/internal/Webhooks.js +489 -0
  234. package/dist/internal/Webhooks.js.map +1 -0
  235. package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
  236. package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
  237. package/dist/internal/webhookDestinations/betterStack.js +76 -0
  238. package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
  239. package/dist/internal/webhookDestinations/slack.d.ts +59 -0
  240. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
  241. package/dist/internal/webhookDestinations/slack.js +422 -0
  242. package/dist/internal/webhookDestinations/slack.js.map +1 -0
  243. package/dist/internal/webhookDestinations/url.d.ts +17 -0
  244. package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
  245. package/dist/internal/webhookDestinations/url.js +130 -0
  246. package/dist/internal/webhookDestinations/url.js.map +1 -0
  247. package/dist/openrpc.json +5302 -0
  248. package/package.json +46 -4
  249. package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
  250. package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  251. package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  252. package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  253. package/seeds/31318/verified-tokens.json +30 -0
  254. package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  255. package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
  256. package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
  257. package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
  258. package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
  259. package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
  260. package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
  261. package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
  262. package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
  263. package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
  264. package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
  265. package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
  266. package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
  267. package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
  268. package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
  269. package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
  270. package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
  271. package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
  272. package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
  273. package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
  274. package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
  275. package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
  276. package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
  277. package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
  278. package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
  279. package/seeds/4217/verified-tokens.json +177 -0
  280. package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  281. package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  282. package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  283. package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  284. package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
  285. package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
  286. package/seeds/42431/verified-tokens.json +44 -0
  287. package/seeds/openrpc/README.md +27 -0
  288. package/seeds/openrpc/execution-apis.json +8989 -0
  289. package/src/ApiKey.test.ts +91 -0
  290. package/src/ApiKey.ts +122 -0
  291. package/src/ApiKeySource.test-d.ts +11 -0
  292. package/src/ApiKeySource.test.ts +295 -0
  293. package/src/ApiKeySource.ts +303 -0
  294. package/src/App.test-d.ts +38 -0
  295. package/src/App.test.ts +65927 -0
  296. package/src/App.ts +855 -0
  297. package/src/Assets.ts +58 -0
  298. package/src/Auth.ts +1 -0
  299. package/src/Cli.test.ts +81 -0
  300. package/src/Cli.ts +38 -0
  301. package/src/Client.test-d.ts +1438 -0
  302. package/src/Client.test.ts +134 -0
  303. package/src/Client.ts +45 -0
  304. package/src/Metrics.ts +154 -0
  305. package/src/Scope.ts +50 -0
  306. package/src/Webhooks.test.ts +49 -0
  307. package/src/Webhooks.ts +172 -0
  308. package/src/admin/Access.test.ts +130 -0
  309. package/src/admin/Access.ts +87 -0
  310. package/src/admin/App.test.ts +225 -0
  311. package/src/admin/App.ts +216 -0
  312. package/src/admin/apps/api-keys.test.ts +156 -0
  313. package/src/admin/apps/api-keys.ts +157 -0
  314. package/src/admin/apps/scopes.test.ts +52 -0
  315. package/src/admin/apps/scopes.ts +47 -0
  316. package/src/admin/apps/verified-tokens.test.ts +415 -0
  317. package/src/admin/apps/verified-tokens.ts +566 -0
  318. package/src/admin/index.ts +2 -0
  319. package/src/admin/ui/plugin.ts +135 -0
  320. package/src/admin/ui/src/components/Checkbox.tsx +22 -0
  321. package/src/admin/ui/src/components/Dialog.tsx +32 -0
  322. package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
  323. package/src/admin/ui/src/components/Page.tsx +53 -0
  324. package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
  325. package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
  326. package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
  327. package/src/admin/ui/src/lib/api-keys.ts +63 -0
  328. package/src/admin/ui/src/lib/client.ts +15 -0
  329. package/src/admin/ui/src/lib/config.ts +57 -0
  330. package/src/admin/ui/src/lib/query-client.ts +26 -0
  331. package/src/admin/ui/src/lib/scopes.ts +25 -0
  332. package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
  333. package/src/admin/ui/src/lib/wagmi.ts +18 -0
  334. package/src/admin/ui/src/routeTree.gen.ts +104 -0
  335. package/src/admin/ui/src/router.tsx +25 -0
  336. package/src/admin/ui/src/routes/__root.tsx +219 -0
  337. package/src/admin/ui/src/routes/api-keys.tsx +329 -0
  338. package/src/admin/ui/src/routes/index.tsx +7 -0
  339. package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
  340. package/src/admin/ui/src/styles.css +20 -0
  341. package/src/admin/ui/tsconfig.json +16 -0
  342. package/src/admin/ui/vite.config.ts +29 -0
  343. package/src/admin/ui.generated.ts +60 -0
  344. package/src/apps/activities.test.ts +1020 -0
  345. package/src/apps/activities.ts +3934 -0
  346. package/src/apps/addresses.ts +24 -0
  347. package/src/apps/balances.test.ts +274 -0
  348. package/src/apps/balances.ts +533 -0
  349. package/src/apps/blocks.test.ts +385 -0
  350. package/src/apps/blocks.ts +896 -0
  351. package/src/apps/coingecko.test.ts +218 -0
  352. package/src/apps/coingecko.ts +1383 -0
  353. package/src/apps/exchanges.test.ts +958 -0
  354. package/src/apps/exchanges.ts +4773 -0
  355. package/src/apps/fee-amm.test.ts +321 -0
  356. package/src/apps/fee-amm.ts +1121 -0
  357. package/src/apps/indexer.test.ts +153 -0
  358. package/src/apps/indexer.ts +330 -0
  359. package/src/apps/receipts.test.ts +285 -0
  360. package/src/apps/receipts.ts +1193 -0
  361. package/src/apps/rpc.test.ts +302 -0
  362. package/src/apps/rpc.ts +222 -0
  363. package/src/apps/tokenlist.test.ts +80 -0
  364. package/src/apps/tokenlist.ts +207 -0
  365. package/src/apps/tokens.test.ts +1062 -0
  366. package/src/apps/tokens.ts +2292 -0
  367. package/src/apps/transactions.test.ts +408 -0
  368. package/src/apps/transactions.ts +1363 -0
  369. package/src/apps/transfers.test.ts +371 -0
  370. package/src/apps/transfers.ts +1116 -0
  371. package/src/apps/verified-tokens.test.ts +114 -0
  372. package/src/apps/verified-tokens.ts +295 -0
  373. package/src/apps/webhooks.test.ts +1937 -0
  374. package/src/apps/webhooks.ts +2926 -0
  375. package/src/cloudflare.ts +55 -0
  376. package/src/index.ts +15 -0
  377. package/src/internal/Auth.test.ts +980 -0
  378. package/src/internal/Auth.ts +1012 -0
  379. package/src/internal/Cache.test.ts +240 -0
  380. package/src/internal/Cache.ts +262 -0
  381. package/src/internal/Cursor.test.ts +100 -0
  382. package/src/internal/Cursor.ts +149 -0
  383. package/src/internal/EdgeCache.test.ts +234 -0
  384. package/src/internal/EdgeCache.ts +224 -0
  385. package/src/internal/Errors.ts +14 -0
  386. package/src/internal/Log.test.ts +244 -0
  387. package/src/internal/Log.ts +216 -0
  388. package/src/internal/MetricSink.ts +220 -0
  389. package/src/internal/Mpp.test.ts +64 -0
  390. package/src/internal/Mpp.ts +141 -0
  391. package/src/internal/OpenApi.fonts.ts +38 -0
  392. package/src/internal/OpenApi.logo.ts +18 -0
  393. package/src/internal/OpenApi.ts +488 -0
  394. package/src/internal/Path.test.ts +14 -0
  395. package/src/internal/Path.ts +15 -0
  396. package/src/internal/RateLimit.test.ts +93 -0
  397. package/src/internal/RateLimit.ts +93 -0
  398. package/src/internal/Response.ts +131 -0
  399. package/src/internal/Schema.test.ts +34 -0
  400. package/src/internal/Schema.ts +523 -0
  401. package/src/internal/Store.test.ts +545 -0
  402. package/src/internal/Store.ts +526 -0
  403. package/src/internal/Tidx.local.test.ts +33 -0
  404. package/src/internal/Tidx.test.ts +294 -0
  405. package/src/internal/Tidx.ts +238 -0
  406. package/src/internal/Timing.test.ts +22 -0
  407. package/src/internal/Timing.ts +62 -0
  408. package/src/internal/Ttl.test.ts +18 -0
  409. package/src/internal/Ttl.ts +59 -0
  410. package/src/internal/Value.ts +114 -0
  411. package/src/internal/VerifiedTokens.test.ts +333 -0
  412. package/src/internal/VerifiedTokens.ts +645 -0
  413. package/src/internal/Viem.test.ts +115 -0
  414. package/src/internal/Viem.ts +165 -0
  415. package/src/internal/WebhookDestination.test.ts +648 -0
  416. package/src/internal/WebhookDestination.ts +331 -0
  417. package/src/internal/Webhooks.test.ts +668 -0
  418. package/src/internal/Webhooks.ts +940 -0
  419. package/src/internal/webhookDestinations/betterStack.ts +91 -0
  420. package/src/internal/webhookDestinations/slack.ts +496 -0
  421. package/src/internal/webhookDestinations/url.ts +128 -0
  422. package/src/openrpc.json +5302 -0
@@ -0,0 +1,769 @@
1
+ import { Hono } from 'hono';
2
+ import { Address, Hex } from 'ox';
3
+ import { Addresses } from 'viem/tempo';
4
+ import * as z from 'zod/mini';
5
+ import * as Auth from '../internal/Auth.js';
6
+ import * as Cache from '../internal/Cache.js';
7
+ import * as Cursor from '../internal/Cursor.js';
8
+ import * as Mpp from '../internal/Mpp.js';
9
+ import * as OpenApi from '../internal/OpenApi.js';
10
+ import * as Response from '../internal/Response.js';
11
+ import * as Schema from '../internal/Schema.js';
12
+ import * as Store from '../internal/Store.js';
13
+ import * as Timing from '../internal/Timing.js';
14
+ import * as Ttl from '../internal/Ttl.js';
15
+ import * as Value from '../internal/Value.js';
16
+ import * as VerifiedTokens from '../internal/VerifiedTokens.js';
17
+ import * as Tokens from './tokens.js';
18
+ // TIP-20 tokens emit the standard ERC-20 Transfer event. Supplying the
19
+ // signature exposes a decoded `Transfer` CTE (with `from`/`to`/`value`) to
20
+ // the indexer SQL.
21
+ export const signature = 'event Transfer(address indexed from, address indexed to, uint256 value)';
22
+ // A memo'd TIP-20 transfer emits `TransferWithMemo` alongside the plain
23
+ // `Transfer`; the two rows match on `(tx_hash, from, to, amount, token)`. A
24
+ // text memo is decoded into `memo`; an MPP attribution fingerprint memo is
25
+ // resolved into `attribution` instead.
26
+ const memoSignature = 'event TransferWithMemo(address indexed from, address indexed to, uint256 amount, bytes32 indexed memo)';
27
+ /** Zod schemas owned by the transfer handlers. */
28
+ export var schema;
29
+ (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
+ */
45
+ schema.SourceToken = z
46
+ .object({
47
+ 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
+ currency: z
52
+ .optional(z.string())
53
+ .check(z.describe('The currency label for this token, such as `USD` for USD-denominated stablecoins.'), z.meta({ examples: ['USD'] })),
54
+ decimals: z
55
+ .optional(z.number().check(z.int(), z.nonnegative()))
56
+ .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({
58
+ examples: [
59
+ 'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
60
+ ],
61
+ })),
62
+ name: z
63
+ .optional(z.string())
64
+ .check(z.describe('The token’s human-readable name.'), z.meta({ examples: ['USD Coin'] })),
65
+ symbol: z
66
+ .optional(z.string())
67
+ .check(z.describe('The short ticker symbol wallets and apps show for this token.'), z.meta({ examples: ['USDC'] })),
68
+ verified: z
69
+ .optional(z.boolean())
70
+ .check(z.describe('Whether this token is in Tempo’s curated verified token list.'), z.meta({ examples: [true] })),
71
+ })
72
+ .check(z.describe('The token and amount that moved in this transfer, with token metadata added when you request `include=token`.'));
73
+ /**
74
+ * Filter predicates shared verbatim by the `GET /transfers` list query
75
+ * ({@link getTransfers.Query}, which spreads `Predicates.shape`) and `transfer`
76
+ * webhook subscriptions (`Webhooks.schema.TransferFilters`), so the two never
77
+ * drift. Read-only concerns (pagination, windowing, ordering, `include`,
78
+ * `chainId`) are *not* here — they live only on the list query and have no
79
+ * meaning for an open-ended subscription. Consumers that need different
80
+ * per-field docs (e.g. the list query's mutually-exclusive `address`) override
81
+ * that one field.
82
+ */
83
+ schema.Predicates = z.object({
84
+ address: z
85
+ .optional(Schema.Address)
86
+ .check(z.describe('Match transfers where this account is either the sender or the recipient.')),
87
+ recipient: z
88
+ .optional(Schema.Address)
89
+ .check(z.describe('Match transfers sent to this recipient address (`to`).')),
90
+ sender: z
91
+ .optional(Schema.Address)
92
+ .check(z.describe('Match transfers sent from this sender address (`from`).')),
93
+ token: z
94
+ .optional(Schema.TokenAddress)
95
+ .check(z.describe('Match transfers for this TIP-20 token contract address.')),
96
+ });
97
+ /** Schemas for the getTransfers (list) operation. */
98
+ let getTransfers;
99
+ (function (getTransfers) {
100
+ /** Optional related resources that callers opt into via `include`. */
101
+ getTransfers.Include = z
102
+ .enum(['crossToken', 'memo', 'token', 'totalCount'])
103
+ .check(z.describe('Related resources to include only when you request them with `include`.'));
104
+ /**
105
+ * 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.
109
+ */
110
+ getTransfers.includeQuery = Schema.includeQuery(getTransfers.Include, 'Comma-separated related resources to include, such as `token,memo,crossToken,totalCount`.');
111
+ /**
112
+ * Query parameters for top-level transfer list requests. `address` is a
113
+ * convenience for "either side" — passing it alongside an explicit
114
+ * `sender` or `recipient` is rejected as ambiguous (the helper would have
115
+ * to AND/OR them in a non-obvious way).
116
+ */
117
+ getTransfers.Query = z
118
+ .strictObject({
119
+ // Predicate fields (`address`, `recipient`, `sender`, `token`) are shared
120
+ // verbatim with `transfer` webhook filters via `schema.Predicates`;
121
+ // `address` is overridden below for its list-only mutual-exclusion note.
122
+ ...schema.Predicates.shape,
123
+ address: z
124
+ .optional(Schema.Address)
125
+ .check(z.describe('Only include transfers where this account is the sender or recipient. Use this shortcut by itself, not with `sender` or `recipient`.')),
126
+ 'blockNumber.from': Schema.blockNumberBound('transfers', 'from'),
127
+ 'blockNumber.to': Schema.blockNumberBound('transfers', 'to'),
128
+ chainId: Schema.ChainIdQuery,
129
+ cursor: Schema.Cursor,
130
+ include: getTransfers.includeQuery,
131
+ limit: Schema.Limit,
132
+ order: Schema.Order,
133
+ page: Schema.Page,
134
+ 'timestamp.from': Schema.timestampBound('transfers', 'from'),
135
+ 'timestamp.to': Schema.timestampBound('transfers', 'to'),
136
+ })
137
+ .check(z.refine((query) => !(query.address && (query.sender || query.recipient)), {
138
+ error: '`address` is a shortcut for `sender` or `recipient`; pass either `address` or one explicit side, not both.',
139
+ path: ['address'],
140
+ }), ...Schema.pageChecks())
141
+ .check(z.describe('Query parameters for listing token transfer events.'));
142
+ /**
143
+ * A single transfer row. The token contract is carried per-row via
144
+ * `sourceToken.address` because `/transfers` is unscoped and rows can span
145
+ * multiple contracts.
146
+ */
147
+ getTransfers.Transfer = z
148
+ .object({
149
+ attribution: z
150
+ .optional(z.string())
151
+ .check(z.describe('The resolved MPP service name, when the transfer memo matches a known service fingerprint. Returned when you request `include=memo`.'), z.meta({ examples: ['example-service'] })),
152
+ blockNumber: z
153
+ .number()
154
+ .check(z.int(), z.nonnegative(), z.describe('The block number where this transfer was recorded.'), z.meta({ examples: [23456789] })),
155
+ destinationToken: z
156
+ .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`.')),
158
+ id: z.string().check(z.describe('A stable resource ID for this transfer, built from `${transactionHash}-${logIndex}`.'), z.meta({
159
+ examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-0'],
160
+ })),
161
+ logIndex: z
162
+ .number()
163
+ .check(z.int(), z.nonnegative(), z.describe('The transfer event’s log index within the block.'), z.meta({ examples: [0] })),
164
+ memo: z
165
+ .optional(z.string())
166
+ .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
+ recipient: Schema.Address.check(z.describe('The account address that received the tokens.')),
168
+ 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.')),
170
+ timestamp: z.iso
171
+ .datetime()
172
+ .check(z.describe('The block timestamp when this transfer was recorded.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
173
+ transactionHash: Schema.Hash.check(z.describe('The hash of the transaction that emitted this transfer event.')),
174
+ })
175
+ .check(z.describe('One TIP-20 token transfer event, optionally including a payment memo for reconciliation or invoices.'));
176
+ /** Page of TIP-20 token transfers across the chain, ordered by block then log index. */
177
+ getTransfers.Response = z
178
+ .object({
179
+ data: z.array(getTransfers.Transfer).check(z.describe('The transfers in this page.')),
180
+ // 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.
183
+ meta: z
184
+ .optional(Schema.CountMeta)
185
+ .check(z.describe('Page-level resources you requested with `include`.')),
186
+ nextCursor: Schema.NextCursor,
187
+ })
188
+ .check(z.describe('A page of TIP-20 token transfer events across the chain.'));
189
+ })(getTransfers = schema.getTransfers || (schema.getTransfers = {}));
190
+ })(schema || (schema = {}));
191
+ /** Creates transfer handlers. */
192
+ export function transfers() {
193
+ return new Hono().get('/', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getTransfers.Query, {
194
+ code: 'query_invalid',
195
+ message: 'Invalid query parameters',
196
+ }), OpenApi.describeRoute({
197
+ description: 'List token transfer events across Tempo.',
198
+ operationId: 'getTransfers',
199
+ responses: OpenApi.responses({
200
+ errors: { 502: 'The upstream indexer could not serve this request.' },
201
+ success: {
202
+ description: 'A page of token transfer events.',
203
+ schema: schema.getTransfers.Response,
204
+ },
205
+ }),
206
+ summary: 'List transfers',
207
+ tags: ['Transfers'],
208
+ }), Cache.response({
209
+ cacheControl: Cache.policies.feed,
210
+ name: 'tempo-api:transfers:v1',
211
+ key: (c) => Cache.urlKey(c, schema.getTransfers.Query),
212
+ }), async (c) => {
213
+ if (Auth.narrowAccess)
214
+ return Auth.paidAccessError(c);
215
+ if (OpenApi.narrowValidation)
216
+ return OpenApi.validationError(c, {
217
+ code: 'query_invalid',
218
+ message: 'Invalid query parameters',
219
+ });
220
+ const options = c.req.valid('query');
221
+ 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.
225
+ if (options.cursor !== undefined)
226
+ Cache.setPolicy(c, Cache.policies.immutable);
227
+ // Filters shared by the page query and the opt-in total count, so the
228
+ // count always matches the page it annotates.
229
+ const predicates = {
230
+ chainId,
231
+ eitherSide: options.address,
232
+ fromBlock: options['blockNumber.from'],
233
+ fromTimestamp: options['timestamp.from'],
234
+ recipient: options.recipient,
235
+ sender: options.sender,
236
+ toBlock: options['blockNumber.to'],
237
+ toTimestamp: options['timestamp.to'],
238
+ token: options.token,
239
+ };
240
+ try {
241
+ // `totalCount` is opt-in and independent of the page rows, so kick it
242
+ // off concurrently with the page query. It is best-effort: a count
243
+ // failure (e.g. upstream timeout) omits `meta` rather than failing the
244
+ // whole page.
245
+ const countPromise = options.include.includes('totalCount')
246
+ ? count(c, {
247
+ ...predicates,
248
+ crossToken: options.include.includes('crossToken'),
249
+ timing: 'transfers_count',
250
+ }).catch(() => undefined)
251
+ : undefined;
252
+ const page = await query(c, {
253
+ ...predicates,
254
+ cursor: options.cursor,
255
+ limit: options.limit,
256
+ order: options.order,
257
+ page: options.page,
258
+ timing: 'transfers',
259
+ });
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
+ let data = page.data.map((row) => ({
266
+ blockNumber: row.blockNumber,
267
+ id: `${row.transactionHash}-${row.logIndex}`,
268
+ logIndex: row.logIndex,
269
+ recipient: row.recipient,
270
+ sender: row.sender,
271
+ sourceToken: { address: row.address, amount: row.amount },
272
+ timestamp: row.timestamp,
273
+ transactionHash: row.transactionHash,
274
+ }));
275
+ // Memo/attribution are opt-in: one `TransferWithMemo` lookup bounded
276
+ // to the page's transactions resolves both — the decoded text `memo`,
277
+ // and `attribution` when the memo is a known MPP service fingerprint.
278
+ if (options.include.includes('memo') && data.length > 0) {
279
+ const memoByRow = await memoMap(c, { chainId, rows: data });
280
+ data = data.map((transfer) => {
281
+ const entry = memoByRow.get(transferKey(transfer.transactionHash, transfer.sender, transfer.recipient, transfer.sourceToken.amount, transfer.sourceToken.address));
282
+ if (!entry)
283
+ return transfer;
284
+ return {
285
+ ...transfer,
286
+ ...(entry.attribution ? { attribution: entry.attribution } : {}),
287
+ ...(entry.memo ? { memo: entry.memo } : {}),
288
+ };
289
+ });
290
+ }
291
+ // Cross-token routing is opt-in. One `Transfer` lookup bounded to the
292
+ // page's transactions folds the account server's approve+swap+forward
293
+ // multicall into a single row on the delivery leg: `sourceToken` becomes
294
+ // the token swapped into the DEX and `destinationToken` the token
295
+ // delivered to the recipient. The intermediate DEX legs and the fee leg
296
+ // are dropped from the page. Folding anchors on the delivery leg (whose
297
+ // own event token is the delivered token), so a `token=` filter matches
298
+ // cross-token rows by their destination token, and each transfer still
299
+ // appears exactly once across pages.
300
+ if (options.include.includes('crossToken') && data.length > 0) {
301
+ const { fold, suppress } = await crossTokenMap(c, { chainId, rows: data });
302
+ data = data.flatMap((transfer) => {
303
+ const key = eventKey(transfer.transactionHash, transfer.logIndex);
304
+ const folded = fold.get(key);
305
+ if (folded)
306
+ return [
307
+ {
308
+ ...transfer,
309
+ destinationToken: folded.destinationToken,
310
+ recipient: folded.recipient,
311
+ sender: folded.sender,
312
+ sourceToken: folded.sourceToken,
313
+ },
314
+ ];
315
+ return suppress.has(key) ? [] : [transfer];
316
+ });
317
+ }
318
+ if (options.include.includes('token') && data.length > 0) {
319
+ const uniqueTokens = Array.from(new Set(data.flatMap((transfer) => [
320
+ transfer.sourceToken.address,
321
+ ...(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;
335
+ };
336
+ data = data.map((transfer) => ({
337
+ ...transfer,
338
+ sourceToken: withToken(transfer.sourceToken),
339
+ ...(transfer.destinationToken
340
+ ? { destinationToken: withToken(transfer.destinationToken) }
341
+ : {}),
342
+ }));
343
+ }
344
+ const meta = countPromise ? await countPromise : undefined;
345
+ return c.json(Response.validated(schema.getTransfers.Response, {
346
+ data,
347
+ ...(meta ? { meta } : {}),
348
+ nextCursor: page.nextCursor,
349
+ }), 200);
350
+ }
351
+ catch (cause) {
352
+ return Response.upstream(c, cause);
353
+ }
354
+ });
355
+ }
356
+ /**
357
+ * Shared TIP-20 `Transfer` page query. Used by `GET /transfers` and the
358
+ * webhook poller. Builds the signature-decoded `Transfer` CTE query, applies
359
+ * opaque keyset pagination on `(block_num, log_idx)`, and decodes rows into a
360
+ * strongly-typed intermediate shape that each caller maps to its own public
361
+ * response.
362
+ *
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.
367
+ *
368
+ * Memoization lives inside the helper under one canonical `transfers:v1:…`
369
+ * key derived from every filter — so equivalent filter combinations share a
370
+ * single cache entry. Callers supply only a `timing` label for Server-Timing.
371
+ */
372
+ export function query(c, options) {
373
+ const { chainId, timing, ...rest } = options;
374
+ const store = c.get('store');
375
+ const tidx = c.get('getTidx')(chainId);
376
+ const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
377
+ return Timing.time(c, timing, () => Store.memoize(async () => {
378
+ const page = await scan({ tidx }, { chainId, ...rest });
379
+ return {
380
+ data: page.items.map((item) => item.data),
381
+ nextCursor: page.nextCursor,
382
+ };
383
+ }, {
384
+ key: `transfers:v1:${chainId}:${options.order}:${options.token ?? ''}:${options.eitherSide ?? ''}:${options.sender ?? ''}:${options.recipient ?? ''}:${options.fromBlock ?? ''}:${options.toBlock ?? ''}:${options.fromTimestamp ?? ''}:${options.toTimestamp ?? ''}:${cursor ? `cursor:${cursor[0]}:${cursor[1]}` : options.page !== undefined && options.page > 1 ? `page:${options.page}` : 'head'}:${options.limit}`,
385
+ store,
386
+ ttl: Ttl.seconds(15),
387
+ }));
388
+ }
389
+ /**
390
+ * Context-free core of {@link query}: builds the signature-decoded `Transfer`
391
+ * CTE query, applies keyset pagination on `(block_num, log_idx)`, and decodes
392
+ * rows. Used by the Hono routes (via {@link query}, which adds Server-Timing +
393
+ * memoization) and by the webhook poller (uncached, ascending, per-row cursors).
394
+ */
395
+ export async function scan(deps, options) {
396
+ const { chainId, limit, order } = options;
397
+ const { tidx } = deps;
398
+ const direction = order === 'asc' ? 'ASC' : 'DESC';
399
+ // Keyset pagination: anchor the page below the previous row's `(block, log_idx)`
400
+ // position instead of a numeric offset, so rows arriving at the head can't
401
+ // shift items across pages. A malformed cursor falls back to the head page.
402
+ const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
403
+ // Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
404
+ // the head page, so it shares the head's SQL and cache entries; deeper pages
405
+ // translate to a SQL OFFSET of `(page - 1) * limit` rows.
406
+ 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.
411
+ const filters = filterClauses(options);
412
+ if (cursor !== undefined)
413
+ filters.push(Cursor.keyset([
414
+ { literal: Cursor.literal(cursor[0], 'int'), name: 'block_num', order },
415
+ { literal: Cursor.literal(cursor[1], 'int'), name: 'log_idx', order },
416
+ ]));
417
+ const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
418
+ // Fetch one extra row to detect `hasMore` without a separate count query. The
419
+ // global minimum page size (`Schema.minLimit`) keeps this at or above the
420
+ // indexer's floor for signature-decoded `Transfer` queries.
421
+ const result = await tidx.fetch({
422
+ chainId,
423
+ query: `
424
+ SELECT "from", "to", address, value, tx_hash, block_num, log_idx, block_timestamp
425
+ FROM Transfer
426
+ ${where}
427
+ ORDER BY block_num ${direction}, log_idx ${direction}
428
+ LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
429
+ `,
430
+ signatures: [signature],
431
+ });
432
+ // The next page anchors below the last fetched row's `(block, log_idx)`.
433
+ const page = Cursor.paginate({
434
+ rows: result.rows,
435
+ limit,
436
+ key: (row) => {
437
+ const block = Value.toNumber(row['block_num']);
438
+ const index = Value.toNumber(row['log_idx']);
439
+ return block !== undefined && index !== undefined ? [block, index] : undefined;
440
+ },
441
+ });
442
+ const items = [];
443
+ for (const row of page.rows) {
444
+ const tokenAddress = Schema.Address.safeParse(row['address']);
445
+ const from = Schema.Address.safeParse(row['from']);
446
+ const to = Schema.Address.safeParse(row['to']);
447
+ const transactionHash = Schema.Hash.safeParse(row['tx_hash']);
448
+ const amount = Value.toIntegerString(row['value']);
449
+ const blockNumber = Value.toNumber(row['block_num']);
450
+ const logIndex = Value.toNumber(row['log_idx']);
451
+ const timestamp = Value.toIsoDateTime(row['block_timestamp']);
452
+ if (tokenAddress.success &&
453
+ from.success &&
454
+ to.success &&
455
+ transactionHash.success &&
456
+ amount !== undefined &&
457
+ blockNumber !== undefined &&
458
+ logIndex !== undefined &&
459
+ timestamp !== undefined)
460
+ items.push({
461
+ cursor: [blockNumber, logIndex],
462
+ data: {
463
+ address: tokenAddress.data,
464
+ amount,
465
+ blockNumber,
466
+ logIndex,
467
+ recipient: to.data,
468
+ sender: from.data,
469
+ timestamp,
470
+ transactionHash: transactionHash.data,
471
+ },
472
+ });
473
+ }
474
+ return { hasMore: page.hasMore, items, limit, nextCursor: page.nextCursor };
475
+ }
476
+ /**
477
+ * Builds the shared `WHERE` filter clauses for the `Transfer` feed: contract /
478
+ * subject / sender / recipient / block / timestamp predicates. Shared verbatim
479
+ * by the page query ({@link scan}) and the capped count ({@link countScan}) so a
480
+ * `totalCount` always matches the page it annotates; the keyset cursor clause is
481
+ * page-only and added by {@link scan} on top of these.
482
+ */
483
+ function filterClauses(options) {
484
+ const { eitherSide, fromBlock, fromTimestamp, recipient, sender, toBlock, toTimestamp, token } = options;
485
+ const filters = [];
486
+ if (token !== undefined)
487
+ filters.push(`address = '${token}'`);
488
+ if (eitherSide !== undefined)
489
+ filters.push(`("from" = '${eitherSide}' OR "to" = '${eitherSide}')`);
490
+ if (sender !== undefined)
491
+ filters.push(`"from" = '${sender}'`);
492
+ if (recipient !== undefined)
493
+ filters.push(`"to" = '${recipient}'`);
494
+ if (fromBlock !== undefined)
495
+ filters.push(`block_num >= ${fromBlock}`);
496
+ if (toBlock !== undefined)
497
+ filters.push(`block_num <= ${toBlock}`);
498
+ if (fromTimestamp !== undefined)
499
+ filters.push(`block_timestamp >= '${fromTimestamp}'`);
500
+ if (toTimestamp !== undefined)
501
+ filters.push(`block_timestamp <= '${toTimestamp}'`);
502
+ return filters;
503
+ }
504
+ /**
505
+ * Capped total-row count for the `Transfer` feed, sharing {@link query}'s
506
+ * filters (minus pagination) so the count matches the page it annotates. Timed
507
+ * and memoized like {@link query}; the result feeds `meta.totalCount`/`totalCountCapped`
508
+ * when a caller opts in via `include=totalCount`.
509
+ */
510
+ export function count(c, options) {
511
+ const { chainId, timing, ...rest } = options;
512
+ const store = c.get('store');
513
+ const tidx = c.get('getTidx')(chainId);
514
+ return Timing.time(c, timing, () => Store.memoize(() => countScan({ tidx }, { chainId, ...rest }), {
515
+ key: `transfers:count:v1:${chainId}:${rest.crossToken ? 'fold' : 'raw'}:${rest.token ?? ''}:${rest.eitherSide ?? ''}:${rest.sender ?? ''}:${rest.recipient ?? ''}:${rest.fromBlock ?? ''}:${rest.toBlock ?? ''}:${rest.fromTimestamp ?? ''}:${rest.toTimestamp ?? ''}`,
516
+ store,
517
+ ttl: Ttl.seconds(15),
518
+ }));
519
+ }
520
+ /**
521
+ * Context-free core of {@link count}: runs a capped `count(*)` over the
522
+ * signature-decoded `Transfer` CTE under the shared {@link filterClauses}. The
523
+ * inner `LIMIT Schema.countCap` bounds the scan so the aggregate can't exceed
524
+ * the indexer's execution budget on the 10⁹-plus row table (an unbounded count
525
+ * there blows the 5s `max_execution_time`); a result equal to the cap is a
526
+ * lower bound, surfaced as `totalCountCapped: true`.
527
+ */
528
+ export async function countScan(deps, options) {
529
+ const { chainId } = options;
530
+ const { tidx } = deps;
531
+ const filters = filterClauses(options);
532
+ const where = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
533
+ // Folded count: cross-token transfers collapse several `Transfer` legs into
534
+ // one row, so a raw `count(*)` would overcount. Scan the capped `Transfer`
535
+ // rows, classify each transaction, and subtract the folded-away legs. Bounded
536
+ // by `countCap` like the raw count, so it stays within the indexer's budget.
537
+ if (options.crossToken) {
538
+ const result = await tidx.fetch({
539
+ chainId,
540
+ query: `
541
+ SELECT tx_hash, log_idx, "from", "to", address, value
542
+ FROM Transfer
543
+ ${where}
544
+ ORDER BY block_num, log_idx
545
+ LIMIT ${Schema.countCap}
546
+ `,
547
+ signatures: [signature],
548
+ });
549
+ const rawCount = result.rows.length;
550
+ let suppressed = 0;
551
+ for (const legs of legsByTransaction(result.rows).values()) {
552
+ const bundle = classifyCrossToken(legs);
553
+ if (bundle)
554
+ suppressed += bundle.suppressLogIndexes.length;
555
+ }
556
+ return { totalCountCapped: rawCount >= Schema.countCap, totalCount: rawCount - suppressed };
557
+ }
558
+ const result = await tidx.fetch({
559
+ chainId,
560
+ query: `
561
+ SELECT count(*) AS total
562
+ FROM (
563
+ SELECT 1
564
+ FROM Transfer
565
+ ${where}
566
+ LIMIT ${Schema.countCap}
567
+ ) AS capped
568
+ `,
569
+ signatures: [signature],
570
+ });
571
+ const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0;
572
+ return { totalCountCapped: totalCount >= Schema.countCap, totalCount };
573
+ }
574
+ /** `'tx1', 'tx2', …` SQL `IN` list of the page's distinct transaction hashes. */
575
+ function txInList(rows) {
576
+ return [...new Set(rows.map((row) => row.transactionHash.toLowerCase()))]
577
+ .map((hash) => `'${hash}'`)
578
+ .join(', ');
579
+ }
580
+ /**
581
+ * `block_num BETWEEN min AND max` bound covering the page's rows. Event-CTE
582
+ * predicates on `tx_hash` cannot prune `logs` (sorted by block position), so
583
+ * a bare `tx_hash IN (…)` scans to genesis; pairing it with the page's block
584
+ * range keeps the lookup a bounded range read. Callers only invoke the memo
585
+ * lookup for non-empty pages.
586
+ */
587
+ function blockBound(rows) {
588
+ const blocks = rows.map((row) => row.blockNumber);
589
+ return `block_num BETWEEN ${Math.min(...blocks)} AND ${Math.max(...blocks)}`;
590
+ }
591
+ /**
592
+ * Resolves the memo of each of a page's transfers from their matching
593
+ * `TransferWithMemo` events, matched back to rows by {@link transferKey}: the
594
+ * decoded text `memo`, and `attribution` (the resolved MPP service name) when
595
+ * the memo is a known service fingerprint. The fingerprint directory is
596
+ * best-effort: a fetch failure leaves a memo unattributed rather than failing
597
+ * the page.
598
+ */
599
+ function memoMap(c, options) {
600
+ const { chainId, rows } = options;
601
+ const tidx = c.get('getTidx')(chainId);
602
+ return Timing.time(c, 'transfers_memo', async () => {
603
+ const [memos, fingerprintMap] = await Promise.all([
604
+ tidx.fetch({
605
+ chainId,
606
+ query: `
607
+ SELECT tx_hash, "from", "to", amount, address, memo
608
+ FROM TransferWithMemo
609
+ WHERE ${blockBound(rows)} AND tx_hash IN (${txInList(rows)})
610
+ `,
611
+ signatures: [memoSignature],
612
+ }),
613
+ Mpp.fingerprintMap({ store: c.get('store') }).catch(() => ({})),
614
+ ]);
615
+ const memoByRow = new Map();
616
+ for (const row of memos.rows) {
617
+ const rawMemo = Value.toText(row['memo']);
618
+ const attribution = Mpp.resolve({ fingerprintMap, memo: rawMemo });
619
+ const memo = memoToString(rawMemo);
620
+ const amount = Value.toIntegerString(row['amount']);
621
+ const txHash = Value.toText(row['tx_hash']);
622
+ const from = Value.toText(row['from']);
623
+ const to = Value.toText(row['to']);
624
+ const token = Value.toText(row['address']);
625
+ if ((attribution || memo) && amount && txHash && from && to && token)
626
+ memoByRow.set(transferKey(txHash, from, to, amount, token), { attribution, memo });
627
+ }
628
+ return memoByRow;
629
+ });
630
+ }
631
+ /**
632
+ * Classifies a single transaction's `Transfer` legs as a cross-token transfer,
633
+ * or returns `undefined`. A cross-token transfer is the account server's
634
+ * approve+swap+forward multicall: the source token is sent into the DEX, the
635
+ * swapped token comes back to the swapper, and that swapped token is forwarded
636
+ * to a different recipient in the same transaction. The transfer folds onto the
637
+ * delivery (forwarding) leg; the two DEX legs and any fee leg fold away. A swap
638
+ * whose output stays with the swapper (no onward leg) is not a cross-token
639
+ * transfer and is left untouched.
640
+ */
641
+ function classifyCrossToken(legs) {
642
+ const toDex = legs.find((leg) => Address.isEqual(leg.to, Addresses.stablecoinDex));
643
+ const fromDex = legs.find((leg) => Address.isEqual(leg.from, Addresses.stablecoinDex));
644
+ if (!toDex || !fromDex)
645
+ return undefined;
646
+ const swapper = fromDex.to;
647
+ const destTokenAddress = fromDex.address;
648
+ const delivery = legs.find((leg) => Address.isEqual(leg.address, destTokenAddress) &&
649
+ Address.isEqual(leg.from, swapper) &&
650
+ !Address.isEqual(leg.to, Addresses.stablecoinDex) &&
651
+ !Address.isEqual(leg.to, swapper) &&
652
+ !Address.isEqual(leg.to, Addresses.feeManager));
653
+ // Require a token change so plain same-token DEX activity is not folded.
654
+ if (!delivery || Address.isEqual(toDex.address, delivery.address))
655
+ return undefined;
656
+ const suppressLogIndexes = legs
657
+ .filter((leg) => leg.logIndex !== delivery.logIndex &&
658
+ (Address.isEqual(leg.to, Addresses.stablecoinDex) ||
659
+ Address.isEqual(leg.from, Addresses.stablecoinDex) ||
660
+ Address.isEqual(leg.to, Addresses.feeManager)))
661
+ .map((leg) => leg.logIndex);
662
+ return {
663
+ deliveryLogIndex: delivery.logIndex,
664
+ destinationToken: { address: delivery.address, amount: delivery.amount },
665
+ recipient: delivery.to,
666
+ sender: swapper,
667
+ sourceToken: { address: toDex.address, amount: toDex.amount },
668
+ suppressLogIndexes,
669
+ };
670
+ }
671
+ /** Groups decoded `Transfer` rows by lowercased transaction hash. */
672
+ function legsByTransaction(rows) {
673
+ const legsByTx = new Map();
674
+ for (const row of rows) {
675
+ const txHash = Value.toText(row['tx_hash']);
676
+ const from = Schema.Address.safeParse(row['from']);
677
+ const to = Schema.Address.safeParse(row['to']);
678
+ const address = Schema.Address.safeParse(row['address']);
679
+ const amount = Value.toIntegerString(row['value']);
680
+ const logIndex = Value.toNumber(row['log_idx']);
681
+ if (txHash &&
682
+ from.success &&
683
+ to.success &&
684
+ address.success &&
685
+ amount !== undefined &&
686
+ logIndex !== undefined) {
687
+ const legs = legsByTx.get(txHash.toLowerCase()) ?? [];
688
+ legs.push({ address: address.data, amount, from: from.data, logIndex, to: to.data });
689
+ legsByTx.set(txHash.toLowerCase(), legs);
690
+ }
691
+ }
692
+ return legsByTx;
693
+ }
694
+ /**
695
+ * Detects cross-token transfers among a page's transactions and returns the
696
+ * folding plan: `fold` maps each delivery leg (by `${txHash}-${logIndex}`) to
697
+ * its folded row (swapper, recipient, swapped-in `sourceToken`, delivered
698
+ * `destinationToken`), and `suppress` is the set of the other bundle legs (the
699
+ * two DEX legs and any fee leg) to drop from the page.
700
+ *
701
+ * Bounded to the page's transactions and block range like {@link memoMap}, so it
702
+ * discovers the sibling DEX legs even when they fall outside the current page.
703
+ *
704
+ * TODO(https://tips.sh/1072): once TIP-1072 lands, cross-token conversions emit
705
+ * a single self-describing `Convert(user, sourceToken, destinationToken,
706
+ * backingToken, amount)` event. Replace this extra `Transfer` lookup and the
707
+ * multi-leg {@link classifyCrossToken} heuristic with a direct `Convert` decode
708
+ * (and drop the folded-count scan in {@link countScan}).
709
+ */
710
+ function crossTokenMap(c, options) {
711
+ const { chainId, rows } = options;
712
+ const tidx = c.get('getTidx')(chainId);
713
+ return Timing.time(c, 'transfers_cross_token', async () => {
714
+ const result = await tidx.fetch({
715
+ chainId,
716
+ query: `
717
+ SELECT tx_hash, log_idx, "from", "to", address, value
718
+ FROM Transfer
719
+ WHERE ${blockBound(rows)} AND tx_hash IN (${txInList(rows)})
720
+ ORDER BY tx_hash, log_idx
721
+ `,
722
+ signatures: [signature],
723
+ });
724
+ const fold = new Map();
725
+ const suppress = new Set();
726
+ for (const [txHash, legs] of legsByTransaction(result.rows)) {
727
+ const bundle = classifyCrossToken(legs);
728
+ if (!bundle)
729
+ continue;
730
+ fold.set(eventKey(txHash, bundle.deliveryLogIndex), {
731
+ destinationToken: bundle.destinationToken,
732
+ recipient: bundle.recipient,
733
+ sender: bundle.sender,
734
+ sourceToken: bundle.sourceToken,
735
+ });
736
+ for (const logIndex of bundle.suppressLogIndexes)
737
+ suppress.add(eventKey(txHash, logIndex));
738
+ }
739
+ return { fold, suppress };
740
+ });
741
+ }
742
+ /** Composite `${transactionHash}-${logIndex}` key for a single transfer event. */
743
+ function eventKey(transactionHash, logIndex) {
744
+ return `${transactionHash}-${logIndex}`.toLowerCase();
745
+ }
746
+ /**
747
+ * Attempts to decode a bytes32 memo into a human-readable string. MPP
748
+ * attribution memos encode a service fingerprint, not text — they are
749
+ * suppressed from the display memo (and resolved to `attribution` instead).
750
+ * Shared with the activity feed's transfer entries.
751
+ */
752
+ export function memoToString(memo) {
753
+ if (Mpp.isAttributionMemo(memo))
754
+ return undefined;
755
+ try {
756
+ const trimmed = Hex.trimLeft(memo);
757
+ if (trimmed === '0x' || trimmed === '0x0')
758
+ return undefined;
759
+ return Hex.toString(trimmed);
760
+ }
761
+ catch {
762
+ return undefined;
763
+ }
764
+ }
765
+ /** Composite match key shared by the `Transfer` row and its `TransferWithMemo`. */
766
+ function transferKey(txHash, from, to, amount, token) {
767
+ return `${txHash}:${from}:${to}:${amount}:${token}`.toLowerCase();
768
+ }
769
+ //# sourceMappingURL=transfers.js.map