perp-cli 0.11.0 → 0.12.0

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 (240) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bridge-engine.js +102 -49
  21. package/dist/cli-spec.js +1 -2
  22. package/dist/commands/account.js +2 -500
  23. package/dist/commands/agent.d.ts +8 -2
  24. package/dist/commands/agent.js +1903 -366
  25. package/dist/commands/alerts.js +3 -1
  26. package/dist/commands/arb/index.js +37 -7
  27. package/dist/commands/arb-auto.js +44 -11
  28. package/dist/commands/arb.js +83 -34
  29. package/dist/commands/bot.d.ts +1 -1
  30. package/dist/commands/bot.js +18 -14
  31. package/dist/commands/bridge.d.ts +1 -1
  32. package/dist/commands/bridge.js +4 -4
  33. package/dist/commands/funds.d.ts +1 -1
  34. package/dist/commands/funds.js +18 -68
  35. package/dist/commands/jobs.d.ts +1 -1
  36. package/dist/commands/jobs.js +2 -2
  37. package/dist/commands/manage.d.ts +1 -1
  38. package/dist/commands/manage.js +12 -84
  39. package/dist/commands/plan.d.ts +1 -1
  40. package/dist/commands/plan.js +2 -2
  41. package/dist/commands/portfolio.d.ts +19 -3
  42. package/dist/commands/portfolio.js +747 -163
  43. package/dist/commands/rebalance.d.ts +1 -1
  44. package/dist/commands/rebalance.js +18 -12
  45. package/dist/commands/risk.js +2 -6
  46. package/dist/commands/settings.js +30 -53
  47. package/dist/commands/trade.js +16 -15
  48. package/dist/commands/wallet.d.ts +2 -1
  49. package/dist/commands/wallet.js +756 -28
  50. package/dist/errors.d.ts +64 -0
  51. package/dist/errors.js +82 -2
  52. package/dist/event-stream.js +12 -1
  53. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  54. package/dist/exchanges/aster-typed-data.js +189 -0
  55. package/dist/exchanges/aster.d.ts +60 -11
  56. package/dist/exchanges/aster.js +278 -136
  57. package/dist/exchanges/hyperliquid.d.ts +14 -0
  58. package/dist/exchanges/hyperliquid.js +84 -34
  59. package/dist/exchanges/lighter-spot.js +14 -3
  60. package/dist/exchanges/lighter.d.ts +66 -0
  61. package/dist/exchanges/lighter.js +199 -27
  62. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  63. package/dist/exchanges/pacifica-typed-data.js +74 -0
  64. package/dist/exchanges/pacifica.d.ts +29 -1
  65. package/dist/exchanges/pacifica.js +94 -6
  66. package/dist/funding/rates.js +59 -21
  67. package/dist/guardrail/perp-guardrail.js +9 -1
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +255 -360
  70. package/dist/jobs.js +3 -1
  71. package/dist/mcp-server.js +85 -83
  72. package/dist/position-history.js +47 -4
  73. package/dist/settings.d.ts +67 -3
  74. package/dist/settings.js +1 -2
  75. package/dist/signer/ows-evm.d.ts +14 -0
  76. package/dist/signer/ows-evm.js +28 -9
  77. package/dist/smart-order.d.ts +8 -2
  78. package/dist/smart-order.js +46 -11
  79. package/dist/strategies/funding-arb.js +29 -8
  80. package/dist/trade-validator.js +30 -5
  81. package/dist/utils.d.ts +10 -0
  82. package/dist/utils.js +19 -0
  83. package/package.json +2 -2
  84. package/skills/perp-cli/SKILL.md +23 -14
  85. package/skills/perp-cli/references/agent-operations.md +6 -4
  86. package/skills/perp-cli/references/commands.md +55 -34
  87. package/skills/perp-cli/scripts/preflight.sh +4 -4
  88. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  89. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  90. package/dist/__tests__/alert-logic.test.js +0 -107
  91. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  92. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  93. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  94. package/dist/__tests__/arb-history-stats.test.js +0 -176
  95. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  96. package/dist/__tests__/arb-logic.test.js +0 -84
  97. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  98. package/dist/__tests__/arb-manage.test.js +0 -253
  99. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  100. package/dist/__tests__/arb-new-features.test.js +0 -457
  101. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  102. package/dist/__tests__/arb-sizing.test.js +0 -48
  103. package/dist/__tests__/arb-state.test.d.ts +0 -1
  104. package/dist/__tests__/arb-state.test.js +0 -284
  105. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  106. package/dist/__tests__/arb-userflow.test.js +0 -945
  107. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  108. package/dist/__tests__/arb-utils.test.js +0 -264
  109. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  110. package/dist/__tests__/bot-conditions.test.js +0 -341
  111. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  112. package/dist/__tests__/client-id-tracker.test.js +0 -137
  113. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  114. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  115. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  116. package/dist/__tests__/commands/order-intent.test.js +0 -600
  117. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  118. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  119. package/dist/__tests__/config.test.d.ts +0 -1
  120. package/dist/__tests__/config.test.js +0 -86
  121. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  122. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  123. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  124. package/dist/__tests__/dex-asset-map.test.js +0 -191
  125. package/dist/__tests__/errors.test.d.ts +0 -1
  126. package/dist/__tests__/errors.test.js +0 -110
  127. package/dist/__tests__/event-stream.test.d.ts +0 -1
  128. package/dist/__tests__/event-stream.test.js +0 -276
  129. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  130. package/dist/__tests__/exchanges/interface.test.js +0 -132
  131. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  132. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  133. package/dist/__tests__/execution-log.test.d.ts +0 -1
  134. package/dist/__tests__/execution-log.test.js +0 -106
  135. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  136. package/dist/__tests__/funding-calc.test.js +0 -71
  137. package/dist/__tests__/funding-history.test.d.ts +0 -1
  138. package/dist/__tests__/funding-history.test.js +0 -343
  139. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  140. package/dist/__tests__/funding-rates.test.js +0 -342
  141. package/dist/__tests__/funding.test.d.ts +0 -1
  142. package/dist/__tests__/funding.test.js +0 -173
  143. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  144. package/dist/__tests__/gap-logic.test.js +0 -43
  145. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  146. package/dist/__tests__/hip3-dex.test.js +0 -234
  147. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  148. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  149. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  150. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  151. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  152. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  153. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  154. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  155. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  156. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  157. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  158. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  159. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  160. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  161. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  162. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  163. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  165. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  166. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  167. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  169. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  170. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  171. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  173. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  175. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  177. package/dist/__tests__/liquidity.test.d.ts +0 -1
  178. package/dist/__tests__/liquidity.test.js +0 -225
  179. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  180. package/dist/__tests__/plan-executor.test.js +0 -314
  181. package/dist/__tests__/position-history.test.d.ts +0 -1
  182. package/dist/__tests__/position-history.test.js +0 -367
  183. package/dist/__tests__/retry.test.d.ts +0 -1
  184. package/dist/__tests__/retry.test.js +0 -310
  185. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  186. package/dist/__tests__/risk-assessment.test.js +0 -288
  187. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  188. package/dist/__tests__/security-adversarial.test.js +0 -574
  189. package/dist/__tests__/strategies.test.d.ts +0 -1
  190. package/dist/__tests__/strategies.test.js +0 -539
  191. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  192. package/dist/__tests__/trade-execution.test.js +0 -129
  193. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  194. package/dist/__tests__/trade-validator.test.js +0 -655
  195. package/dist/__tests__/utils.test.d.ts +0 -1
  196. package/dist/__tests__/utils.test.js +0 -76
  197. package/dist/arb/reconcile.d.ts +0 -28
  198. package/dist/arb/reconcile.js +0 -162
  199. package/dist/commands/alert.d.ts +0 -2
  200. package/dist/commands/alert.js +0 -421
  201. package/dist/commands/analytics.d.ts +0 -3
  202. package/dist/commands/analytics.js +0 -311
  203. package/dist/commands/arb-manage.d.ts +0 -5
  204. package/dist/commands/arb-manage.js +0 -5
  205. package/dist/commands/dashboard.d.ts +0 -3
  206. package/dist/commands/dashboard.js +0 -176
  207. package/dist/commands/deposit.d.ts +0 -4
  208. package/dist/commands/deposit.js +0 -573
  209. package/dist/commands/dex.d.ts +0 -3
  210. package/dist/commands/dex.js +0 -120
  211. package/dist/commands/env.d.ts +0 -2
  212. package/dist/commands/env.js +0 -136
  213. package/dist/commands/funding.d.ts +0 -3
  214. package/dist/commands/funding.js +0 -554
  215. package/dist/commands/gap.d.ts +0 -2
  216. package/dist/commands/gap.js +0 -305
  217. package/dist/commands/health.d.ts +0 -2
  218. package/dist/commands/health.js +0 -67
  219. package/dist/commands/multileg.d.ts +0 -16
  220. package/dist/commands/multileg.js +0 -174
  221. package/dist/commands/ows.d.ts +0 -2
  222. package/dist/commands/ows.js +0 -887
  223. package/dist/commands/pnl.d.ts +0 -12
  224. package/dist/commands/pnl.js +0 -243
  225. package/dist/commands/run.d.ts +0 -3
  226. package/dist/commands/run.js +0 -202
  227. package/dist/commands/stream.d.ts +0 -5
  228. package/dist/commands/stream.js +0 -200
  229. package/dist/commands/withdraw.d.ts +0 -3
  230. package/dist/commands/withdraw.js +0 -187
  231. package/dist/exchanges/edgex.d.ts +0 -62
  232. package/dist/exchanges/edgex.js +0 -552
  233. package/dist/exchanges/nado.d.ts +0 -52
  234. package/dist/exchanges/nado.js +0 -223
  235. package/dist/funding/index.d.ts +0 -3
  236. package/dist/funding/index.js +0 -3
  237. package/dist/retry.d.ts +0 -74
  238. package/dist/retry.js +0 -129
  239. package/dist/ws/market-feed.d.ts +0 -74
  240. package/dist/ws/market-feed.js +0 -227
package/dist/jobs.js CHANGED
@@ -138,7 +138,9 @@ export function removeJob(id) {
138
138
  */
139
139
  function buildEnvString() {
140
140
  const keys = [
141
- "LIGHTER_PRIVATE_KEY", "LIGHTER_API_KEY", "LIGHTER_ACCOUNT_INDEX", "LIGHTER_API_KEY_INDEX",
141
+ // L2 agent key now lives in ~/.perp/lighter-agents/*.json (SSOT Rule #3),
142
+ // accountIndex/apiKeyIndex live in settings.agents.lighter — none need env passthrough.
143
+ "LIGHTER_PRIVATE_KEY",
142
144
  "PRIVATE_KEY", "pk",
143
145
  "PACIFICA_BUILDER_CODE", "NEXT_PUBLIC_BUILDER_CODE",
144
146
  "HL_REFERRAL_CODE", "LIGHTER_REFERRAL_CODE",
@@ -436,15 +436,15 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
436
436
  else if (g.includes("grid") || g.includes("dca") || g.includes("bot")) {
437
437
  const symbol = extractSymbol(g) || "<symbol>";
438
438
  if (g.includes("grid")) {
439
- steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: "Check current price" }, { step: 2, command: `perp -e ${ex} --json bot quick-grid ${symbol}`, description: "Start grid bot", dangerous: true }, { step: 3, command: "perp --json jobs list", description: "Verify bot is running" });
439
+ steps.push({ step: 1, command: `perp -e ${ex} --json market book ${symbol}`, description: "Check current price" }, { step: 2, command: `perp -e ${ex} --json strategy quick-grid ${symbol}`, description: "Start grid bot", dangerous: true }, { step: 3, command: "perp --json background list", description: "Verify bot is running" });
440
440
  }
441
441
  else if (g.includes("dca")) {
442
442
  const amount = extractNumber(g) || "<amount>";
443
443
  const side = g.includes("sell") || g.includes("short") ? "sell" : "buy";
444
- steps.push({ step: 1, command: `perp -e ${ex} --json account balance`, description: "Check available balance" }, { step: 2, command: `perp -e ${ex} --json bot quick-dca ${symbol} ${side} ${amount} <interval>`, description: "Start DCA bot", dangerous: true }, { step: 3, command: "perp --json jobs list", description: "Verify bot is running" });
444
+ steps.push({ step: 1, command: `perp -e ${ex} --json account balance`, description: "Check available balance" }, { step: 2, command: `perp -e ${ex} --json strategy quick-dca ${symbol} ${side} ${amount} <interval>`, description: "Start DCA bot", dangerous: true }, { step: 3, command: "perp --json background list", description: "Verify bot is running" });
445
445
  }
446
446
  else {
447
- steps.push({ step: 1, command: "perp --json bot preset-list", description: "List available bot presets" }, { step: 2, command: `perp -e ${ex} --json bot quick-arb`, description: "Start arb bot", dangerous: true }, { step: 3, command: "perp --json jobs list", description: "Verify bot is running" });
447
+ steps.push({ step: 1, command: "perp --json strategy preset-list", description: "List available bot presets" }, { step: 2, command: `perp -e ${ex} --json strategy quick-arb`, description: "Start arb bot", dangerous: true }, { step: 3, command: "perp --json background list", description: "Verify bot is running" });
448
448
  }
449
449
  }
450
450
  else if (g.includes("backtest")) {
@@ -486,13 +486,13 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
486
486
  steps.push({ step: 1, command: "perp --json rebalance check", description: "Check balance distribution" }, { step: 2, command: "perp --json rebalance plan", description: "Generate rebalance plan" }, { step: 3, command: "perp --json rebalance execute", description: "Execute rebalance", dangerous: true });
487
487
  }
488
488
  else if (g.includes("job") || g.includes("running") || g.includes("background")) {
489
- steps.push({ step: 1, command: "perp --json jobs list", description: "List running background jobs" });
489
+ steps.push({ step: 1, command: "perp --json background list", description: "List running background jobs" });
490
490
  }
491
491
  else if (g.includes("dex") || g.includes("hip-3") || g.includes("hip3")) {
492
492
  steps.push({ step: 1, command: "perp --json -e hl market hip3", description: "List HIP-3 deployed dexes" }, { step: 2, command: "perp --json -e hl --dex <name> market list", description: "Show markets on a specific dex" });
493
493
  }
494
494
  else if (g.includes("plan") || g.includes("composite") || g.includes("multi-step")) {
495
- steps.push({ step: 1, command: "perp plan example", description: "Show example execution plan format" }, { step: 2, command: "perp --json plan validate <file>", description: "Validate plan file" }, { step: 3, command: "perp --json plan execute <file> --dry-run", description: "Dry-run the plan", dangerous: true });
495
+ steps.push({ step: 1, command: "perp strategy plan example", description: "Show example execution plan format" }, { step: 2, command: "perp --json strategy plan validate <file>", description: "Validate plan file" }, { step: 3, command: "perp --json strategy plan execute <file> --dry-run", description: "Dry-run the plan", dangerous: true });
496
496
  }
497
497
  else if (g.includes("status") || g.includes("check") || g.includes("overview") || g.includes("portfolio")) {
498
498
  steps.push({ step: 1, command: `perp -e ${ex} --json status`, description: "Full account overview" }, { step: 2, command: `perp -e ${ex} --json account positions`, description: "Detailed positions" }, { step: 3, command: `perp -e ${ex} --json account orders`, description: "Open orders" }, { step: 4, command: "perp --json portfolio", description: "Cross-exchange portfolio summary" });
@@ -532,7 +532,7 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
532
532
  steps.push({ step: 1, command: "perp settings show", description: "Show current settings" }, { step: 2, command: "perp settings referrals on", description: "Enable referral codes (no extra fees, just volume tracking)" });
533
533
  }
534
534
  else {
535
- steps.push({ step: 1, command: "perp agent capabilities", description: "List all available CLI capabilities" }, { step: 2, command: `perp -e ${ex} --json status`, description: "Check account status" }, { step: 3, command: "perp --json agent ping", description: "Check exchange connectivity" });
535
+ steps.push({ step: 1, command: `perp -e ${ex} --json status`, description: "Check account status" }, { step: 2, command: `perp -e ${ex} --json account balance`, description: "Check exchange connectivity via balance fetch" });
536
536
  }
537
537
  return {
538
538
  content: [{
@@ -831,7 +831,7 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
831
831
  parameters: [],
832
832
  risks: sub === "send" || sub === "exchange" ? ["Transfers real funds cross-chain — verify addresses carefully"] : [],
833
833
  category: sub === "send" || sub === "exchange" ? "write" : "read",
834
- relatedCommands: ["perp bridge chains", "perp bridge quote"],
834
+ relatedCommands: ["perp funds bridge chains", "perp funds bridge quote"],
835
835
  };
836
836
  }
837
837
  else if (category === "funds") {
@@ -855,14 +855,14 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
855
855
  relatedCommands: ["perp account positions", "perp portfolio"],
856
856
  };
857
857
  }
858
- else if (category === "bot" || category === "run") {
858
+ else if (category === "strategy" || category === "run") {
859
859
  explanation = {
860
860
  command,
861
861
  description: `Automated strategy: ${sub}. Runs as a background job.`,
862
862
  parameters: args.slice(2).map((a, i) => ({ name: `arg${i}`, value: a, description: "See --help" })),
863
- risks: ["Runs automated trades — monitor with 'perp jobs list'", "Use --dry-run to simulate first"],
863
+ risks: ["Runs automated trades — monitor with 'perp background list'", "Use --dry-run to simulate first"],
864
864
  category: "write",
865
- relatedCommands: ["perp jobs list", "perp jobs stop"],
865
+ relatedCommands: ["perp background list", "perp background stop"],
866
866
  };
867
867
  }
868
868
  else if (category === "history") {
@@ -878,11 +878,11 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
878
878
  else if (category === "wallet") {
879
879
  explanation = {
880
880
  command,
881
- description: { show: "Show configured wallets with public addresses", list: "List all wallets (OWS vault + legacy)", balance: "Check on-chain USDC/SOL/ETH balances", generate: "Generate a new OWS wallet (encrypted, multi-chain)", import: "Import key into OWS vault", migrate: "Migrate legacy wallets to OWS encrypted vault", set: "Set private key for an exchange (legacy)", use: "Set active wallet (OWS or legacy)", remove: "Remove a wallet", rename: "Rename a wallet", ows: "OWS vault management (create, list, info, delete)" }[sub] || `Wallet: ${sub}`,
881
+ description: { show: "Show configured wallets with public addresses", list: "List all wallets (OWS vault + legacy)", balance: "Check on-chain USDC/SOL/ETH balances", generate: "Generate a new OWS wallet (encrypted, multi-chain)", import: "Import key into OWS vault", migrate: "Migrate legacy wallets to OWS encrypted vault", set: "Set private key for an exchange (legacy)", use: "Set active wallet (OWS or legacy)", remove: "Remove a wallet", rename: "Rename a wallet", policy: "OWS signing policy: create/list/show/delete", key: "OWS API key: create/list/revoke", agent: "DEX-side agent delegation: approve/list/revoke/rotate/verify", deposit: "MoonPay multi-chain deposit (auto-converts to USDC)", setup: "One-click setup: wallet + guardrail policy + agent API key", backup: "Backup encrypted OWS vault", restore: "Restore OWS vault from encrypted backup", rotate: "Rotate wallet keys (new wallet + transfer assets)" }[sub] || `Wallet: ${sub}`,
882
882
  parameters: [],
883
883
  risks: sub === "generate" ? ["Save the mnemonic securely — it cannot be recovered. Keys are stored in ~/.ows/ encrypted vault."] : sub === "remove" ? ["Wallet will be removed — ensure you have backups"] : [],
884
884
  category: "read",
885
- relatedCommands: ["perp wallet list", "perp wallet balance", "perp wallet ows list"],
885
+ relatedCommands: ["perp wallet list", "perp wallet balance", "perp wallet agent list"],
886
886
  };
887
887
  }
888
888
  else if (category === "backtest") {
@@ -906,14 +906,14 @@ server.tool("explain_command", "Explain what a perp CLI command does, its parame
906
906
  };
907
907
  }
908
908
  else {
909
- const writeCommands = new Set(["trade", "funds", "manage", "rebalance"]);
909
+ const writeCommands = new Set(["trade", "funds", "manage"]);
910
910
  explanation = {
911
911
  command,
912
912
  description: `CLI command: ${command}. Run 'perp ${category} --help' for detailed usage.`,
913
913
  parameters: args.slice(1).map((a, i) => ({ name: `arg${i}`, value: a, description: "See --help for details" })),
914
914
  risks: writeCommands.has(category) ? ["This command may modify account state — review carefully"] : [],
915
915
  category: writeCommands.has(category) ? "write" : "read",
916
- relatedCommands: ["perp agent capabilities", "perp schema"],
916
+ relatedCommands: [`perp ${category} --help`],
917
917
  };
918
918
  }
919
919
  return { content: [{ type: "text", text: ok(explanation) }] };
@@ -1096,35 +1096,28 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1096
1096
  check: { usage: "perp risk check --notional <usd> --leverage <n>", description: "Pre-trade risk check" },
1097
1097
  },
1098
1098
  },
1099
- bridge: {
1100
- description: "Cross-chain USDC bridge",
1099
+ strategy: {
1100
+ description: "Trading strategies — long-running bot algorithms + one-shot scripted plans",
1101
1101
  subcommands: {
1102
- chains: { usage: "perp bridge chains", description: "Supported chains" },
1103
- quote: { usage: "perp bridge quote --from <chain> --to <chain> --amount <n>", description: "Get quote" },
1104
- send: { usage: "perp bridge send --from <chain> --to <chain> --amount <n>", description: "Execute bridge" },
1105
- exchange: { usage: "perp bridge exchange --from <ex> --to <ex> --amount <n>", description: "Bridge between exchanges" },
1106
- status: { usage: "perp bridge status <orderId>", description: "Track bridge status" },
1107
- },
1108
- },
1109
- bot: {
1110
- description: "Automated trading bots",
1111
- subcommands: {
1112
- start: { usage: "perp bot start <config>", description: "Start bot from config file" },
1113
- twap: { usage: "perp bot twap <symbol> <side> <size> <duration>", description: "TWAP execution" },
1114
- grid: { usage: "perp bot grid <symbol> --range <pct> --grids <n> --size <usd>", description: "Grid trading bot" },
1115
- dca: { usage: "perp bot dca <symbol> <side> <amount> <interval>", description: "DCA bot" },
1116
- "funding-arb": { usage: "perp bot funding-arb", description: "Funding arb bot" },
1117
- "trailing-stop": { usage: "perp bot trailing-stop <symbol>", description: "Trailing stop bot" },
1118
- "quick-grid": { usage: "perp bot quick-grid <symbol>", description: "Quick grid bot" },
1119
- "quick-dca": { usage: "perp bot quick-dca <symbol> <side> <amount> <interval>", description: "Quick DCA bot" },
1120
- "quick-arb": { usage: "perp bot quick-arb", description: "Quick arb bot" },
1121
- "preset-list": { usage: "perp bot preset-list", description: "List available bot presets" },
1122
- preset: { usage: "perp bot preset <name> [symbol]", description: "Run a bot preset" },
1123
- example: { usage: "perp bot example", description: "Show example bot config" },
1102
+ start: { usage: "perp strategy start <config>", description: "Start bot from config file" },
1103
+ twap: { usage: "perp strategy twap <symbol> <side> <size> <duration>", description: "TWAP execution" },
1104
+ grid: { usage: "perp strategy grid <symbol> --range <pct> --grids <n> --size <usd>", description: "Grid trading bot" },
1105
+ dca: { usage: "perp strategy dca <symbol> <side> <amount> <interval>", description: "DCA bot" },
1106
+ "funding-arb": { usage: "perp strategy funding-arb", description: "Funding arb bot" },
1107
+ "trailing-stop": { usage: "perp strategy trailing-stop <symbol>", description: "Trailing stop bot" },
1108
+ "quick-grid": { usage: "perp strategy quick-grid <symbol>", description: "Quick grid bot" },
1109
+ "quick-dca": { usage: "perp strategy quick-dca <symbol> <side> <amount> <interval>", description: "Quick DCA bot" },
1110
+ "quick-arb": { usage: "perp strategy quick-arb", description: "Quick arb bot" },
1111
+ "preset-list": { usage: "perp strategy preset-list", description: "List available bot presets" },
1112
+ preset: { usage: "perp strategy preset <name> [symbol]", description: "Run a bot preset" },
1113
+ example: { usage: "perp strategy example", description: "Show example bot config" },
1114
+ "plan validate": { usage: "perp strategy plan validate <file>", description: "Validate scripted execution plan" },
1115
+ "plan execute": { usage: "perp strategy plan execute <file>", description: "Execute scripted plan" },
1116
+ "plan example": { usage: "perp strategy plan example", description: "Show plan format" },
1124
1117
  },
1125
1118
  },
1126
1119
  wallet: {
1127
- description: "Wallet management (OWS encrypted vault + legacy)",
1120
+ description: "Wallet management (OWS encrypted vault + legacy + agent delegation + lifecycle)",
1128
1121
  subcommands: {
1129
1122
  show: { usage: "perp wallet show", description: "Show configured wallets with public addresses" },
1130
1123
  list: { usage: "perp wallet list", description: "List all wallets (OWS vault + legacy)" },
@@ -1136,7 +1129,14 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1136
1129
  use: { usage: "perp wallet use <name>", description: "Set active wallet (OWS or legacy)" },
1137
1130
  remove: { usage: "perp wallet remove <name>", description: "Remove a wallet" },
1138
1131
  rename: { usage: "perp wallet rename <oldName> <newName>", description: "Rename a wallet" },
1139
- ows: { usage: "perp wallet ows <sub>", description: "OWS vault: create, list, info, delete, policy, key, fund, pay, setup" },
1132
+ policy: { usage: "perp wallet policy <create|list|show|delete>", description: "OWS signing policies (guardrails for agent signing)" },
1133
+ key: { usage: "perp wallet key <create|list|revoke>", description: "OWS API keys (scoped agent access tokens)" },
1134
+ agent: { usage: "perp wallet agent <approve|list|revoke|rotate|verify>", description: "DEX-side agent wallet delegation (per-exchange)" },
1135
+ deposit: { usage: "perp wallet deposit <walletName>", description: "MoonPay multi-chain deposit (auto-converts to USDC)" },
1136
+ setup: { usage: "perp wallet setup", description: "One-click: wallet + guardrail policy + agent API key" },
1137
+ backup: { usage: "perp wallet backup", description: "Backup encrypted OWS vault" },
1138
+ restore: { usage: "perp wallet restore <file>", description: "Restore OWS vault from encrypted backup" },
1139
+ rotate: { usage: "perp wallet rotate --from <a> --to <b>", description: "Rotate wallet keys (create new + transfer assets)" },
1140
1140
  },
1141
1141
  },
1142
1142
  history: {
@@ -1162,44 +1162,37 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1162
1162
  grid: { usage: "perp backtest grid", description: "Backtest grid strategy" },
1163
1163
  },
1164
1164
  },
1165
- plan: {
1166
- description: "Composite multi-step execution plans",
1167
- subcommands: {
1168
- validate: { usage: "perp plan validate <file>", description: "Validate plan" },
1169
- execute: { usage: "perp plan execute <file>", description: "Execute plan" },
1170
- example: { usage: "perp plan example", description: "Show plan format" },
1171
- },
1172
- },
1173
- rebalance: {
1174
- description: "Cross-exchange balance rebalancing",
1175
- subcommands: {
1176
- check: { usage: "perp rebalance check", description: "Check distribution" },
1177
- plan: { usage: "perp rebalance plan", description: "Generate plan" },
1178
- execute: { usage: "perp rebalance execute", description: "Execute rebalance" },
1179
- },
1180
- },
1181
- jobs: {
1182
- description: "Background job management",
1165
+ background: {
1166
+ description: "Background process supervisor (tmux sessions for strategies, alerts, etc.)",
1183
1167
  subcommands: {
1184
- list: { usage: "perp jobs list", description: "List running jobs" },
1185
- stop: { usage: "perp jobs stop <id>", description: "Stop a job" },
1186
- logs: { usage: "perp jobs logs <id>", description: "View job logs" },
1168
+ list: { usage: "perp background list", description: "List running jobs" },
1169
+ stop: { usage: "perp background stop <id>", description: "Stop a job" },
1170
+ logs: { usage: "perp background logs <id>", description: "View job logs" },
1171
+ remove: { usage: "perp background remove <id>", description: "Remove a job entry and its logs" },
1172
+ clean: { usage: "perp background clean", description: "Remove all stopped/done jobs" },
1187
1173
  },
1188
1174
  },
1189
1175
  funds: {
1190
- usage: "perp funds <deposit|withdraw|transfer|bridge> ...",
1191
- description: "Deposit, withdraw, bridge & transfer funds",
1176
+ usage: "perp funds <deposit|withdraw|transfer|bridge|rebalance|info> ...",
1177
+ description: "Fund movement — deposit, withdraw, transfer, cross-chain bridge, inter-exchange rebalance",
1192
1178
  subcommands: {
1193
- "deposit <exchange> <amount>": "Deposit USDC to exchange",
1194
- "withdraw <exchange> <amount>": "Withdraw USDC from exchange",
1195
- "transfer <amount> <address>": "HL internal transfer",
1196
- "bridge --from <chain> --to <chain> --amount <n> --recipient <addr>": "CCTP bridge",
1197
- info: "Show all deposit/withdraw routes & limits",
1179
+ "deposit <exchange> <amount>": "Deposit USDC to exchange (pacifica, hyperliquid, lighter)",
1180
+ "withdraw <exchange> <amount>": "Withdraw USDC from exchange (pacifica, hyperliquid, lighter)",
1181
+ "transfer <amount> <address>": "HL internal transfer (instant)",
1182
+ "bridge chains": "List supported chains and USDC addresses",
1183
+ "bridge quote --from <chain> --to <chain> --amount <n>": "Get bridge quotes from all providers",
1184
+ "bridge send --from <chain> --to <chain> --amount <n> [--provider cctp|relay|debridge]": "Execute bridge (auto-cheapest by default)",
1185
+ "bridge exchange --from <ex> --to <ex> --amount <n>": "Bridge USDC between exchanges (shortcut)",
1186
+ "bridge status <orderId>": "Check bridge order status",
1187
+ "rebalance check": "Show balances across exchanges",
1188
+ "rebalance plan": "Calculate optimal rebalancing moves",
1189
+ "rebalance execute [--auto-bridge]": "Execute rebalancing (withdraw → bridge → deposit)",
1190
+ info: "Show all deposit/withdraw/bridge/rebalance routes & limits",
1198
1191
  },
1199
1192
  },
1200
1193
  portfolio: { usage: "perp portfolio", description: "Cross-exchange portfolio summary" },
1201
1194
  status: { usage: "perp portfolio", description: "Full account overview" },
1202
- health: { usage: "perp agent ping", description: "Exchange connectivity check" },
1195
+ health: { usage: "perp account balance --json", description: "Exchange connectivity check via balance fetch" },
1203
1196
  settings: {
1204
1197
  description: "CLI settings",
1205
1198
  subcommands: {
@@ -1207,16 +1200,7 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1207
1200
  referrals: { usage: "perp settings referrals on|off", description: "Toggle referral codes (no extra fees)" },
1208
1201
  },
1209
1202
  },
1210
- agent: {
1211
- description: "Agent discovery & execution",
1212
- subcommands: {
1213
- schema: { usage: "perp agent schema", description: "Full CLI schema as JSON" },
1214
- capabilities: { usage: "perp agent capabilities", description: "Capability list" },
1215
- plan: { usage: "perp agent plan '<goal>'", description: "Suggest commands for goal" },
1216
- exec: { usage: "perp agent exec <command...>", description: "Execute with JSON output" },
1217
- ping: { usage: "perp agent ping", description: "Health check" },
1218
- },
1219
- },
1203
+ // `agent` is now `wallet agent ...` — see `wallet.subcommands.agent` above.
1220
1204
  },
1221
1205
  tips: [
1222
1206
  "Always use --json for structured output when automating",
@@ -1255,12 +1239,30 @@ server.tool("trade_preview", "Preview a trade WITHOUT executing. Returns estimat
1255
1239
  const notional = estPrice ? Number(estPrice) * Number(size) : 0;
1256
1240
  const existingPos = positions.find(p => p.symbol.toUpperCase().includes(symbol.toUpperCase()));
1257
1241
  // Run validation
1242
+ // SSOT rule #2: validation failure must surface as a structured invalid
1243
+ // result, NOT be swallowed into a fake `{ valid: true }`. The previous
1244
+ // shape silently green-lit trades when the underlying balance/orderbook
1245
+ // fetches errored out.
1258
1246
  let validation;
1247
+ // For a preview the user has not yet committed leverage, so 1x is the
1248
+ // documented default exposed in the preview shape (no silent fallback —
1249
+ // the caller can override via existingPos.leverage when re-entering an
1250
+ // open position).
1251
+ const previewLeverage = existingPos?.leverage ?? 1;
1259
1252
  try {
1260
- validation = await validateTrade(adapter, { symbol, side, size: Number(size), leverage: existingPos?.leverage ?? 1 });
1261
- }
1262
- catch {
1263
- validation = { valid: true, checks: [], warnings: ["Validation unavailable — proceed with caution"] };
1253
+ validation = await validateTrade(adapter, { symbol, side, size: Number(size), leverage: previewLeverage });
1254
+ }
1255
+ catch (err) {
1256
+ const msg = err instanceof Error ? err.message : String(err);
1257
+ validation = {
1258
+ valid: false,
1259
+ checks: [{
1260
+ check: "symbol_valid",
1261
+ passed: false,
1262
+ message: `Validation could not run: ${msg}`,
1263
+ }],
1264
+ warnings: [],
1265
+ };
1264
1266
  }
1265
1267
  const preview = {
1266
1268
  order: { exchange, symbol, side, size, orderType, price: price ?? estPrice },
@@ -13,6 +13,32 @@ function ensureDir() {
13
13
  function genId() {
14
14
  return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
15
15
  }
16
+ /**
17
+ * Validate position side from a stream event. SSOT rule #2: the stream must
18
+ * deliver a real side, never silently default to "long" — defaulting can flip
19
+ * a short into a long in the position history and corrupt P&L analytics.
20
+ */
21
+ function requireSide(value, eventType, exchange, symbol) {
22
+ if (value === "long" || value === "short")
23
+ return value;
24
+ throw new Error(`position_${eventType} for ${exchange}:${symbol} has missing/invalid side ` +
25
+ `(received ${JSON.stringify(value)}); refusing to silently default. ` +
26
+ `Fix the upstream adapter to emit { side: "long" | "short" } on every event.`);
27
+ }
28
+ /**
29
+ * Validate a stringified numeric field from a stream event. SSOT rule #2: do
30
+ * NOT silently default missing realizedPnl to "0", because we cannot
31
+ * distinguish "trade closed flat" from "adapter forgot the field".
32
+ */
33
+ function requireNumericString(value, fieldName, eventType, exchange, symbol) {
34
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)))
35
+ return value;
36
+ if (typeof value === "number" && Number.isFinite(value))
37
+ return String(value);
38
+ throw new Error(`position_${eventType} for ${exchange}:${symbol} has missing/invalid ${fieldName} ` +
39
+ `(received ${JSON.stringify(value)}); refusing to silently default. ` +
40
+ `Fix the upstream adapter to emit a numeric ${fieldName}.`);
41
+ }
16
42
  /** Append a position record to the log */
17
43
  export function logPosition(record) {
18
44
  ensureDir();
@@ -154,7 +180,10 @@ export function attachPositionLogger(onEvent) {
154
180
  if (event.type === "position_opened") {
155
181
  const id = genId();
156
182
  const symbol = String(data.symbol ?? "");
157
- const side = String(data.side ?? "long");
183
+ if (!symbol) {
184
+ throw new Error(`position_opened on ${exchange} missing symbol; refusing to silently default. Raw data: ${JSON.stringify(data)}`);
185
+ }
186
+ const side = requireSide(data.side, "opened", exchange, symbol);
158
187
  const key = `${exchange}:${symbol}`;
159
188
  openPositions.set(key, { id, openedAt: ts, entryPrice: String(data.entryPrice ?? "") });
160
189
  logPosition({
@@ -172,9 +201,15 @@ export function attachPositionLogger(onEvent) {
172
201
  }
173
202
  else if (event.type === "position_updated") {
174
203
  const symbol = String(data.symbol ?? "");
175
- const side = String(data.side ?? "long");
204
+ if (!symbol) {
205
+ throw new Error(`position_updated on ${exchange} missing symbol; refusing to silently default. Raw data: ${JSON.stringify(data)}`);
206
+ }
207
+ const side = requireSide(data.side, "updated", exchange, symbol);
176
208
  const key = `${exchange}:${symbol}`;
177
209
  const tracked = openPositions.get(key);
210
+ // tracked?.id ?? genId() is NOT a fallback in the SSOT sense: it covers the
211
+ // legitimate case where the stream delivers an update for a position the
212
+ // logger never saw open (out-of-order delivery, mid-session attach).
178
213
  logPosition({
179
214
  id: tracked?.id ?? genId(),
180
215
  exchange,
@@ -194,12 +229,20 @@ export function attachPositionLogger(onEvent) {
194
229
  }
195
230
  else if (event.type === "position_closed") {
196
231
  const symbol = String(data.symbol ?? "");
197
- const side = String(data.side ?? "long");
232
+ if (!symbol) {
233
+ throw new Error(`position_closed on ${exchange} missing symbol; refusing to silently default. Raw data: ${JSON.stringify(data)}`);
234
+ }
235
+ const side = requireSide(data.side, "closed", exchange, symbol);
198
236
  const key = `${exchange}:${symbol}`;
199
237
  const tracked = openPositions.get(key);
200
238
  const openedAt = tracked?.openedAt ?? ts;
201
239
  const duration = new Date(ts).getTime() - new Date(openedAt).getTime();
202
- const realizedPnl = String(data.unrealizedPnl ?? "0");
240
+ // SSOT rule #2: do NOT alias unrealizedPnl as realizedPnl. They mean
241
+ // different things: unrealizedPnl is the snapshot before close, and on
242
+ // a real close event the upstream adapter must emit realizedPnl
243
+ // explicitly. Aliasing them would have a flat-close (realizedPnl=0)
244
+ // pretend to be the last unrealized number from the previous tick.
245
+ const realizedPnl = requireNumericString(data.realizedPnl, "realizedPnl", "closed", exchange, symbol);
203
246
  logPosition({
204
247
  id: tracked?.id ?? genId(),
205
248
  exchange,
@@ -26,14 +26,76 @@ export interface AlertSettings {
26
26
  /** Cooldown per symbol in minutes after alert fires (default: 60) */
27
27
  cooldownMin: number;
28
28
  }
29
+ export interface AgentMeta {
30
+ agentName: string;
31
+ agentWalletName: string;
32
+ /**
33
+ * EVM address of the agent wallet (required for Aster + Hyperliquid).
34
+ * For Pacifica (Phase 2c): kept as a sentinel zero-address since the
35
+ * curve is Solana/Ed25519; the canonical address lives in
36
+ * `agentSolanaAddress`. For Lighter (Phase 2d): populated with the master
37
+ * EVM address (since L1 ChangePubKey is signed by the master EVM key); the
38
+ * canonical agent identity lives in `publicKey + apiKeyIndex`.
39
+ * The `0x${string}` literal is preserved to avoid disrupting Aster/HL strict typing.
40
+ */
41
+ agentEvmAddress: `0x${string}`;
42
+ /** EVM address of the master/owner wallet — required as `user` field in Aster/HL requests. Populated by `agent approve` (Step 4). */
43
+ userEvmAddress: `0x${string}`;
44
+ /**
45
+ * Solana base58 public key of the agent wallet (Phase 2c — Pacifica only).
46
+ * Optional for Aster/HL since they live on EVM curves.
47
+ */
48
+ agentSolanaAddress?: string;
49
+ /**
50
+ * Solana base58 public key of the master/owner wallet (Phase 2c — Pacifica only).
51
+ * Optional for Aster/HL.
52
+ */
53
+ userSolanaAddress?: string;
54
+ /**
55
+ * Lighter slot index (Phase 2d — Lighter only).
56
+ * Range: [4, 254] inclusive. Slots 0-3 are reserved by the Lighter frontend.
57
+ * Selected at approve time as `max(existing slots) + 1`.
58
+ */
59
+ apiKeyIndex?: number;
60
+ /**
61
+ * Lighter agent secp256k1 public key (hex, no `0x` prefix) (Phase 2d — Lighter only).
62
+ * Canonical identity for the Lighter L2 hot-path. Cross-checked against
63
+ * `GET /api/v1/apikeys?account_index=N` during `wallet agent verify lighter`.
64
+ */
65
+ publicKey?: string;
66
+ /**
67
+ * Lighter L2 account index (Phase 2d — Lighter only).
68
+ * Resolved at approve time from L1 EVM address via the Lighter REST API
69
+ * and cached so subsequent verify/trade calls don't need to re-query.
70
+ */
71
+ accountIndex?: number;
72
+ masterWalletName: string;
73
+ owsApiKeyId: string;
74
+ owsPolicyId: string;
75
+ expiresAt: string;
76
+ approvedAt: string;
77
+ permissions: {
78
+ canPerpTrade: boolean;
79
+ canSpotTrade: boolean;
80
+ canWithdraw: boolean;
81
+ };
82
+ asterApprovalNonce: string;
83
+ status?: "active" | "partial";
84
+ }
85
+ export interface AgentsByExchange {
86
+ aster?: Record<string, AgentMeta>;
87
+ hyperliquid?: Record<string, AgentMeta>;
88
+ pacifica?: Record<string, AgentMeta>;
89
+ lighter?: Record<string, AgentMeta>;
90
+ }
29
91
  export interface Settings {
30
92
  /** Default exchange when -e flag is omitted */
31
93
  defaultExchange: string;
32
94
  /** Active OWS wallet name (used when no --ows or --wallet flag) */
33
95
  owsActiveWallet: string;
34
- /** Enable referral/builder codes (default: false — opt-in only) */
35
- referrals: boolean;
36
- /** Per-exchange referral codes (used when referrals=true) */
96
+ /** Per-exchange referral / builder codes (always applied per SSOT Rule #3:
97
+ * no opt-out — Pacifica builder code has always been mandatory; HL/LT
98
+ * aligned 2026-05-01). */
37
99
  referralCodes: {
38
100
  pacifica: string;
39
101
  hyperliquid: string;
@@ -48,6 +110,8 @@ export interface Settings {
48
110
  fees: Record<string, ExchangeFees>;
49
111
  /** Alert configuration */
50
112
  alerts: AlertSettings;
113
+ /** OWS-native agent wallets registered per exchange (Phase 2a: Aster only) */
114
+ agents?: AgentsByExchange;
51
115
  }
52
116
  export declare function loadSettings(): Settings;
53
117
  export declare function saveSettings(settings: Settings): void;
package/dist/settings.js CHANGED
@@ -5,7 +5,6 @@ const SETTINGS_FILE = resolve(PERP_DIR, "settings.json");
5
5
  const DEFAULTS = {
6
6
  defaultExchange: "",
7
7
  owsActiveWallet: "",
8
- referrals: false,
9
8
  referralCodes: {
10
9
  pacifica: "",
11
10
  hyperliquid: "HYPERCASH",
@@ -44,7 +43,6 @@ export function loadSettings() {
44
43
  return {
45
44
  defaultExchange: stored.defaultExchange ?? DEFAULTS.defaultExchange,
46
45
  owsActiveWallet: stored.owsActiveWallet ?? DEFAULTS.owsActiveWallet,
47
- referrals: stored.referrals ?? DEFAULTS.referrals,
48
46
  referralCodes: {
49
47
  pacifica: stored.referralCodes?.pacifica ?? DEFAULTS.referralCodes.pacifica,
50
48
  hyperliquid: stored.referralCodes?.hyperliquid ?? DEFAULTS.referralCodes.hyperliquid,
@@ -62,6 +60,7 @@ export function loadSettings() {
62
60
  cooldownMin: stored.alerts?.cooldownMin ?? DEFAULTS.alerts.cooldownMin,
63
61
  },
64
62
  fees,
63
+ agents: stored.agents && typeof stored.agents === "object" ? stored.agents : undefined,
65
64
  };
66
65
  }
67
66
  catch {
@@ -16,3 +16,17 @@ export declare class OwsEvmSigner implements EvmSigner {
16
16
  }>>, value: Record<string, unknown>): Promise<string>;
17
17
  signMessage(message: string | Uint8Array): Promise<string>;
18
18
  }
19
+ /**
20
+ * Normalize OWS signature output to canonical 65-byte hex (0x-prefixed).
21
+ *
22
+ * OWS may return the signature in two shapes depending on version:
23
+ * (A) 65-byte hex with v already embedded — use as-is
24
+ * (B) 64-byte hex (r+s only) + separate recoveryId — append v
25
+ *
26
+ * For shape B, recoveryId may be either canonical (27/28) or raw (0/1).
27
+ * If raw, add 27 to canonicalize.
28
+ */
29
+ export declare function canonicalizeOwsSignature(result: {
30
+ signature: string;
31
+ recoveryId?: number;
32
+ }): string;
@@ -39,11 +39,8 @@ export class OwsEvmSigner {
39
39
  domain,
40
40
  message: value,
41
41
  });
42
- const result = ows.signTypedData(this._walletName, "evm", typedData, this._passphrase);
43
- // OWS returns hex signature + recoveryId; combine to 65-byte EIP-712 sig
44
- const sig = result.signature.startsWith("0x") ? result.signature : `0x${result.signature}`;
45
- const v = result.recoveryId !== undefined ? result.recoveryId + 27 : 27;
46
- return `${sig}${v.toString(16).padStart(2, "0")}`;
42
+ const result = ows.signTypedData(this._walletName, "ethereum", typedData, this._passphrase);
43
+ return canonicalizeOwsSignature(result);
47
44
  }
48
45
  async signMessage(message) {
49
46
  const ows = loadOws();
@@ -51,12 +48,34 @@ export class OwsEvmSigner {
51
48
  ? message
52
49
  : Buffer.from(message).toString("hex");
53
50
  const encoding = typeof message === "string" ? "utf8" : "hex";
54
- const result = ows.signMessage(this._walletName, "evm", msgStr, this._passphrase, encoding);
55
- const sig = result.signature.startsWith("0x") ? result.signature : `0x${result.signature}`;
56
- const v = result.recoveryId !== undefined ? result.recoveryId + 27 : 27;
57
- return `${sig}${v.toString(16).padStart(2, "0")}`;
51
+ const result = ows.signMessage(this._walletName, "ethereum", msgStr, this._passphrase, encoding);
52
+ return canonicalizeOwsSignature(result);
58
53
  }
59
54
  }
55
+ /**
56
+ * Normalize OWS signature output to canonical 65-byte hex (0x-prefixed).
57
+ *
58
+ * OWS may return the signature in two shapes depending on version:
59
+ * (A) 65-byte hex with v already embedded — use as-is
60
+ * (B) 64-byte hex (r+s only) + separate recoveryId — append v
61
+ *
62
+ * For shape B, recoveryId may be either canonical (27/28) or raw (0/1).
63
+ * If raw, add 27 to canonicalize.
64
+ */
65
+ export function canonicalizeOwsSignature(result) {
66
+ const sigHex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
67
+ if (sigHex.length === 130) {
68
+ // Already 65-byte sig with embedded v — trust OWS
69
+ return `0x${sigHex}`;
70
+ }
71
+ if (sigHex.length !== 128) {
72
+ throw new Error(`Unexpected OWS signature length: ${sigHex.length} hex chars (expected 128 or 130)`);
73
+ }
74
+ // 64-byte r+s — append v from recoveryId
75
+ const rid = result.recoveryId ?? 0;
76
+ const v = rid >= 27 ? rid : rid + 27; // accept canonical or raw recoveryId
77
+ return `0x${sigHex}${v.toString(16).padStart(2, "0")}`;
78
+ }
60
79
  function inferEip712DomainType(key) {
61
80
  switch (key) {
62
81
  case "chainId": return "uint256";
@@ -16,13 +16,19 @@
16
16
  * - Saves 5-50+ bps vs raw market orders on thin books
17
17
  * - Predictable execution price
18
18
  *
19
- * Fallback: if the IOC limit fails, falls back to market order.
19
+ * SSOT rule #2: silent fallback is forbidden. The opt-in `fallback: true` flag
20
+ * is preserved for callers that explicitly want best-effort market substitution
21
+ * after IOC reject, but the DEFAULT is `false` — failure must surface as a
22
+ * thrown error, never a silent market sweep.
20
23
  */
21
24
  import type { ExchangeAdapter } from "./exchanges/index.js";
22
25
  export interface SmartOrderOpts {
23
26
  /** Extra ticks of tolerance beyond best price. Default: 1 */
24
27
  tickTolerance?: number;
25
- /** Fall back to market order if IOC limit fails. Default: true */
28
+ /**
29
+ * Opt-in: fall back to market order if IOC limit fails.
30
+ * SSOT rule #2 default: `false` — failure throws unless caller explicitly opts in.
31
+ */
26
32
  fallback?: boolean;
27
33
  /** Reduce only flag for closing positions. Default: false */
28
34
  reduceOnly?: boolean;