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,1020 @@
1
+ import { Address, Hex } from 'ox'
2
+ import { encodeAbiParameters, encodeEventTopics } from 'viem'
3
+ import { Abis } from 'viem/tempo'
4
+
5
+ import * as TestApp from '../../test/App.js'
6
+ import * as Runtime from '../../test/runtime.js'
7
+ import * as Store from '../internal/Store.js'
8
+ import * as Activities from './activities.js'
9
+
10
+ /**
11
+ * Real Tempo (Moderato) accounts, each exercising specific activity types.
12
+ * Discovered by scanning indexed `logs` for the event each type classifies
13
+ * from, then confirming the classified feed through the live endpoint. If the
14
+ * testnet prunes this history, rediscover replacements the same way.
15
+ */
16
+ const liveAccounts = {
17
+ // Payment-channel close (session-closed), folded into access-key groups.
18
+ channel: '0x5217c502adf3c96a9551770af4caa5f5144a6984',
19
+ // TokenCreated + Mint + Transfer.
20
+ creator: '0x253fb3bf1e3d75ad6ee1554f68f4ea0568f7d24e',
21
+ // Token burns.
22
+ burner: '0x69519497b59bd64e109d993157d45f14455cc7e6',
23
+ // KeyAuthorized + KeyRevoked + Approval (+ transfer/mint/exchange).
24
+ keychain: '0xfe776fd28e06ecc71a157398b2c9991318f1ea2e',
25
+ // Swaps through the stablecoin DEX (output kept, so it stays a plain `swap`).
26
+ swapper: '0x7f4e7fdd75e4c5beb77d0eb223823d3a5a9865c0',
27
+ } as const
28
+
29
+ const runtime = Runtime.get()
30
+ const localAccounts = runtime.fixtures?.activityAccounts
31
+ const accounts =
32
+ runtime.mode === 'localnet' && localAccounts
33
+ ? {
34
+ channel: localAccounts.keychain,
35
+ creator: localAccounts.creator,
36
+ burner: localAccounts.burner,
37
+ keychain: localAccounts.keychain,
38
+ swapper: localAccounts.swapper,
39
+ }
40
+ : liveAccounts
41
+
42
+ /** The account expected to surface each activity type. */
43
+ const coverage =
44
+ runtime.mode === 'localnet'
45
+ ? {
46
+ approval: accounts.keychain,
47
+ burn: accounts.burner,
48
+ mint: accounts.creator,
49
+ 'token-created': accounts.creator,
50
+ transfer: accounts.creator,
51
+ }
52
+ : {
53
+ 'access-key-created': accounts.keychain,
54
+ 'access-key-revoked': accounts.keychain,
55
+ approval: accounts.keychain,
56
+ burn: accounts.burner,
57
+ mint: accounts.creator,
58
+ 'session-closed': accounts.channel,
59
+ swap: accounts.swapper,
60
+ 'token-created': accounts.creator,
61
+ transfer: accounts.creator,
62
+ }
63
+
64
+ const client = TestApp.client()
65
+
66
+ /**
67
+ * Requests an address's activity page, retrying transient upstream `502`s
68
+ * (TIDX surfaces a busy ClickHouse planner as a `422` the route maps to `502`).
69
+ */
70
+ async function request(address: string, query: Record<string, string> = {}) {
71
+ let response = await activitiesRequest(address, query)
72
+ for (let attempt = 0; attempt < 2 && response.status !== 200; attempt++)
73
+ response = await activitiesRequest(address, query)
74
+ return response
75
+ }
76
+
77
+ function activitiesRequest(address: string, query: Record<string, string>) {
78
+ return client.v1.addresses[':address'].activities.$get(
79
+ { param: { address: address as Hex.Hex }, query: { limit: '50', ...query } },
80
+ TestApp.auth,
81
+ )
82
+ }
83
+
84
+ /** Requests and schema-validates an address's activity page. */
85
+ async function activities(address: string, query: Record<string, string> = {}) {
86
+ return TestApp.json(
87
+ await request(address, query),
88
+ Activities.schema.getAddressActivities.Response,
89
+ )
90
+ }
91
+
92
+ /** Finds an entry (or grouped item) of the given type, searching inside groups. */
93
+ function find(items: readonly Activities.Entry[], type: string): Activities.Entry | undefined {
94
+ for (const entry of items) {
95
+ if (entry.type === type) return entry
96
+ if (entry.type === 'group') {
97
+ const item = entry.data.items.find((i) => i.type === type)
98
+ if (item) return item
99
+ }
100
+ }
101
+ return undefined
102
+ }
103
+
104
+ describe('GET /addresses/:address/activities', () => {
105
+ test('returns a newest-first, schema-valid feed with timing', async () => {
106
+ const response = await request(accounts.keychain, { limit: '10' })
107
+ expect(response.status).toBe(200)
108
+ expect(response.headers.get('server-timing')?.includes('address_activities;dur=')).toBe(true)
109
+
110
+ const body = await TestApp.json(response, Activities.schema.getAddressActivities.Response)
111
+ expect(Array.isArray(body.data)).toBe(true)
112
+ expect(body.nextCursor === null || typeof body.nextCursor === 'string').toBe(true)
113
+
114
+ // Entries are ordered newest-first by timestamp (ISO 8601 sorts
115
+ // lexicographically the same as chronologically).
116
+ const timestamps = body.data.map((entry) => entry.timestamp)
117
+ expect(timestamps.every((ts, i) => i === 0 || timestamps[i - 1]! >= ts)).toBe(true)
118
+ })
119
+
120
+ test.each(Object.entries(coverage))('classifies %s activity', async (type, address) => {
121
+ const body = await activities(address)
122
+ expect(find(body.data, type)).toBeDefined()
123
+ })
124
+
125
+ test('transfer data mirrors the `Transfer` resource', async () => {
126
+ const transfer = find((await activities(accounts.creator)).data, 'transfer')
127
+ expect(transfer?.type).toBe('transfer')
128
+ if (transfer?.type !== 'transfer') return
129
+ expect(['in', 'out']).toContain(transfer.data.direction)
130
+ expect(transfer.data.transactionHash).toMatch(/^0x[0-9a-f]{64}$/)
131
+ expect(typeof transfer.data.blockNumber).toBe('number')
132
+ expect(transfer.data.sourceToken.address).toMatch(/^0x[0-9a-f]{40}$/)
133
+ expect(transfer.data.sourceToken.amount).toMatch(/^\d+$/)
134
+ })
135
+
136
+ test.skipIf(runtime.mode === 'localnet')('swap data carries both sides of the swap', async () => {
137
+ const swap = find((await activities(accounts.swapper)).data, 'swap')
138
+ expect(swap?.type).toBe('swap')
139
+ if (swap?.type !== 'swap') return
140
+ expect(swap.data.sourceToken.address).toMatch(/^0x[0-9a-f]{40}$/)
141
+ expect(swap.data.destinationToken.address).toMatch(/^0x[0-9a-f]{40}$/)
142
+ })
143
+
144
+ test.skipIf(runtime.mode !== 'localnet')(
145
+ 'surfaces a cross-token transfer as outgoing in the sender feed',
146
+ async () => {
147
+ const crossToken = runtime.fixtures?.crossTokenTransfer
148
+ expect(crossToken).toBeDefined()
149
+ if (!crossToken) return
150
+
151
+ const { data } = await activities(crossToken.sender)
152
+ const transfer = data.find(
153
+ (entry) =>
154
+ entry.type === 'transfer' &&
155
+ entry.transactionHash === crossToken.hash &&
156
+ entry.data.destinationToken?.address === crossToken.destinationToken,
157
+ )
158
+ expect(transfer?.type).toBe('transfer')
159
+ if (transfer?.type !== 'transfer') return
160
+ expect(transfer.data.direction).toBe('out')
161
+ expect(transfer.data.sender).toBe(crossToken.sender)
162
+ expect(transfer.data.recipient).toBe(crossToken.recipient)
163
+ expect(transfer.data.sourceToken.address).toBe(crossToken.sourceToken)
164
+ expect(transfer.data.destinationToken?.address).toBe(crossToken.destinationToken)
165
+ },
166
+ )
167
+
168
+ test.skipIf(runtime.mode !== 'localnet')(
169
+ 'surfaces a cross-token transfer as a plain incoming delivery in the recipient feed',
170
+ async () => {
171
+ const crossToken = runtime.fixtures?.crossTokenTransfer
172
+ expect(crossToken).toBeDefined()
173
+ if (!crossToken) return
174
+
175
+ // The recipient feed is built only from the inbound delivery leg (the
176
+ // sender's `from`/`fee_payer` rows, which carry the swap, are out of
177
+ // scope for the recipient). So the recipient sees an ordinary incoming
178
+ // transfer of the token they received, with no cross-token framing: the
179
+ // delivered token is the `sourceToken` and there is no `destinationToken`.
180
+ const { data } = await activities(crossToken.recipient)
181
+ const transfer = data.find(
182
+ (entry) => entry.type === 'transfer' && entry.transactionHash === crossToken.hash,
183
+ )
184
+ expect(transfer?.type).toBe('transfer')
185
+ if (transfer?.type !== 'transfer') return
186
+ expect(transfer.data.direction).toBe('in')
187
+ expect(transfer.data.sender).toBe(crossToken.sender)
188
+ expect(transfer.data.recipient).toBe(crossToken.recipient)
189
+ expect(transfer.data.sourceToken.address).toBe(crossToken.destinationToken)
190
+ expect(transfer.data.destinationToken).toBeUndefined()
191
+ },
192
+ )
193
+
194
+ test.skipIf(runtime.mode === 'localnet')(
195
+ 'folds consecutive access-key items into a group when group=true',
196
+ async () => {
197
+ const group = find((await activities(accounts.channel, { group: 'true' })).data, 'group')
198
+ expect(group?.type).toBe('group')
199
+ if (group?.type !== 'group') return
200
+ expect(group.data.items.length).toBeGreaterThan(1)
201
+ expect(group.data.signer).toMatch(/^0x[0-9a-f]{40}$/)
202
+ },
203
+ )
204
+
205
+ test('returns every item at the root by default (no group entries)', async () => {
206
+ const body = await activities(accounts.channel)
207
+ expect(body.data.every((entry) => entry.type !== 'group')).toBe(true)
208
+ })
209
+
210
+ test('paginates with an opaque cursor', async () => {
211
+ const first = await activities(accounts.keychain, { limit: '5' })
212
+ if (!first.nextCursor) return
213
+ const second = await activities(accounts.keychain, { limit: '5', cursor: first.nextCursor })
214
+ expect(Array.isArray(second.data)).toBe(true)
215
+ })
216
+ })
217
+
218
+ /** Resolves a real transaction hash from an address's (non-grouped) activity feed. */
219
+ async function firstTransactionHash(address: string): Promise<Hex.Hex | undefined> {
220
+ const { data } = await activities(address)
221
+ for (const entry of data) if (entry.type !== 'group') return entry.transactionHash
222
+ return undefined
223
+ }
224
+
225
+ function transactionActivitiesRequest(transactionHash: string, query: Record<string, string> = {}) {
226
+ return client.v1.transactions[':transactionHash'].activities.$get(
227
+ { param: { transactionHash: transactionHash as Hex.Hex }, query },
228
+ TestApp.auth,
229
+ )
230
+ }
231
+
232
+ describe('GET /transactions/:transactionHash/activities', () => {
233
+ test('classifies the activity on a real transaction', async () => {
234
+ // Resolve a real transaction hash from the address feed, then classify it
235
+ // through the transaction-scoped endpoint.
236
+ const transactionHash = await firstTransactionHash(accounts.creator)
237
+ expect(transactionHash).toBeDefined()
238
+ if (!transactionHash) return
239
+
240
+ const response = await transactionActivitiesRequest(transactionHash)
241
+ expect(response.status).toBe(200)
242
+ expect(response.headers.get('server-timing')?.includes('transaction_activities;dur=')).toBe(
243
+ true,
244
+ )
245
+
246
+ const body = await TestApp.json(response, Activities.schema.getTransactionActivities.Response)
247
+ expect(body.data.length).toBeGreaterThan(0)
248
+ // Every classified item belongs to the requested transaction.
249
+ expect(body.data.every((item) => item.transactionHash === transactionHash)).toBe(true)
250
+ })
251
+
252
+ test('attaches the full event set when logs=true', async () => {
253
+ const transactionHash = await firstTransactionHash(accounts.creator)
254
+ if (!transactionHash) return
255
+
256
+ const body = await TestApp.json(
257
+ await transactionActivitiesRequest(transactionHash, { logs: 'true' }),
258
+ Activities.schema.getTransactionActivities.Response,
259
+ )
260
+ expect(body.data.some((item) => Array.isArray(item.events) && item.events.length > 0)).toBe(
261
+ true,
262
+ )
263
+ })
264
+
265
+ test.skipIf(runtime.mode !== 'localnet')(
266
+ 'classifies a seeded cross-token transfer with both source and destination tokens',
267
+ async () => {
268
+ const crossToken = runtime.fixtures?.crossTokenTransfer
269
+ expect(crossToken).toBeDefined()
270
+ if (!crossToken) return
271
+
272
+ const body = await TestApp.json(
273
+ await transactionActivitiesRequest(crossToken.hash),
274
+ Activities.schema.getTransactionActivities.Response,
275
+ )
276
+ // The swap and its onward delivery leg fold into a single cross-token
277
+ // `transfer`; there is no separate `swap` item.
278
+ expect(body.data.some((item) => item.type === 'swap')).toBe(false)
279
+ const transfer = body.data.find((item) => item.type === 'transfer')
280
+ expect(transfer?.type).toBe('transfer')
281
+ if (transfer?.type !== 'transfer') return
282
+ expect(transfer.data.direction).toBe('out')
283
+ expect(transfer.data.recipient).toBe(crossToken.recipient)
284
+ expect(transfer.data.sourceToken.address).toBe(crossToken.sourceToken)
285
+ expect(transfer.data.destinationToken?.address).toBe(crossToken.destinationToken)
286
+ },
287
+ )
288
+
289
+ test('404s for an unknown transaction hash', async () => {
290
+ const response = await transactionActivitiesRequest(`0x${'0'.repeat(64)}`)
291
+ expect(response.status).toBe(404)
292
+ })
293
+ })
294
+
295
+ describe('Activities.list classification', () => {
296
+ const viewer = '0x1111111111111111111111111111111111111111' as Address.Address
297
+
298
+ /** A memory store pre-seeded so the MPP directory fetch never hits the network. */
299
+ function store() {
300
+ const store = Store.memory()
301
+ void store.put('mpp-services:v1', JSON.stringify({}))
302
+ return store
303
+ }
304
+
305
+ /** A {@link Activities.Provider} that returns a fixed set of rows once. */
306
+ function provider(rows: Activities.Row[]): Activities.Provider {
307
+ return {
308
+ cursorForGroup: (group) => `${group.blockNum}:${group.txIdx}`,
309
+ fetchRows: async () => ({ rows }),
310
+ }
311
+ }
312
+
313
+ test('classifies an unrecognized Q1 tx as `unknown` with decoded events', async () => {
314
+ // A gas-fee Transfer to the fee manager decodes as a known `Transfer`
315
+ // event, but the classifier excludes fee-manager transfers, so the tx has
316
+ // no classifiable activity and falls through to `unknown`.
317
+ const token = '0x2222222222222222222222222222222222222222' as Address.Address
318
+ const feeManager = '0xfeec000000000000000000000000000000000000'
319
+ const transferSelector = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
320
+ const pad = (a: string) => `0x${a.slice(2).padStart(64, '0')}`
321
+ const { items } = await Activities.list(
322
+ provider([
323
+ {
324
+ blockNum: 100,
325
+ blockTimestamp: 1_700_000_000,
326
+ data: `0x${(5).toString(16).padStart(64, '0')}`,
327
+ feePayer: null,
328
+ logAddress: token,
329
+ logIdx: 0,
330
+ selector: transferSelector,
331
+ source: 'q1',
332
+ topic1: pad(viewer),
333
+ topic2: pad(feeManager),
334
+ topic3: null,
335
+ txHash: `0x${'a'.repeat(64)}` as Hex.Hex,
336
+ txIdx: 0,
337
+ txSender: viewer,
338
+ },
339
+ ]),
340
+ { address: viewer, store: store() },
341
+ )
342
+
343
+ expect(items).toHaveLength(1)
344
+ const item = items[0]!
345
+ expect(item.type).toBe('unknown')
346
+ if (item.type !== 'unknown') return
347
+ expect(item.data.signer).toBe('self')
348
+ expect(item.transactionHash).toBe(`0x${'a'.repeat(64)}`)
349
+ expect(item.events).toHaveLength(1)
350
+ const event = item.events![0]!
351
+ expect(event.eventName).toBe('Transfer')
352
+ expect(event.args!['amount']).toBe('5')
353
+ // Addresses are normalized to lowercase (viem decodes them checksummed).
354
+ expect(event.args!['from']).toBe(viewer)
355
+ expect(event.args!['to']).toBe(feeManager)
356
+ })
357
+
358
+ test('captures undecoded logs on an `unknown` tx so no log is dropped', async () => {
359
+ // A log whose selector is not in any known Tempo ABI cannot decode, so it
360
+ // would be invisible without the raw `logs` capture.
361
+ const contract = '0x3333333333333333333333333333333333333333' as Address.Address
362
+ const unknownSelector = `0x${'b'.repeat(64)}`
363
+ const pad = (a: string) => `0x${a.slice(2).padStart(64, '0')}`
364
+ const { items } = await Activities.list(
365
+ provider([
366
+ {
367
+ blockNum: 100,
368
+ blockTimestamp: 1_700_000_000,
369
+ data: '0x1234',
370
+ feePayer: null,
371
+ logAddress: contract,
372
+ logIdx: 2,
373
+ selector: unknownSelector,
374
+ source: 'q1',
375
+ topic1: pad(viewer),
376
+ topic2: null,
377
+ topic3: null,
378
+ txHash: `0x${'c'.repeat(64)}` as Hex.Hex,
379
+ txIdx: 0,
380
+ txSender: viewer,
381
+ },
382
+ ]),
383
+ { address: viewer, store: store() },
384
+ )
385
+
386
+ expect(items).toHaveLength(1)
387
+ const item = items[0]!
388
+ expect(item.type).toBe('unknown')
389
+ if (item.type !== 'unknown') return
390
+ // The undecoded log is surfaced as a raw event entry (no `eventName`/`args`).
391
+ expect(item.events).toHaveLength(1)
392
+ const log = item.events![0]!
393
+ expect(log.eventName).toBeUndefined()
394
+ expect(log.args).toBeUndefined()
395
+ expect(log.address).toBe(contract)
396
+ expect(log.logIndex).toBe(2)
397
+ expect(log.data).toBe('0x1234')
398
+ expect(log.topics).toEqual([unknownSelector, pad(viewer)])
399
+ })
400
+ })
401
+
402
+ describe('Activities.list precompile classification', () => {
403
+ const viewer = '0x1111111111111111111111111111111111111111' as Address.Address
404
+ const tokenA = '0x2222222222222222222222222222222222222222' as Address.Address
405
+ const stablecoinDex = '0xdec0000000000000000000000000000000000000' as Address.Address
406
+ const feeManager = '0xfeec000000000000000000000000000000000000' as Address.Address
407
+ const accountKeychain = '0xaaaaaaaa00000000000000000000000000000000' as Address.Address
408
+ const channelReserve = '0x4d50500000000000000000000000000000000000' as Address.Address
409
+ const tip20Factory = '0x20fc000000000000000000000000000000000000' as Address.Address
410
+ const txHash = `0x${'b'.repeat(64)}` as Hex.Hex
411
+
412
+ function store() {
413
+ const store = Store.memory()
414
+ void store.put('mpp-services:v1', JSON.stringify({}))
415
+ return store
416
+ }
417
+
418
+ function provider(rows: Activities.Row[]): Activities.Provider {
419
+ return {
420
+ cursorForGroup: (group) => `${group.blockNum}:${group.txIdx}`,
421
+ fetchRows: async () => ({ rows }),
422
+ }
423
+ }
424
+
425
+ /** Builds a Q1 log row for `eventName` by ABI-encoding its indexed topics and data. */
426
+ function logRow(options: {
427
+ address: Address.Address
428
+ args: Record<string, unknown>
429
+ eventName: string
430
+ logIdx: number
431
+ }): Activities.Row {
432
+ type Param = { indexed?: boolean; name?: string; type: string }
433
+ const event = Abis.abis.find((a) => a.type === 'event' && a.name === options.eventName) as {
434
+ inputs: readonly Param[]
435
+ }
436
+ const topics = encodeEventTopics({
437
+ abi: Abis.abis,
438
+ eventName: options.eventName as never,
439
+ args: options.args as never,
440
+ }) as (string | null)[]
441
+ const dataInputs = event.inputs.filter((i) => !i.indexed)
442
+ const data = encodeAbiParameters(
443
+ dataInputs,
444
+ dataInputs.map((i) => options.args[i.name!]),
445
+ )
446
+ return {
447
+ blockNum: 100,
448
+ blockTimestamp: 1_700_000_000,
449
+ data,
450
+ feePayer: null,
451
+ logAddress: options.address,
452
+ logIdx: options.logIdx,
453
+ selector: topics[0] ?? null,
454
+ source: 'q1',
455
+ topic1: topics[1] ?? null,
456
+ topic2: topics[2] ?? null,
457
+ topic3: topics[3] ?? null,
458
+ txHash,
459
+ txIdx: 0,
460
+ txSender: viewer,
461
+ }
462
+ }
463
+
464
+ /** A Q1 TIP-20 `Transfer` log row, used to back channel token recovery. */
465
+ function transferRow(options: {
466
+ from: Address.Address
467
+ logIdx: number
468
+ to: Address.Address
469
+ token: Address.Address
470
+ value: bigint
471
+ }): Activities.Row {
472
+ return logRow({
473
+ address: options.token,
474
+ args: { amount: options.value, from: options.from, to: options.to },
475
+ eventName: 'Transfer',
476
+ logIdx: options.logIdx,
477
+ })
478
+ }
479
+
480
+ async function classify(rows: Activities.Row[]) {
481
+ const { items } = await Activities.list(provider(rows), { address: viewer, store: store() })
482
+ return items
483
+ }
484
+
485
+ test('classifies ChannelOpened as `channel-opened` with the deposit token', async () => {
486
+ const items = await classify([
487
+ logRow({
488
+ address: channelReserve,
489
+ args: {
490
+ authorizedSigner: viewer,
491
+ channelId: `0x${'c'.repeat(64)}`,
492
+ deposit: 1000n,
493
+ expiringNonceHash: `0x${'0'.repeat(64)}`,
494
+ operator: viewer,
495
+ payee: tokenA,
496
+ payer: viewer,
497
+ salt: `0x${'0'.repeat(64)}`,
498
+ token: tokenA,
499
+ },
500
+ eventName: 'ChannelOpened',
501
+ logIdx: 0,
502
+ }),
503
+ ])
504
+ expect(items).toHaveLength(1)
505
+ expect(items[0]!.type).toBe('channel-opened')
506
+ if (items[0]!.type !== 'channel-opened') return
507
+ expect(items[0]!.data.sourceToken.amount).toBe('1000')
508
+ expect(items[0]!.data.payer).toBe(viewer)
509
+ })
510
+
511
+ test('classifies TopUp as `channel-funded`, recovering token from the paired transfer', async () => {
512
+ const items = await classify([
513
+ transferRow({ from: viewer, logIdx: 0, to: channelReserve, token: tokenA, value: 500n }),
514
+ logRow({
515
+ address: channelReserve,
516
+ args: {
517
+ additionalDeposit: 500n,
518
+ channelId: `0x${'c'.repeat(64)}`,
519
+ newDeposit: 1500n,
520
+ payee: tokenA,
521
+ payer: viewer,
522
+ },
523
+ eventName: 'TopUp',
524
+ logIdx: 1,
525
+ }),
526
+ ])
527
+ expect(items).toHaveLength(1)
528
+ expect(items[0]!.type).toBe('channel-funded')
529
+ if (items[0]!.type !== 'channel-funded') return
530
+ expect(items[0]!.data.sourceToken.address).toBe(tokenA)
531
+ expect(items[0]!.data.sourceToken.amount).toBe('500')
532
+ })
533
+
534
+ test('classifies Settled as `channel-settled`', async () => {
535
+ const payee = '0x3333333333333333333333333333333333333333' as Address.Address
536
+ const items = await classify([
537
+ transferRow({ from: channelReserve, logIdx: 0, to: payee, token: tokenA, value: 250n }),
538
+ logRow({
539
+ address: channelReserve,
540
+ args: {
541
+ channelId: `0x${'c'.repeat(64)}`,
542
+ cumulativeAmount: 250n,
543
+ deltaPaid: 250n,
544
+ newSettled: 250n,
545
+ payee,
546
+ payer: viewer,
547
+ },
548
+ eventName: 'Settled',
549
+ logIdx: 1,
550
+ }),
551
+ ])
552
+ expect(items[0]!.type).toBe('channel-settled')
553
+ if (items[0]!.type !== 'channel-settled') return
554
+ expect(items[0]!.data.sourceToken.amount).toBe('250')
555
+ expect(items[0]!.data.payee).toBe(payee)
556
+ })
557
+
558
+ test('classifies ChannelClosed as `channel-closed` with payout and refund', async () => {
559
+ const payee = '0x3333333333333333333333333333333333333333' as Address.Address
560
+ const items = await classify([
561
+ transferRow({ from: channelReserve, logIdx: 0, to: payee, token: tokenA, value: 600n }),
562
+ transferRow({ from: channelReserve, logIdx: 1, to: viewer, token: tokenA, value: 400n }),
563
+ logRow({
564
+ address: channelReserve,
565
+ args: {
566
+ channelId: `0x${'c'.repeat(64)}`,
567
+ payee,
568
+ payer: viewer,
569
+ refundedToPayer: 400n,
570
+ settledToPayee: 600n,
571
+ },
572
+ eventName: 'ChannelClosed',
573
+ logIdx: 2,
574
+ }),
575
+ ])
576
+ expect(items[0]!.type).toBe('channel-closed')
577
+ if (items[0]!.type !== 'channel-closed') return
578
+ expect(items[0]!.data.sourceToken?.amount).toBe('600')
579
+ expect(items[0]!.data.refund?.amount).toBe('400')
580
+ })
581
+
582
+ test('classifies CloseRequestCancelled as `channel-close-cancelled`', async () => {
583
+ const payee = '0x3333333333333333333333333333333333333333' as Address.Address
584
+ const items = await classify([
585
+ logRow({
586
+ address: channelReserve,
587
+ args: { channelId: `0x${'c'.repeat(64)}`, payee, payer: viewer },
588
+ eventName: 'CloseRequestCancelled',
589
+ logIdx: 0,
590
+ }),
591
+ ])
592
+ expect(items[0]!.type).toBe('channel-close-cancelled')
593
+ if (items[0]!.type !== 'channel-close-cancelled') return
594
+ expect(items[0]!.data.channelId).toBe(`0x${'c'.repeat(64)}`)
595
+ expect(items[0]!.data.payee).toBe(payee)
596
+ expect(items[0]!.data.payer).toBe(viewer)
597
+ })
598
+
599
+ test('classifies OrderPlaced as `order-placed`', async () => {
600
+ const items = await classify([
601
+ transferRow({ from: viewer, logIdx: 0, to: stablecoinDex, token: tokenA, value: 1000n }),
602
+ logRow({
603
+ address: stablecoinDex,
604
+ args: {
605
+ amount: 1000n,
606
+ flipTick: 0,
607
+ isBid: true,
608
+ isFlipOrder: false,
609
+ maker: viewer,
610
+ orderId: 42n,
611
+ tick: 5,
612
+ token: tokenA,
613
+ },
614
+ eventName: 'OrderPlaced',
615
+ logIdx: 1,
616
+ }),
617
+ ])
618
+ expect(items[0]!.type).toBe('order-placed')
619
+ if (items[0]!.type !== 'order-placed') return
620
+ expect(items[0]!.data.orderId).toBe('42')
621
+ expect(items[0]!.data.side).toBe('bid')
622
+ expect(items[0]!.data.tick).toBe(5)
623
+ })
624
+
625
+ test('folds a swap plus onward delivery into one cross-token `transfer`', async () => {
626
+ const tokenB = '0x5555555555555555555555555555555555555555' as Address.Address
627
+ const recipient = '0x7777777777777777777777777777777777777777' as Address.Address
628
+ const items = await classify([
629
+ // Source token spent into the DEX.
630
+ transferRow({ from: viewer, logIdx: 0, to: stablecoinDex, token: tokenA, value: 1_000n }),
631
+ // Destination token received back from the DEX.
632
+ transferRow({ from: stablecoinDex, logIdx: 1, to: viewer, token: tokenB, value: 990n }),
633
+ // Destination token forwarded to a third-party recipient in the same tx.
634
+ transferRow({ from: viewer, logIdx: 2, to: recipient, token: tokenB, value: 990n }),
635
+ ])
636
+ // The delivery leg folds into the swap, so there is no separate `swap`.
637
+ expect(items.some((item) => item.type === 'swap')).toBe(false)
638
+ const transfer = items.find((item) => item.type === 'transfer')
639
+ expect(transfer?.type).toBe('transfer')
640
+ if (transfer?.type !== 'transfer') return
641
+ expect(transfer.data.direction).toBe('out')
642
+ expect(transfer.data.sender).toBe(viewer)
643
+ expect(transfer.data.recipient).toBe(recipient)
644
+ expect(transfer.data.sourceToken).toEqual({ address: tokenA, amount: '1000' })
645
+ expect(transfer.data.destinationToken).toEqual({ address: tokenB, amount: '990' })
646
+ })
647
+
648
+ test('keeps a swap whose output stays with the swapper as a `swap`', async () => {
649
+ const tokenB = '0x5555555555555555555555555555555555555555' as Address.Address
650
+ const items = await classify([
651
+ transferRow({ from: viewer, logIdx: 0, to: stablecoinDex, token: tokenA, value: 1_000n }),
652
+ transferRow({ from: stablecoinDex, logIdx: 1, to: viewer, token: tokenB, value: 990n }),
653
+ ])
654
+ const swap = items.find((item) => item.type === 'swap')
655
+ expect(swap?.type).toBe('swap')
656
+ if (swap?.type !== 'swap') return
657
+ expect(swap.data.sourceToken).toEqual({ address: tokenA, amount: '1000' })
658
+ expect(swap.data.destinationToken).toEqual({ address: tokenB, amount: '990' })
659
+ })
660
+
661
+ test('classifies OrderCancelled as `order-cancelled`', async () => {
662
+ const items = await classify([
663
+ logRow({
664
+ address: stablecoinDex,
665
+ args: { orderId: 42n },
666
+ eventName: 'OrderCancelled',
667
+ logIdx: 0,
668
+ }),
669
+ ])
670
+ expect(items[0]!.type).toBe('order-cancelled')
671
+ if (items[0]!.type !== 'order-cancelled') return
672
+ expect(items[0]!.data.orderId).toBe('42')
673
+ })
674
+
675
+ test('classifies FeesDistributed as `fees-distributed`', async () => {
676
+ const validator = '0xcccccccc00000000000000000000000000000000' as Address.Address
677
+ const items = await classify([
678
+ transferRow({ from: feeManager, logIdx: 0, to: validator, token: tokenA, value: 77n }),
679
+ logRow({
680
+ address: feeManager,
681
+ args: { amount: 77n, token: tokenA, validator },
682
+ eventName: 'FeesDistributed',
683
+ logIdx: 1,
684
+ }),
685
+ ])
686
+ expect(items[0]!.type).toBe('fees-distributed')
687
+ if (items[0]!.type !== 'fees-distributed') return
688
+ expect(items[0]!.data.sourceToken.amount).toBe('77')
689
+ expect(items[0]!.data.validator).toBe(validator)
690
+ })
691
+
692
+ test('classifies RebalanceSwap as `fee-rebalance-swap`', async () => {
693
+ const tokenB = '0x4444444444444444444444444444444444444444' as Address.Address
694
+ const items = await classify([
695
+ logRow({
696
+ address: feeManager,
697
+ args: {
698
+ amountIn: 10n,
699
+ amountOut: 9n,
700
+ swapper: viewer,
701
+ userToken: tokenB,
702
+ validatorToken: tokenA,
703
+ },
704
+ eventName: 'RebalanceSwap',
705
+ logIdx: 0,
706
+ }),
707
+ ])
708
+ expect(items[0]!.type).toBe('fee-rebalance-swap')
709
+ if (items[0]!.type !== 'fee-rebalance-swap') return
710
+ expect(items[0]!.data.sourceToken.address).toBe(tokenA)
711
+ expect(items[0]!.data.destinationToken.address).toBe(tokenB)
712
+ })
713
+
714
+ test('classifies RewardDistributed as `reward-distributed` on the token contract', async () => {
715
+ const items = await classify([
716
+ transferRow({ from: viewer, logIdx: 0, to: tokenA, token: tokenA, value: 30n }),
717
+ logRow({
718
+ address: tokenA,
719
+ args: { amount: 30n, funder: viewer },
720
+ eventName: 'RewardDistributed',
721
+ logIdx: 1,
722
+ }),
723
+ ])
724
+ expect(items[0]!.type).toBe('reward-distributed')
725
+ if (items[0]!.type !== 'reward-distributed') return
726
+ expect(items[0]!.data.sourceToken.address).toBe(tokenA)
727
+ expect(items[0]!.data.sourceToken.amount).toBe('30')
728
+ })
729
+
730
+ test('classifies RewardRecipientSet as `reward-recipient-set`', async () => {
731
+ const recipient = '0x5555555555555555555555555555555555555555' as Address.Address
732
+ const items = await classify([
733
+ logRow({
734
+ address: tokenA,
735
+ args: { holder: viewer, recipient },
736
+ eventName: 'RewardRecipientSet',
737
+ logIdx: 0,
738
+ }),
739
+ ])
740
+ expect(items[0]!.type).toBe('reward-recipient-set')
741
+ if (items[0]!.type !== 'reward-recipient-set') return
742
+ expect(items[0]!.data.recipient).toBe(recipient)
743
+ })
744
+
745
+ test('classifies SpendingLimitUpdated as `spending-limit-updated`', async () => {
746
+ const publicKey = '0x6666666666666666666666666666666666666666' as Address.Address
747
+ const items = await classify([
748
+ logRow({
749
+ address: accountKeychain,
750
+ args: { account: viewer, newLimit: 999n, publicKey, token: tokenA },
751
+ eventName: 'SpendingLimitUpdated',
752
+ logIdx: 0,
753
+ }),
754
+ ])
755
+ expect(items[0]!.type).toBe('spending-limit-updated')
756
+ if (items[0]!.type !== 'spending-limit-updated') return
757
+ expect(items[0]!.data.publicKey).toBe(publicKey)
758
+ expect(items[0]!.data.sourceToken.amount).toBe('999')
759
+ })
760
+
761
+ test('classifies BurnBlocked as `burn-blocked` before plain burn', async () => {
762
+ const items = await classify([
763
+ transferRow({
764
+ from: viewer,
765
+ logIdx: 0,
766
+ to: '0x0000000000000000000000000000000000000000' as Address.Address,
767
+ token: tokenA,
768
+ value: 15n,
769
+ }),
770
+ logRow({
771
+ address: tokenA,
772
+ args: { amount: 15n, from: viewer },
773
+ eventName: 'BurnBlocked',
774
+ logIdx: 1,
775
+ }),
776
+ ])
777
+ expect(items[0]!.type).toBe('burn-blocked')
778
+ if (items[0]!.type !== 'burn-blocked') return
779
+ expect(items[0]!.data.sourceToken.amount).toBe('15')
780
+ })
781
+
782
+ test('classifies OrderFilled as `order-filled`', async () => {
783
+ const maker = '0x4444444444444444444444444444444444444444' as Address.Address
784
+ const items = await classify([
785
+ logRow({
786
+ address: stablecoinDex,
787
+ args: { amountFilled: 250n, maker, orderId: 7n, partialFill: true, taker: viewer },
788
+ eventName: 'OrderFilled',
789
+ logIdx: 0,
790
+ }),
791
+ ])
792
+ expect(items[0]!.type).toBe('order-filled')
793
+ if (items[0]!.type !== 'order-filled') return
794
+ expect(items[0]!.data.orderId).toBe('7')
795
+ expect(items[0]!.data.amountFilled).toBe('250')
796
+ expect(items[0]!.data.partialFill).toBe(true)
797
+ })
798
+
799
+ test('classifies PairCreated as `pair-created`', async () => {
800
+ const quote = '0x5555555555555555555555555555555555555555' as Address.Address
801
+ const items = await classify([
802
+ logRow({
803
+ address: stablecoinDex,
804
+ args: { base: tokenA, key: `0x${'a'.repeat(64)}`, quote },
805
+ eventName: 'PairCreated',
806
+ logIdx: 0,
807
+ }),
808
+ ])
809
+ expect(items[0]!.type).toBe('pair-created')
810
+ if (items[0]!.type !== 'pair-created') return
811
+ expect(items[0]!.data.base).toBe(tokenA)
812
+ expect(items[0]!.data.quote).toBe(quote)
813
+ })
814
+
815
+ test('classifies PauseStateUpdate as `token-pause-set`', async () => {
816
+ const items = await classify([
817
+ logRow({
818
+ address: tokenA,
819
+ args: { isPaused: true, updater: viewer },
820
+ eventName: 'PauseStateUpdate',
821
+ logIdx: 0,
822
+ }),
823
+ ])
824
+ expect(items[0]!.type).toBe('token-pause-set')
825
+ if (items[0]!.type !== 'token-pause-set') return
826
+ expect(items[0]!.data.paused).toBe(true)
827
+ expect(items[0]!.data.updater).toBe(viewer)
828
+ })
829
+
830
+ test('classifies LogoURIUpdated as `token-logo-set`', async () => {
831
+ const items = await classify([
832
+ logRow({
833
+ address: tokenA,
834
+ args: { newLogoURI: 'https://example.com/logo.svg', updater: viewer },
835
+ eventName: 'LogoURIUpdated',
836
+ logIdx: 0,
837
+ }),
838
+ ])
839
+ expect(items[0]!.type).toBe('token-logo-set')
840
+ if (items[0]!.type !== 'token-logo-set') return
841
+ expect(items[0]!.data.logoUri).toBe('https://example.com/logo.svg')
842
+ })
843
+
844
+ test('classifies RoleMembershipUpdated as `role-membership-set`', async () => {
845
+ const account = '0x6666666666666666666666666666666666666666' as Address.Address
846
+ const items = await classify([
847
+ logRow({
848
+ address: tokenA,
849
+ args: { account, hasRole: true, role: `0x${'1'.repeat(64)}`, sender: viewer },
850
+ eventName: 'RoleMembershipUpdated',
851
+ logIdx: 0,
852
+ }),
853
+ ])
854
+ expect(items[0]!.type).toBe('role-membership-set')
855
+ if (items[0]!.type !== 'role-membership-set') return
856
+ expect(items[0]!.data.account).toBe(account)
857
+ expect(items[0]!.data.granted).toBe(true)
858
+ })
859
+
860
+ test('classifies TokenCreated before a co-located RoleMembershipUpdated', async () => {
861
+ const items = await classify([
862
+ logRow({
863
+ address: tokenA,
864
+ args: { account: viewer, hasRole: true, role: `0x${'1'.repeat(64)}`, sender: viewer },
865
+ eventName: 'RoleMembershipUpdated',
866
+ logIdx: 0,
867
+ }),
868
+ logRow({
869
+ address: tip20Factory,
870
+ args: {
871
+ admin: viewer,
872
+ currency: 'USD',
873
+ name: 'Test',
874
+ quoteToken: tokenA,
875
+ salt: `0x${'0'.repeat(64)}`,
876
+ symbol: 'TST',
877
+ token: tokenA,
878
+ },
879
+ eventName: 'TokenCreated',
880
+ logIdx: 1,
881
+ }),
882
+ ])
883
+ expect(items[0]!.type).toBe('token-created')
884
+ })
885
+
886
+ test('classifies PolicyCreated before the WhitelistUpdated it bundles', async () => {
887
+ const account = '0x7777777777777777777777777777777777777777' as Address.Address
888
+ const items = await classify([
889
+ logRow({
890
+ address: tokenA,
891
+ args: { account, allowed: true, policyId: 9n, updater: viewer },
892
+ eventName: 'WhitelistUpdated',
893
+ logIdx: 0,
894
+ }),
895
+ logRow({
896
+ address: tokenA,
897
+ args: { policyId: 9n, policyType: 1, updater: viewer },
898
+ eventName: 'PolicyCreated',
899
+ logIdx: 1,
900
+ }),
901
+ ])
902
+ expect(items[0]!.type).toBe('policy-created')
903
+ if (items[0]!.type !== 'policy-created') return
904
+ expect(items[0]!.data.policyId).toBe('9')
905
+ expect(items[0]!.data.policyType).toBe(1)
906
+ })
907
+
908
+ test('classifies WhitelistUpdated as `whitelist-updated`', async () => {
909
+ const account = '0x7777777777777777777777777777777777777777' as Address.Address
910
+ const items = await classify([
911
+ logRow({
912
+ address: tokenA,
913
+ args: { account, allowed: false, policyId: 9n, updater: viewer },
914
+ eventName: 'WhitelistUpdated',
915
+ logIdx: 0,
916
+ }),
917
+ ])
918
+ expect(items[0]!.type).toBe('whitelist-updated')
919
+ if (items[0]!.type !== 'whitelist-updated') return
920
+ expect(items[0]!.data.allowed).toBe(false)
921
+ })
922
+
923
+ test('classifies MasterRegistered as `master-registered`', async () => {
924
+ const master = '0x8888888888888888888888888888888888888888' as Address.Address
925
+ const items = await classify([
926
+ logRow({
927
+ address: accountKeychain,
928
+ args: { masterAddress: master, masterId: '0xdeadbeef' },
929
+ eventName: 'MasterRegistered',
930
+ logIdx: 0,
931
+ }),
932
+ ])
933
+ expect(items[0]!.type).toBe('master-registered')
934
+ if (items[0]!.type !== 'master-registered') return
935
+ expect(items[0]!.data.masterId).toBe('0xdeadbeef')
936
+ expect(items[0]!.data.masterAddress).toBe(master)
937
+ })
938
+
939
+ test('classifies NonceIncremented as `nonce-incremented`', async () => {
940
+ const items = await classify([
941
+ logRow({
942
+ address: accountKeychain,
943
+ args: { account: viewer, newNonce: 5n, nonceKey: 0n },
944
+ eventName: 'NonceIncremented',
945
+ logIdx: 0,
946
+ }),
947
+ ])
948
+ expect(items[0]!.type).toBe('nonce-incremented')
949
+ if (items[0]!.type !== 'nonce-incremented') return
950
+ expect(items[0]!.data.nonce).toBe('5')
951
+ })
952
+
953
+ test('classifies ValidatorRotated before the ValidatorAdded it bundles', async () => {
954
+ const validator = '0x9999999999999999999999999999999999999999' as Address.Address
955
+ const feeRecipient = '0xabababababababababababababababababababab' as Address.Address
956
+ const items = await classify([
957
+ logRow({
958
+ address: accountKeychain,
959
+ args: {
960
+ egress: 'https://v.example.com:2',
961
+ feeRecipient,
962
+ index: 2n,
963
+ ingress: 'https://v.example.com:1',
964
+ publicKey: `0x${'2'.repeat(64)}`,
965
+ validatorAddress: validator,
966
+ },
967
+ eventName: 'ValidatorAdded',
968
+ logIdx: 0,
969
+ }),
970
+ logRow({
971
+ address: accountKeychain,
972
+ args: {
973
+ caller: viewer,
974
+ deactivatedIndex: 1n,
975
+ egress: 'https://v.example.com:2',
976
+ index: 2n,
977
+ ingress: 'https://v.example.com:1',
978
+ newPublicKey: `0x${'3'.repeat(64)}`,
979
+ oldPublicKey: `0x${'2'.repeat(64)}`,
980
+ validatorAddress: validator,
981
+ },
982
+ eventName: 'ValidatorRotated',
983
+ logIdx: 1,
984
+ }),
985
+ ])
986
+ expect(items[0]!.type).toBe('validator-rotated')
987
+ if (items[0]!.type !== 'validator-rotated') return
988
+ expect(items[0]!.data.index).toBe('2')
989
+ expect(items[0]!.data.deactivatedIndex).toBe('1')
990
+ })
991
+
992
+ test('classifies OwnershipTransferred as `ownership-transferred`', async () => {
993
+ const newOwner = '0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' as Address.Address
994
+ const items = await classify([
995
+ logRow({
996
+ address: accountKeychain,
997
+ args: { newOwner, oldOwner: viewer },
998
+ eventName: 'OwnershipTransferred',
999
+ logIdx: 0,
1000
+ }),
1001
+ ])
1002
+ expect(items[0]!.type).toBe('ownership-transferred')
1003
+ if (items[0]!.type !== 'ownership-transferred') return
1004
+ expect(items[0]!.data.newOwner).toBe(newOwner)
1005
+ })
1006
+
1007
+ test('classifies Initialized as `initialized`', async () => {
1008
+ const items = await classify([
1009
+ logRow({
1010
+ address: accountKeychain,
1011
+ args: { height: 42n },
1012
+ eventName: 'Initialized',
1013
+ logIdx: 0,
1014
+ }),
1015
+ ])
1016
+ expect(items[0]!.type).toBe('initialized')
1017
+ if (items[0]!.type !== 'initialized') return
1018
+ expect(items[0]!.data.height).toBe('42')
1019
+ })
1020
+ })