dexterm-cli 0.1.6 → 0.1.7

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 (331) hide show
  1. package/dist/packages/agents/src/schema.js +22 -1
  2. package/dist/packages/agents/src/story.js +11 -2
  3. package/dist/packages/agents/src/tiers.js +27 -2
  4. package/dist/packages/agents/src/tools.js +30 -2
  5. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  6. package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
  7. package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
  8. package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
  9. package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
  10. package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
  11. package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
  12. package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
  13. package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
  14. package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
  15. package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
  16. package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
  17. package/dist/packages/autotrade/src/account.js +74 -0
  18. package/dist/packages/autotrade/src/backtest.js +357 -0
  19. package/dist/packages/autotrade/src/basis-text.js +32 -0
  20. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  21. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  22. package/dist/packages/autotrade/src/chat/config.js +93 -0
  23. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  24. package/dist/packages/autotrade/src/chat/control.js +103 -0
  25. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  26. package/dist/packages/autotrade/src/chat/format.js +71 -0
  27. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  28. package/dist/packages/autotrade/src/chat/index.js +18 -0
  29. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  30. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  31. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  32. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  33. package/dist/packages/autotrade/src/chat/service.js +248 -0
  34. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  35. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  36. package/dist/packages/autotrade/src/engine.js +387 -0
  37. package/dist/packages/autotrade/src/execution-port.js +97 -0
  38. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  39. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  40. package/dist/packages/autotrade/src/fleet.js +639 -0
  41. package/dist/packages/autotrade/src/index.js +53 -0
  42. package/dist/packages/autotrade/src/ledger.js +108 -0
  43. package/dist/packages/autotrade/src/live-source.js +182 -0
  44. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  45. package/dist/packages/autotrade/src/monitor.js +190 -0
  46. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  47. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  48. package/dist/packages/autotrade/src/replay.js +198 -0
  49. package/dist/packages/autotrade/src/runner-config.js +99 -0
  50. package/dist/packages/autotrade/src/runner.js +872 -0
  51. package/dist/packages/autotrade/src/safety.js +243 -0
  52. package/dist/packages/autotrade/src/session.js +269 -0
  53. package/dist/packages/autotrade/src/sessions.js +503 -0
  54. package/dist/packages/autotrade/src/store.js +250 -0
  55. package/dist/packages/autotrade/src/strategy.js +256 -0
  56. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  57. package/dist/packages/autotrade/src/yolo.js +140 -0
  58. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  59. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  60. package/dist/packages/cli/src/ai-cmd.js +412 -0
  61. package/dist/packages/cli/src/analyst/backend.js +82 -15
  62. package/dist/packages/cli/src/analyst/codex.js +14 -9
  63. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  64. package/dist/packages/cli/src/analyst/session.js +5 -2
  65. package/dist/packages/cli/src/app-help-gen.js +44 -8
  66. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  67. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  68. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  69. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  70. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  71. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  72. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  73. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  74. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  75. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  76. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  77. package/dist/packages/cli/src/display.js +156 -17
  78. package/dist/packages/cli/src/doctor.js +62 -3
  79. package/dist/packages/cli/src/export.js +10 -2
  80. package/dist/packages/cli/src/index.js +1 -1
  81. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  82. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  83. package/dist/packages/cli/src/live/index.js +23 -89
  84. package/dist/packages/cli/src/live/screens/token-live.js +9 -6
  85. package/dist/packages/cli/src/live/trade.js +12 -457
  86. package/dist/packages/cli/src/live/watchlist.js +1 -1
  87. package/dist/packages/cli/src/main.js +147 -190
  88. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  89. package/dist/packages/cli/src/plan-cmd.js +1 -1
  90. package/dist/packages/cli/src/pnl-app.js +31 -21
  91. package/dist/packages/cli/src/pnl-card.js +158 -21
  92. package/dist/packages/cli/src/popup.js +22 -6
  93. package/dist/packages/cli/src/record-cmd.js +141 -20
  94. package/dist/packages/cli/src/screens/agent-hud.js +34 -2
  95. package/dist/packages/cli/src/screens/analyst-session.js +7 -4
  96. package/dist/packages/cli/src/screens/analyst.js +3 -2
  97. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  98. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  99. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  100. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  101. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  102. package/dist/packages/cli/src/screens/brain.js +37 -11
  103. package/dist/packages/cli/src/screens/overlay.js +5 -2
  104. package/dist/packages/cli/src/screens/plans.js +5 -17
  105. package/dist/packages/cli/src/screens/pnl-chip.js +9 -8
  106. package/dist/packages/cli/src/screens/positions.js +6 -28
  107. package/dist/packages/cli/src/screens/ticket.js +38 -205
  108. package/dist/packages/cli/src/screens/token.js +3 -3
  109. package/dist/packages/cli/src/state.js +25 -19
  110. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  111. package/dist/packages/cli/src/trade.js +3 -128
  112. package/dist/packages/cli/src/version.js +1 -1
  113. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  114. package/dist/packages/cli/src/wallet-command.js +8 -11
  115. package/dist/packages/cli/src/widgets.js +0 -12
  116. package/dist/packages/custom/src/config.js +20 -2
  117. package/dist/packages/custom/src/schema.js +35 -10
  118. package/dist/packages/custom/src/workspace.js +3 -3
  119. package/dist/packages/data/src/basis.js +191 -0
  120. package/dist/packages/data/src/detail.js +53 -2
  121. package/dist/packages/data/src/index.js +0 -14
  122. package/dist/packages/data/src/store.js +14 -5
  123. package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
  124. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  125. package/dist/packages/data/src/trade/bridge.js +4 -102
  126. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  127. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  128. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  129. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  130. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  131. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  132. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  133. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  134. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  135. package/dist/packages/data/src/trade/plans/index.js +0 -1
  136. package/dist/packages/data/src/trade/plans/service.js +3 -27
  137. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  138. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  139. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  140. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  141. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  142. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  143. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  144. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  145. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  146. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  147. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  148. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  149. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  150. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  151. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  152. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  153. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  154. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  155. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
  156. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  157. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  158. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  159. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  160. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  161. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +4 -1
  162. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  163. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  164. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  165. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  166. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  167. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  168. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  169. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  170. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  171. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  172. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  173. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  174. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  175. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  176. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  177. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  178. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  179. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  180. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  181. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  182. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  183. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  184. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  186. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  188. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  189. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  194. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  195. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +1 -1
  197. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  198. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  200. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +8 -6
  201. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  202. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  203. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  204. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  205. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  208. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  209. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  210. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  211. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  219. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  220. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  223. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  225. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  226. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  227. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  228. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  229. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  230. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  233. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  234. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +3 -4
  235. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  236. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +6 -7
  237. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  238. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  240. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  241. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  242. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  243. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  244. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +2 -1
  246. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  248. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +1 -1
  250. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  260. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  262. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  263. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  264. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  266. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  267. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  269. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  270. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  271. package/dist/packages/mcp/src/app-help.js +1 -1
  272. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  273. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  274. package/dist/packages/mcp/src/engine/config.js +159 -0
  275. package/dist/packages/mcp/src/engine/index.js +10 -0
  276. package/dist/packages/mcp/src/engine/loop.js +101 -0
  277. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  278. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  279. package/dist/packages/mcp/src/engine/probe.js +110 -0
  280. package/dist/packages/mcp/src/engine/reason.js +35 -0
  281. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  282. package/dist/packages/mcp/src/index.js +7 -1
  283. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  284. package/dist/packages/mcp/src/paper-agent.js +2 -0
  285. package/dist/packages/mcp/src/server.js +127 -19
  286. package/dist/packages/packs/src/backtest.js +3 -1
  287. package/dist/packages/popup/src/pages/chart-lib.js +134 -21
  288. package/dist/packages/popup/src/pages/chart.css +9 -0
  289. package/dist/packages/popup/src/pages/chart.html +3 -1
  290. package/dist/packages/popup/src/pages/chart.js +104 -7
  291. package/dist/packages/popup/src/protocol.js +2 -0
  292. package/dist/packages/popup/src/server.js +4 -0
  293. package/dist/packages/recorder/src/index.js +2 -0
  294. package/dist/packages/recorder/src/recorder.js +63 -6
  295. package/dist/packages/recorder/src/retention.js +100 -0
  296. package/dist/packages/recorder/src/schema.js +34 -1
  297. package/dist/packages/recorder/src/ticks.js +237 -0
  298. package/dist/packages/tui/src/index.js +1 -0
  299. package/dist/packages/tui/src/keymap.js +21 -1
  300. package/dist/packages/tui/src/markers.js +219 -0
  301. package/dist/packages/tui/src/widgets.js +149 -2
  302. package/package.json +2 -1
  303. package/dist/packages/data/src/trade/caps.js +0 -323
  304. package/dist/packages/data/src/trade/executor.js +0 -933
  305. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  306. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  307. package/dist/packages/data/src/trade/signer/client.js +0 -197
  308. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  309. package/dist/packages/data/src/trade/signer/host.js +0 -97
  310. package/dist/packages/data/src/trade/signer/main.js +0 -121
  311. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  312. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  313. package/dist/packages/data/src/trade/signer/service.js +0 -400
  314. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  315. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  316. package/dist/packages/data/src/trade/swap-build.js +0 -108
  317. package/dist/packages/data/src/trade/token-delta.js +0 -201
  318. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  319. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  320. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  321. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  322. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  323. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  324. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  325. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  326. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  327. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  328. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  329. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  330. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  331. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -1,220 +0,0 @@
1
- // task-1363 — the LIVE fire path for the plan engine: an Executor that fills through the REAL signer process
2
- // (devnet only, from the capped FAST WALLET). The engine is synchronous and tick-driven; the signer is a child
3
- // process answered asynchronously. This adapter bridges the two WITHOUT the engine ever waiting:
4
- //
5
- // quote(leg) first call starts `signer.fill(...)` (build + policy + simulate + USD pricing in the SIGNER)
6
- // and throws RpcOutage (the engine logs price.unusable/rpc-outage and retries on the next
7
- // fresh tick, no attempt consumed). Once the signer has answered, the next quote() returns a
8
- // Quote carrying the signer's verdict: policy/sim/caps refusals arrive as `simOk:false` with
9
- // the signer's own reason, so the engine journals `policy.refused` with that reason.
10
- // nextSignature() an INTENT id (`live_N`) the engine journals BEFORE anything is sent (CL-03).
11
- // submit(leg) starts `signer.confirm(hash)` — the signer re-checks caps, reserves USD, has the fast
12
- // wallet sign, verifies + re-simulates the bytes, submits and confirms — and returns
13
- // `send-unknown` at once: the engine marks the leg in flight and settles it by `lookup`.
14
- // lookup(intent) 'not-found' while the confirm runs; {confirmed, fill} / 'failed' once the signer answered;
15
- // a transport error at confirm stays 'not-found' (the engine expires it after 90 s, never
16
- // resends — the chain may still have it; the fast wallet is capped so exposure is bounded).
17
- //
18
- // What is real on devnet: the lamports the fill moves out of the fast wallet (fees of the wrap-and-unwrap
19
- // sized to the plan's SOL leg), the signature, the on-chain memo naming the plan leg, the signer's USD
20
- // per-trade/daily ledger. What is MODELLED: the token leg (Jupiter has no devnet, so no token changes hands);
21
- // the fill is booked from the planned USD at the fire-time price and labelled `kind:'devnet-fill'` +
22
- // `modelledTokens:true` in the journal. Nothing in this file signs, holds a key, or reaches mainnet: the signer
23
- // refuses `fill` off devnet and never loads a fast wallet there.
24
- import { RpcOutage } from "./executor.js";
25
- /** How long a signer build is reused before it is rebuilt (the signer's own plan TTL is 90 s). */
26
- export const LIVE_BUILD_TTL_MS = 60_000;
27
- /** Remote refusals the signer formats as `reason: message`; anything else from the client is a transport problem. */
28
- const REMOTE_REFUSAL = /^(network-mismatch|no-fast-wallet|build-failed|bad-request|panicked|no-wallet|signer-mismatch|plan-mismatch):\s*/;
29
- export class LivePlanExecutor {
30
- kind = 'live';
31
- signer;
32
- clock;
33
- solUsd;
34
- ttl;
35
- onChange;
36
- log;
37
- builds = new Map();
38
- pending = new Map();
39
- intents = 0;
40
- constructor(o) {
41
- this.signer = o.signer;
42
- this.clock = o.clock;
43
- this.solUsd = o.solUsd;
44
- this.ttl = o.buildTtlMs ?? LIVE_BUILD_TTL_MS;
45
- this.onChange = o.onChange ?? (() => { });
46
- this.log = o.log ?? (() => { });
47
- }
48
- /** The signer must be alive, on DEVNET, with a fast wallet loaded. Checked by the host before switching to live. */
49
- async ready() {
50
- if (!this.signer.alive)
51
- return { ok: false, why: 'the signer process is not available' };
52
- let status;
53
- try {
54
- status = await this.signer.status();
55
- }
56
- catch (e) {
57
- return { ok: false, why: `signer status: ${msg(e)}` };
58
- }
59
- if (status.network !== 'devnet')
60
- return { ok: false, why: `the signer is on ${status.network}; live plan fills run on devnet only` };
61
- if (status.panic || status.ledgerLocked)
62
- return { ok: false, why: 'live trading is panicked (dexterm wallet resume)' };
63
- let fast;
64
- try {
65
- fast = await this.signer.fast();
66
- }
67
- catch (e) {
68
- return { ok: false, why: `signer fast: ${msg(e)}` };
69
- }
70
- if (!fast.loaded || !fast.publicKey)
71
- return { ok: false, why: fast.reason ?? 'no fast wallet is loaded in the signer' };
72
- return { ok: true, network: 'devnet', fastWallet: fast.publicKey, balanceLamports: fast.balanceLamports };
73
- }
74
- /** One-line label for banners. */
75
- describe(fastWallet) { return `LIVE · DEVNET · fast wallet${fastWallet ? ` ${fastWallet.slice(0, 4)}…${fastWallet.slice(-4)}` : ''}`; }
76
- legId(req) { return `${req.plan.id}:${req.legKey}`; }
77
- /** The SOL leg in lamports for this leg (buy: USD / SOL-USD; sell: tokens × price / SOL-USD). Null without a SOL price. */
78
- lamportsFor(req) {
79
- const sol = this.solUsd();
80
- if (sol === null || !(sol > 0))
81
- return null;
82
- const usd = req.side === 'buy' ? (req.usd ?? 0) : req.tokenRaw !== null ? Number(req.tokenRaw) / 10 ** req.plan.decimals * req.priceUsd : 0;
83
- if (!(usd > 0))
84
- return null;
85
- return BigInt(Math.round(usd / sol * 1e9));
86
- }
87
- quote(req) {
88
- const id = this.legId(req);
89
- const now = this.clock.now();
90
- const b = this.builds.get(id);
91
- if (b && b.state === 'building')
92
- throw new RpcOutage();
93
- if (b && b.state === 'outage') {
94
- this.builds.delete(id);
95
- throw new RpcOutage();
96
- } // retried: a new build starts on the next quote
97
- if (b && b.state === 'refused') {
98
- this.builds.delete(id);
99
- return this.refusedQuote(req, b.error ?? 'build refused', b.at);
100
- }
101
- if (b && b.state === 'built' && b.view && now - b.at < this.ttl && b.view.expiresAt > now)
102
- return this.quoteFromView(req, b.view, b.at);
103
- if (b)
104
- this.builds.delete(id);
105
- // start a build
106
- const lamports = this.lamportsFor(req);
107
- if (lamports === null) {
108
- this.log(`live plans: no SOL/USD price yet to size ${id}; retrying`);
109
- throw new RpcOutage();
110
- }
111
- const build = { state: 'building', view: null, error: null, at: now, usd: req.usd, tokenRaw: req.tokenRaw, priceUsd: req.priceUsd };
112
- this.builds.set(id, build);
113
- void this.signer.fill({ ref: id, mint: req.plan.mint, symbol: req.plan.symbol, decimals: req.plan.decimals, side: req.side, lamports: lamports.toString() })
114
- .then((view) => { build.state = 'built'; build.view = view; build.at = this.clock.now(); this.onChange(); })
115
- .catch((e) => {
116
- const m = msg(e);
117
- if (REMOTE_REFUSAL.test(m)) {
118
- build.state = 'refused';
119
- build.error = m;
120
- }
121
- else {
122
- build.state = 'outage';
123
- build.error = m;
124
- this.log(`live plans: signer fill failed (${m}); retrying`);
125
- }
126
- this.onChange();
127
- });
128
- throw new RpcOutage();
129
- }
130
- refusedQuote(req, error, at) {
131
- const m = REMOTE_REFUSAL.exec(error);
132
- const reason = m ? m[1] : 'signer-refused';
133
- return {
134
- side: req.side, priceUsd: req.priceUsd, impactPct: 0, route: ['devnet-fill'], feeLamports: 0, tipLamports: 0,
135
- simOk: false, simError: error, refusal: { reason, message: error.replace(REMOTE_REFUSAL, '') }, usd: req.usd, tokenRaw: req.tokenRaw, at,
136
- };
137
- }
138
- /** The signer's verdict as a Quote: its policy / simulation / caps refusal (if any) travels with its own reason. */
139
- quoteFromView(req, v, at) {
140
- const bad = !v.policy.ok ? v.policy : !v.sim.ok ? { ok: false, reason: 'simulation-failed', message: v.sim.error ?? 'simulation failed' } : !v.precheck.ok ? v.precheck : null;
141
- return {
142
- side: req.side,
143
- // a devnet fill has no route, so the signer cannot imply a price; the tick's price stands (on a Jupiter
144
- // route the quote-implied price would come from the signer's view instead)
145
- priceUsd: req.priceUsd,
146
- impactPct: v.priceImpactPct ?? 0, route: [...v.route],
147
- feeLamports: Number(v.feeLamports), tipLamports: 0,
148
- simOk: bad === null, simError: bad ? `${bad.reason ?? 'refused'}: ${bad.message ?? ''}` : null,
149
- refusal: bad ? { reason: bad.reason ?? 'signer-refused', message: bad.message ?? 'refused by the signer' } : null,
150
- usd: req.usd, tokenRaw: req.tokenRaw, at,
151
- };
152
- }
153
- nextSignature() { return `live_${++this.intents}`; }
154
- submit(req) {
155
- const id = this.legId(req);
156
- const b = this.builds.get(id);
157
- this.builds.delete(id);
158
- if (!b || b.state !== 'built' || !b.view)
159
- return { kind: 'send-failed', signature: req.signature, error: 'no fresh signer plan for this leg (rebuilt on the next tick)', feeLamports: 0 };
160
- const view = b.view;
161
- const p = { legId: id, hash: view.hash, state: 'confirming', outcome: null, error: null, fill: null, signature: null };
162
- this.pending.set(req.signature, p);
163
- void this.signer.confirm(view.hash)
164
- .then((out) => {
165
- p.outcome = out;
166
- if (out.kind === 'filled') {
167
- p.state = 'filled';
168
- p.signature = out.signature;
169
- p.fill = this.modelFill(req, out);
170
- }
171
- else if (out.kind === 'refused' || out.kind === 'send-failed') {
172
- p.state = 'failed';
173
- p.error = out.kind === 'refused' ? `${out.reason}: ${out.message}` : out.error;
174
- p.signature = out.kind === 'send-failed' ? out.signature : null;
175
- }
176
- else {
177
- p.state = 'unknown';
178
- p.error = out.error;
179
- p.signature = out.signature;
180
- }
181
- this.onChange();
182
- })
183
- .catch((e) => { p.state = 'unknown'; p.error = msg(e); this.log(`live plans: confirm transport error (${p.error}); outcome unknown, never resent`); this.onChange(); });
184
- return { kind: 'send-unknown', signature: req.signature, error: 'live confirm in flight (fast wallet, devnet)', feeLamports: req.quote.feeLamports + req.quote.tipLamports };
185
- }
186
- /**
187
- * The fill the engine books. SOL: the real on-chain delta the signer reported (fees of the wrap-and-unwrap).
188
- * Tokens: MODELLED from the planned USD at the fire-time price (no token moves on devnet) and labelled so.
189
- */
190
- modelFill(req, out) {
191
- const dec = req.plan.decimals;
192
- const usd = req.usd ?? (req.tokenRaw !== null ? Number(req.tokenRaw) / 10 ** dec * req.quote.priceUsd : 0);
193
- const tokens = req.tokenRaw ?? BigInt(Math.round(usd / req.quote.priceUsd * 10 ** dec));
194
- return {
195
- tokenDelta: req.quote.side === 'buy' ? tokens : -tokens,
196
- solDelta: BigInt(out.fill.solDelta),
197
- usd, priceUsd: req.quote.priceUsd,
198
- kind: 'devnet-fill', signature: out.signature, modelledTokens: true, network: out.network,
199
- };
200
- }
201
- lookup(signature) {
202
- const p = this.pending.get(signature);
203
- if (!p)
204
- return 'not-found';
205
- if (p.state === 'filled' && p.fill)
206
- return { status: 'confirmed', fill: p.fill };
207
- if (p.state === 'failed')
208
- return { status: 'failed' };
209
- return 'not-found'; // confirming, or an unknown outcome: never resent, expires in the engine
210
- }
211
- chainFill(signature) { return this.pending.get(signature)?.fill ?? null; }
212
- /** Diagnostics for the TUI (what is building / in flight / settled). */
213
- snapshot() {
214
- return {
215
- builds: [...this.builds.entries()].map(([leg, b]) => ({ leg, state: b.state, error: b.error })),
216
- pending: [...this.pending.entries()].map(([intent, p]) => ({ leg: p.legId, intent, state: p.state, signature: p.signature, error: p.error })),
217
- };
218
- }
219
- }
220
- function msg(e) { return e instanceof Error ? e.message : String(e); }
@@ -1,229 +0,0 @@
1
- // dexterm T1 — the ONLY place that can submit a transaction (SPEC §6.1 steps 2/5). Kept out of the
2
- // read-only `sources/solana-rpc.ts` (which refuses non-read methods) so the broadcast path is small,
3
- // explicit and auditable. It simulates against the user's RPC, submits an already-signed transaction
4
- // (dexterm never signs), and confirms it. It holds no keys.
5
- //
6
- // DEVNET is the default RPC. Mainnet is only used when the caps allow it (--mainnet).
7
- import { systemClock } from "../clock.js";
8
- export const DEVNET_RPC = 'https://api.devnet.solana.com';
9
- export const MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
10
- /**
11
- * Hostnames that are devnet/testnet by construction. EXACT matches only (round 2, re-verify MEDIUM #5):
12
- * the old substring test took `https://mainnet.helius-rpc.com/?api-key=devnet-team-key` for devnet and
13
- * dropped every USD cap. Any other host — mainnet, a vendor URL, localhost, an unparseable string — is
14
- * classified MAINNET so the caps apply by default. A deployment that really runs a private devnet can
15
- * say so explicitly via `TradeRpc({ network: 'devnet' })`.
16
- */
17
- export const DEVNET_HOSTS = new Set([
18
- 'api.devnet.solana.com',
19
- 'api.testnet.solana.com',
20
- 'devnet.helius-rpc.com',
21
- ]);
22
- /**
23
- * DEFECT 4 / round 2: classify a trade from the RPC endpoint it will actually talk to, NOT from a
24
- * free-text "network" label and NOT from a substring. The URL is PARSED and only an exact known
25
- * devnet/testnet hostname is devnet. Everything else (including an empty or malformed URL) is mainnet.
26
- */
27
- export function rpcNetwork(url, explicit) {
28
- if (explicit === 'devnet' || explicit === 'mainnet')
29
- return explicit;
30
- if (typeof url !== 'string' || url.length === 0)
31
- return 'mainnet';
32
- let host;
33
- try {
34
- const u = new URL(url);
35
- if (u.protocol !== 'https:' && u.protocol !== 'http:' && u.protocol !== 'wss:' && u.protocol !== 'ws:')
36
- return 'mainnet';
37
- host = u.hostname.toLowerCase();
38
- }
39
- catch {
40
- return 'mainnet';
41
- }
42
- return DEVNET_HOSTS.has(host) ? 'devnet' : 'mainnet';
43
- }
44
- /**
45
- * Only these JSON-RPC methods are reachable; `simulateTransaction` is the gate before any submit.
46
- * task-1337 adds the two READ methods the token-balance diff needs (the wallet's token accounts before
47
- * the trade, and address-lookup-table contents); still nothing that moves funds besides `sendTransaction`.
48
- */
49
- const ALLOWED = new Set(['simulateTransaction', 'sendTransaction', 'getLatestBlockhash', 'getSignatureStatuses', 'getBalance', 'getMultipleAccounts', 'getTokenAccountsByOwner', 'requestAirdrop']);
50
- /** The node's `[base64, 'base64']` (or a bare base64 string) account data → bytes; null when absent/odd. */
51
- export function decodeAccountData(data) {
52
- const s = Array.isArray(data) ? data[0] : data;
53
- if (typeof s !== 'string')
54
- return null;
55
- if (Array.isArray(data) && data[1] !== undefined && data[1] !== 'base64')
56
- return null;
57
- return Buffer.from(s, 'base64');
58
- }
59
- /**
60
- * The node ANSWERED with a JSON-RPC error. For `sendTransaction` this is a DEFINITE pre-broadcast
61
- * rejection (preflight failure, bad blockhash, malformed tx): nothing reached the network, so the
62
- * executor may refund the daily reservation. Any other failure (timeout, reset, 5xx) is a transport
63
- * error whose outcome is UNKNOWN and must not be refunded (round 2, re-verify MEDIUM #4).
64
- */
65
- export class RpcRejectedError extends Error {
66
- method;
67
- code;
68
- data;
69
- constructor(method, message, code, data) {
70
- super(`rpc ${method}: ${message}`);
71
- this.name = 'RpcRejectedError';
72
- this.method = method;
73
- this.code = code;
74
- this.data = data;
75
- }
76
- }
77
- export class TradeRpc {
78
- http;
79
- url;
80
- /** The classified network of `url` (or the explicit config). The executor caps by THIS. */
81
- network;
82
- meta = { source: 'solana-rpc', bucket: 'solana-rpc' };
83
- clock;
84
- constructor(http, opts = {}) {
85
- this.http = http;
86
- this.url = opts.url ?? DEVNET_RPC;
87
- this.network = rpcNetwork(this.url, opts.network ?? null);
88
- this.clock = opts.clock ?? systemClock;
89
- }
90
- async call(method, params) {
91
- if (!ALLOWED.has(method))
92
- throw new Error(`refusing RPC method ${method}`);
93
- const r = await this.http.postJson(this.url, { jsonrpc: '2.0', id: 1, method, params }, this.meta);
94
- if (r.data.error)
95
- throw new RpcRejectedError(method, r.data.error.message ?? 'error', typeof r.data.error.code === 'number' ? r.data.error.code : null, r.data.error.data);
96
- if (r.data.result === undefined)
97
- throw new Error(`rpc ${method}: no result`);
98
- return r.data.result;
99
- }
100
- /**
101
- * Simulate a (possibly unsigned) base64 transaction with `replaceRecentBlockhash` + `sigVerify:false`
102
- * so the plan can be checked before the user signs. A non-null `err` means the trade must be refused.
103
- * `opts.accounts` asks the node for the POST-simulation state of those addresses (their lamports are
104
- * what the executor compares against the pre-balance to learn how much SOL the tx really moves).
105
- */
106
- async simulate(txBase64, opts = {}) {
107
- const cfg = { encoding: 'base64', replaceRecentBlockhash: true, sigVerify: false, commitment: 'confirmed' };
108
- const want = opts.accounts ?? [];
109
- if (want.length > 0)
110
- cfg.accounts = { encoding: 'base64', addresses: want };
111
- const res = await this.call('simulateTransaction', [txBase64, cfg]);
112
- const v = res.value;
113
- let accounts = null;
114
- let accountsData = null;
115
- if (want.length > 0) {
116
- accounts = want.map((_, i) => {
117
- const a = v.accounts?.[i];
118
- return a && typeof a.lamports === 'number' && Number.isFinite(a.lamports) ? BigInt(Math.trunc(a.lamports)) : null;
119
- });
120
- if (Array.isArray(v.accounts))
121
- accountsData = want.map((_, i) => decodeAccountData(v.accounts?.[i]?.data));
122
- }
123
- return { ok: v.err === null || v.err === undefined, err: v.err ?? null, logs: v.logs ?? null, unitsConsumed: typeof v.unitsConsumed === 'number' ? v.unitsConsumed : null, accounts, accountsData };
124
- }
125
- /**
126
- * task-1363: devnet faucet (`requestAirdrop`) for the capped fast wallet — refused by classification on any
127
- * non-devnet endpoint (mainnet nodes reject it anyway). Returns the airdrop signature.
128
- */
129
- async airdrop(pubkey, lamports) {
130
- if (this.network !== 'devnet')
131
- throw new Error('requestAirdrop runs only against a devnet RPC endpoint');
132
- if (!(lamports > 0n))
133
- throw new Error('airdrop needs a positive lamport amount');
134
- return this.call('requestAirdrop', [pubkey, Number(lamports)]);
135
- }
136
- /** Lamport balance of an account (read-only `getBalance`). Used for the pre-simulation balance. */
137
- async balance(pubkey) {
138
- const res = await this.call('getBalance', [pubkey, { commitment: 'confirmed' }]);
139
- if (typeof res.value !== 'number' || !Number.isFinite(res.value))
140
- throw new Error('rpc getBalance: bad value');
141
- return BigInt(Math.trunc(res.value));
142
- }
143
- /**
144
- * task-1337: read-only snapshots of several accounts (`getMultipleAccounts`, base64). null per entry
145
- * when the account does not exist. Used for the token-account PRE-balances the simulation diff
146
- * compares against, and to read address-lookup tables so every account a v0 transaction can touch is
147
- * known. Throws on a malformed answer (the executor fails closed).
148
- */
149
- async accountsInfo(pubkeys) {
150
- if (pubkeys.length === 0)
151
- return [];
152
- const out = [];
153
- for (let i = 0; i < pubkeys.length; i += 100) { // the node caps one call at 100 accounts
154
- const chunk = pubkeys.slice(i, i + 100);
155
- const res = await this.call('getMultipleAccounts', [chunk, { encoding: 'base64', commitment: 'confirmed' }]);
156
- if (!Array.isArray(res.value) || res.value.length !== chunk.length)
157
- throw new Error('rpc getMultipleAccounts: bad value');
158
- for (const a of res.value) {
159
- if (a === null) {
160
- out.push(null);
161
- continue;
162
- }
163
- const data = decodeAccountData(a.data);
164
- if (typeof a.lamports !== 'number' || typeof a.owner !== 'string' || data === null)
165
- throw new Error('rpc getMultipleAccounts: malformed account');
166
- out.push({ lamports: BigInt(Math.trunc(a.lamports)), owner: a.owner, data });
167
- }
168
- }
169
- return out;
170
- }
171
- /**
172
- * task-1337: every token account `owner` holds under `programId` (`getTokenAccountsByOwner`, base64).
173
- * These are the accounts a hostile route could drain; the executor watches their balances through the
174
- * simulation. Throws on a malformed answer (fail closed).
175
- */
176
- async tokenAccountsByOwner(owner, programId) {
177
- const res = await this.call('getTokenAccountsByOwner', [owner, { programId }, { encoding: 'base64', commitment: 'confirmed' }]);
178
- if (!Array.isArray(res.value))
179
- throw new Error('rpc getTokenAccountsByOwner: bad value');
180
- return res.value.map((e) => {
181
- const data = decodeAccountData(e.account?.data);
182
- if (typeof e.pubkey !== 'string' || data === null)
183
- throw new Error('rpc getTokenAccountsByOwner: malformed entry');
184
- return { pubkey: e.pubkey, data };
185
- });
186
- }
187
- /** A recent blockhash for a transaction dexterm builds itself (the devnet probe). Read-only. */
188
- async latestBlockhash() {
189
- const res = await this.call('getLatestBlockhash', [{ commitment: 'confirmed' }]);
190
- const v = res.value;
191
- if (!v || typeof v.blockhash !== 'string' || !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(v.blockhash))
192
- throw new Error('rpc getLatestBlockhash: bad value');
193
- return { blockhash: v.blockhash, lastValidBlockHeight: typeof v.lastValidBlockHeight === 'number' ? v.lastValidBlockHeight : 0 };
194
- }
195
- /** Broadcast an already-signed base64 transaction. Returns the signature. */
196
- async submit(signedTxBase64) {
197
- return this.call('sendTransaction', [signedTxBase64, { encoding: 'base64', skipPreflight: false, maxRetries: 3, preflightCommitment: 'confirmed' }]);
198
- }
199
- /**
200
- * One status lookup for a signature we already know (from the signed bytes). Returns null when the
201
- * node has not seen it. Used after a transport error on submit to learn whether the send landed.
202
- */
203
- async signatureStatus(signature) {
204
- const res = await this.call('getSignatureStatuses', [[signature], { searchTransactionHistory: true }]);
205
- const st = res.value?.[0];
206
- if (!st)
207
- return null;
208
- return { confirmationStatus: st.confirmationStatus ?? null, err: st.err ?? null, slot: st.slot ?? null };
209
- }
210
- /** Poll signature status until confirmed/finalized, a chain error, or the bound elapses. */
211
- async confirm(signature, opts = {}) {
212
- const deadline = this.clock.now() + (opts.timeoutMs ?? 60_000);
213
- const interval = opts.intervalMs ?? 2_000;
214
- for (;;) {
215
- const res = await this.call('getSignatureStatuses', [[signature], { searchTransactionHistory: false }]);
216
- const st = res.value[0];
217
- if (st) {
218
- if (st.err)
219
- return { signature, confirmed: false, err: st.err, slot: st.slot ?? null };
220
- if (st.confirmationStatus === 'confirmed' || st.confirmationStatus === 'finalized') {
221
- return { signature, confirmed: true, err: null, slot: st.slot ?? null };
222
- }
223
- }
224
- if (this.clock.now() >= deadline)
225
- return { signature, confirmed: false, err: 'confirmation timed out', slot: null };
226
- await this.clock.sleep(interval);
227
- }
228
- }
229
- }
@@ -1,197 +0,0 @@
1
- // dexterm T2 — the signer CLIENT (task-1328): the UI's only handle on the signer process. It spawns the
2
- // signer as a child with piped stdio, hands it the session secret as the first stdin line, and sends
3
- // authenticated requests with per-call timeouts. Every failure is a REFUSAL (fail closed): a dead or
4
- // silent signer never becomes a "maybe it signed". The client never respawns a signer that died — LIVE
5
- // trading stays off for the rest of the run, and the user restarts dexterm.
6
- //
7
- // The client forwards exactly the argv the user started dexterm with (caps flags, --mainnet, --rpc).
8
- // It cannot change caps afterwards: there is no such message.
9
- import { spawn } from 'node:child_process';
10
- import { createInterface } from 'node:readline';
11
- import { fileURLToPath } from 'node:url';
12
- import { decodeFrame, encodeFrame, encodeHello, isEvent, isResponse, newSecret } from "./protocol.js";
13
- export class SignerUnavailableError extends Error {
14
- /** The signer's refusal/error reason when it sent one (`rate-limited`, `build-failed`, a CapRefusal, …). */
15
- reason;
16
- /** task-1368: for `rate-limited`, how long to wait before asking the signer again. */
17
- retryAfterMs;
18
- constructor(message, detail = {}) {
19
- super(message);
20
- this.name = 'SignerUnavailableError';
21
- this.reason = detail.reason ?? null;
22
- this.retryAfterMs = typeof detail.retryAfterMs === 'number' && Number.isFinite(detail.retryAfterMs) ? detail.retryAfterMs : null;
23
- }
24
- }
25
- export const DEFAULT_TIMEOUTS = { status: 8_000, plan: 60_000, probe: 30_000, precheck: 8_000, confirm: 240_000, drop: 8_000, panic: 8_000, fast: 12_000, fill: 30_000, quote: 20_000 };
26
- export function defaultSignerEntry() {
27
- return fileURLToPath(new URL('./main.js', import.meta.url));
28
- }
29
- export class SignerClient {
30
- child = null;
31
- secret = newSecret();
32
- outSeq = 0;
33
- inSeq = 0;
34
- nextId = 1;
35
- pending = new Map();
36
- ready = null;
37
- dead = null;
38
- opts;
39
- argv;
40
- pid = null;
41
- constructor(opts = {}) {
42
- this.opts = opts;
43
- this.argv = [...(opts.argv ?? [])];
44
- }
45
- /**
46
- * Available for LIVE work: not dead. A client that has not started yet IS alive — the signer is
47
- * spawned lazily on the first request. task-1337: the old `child !== null && …` made every real run
48
- * refuse `L` with "the signer process died" before the signer had ever been started (the ticket
49
- * checks `alive` before its first call); only the fake signer in the tests, alive from construction,
50
- * ever reached LIVE. `dead` is the fail-closed marker: set once on exit/error/timeout/close, never reset.
51
- */
52
- get alive() { return this.dead === null; }
53
- /** Spawn + handshake (idempotent). Rejects when the signer cannot start; never retries. */
54
- start() {
55
- if (this.dead)
56
- return Promise.reject(new SignerUnavailableError(this.dead));
57
- if (this.ready)
58
- return this.ready;
59
- this.ready = new Promise((resolve, reject) => {
60
- const entry = this.opts.entry ?? defaultSignerEntry();
61
- let child;
62
- try {
63
- child = spawn(process.execPath, [entry, ...this.argv], { stdio: ['pipe', 'pipe', 'pipe'], env: this.opts.env ?? process.env });
64
- }
65
- catch (e) {
66
- this.dead = `spawn failed: ${e instanceof Error ? e.message : String(e)}`;
67
- reject(new SignerUnavailableError(this.dead));
68
- return;
69
- }
70
- this.child = child;
71
- this.pid = child.pid ?? null;
72
- let settled = false;
73
- const readyTimer = setTimeout(() => { if (!settled) {
74
- settled = true;
75
- this.kill('signer did not become ready');
76
- reject(new SignerUnavailableError('signer did not become ready'));
77
- } }, this.opts.readyTimeoutMs ?? 15_000);
78
- readyTimer.unref?.();
79
- child.on('error', (e) => { this.markDead(`signer error: ${e.message}`); if (!settled) {
80
- settled = true;
81
- clearTimeout(readyTimer);
82
- reject(new SignerUnavailableError(this.dead ?? 'signer error'));
83
- } });
84
- child.on('exit', (code, signal) => { this.markDead(`signer exited (${code ?? signal ?? '?'})`); if (!settled) {
85
- settled = true;
86
- clearTimeout(readyTimer);
87
- reject(new SignerUnavailableError(this.dead ?? 'signer exited'));
88
- } });
89
- if (child.stderr)
90
- createInterface({ input: child.stderr }).on('line', (l) => this.opts.log?.(l));
91
- if (child.stdout) {
92
- createInterface({ input: child.stdout, crlfDelay: Infinity }).on('line', (line) => {
93
- const d = decodeFrame(this.secret, line, this.inSeq);
94
- if (!d.ok) {
95
- this.opts.log?.(`signer client: dropped frame (${d.why})`);
96
- return;
97
- }
98
- this.inSeq = d.seq + 1;
99
- this.onMessage(d.message, () => { if (!settled) {
100
- settled = true;
101
- clearTimeout(readyTimer);
102
- resolve();
103
- } });
104
- });
105
- }
106
- child.stdin?.on('error', () => { });
107
- child.stdin?.write(encodeHello(this.secret));
108
- });
109
- return this.ready;
110
- }
111
- onMessage(m, onReady) {
112
- if (isEvent(m)) {
113
- if (m.event === 'ready')
114
- onReady();
115
- return;
116
- }
117
- if (!isResponse(m))
118
- return;
119
- const p = this.pending.get(m.id);
120
- if (!p)
121
- return;
122
- this.pending.delete(m.id);
123
- clearTimeout(p.timer);
124
- if (m.ok)
125
- p.resolve(m.result);
126
- else
127
- p.reject(new SignerUnavailableError(m.error, { reason: m.reason, retryAfterMs: m.retryAfterMs }));
128
- }
129
- markDead(why) {
130
- if (this.dead)
131
- return;
132
- this.dead = why;
133
- for (const [id, p] of this.pending) {
134
- clearTimeout(p.timer);
135
- this.pending.delete(id);
136
- p.reject(new SignerUnavailableError(why));
137
- }
138
- }
139
- kill(why) {
140
- this.markDead(why);
141
- try {
142
- this.child?.kill('SIGTERM');
143
- }
144
- catch { /* ignore */ }
145
- }
146
- async request(method, params, timeoutMs = DEFAULT_TIMEOUTS[method] ?? 8_000) {
147
- if (this.dead)
148
- throw new SignerUnavailableError(this.dead);
149
- await this.start();
150
- if (this.dead || !this.child?.stdin)
151
- throw new SignerUnavailableError(this.dead ?? 'signer has no stdin');
152
- const id = this.nextId++;
153
- return new Promise((resolve, reject) => {
154
- const timer = setTimeout(() => { this.pending.delete(id); reject(new SignerUnavailableError(`signer ${method} timed out after ${Math.round(timeoutMs / 1000)} s`)); }, timeoutMs);
155
- timer.unref?.();
156
- this.pending.set(id, { resolve: resolve, reject, timer });
157
- try {
158
- this.child.stdin.write(encodeFrame(this.secret, this.outSeq++, { id, method, params }));
159
- }
160
- catch (e) {
161
- this.pending.delete(id);
162
- clearTimeout(timer);
163
- reject(new SignerUnavailableError(`write failed: ${e instanceof Error ? e.message : String(e)}`));
164
- }
165
- });
166
- }
167
- status() { return this.request('status', {}); }
168
- plan(params) { return this.request('plan', { ...params }); }
169
- probe() { return this.request('probe', {}); }
170
- precheck(hash) { return this.request('precheck', { hash }); }
171
- confirm(hash) { return this.request('confirm', { hash }); }
172
- async drop(hash) { await this.request('drop', { hash }); }
173
- async panic(reason) { await this.request('panic', { reason }); }
174
- fast() { return this.request('fast', {}); }
175
- fill(params) { return this.request('fill', { ...params }); }
176
- quote(params) { return this.request('quote', { ...params }); }
177
- close() {
178
- this.markDead('closed');
179
- try {
180
- this.child?.stdin?.end();
181
- }
182
- catch { /* ignore */ }
183
- const c = this.child;
184
- if (c) {
185
- const t = setTimeout(() => { try {
186
- c.kill('SIGKILL');
187
- }
188
- catch { /* ignore */ } }, 2_000);
189
- t.unref?.();
190
- c.on('exit', () => clearTimeout(t));
191
- try {
192
- c.kill('SIGTERM');
193
- }
194
- catch { /* ignore */ }
195
- }
196
- }
197
- }