dexterm-cli 0.0.0-stage → 0.1.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 (377) hide show
  1. package/LICENSE +661 -0
  2. package/dist/packages/agents/src/audit.js +122 -0
  3. package/dist/packages/agents/src/bus.js +394 -0
  4. package/dist/packages/agents/src/executor.js +202 -0
  5. package/dist/packages/agents/src/gate.js +179 -0
  6. package/dist/packages/agents/src/identity.js +218 -0
  7. package/dist/packages/agents/src/index.js +15 -0
  8. package/dist/packages/agents/src/proposals.js +153 -0
  9. package/dist/packages/agents/src/schema.js +87 -0
  10. package/dist/packages/agents/src/sdk.js +140 -0
  11. package/dist/packages/agents/src/tiers.js +119 -0
  12. package/dist/packages/agents/src/tools.js +72 -0
  13. package/dist/packages/agents/src/ws.js +158 -0
  14. package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
  15. package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
  16. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
  17. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +75 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +74 -0
  19. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
  20. package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
  21. package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
  22. package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
  23. package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
  24. package/dist/packages/cli/bin/dexterm.mjs +11 -0
  25. package/dist/packages/cli/src/agent-cmd.js +336 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
  28. package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
  29. package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
  30. package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
  31. package/dist/packages/cli/src/alerts/creds.js +83 -0
  32. package/dist/packages/cli/src/alerts/format.js +41 -0
  33. package/dist/packages/cli/src/alerts/index.js +10 -0
  34. package/dist/packages/cli/src/alerts/notifier.js +53 -0
  35. package/dist/packages/cli/src/alerts/route.js +19 -0
  36. package/dist/packages/cli/src/alerts/types.js +1 -0
  37. package/dist/packages/cli/src/alerts-cmd.js +212 -0
  38. package/dist/packages/cli/src/analyst/backend.js +526 -0
  39. package/dist/packages/cli/src/analyst/results.js +38 -0
  40. package/dist/packages/cli/src/analyst/session.js +319 -0
  41. package/dist/packages/cli/src/audit.js +51 -0
  42. package/dist/packages/cli/src/backtest-cmd.js +208 -0
  43. package/dist/packages/cli/src/boot-state.js +62 -0
  44. package/dist/packages/cli/src/coin-target.js +2 -0
  45. package/dist/packages/cli/src/custom.js +261 -0
  46. package/dist/packages/cli/src/display.js +191 -0
  47. package/dist/packages/cli/src/doctor.js +375 -0
  48. package/dist/packages/cli/src/export.js +235 -0
  49. package/dist/packages/cli/src/feed.js +634 -0
  50. package/dist/packages/cli/src/fixtures.js +275 -0
  51. package/dist/packages/cli/src/follow-cmd.js +221 -0
  52. package/dist/packages/cli/src/images-config.js +59 -0
  53. package/dist/packages/cli/src/index.js +31 -0
  54. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  55. package/dist/packages/cli/src/larp-record.js +21 -0
  56. package/dist/packages/cli/src/live/copy.js +80 -0
  57. package/dist/packages/cli/src/live/flow.js +233 -0
  58. package/dist/packages/cli/src/live/index.js +511 -0
  59. package/dist/packages/cli/src/live/intel.js +227 -0
  60. package/dist/packages/cli/src/live/newpairs.js +334 -0
  61. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  62. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  63. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  64. package/dist/packages/cli/src/live/token.js +585 -0
  65. package/dist/packages/cli/src/live/trade.js +929 -0
  66. package/dist/packages/cli/src/live/watchlist.js +178 -0
  67. package/dist/packages/cli/src/main.js +5785 -0
  68. package/dist/packages/cli/src/mcp-cmd.js +326 -0
  69. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  70. package/dist/packages/cli/src/pack-cmd.js +323 -0
  71. package/dist/packages/cli/src/plan-cmd.js +278 -0
  72. package/dist/packages/cli/src/popup.js +810 -0
  73. package/dist/packages/cli/src/rules-cmd.js +131 -0
  74. package/dist/packages/cli/src/rules.js +358 -0
  75. package/dist/packages/cli/src/scan/command.js +57 -0
  76. package/dist/packages/cli/src/scan/engine.js +419 -0
  77. package/dist/packages/cli/src/scan/observe.js +64 -0
  78. package/dist/packages/cli/src/scan/ui.js +98 -0
  79. package/dist/packages/cli/src/screener/filter.js +241 -0
  80. package/dist/packages/cli/src/screener/presets.js +80 -0
  81. package/dist/packages/cli/src/screener/rows.js +146 -0
  82. package/dist/packages/cli/src/screener/rug.js +50 -0
  83. package/dist/packages/cli/src/screener/sort.js +52 -0
  84. package/dist/packages/cli/src/screener/view.js +183 -0
  85. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  86. package/dist/packages/cli/src/screens/analyst.js +326 -0
  87. package/dist/packages/cli/src/screens/boot.js +74 -0
  88. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  89. package/dist/packages/cli/src/screens/coin.js +33 -0
  90. package/dist/packages/cli/src/screens/follows.js +201 -0
  91. package/dist/packages/cli/src/screens/kol.js +90 -0
  92. package/dist/packages/cli/src/screens/narrative.js +90 -0
  93. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  94. package/dist/packages/cli/src/screens/overlay.js +257 -0
  95. package/dist/packages/cli/src/screens/plans.js +126 -0
  96. package/dist/packages/cli/src/screens/positions.js +176 -0
  97. package/dist/packages/cli/src/screens/rules.js +170 -0
  98. package/dist/packages/cli/src/screens/screener.js +459 -0
  99. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  100. package/dist/packages/cli/src/screens/ticket.js +342 -0
  101. package/dist/packages/cli/src/screens/token.js +312 -0
  102. package/dist/packages/cli/src/screens/trending.js +181 -0
  103. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  104. package/dist/packages/cli/src/skill-eval-build.js +109 -0
  105. package/dist/packages/cli/src/social/demo.js +96 -0
  106. package/dist/packages/cli/src/social/view.js +250 -0
  107. package/dist/packages/cli/src/state.js +463 -0
  108. package/dist/packages/cli/src/theme-cmd.js +126 -0
  109. package/dist/packages/cli/src/trade.js +319 -0
  110. package/dist/packages/cli/src/update.js +294 -0
  111. package/dist/packages/cli/src/version.js +3 -0
  112. package/dist/packages/cli/src/voice-cmd.js +119 -0
  113. package/dist/packages/cli/src/voice-hud.js +42 -0
  114. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  115. package/dist/packages/cli/src/wallet-command.js +31 -0
  116. package/dist/packages/cli/src/watch-cmd.js +190 -0
  117. package/dist/packages/cli/src/widgets.js +208 -0
  118. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  119. package/dist/packages/cli/src/x-cmd.js +154 -0
  120. package/dist/packages/custom/src/config.js +213 -0
  121. package/dist/packages/custom/src/index.js +10 -0
  122. package/dist/packages/custom/src/keymap.js +107 -0
  123. package/dist/packages/custom/src/layout.js +235 -0
  124. package/dist/packages/custom/src/packs.js +633 -0
  125. package/dist/packages/custom/src/rules.js +704 -0
  126. package/dist/packages/custom/src/schema.js +538 -0
  127. package/dist/packages/custom/src/themes.js +176 -0
  128. package/dist/packages/custom/src/widget-html.js +222 -0
  129. package/dist/packages/custom/src/workspace.js +141 -0
  130. package/dist/packages/data/src/analyst-actions.js +133 -0
  131. package/dist/packages/data/src/budget.js +88 -0
  132. package/dist/packages/data/src/cache.js +142 -0
  133. package/dist/packages/data/src/candles.js +168 -0
  134. package/dist/packages/data/src/clock.js +64 -0
  135. package/dist/packages/data/src/coin-target.js +82 -0
  136. package/dist/packages/data/src/credits.js +137 -0
  137. package/dist/packages/data/src/detail.js +97 -0
  138. package/dist/packages/data/src/fast-lane.js +321 -0
  139. package/dist/packages/data/src/fixtures.js +135 -0
  140. package/dist/packages/data/src/health.js +245 -0
  141. package/dist/packages/data/src/http.js +277 -0
  142. package/dist/packages/data/src/images/cache.js +141 -0
  143. package/dist/packages/data/src/images/decode-worker.js +28 -0
  144. package/dist/packages/data/src/images/decode.js +81 -0
  145. package/dist/packages/data/src/images/fetch.js +162 -0
  146. package/dist/packages/data/src/images/gif.js +214 -0
  147. package/dist/packages/data/src/images/index.js +10 -0
  148. package/dist/packages/data/src/images/jpeg.js +544 -0
  149. package/dist/packages/data/src/images/png.js +310 -0
  150. package/dist/packages/data/src/images/service.js +334 -0
  151. package/dist/packages/data/src/images/types.js +27 -0
  152. package/dist/packages/data/src/index.js +211 -0
  153. package/dist/packages/data/src/intel/helius-history.js +70 -0
  154. package/dist/packages/data/src/intel/holder-class.js +125 -0
  155. package/dist/packages/data/src/intel/rules.js +421 -0
  156. package/dist/packages/data/src/intel/scan.js +170 -0
  157. package/dist/packages/data/src/larp/evidence.js +288 -0
  158. package/dist/packages/data/src/larp/link-baseline.js +196 -0
  159. package/dist/packages/data/src/larp/links.js +137 -0
  160. package/dist/packages/data/src/larp/robots.js +90 -0
  161. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  162. package/dist/packages/data/src/limiter.js +341 -0
  163. package/dist/packages/data/src/model.js +12 -0
  164. package/dist/packages/data/src/paper.js +331 -0
  165. package/dist/packages/data/src/rpc-config.js +126 -0
  166. package/dist/packages/data/src/rpc-pool.js +166 -0
  167. package/dist/packages/data/src/social/chain.js +83 -0
  168. package/dist/packages/data/src/social/dedupe.js +205 -0
  169. package/dist/packages/data/src/social/feed.js +462 -0
  170. package/dist/packages/data/src/social/firewall.js +110 -0
  171. package/dist/packages/data/src/social/index.js +11 -0
  172. package/dist/packages/data/src/social/kol.js +269 -0
  173. package/dist/packages/data/src/social/model.js +11 -0
  174. package/dist/packages/data/src/social/rank.js +184 -0
  175. package/dist/packages/data/src/social/rss.js +113 -0
  176. package/dist/packages/data/src/social/summary.js +104 -0
  177. package/dist/packages/data/src/social/venues.js +186 -0
  178. package/dist/packages/data/src/sources/dexscreener.js +184 -0
  179. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  180. package/dist/packages/data/src/sources/goplus.js +161 -0
  181. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  182. package/dist/packages/data/src/sources/jupiter.js +198 -0
  183. package/dist/packages/data/src/sources/pda.js +113 -0
  184. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  185. package/dist/packages/data/src/sources/pumpportal.js +252 -0
  186. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  187. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  188. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  189. package/dist/packages/data/src/store.js +289 -0
  190. package/dist/packages/data/src/trade/bridge.js +464 -0
  191. package/dist/packages/data/src/trade/caps.js +323 -0
  192. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  193. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  194. package/dist/packages/data/src/trade/copy/index.js +9 -0
  195. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  196. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  197. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  198. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  199. package/dist/packages/data/src/trade/copy/types.js +6 -0
  200. package/dist/packages/data/src/trade/executor.js +933 -0
  201. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  202. package/dist/packages/data/src/trade/link-store.js +52 -0
  203. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  204. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  205. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  206. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  207. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  208. package/dist/packages/data/src/trade/plans/index.js +12 -0
  209. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  210. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  211. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  212. package/dist/packages/data/src/trade/plans/service.js +168 -0
  213. package/dist/packages/data/src/trade/plans/types.js +6 -0
  214. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  215. package/dist/packages/data/src/trade/signer/client.js +197 -0
  216. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  217. package/dist/packages/data/src/trade/signer/host.js +97 -0
  218. package/dist/packages/data/src/trade/signer/main.js +121 -0
  219. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  220. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  221. package/dist/packages/data/src/trade/signer/service.js +400 -0
  222. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  223. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  224. package/dist/packages/data/src/trade/swap-build.js +108 -0
  225. package/dist/packages/data/src/trade/token-delta.js +201 -0
  226. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  227. package/dist/packages/data/src/util.js +24 -0
  228. package/dist/packages/data/src/watchlist.js +176 -0
  229. package/dist/packages/data/src/x/api.js +256 -0
  230. package/dist/packages/data/src/x/budget.js +66 -0
  231. package/dist/packages/data/src/x/config.js +49 -0
  232. package/dist/packages/data/src/x/index.js +10 -0
  233. package/dist/packages/data/src/x/mock-server.js +217 -0
  234. package/dist/packages/data/src/x/oauth.js +242 -0
  235. package/dist/packages/data/src/x/pkce.js +21 -0
  236. package/dist/packages/data/src/x/source.js +285 -0
  237. package/dist/packages/data/src/x/token-store.js +134 -0
  238. package/dist/packages/data/src/x/transport.js +71 -0
  239. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  240. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  241. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  242. package/dist/packages/mcp/assets/cases.json +1194 -0
  243. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  244. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  245. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  246. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  247. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  248. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  249. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  250. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  251. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  252. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  253. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  254. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  255. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  256. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  257. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  258. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  259. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  260. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  261. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  262. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  263. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  264. package/dist/packages/mcp/src/dossier.js +78 -0
  265. package/dist/packages/mcp/src/firewall.js +21 -0
  266. package/dist/packages/mcp/src/index.js +22 -0
  267. package/dist/packages/mcp/src/server.js +936 -0
  268. package/dist/packages/mcp/src/user-content.js +434 -0
  269. package/dist/packages/packs/src/backtest.js +183 -0
  270. package/dist/packages/packs/src/digest.js +95 -0
  271. package/dist/packages/packs/src/firewall.js +57 -0
  272. package/dist/packages/packs/src/index.js +20 -0
  273. package/dist/packages/packs/src/larp-eval.js +100 -0
  274. package/dist/packages/packs/src/narrative.js +309 -0
  275. package/dist/packages/packs/src/reason.js +135 -0
  276. package/dist/packages/packs/src/rug-model.js +137 -0
  277. package/dist/packages/packs/src/schema.js +54 -0
  278. package/dist/packages/packs/src/skill-backtest.js +300 -0
  279. package/dist/packages/packs/src/token-dossier.js +174 -0
  280. package/dist/packages/packs/src/verdicts.js +17 -0
  281. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  282. package/dist/packages/popup/src/config.js +71 -0
  283. package/dist/packages/popup/src/dock.js +271 -0
  284. package/dist/packages/popup/src/host.js +210 -0
  285. package/dist/packages/popup/src/index.js +12 -0
  286. package/dist/packages/popup/src/loopback.js +53 -0
  287. package/dist/packages/popup/src/open.js +183 -0
  288. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  289. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  290. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  291. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  292. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  293. package/dist/packages/popup/src/pages/chart.css +50 -0
  294. package/dist/packages/popup/src/pages/chart.html +56 -0
  295. package/dist/packages/popup/src/pages/chart.js +554 -0
  296. package/dist/packages/popup/src/pages/common.css +34 -0
  297. package/dist/packages/popup/src/pages/common.js +187 -0
  298. package/dist/packages/popup/src/pages/feeds.html +20 -0
  299. package/dist/packages/popup/src/pages/feeds.js +77 -0
  300. package/dist/packages/popup/src/pages/narrative.html +20 -0
  301. package/dist/packages/popup/src/pages/narrative.js +61 -0
  302. package/dist/packages/popup/src/pages/plans.css +30 -0
  303. package/dist/packages/popup/src/pages/plans.html +26 -0
  304. package/dist/packages/popup/src/pages/plans.js +82 -0
  305. package/dist/packages/popup/src/pages/roster.css +41 -0
  306. package/dist/packages/popup/src/pages/roster.html +25 -0
  307. package/dist/packages/popup/src/pages/roster.js +75 -0
  308. package/dist/packages/popup/src/pages/token.html +20 -0
  309. package/dist/packages/popup/src/pages/token.js +112 -0
  310. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  311. package/dist/packages/popup/src/pages/widget.css +5 -0
  312. package/dist/packages/popup/src/pages/widget.html +21 -0
  313. package/dist/packages/popup/src/pages/widget.js +85 -0
  314. package/dist/packages/popup/src/protocol.js +31 -0
  315. package/dist/packages/popup/src/selftest.js +156 -0
  316. package/dist/packages/popup/src/server.js +582 -0
  317. package/dist/packages/popup/src/sinkproxy.js +167 -0
  318. package/dist/packages/popup/src/widget-html.js +231 -0
  319. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  320. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  321. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  322. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  323. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  324. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  325. package/dist/packages/recorder/src/cli.js +113 -0
  326. package/dist/packages/recorder/src/export.js +71 -0
  327. package/dist/packages/recorder/src/index.js +20 -0
  328. package/dist/packages/recorder/src/labels.js +151 -0
  329. package/dist/packages/recorder/src/open.js +29 -0
  330. package/dist/packages/recorder/src/recorder.js +553 -0
  331. package/dist/packages/recorder/src/schedule.js +43 -0
  332. package/dist/packages/recorder/src/schema.js +135 -0
  333. package/dist/packages/recorder/src/snapshot.js +191 -0
  334. package/dist/packages/recorder/src/sweep.js +52 -0
  335. package/dist/packages/recorder/src/verdicts.js +52 -0
  336. package/dist/packages/tui/src/bubbles.js +415 -0
  337. package/dist/packages/tui/src/canvas.js +370 -0
  338. package/dist/packages/tui/src/chrome.js +85 -0
  339. package/dist/packages/tui/src/color.js +133 -0
  340. package/dist/packages/tui/src/flush.js +62 -0
  341. package/dist/packages/tui/src/fmt.js +188 -0
  342. package/dist/packages/tui/src/fx-trade.js +278 -0
  343. package/dist/packages/tui/src/fx.js +527 -0
  344. package/dist/packages/tui/src/images/blocks.js +141 -0
  345. package/dist/packages/tui/src/images/coin.js +22 -0
  346. package/dist/packages/tui/src/images/detect.js +140 -0
  347. package/dist/packages/tui/src/images/index.js +7 -0
  348. package/dist/packages/tui/src/images/layer.js +146 -0
  349. package/dist/packages/tui/src/images/pixels.js +157 -0
  350. package/dist/packages/tui/src/images/protocols.js +87 -0
  351. package/dist/packages/tui/src/index.js +18 -0
  352. package/dist/packages/tui/src/keymap.js +357 -0
  353. package/dist/packages/tui/src/layout.js +11 -0
  354. package/dist/packages/tui/src/raster-font.js +195 -0
  355. package/dist/packages/tui/src/raster.js +213 -0
  356. package/dist/packages/tui/src/sound.js +177 -0
  357. package/dist/packages/tui/src/theme.js +182 -0
  358. package/dist/packages/tui/src/widgets.js +598 -0
  359. package/dist/packages/voice/src/controller.js +407 -0
  360. package/dist/packages/voice/src/doctor.js +131 -0
  361. package/dist/packages/voice/src/download.js +184 -0
  362. package/dist/packages/voice/src/engines.js +84 -0
  363. package/dist/packages/voice/src/index.js +20 -0
  364. package/dist/packages/voice/src/router.js +502 -0
  365. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  366. package/dist/packages/voice/src/stt/apple.js +179 -0
  367. package/dist/packages/voice/src/stt/fake.js +34 -0
  368. package/dist/packages/voice/src/stt/whisper.js +112 -0
  369. package/dist/packages/voice/src/summary.js +44 -0
  370. package/dist/packages/voice/src/tts/espeak.js +131 -0
  371. package/dist/packages/voice/src/tts/fake.js +30 -0
  372. package/dist/packages/voice/src/tts/piper.js +206 -0
  373. package/dist/packages/voice/src/tts/say.js +132 -0
  374. package/dist/packages/voice/src/tts/voices.js +206 -0
  375. package/dist/packages/voice/src/types.js +3 -0
  376. package/package.json +38 -4
  377. package/README.md +0 -3
@@ -0,0 +1,299 @@
1
+ // Solana JSON-RPC adapter (SPEC §3.1 S6). Keyless, read-only.
2
+ // Default endpoint is the public mainnet RPC (heavily rate-limited: "use sparingly");
3
+ // any RPC URL can be configured. Only the read methods in READ_METHODS are ever sent.
4
+ // getAccountInfo(mint, base64) -> SPL / Token-2022 mint: authorities, supply,
5
+ // decimals, Token-2022 extension list
6
+ // getTokenLargestAccounts(mint) -> top 20 token accounts (raw amounts)
7
+ // getMultipleAccounts(accounts, base64) -> token-account owners for those 20
8
+ // We decode the binary layouts ourselves (no @solana/* dependency) and use base64,
9
+ // which every RPC supports, rather than jsonParsed.
10
+ //
11
+ // Strict backoff: no retries here. A 429, or a JSON-RPC error that says "too many
12
+ // requests" (some RPCs send it with HTTP 200), pauses the bucket with a pause that
13
+ // doubles per consecutive 429 (HttpClient) and counts toward health demotion.
14
+ export const SOLANA_PUBLIC_RPC = 'https://api.mainnet-beta.solana.com';
15
+ export const READ_METHODS = new Set(['getAccountInfo', 'getMultipleAccounts', 'getTokenLargestAccounts', 'getTokenSupply']);
16
+ export const TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
17
+ export const TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
18
+ const MINT_LEN = 82;
19
+ const ACCOUNT_LEN = 165; // Token-2022 pads mints to the account size before the extension TLVs
20
+ const ACCOUNT_TYPE_MINT = 1;
21
+ /** Token-2022 ExtensionType ids (spl-token-2022 `extension/mod.rs`). */
22
+ export const TOKEN_2022_EXTENSIONS = {
23
+ 1: 'transferFeeConfig',
24
+ 2: 'transferFeeAmount',
25
+ 3: 'mintCloseAuthority',
26
+ 4: 'confidentialTransferMint',
27
+ 5: 'confidentialTransferAccount',
28
+ 6: 'defaultAccountState',
29
+ 7: 'immutableOwner',
30
+ 8: 'memoTransfer',
31
+ 9: 'nonTransferable',
32
+ 10: 'interestBearingConfig',
33
+ 11: 'cpiGuard',
34
+ 12: 'permanentDelegate',
35
+ 13: 'nonTransferableAccount',
36
+ 14: 'transferHook',
37
+ 15: 'transferHookAccount',
38
+ 16: 'confidentialTransferFeeConfig',
39
+ 17: 'confidentialTransferFeeAmount',
40
+ 18: 'metadataPointer',
41
+ 19: 'tokenMetadata',
42
+ 20: 'groupPointer',
43
+ 21: 'tokenGroup',
44
+ 22: 'groupMemberPointer',
45
+ 23: 'tokenGroupMember',
46
+ 24: 'confidentialMintBurn',
47
+ 25: 'scaledUiAmountConfig',
48
+ 26: 'pausableConfig',
49
+ 27: 'pausableAccount',
50
+ };
51
+ /** Extensions that let an issuer take, block, tax or hook transfers: shown as risk flags. */
52
+ export const RISKY_EXTENSIONS = new Set([
53
+ 'permanentDelegate',
54
+ 'transferHook',
55
+ 'transferFeeConfig',
56
+ 'nonTransferable',
57
+ 'defaultAccountState',
58
+ 'pausableConfig',
59
+ 'mintCloseAuthority',
60
+ ]);
61
+ /**
62
+ * Owners whose token accounts are pool vaults, not holders (SPEC §3.6 allow-list seed).
63
+ * Only program authorities that own the vaults of every pool of that program are listed;
64
+ * PumpSwap / CLMM / Meteora vaults are owned by per-pool PDAs and need Tier 1 labelling.
65
+ */
66
+ export const KNOWN_OWNERS = {
67
+ '5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1': 'LP', // Raydium AMM v4 authority
68
+ GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL: 'LP', // Raydium CPMM authority
69
+ };
70
+ // ---------------------------------------------------------------- decoding
71
+ const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
72
+ /** Base58 (Bitcoin alphabet) encode, used for 32-byte public keys. */
73
+ export function base58(bytes) {
74
+ const digits = [];
75
+ for (const byte of bytes) {
76
+ let carry = byte;
77
+ for (let i = 0; i < digits.length; i++) {
78
+ carry += digits[i] << 8;
79
+ digits[i] = carry % 58;
80
+ carry = (carry / 58) | 0;
81
+ }
82
+ while (carry > 0) {
83
+ digits.push(carry % 58);
84
+ carry = (carry / 58) | 0;
85
+ }
86
+ }
87
+ let out = '';
88
+ for (const byte of bytes) {
89
+ if (byte !== 0)
90
+ break;
91
+ out += '1';
92
+ }
93
+ for (let i = digits.length - 1; i >= 0; i--)
94
+ out += B58[digits[i]];
95
+ return out;
96
+ }
97
+ function accountBytes(acc) {
98
+ const [data, enc] = Array.isArray(acc.data) ? acc.data : [acc.data, 'base58'];
99
+ if (enc !== 'base64')
100
+ throw new Error(`unexpected account encoding ${enc}`);
101
+ return Buffer.from(data, 'base64');
102
+ }
103
+ function optionPubkey(buf, at) {
104
+ return buf.readUInt32LE(at) === 1 ? base58(buf.subarray(at + 4, at + 36)) : null;
105
+ }
106
+ /** Decode an SPL Token / Token-2022 mint account (82-byte base layout + Token-2022 TLV extensions). */
107
+ export function decodeMint(mint, data, programId, fetchedAt, source = 'solana-rpc') {
108
+ if (data.length < MINT_LEN)
109
+ throw new Error(`account ${mint} is not a mint (${data.length} bytes)`);
110
+ const program = programId === TOKEN_PROGRAM ? 'spl-token' : programId === TOKEN_2022_PROGRAM ? 'token-2022' : 'unknown';
111
+ const extensions = [];
112
+ if (data.length > ACCOUNT_LEN && data[ACCOUNT_LEN] === ACCOUNT_TYPE_MINT) {
113
+ let at = ACCOUNT_LEN + 1;
114
+ while (at + 4 <= data.length) {
115
+ const type = data.readUInt16LE(at);
116
+ const len = data.readUInt16LE(at + 2);
117
+ if (type === 0)
118
+ break; // Uninitialized: rest is padding
119
+ extensions.push(TOKEN_2022_EXTENSIONS[type] ?? `unknown(${type})`);
120
+ at += 4 + len;
121
+ }
122
+ }
123
+ return {
124
+ mint,
125
+ program,
126
+ programId,
127
+ mintAuthority: optionPubkey(data, 0),
128
+ supply: data.readBigUInt64LE(36).toString(),
129
+ decimals: data[44],
130
+ isInitialized: data[45] === 1,
131
+ freezeAuthority: optionPubkey(data, 46),
132
+ extensions,
133
+ riskyExtensions: extensions.filter((e) => RISKY_EXTENSIONS.has(e)),
134
+ source,
135
+ fetchedAt,
136
+ };
137
+ }
138
+ /** Token account (SPL and Token-2022 share the first 165 bytes): mint, owner, amount. */
139
+ export function decodeTokenAccount(data) {
140
+ if (data.length < 72)
141
+ throw new Error(`not a token account (${data.length} bytes)`);
142
+ return { mint: base58(data.subarray(0, 32)), owner: base58(data.subarray(32, 64)), amount: data.readBigUInt64LE(64) };
143
+ }
144
+ /** Percent of supply, computed in bigint so 18-digit raw amounts keep their precision. */
145
+ export function pctOf(amount, supply) {
146
+ if (supply <= 0n)
147
+ return 0;
148
+ return Number((amount * 10000000n) / supply) / 100_000;
149
+ }
150
+ /** Mint authorities as the shared Audit flags (true = still enabled). */
151
+ export function auditFromMint(info, top10Pct = null) {
152
+ return {
153
+ mint: info.mint,
154
+ mintAuthority: info.mintAuthority !== null,
155
+ freezeAuthority: info.freezeAuthority !== null,
156
+ top10Pct,
157
+ devPct: null,
158
+ devMints: null,
159
+ lpLockedPct: null,
160
+ rugRisks: info.riskyExtensions.map((e) => `token-2022:${e}`),
161
+ rugScore: null,
162
+ source: info.source,
163
+ fetchedAt: info.fetchedAt,
164
+ };
165
+ }
166
+ /** JSON-RPC errors are failures; rate-limit errors count as 429s (some RPCs send them with HTTP 200). */
167
+ export const rpcBodyCheck = (data) => {
168
+ const err = data?.error;
169
+ if (!err)
170
+ return null;
171
+ const message = `rpc error ${err.code ?? '?'}: ${err.message ?? 'unknown'}`;
172
+ const limited = err.code === 429 || /too many requests|rate limit/i.test(err.message ?? '');
173
+ return limited ? { status: 429, error: message } : { error: message };
174
+ };
175
+ // ---------------------------------------------------------------- adapter
176
+ export class SolanaRpc {
177
+ http;
178
+ /** May carry an api-key: never print it (see rpc-config.ts redactSecrets). */
179
+ url;
180
+ source;
181
+ meta;
182
+ constructor(http, opts = {}) {
183
+ this.http = http;
184
+ this.url = opts.url ?? SOLANA_PUBLIC_RPC;
185
+ this.source = opts.source ?? 'solana-rpc';
186
+ // Each pool endpoint gets its own bucket (task-1309) so one endpoint's 429 does not pause the others.
187
+ this.meta = { source: this.source, bucket: opts.bucket ?? this.source };
188
+ }
189
+ async call(method, params) {
190
+ if (!READ_METHODS.has(method))
191
+ throw new Error(`refusing non-read RPC method ${method}`);
192
+ // Constant id: one request per HTTP call, and recorded fixtures match on replay.
193
+ const body = { jsonrpc: '2.0', id: 1, method, params };
194
+ const r = await this.http.postJson(this.url, body, this.meta, rpcBodyCheck);
195
+ if (r.data.result === undefined)
196
+ throw new Error(`rpc ${method}: no result`);
197
+ return { result: r.data.result, fetchedAt: r.fetchedAt };
198
+ }
199
+ /** Mint/freeze authority, supply, decimals and Token-2022 extensions. */
200
+ async mintInfo(mint) {
201
+ const { result, fetchedAt } = await this.call('getAccountInfo', [mint, { encoding: 'base64', commitment: 'confirmed' }]);
202
+ if (!result.value)
203
+ throw new Error(`mint ${mint} not found`);
204
+ return decodeMint(mint, accountBytes(result.value), result.value.owner, fetchedAt, this.source);
205
+ }
206
+ /** Top 20 token accounts; `pct` uses `supply` from mintInfo(). */
207
+ async largestAccounts(mint) {
208
+ const { result, fetchedAt } = await this.call('getTokenLargestAccounts', [mint, { commitment: 'confirmed' }]);
209
+ return { accounts: result.value.map((a) => ({ address: a.address, amount: BigInt(a.amount) })), fetchedAt };
210
+ }
211
+ /**
212
+ * Raw base64 account data for arbitrary accounts (one getMultipleAccounts call, ≤ 100). Used by the
213
+ * fast lane to seed a first on-chain price (e.g. a pump.fun bonding curve) before the first push.
214
+ * A missing account maps to null; the caller decodes the bytes itself.
215
+ */
216
+ async accountsRaw(accounts, commitment = 'processed') {
217
+ const out = new Map();
218
+ if (accounts.length === 0)
219
+ return { data: out, fetchedAt: Date.now() };
220
+ const slice = accounts.slice(0, 100);
221
+ const { result, fetchedAt } = await this.call('getMultipleAccounts', [slice, { encoding: 'base64', commitment }]);
222
+ result.value.forEach((acc, i) => {
223
+ const bytes = acc ? (Array.isArray(acc.data) ? acc.data[0] : null) : null;
224
+ out.set(slice[i], bytes ?? null);
225
+ });
226
+ return { data: out, fetchedAt };
227
+ }
228
+ /**
229
+ * Account META for arbitrary addresses (task-1340 wallet intel): the owning program (System = a wallet,
230
+ * anything else = program-owned vault / PDA), whether the account exists, lamports. One getMultipleAccounts
231
+ * with a zero-length data slice, so a 100-address call stays a few KB.
232
+ */
233
+ async accountMeta(accounts, commitment = 'confirmed') {
234
+ const out = new Map();
235
+ if (accounts.length === 0)
236
+ return out;
237
+ const slice = accounts.slice(0, 100);
238
+ const { result } = await this.call('getMultipleAccounts', [slice, { encoding: 'base64', commitment, dataSlice: { offset: 0, length: 0 } }]);
239
+ result.value.forEach((acc, i) => {
240
+ out.set(slice[i], acc ? { programOwner: acc.owner, exists: true, lamports: acc.lamports ?? 0 } : { programOwner: null, exists: false, lamports: 0 });
241
+ });
242
+ return out;
243
+ }
244
+ /** Owners of token accounts (one getMultipleAccounts call, ≤ 100 accounts). */
245
+ async tokenAccountOwners(accounts) {
246
+ const out = new Map();
247
+ if (accounts.length === 0)
248
+ return out;
249
+ const { result } = await this.call('getMultipleAccounts', [accounts.slice(0, 100), { encoding: 'base64', commitment: 'confirmed' }]);
250
+ result.value.forEach((acc, i) => {
251
+ if (acc)
252
+ out.set(accounts[i], decodeTokenAccount(accountBytes(acc)).owner);
253
+ });
254
+ return out;
255
+ }
256
+ /**
257
+ * Top-20 holders (SPEC §3.2 keyless): 2 requests (largest accounts + owners), plus
258
+ * getAccountInfo when `info` is not supplied. Owner lookup failing keeps the token
259
+ * accounts as addresses rather than failing the whole call.
260
+ */
261
+ async topHolders(mint, opts = {}) {
262
+ const info = opts.info ?? (await this.mintInfo(mint));
263
+ const { accounts: largest, fetchedAt } = await this.largestAccounts(mint);
264
+ let owners = new Map();
265
+ if (opts.resolveOwners ?? true) {
266
+ try {
267
+ owners = await this.tokenAccountOwners(largest.map((a) => a.address));
268
+ }
269
+ catch {
270
+ // keep token accounts; the caller still gets amounts and shares
271
+ }
272
+ }
273
+ const supply = BigInt(info.supply);
274
+ const scale = 10 ** info.decimals;
275
+ const holders = largest.map((a) => {
276
+ const owner = owners.get(a.address) ?? null;
277
+ const label = owner ? KNOWN_OWNERS[owner] : undefined;
278
+ return {
279
+ address: owner ?? a.address,
280
+ tokenAccount: a.address,
281
+ owner,
282
+ amount: Number(a.amount) / scale,
283
+ pct: pctOf(a.amount, supply),
284
+ ...(label ? { label } : {}),
285
+ };
286
+ });
287
+ const excluded = new Set(['LP', 'cex', 'bridge']);
288
+ const top10 = holders.filter((h) => !h.label || !excluded.has(h.label)).slice(0, 10);
289
+ return {
290
+ mint,
291
+ holders,
292
+ top10Pct: supply > 0n ? Math.round(top10.reduce((s, h) => s + h.pct, 0) * 1000) / 1000 : null,
293
+ supply: info.supply,
294
+ decimals: info.decimals,
295
+ source: this.source,
296
+ fetchedAt,
297
+ };
298
+ }
299
+ }
@@ -0,0 +1,287 @@
1
+ // Solana JSON-RPC WebSocket subscription client (SPEC §3.9 fast lane, task-1312). Read-only.
2
+ // accountSubscribe(pubkey, { encoding: 'base64', commitment: 'processed' })
3
+ // -> accountNotification { params: { subscription, result: { context:{slot}, value:{ data:[b64,'base64'] } } } }
4
+ //
5
+ // One connection multiplexes every watched account's subscription (SPEC §3.9.4: "multiplex all watchlist
6
+ // subs on one connection"). The price is computed on-chain from the account bytes by the caller (the
7
+ // fast lane decodes a pump.fun bonding curve), so this client is a pure transport: it owns the socket,
8
+ // the desired-subscription set, reconnect with doubling backoff, and resubscribe on reopen (a reconnect
9
+ // resets the server's subscription ids). It reports health under `helius-ws` (keyed) or `solana-ws`
10
+ // (keyless public RPC), meters streamed bytes for the Helius credit budget, and records/replays a session
11
+ // through the same handler (tests, --replay). The URL may carry an api-key: it is never logged — every
12
+ // error/health string goes through redactSecrets().
13
+ import { systemClock } from "../clock.js";
14
+ import { redactSecrets } from "../rpc-config.js";
15
+ export class SolanaWsClient {
16
+ url;
17
+ source;
18
+ make;
19
+ clock;
20
+ health;
21
+ opts;
22
+ commitment;
23
+ ws = null;
24
+ /** Accounts the caller wants (persists across reconnects). */
25
+ desired = new Set();
26
+ /** server subId -> account (active subscriptions on the current socket). */
27
+ bySubId = new Map();
28
+ /** request id -> account (subscribe acks still pending on the current socket). */
29
+ pending = new Map();
30
+ nextReqId = 1;
31
+ status = { state: 'idle', attempts: 0, failures: 0, desired: 0, active: 0, messages: 0, notifications: 0, bytes: 0, connectedAt: null, lastMessageAt: null, lastError: null, nextRetryMs: null };
32
+ stopped = false;
33
+ startedAt = 0;
34
+ /** Aborts a pending reconnect-backoff sleep so stop() never leaves a timer holding the loop (task-1311). */
35
+ retryAbort = null;
36
+ constructor(opts) {
37
+ this.opts = opts;
38
+ this.url = opts.url;
39
+ this.source = opts.source ?? 'solana-ws';
40
+ this.clock = opts.clock ?? systemClock;
41
+ this.health = opts.health;
42
+ this.commitment = opts.commitment ?? 'processed';
43
+ const g = globalThis;
44
+ this.make = opts.ws ?? ((url) => {
45
+ if (!g.WebSocket)
46
+ throw new Error('WebSocket is not available in this runtime (Node 22+ required)');
47
+ return new g.WebSocket(url);
48
+ });
49
+ }
50
+ getStatus() {
51
+ return { ...this.status, desired: this.desired.size, active: this.bySubId.size };
52
+ }
53
+ /** Number of accounts with a live server subscription right now. */
54
+ get activeCount() {
55
+ return this.bySubId.size;
56
+ }
57
+ /** True when this specific account has an acked server subscription on the current socket. */
58
+ isActive(account) {
59
+ if (this.status.state !== 'open')
60
+ return false;
61
+ for (const acct of this.bySubId.values())
62
+ if (acct === account)
63
+ return true;
64
+ return false;
65
+ }
66
+ /** Accounts the caller wants subscribed (the cap the fast lane enforces applies to this set). */
67
+ get desiredCount() {
68
+ return this.desired.size;
69
+ }
70
+ start() {
71
+ if (this.status.state !== 'idle' && this.status.state !== 'stopped')
72
+ return;
73
+ this.stopped = false;
74
+ this.startedAt = this.clock.now();
75
+ if (this.opts.record)
76
+ this.opts.record.startedAt = new Date(this.startedAt).toISOString();
77
+ void this.connect();
78
+ }
79
+ stop() {
80
+ this.stopped = true;
81
+ this.retryAbort?.abort(); // cancel a pending backoff sleep so the process can exit at once (task-1311)
82
+ this.set({ state: 'stopped', nextRetryMs: null });
83
+ this.bySubId.clear();
84
+ this.pending.clear();
85
+ const ws = this.ws;
86
+ this.ws = null;
87
+ try {
88
+ ws?.close(1000, 'dexterm stop');
89
+ }
90
+ catch { /* already closed */ }
91
+ }
92
+ /** Add an account to the desired set and subscribe it now if the socket is open. Idempotent. */
93
+ subscribe(account) {
94
+ if (this.desired.has(account))
95
+ return;
96
+ this.desired.add(account);
97
+ this.set({});
98
+ if (this.ws && this.status.state === 'open')
99
+ this.sendSubscribe(this.ws, account);
100
+ }
101
+ /** Remove an account; unsubscribe it on the server if it has a live id. Idempotent. */
102
+ unsubscribe(account) {
103
+ if (!this.desired.delete(account))
104
+ return;
105
+ for (const [subId, acct] of this.bySubId) {
106
+ if (acct !== account)
107
+ continue;
108
+ this.bySubId.delete(subId);
109
+ if (this.ws && this.status.state === 'open') {
110
+ try {
111
+ this.ws.send(JSON.stringify({ jsonrpc: '2.0', id: this.nextReqId++, method: 'accountUnsubscribe', params: [subId] }));
112
+ }
113
+ catch { /* socket will drop */ }
114
+ }
115
+ }
116
+ for (const [id, acct] of this.pending)
117
+ if (acct === account)
118
+ this.pending.delete(id);
119
+ this.set({});
120
+ }
121
+ /** Replace the desired set wholesale (add new, drop missing). */
122
+ setAccounts(accounts) {
123
+ const next = new Set(accounts);
124
+ for (const a of [...this.desired])
125
+ if (!next.has(a))
126
+ this.unsubscribe(a);
127
+ for (const a of next)
128
+ this.subscribe(a);
129
+ }
130
+ set(patch) {
131
+ this.status = { ...this.status, ...patch, desired: this.desired.size, active: this.bySubId.size };
132
+ this.opts.onStatus?.(this.getStatus());
133
+ }
134
+ sendSubscribe(ws, account) {
135
+ const id = this.nextReqId++;
136
+ this.pending.set(id, account);
137
+ const text = JSON.stringify({ jsonrpc: '2.0', id, method: 'accountSubscribe', params: [account, { encoding: 'base64', commitment: this.commitment }] });
138
+ try {
139
+ ws.send(text);
140
+ }
141
+ catch {
142
+ this.pending.delete(id);
143
+ }
144
+ this.opts.record?.sent.push(text);
145
+ }
146
+ async connect() {
147
+ if (this.stopped)
148
+ return;
149
+ this.set({ state: this.status.attempts === 0 ? 'connecting' : 'reconnecting', attempts: this.status.attempts + 1, nextRetryMs: null });
150
+ this.bySubId.clear();
151
+ this.pending.clear();
152
+ let ws;
153
+ try {
154
+ ws = this.make(this.url);
155
+ }
156
+ catch (err) {
157
+ this.fail(err instanceof Error ? err.message : String(err));
158
+ return this.retry();
159
+ }
160
+ this.ws = ws;
161
+ let opened = false;
162
+ ws.addEventListener('open', () => {
163
+ if (this.ws !== ws)
164
+ return;
165
+ opened = true;
166
+ this.set({ state: 'open', failures: 0, connectedAt: this.clock.now(), lastError: null });
167
+ // Resubscribe every desired account: a reconnect reset the server's subscription ids.
168
+ for (const account of this.desired)
169
+ this.sendSubscribe(ws, account);
170
+ });
171
+ ws.addEventListener('message', (ev) => {
172
+ if (this.ws !== ws)
173
+ return;
174
+ const text = typeof ev.data === 'string' ? ev.data : ev.data instanceof ArrayBuffer ? Buffer.from(ev.data).toString('utf8') : String(ev.data ?? '');
175
+ this.handle(text);
176
+ });
177
+ ws.addEventListener('error', (ev) => {
178
+ if (this.ws !== ws)
179
+ return;
180
+ this.set({ lastError: redactSecrets(ev.message ?? 'websocket error') });
181
+ });
182
+ ws.addEventListener('close', (ev) => {
183
+ if (this.ws !== ws)
184
+ return;
185
+ this.ws = null;
186
+ this.bySubId.clear();
187
+ this.pending.clear();
188
+ if (this.stopped)
189
+ return;
190
+ const reason = `closed ${ev.code ?? '?'}${ev.reason ? ` ${ev.reason}` : ''}${opened ? '' : ' before open'}`;
191
+ this.fail(reason);
192
+ void this.retry();
193
+ });
194
+ }
195
+ fail(message) {
196
+ const msg = redactSecrets(message);
197
+ this.health?.record(this.source, { ok: false, error: msg });
198
+ this.set({ failures: this.status.failures + 1, lastError: msg });
199
+ }
200
+ async retry() {
201
+ if (this.stopped)
202
+ return;
203
+ const base = this.opts.baseBackoffMs ?? 1_000;
204
+ const max = this.opts.maxBackoffMs ?? 30_000;
205
+ const wait = Math.min(max, base * 2 ** Math.max(0, this.status.failures - 1));
206
+ this.set({ state: 'reconnecting', nextRetryMs: wait });
207
+ this.retryAbort = new AbortController();
208
+ await this.clock.sleep(wait, this.retryAbort.signal);
209
+ if (this.stopped)
210
+ return; // stop() fired during the backoff: do not reconnect
211
+ await this.connect();
212
+ }
213
+ /** Parse one frame: subscribe ack, account notification, or ignorable. Shared by live and replay. */
214
+ handle(text, receivedAt = this.clock.now()) {
215
+ const bytes = Buffer.byteLength(text, 'utf8');
216
+ this.opts.record?.messages.push({ tMs: Math.max(0, receivedAt - this.startedAt), text });
217
+ this.status.messages += 1;
218
+ this.status.bytes += bytes;
219
+ this.status.lastMessageAt = receivedAt;
220
+ if (bytes > 0)
221
+ this.opts.onBytes?.(bytes);
222
+ let j;
223
+ try {
224
+ j = JSON.parse(text);
225
+ }
226
+ catch {
227
+ this.set({});
228
+ return null;
229
+ }
230
+ if (!j || typeof j !== 'object') {
231
+ this.set({});
232
+ return null;
233
+ }
234
+ const o = j;
235
+ // subscribe ack: { id, result: <subId number> }
236
+ if (typeof o.id === 'number' && typeof o.result === 'number' && this.pending.has(o.id)) {
237
+ const account = this.pending.get(o.id);
238
+ this.pending.delete(o.id);
239
+ if (this.desired.has(account))
240
+ this.bySubId.set(o.result, account);
241
+ this.set({});
242
+ return null;
243
+ }
244
+ if (o.method === 'accountNotification' && o.params) {
245
+ const account = typeof o.params.subscription === 'number' ? this.bySubId.get(o.params.subscription) : undefined;
246
+ const data = o.params.result?.value?.data;
247
+ const base64 = Array.isArray(data) ? data[0] : typeof data === 'string' ? data : null;
248
+ if (account && base64) {
249
+ const slot = typeof o.params.result?.context?.slot === 'number' ? o.params.result.context.slot : null;
250
+ const update = { account, slot, base64, receivedAt, bytes };
251
+ this.status.notifications += 1;
252
+ this.health?.record(this.source, { ok: true, latencyMs: 0 });
253
+ this.set({});
254
+ this.opts.onAccount?.(update);
255
+ return update;
256
+ }
257
+ }
258
+ this.set({});
259
+ return null;
260
+ }
261
+ /** Replay a recorded session through the same handler. `speed` 0 = instant, 1 = recorded gaps. */
262
+ async replay(rec, speed = 0) {
263
+ this.startedAt = this.clock.now();
264
+ // Seed the subscription map from the recorded subscribe frames so notifications resolve.
265
+ for (const sent of rec.sent) {
266
+ try {
267
+ const m = JSON.parse(sent);
268
+ if (typeof m.id === 'number' && Array.isArray(m.params) && typeof m.params[0] === 'string') {
269
+ this.desired.add(m.params[0]);
270
+ this.pending.set(m.id, m.params[0]);
271
+ }
272
+ }
273
+ catch { /* ignore */ }
274
+ }
275
+ this.set({ state: 'open', connectedAt: this.startedAt, attempts: 1 });
276
+ let last = 0;
277
+ for (const m of rec.messages) {
278
+ if (this.stopped)
279
+ break;
280
+ if (speed > 0 && m.tMs > last)
281
+ await this.clock.sleep((m.tMs - last) / speed);
282
+ last = m.tMs;
283
+ this.handle(m.text, this.clock.now());
284
+ }
285
+ return rec.messages.length;
286
+ }
287
+ }