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,815 @@
1
+ import { Hono } from 'hono';
2
+ import { AbiEvent, AbiParameters, Hash, Value as core_Value } from 'ox';
3
+ import { Abis, 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 OpenApi from '../internal/OpenApi.js';
9
+ import * as Response from '../internal/Response.js';
10
+ import * as Schema from '../internal/Schema.js';
11
+ import * as Store from '../internal/Store.js';
12
+ import * as Timing from '../internal/Timing.js';
13
+ import * as Ttl from '../internal/Ttl.js';
14
+ import * as Value from '../internal/Value.js';
15
+ import * as VerifiedTokens from '../internal/VerifiedTokens.js';
16
+ import * as Tokens from './tokens.js';
17
+ // The protocol Fee AMM lives at the reserved FeeManager address. Pools are
18
+ // `(userToken, validatorToken)` pairs discovered from the `Mint` events the
19
+ // FeeManager emits; there is no on-chain pool registry to enumerate.
20
+ const feeManager = Addresses.feeManager.toLowerCase();
21
+ // pathUSD is the protocol's default fee token: a transaction whose fee token
22
+ // matches the validator's token (or falls back to pathUSD) pays fees without
23
+ // needing a Fee AMM pool, so pathUSD is always fee-usable even when it has no
24
+ // user-side pool. Lowercased to match the indexer's stored address casing.
25
+ const pathUsd = Addresses.pathUsd.toLowerCase();
26
+ // Topic0 hashes of the FeeManager `Mint` event, current and legacy. Both
27
+ // signatures index `userToken` as topic2 and `validatorToken` as topic3, but
28
+ // their other fields differ, so rows are decoded per selector: the current
29
+ // event carries data words `[sender, amountValidatorToken, liquidity]` with
30
+ // topic1 = `to`; the legacy event carries `[amountUserToken,
31
+ // amountValidatorToken, liquidity]` with topic1 = `sender`. Queries hit the
32
+ // raw `logs` table rather than a signature CTE: the event-CTE planner rejects
33
+ // filters on event params, and the two signatures need different decoders
34
+ // anyway.
35
+ const mintTopic = AbiEvent.getSelector('event Mint(address sender, address indexed to, address indexed userToken, address indexed validatorToken, uint256 amountValidatorToken, uint256 liquidity)');
36
+ const legacyMintTopic = AbiEvent.getSelector('event Mint(address indexed sender, address indexed userToken, address indexed validatorToken, uint256 amountUserToken, uint256 amountValidatorToken, uint256 liquidity)');
37
+ /** Zod schemas owned by the fee AMM handlers. */
38
+ export var schema;
39
+ (function (schema) {
40
+ /**
41
+ * A token referenced by a fee pool: its contract address plus best-effort
42
+ * metadata. Mirrors the transfer feed's `sourceToken` technique: `address`
43
+ * is always present; the metadata fields spread in only when resolution
44
+ * succeeds, so a token whose metadata is unavailable keeps just the bare
45
+ * reference. The pools listing extends this shape with the side's reserve
46
+ * (`getFeeAmmPools.Token`).
47
+ */
48
+ schema.PoolToken = Schema.describe(z.object({
49
+ address: Schema.TokenAddress.check(z.describe('TIP-20 token contract address used by this fee pool.')),
50
+ currency: z
51
+ .optional(z.string())
52
+ .check(z.describe('Display currency for the token, when available.'), z.meta({ examples: ['USD'] })),
53
+ decimals: z
54
+ .optional(z.number().check(z.int(), z.nonnegative()))
55
+ .check(z.describe('Number of decimal places the token uses.'), z.meta({ examples: [6] })),
56
+ logoUri: z.optional(z.string()).check(z.describe('URL for the token logo SVG, when available.'), z.meta({
57
+ examples: [
58
+ 'https://api.tempo.xyz/data/4217/icons/0x20c0000000000000000000008f5425160ebe5525.svg',
59
+ ],
60
+ })),
61
+ name: z
62
+ .optional(z.string())
63
+ .check(z.describe('Human-readable token name.'), z.meta({ examples: ['USD Coin'] })),
64
+ symbol: z
65
+ .optional(z.string())
66
+ .check(z.describe('Short token ticker symbol.'), z.meta({ examples: ['USDC'] })),
67
+ verified: z
68
+ .optional(z.boolean())
69
+ .check(z.describe('Whether Tempo has verified this token’s metadata.'), z.meta({ examples: [true] })),
70
+ }), 'A TIP-20 token used by a Fee AMM pool, with metadata when available.');
71
+ /** Schemas for the getFeeAmmPools operation. */
72
+ let getFeeAmmPools;
73
+ (function (getFeeAmmPools) {
74
+ /** Optional related resources that callers opt into via `include`. */
75
+ getFeeAmmPools.Include = z
76
+ .enum(['token'])
77
+ .check(z.describe('Optional related data you can request with `include`.'));
78
+ /**
79
+ * Parses a comma-separated `include` query value into a list of optional
80
+ * resources to embed. Token metadata resolution is one extra (batched)
81
+ * `token.getMetadata` round trip, so it runs only when `include=token` is
82
+ * requested; the base page returns each side's bare `address` plus its
83
+ * reserve and skips the lookup entirely.
84
+ */
85
+ getFeeAmmPools.includeQuery = Schema.includeQuery(getFeeAmmPools.Include, 'Comma-separated related resources to include, such as `token` metadata.');
86
+ /** Query parameters for the fee pool list request. */
87
+ getFeeAmmPools.Query = z
88
+ .strictObject({
89
+ chainId: Schema.ChainIdQuery,
90
+ cursor: Schema.Cursor,
91
+ include: getFeeAmmPools.includeQuery,
92
+ limit: Schema.Limit,
93
+ page: Schema.Page,
94
+ })
95
+ .check(...Schema.pageChecks(), z.describe('Query parameters for listing Fee AMM pools.'));
96
+ /**
97
+ * One side of a fee pool: the token reference carrying the side's current
98
+ * on-chain reserve. `amount`/`formatted` come from `FeeManager.getPool`
99
+ * and are absent when the read fails (best-effort, like the metadata
100
+ * fields).
101
+ */
102
+ getFeeAmmPools.Token = Schema.describe(z.extend(schema.PoolToken, {
103
+ amount: z
104
+ .optional(Schema.DecimalString)
105
+ .check(z.describe('Current onchain reserve for this token, in base units, from `FeeManager.getPool`. Omitted if the read fails.')),
106
+ formatted: z
107
+ .optional(z.string())
108
+ .check(z.describe('Reserve as a human-readable decimal string using the token’s `decimals`. Omitted if the read fails.'), z.meta({ examples: ['1.5'] })),
109
+ }), 'One token side of a Fee AMM pool, including its current onchain reserve when available.');
110
+ /** A fee AMM pool, aggregated from its `Mint` events. */
111
+ getFeeAmmPools.Pool = z
112
+ .object({
113
+ createdAt: z.iso
114
+ .datetime()
115
+ .check(z.describe('Timestamp when liquidity was first minted into this pool.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
116
+ id: Schema.Hash.check(z.describe('Stable resource id for the pool; this is the same value as `poolId`.')),
117
+ lastMintAt: z.iso
118
+ .datetime()
119
+ .check(z.describe('Timestamp when liquidity was most recently minted into this pool.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
120
+ mintCount: z
121
+ .number()
122
+ .check(z.int(), z.nonnegative(), z.describe('Number of liquidity mints into this pool.'), z.meta({ examples: [25] })),
123
+ poolId: Schema.Hash.check(z.describe('Pool id computed as `keccak256(abi.encode(userToken, validatorToken))`, matching `FeeManager.getPoolId`.')),
124
+ userToken: getFeeAmmPools.Token.check(z.describe('Token users pay fees with in this pool, including its current reserve when available.')),
125
+ validatorToken: getFeeAmmPools.Token.check(z.describe('Token validators receive from this pool, including its current reserve when available.')),
126
+ })
127
+ .check(z.describe('A Fee AMM pool discovered from its onchain `Mint` events.'));
128
+ /** Page of fee pools, ordered by mint count (most active first). */
129
+ getFeeAmmPools.Response = z
130
+ .object({
131
+ data: z
132
+ .array(getFeeAmmPools.Pool)
133
+ .check(z.describe('Fee AMM pools ordered by mint count, most active first.')),
134
+ nextCursor: Schema.NextCursor,
135
+ })
136
+ .check(z.describe('A page of Fee AMM pools ordered by mint count, most active first.'));
137
+ })(getFeeAmmPools = schema.getFeeAmmPools || (schema.getFeeAmmPools = {}));
138
+ /** Schemas for the getFeeAmmMints operation. */
139
+ let getFeeAmmMints;
140
+ (function (getFeeAmmMints) {
141
+ /** Query parameters for the pool mint list request. */
142
+ getFeeAmmMints.Query = z
143
+ .strictObject({
144
+ chainId: Schema.ChainIdQuery,
145
+ cursor: Schema.Cursor,
146
+ include: Schema.totalCountInclude,
147
+ limit: Schema.Limit,
148
+ order: Schema.Order,
149
+ page: Schema.Page,
150
+ userToken: z
151
+ .optional(Schema.TokenAddress)
152
+ .check(z.describe('Only include mints for pools where users paid fees with this token.')),
153
+ validatorToken: z
154
+ .optional(Schema.TokenAddress)
155
+ .check(z.describe('Only include mints for pools where validators receive this token.')),
156
+ })
157
+ .check(...Schema.pageChecks(), z.describe('Query parameters for listing Fee AMM liquidity mints.'));
158
+ /** A single liquidity mint into a fee pool. */
159
+ getFeeAmmMints.Mint = z
160
+ .object({
161
+ amountUserToken: z
162
+ .optional(Schema.DecimalString)
163
+ .check(z.describe('Amount of the user-side token deposited, in base units. Only present on legacy-signature mints.')),
164
+ amountValidatorToken: Schema.DecimalString.check(z.describe('Amount of the validator-side token deposited, in base units.')),
165
+ blockNumber: z
166
+ .number()
167
+ .check(z.int(), z.nonnegative(), z.describe('Block number where the mint occurred.'), z.meta({ examples: [23456789] })),
168
+ id: z.string().check(z.describe('Stable mint id built from the transaction hash and log index (`${transactionHash}-${logIndex}`).'), z.meta({
169
+ examples: ['0x515801d7f9a5ac705e793e85904c9c69b3f1694b465cc8fb6ba3f0298dc82665-0'],
170
+ })),
171
+ liquidity: Schema.DecimalString.check(z.describe('Amount of pool liquidity minted, as an integer string.')),
172
+ logIndex: z
173
+ .number()
174
+ .check(z.int(), z.nonnegative(), z.describe('Log index of this mint within the block.'), z.meta({ examples: [0] })),
175
+ minter: Schema.Address.check(z.describe('Address that deposited tokens into the pool.')),
176
+ recipient: z
177
+ .optional(Schema.Address)
178
+ .check(z.describe('Address that received the minted liquidity. Only present on current-signature mints.')),
179
+ timestamp: z.iso
180
+ .datetime()
181
+ .check(z.describe('Block timestamp when the mint occurred.'), z.meta({ examples: ['2024-01-01T00:00:00Z'] })),
182
+ transactionHash: Schema.Hash.check(z.describe('Hash of the transaction that emitted this mint.')),
183
+ userToken: schema.PoolToken.check(z.describe('Token users pay fees with in this pool.')),
184
+ validatorToken: schema.PoolToken.check(z.describe('Token validators receive from this pool.')),
185
+ })
186
+ .check(z.describe('One liquidity mint into a Fee AMM pool.'));
187
+ /** Page of fee pool mints, ordered by block then log index. */
188
+ getFeeAmmMints.Response = z
189
+ .object({
190
+ data: z.array(getFeeAmmMints.Mint).check(z.describe('Liquidity mints returned on this page.')),
191
+ meta: z
192
+ .optional(Schema.CountMeta)
193
+ .check(z.describe('Response-wide metadata requested with `include`, such as `totalCount`.')),
194
+ nextCursor: Schema.NextCursor,
195
+ })
196
+ .check(z.describe('A page of Fee AMM liquidity mints.'));
197
+ })(getFeeAmmMints = schema.getFeeAmmMints || (schema.getFeeAmmMints = {}));
198
+ })(schema || (schema = {}));
199
+ /** Creates fee AMM handlers. */
200
+ export function feeAmm() {
201
+ return new Hono()
202
+ .get('/pools', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getFeeAmmPools.Query, {
203
+ code: 'query_invalid',
204
+ message: 'Invalid query parameters',
205
+ }), OpenApi.describeRoute({
206
+ description: 'Lists Fee AMM pools, most active first, so you can see which stablecoin fee conversions have the most liquidity activity.',
207
+ operationId: 'getFeeAmmPools',
208
+ responses: OpenApi.responses({
209
+ errors: { 502: 'The upstream Tempo indexer could not complete the request.' },
210
+ success: {
211
+ description: 'List of Fee AMM pools.',
212
+ schema: schema.getFeeAmmPools.Response,
213
+ },
214
+ }),
215
+ summary: 'List pools',
216
+ tags: ['Fee AMM'],
217
+ }), Cache.response({
218
+ cacheControl: Cache.policies.state,
219
+ name: 'tempo-api:fee-amm:v1',
220
+ key: (c) => Cache.urlKey(c, schema.getFeeAmmPools.Query),
221
+ }), async (c) => {
222
+ if (Auth.narrowAccess)
223
+ return Auth.paidAccessError(c);
224
+ if (OpenApi.narrowValidation)
225
+ return OpenApi.validationError(c, {
226
+ code: 'query_invalid',
227
+ message: 'Invalid query parameters',
228
+ });
229
+ const query = c.req.valid('query');
230
+ const chainId = query.chainId ?? c.get('chainId');
231
+ try {
232
+ const page = await listPools(c, {
233
+ chainId,
234
+ cursor: query.cursor,
235
+ limit: query.limit,
236
+ page: query.page,
237
+ });
238
+ const pools = page.data;
239
+ // Reserves are one `getPool` eth_call per pool; viem auto-batches
240
+ // the concurrent calls into a single deployless multicall round
241
+ // trip (see `internal/Viem.ts`).
242
+ const reserves = await Timing.time(c, 'fee_amm_reserves', () => Promise.all(pools.map((pool) => getPoolReserves(c, {
243
+ chainId,
244
+ userToken: pool.userToken,
245
+ validatorToken: pool.validatorToken,
246
+ }))));
247
+ // Token metadata is one extra (batched) `token.getMetadata` round
248
+ // trip, opt-in via `include=token`. Without it each side carries
249
+ // just its `address` and reserve; reserves still format via the
250
+ // `reserve` helper's TIP-20 (6-decimal) fallback.
251
+ const tokensByAddress = query.include.includes('token')
252
+ ? await resolveTokenMap(c, {
253
+ addresses: Array.from(new Set(pools.flatMap((pool) => [pool.userToken, pool.validatorToken]))),
254
+ chainId,
255
+ })
256
+ : undefined;
257
+ const data = pools.map((pool, index) => {
258
+ const id = poolId(pool.userToken, pool.validatorToken);
259
+ const userToken = { address: pool.userToken, ...tokensByAddress?.get(pool.userToken) };
260
+ const validatorToken = {
261
+ address: pool.validatorToken,
262
+ ...tokensByAddress?.get(pool.validatorToken),
263
+ };
264
+ // Each side's reserve rides on its token reference (like the
265
+ // metadata fields): absent when the `getPool` read failed.
266
+ return {
267
+ createdAt: pool.createdAt,
268
+ id,
269
+ lastMintAt: pool.lastMintAt,
270
+ mintCount: pool.mintCount,
271
+ poolId: id,
272
+ userToken: {
273
+ ...userToken,
274
+ ...reserve(reserves[index]?.userToken, userToken.decimals),
275
+ },
276
+ validatorToken: {
277
+ ...validatorToken,
278
+ ...reserve(reserves[index]?.validatorToken, validatorToken.decimals),
279
+ },
280
+ };
281
+ });
282
+ return c.json(Response.validated(schema.getFeeAmmPools.Response, {
283
+ data,
284
+ nextCursor: page.nextCursor,
285
+ }), 200);
286
+ }
287
+ catch (cause) {
288
+ return Response.upstream(c, cause);
289
+ }
290
+ })
291
+ .get('/mints', Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }), OpenApi.validate('query', schema.getFeeAmmMints.Query, {
292
+ code: 'query_invalid',
293
+ message: 'Invalid query parameters',
294
+ }), OpenApi.describeRoute({
295
+ description: 'Lists liquidity mints into Fee AMM pools.',
296
+ operationId: 'getFeeAmmMints',
297
+ responses: OpenApi.responses({
298
+ errors: { 502: 'The upstream Tempo indexer could not complete the request.' },
299
+ success: {
300
+ description: 'Page of Fee AMM liquidity mints.',
301
+ schema: schema.getFeeAmmMints.Response,
302
+ },
303
+ }),
304
+ summary: 'List mints',
305
+ tags: ['Fee AMM'],
306
+ }), Cache.response({
307
+ cacheControl: Cache.policies.feed,
308
+ name: 'tempo-api:fee-amm:v1',
309
+ key: (c) => Cache.urlKey(c, schema.getFeeAmmMints.Query),
310
+ }), async (c) => {
311
+ if (Auth.narrowAccess)
312
+ return Auth.paidAccessError(c);
313
+ if (OpenApi.narrowValidation)
314
+ return OpenApi.validationError(c, {
315
+ code: 'query_invalid',
316
+ message: 'Invalid query parameters',
317
+ });
318
+ const query = c.req.valid('query');
319
+ const chainId = query.chainId ?? c.get('chainId');
320
+ // A cursor page is anchored below the head, so its contents are
321
+ // append-only and effectively immutable; cache it aggressively. The
322
+ // head page (no cursor) keeps the route's short `feed` default.
323
+ if (query.cursor !== undefined)
324
+ Cache.setPolicy(c, Cache.policies.immutable);
325
+ try {
326
+ // `totalCount` is opt-in and shares the page's filters; run it
327
+ // concurrently with the page. Best-effort: a count failure omits
328
+ // `meta` rather than failing the page.
329
+ const countPromise = query.include.includes('totalCount')
330
+ ? countMints(c, {
331
+ chainId,
332
+ userToken: query.userToken,
333
+ validatorToken: query.validatorToken,
334
+ }).catch(() => undefined)
335
+ : undefined;
336
+ const page = await listMints(c, {
337
+ chainId,
338
+ cursor: query.cursor,
339
+ limit: query.limit,
340
+ order: query.order,
341
+ page: query.page,
342
+ userToken: query.userToken,
343
+ validatorToken: query.validatorToken,
344
+ });
345
+ // Token references take the same enriched shape as the pools
346
+ // listing. The page's unique tokens are few (often one pool), and
347
+ // each resolution is memoized, so this stays one bounded fan-out.
348
+ const tokensByAddress = await resolveTokenMap(c, {
349
+ addresses: Array.from(new Set(page.data.flatMap((mint) => [mint.userToken, mint.validatorToken]))),
350
+ chainId,
351
+ });
352
+ const data = page.data.map((mint) => ({
353
+ ...mint,
354
+ userToken: { address: mint.userToken, ...tokensByAddress.get(mint.userToken) },
355
+ validatorToken: {
356
+ address: mint.validatorToken,
357
+ ...tokensByAddress.get(mint.validatorToken),
358
+ },
359
+ }));
360
+ const meta = countPromise ? await countPromise : undefined;
361
+ return c.json(Response.validated(schema.getFeeAmmMints.Response, {
362
+ data,
363
+ ...(meta ? { meta } : {}),
364
+ nextCursor: page.nextCursor,
365
+ }), 200);
366
+ }
367
+ catch (cause) {
368
+ return Response.upstream(c, cause);
369
+ }
370
+ });
371
+ }
372
+ /**
373
+ * Discovers fee pools by aggregating the FeeManager's `Mint` logs into one
374
+ * row per `(userToken, validatorToken)` pair, paginated and ordered by mint
375
+ * count (most active first), so the real fee pools (thousands to millions of
376
+ * mints) lead the head pages and the one-shot spam pairs (mint count 1) sink
377
+ * to the tail.
378
+ *
379
+ * Keyset pagination on `(mint_count, topic2, topic3)`: `mint_count` ranks the
380
+ * page (mutable — it grows as pools mint — so this is best-effort across mint
381
+ * activity, the inherent limit of a ranking feed), and the `(topic2, topic3)`
382
+ * group key is a stable, unique tiebreaker. A malformed cursor falls back to
383
+ * the head page.
384
+ */
385
+ function listPools(c, options) {
386
+ const { chainId, limit } = options;
387
+ const store = c.get('store');
388
+ const tidx = c.get('getTidx')(chainId);
389
+ const cursor = options.cursor
390
+ ? Cursor.decode(options.cursor, ['int', 'address', 'address'])
391
+ : undefined;
392
+ // Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
393
+ // the head page, so it shares the head's SQL and cache entries; deeper pages
394
+ // translate to a SQL OFFSET of `(page - 1) * limit` rows.
395
+ const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
396
+ return Timing.time(c, 'fee_amm_pools', () => Store.memoize(async () => {
397
+ // The keyset compares the aggregate `mint_count`, so it lives in the
398
+ // outer WHERE over the GROUP BY subquery. Topics are padded 32-byte
399
+ // log topics, so render their literals via `padTopic` (lowercased to
400
+ // match ClickHouse's stored topic casing) rather than `Cursor.literal`.
401
+ const keyset = cursor !== undefined
402
+ ? ` WHERE ${Cursor.keyset([
403
+ { literal: Cursor.literal(cursor[0], 'int'), name: 'mint_count', order: 'desc' },
404
+ {
405
+ literal: `'${padTopic(cursor[1]).toLowerCase()}'`,
406
+ name: 'topic2',
407
+ order: 'desc',
408
+ },
409
+ {
410
+ literal: `'${padTopic(cursor[2]).toLowerCase()}'`,
411
+ name: 'topic3',
412
+ order: 'desc',
413
+ },
414
+ ])}`
415
+ : '';
416
+ // Raw-`logs` ClickHouse aggregate (dynamic query, hence the cast):
417
+ // ordering is by mint count so the head pages surface the real fee
418
+ // pools and the one-shot spam pairs sink to the tail. Fetch one extra
419
+ // row to detect `hasMore` without a separate count query.
420
+ const result = await tidx.fetch({
421
+ chainId,
422
+ engine: 'clickhouse',
423
+ query: `
424
+ SELECT topic2, topic3, mint_count, first_at, last_at
425
+ FROM (
426
+ SELECT topic2, topic3, count() AS mint_count, min(block_timestamp) AS first_at, max(block_timestamp) AS last_at
427
+ FROM logs
428
+ WHERE address = '${feeManager}' AND selector IN ('${mintTopic}', '${legacyMintTopic}')
429
+ GROUP BY topic2, topic3
430
+ )${keyset}
431
+ ORDER BY mint_count DESC, topic2 DESC, topic3 DESC
432
+ LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
433
+ `,
434
+ });
435
+ // The next page anchors below the last row's `(mint_count, topic2,
436
+ // topic3)`; topics use the lowercase unpadded address so they round
437
+ // trip through the keyset literals above.
438
+ const page = Cursor.paginate({
439
+ rows: result.rows,
440
+ limit,
441
+ key: (row) => {
442
+ const mintCount = Value.toNumber(row['mint_count']);
443
+ const userToken = unpadTopic(Value.toText(row['topic2']))?.toLowerCase();
444
+ const validatorToken = unpadTopic(Value.toText(row['topic3']))?.toLowerCase();
445
+ return mintCount !== undefined &&
446
+ userToken !== undefined &&
447
+ validatorToken !== undefined
448
+ ? [mintCount, userToken, validatorToken]
449
+ : undefined;
450
+ },
451
+ });
452
+ const data = [];
453
+ for (const row of page.rows) {
454
+ const userToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic2'])));
455
+ const validatorToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic3'])));
456
+ const mintCount = Value.toNumber(row['mint_count']);
457
+ const createdAt = Value.toIsoDateTime(row['first_at']);
458
+ const lastMintAt = Value.toIsoDateTime(row['last_at']);
459
+ if (!userToken.success ||
460
+ !validatorToken.success ||
461
+ mintCount === undefined ||
462
+ createdAt === undefined ||
463
+ lastMintAt === undefined)
464
+ continue;
465
+ data.push({
466
+ createdAt,
467
+ lastMintAt,
468
+ mintCount,
469
+ userToken: userToken.data,
470
+ validatorToken: validatorToken.data,
471
+ });
472
+ }
473
+ return { data, nextCursor: page.nextCursor };
474
+ }, {
475
+ key: `feeamm:v1:${chainId}:pools:${cursor ? `cursor:${cursor[0]}:${cursor[1]}:${cursor[2]}` : offset !== undefined ? `page:${options.page}` : 'head'}:${limit}`,
476
+ store,
477
+ ttl: Ttl.minutes(1),
478
+ }));
479
+ }
480
+ /**
481
+ * The set of token addresses usable to pay transaction fees on a chain: the
482
+ * curated verified tokens that have at least one Fee AMM pool (i.e. appear as a
483
+ * `userToken` / topic2 in the FeeManager's `Mint` logs), plus the protocol
484
+ * default fee token (`pathUSD`), which pays fees without needing a pool.
485
+ * Addresses are lowercased to match the indexer's stored casing.
486
+ *
487
+ * The set is intentionally gated to the verified list rather than every pooled
488
+ * token: anyone can mint a one-shot spam pool, so the raw set of pool
489
+ * `userToken`s is unbounded (tens of thousands on busy chains) and those
490
+ * liquidity-less pools can't actually settle fees. Restricting to verified
491
+ * tokens keeps the set small (a handful of USD stablecoins), bounds the scan
492
+ * (the `topic2 IN (…)` candidate list is the verified list), and matches the
493
+ * tokens fees are realistically paid in.
494
+ *
495
+ * Memoized per chain and verified-snapshot version (the only inputs), so
496
+ * callers — e.g. the address balances endpoint — can flag which holdings are
497
+ * fee-usable without a second `/fee-amm/pools` query.
498
+ */
499
+ export async function feeTokenSet(c, chainId) {
500
+ const store = c.get('store');
501
+ const tidx = c.get('getTidx')(chainId);
502
+ const snapshot = await VerifiedTokens.snapshot(c, chainId);
503
+ // pathUSD is always fee-usable; the verified tokens are the only other
504
+ // candidates we probe for a pool. With no verified candidates there is
505
+ // nothing to query — pathUSD stands alone.
506
+ const candidates = snapshot.list.map((token) => token.address.toLowerCase());
507
+ if (candidates.length === 0)
508
+ return new Set([pathUsd]);
509
+ // `memoize` JSON-encodes its value, so cache a plain `string[]` and rebuild
510
+ // the `Set` per call (cheap; the list is a handful of stablecoins).
511
+ const addresses = await Timing.time(c, 'fee_token_set', () => Store.memoize(async () => {
512
+ // Dynamic raw-`logs` query (hence the cast), mirroring the pools
513
+ // listing's FeeManager `Mint` filter but collapsing to the distinct
514
+ // user-side tokens — and scoped to the verified candidates via
515
+ // `topic2 IN (…)` so the result is bounded by the verified list.
516
+ const topics = candidates.map((address) => `'${padTopic(address)}'`);
517
+ const result = await tidx.fetch({
518
+ chainId,
519
+ engine: 'clickhouse',
520
+ query: `
521
+ SELECT DISTINCT topic2
522
+ FROM logs
523
+ WHERE address = '${feeManager}' AND selector IN ('${mintTopic}', '${legacyMintTopic}')
524
+ AND topic2 IN (${topics.join(', ')})
525
+ `,
526
+ });
527
+ const tokens = new Set([pathUsd]);
528
+ for (const row of result.rows) {
529
+ const token = unpadTopic(Value.toText(row['topic2']))?.toLowerCase();
530
+ if (token !== undefined && Schema.Address.safeParse(token).success)
531
+ tokens.add(token);
532
+ }
533
+ return Array.from(tokens);
534
+ }, {
535
+ key: `feeamm:v2:${chainId}:fee-token-set:v:${snapshot.version}`,
536
+ store,
537
+ ttl: Ttl.minutes(5),
538
+ }));
539
+ return new Set(addresses);
540
+ }
541
+ /**
542
+ * Reads a pool's current on-chain reserves via `FeeManager.getPool`, memoized
543
+ * briefly per pool. Reserves are best-effort decoration on the pool listing:
544
+ * a failing RPC read resolves to `null` (degrading that pool's reserve
545
+ * fields) rather than failing the whole response. Failures are not memoized,
546
+ * so the next request retries.
547
+ */
548
+ function getPoolReserves(c, options) {
549
+ const { chainId, userToken, validatorToken } = options;
550
+ const client = c.get('getClient')(chainId);
551
+ const store = c.get('store');
552
+ return Store.memoize(async () => {
553
+ const pool = await client.readContract({
554
+ abi: Abis.feeAmm,
555
+ address: Addresses.feeManager,
556
+ args: [userToken, validatorToken],
557
+ functionName: 'getPool',
558
+ });
559
+ return {
560
+ userToken: pool.reserveUserToken.toString(),
561
+ validatorToken: pool.reserveValidatorToken.toString(),
562
+ };
563
+ }, {
564
+ key: `feeamm:v1:${chainId}:pool:${userToken}:${validatorToken}:reserves`,
565
+ store,
566
+ ttl: Ttl.seconds(30),
567
+ }).catch(() => null);
568
+ }
569
+ /**
570
+ * Lists `Mint` logs as a keyset-paginated page on `(block_num, log_idx)`,
571
+ * optionally filtered by the pool's tokens. Runs on ClickHouse: the postgres
572
+ * `logs` table has no topic index, so topic-filtered pages exceed its
573
+ * statement timeout, while ClickHouse serves every filter shape.
574
+ */
575
+ function listMints(c, options) {
576
+ const { chainId, limit, order, userToken, validatorToken } = options;
577
+ const store = c.get('store');
578
+ const tidx = c.get('getTidx')(chainId);
579
+ const direction = order === 'asc' ? 'ASC' : 'DESC';
580
+ // Keyset pagination: anchor the page below the previous row's
581
+ // `(block, log_idx)` position instead of a numeric offset, so rows arriving
582
+ // at the head can't shift items across pages. A malformed cursor falls back
583
+ // to the head page.
584
+ const cursor = options.cursor ? Cursor.decode(options.cursor, ['int', 'int']) : undefined;
585
+ // Bounded positional lane (exclusive with `cursor` at the schema): page 1 is
586
+ // the head page, so it shares the head's SQL and cache entries; deeper pages
587
+ // translate to a SQL OFFSET of `(page - 1) * limit` rows.
588
+ const offset = options.page !== undefined && options.page > 1 ? (options.page - 1) * limit : undefined;
589
+ return Timing.time(c, 'fee_amm_mints', () => Store.memoize(async () => {
590
+ const filters = [
591
+ `address = '${feeManager}'`,
592
+ `selector IN ('${mintTopic}', '${legacyMintTopic}')`,
593
+ ];
594
+ if (userToken !== undefined)
595
+ filters.push(`topic2 = '${padTopic(userToken)}'`);
596
+ if (validatorToken !== undefined)
597
+ filters.push(`topic3 = '${padTopic(validatorToken)}'`);
598
+ if (cursor !== undefined)
599
+ filters.push(Cursor.keyset([
600
+ { literal: Cursor.literal(cursor[0], 'int'), name: 'block_num', order },
601
+ { literal: Cursor.literal(cursor[1], 'int'), name: 'log_idx', order },
602
+ ]));
603
+ // Fetch one extra row to detect `hasMore` without a separate count
604
+ // query. Raw-`logs` dynamic query, hence the cast. `block_timestamp`
605
+ // is aliased to a non-standard name on purpose: the TIDX client
606
+ // coerces standard `logs` columns, and its coercion parses
607
+ // ClickHouse's timezone-less `DateTime` text as *local* time. The
608
+ // alias passes the raw text through so `Value.toIsoDateTime` can
609
+ // apply its UTC normalization instead.
610
+ const result = await tidx.fetch({
611
+ chainId,
612
+ engine: 'clickhouse',
613
+ query: `
614
+ SELECT tx_hash, block_num, log_idx, block_timestamp AS block_time, selector, topic1, topic2, topic3, data
615
+ FROM logs
616
+ WHERE ${filters.join(' AND ')}
617
+ ORDER BY block_num ${direction}, log_idx ${direction}
618
+ LIMIT ${limit + 1}${offset !== undefined ? ` OFFSET ${offset}` : ''}
619
+ `,
620
+ });
621
+ // The next page anchors below the last fetched row's `(block, log_idx)`.
622
+ const page = Cursor.paginate({
623
+ rows: result.rows,
624
+ limit,
625
+ key: (row) => {
626
+ const block = Value.toNumber(row['block_num']);
627
+ const index = Value.toNumber(row['log_idx']);
628
+ return block !== undefined && index !== undefined ? [block, index] : undefined;
629
+ },
630
+ });
631
+ const data = [];
632
+ for (const row of page.rows) {
633
+ const mint = decodeMint(row);
634
+ if (mint)
635
+ data.push(mint);
636
+ }
637
+ return { data, nextCursor: page.nextCursor };
638
+ }, {
639
+ key: `feeamm:v1:${chainId}:mints:${order}:${userToken ?? ''}:${validatorToken ?? ''}:${cursor ? `cursor:${cursor[0]}:${cursor[1]}` : offset !== undefined ? `page:${options.page}` : 'head'}:${limit}`,
640
+ store,
641
+ ttl: Ttl.seconds(15),
642
+ }));
643
+ }
644
+ /**
645
+ * Exact total count of pool mints matching the same filters as the page. Runs
646
+ * on ClickHouse over `logs`, pruning on the FeeManager `address` (the page's
647
+ * own engine/key). Because `address` aligns with the `logs` sort key,
648
+ * ClickHouse counts via the sparse primary index without a row scan, so the
649
+ * count is exact and cheap with no cap (`totalCountCapped` is always `false`) —
650
+ * verified at ~20M matching mints in ~1s.
651
+ */
652
+ function countMints(c, options) {
653
+ const { chainId, userToken, validatorToken } = options;
654
+ const store = c.get('store');
655
+ const tidx = c.get('getTidx')(chainId);
656
+ return Timing.time(c, 'fee_amm_mints_count', () => Store.memoize(async () => {
657
+ const filters = [
658
+ `address = '${feeManager}'`,
659
+ `selector IN ('${mintTopic}', '${legacyMintTopic}')`,
660
+ ];
661
+ if (userToken !== undefined)
662
+ filters.push(`topic2 = '${padTopic(userToken)}'`);
663
+ if (validatorToken !== undefined)
664
+ filters.push(`topic3 = '${padTopic(validatorToken)}'`);
665
+ const result = await tidx.fetch({
666
+ chainId,
667
+ engine: 'clickhouse',
668
+ query: `SELECT count(*) AS total FROM logs WHERE ${filters.join(' AND ')}`,
669
+ });
670
+ const totalCount = Value.toNumber(result.rows[0]?.['total']) ?? 0;
671
+ return { totalCountCapped: false, totalCount };
672
+ }, {
673
+ key: `feeamm:v1:${chainId}:mints-count:${userToken ?? ''}:${validatorToken ?? ''}`,
674
+ store,
675
+ ttl: Ttl.seconds(15),
676
+ }));
677
+ }
678
+ /**
679
+ * Decodes one raw `Mint` log row, dispatching on its selector (current vs
680
+ * legacy signature — see the topic constants for the field layouts). Returns
681
+ * undefined for rows that fail to decode so callers can skip them.
682
+ */
683
+ function decodeMint(row) {
684
+ const selector = Value.toText(row['selector']);
685
+ const transactionHash = Schema.Hash.safeParse(row['tx_hash']);
686
+ const blockNumber = Value.toNumber(row['block_num']);
687
+ const logIndex = Value.toNumber(row['log_idx']);
688
+ const timestamp = Value.toIsoDateTime(row['block_time']);
689
+ const userToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic2'])));
690
+ const validatorToken = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic3'])));
691
+ const data = Value.toText(row['data']);
692
+ if (!transactionHash.success ||
693
+ blockNumber === undefined ||
694
+ logIndex === undefined ||
695
+ timestamp === undefined ||
696
+ !userToken.success ||
697
+ !validatorToken.success ||
698
+ data === undefined)
699
+ return undefined;
700
+ const word = (index) => data.slice(2 + 64 * index, 2 + 64 * (index + 1));
701
+ const uint = (value) => /^[0-9a-fA-F]{64}$/.test(value) ? BigInt(`0x${value}`).toString() : undefined;
702
+ if (selector === mintTopic) {
703
+ const minter = Schema.Address.safeParse(`0x${word(0).slice(24)}`);
704
+ const recipient = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic1'])));
705
+ const amountValidatorToken = uint(word(1));
706
+ const liquidity = uint(word(2));
707
+ if (!minter.success ||
708
+ !recipient.success ||
709
+ amountValidatorToken === undefined ||
710
+ liquidity === undefined)
711
+ return undefined;
712
+ return {
713
+ amountValidatorToken,
714
+ blockNumber,
715
+ id: `${transactionHash.data}-${logIndex}`,
716
+ liquidity,
717
+ logIndex,
718
+ minter: minter.data,
719
+ recipient: recipient.data,
720
+ timestamp,
721
+ transactionHash: transactionHash.data,
722
+ userToken: userToken.data,
723
+ validatorToken: validatorToken.data,
724
+ };
725
+ }
726
+ if (selector === legacyMintTopic) {
727
+ const minter = Schema.Address.safeParse(unpadTopic(Value.toText(row['topic1'])));
728
+ const amountUserToken = uint(word(0));
729
+ const amountValidatorToken = uint(word(1));
730
+ const liquidity = uint(word(2));
731
+ if (!minter.success ||
732
+ amountUserToken === undefined ||
733
+ amountValidatorToken === undefined ||
734
+ liquidity === undefined)
735
+ return undefined;
736
+ return {
737
+ amountUserToken,
738
+ amountValidatorToken,
739
+ blockNumber,
740
+ id: `${transactionHash.data}-${logIndex}`,
741
+ liquidity,
742
+ logIndex,
743
+ minter: minter.data,
744
+ timestamp,
745
+ transactionHash: transactionHash.data,
746
+ userToken: userToken.data,
747
+ validatorToken: validatorToken.data,
748
+ };
749
+ }
750
+ return undefined;
751
+ }
752
+ /**
753
+ * Resolves best-effort metadata for the given token addresses, sharing one
754
+ * verified-token snapshot across the lookups. Mirrors the transfer feed's
755
+ * `sourceToken` enrichment: a token whose metadata is unavailable maps to
756
+ * undefined, so call sites spreading the result keep just the bare `address`
757
+ * (the `PoolToken` shape).
758
+ *
759
+ * Unlike the full token resource, this resolves only the on-chain metadata
760
+ * (one `token.getMetadata` read per token, auto-batched into a single
761
+ * multicall) plus the in-memory verified snapshot. It deliberately skips the
762
+ * curated R2 logo lookup (`getTokenLogo`), which is an un-batched storage read
763
+ * per token and the dominant cost of the pools/mints listings; `logoUri`
764
+ * therefore falls back to the verified entry's curated URL or the precompile's
765
+ * on-chain `logoURI`, both already in hand.
766
+ */
767
+ async function resolveTokenMap(c, options) {
768
+ const { addresses, chainId } = options;
769
+ const snapshot = await VerifiedTokens.snapshot(c, chainId);
770
+ const resolved = await Timing.time(c, 'tokens', () => Promise.all(addresses.map((token) => Tokens.getTokenMetadata(c, { address: token, chainId })
771
+ .then((metadata) => {
772
+ const verified = snapshot.byAddress.get(token);
773
+ return [
774
+ token,
775
+ {
776
+ currency: metadata.currency,
777
+ decimals: metadata.decimals,
778
+ logoUri: verified?.logoUri ?? metadata.logoUri,
779
+ name: metadata.name,
780
+ symbol: metadata.symbol,
781
+ verified: snapshot.byAddress.has(token),
782
+ },
783
+ ];
784
+ })
785
+ .catch(() => [token, undefined]))));
786
+ return new Map(resolved);
787
+ }
788
+ /**
789
+ * Computes a pool id locally: `keccak256(abi.encode(userToken,
790
+ * validatorToken))`, matching the on-chain `FeeManager.getPoolId`.
791
+ */
792
+ function poolId(userToken, validatorToken) {
793
+ return Hash.keccak256(AbiParameters.encode(AbiParameters.from(['address', 'address']), [userToken, validatorToken]));
794
+ }
795
+ /**
796
+ * Shapes one side's on-chain reserve for spreading into its token reference:
797
+ * the base-unit `amount` plus its `formatted` decimal form. `decimals` comes
798
+ * from the side's resolved token metadata; an unresolvable token falls back
799
+ * to 6, the fixed TIP-20 precision, so the reserve still formats. A missing
800
+ * amount (failed `getPool` read) yields undefined, leaving both fields absent.
801
+ */
802
+ function reserve(amount, decimals) {
803
+ if (amount === undefined)
804
+ return undefined;
805
+ return { amount, formatted: core_Value.format(BigInt(amount), decimals ?? 6) };
806
+ }
807
+ /** Left-pads a 20-byte address to its 32-byte log-topic form. */
808
+ function padTopic(address) {
809
+ return `0x${'0'.repeat(24)}${address.slice(2)}`;
810
+ }
811
+ /** Unpads a 32-byte log topic to its trailing 20-byte address. */
812
+ function unpadTopic(topic) {
813
+ return topic === undefined ? undefined : `0x${topic.slice(26)}`;
814
+ }
815
+ //# sourceMappingURL=fee-amm.js.map