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,82 @@
1
+ // @dexterm/data — "paste a CA or a coin link anywhere" (task-1401, building on the task-1388 GO-TO parser). Shared by
2
+ // the CLI (go-to prompt, analyst chat, the screener `/` line, --mint) and the MCP server (a link passed as `mint`).
3
+ //
4
+ // parseMintTarget (task-1388) pulls a base58 mint out of a bare address or a dexscreener / pump.fun / solscan URL.
5
+ // parseCoinTarget adds what the other inputs need: WHICH site the link came from (a dexscreener.com/solana/<addr>
6
+ // link usually names the POOL, so it is resolved to its coin before the token screen opens) and the REST of the
7
+ // text, so "is <CA> a rug?" in the analyst chat opens the coin and asks the question about it.
8
+ // Pure and total: never throws, only ever returns a validated base58 string.
9
+ /** A full base58 Solana mint: 32–44 chars, no 0/O/I/l. Same shape `--mint` validates. */
10
+ export const MINT_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
11
+ /**
12
+ * task-1388: turn a pasted/typed go-to target into a mint, or null when there is none. Accepts a bare base58
13
+ * mint, or a dexscreener / pump.fun / solscan URL (or any text) by pulling the first base58 run of mint length.
14
+ * Base58 excludes 0, O, I and l, so splitting on every non-base58 character also splits on them and on the URL
15
+ * punctuation (/, :, ., ?, =, &), leaving each path/query segment as its own token; a real mint is never cut.
16
+ */
17
+ export function parseMintTarget(raw) {
18
+ const s = raw.trim();
19
+ if (!s)
20
+ return null;
21
+ if (MINT_RE.test(s))
22
+ return s;
23
+ for (const tok of s.split(/[^1-9A-HJ-NP-Za-km-z]+/))
24
+ if (MINT_RE.test(tok))
25
+ return tok;
26
+ return null;
27
+ }
28
+ const HOSTS = [
29
+ [/(^|\.)dexscreener\.com$/, 'dexscreener'],
30
+ [/(^|\.)pump\.fun$/, 'pumpfun'],
31
+ [/(^|\.)solscan\.io$/, 'solscan'],
32
+ [/(^|\.)birdeye\.so$/, 'birdeye'],
33
+ ];
34
+ const URLISH = /^(?:https?:\/\/)?(?:www\.)?([a-z0-9-]+(?:\.[a-z0-9-]+)+)(\/\S*)$/i;
35
+ /**
36
+ * Find the first coin address in free text: a link with an address in its path (its host decides `via`), else a
37
+ * bare base58 address word. `rest` is the remaining text. Null when the text names no address.
38
+ */
39
+ export function parseCoinTarget(raw) {
40
+ const s = String(raw ?? '').trim().slice(0, 800);
41
+ if (!s)
42
+ return null;
43
+ const words = s.split(/\s+/);
44
+ const restWithout = (i) => words.filter((_, k) => k !== i).join(' ').trim();
45
+ for (let i = 0; i < words.length; i++) {
46
+ const m = URLISH.exec(words[i]);
47
+ if (!m)
48
+ continue;
49
+ const address = parseMintTarget(m[2]);
50
+ if (!address)
51
+ continue;
52
+ const host = m[1].toLowerCase();
53
+ return { address, via: HOSTS.find(([re]) => re.test(host))?.[1] ?? 'link', rest: restWithout(i) };
54
+ }
55
+ for (let i = 0; i < words.length; i++) {
56
+ // a bare address, allowing surrounding punctuation ("<CA>?", "(CA)", "CA,")
57
+ const core = words[i].replace(/^[^1-9A-HJ-NP-Za-km-z]+|[^1-9A-HJ-NP-Za-km-z]+$/g, '');
58
+ if (MINT_RE.test(core))
59
+ return { address: core, via: 'address', rest: restWithout(i) };
60
+ }
61
+ return null;
62
+ }
63
+ /** True when the text left after the address asks nothing ("", "open", "check this", "?"): just open the coin. */
64
+ export function isBareOpen(rest) {
65
+ return /^(?:(?:open|go|to|show|me|check|look|at|this|that|one|the|ca|token|coin|pls|please|here|it)\b|[\s?.!:,;\-–—])*$/i.test(rest.trim());
66
+ }
67
+ /**
68
+ * Resolve a target to the coin's mint. A dexscreener link's address is looked up as a pair first (one keyless
69
+ * request through the limiter); when DexScreener has no such pair, or the lookup fails (offline, a replay without
70
+ * it), the address is used as the mint. Every other target is already a mint.
71
+ */
72
+ export async function resolveCoinTarget(t, dl) {
73
+ if (t.via !== 'dexscreener' || !dl)
74
+ return { mint: t.address, fromPair: false };
75
+ try {
76
+ const coin = await dl.dexscreener.pairCoin(t.address);
77
+ if (coin && MINT_RE.test(coin))
78
+ return { mint: coin, fromPair: coin !== t.address };
79
+ }
80
+ catch { /* fall through: treat the address as the mint */ }
81
+ return { mint: t.address, fromPair: false };
82
+ }
@@ -0,0 +1,137 @@
1
+ // Helius credit meter (task-1298; inventory 2026-10-06 §0.1, §5). The Free plan has 1M credits a
2
+ // month (docs/helius/pricing.md). We budget it per day (1M / 30), count every request we send and
3
+ // expose three levels: `ok`, `warn` at 50 % of the day, `degraded` at 90 % (the data layer then
4
+ // switches Helius off for the day and the keyless sources take over). A hard per-run cap bounds
5
+ // what one dexterm process can spend however long it runs. Day totals persist in the SQLite cache
6
+ // so restarts keep counting; the meter never sees, stores or prints the key.
7
+ import { systemClock } from "./clock.js";
8
+ export const HELIUS_MONTHLY_CREDITS = 1_000_000;
9
+ export const HELIUS_DAILY_BUDGET = Math.floor(HELIUS_MONTHLY_CREDITS / 30); // 33,333
10
+ /** Default hard cap per process run (credits). */
11
+ export const DEFAULT_RUN_CAP = 5_000;
12
+ export const WARN_PCT = 0.5;
13
+ export const DEGRADE_PCT = 0.9;
14
+ /**
15
+ * Credits per request by JSON-RPC method (docs/helius/pricing.md: standard RPC calls 1 credit,
16
+ * DAS / getProgramAccounts-class and Parsed Events 10). Unknown methods are charged 10 to be safe.
17
+ */
18
+ export const RPC_CREDIT_COST = {
19
+ getAccountInfo: 1,
20
+ getMultipleAccounts: 1,
21
+ getTokenLargestAccounts: 1,
22
+ getTokenSupply: 1,
23
+ getSlot: 1,
24
+ getAsset: 10,
25
+ getTokenAccounts: 10,
26
+ getTransactionsForAddress: 10,
27
+ };
28
+ export const UNKNOWN_METHOD_COST = 10;
29
+ /** WebSocket traffic: 2 credits per 0.1 MB (inventory §0.1), i.e. 20 credits per MB. */
30
+ export const WS_CREDITS_PER_MB = 20;
31
+ const NS = 'meter';
32
+ const DAY_TTL_MS = 2 * 86_400_000;
33
+ export class CreditMeter {
34
+ clock;
35
+ cache;
36
+ dayBudget;
37
+ runCap;
38
+ warnPct;
39
+ degradePct;
40
+ onLevel;
41
+ session = 0;
42
+ requests = 0;
43
+ bytes = 0;
44
+ lastLevel = 'ok';
45
+ dayKey = '';
46
+ dayTotal = 0;
47
+ constructor(opts = {}) {
48
+ this.clock = opts.clock ?? systemClock;
49
+ this.cache = opts.cache;
50
+ this.dayBudget = opts.dayBudget ?? HELIUS_DAILY_BUDGET;
51
+ this.runCap = opts.runCap ?? DEFAULT_RUN_CAP;
52
+ this.warnPct = opts.warnPct ?? WARN_PCT;
53
+ this.degradePct = opts.degradePct ?? DEGRADE_PCT;
54
+ this.onLevel = opts.onLevel;
55
+ this.roll();
56
+ }
57
+ /** YYYY-MM-DD in UTC for the clock's now (the plan's month is UTC-ish; a day boundary is good enough). */
58
+ static dayKeyFor(ms) {
59
+ return new Date(ms).toISOString().slice(0, 10);
60
+ }
61
+ roll() {
62
+ const key = CreditMeter.dayKeyFor(this.clock.now());
63
+ if (key === this.dayKey)
64
+ return;
65
+ this.dayKey = key;
66
+ const hit = this.cache?.get(NS, `helius:day:${key}`);
67
+ this.dayTotal = hit && typeof hit.value === 'number' ? hit.value : 0;
68
+ }
69
+ /** Credits a JSON-RPC method costs. */
70
+ static costOf(method) {
71
+ if (!method)
72
+ return UNKNOWN_METHOD_COST;
73
+ return RPC_CREDIT_COST[method] ?? UNKNOWN_METHOD_COST;
74
+ }
75
+ /** True while another request may be sent (below 90 % of the day and below the run cap). */
76
+ allow() {
77
+ this.roll();
78
+ return this.level() !== 'degraded';
79
+ }
80
+ /** Charge one request. Returns the new level. */
81
+ charge(credits) {
82
+ this.roll();
83
+ const c = Math.max(0, credits);
84
+ this.session += c;
85
+ this.requests += 1;
86
+ this.dayTotal += c;
87
+ this.cache?.set(NS, `helius:day:${this.dayKey}`, this.dayTotal, { ttlMs: DAY_TTL_MS, source: 'helius' });
88
+ return this.emit();
89
+ }
90
+ /** Charge streamed WebSocket bytes at the documented rate. */
91
+ chargeBytes(bytes) {
92
+ this.roll();
93
+ this.bytes += bytes;
94
+ return this.charge((bytes / 1_048_576) * WS_CREDITS_PER_MB);
95
+ }
96
+ level() {
97
+ if (this.session >= this.runCap)
98
+ return 'degraded';
99
+ const pct = this.dayBudget > 0 ? this.dayTotal / this.dayBudget : 0;
100
+ if (pct >= this.degradePct)
101
+ return 'degraded';
102
+ if (pct >= this.warnPct)
103
+ return 'warn';
104
+ return 'ok';
105
+ }
106
+ status() {
107
+ this.roll();
108
+ return {
109
+ level: this.level(),
110
+ session: Math.round(this.session * 100) / 100,
111
+ day: Math.round(this.dayTotal * 100) / 100,
112
+ dayBudget: this.dayBudget,
113
+ dayPct: this.dayBudget > 0 ? this.dayTotal / this.dayBudget : 0,
114
+ runCap: this.runCap,
115
+ runCapHit: this.session >= this.runCap,
116
+ requests: this.requests,
117
+ bytesStreamed: this.bytes,
118
+ dayKey: this.dayKey,
119
+ };
120
+ }
121
+ /** Header-sized summary: `helius 1.2K/33K credits · run 12`. */
122
+ label() {
123
+ const s = this.status();
124
+ const k = (n) => (n >= 1000 ? `${(n / 1000).toFixed(n >= 10_000 ? 0 : 1)}K` : String(Math.round(n)));
125
+ const pct = Math.round(s.dayPct * 100);
126
+ const tail = s.runCapHit ? ' · run cap' : s.level === 'degraded' ? ' · off' : s.level === 'warn' ? ' · warn' : '';
127
+ return `helius ${k(s.day)}/${k(s.dayBudget)} (${pct}%) · run ${k(s.session)}${tail}`;
128
+ }
129
+ emit() {
130
+ const level = this.level();
131
+ if (level !== this.lastLevel) {
132
+ this.lastLevel = level;
133
+ this.onLevel?.(level, this.status());
134
+ }
135
+ return level;
136
+ }
137
+ }
@@ -0,0 +1,97 @@
1
+ // Token-detail data (SPEC §4.3, D-06): candles, on-chain authorities, top-20
2
+ // holders and the RugCheck summary, each cached with its SPEC §3.4 TTL and
3
+ // stale-while-revalidate, so the detail screen and the MCP tools share one
4
+ // set of requests.
5
+ import { TTL } from "./cache.js";
6
+ const NS = { mint: 'rpc:mint', holders: 'rpc:holders', rug: 'rugcheck', pools: 'gt:pools', goplus: 'goplus', trades: 'gt:trades' };
7
+ /** Pools for a mint change rarely; one lookup per hour per mint. */
8
+ const POOLS_TTL = 3_600_000;
9
+ /** The trades tape shares GeckoTerminal's 10/min bucket with the candles: refresh at most every 20 s per pool. */
10
+ export const TRADES_TTL = 20_000;
11
+ export class TokenDetail {
12
+ cache;
13
+ gecko;
14
+ /** RPC pool: rotates across keyed + keyless endpoints with health scoring and 429 backoff (task-1309). */
15
+ pool;
16
+ /** Keyless holder aggregate (Jupiter) used when no RPC endpoint can serve the ranked list. */
17
+ holdersAggregate;
18
+ rug;
19
+ goplus;
20
+ candleStore;
21
+ constructor(deps) {
22
+ this.cache = deps.cache;
23
+ this.gecko = deps.gecko;
24
+ this.pool = deps.pool;
25
+ this.holdersAggregate = deps.holdersAggregate ?? null;
26
+ this.rug = deps.rugcheck;
27
+ this.goplus = deps.goplus ?? null;
28
+ this.candleStore = deps.candles;
29
+ }
30
+ /** Candles for a pool (pass DexScreener's `pair.pairAddress` to avoid a pool lookup). */
31
+ candles(pool, interval, req) {
32
+ return this.candleStore.get(pool, interval, req);
33
+ }
34
+ /** Most liquid GeckoTerminal pool for a mint (one request per hour per mint). */
35
+ async topPool(mint) {
36
+ const r = await this.cache.fetch(NS.pools, mint, () => this.gecko.tokenPools(mint), { ttlMs: POOLS_TTL, source: 'geckoterminal' });
37
+ return r.value[0] ?? null;
38
+ }
39
+ /** Mint/freeze authority truth from chain (TTL audit, 10 min). RPC pool: keyed endpoints first, keyless public RPC rotation behind them. */
40
+ mintInfo(mint) {
41
+ return this.cache.fetch(NS.mint, mint, () => this.pool.mintInfo(mint), { ttlMs: TTL.audit, source: this.pool.primary.source });
42
+ }
43
+ /**
44
+ * Top-20 holders (TTL holders, 60 s); reuses the cached mint info for supply/decimals. The RPC pool
45
+ * rotates across every endpoint; the result's `source` says which answered. When no endpoint can serve
46
+ * the ranked list (keyless: getTokenLargestAccounts is throttled/blocked everywhere), fall back to the
47
+ * Jupiter aggregate (`aggregateOnly`: holder count + top-10 share, empty list). Only when that also fails
48
+ * does the RPC error propagate, so the caller shows "unavailable".
49
+ */
50
+ async holders(mint) {
51
+ return this.cache.fetch(NS.holders, mint, async () => {
52
+ const info = (await this.mintInfo(mint)).value;
53
+ try {
54
+ return await this.pool.topHolders(mint, { info });
55
+ }
56
+ catch (rpcErr) {
57
+ const agg = await this.aggregateHolders(mint, info).catch(() => null);
58
+ if (agg)
59
+ return agg;
60
+ throw rpcErr;
61
+ }
62
+ }, { ttlMs: TTL.holders, source: this.pool.primary.source });
63
+ }
64
+ /** Build an aggregate-only TopHolders from the keyless Jupiter holder data; null when unavailable. */
65
+ async aggregateHolders(mint, info) {
66
+ if (!this.holdersAggregate)
67
+ return null;
68
+ const a = await this.holdersAggregate(mint);
69
+ if (!a || (a.holderCount === null && a.top10Pct === null))
70
+ return null;
71
+ return {
72
+ mint,
73
+ holders: [],
74
+ top10Pct: a.top10Pct,
75
+ supply: info.supply,
76
+ decimals: info.decimals,
77
+ source: a.source,
78
+ fetchedAt: a.fetchedAt,
79
+ aggregateOnly: true,
80
+ holderCount: a.holderCount,
81
+ };
82
+ }
83
+ rugcheck(mint) {
84
+ return this.cache.fetch(NS.rug, mint, () => this.rug.summary(mint), { ttlMs: TTL.rugcheck, source: 'rugcheck' });
85
+ }
86
+ /** GoPlus second opinion (TTL rugcheck, 10 min). Throws when the adapter is not configured. */
87
+ security(mint) {
88
+ const gp = this.goplus;
89
+ if (!gp)
90
+ throw new Error('goplus adapter not configured');
91
+ return this.cache.fetch(NS.goplus, mint, () => gp.tokenSecurity(mint), { ttlMs: TTL.rugcheck, source: 'goplus' });
92
+ }
93
+ /** Recent trades of the token's pool from GeckoTerminal (best keyless tape), newest first, 20 s TTL. */
94
+ trades(pool, mint) {
95
+ return this.cache.fetch(NS.trades, `${pool}:${mint ?? ''}`, async () => (await this.gecko.trades(pool, { mint })).trades, { ttlMs: TRADES_TTL, source: 'geckoterminal' });
96
+ }
97
+ }
@@ -0,0 +1,321 @@
1
+ // Fast lane: real-time on-chain price for the few tokens the user is actually looking at (SPEC §3.9,
2
+ // task-1312). A token's lane is a property of attention: the open token, the watchlist and fresh pump
3
+ // launches are priced by push (accountSubscribe on their pump.fun bonding-curve account, price computed
4
+ // on-chain, sub-second); everything else stays on the slow (poll) lane. This coordinator owns:
5
+ // - the fast set (capped — see FAST_LANE_CAP) and each mint's resolved PriceAccount,
6
+ // - one shared SolanaWsClient multiplexing every subscription on one connection,
7
+ // - an initial seed (one batched getMultipleAccounts through the RPC pool) so a price shows at once,
8
+ // - endpoint failover (keyed Helius-ws first, keyless public RPC ws as the fallback) and graceful
9
+ // degradation to the slow lane when no stream is available or the Helius credit budget is spent,
10
+ // - honest lane/badge state: laneFor()/badgeFor() report POLL whenever the stream is not actually
11
+ // delivering that token (down, not pump.fun, migrated, or over the cap).
12
+ //
13
+ // Price USD comes from an injected SOL/USD getter (the slow lane already fetches it); the on-chain decode
14
+ // only yields SOL. Never logs an endpoint URL (may carry a key).
15
+ import { systemClock } from "./clock.js";
16
+ import { decodeBondingCurve } from "./sources/pump-curve.js";
17
+ import { isPumpMint, pumpBondingCurve } from "./sources/pda.js";
18
+ import { SolanaWsClient } from "./sources/solana-ws.js";
19
+ /**
20
+ * Max simultaneous fast-lane subscriptions. Chosen for the free tier: one shared WS connection
21
+ * multiplexes them (SPEC §3.9.4), each subscription only fires on an account change (cheap), the Helius
22
+ * credit meter bounds total spend regardless, and the keyless public RPC WS has tight undocumented
23
+ * limits — so a conservative cap of 24 comfortably covers the open token + a 5–20 watchlist + a few
24
+ * fresh launches. Tokens past the cap simply stay on the slow lane (honest POLL badge), never dropped.
25
+ */
26
+ export const FAST_LANE_CAP = 24;
27
+ /** How long after the last push a token is still considered "streaming" for the LIVE badge. */
28
+ export const LIVE_FRESHNESS_MS = 90_000;
29
+ /** Rotate to the next endpoint after this many consecutive WS failures. */
30
+ const ROTATE_AFTER_FAILURES = 3;
31
+ export class FastLane {
32
+ opts;
33
+ clock;
34
+ cap;
35
+ tracked = new Map();
36
+ byAccount = new Map(); // price account -> mint
37
+ endpoints;
38
+ epIndex = 0;
39
+ ws = null;
40
+ started = false;
41
+ switching = false;
42
+ lastError = null;
43
+ constructor(opts) {
44
+ if (opts.endpoints.length === 0)
45
+ throw new Error('FastLane needs at least one WS endpoint');
46
+ this.opts = opts;
47
+ this.clock = opts.clock ?? systemClock;
48
+ this.cap = opts.cap ?? FAST_LANE_CAP;
49
+ this.endpoints = opts.endpoints;
50
+ }
51
+ get endpoint() {
52
+ return this.endpoints[this.epIndex];
53
+ }
54
+ /** Attach the stream and subscribe whatever is already tracked. Idempotent. */
55
+ start() {
56
+ if (this.started)
57
+ return;
58
+ this.started = true;
59
+ this.openWs();
60
+ }
61
+ stop() {
62
+ this.started = false;
63
+ this.ws?.stop();
64
+ this.ws = null;
65
+ this.emit();
66
+ }
67
+ openWs() {
68
+ const ep = this.endpoint;
69
+ const meter = ep.keyed ? this.opts.credits ?? null : null;
70
+ this.ws = new SolanaWsClient({
71
+ url: ep.url,
72
+ source: ep.source,
73
+ clock: this.clock,
74
+ health: this.opts.health,
75
+ ws: this.opts.ws,
76
+ onAccount: (u) => this.onAccount(u),
77
+ onBytes: meter ? (b) => { meter.chargeBytes(b); this.maybeDegrade(); } : undefined,
78
+ onStatus: (s) => this.onWsStatus(s),
79
+ });
80
+ this.ws.start();
81
+ // (Re)subscribe everything already tracked on the new socket.
82
+ this.ws.setAccounts([...this.byAccount.keys()]);
83
+ // Seed an initial price for everything tracked (so a value shows before the first push).
84
+ void this.seed([...this.tracked.keys()]);
85
+ this.emit();
86
+ }
87
+ onWsStatus(s) {
88
+ if (this.switching || s.state === 'stopped')
89
+ return; // ignore events we caused while swapping endpoints
90
+ if (s.lastError)
91
+ this.lastError = s.lastError;
92
+ // Endpoint keeps failing: fail over to the next one (keyed → public keyless).
93
+ if (s.failures >= ROTATE_AFTER_FAILURES && this.endpoints.length > 1)
94
+ this.rotate('repeated ws failures');
95
+ this.emit();
96
+ }
97
+ /** Swap to `index`, preserving the desired subscription set. Reentrancy-guarded (openWs/stop emit). */
98
+ switchTo(index) {
99
+ if (index === this.epIndex || index < 0 || index >= this.endpoints.length)
100
+ return;
101
+ this.switching = true;
102
+ try {
103
+ this.epIndex = index;
104
+ this.ws?.stop();
105
+ this.ws = null;
106
+ if (this.started)
107
+ this.openWs();
108
+ }
109
+ finally {
110
+ this.switching = false;
111
+ }
112
+ this.emit();
113
+ }
114
+ /** Switch to the next endpoint, preserving the desired subscription set. */
115
+ rotate(_reason) {
116
+ if (this.endpoints.length <= 1)
117
+ return;
118
+ this.switchTo((this.epIndex + 1) % this.endpoints.length);
119
+ }
120
+ /** On Helius credit exhaustion, drop to the first keyless endpoint so the lane keeps streaming free. */
121
+ maybeDegrade() {
122
+ const credits = this.opts.credits;
123
+ if (this.switching || !credits || !this.endpoint.keyed)
124
+ return;
125
+ if (credits.level() !== 'degraded')
126
+ return;
127
+ const keyless = this.endpoints.findIndex((e) => !e.keyed);
128
+ if (keyless >= 0)
129
+ this.switchTo(keyless);
130
+ }
131
+ /**
132
+ * Resolve a mint's on-chain pricing account. A known bonding-curve (from a PumpPortal Create event) is
133
+ * used directly; otherwise a pump.fun mint's curve is derived as a PDA. Non-pump mints are `unknown`
134
+ * and stay on the slow lane.
135
+ */
136
+ resolvePriceAccount(mint, curveHint) {
137
+ if (curveHint)
138
+ return { mint, kind: 'pump-curve', curve: curveHint };
139
+ if (isPumpMint(mint)) {
140
+ try {
141
+ return { mint, kind: 'pump-curve', curve: pumpBondingCurve(mint) };
142
+ }
143
+ catch {
144
+ return { mint, kind: 'unknown' };
145
+ }
146
+ }
147
+ return { mint, kind: 'unknown' };
148
+ }
149
+ /**
150
+ * Add a mint to the fast set. Returns true when it was promoted to the fast lane (streamable and within
151
+ * the cap), false when it must stay on the slow lane. Idempotent for an already-tracked mint.
152
+ */
153
+ track(mint, curveHint) {
154
+ const existing = this.tracked.get(mint);
155
+ if (existing)
156
+ return existing.priceAccount.kind !== 'unknown' && !existing.migrated;
157
+ const priceAccount = this.resolvePriceAccount(mint, curveHint);
158
+ if (priceAccount.kind === 'unknown' || !priceAccount.curve)
159
+ return false;
160
+ if (this.tracked.size >= this.cap)
161
+ return false; // over the cap: stay slow, never drop
162
+ const t = { mint, priceAccount, lastTickAt: null, lastPrice: null, migrated: false };
163
+ this.tracked.set(mint, t);
164
+ this.byAccount.set(priceAccount.curve, mint);
165
+ this.ws?.subscribe(priceAccount.curve);
166
+ if (this.started)
167
+ void this.seed([mint]);
168
+ this.emit();
169
+ return true;
170
+ }
171
+ untrack(mint) {
172
+ const t = this.tracked.get(mint);
173
+ if (!t)
174
+ return;
175
+ this.tracked.delete(mint);
176
+ if (t.priceAccount.curve) {
177
+ this.byAccount.delete(t.priceAccount.curve);
178
+ this.ws?.unsubscribe(t.priceAccount.curve);
179
+ }
180
+ this.emit();
181
+ }
182
+ /**
183
+ * Replace the fast set wholesale (open token + watchlist + fresh launches). `curveHints` carries known
184
+ * curves (e.g. from Create events) so no PDA derivation is needed. Order matters when over the cap:
185
+ * earlier mints win (callers pass the open token first).
186
+ */
187
+ setTracked(mints, curveHints) {
188
+ const next = [...mints];
189
+ const nextSet = new Set(next);
190
+ for (const mint of [...this.tracked.keys()])
191
+ if (!nextSet.has(mint))
192
+ this.untrack(mint);
193
+ for (const mint of next)
194
+ this.track(mint, curveHints?.get(mint) ?? null);
195
+ }
196
+ /** Seed first prices for `mints` via one batched getMultipleAccounts (through the pool). Best-effort. */
197
+ async seed(mints) {
198
+ const curves = [];
199
+ for (const m of mints) {
200
+ const t = this.tracked.get(m);
201
+ if (t?.priceAccount.curve)
202
+ curves.push(t.priceAccount.curve);
203
+ }
204
+ if (curves.length === 0)
205
+ return;
206
+ let data;
207
+ try {
208
+ ({ data } = await this.opts.rpcPool.accountsRaw(curves, 'processed'));
209
+ }
210
+ catch {
211
+ return; // the first push will supply a price
212
+ }
213
+ for (const [account, base64] of data) {
214
+ if (base64)
215
+ this.ingest(account, null, base64, this.clock.now(), true);
216
+ }
217
+ }
218
+ onAccount(u) {
219
+ this.ingest(u.account, u.slot, u.base64, u.receivedAt, false);
220
+ }
221
+ /** Decode a curve account to a PricePoint and emit it. `seed` marks the initial (non-push) value. */
222
+ ingest(account, slot, base64, t, seed) {
223
+ const mint = this.byAccount.get(account);
224
+ if (!mint)
225
+ return;
226
+ const tracked = this.tracked.get(mint);
227
+ if (!tracked)
228
+ return;
229
+ const curve = decodeBondingCurve(base64);
230
+ if (!curve || curve.priceSol == null)
231
+ return;
232
+ if (curve.complete) {
233
+ // Migrated to a DEX pool: the bonding-curve price is stale. Demote to the slow lane honestly;
234
+ // AMM-pool on-chain pricing is a later card (SPEC §3.9.4).
235
+ tracked.migrated = true;
236
+ this.emit();
237
+ return;
238
+ }
239
+ const solUsd = this.opts.solUsd();
240
+ const point = {
241
+ mint,
242
+ priceSol: curve.priceSol,
243
+ priceUsd: solUsd != null && Number.isFinite(solUsd) ? curve.priceSol * solUsd : null,
244
+ slot,
245
+ t,
246
+ source: 'onchain-curve',
247
+ lane: 'fast',
248
+ };
249
+ tracked.lastPrice = point;
250
+ if (!seed)
251
+ tracked.lastTickAt = t;
252
+ this.opts.onPrice?.(point);
253
+ if (!seed)
254
+ this.emit();
255
+ }
256
+ /** The lane a mint is on right now (honest): fast only when its curve is actually streaming. */
257
+ laneFor(mint) {
258
+ return this.isLive(mint) ? 'fast' : 'slow';
259
+ }
260
+ /** Honest per-row/panel badge: LIVE when a fresh on-chain push is flowing, else POLL. */
261
+ badgeFor(mint) {
262
+ return this.isLive(mint) ? 'LIVE' : 'POLL';
263
+ }
264
+ isLive(mint) {
265
+ const t = this.tracked.get(mint);
266
+ if (!t || t.migrated || t.priceAccount.kind === 'unknown' || !t.priceAccount.curve)
267
+ return false;
268
+ if (!this.ws || this.ws.getStatus().state !== 'open')
269
+ return false;
270
+ // Keyed stream with the credit budget spent is not actually delivering: POLL.
271
+ if (this.endpoint.keyed && this.opts.credits && this.opts.credits.level() === 'degraded')
272
+ return false;
273
+ // This specific curve must have a live server subscription (its own ack received).
274
+ if (!this.ws.isActive(t.priceAccount.curve))
275
+ return false;
276
+ // LIVE means a real on-chain push arrived recently; a seed-only or stale value is honestly POLL.
277
+ return t.lastTickAt != null && this.clock.now() - t.lastTickAt <= LIVE_FRESHNESS_MS;
278
+ }
279
+ /** Last fast-lane price for a mint, or null. */
280
+ price(mint) {
281
+ return this.tracked.get(mint)?.lastPrice ?? null;
282
+ }
283
+ /**
284
+ * Last fast-lane USD price, recomputed from the stored on-chain SOL price and the *current* SOL/USD —
285
+ * so a tick that arrived before SOL/USD was known still shows a USD value once the rate lands. Null
286
+ * when there is no tick yet or no SOL/USD.
287
+ */
288
+ priceUsd(mint) {
289
+ const last = this.tracked.get(mint)?.lastPrice;
290
+ if (!last)
291
+ return null;
292
+ const s = this.opts.solUsd();
293
+ if (s != null && Number.isFinite(s))
294
+ return last.priceSol * s;
295
+ return last.priceUsd;
296
+ }
297
+ isTracked(mint) {
298
+ return this.tracked.has(mint);
299
+ }
300
+ status() {
301
+ const ws = this.ws?.getStatus();
302
+ let live = 0;
303
+ for (const m of this.tracked.keys())
304
+ if (this.isLive(m))
305
+ live++;
306
+ return {
307
+ active: this.started && !!this.ws,
308
+ endpoint: this.endpoint.label,
309
+ keyed: this.endpoint.keyed,
310
+ wsState: ws?.state ?? 'idle',
311
+ subscriptions: ws?.active ?? 0,
312
+ tracked: this.tracked.size,
313
+ live,
314
+ failures: ws?.failures ?? 0,
315
+ lastError: this.lastError,
316
+ };
317
+ }
318
+ emit() {
319
+ this.opts.onStatus?.(this.status());
320
+ }
321
+ }