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
@@ -16,7 +16,10 @@
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
  /**
22
25
  * Infer tick size from orderbook prices.
@@ -77,12 +80,38 @@ function hasOrderError(result) {
77
80
  return true;
78
81
  return false;
79
82
  }
83
+ /**
84
+ * Best-effort extraction of the embedded reject reason for the thrown error
85
+ * message. Falls back to JSON.stringify so the caller always gets actionable
86
+ * context.
87
+ */
88
+ function describeOrderError(result) {
89
+ if (!result || typeof result !== "object")
90
+ return String(result);
91
+ const r = result;
92
+ const resp = r.response;
93
+ const data = resp?.data;
94
+ const statuses = data?.statuses;
95
+ if (Array.isArray(statuses) && statuses.length > 0 && statuses[0].error) {
96
+ return String(statuses[0].error);
97
+ }
98
+ if (typeof r.error === "string")
99
+ return r.error;
100
+ if (typeof r.message === "string")
101
+ return r.message;
102
+ try {
103
+ return JSON.stringify(result);
104
+ }
105
+ catch {
106
+ return String(result);
107
+ }
108
+ }
80
109
  /**
81
110
  * Execute a smart order at best bid/ask + 1 tick.
82
111
  * Drop-in replacement for `adapter.marketOrder()`.
83
112
  */
84
113
  export async function smartOrder(adapter, symbol, side, size, opts = {}) {
85
- const { tickTolerance = 1, fallback = true, reduceOnly = false } = opts;
114
+ const { tickTolerance = 1, fallback = false, reduceOnly = false } = opts;
86
115
  // 1. Fetch orderbook
87
116
  const book = await adapter.getOrderbook(symbol);
88
117
  // 2. Validate book has liquidity on the relevant side
@@ -111,15 +140,21 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
111
140
  });
112
141
  // Check for exchange-level rejection inside a "success" response
113
142
  // (e.g., Hyperliquid returns { status: "ok", response: { data: { statuses: [{ error: "..." }] } } })
114
- if (fallback && hasOrderError(result)) {
115
- const fallbackResult = await adapter.marketOrder(symbol, side, size);
116
- return {
117
- result: fallbackResult,
118
- method: "market_fallback",
119
- price: formattedPrice,
120
- bestBookPrice: bestEntry[0],
121
- tickSize: tick.toFixed(decimals),
122
- };
143
+ // SSOT rule #2: when fallback is opt-out (default), an embedded reject
144
+ // must throw — otherwise the caller would silently receive a "limit_ioc"
145
+ // result for a rejected order and treat the failure as success.
146
+ if (hasOrderError(result)) {
147
+ if (fallback) {
148
+ const fallbackResult = await adapter.marketOrder(symbol, side, size);
149
+ return {
150
+ result: fallbackResult,
151
+ method: "market_fallback",
152
+ price: formattedPrice,
153
+ bestBookPrice: bestEntry[0],
154
+ tickSize: tick.toFixed(decimals),
155
+ };
156
+ }
157
+ throw new Error(`smartOrder: exchange rejected IOC limit (${describeOrderError(result)})`);
123
158
  }
124
159
  return {
125
160
  result,
@@ -1,4 +1,4 @@
1
- import { symbolMatch } from "../utils.js";
1
+ import { symbolMatch, logSettledRejections } from "../utils.js";
2
2
  import { updateJobState } from "../jobs.js";
3
3
  import { fetchAllBalances, computeRebalancePlan, hasEnoughBalance } from "../rebalance.js";
4
4
  import { checkArbLiquidity } from "../liquidity.js";
@@ -6,6 +6,9 @@ import { computeAnnualSpread } from "../funding.js";
6
6
  import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../shared-api.js";
7
7
  import { computeMatchedSize, reconcileArbFills } from "../arb-sizing.js";
8
8
  async function fetchAllRates() {
9
+ // SSOT rule #2: each DEX is independent. allSettled keeps the comparison
10
+ // alive when one DEX errors out, while rejection reasons get logged to
11
+ // stderr (explicit failure, never silent).
9
12
  const [pacRates, hlRates, ltRates] = await Promise.allSettled([
10
13
  fetchPacificaPrices().then(assets => assets.map(p => ({
11
14
  exchange: "pacifica", symbol: p.symbol, fundingRate: p.funding, markPrice: p.mark,
@@ -20,14 +23,32 @@ async function fetchAllRates() {
20
23
  ]);
21
24
  const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
22
25
  const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
23
- return funding.map(fr => ({
24
- exchange: "lighter",
25
- symbol: fr.symbol || symMap.get(fr.marketId) || "",
26
- fundingRate: fr.rate,
27
- markPrice: fr.markPrice || priceMap.get(fr.marketId) || 0,
28
- }));
26
+ const out = [];
27
+ for (const fr of funding) {
28
+ const symbol = fr.symbol || symMap.get(fr.marketId) || "";
29
+ if (!symbol)
30
+ continue;
31
+ // Prefer fr.markPrice; fall back to orderBook last-trade as documented
32
+ // price source preference (NOT an error fallback).
33
+ const directMark = fr.markPrice;
34
+ const fallbackMark = priceMap.get(fr.marketId);
35
+ let markPrice;
36
+ if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
37
+ markPrice = directMark;
38
+ }
39
+ else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
40
+ markPrice = fallbackMark;
41
+ }
42
+ // SSOT rule #2: skip rows whose mark price cannot be resolved at all
43
+ // — never publish a 0 mark price downstream.
44
+ if (markPrice === undefined)
45
+ continue;
46
+ out.push({ exchange: "lighter", symbol, fundingRate: fr.rate, markPrice });
47
+ }
48
+ return out;
29
49
  })(),
30
50
  ]);
51
+ logSettledRejections([pacRates, hlRates, ltRates], ["pacifica", "hyperliquid", "lighter"], "funding-arb");
31
52
  return [
32
53
  ...(pacRates.status === "fulfilled" ? pacRates.value : []),
33
54
  ...(hlRates.status === "fulfilled" ? hlRates.value : []),
@@ -141,7 +162,7 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
141
162
  for (const move of plan.moves) {
142
163
  log(`[ARB] $${move.amount} ${move.from} → ${move.to}`);
143
164
  }
144
- log(`[ARB] Run 'perp rebalance execute' to rebalance.`);
165
+ log(`[ARB] Run 'perp funds rebalance execute' to rebalance.`);
145
166
  }
146
167
  }
147
168
  }
@@ -10,13 +10,26 @@ export async function validateTrade(adapter, params) {
10
10
  const checks = [];
11
11
  const warnings = [];
12
12
  const sym = params.symbol.toUpperCase();
13
- // Fetch market data, balance, positions, orderbook in parallel
13
+ // Fetch market data, balance, positions, orderbook in parallel.
14
+ // SSOT rule #2: errors must propagate, never silently substitute defaults.
15
+ // getMarkets retains a tracked-error wrapper so the failed-load case surfaces
16
+ // a structured symbol_valid check (not a generic exception).
17
+ let marketsError = null;
14
18
  const [markets, balance, positions, orderbook] = await Promise.all([
15
- adapter.getMarkets().catch(() => []),
16
- adapter.getBalance().catch(() => ({ equity: "0", available: "0", marginUsed: "0", unrealizedPnl: "0" })),
17
- adapter.getPositions().catch(() => []),
18
- params.type !== "limit" ? adapter.getOrderbook(sym).catch(() => ({ bids: [], asks: [] })) : Promise.resolve({ bids: [], asks: [] }),
19
+ adapter.getMarkets().catch((e) => {
20
+ marketsError = e instanceof Error ? e.message : String(e);
21
+ return [];
22
+ }),
23
+ adapter.getBalance(),
24
+ adapter.getPositions(),
25
+ params.type !== "limit"
26
+ ? adapter.getOrderbook(sym)
27
+ : Promise.resolve({ bids: [], asks: [] }),
19
28
  ]);
29
+ if (markets.length === 0 && marketsError) {
30
+ checks.push({ check: "symbol_valid", passed: false, message: `Failed to load markets from ${adapter.name}: ${marketsError}` });
31
+ return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
32
+ }
20
33
  // 1. Symbol validity (handle BTC vs BTC-PERP suffix variants)
21
34
  const market = markets.find(m => {
22
35
  const ms = m.symbol.toUpperCase();
@@ -30,9 +43,21 @@ export async function validateTrade(adapter, params) {
30
43
  return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
31
44
  }
32
45
  const markPrice = Number(market.markPrice);
46
+ if (params.type === "limit" && (params.price === undefined || !Number.isFinite(params.price))) {
47
+ throw new Error(`Limit order requires explicit price (received ${params.price}); refusing silent mark-price substitution.`);
48
+ }
49
+ if (markPrice <= 0 && params.price === undefined) {
50
+ throw new Error(`No mark price available for ${sym} on ${adapter.name} and no override price supplied; cannot validate trade.`);
51
+ }
33
52
  const price = params.price ?? markPrice;
34
53
  const notional = params.size * price;
54
+ if (params.leverage !== undefined && (!Number.isFinite(params.leverage) || params.leverage <= 0)) {
55
+ throw new Error(`Leverage must be a positive number (received ${params.leverage}).`);
56
+ }
35
57
  const leverage = params.leverage ?? market.maxLeverage;
58
+ if (!Number.isFinite(leverage) || leverage <= 0) {
59
+ throw new Error(`No usable leverage for ${sym} (resolved=${leverage}); supply params.leverage explicitly.`);
60
+ }
36
61
  const marginRequired = notional / leverage;
37
62
  // 2. Balance check
38
63
  const available = Number(balance.available);
package/dist/utils.d.ts CHANGED
@@ -38,3 +38,13 @@ export declare function jsonError(code: string, message: string, meta?: Partial<
38
38
  * In JSON mode, errors are returned as JSON instead of crashing.
39
39
  */
40
40
  export declare function withJsonErrors<T>(isJson: boolean, fn: () => Promise<T>, exchange?: string): Promise<T | undefined>;
41
+ /**
42
+ * Log each rejected outcome from Promise.allSettled to stderr with a
43
+ * per-source label. SSOT rule #2 helper: turn a quiet rejection inside a
44
+ * multi-DEX comparison into an explicit one-line failure on stderr.
45
+ *
46
+ * @param settled Outcomes from Promise.allSettled.
47
+ * @param labels Source label per index (e.g. ["pacifica", "hyperliquid", ...]).
48
+ * @param prefix Bracketed prefix prepended to each line (e.g. "arb-auto").
49
+ */
50
+ export declare function logSettledRejections(settled: PromiseSettledResult<unknown>[], labels: string[], prefix: string): void;
package/dist/utils.js CHANGED
@@ -221,3 +221,22 @@ export async function withJsonErrors(isJson, fn, exchange) {
221
221
  return undefined;
222
222
  }
223
223
  }
224
+ /**
225
+ * Log each rejected outcome from Promise.allSettled to stderr with a
226
+ * per-source label. SSOT rule #2 helper: turn a quiet rejection inside a
227
+ * multi-DEX comparison into an explicit one-line failure on stderr.
228
+ *
229
+ * @param settled Outcomes from Promise.allSettled.
230
+ * @param labels Source label per index (e.g. ["pacifica", "hyperliquid", ...]).
231
+ * @param prefix Bracketed prefix prepended to each line (e.g. "arb-auto").
232
+ */
233
+ export function logSettledRejections(settled, labels, prefix) {
234
+ for (let i = 0; i < settled.length; i++) {
235
+ const r = settled[i];
236
+ if (r.status === "rejected") {
237
+ const reason = r.reason instanceof Error ? r.reason.message : String(r.reason);
238
+ // eslint-disable-next-line no-console
239
+ console.error(`[${prefix}] ${labels[i] ?? `index-${i}`} fetch failed: ${reason}`);
240
+ }
241
+ }
242
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@modelcontextprotocol/sdk": "^1.27.1",
48
48
  "@msgpack/msgpack": "^3.1.3",
49
- "@open-wallet-standard/core": "1.2.0",
49
+ "@open-wallet-standard/core": "1.3.2",
50
50
  "@solana/web3.js": "^1.98.0",
51
51
  "@types/react": "^19.2.14",
52
52
  "bs58": "^6.0.0",
@@ -99,20 +99,30 @@ perp --json arb close ETH # sells spot + buys back perp
99
99
 
100
100
  Spot exchanges: HL, LT. Pacifica and Aster are perp-only.
101
101
 
102
- ## Bot Engine (19 Strategies)
102
+ ## Strategy Engine (19 bot algorithms + nested scripted plans)
103
103
 
104
104
  ```bash
105
- perp bot list-strategies # list all 19 strategies
106
- perp bot run <strategy> [symbol] # run any registered strategy
107
- perp bot run funding-auto # multi-exchange funding arb
108
- perp bot apex [symbol] # APEX orchestrator (Radar+Pulse+Guard)
109
- perp bot reflect # performance analysis (win rate, fees, PnL)
110
- perp bot preset-list # list strategy presets
111
- perp bot preset <name> [symbol] # run from preset
112
- perp bot start <config.yaml> # start from YAML/JSON config
113
- perp bot quick-grid <symbol> # quick grid bot
114
- perp bot quick-dca <symbol> <side> <amount> <interval>
115
- perp bot quick-arb # quick funding arb
105
+ perp strategy list-strategies # list all 19 strategies
106
+ perp strategy run <strategy> [symbol] # run any registered strategy
107
+ perp strategy run funding-auto # multi-exchange funding arb
108
+ perp strategy apex [symbol] # APEX orchestrator (Radar+Pulse+Guard)
109
+ perp strategy reflect # performance analysis (win rate, fees, PnL)
110
+ perp strategy preset-list # list strategy presets
111
+ perp strategy preset <name> [symbol] # run from preset
112
+ perp strategy start <config.yaml> # start from YAML/JSON config
113
+ perp strategy quick-grid <symbol> # quick grid bot
114
+ perp strategy quick-dca <symbol> <side> <amount> <interval>
115
+ perp strategy quick-arb # quick funding arb
116
+
117
+ # Scripted plans (one-shot multi-step execution)
118
+ perp strategy plan example # show plan format
119
+ perp --json strategy plan validate <file> # validate before run
120
+ perp --json strategy plan execute <file> --dry-run # dry-run plan
121
+
122
+ # Background process supervisor (tmux)
123
+ perp background list # list running jobs
124
+ perp background stop <id> # stop a job
125
+ perp background logs <id> -f # follow logs
116
126
  ```
117
127
 
118
128
  **Strategies:** grid, dca, funding-arb, funding-arb-v2, funding-auto, basis-arb, simple-mm, engine-mm, avellaneda-mm, regime-mm, grid-mm, liquidation-mm, momentum-breakout, mean-reversion, aggressive-taker, hedge-agent, rfq-agent, claude-agent, apex
@@ -136,7 +146,6 @@ perp --json -e <EX> trade cancel <SYM> # cancel all orders for
136
146
  ## Agent Tools
137
147
 
138
148
  ```bash
139
- perp --json agent schema # discover all commands (don't guess)
140
149
  perp --json -e <EX> trade check <SYM> <SIDE> <SIZE> # pre-validate trade
141
150
  perp --json portfolio # balances + positions + risk level
142
151
  perp --json --fields totalEquity,positions portfolio # filtered output
@@ -172,7 +181,7 @@ Response format: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "
172
181
  | `SYMBOL_NOT_FOUND` | `market list` to verify symbol |
173
182
  | `SIZE_TOO_SMALL` | `market info <SYM>` for min size |
174
183
  | `DUPLICATE_ORDER` | already submitted — check positions |
175
- | Lighter `invalid signature` | `manage setup-api-key` to regenerate |
184
+ | Lighter `invalid signature` | `perp wallet agent approve lighter` to regenerate |
176
185
 
177
186
  ## Examples
178
187
 
@@ -81,12 +81,14 @@ perp --json arb scan --min 5
81
81
  ```
82
82
 
83
83
  ### Lighter API Key Setup
84
- Lighter uses a separate API key for trading, but **this is handled automatically**.
85
- When `LIGHTER_PRIVATE_KEY` is set (env var or `wallet set`), the CLI auto-generates and saves the API key on first use.
84
+ Lighter uses a separate API key for trading. There are two paths:
86
85
 
87
- If auto-setup fails (e.g. no ETH for gas on Lighter chain), retry manually:
86
+ 1. **Auto-setup (default)**: When `LIGHTER_PRIVATE_KEY` is set (env var or `wallet set`), the CLI auto-generates and saves the API key (slot 4) on first use.
87
+ 2. **Managed agent (recommended for long-running agents)**: `perp wallet agent approve lighter --master <wallet> --api-key-index <slot>` registers a per-agent slot (4-254) with explicit expiry tracking + 3-tier signer routing.
88
+
89
+ If auto-setup fails (e.g. transient network error), retry manually:
88
90
  ```bash
89
- perp --json -e lighter manage setup-api-key
91
+ perp --json wallet agent approve lighter --master <wallet>
90
92
  ```
91
93
 
92
94
  ### Using the Same EVM Key for Multiple Exchanges
@@ -69,7 +69,11 @@ perp --json trade market <SYM> buy <SIZE> --split # split via market command f
69
69
  perp --json trade market <SYM> buy <SIZE> --split --max-slippage 0.5
70
70
  ```
71
71
 
72
- ## Funds (Deposit, Withdraw, Transfer, Bridge)
72
+ ## Funds (Deposit, Withdraw, Transfer, Bridge, Rebalance)
73
+
74
+ All fund movement lives under `perp funds`.
75
+
76
+ ### Exchange in/out
73
77
  ```bash
74
78
  perp --json funds deposit pacifica <AMOUNT>
75
79
  perp --json funds deposit hyperliquid <AMOUNT>
@@ -79,19 +83,26 @@ perp --json funds deposit lighter info # all Lighter deposi
79
83
  perp --json funds withdraw pacifica <AMOUNT>
80
84
  perp --json funds withdraw hyperliquid <AMOUNT>
81
85
  perp --json funds withdraw lighter <AMOUNT>
82
- perp --json funds transfer <AMOUNT> <ADDRESS> # HL internal transfer (instant)
83
- perp --json funds bridge --from <CHAIN> --to <CHAIN> --amount <AMT> --recipient <ADDR> # CCTP bridge
84
- perp --json funds bridge-status --hash <HASH> # check CCTP bridge status
85
- perp --json funds info # combined deposit & withdrawal info
86
+ perp --json funds transfer <AMOUNT> <ADDRESS> # HL internal transfer (instant)
87
+ perp --json funds info # combined routes & limits
88
+ ```
89
+
90
+ ### Cross-chain bridge (multi-provider router: cctp / relay / debridge — auto-cheapest)
91
+ ```bash
92
+ perp --json funds bridge chains # supported chains
93
+ perp --json funds bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT>
94
+ perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> # auto-cheapest
95
+ perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> --provider cctp
96
+ perp --json funds bridge exchange --from <EX> --to <EX> --amount <AMT> # between exchanges
97
+ perp --json funds bridge status <ORDER_ID>
86
98
  ```
87
99
 
88
- ## Bridge (Cross-chain USDC)
100
+ ### Inter-exchange rebalance (orchestrated withdraw → bridge → deposit)
89
101
  ```bash
90
- perp --json bridge chains # supported chains
91
- perp --json bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT>
92
- perp --json bridge send --from <CHAIN> --to <CHAIN> --amount <AMT>
93
- perp --json bridge exchange --from <EX> --to <EX> --amount <AMT>
94
- perp --json bridge status <ORDER_ID>
102
+ perp --json funds rebalance check # balances across exchanges
103
+ perp --json funds rebalance plan # compute optimal moves
104
+ perp --json funds rebalance execute --auto-bridge # withdraw → bridge → deposit
105
+ perp --json funds rebalance execute --withdraw-only # only withdraw, manual rest
95
106
  ```
96
107
 
97
108
  ## Arbitrage
@@ -137,7 +148,6 @@ perp --json risk liquidation-distance # % distance from liquidation for AL
137
148
  perp --json risk limits # view current risk limits
138
149
  perp --json risk limits --min-liq-distance 30 --max-leverage 5 # set risk limits
139
150
  perp --json risk check --notional 1000 --leverage 3 # pre-trade risk check
140
- perp --json agent ping # exchange connectivity & latency
141
151
  perp --json history summary # trading performance
142
152
  perp --json history pnl # P&L breakdown by exchange
143
153
  perp --json history funding # funding payment aggregation
@@ -148,24 +158,34 @@ perp --json history perf --period summary # performance summary stats
148
158
  perp --json history list # execution audit trail
149
159
  ```
150
160
 
151
- ## Automated Strategies (bot — 19 strategies)
161
+ ## Automated Strategies (strategy — 19 algorithms + nested scripted plans)
152
162
  ```bash
153
- perp --json bot list-strategies # list all 19 strategies
154
- perp --json bot run <STRATEGY> [SYMBOL] # run any registered strategy
155
- perp --json bot run funding-auto # multi-exchange funding arb (no symbol needed)
156
- perp --json bot apex [SYMBOL] # APEX autonomous orchestrator (Radar+Pulse+Guard)
157
- perp --json bot reflect # trading performance analysis (win rate, fees, PnL)
158
- perp --json bot preset-list # list strategy presets
159
- perp --json bot preset <NAME> [SYMBOL] # run from preset
160
- perp --json bot twap <SYMBOL> <SIDE> <SIZE> <DURATION> # TWAP execution
161
- perp --json bot grid <SYMBOL> --range <PCT> --grids <N> --size <USD>
162
- perp --json bot dca <SYMBOL> <SIDE> <AMOUNT> <INTERVAL>
163
- perp --json bot trailing-stop <SYMBOL> # trailing stop with callback %
164
- perp --json bot funding-arb # auto funding arb
165
- perp --json bot quick-grid <SYMBOL> # quick grid bot
166
- perp --json bot quick-arb # quick arb bot
167
- perp --json jobs list # list running jobs
168
- perp --json jobs stop <ID> # stop a job
163
+ perp --json strategy list-strategies # list all 19 strategies
164
+ perp --json strategy run <STRATEGY> [SYMBOL] # run any registered strategy
165
+ perp --json strategy run funding-auto # multi-exchange funding arb (no symbol needed)
166
+ perp --json strategy apex [SYMBOL] # APEX autonomous orchestrator (Radar+Pulse+Guard)
167
+ perp --json strategy reflect # trading performance analysis (win rate, fees, PnL)
168
+ perp --json strategy preset-list # list strategy presets
169
+ perp --json strategy preset <NAME> [SYMBOL] # run from preset
170
+ perp --json strategy twap <SYMBOL> <SIDE> <SIZE> <DURATION> # TWAP execution
171
+ perp --json strategy grid <SYMBOL> --range <PCT> --grids <N> --size <USD>
172
+ perp --json strategy dca <SYMBOL> <SIDE> <AMOUNT> <INTERVAL>
173
+ perp --json strategy trailing-stop <SYMBOL> # trailing stop with callback %
174
+ perp --json strategy funding-arb # auto funding arb
175
+ perp --json strategy quick-grid <SYMBOL> # quick grid bot
176
+ perp --json strategy quick-arb # quick arb bot
177
+
178
+ # Scripted execution plans (one-shot multi-step)
179
+ perp --json strategy plan example # show plan format
180
+ perp --json strategy plan validate <FILE> # validate plan
181
+ perp --json strategy plan execute <FILE> --dry-run # dry-run plan
182
+
183
+ # Background process supervisor (tmux)
184
+ perp --json background list # list running jobs
185
+ perp --json background stop <ID> # stop a job
186
+ perp --json background logs <ID> # view job logs
187
+ perp --json background remove <ID> # remove a job entry
188
+ perp --json background clean # remove all stopped/done jobs
169
189
  ```
170
190
 
171
191
  ### Available Strategies
@@ -175,10 +195,11 @@ Arbitrage: `funding-arb`, `funding-auto`, `basis-arb`
175
195
  Infrastructure: `hedge-agent`, `rfq-agent`, `claude-agent`
176
196
  Classic: `grid`, `dca`, `twap`, `apex`
177
197
 
178
- ## Command Discovery
198
+ ## Agent Wallet Management
179
199
  ```bash
180
- perp schema # Full CLI schema as JSON
181
- perp agent capabilities # High-level capability list
182
- perp agent plan "<goal>" # Suggest command sequence for a goal
183
- perp agent ping # Health check all exchanges
200
+ perp wallet agent approve <exchange> [--master <name>] # Register an agent wallet
201
+ perp wallet agent list [exchange] # List registered agents
202
+ perp wallet agent revoke <exchange> <agentName> # Revoke an agent
203
+ perp wallet agent rotate <exchange> [agentName] # Revoke + re-approve same name
204
+ perp wallet agent verify [exchange] [agentName] # Verify via DEX query API
184
205
  ```
@@ -41,12 +41,12 @@ else
41
41
  add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> <key>"
42
42
  fi
43
43
 
44
- # 3. Exchange connectivity
45
- HEALTH_OUT=$($PERP --json agent ping 2>/dev/null || echo '{"ok":false}')
44
+ # 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
45
+ HEALTH_OUT=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
46
46
  if echo "$HEALTH_OUT" | grep -q '"ok":true'; then
47
- add_check "connectivity" "pass" "Exchanges reachable"
47
+ add_check "connectivity" "pass" "Exchange reachable"
48
48
  else
49
- add_check "connectivity" "warn" "Some exchanges unreachable"
49
+ add_check "connectivity" "warn" "Exchange unreachable"
50
50
  fi
51
51
 
52
52
  # 4. Portfolio & balances
@@ -38,8 +38,8 @@ fi
38
38
  # 2. Run appropriate scan
39
39
  SCAN_RESULT=$($PERP --json arb scan --mode "$MODE" --min "$MIN_SPREAD" 2>/dev/null || echo '{"ok":false}')
40
40
 
41
- # 3. Get exchange health for context
42
- HEALTH=$($PERP --json agent ping 2>/dev/null || echo '{"ok":false}')
41
+ # 3. Get exchange health for context (proxy via market prices fetch)
42
+ HEALTH=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
43
43
 
44
44
  if $JSON_MODE; then
45
45
  cat <<EOF
@@ -1 +0,0 @@
1
- export {};
@@ -1,107 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- /**
3
- * Tests for the alert price merging logic (extracted from alert.ts).
4
- * Validates the fix for the price map key inconsistency bug.
5
- */
6
- function mergePrices(pacData, hlData) {
7
- const map = new Map();
8
- if (pacData) {
9
- for (const p of pacData) {
10
- map.set(`pac:${p.symbol}`, Number(p.mark));
11
- }
12
- }
13
- if (hlData) {
14
- for (const [symbol, price] of Object.entries(hlData)) {
15
- map.set(`hl:${symbol}`, Number(price));
16
- }
17
- }
18
- // Merge into bare symbol keys: prefer pac price, fallback to hl
19
- const allSymbols = new Set();
20
- for (const k of map.keys()) {
21
- if (k.includes(":"))
22
- allSymbols.add(k.split(":")[1]);
23
- }
24
- for (const sym of allSymbols) {
25
- const pacPrice = map.get(`pac:${sym}`);
26
- const hlPrice = map.get(`hl:${sym}`);
27
- map.set(sym, pacPrice ?? hlPrice ?? 0);
28
- }
29
- return map;
30
- }
31
- describe("Alert price merging", () => {
32
- it("prefers Pacifica price when both available", () => {
33
- const map = mergePrices([{ symbol: "BTC", mark: "100000" }], { BTC: "99900" });
34
- expect(map.get("BTC")).toBe(100000);
35
- expect(map.get("pac:BTC")).toBe(100000);
36
- expect(map.get("hl:BTC")).toBe(99900);
37
- });
38
- it("falls back to HL price when pac unavailable", () => {
39
- const map = mergePrices(null, { ETH: "3500" });
40
- expect(map.get("ETH")).toBe(3500);
41
- expect(map.has("pac:ETH")).toBe(false);
42
- expect(map.get("hl:ETH")).toBe(3500);
43
- });
44
- it("handles Pacifica-only symbols", () => {
45
- const map = mergePrices([{ symbol: "SOL", mark: "150" }], {});
46
- expect(map.get("SOL")).toBe(150);
47
- });
48
- it("handles empty data", () => {
49
- const map = mergePrices(null, null);
50
- expect(map.size).toBe(0);
51
- });
52
- it("handles multiple symbols from both sources", () => {
53
- const map = mergePrices([
54
- { symbol: "BTC", mark: "100000" },
55
- { symbol: "ETH", mark: "3500" },
56
- ], {
57
- BTC: "99900",
58
- ETH: "3490",
59
- SOL: "150",
60
- });
61
- // Pac preferred for BTC and ETH
62
- expect(map.get("BTC")).toBe(100000);
63
- expect(map.get("ETH")).toBe(3500);
64
- // HL-only for SOL
65
- expect(map.get("SOL")).toBe(150);
66
- });
67
- it("bare symbol key is always set (no orphaned prefixed keys)", () => {
68
- const map = mergePrices([{ symbol: "DOGE", mark: "0.15" }], { DOGE: "0.14", ARB: "1.20" });
69
- // Every symbol with a prefixed key should have a bare key
70
- for (const k of map.keys()) {
71
- if (k.includes(":")) {
72
- const bare = k.split(":")[1];
73
- expect(map.has(bare)).toBe(true);
74
- }
75
- }
76
- });
77
- });
78
- describe("Alert trigger conditions", () => {
79
- it("fires price above alert", () => {
80
- const currentPrice = 105000;
81
- const threshold = 100000;
82
- const condition = "above";
83
- expect(condition === "above" && currentPrice >= threshold).toBe(true);
84
- });
85
- it("does NOT fire price above when below", () => {
86
- const currentPrice = 95000;
87
- const threshold = 100000;
88
- const condition = "above";
89
- expect(condition === "above" && currentPrice >= threshold).toBe(false);
90
- });
91
- it("fires price below alert", () => {
92
- const currentPrice = 95000;
93
- const threshold = 100000;
94
- const condition = "below";
95
- expect(condition === "below" && currentPrice <= threshold).toBe(true);
96
- });
97
- it("fires funding spread alert", () => {
98
- const spread = 35; // annual %
99
- const alertThreshold = 30;
100
- expect(spread >= alertThreshold).toBe(true);
101
- });
102
- it("does NOT fire funding spread below threshold", () => {
103
- const spread = 25;
104
- const alertThreshold = 30;
105
- expect(spread >= alertThreshold).toBe(false);
106
- });
107
- });
@@ -1 +0,0 @@
1
- export {};