perp-cli 0.15.0 → 0.16.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 (154) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +129 -535
  4. package/README.zh.md +243 -0
  5. package/dist/api/public/lighter.d.ts +5 -0
  6. package/dist/api/public/lighter.js +5 -0
  7. package/dist/api/public/pacifica.js +2 -2
  8. package/dist/arb/sizing.js +4 -2
  9. package/dist/arb/state.d.ts +6 -0
  10. package/dist/balance-by-address.d.ts +39 -0
  11. package/dist/balance-by-address.js +44 -0
  12. package/dist/bot/quote-rules.js +1 -1
  13. package/dist/bot/strategies/funding-arb-strategy.js +10 -3
  14. package/dist/bot/strategies/funding-arb-utils.d.ts +14 -2
  15. package/dist/bot/strategies/funding-arb-utils.js +48 -15
  16. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  17. package/dist/bot/strategies/spot-perp-arb-strategy.js +4 -2
  18. package/dist/bot/strategies/spot-perp-executor.js +2 -2
  19. package/dist/cli-program.js +1 -1
  20. package/dist/commands/account.js +168 -24
  21. package/dist/commands/arb/index.js +19 -77
  22. package/dist/commands/arb-auto.d.ts +24 -0
  23. package/dist/commands/arb-auto.js +155 -81
  24. package/dist/commands/arb.d.ts +8 -0
  25. package/dist/commands/arb.js +49 -23
  26. package/dist/commands/backtest.js +23 -21
  27. package/dist/commands/market.d.ts +48 -0
  28. package/dist/commands/market.js +241 -78
  29. package/dist/commands/options.d.ts +5 -0
  30. package/dist/commands/options.js +20 -3
  31. package/dist/commands/pacifica-earn.js +8 -21
  32. package/dist/commands/portfolio.d.ts +16 -0
  33. package/dist/commands/portfolio.js +51 -8
  34. package/dist/commands/trade.js +4 -4
  35. package/dist/cross-chain-margin.d.ts +3 -1
  36. package/dist/cross-chain-margin.js +4 -2
  37. package/dist/dashboard/server.js +2 -2
  38. package/dist/dashboard/ui.js +9 -6
  39. package/dist/errors.d.ts +5 -0
  40. package/dist/errors.js +10 -0
  41. package/dist/exchanges/address.d.ts +8 -0
  42. package/dist/exchanges/address.js +47 -0
  43. package/dist/exchanges/arcus/adapter.d.ts +10 -6
  44. package/dist/exchanges/arcus/adapter.js +33 -6
  45. package/dist/exchanges/arcus/public.js +3 -2
  46. package/dist/exchanges/aster.d.ts +27 -7
  47. package/dist/exchanges/aster.js +124 -42
  48. package/dist/exchanges/canonical-symbols.d.ts +7 -0
  49. package/dist/exchanges/canonical-symbols.js +20 -0
  50. package/dist/exchanges/capabilities.d.ts +2 -0
  51. package/dist/exchanges/capabilities.js +2 -1
  52. package/dist/exchanges/derive/options.d.ts +5 -0
  53. package/dist/exchanges/derive/options.js +20 -1
  54. package/dist/exchanges/extended/adapter.d.ts +16 -2
  55. package/dist/exchanges/extended/adapter.js +96 -34
  56. package/dist/exchanges/extended/public.d.ts +3 -0
  57. package/dist/exchanges/extended/public.js +9 -2
  58. package/dist/exchanges/grvt/adapter.d.ts +16 -2
  59. package/dist/exchanges/grvt/adapter.js +98 -21
  60. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  61. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  62. package/dist/exchanges/hyperliquid-spot.d.ts +8 -0
  63. package/dist/exchanges/hyperliquid-spot.js +20 -12
  64. package/dist/exchanges/hyperliquid.d.ts +180 -13
  65. package/dist/exchanges/hyperliquid.js +487 -185
  66. package/dist/exchanges/interface.d.ts +64 -8
  67. package/dist/exchanges/interface.js +31 -0
  68. package/dist/exchanges/lighter-public.d.ts +66 -11
  69. package/dist/exchanges/lighter-public.js +249 -81
  70. package/dist/exchanges/lighter-spot.d.ts +7 -1
  71. package/dist/exchanges/lighter-spot.js +99 -90
  72. package/dist/exchanges/lighter.d.ts +70 -11
  73. package/dist/exchanges/lighter.js +285 -92
  74. package/dist/exchanges/ondo/adapter.d.ts +34 -8
  75. package/dist/exchanges/ondo/adapter.js +73 -16
  76. package/dist/exchanges/ondo/public.d.ts +27 -2
  77. package/dist/exchanges/ondo/public.js +63 -31
  78. package/dist/exchanges/outcome-interface.d.ts +4 -0
  79. package/dist/exchanges/pacifica-spot.d.ts +6 -0
  80. package/dist/exchanges/pacifica-spot.js +23 -15
  81. package/dist/exchanges/pacifica-vaults.d.ts +9 -0
  82. package/dist/exchanges/pacifica-vaults.js +14 -1
  83. package/dist/exchanges/pacifica.d.ts +77 -6
  84. package/dist/exchanges/pacifica.js +321 -130
  85. package/dist/exchanges/public-balances.d.ts +25 -0
  86. package/dist/exchanges/public-balances.js +158 -0
  87. package/dist/exchanges/public-positions.d.ts +43 -0
  88. package/dist/exchanges/public-positions.js +154 -0
  89. package/dist/exchanges/qfex/adapter.d.ts +52 -9
  90. package/dist/exchanges/qfex/adapter.js +74 -9
  91. package/dist/exchanges/qfex/public.d.ts +62 -9
  92. package/dist/exchanges/qfex/public.js +128 -22
  93. package/dist/exchanges/qfex/ws-transport.d.ts +1 -1
  94. package/dist/exchanges/risex/adapter.d.ts +19 -7
  95. package/dist/exchanges/risex/adapter.js +58 -12
  96. package/dist/exchanges/spot-factory.js +1 -1
  97. package/dist/exchanges/txflow/adapter.d.ts +14 -7
  98. package/dist/exchanges/txflow/adapter.js +51 -19
  99. package/dist/exchanges/txflow/client.js +3 -1
  100. package/dist/exchanges/txflow/public.d.ts +12 -1
  101. package/dist/exchanges/txflow/public.js +82 -11
  102. package/dist/exchanges/unknown-symbol.d.ts +6 -1
  103. package/dist/exchanges/unknown-symbol.js +7 -2
  104. package/dist/exchanges/variational/adapter.d.ts +2 -6
  105. package/dist/exchanges/venue-types.d.ts +46 -3
  106. package/dist/exchanges/venue-types.js +80 -9
  107. package/dist/exchanges/venue.d.ts +99 -3
  108. package/dist/exchanges/venue.js +72 -0
  109. package/dist/exchanges/venues/hyperliquid.js +9 -0
  110. package/dist/exchanges/venues/lighter-rh.js +13 -3
  111. package/dist/exchanges/venues/lighter.js +13 -3
  112. package/dist/exchanges/venues/pacifica.js +9 -0
  113. package/dist/exchanges/venues/risex.js +15 -0
  114. package/dist/funding/legacy-public.d.ts +5 -3
  115. package/dist/funding/legacy-public.js +33 -37
  116. package/dist/funding/rates.d.ts +54 -5
  117. package/dist/funding/rates.js +49 -13
  118. package/dist/liquidity.d.ts +3 -1
  119. package/dist/liquidity.js +8 -4
  120. package/dist/mcp-pacifica-earn.js +11 -5
  121. package/dist/mcp-server.js +104 -15
  122. package/dist/pacifica/client.d.ts +2 -1
  123. package/dist/pacifica/client.js +11 -0
  124. package/dist/positions-by-address.d.ts +34 -0
  125. package/dist/positions-by-address.js +46 -0
  126. package/dist/strategies/funding-arb.js +35 -62
  127. package/dist/utils.d.ts +11 -1
  128. package/dist/venue-health.js +1 -1
  129. package/dist/ws/arcus-market.d.ts +9 -0
  130. package/dist/ws/arcus-market.js +9 -0
  131. package/dist/ws/aster-market.d.ts +33 -0
  132. package/dist/ws/aster-market.js +30 -0
  133. package/dist/ws/index.d.ts +30 -9
  134. package/dist/ws/index.js +180 -15
  135. package/dist/ws/lighter-market.d.ts +14 -0
  136. package/dist/ws/lighter-market.js +13 -0
  137. package/dist/ws/node.d.ts +30 -9
  138. package/dist/ws/node.js +204 -16
  139. package/dist/ws/unclassified.d.ts +16 -0
  140. package/dist/ws/unclassified.js +43 -0
  141. package/package.json +15 -2
  142. package/skills/perp-cli/SKILL.md +97 -33
  143. package/skills/perp-cli/package.json +15 -2
  144. package/skills/perp-cli/references/agent-operations.md +15 -12
  145. package/skills/perp-cli/references/commands.md +67 -12
  146. package/skills/perp-cli/references/strategies.md +38 -3
  147. package/skills/perp-cli/references/ws-library.md +55 -24
  148. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  149. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  150. package/skills/perp-cli/scripts/preflight.sh +27 -19
  151. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  152. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  153. package/dist/cli-spec.d.ts +0 -46
  154. package/dist/cli-spec.js +0 -81
@@ -1,56 +1,70 @@
1
1
  #!/bin/bash
2
- # Scan spot+perp funding rate arbitrage opportunities
3
- # Usage: ./spot-perp-scan.sh [--json] [--mode all|spot-perp|perp-perp] [--min 10]
4
- # Combines perp-perp and spot-perp scans with validation
2
+ # Scan spot+perp (and perp-perp) funding rate arbitrage opportunities
3
+ # Usage: ./spot-perp-scan.sh [--json] [--mode all|spot-perp|perp-perp] [--min 10] [-n mainnet|testnet] [--no-portfolio]
4
+ # Combines the scan with balances (portfolio) and venue health (perp health).
5
+ # --no-portfolio: skip the balance read (no wallet needed); the output says it was skipped.
6
+ # Spot-perp rows: only direction == "long-spot-short-perp" is executable with `arb exec <SYM> spot:<venue> <perp>`.
7
+ # Testnet funding / spreads are not meaningful — use mainnet (the default) for yield analysis.
5
8
 
6
9
  set -euo pipefail
7
10
 
8
- # Auto-detect perp command (supports npx fallback for agents without install permissions)
9
- if command -v perp &>/dev/null; then
10
- PERP="perp"
11
- else
12
- PERP="npx -y perp-cli@latest"
11
+ # perp must be installed (no npx / auto-install path: the user decides what gets installed).
12
+ if ! command -v perp &>/dev/null; then
13
+ echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
14
+ echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
15
+ exit 1
13
16
  fi
17
+ PERP="perp"
14
18
 
15
19
  # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
16
- # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
17
- pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
20
+ # (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
21
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
18
22
  isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
19
23
 
20
24
  JSON_MODE=false
21
25
  MODE="all"
22
26
  MIN_SPREAD=10
27
+ NETWORK="mainnet"
28
+ WITH_PORTFOLIO=true
23
29
  while [[ $# -gt 0 ]]; do
24
30
  case "$1" in
25
31
  --json) JSON_MODE=true; shift ;;
26
32
  --mode) MODE="$2"; shift 2 ;;
27
33
  --min) MIN_SPREAD="$2"; shift 2 ;;
28
- *) shift ;;
34
+ -n|--network) NETWORK="$2"; shift 2 ;;
35
+ --no-portfolio) WITH_PORTFOLIO=false; shift ;;
36
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
29
37
  esac
30
38
  done
39
+ PERP="perp -n $NETWORK"
31
40
 
32
- # 1. Portfolio check (ensure we have balances)
33
- PORTFOLIO=$(pj --json portfolio)
34
- if ! isok "$PORTFOLIO"; then
35
- if $JSON_MODE; then
36
- echo '{"ok":false,"error":"Cannot fetch portfolio — check wallet setup"}'
37
- else
38
- echo "ERROR: Cannot fetch portfolio. Check wallet setup first."
41
+ # 1. Portfolio (balances). Explicitly skipped with --no-portfolio; a failed read aborts.
42
+ if $WITH_PORTFOLIO; then
43
+ PORTFOLIO=$(pj --json portfolio)
44
+ if ! isok "$PORTFOLIO"; then
45
+ if $JSON_MODE; then
46
+ jq -c '{ok:false, error:{code:.error.code, message:.error.message, source:"perp --json portfolio", remediation:"Check the wallet setup, or rerun with --no-portfolio to scan without balances"}}' <<<"$PORTFOLIO"
47
+ else
48
+ echo "ERROR: portfolio failed ($(jq -r '.error.code' <<<"$PORTFOLIO")). Check the wallet setup, or rerun with --no-portfolio." >&2
49
+ fi
50
+ exit 1
39
51
  fi
40
- exit 1
52
+ else
53
+ PORTFOLIO='{"skipped":true,"reason":"--no-portfolio"}'
41
54
  fi
42
55
 
43
56
  # 2. Run appropriate scan
44
57
  SCAN_RESULT=$(pj --json arb scan --mode "$MODE" --min "$MIN_SPREAD")
45
58
 
46
- # 3. Get exchange health for context (proxy via market prices fetch)
47
- HEALTH=$(pj --json market prices)
59
+ # 3. Venue health (data.healthy = every official-API venue is up)
60
+ HEALTH=$(pj --json health)
48
61
 
49
62
  if $JSON_MODE; then
50
63
  cat <<EOF
51
64
  {
52
65
  "ok": true,
53
66
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
67
+ "network": "$NETWORK",
54
68
  "mode": "$MODE",
55
69
  "minSpread": $MIN_SPREAD,
56
70
  "portfolio": $PORTFOLIO,
@@ -60,29 +74,35 @@ if $JSON_MODE; then
60
74
  EOF
61
75
  else
62
76
  echo "=== Spot+Perp Arb Scanner ==="
63
- echo "Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
77
+ echo "Network: $NETWORK | Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
64
78
  echo ""
65
79
 
66
- # Show balances
67
80
  echo "--- Balances ---"
68
- echo "$PORTFOLIO" | jq -r '
69
- .data.exchanges // [] | .[] |
70
- " \(.name): equity $\(.perp.equity // "N/A")"
71
- ' 2>/dev/null || echo " (unable to parse)"
81
+ if $WITH_PORTFOLIO; then
82
+ jq -r '.data.exchanges[] | " \(.name): equity $\(.perp.equity) available $\(.perp.available)"' <<<"$PORTFOLIO"
83
+ else
84
+ echo " (skipped: --no-portfolio)"
85
+ fi
72
86
  echo ""
73
87
 
74
- # Show opportunities
75
88
  echo "--- Opportunities ---"
76
- echo "$SCAN_RESULT" | jq -r '
77
- .data // [] | .[] |
78
- " \(.symbol) | \(.mode // "perp-perp") | net: \(.netSpread // .netSpreadPct)%/yr | \(.longExch // ("spot:" + ((.spotExchanges // []) | join("/"))))→\(.shortExch // .perpExchange)"
79
- ' 2>/dev/null || echo " (no opportunities or parse error)"
89
+ if isok "$SCAN_RESULT"; then
90
+ jq -r '.data[] |
91
+ if .mode == "spot-perp" then
92
+ " \(.symbol) | spot-perp | net \(.netSpread)%/yr | \(.direction) spot:\(.spotExchanges | join("/")) perp:\(.perpExchange)"
93
+ + (if .direction == "long-spot-short-perp" then "" else " [NOT executable with arb exec]" end)
94
+ else
95
+ " \(.symbol) | perp-perp | net \(.netSpread)%/yr | long \(.longExch) / short \(.shortExch)"
96
+ end' <<<"$SCAN_RESULT"
97
+ else
98
+ echo " ERROR: arb scan failed: $(jq -r '"\(.error.code): \(.error.message)"' <<<"$SCAN_RESULT")"
99
+ fi
80
100
 
81
- # Show exchange health
82
101
  echo ""
83
- echo "--- Exchange Health ---"
84
- echo "$HEALTH" | jq -r '
85
- .data // {} | to_entries[] |
86
- " \(.key): \(.value.status // .value // "unknown")"
87
- ' 2>/dev/null || echo " (unable to check)"
102
+ echo "--- Venue Health ---"
103
+ if isok "$HEALTH"; then
104
+ jq -r '" healthy (official venues): \(.data.healthy)", (.data.exchanges[] | " \(.exchange): \(.status)")' <<<"$HEALTH"
105
+ else
106
+ echo " ERROR: perp health failed: $(jq -r '.error.code' <<<"$HEALTH")"
107
+ fi
88
108
  fi
@@ -3,20 +3,22 @@
3
3
  # Usage: ./validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]
4
4
  # Performs: price check, liquidity check, balance check, risk check, dry-run via 'arb exec'
5
5
  # Returns JSON validation report
6
- # NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl).
6
+ # NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl) — only for a scan row with
7
+ # direction == "long-spot-short-perp" (arb exec spot: always buys spot + shorts the perp).
7
8
 
8
9
  set -euo pipefail
9
10
 
10
- # Auto-detect perp command (supports npx fallback for agents without install permissions)
11
- if command -v perp &>/dev/null; then
12
- PERP="perp"
13
- else
14
- PERP="npx -y perp-cli@latest"
11
+ # perp must be installed (no npx / auto-install path: the user decides what gets installed).
12
+ if ! command -v perp &>/dev/null; then
13
+ echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
14
+ echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
15
+ exit 1
15
16
  fi
17
+ PERP="perp"
16
18
 
17
19
  # perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
18
- # (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
19
- pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
20
+ # (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
21
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
20
22
  isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
21
23
 
22
24
  SYM="${1:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]}"
@@ -42,24 +44,23 @@ add_result() {
42
44
  if [[ "$ok" == "false" ]]; then pass=false; fi
43
45
  }
44
46
 
45
- # Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below
47
+ # Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below.
48
+ # Aliases (hl, pac, lt, lrh, ast, …) are accepted everywhere, portfolio included.
46
49
  LONG_VENUE="${LONG_EX#spot:}"
47
- # portfolio -e accepts only full venue ids (no aliases)
48
- venue_id() {
49
- case "$1" in
50
- pac) echo pacifica ;; hl) echo hyperliquid ;; lt) echo lighter ;; lrh|rh) echo lighter-rh ;;
51
- ast) echo aster ;; ondo-perps) echo ondo ;; qf) echo qfex ;; *) echo "$1" ;;
52
- esac
53
- }
54
-
55
- # 1. Price comparison — both exchanges must report similar prices
56
- LONG_PRICE=$(pj --json -e "$LONG_VENUE" market mid "$SYM" | jq -r '.data.mid // "0"' 2>/dev/null || echo 0)
57
- SHORT_PRICE=$(pj --json -e "$SHORT_EX" market mid "$SYM" | jq -r '.data.mid // "0"' 2>/dev/null || echo 0)
58
-
59
- if [[ "$LONG_PRICE" == "0" || "$SHORT_PRICE" == "0" ]]; then
60
- add_result "price" "false" "Cannot fetch price: long=$LONG_PRICE short=$SHORT_PRICE"
50
+ SPOT_MODE=false
51
+ [[ "$LONG_EX" == spot:* ]] && SPOT_MODE=true
52
+
53
+ # 1. Price comparison — both exchanges must report similar prices.
54
+ # `market mid` reads the PERP book; there is no spot price read — in spot mode the long price
55
+ # is the spot venue's perp mid (arb exec itself compares the spot and perp books and refuses
56
+ # an empty book or a > 5% gap).
57
+ LONG_MID=$(pj --json -e "$LONG_VENUE" market mid "$SYM")
58
+ SHORT_MID=$(pj --json -e "$SHORT_EX" market mid "$SYM")
59
+ if ! isok "$LONG_MID" || ! isok "$SHORT_MID"; then
60
+ add_result "price" "false" "Cannot fetch mid: long=$(jq -r '.error.code // .data.mid' <<<"$LONG_MID") short=$(jq -r '.error.code // .data.mid' <<<"$SHORT_MID")"
61
61
  else
62
- # Check deviation
62
+ LONG_PRICE=$(jq -r '.data.mid' <<<"$LONG_MID")
63
+ SHORT_PRICE=$(jq -r '.data.mid' <<<"$SHORT_MID")
63
64
  DEVIATION=$(echo "$LONG_PRICE $SHORT_PRICE" | awk '{d=($1-$2)/$2*100; print (d<0?-d:d)}')
64
65
  if (( $(echo "$DEVIATION > 5" | bc -l) )); then
65
66
  add_result "price" "false" "Price deviation ${DEVIATION}% > 5% — possible wrong token"
@@ -68,21 +69,37 @@ else
68
69
  fi
69
70
  fi
70
71
 
71
- # 2. Balance check on both exchanges
72
- LONG_BAL=$(pj --json portfolio -e "$(venue_id "$LONG_VENUE")" | jq -r '.data.exchanges[0].perp.available // .data.exchanges[0].perp.equity // "0"' 2>/dev/null || echo 0)
73
- SHORT_BAL=$(pj --json portfolio -e "$(venue_id "$SHORT_EX")" | jq -r '.data.exchanges[0].perp.available // .data.exchanges[0].perp.equity // "0"' 2>/dev/null || echo 0)
74
-
75
- NEEDED=$(echo "$SIZE" | awk '{print $1}')
76
- if (( $(echo "$LONG_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
77
- add_result "balance_long" "false" "$LONG_EX balance $LONG_BAL < needed $NEEDED"
78
- else
79
- add_result "balance_long" "true" "$LONG_EX balance=$LONG_BAL"
80
- fi
81
- if (( $(echo "$SHORT_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
82
- add_result "balance_short" "false" "$SHORT_EX balance $SHORT_BAL < needed $NEEDED"
72
+ # 2. Balances. Perp legs: portfolio .perp.available only (missing / unreadable = fail, never 0 or equity).
73
+ # The comparison against the full SIZE is conservative (arb exec needs notional / leverage × buffer).
74
+ perp_available() {
75
+ local out; out=$(pj --json portfolio -e "$1")
76
+ if ! isok "$out"; then echo "ERROR:$(jq -r '.error.code' <<<"$out")"; return; fi
77
+ jq -r '.data.exchanges[0].perp.available // "MISSING"' <<<"$out"
78
+ }
79
+ check_perp_balance() {
80
+ local name="$1" venue="$2" bal; bal=$(perp_available "$venue")
81
+ if [[ "$bal" == ERROR:* || "$bal" == "MISSING" ]]; then
82
+ add_result "$name" "false" "$venue perp available unreadable ($bal)"
83
+ elif (( $(echo "$bal < $SIZE" | bc -l) )); then
84
+ add_result "$name" "false" "$venue perp available $bal < $SIZE"
85
+ else
86
+ add_result "$name" "true" "$venue perp available=$bal"
87
+ fi
88
+ }
89
+ if $SPOT_MODE; then
90
+ # Spot leg: needs spot quote balance (or perp funds the CLI transfers perp→spot on that venue).
91
+ # The exact rule (shared vs separate spot margin, transfer shortfall) is enforced by the arb exec
92
+ # dry-run below; here the spot balances are read so a read failure is surfaced.
93
+ SPOT_BAL=$(pj --json -e "$LONG_VENUE" trade spot-balance)
94
+ if isok "$SPOT_BAL"; then
95
+ add_result "balance_spot" "true" "$LONG_VENUE spot balances: $(jq -c '[.data.balances[] | {token, available}]' <<<"$SPOT_BAL" | tr -d '"') — sufficiency checked by the dry-run"
96
+ else
97
+ add_result "balance_spot" "false" "$LONG_VENUE spot balance unreadable ($(jq -r '.error.code' <<<"$SPOT_BAL"))"
98
+ fi
83
99
  else
84
- add_result "balance_short" "true" "$SHORT_EX balance=$SHORT_BAL"
100
+ check_perp_balance "balance_long" "$LONG_VENUE"
85
101
  fi
102
+ check_perp_balance "balance_short" "$SHORT_EX"
86
103
 
87
104
  # 3. Market info (symbol availability)
88
105
  LONG_INFO=$(pj --json -e "$LONG_VENUE" market info "$SYM")
@@ -101,7 +118,7 @@ RISK_CHECK=$(pj --json risk check --notional "$SIZE" --leverage "${LEVERAGE:-1}"
101
118
  if isok "$RISK_CHECK" && [[ "$(jq -r '.data.allowed' <<<"$RISK_CHECK" 2>/dev/null || true)" == "true" ]]; then
102
119
  add_result "risk" "true" "Within risk limits"
103
120
  else
104
- RISK_WHY=$(jq -r '.data.reason // .error.code // "unknown"' <<<"$RISK_CHECK" 2>/dev/null || echo unknown)
121
+ if isok "$RISK_CHECK"; then RISK_WHY=$(jq -r '.data.reason' <<<"$RISK_CHECK"); else RISK_WHY=$(jq -r '.error.code' <<<"$RISK_CHECK"); fi
105
122
  add_result "risk" "false" "Risk check failed: $RISK_WHY"
106
123
  fi
107
124
 
@@ -110,7 +127,7 @@ DRY_RUN=$(pj --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LE
110
127
  if isok "$DRY_RUN"; then
111
128
  add_result "dry_run" "true" "Dry-run succeeded"
112
129
  else
113
- DRY_ERR=$(jq -r '.error.code // "unknown"' <<<"$DRY_RUN" 2>/dev/null || echo unknown)
130
+ DRY_ERR=$(jq -r '"\(.error.code): \(.error.message)"' <<<"$DRY_RUN" | tr -d '"\\')
114
131
  add_result "dry_run" "false" "Dry-run failed: $DRY_ERR"
115
132
  fi
116
133
 
@@ -1,46 +0,0 @@
1
- import type { Command } from "commander";
2
- interface ArgSpec {
3
- name: string;
4
- required: boolean;
5
- }
6
- interface OptionSpec {
7
- flags: string;
8
- description: string;
9
- default?: unknown;
10
- }
11
- interface CommandSpec {
12
- name: string;
13
- description: string;
14
- args: ArgSpec[];
15
- options: OptionSpec[];
16
- subcommands?: CommandSpec[];
17
- }
18
- interface CliSpec {
19
- name: string;
20
- version: string;
21
- description: string;
22
- globalOptions: OptionSpec[];
23
- commands: CommandSpec[];
24
- exchanges: string[];
25
- envelope: {
26
- success: string;
27
- error: string;
28
- example: {
29
- ok: boolean;
30
- data: {
31
- equity: string;
32
- };
33
- meta: {
34
- timestamp: string;
35
- };
36
- };
37
- };
38
- errorCodes: Record<string, {
39
- status: number;
40
- retryable: boolean;
41
- description: string;
42
- }>;
43
- tips: string[];
44
- }
45
- export declare function getCliSpec(program: Command): CliSpec;
46
- export {};
package/dist/cli-spec.js DELETED
@@ -1,81 +0,0 @@
1
- /**
2
- * Generates a structured CLI spec from Commander's program tree.
3
- * Internal helper retained for tests; no command surfaces it externally.
4
- */
5
- import { createRequire } from "node:module";
6
- const require = createRequire(import.meta.url);
7
- const { version: pkgVersion } = require("../package.json");
8
- function extractCommand(cmd) {
9
- const args = cmd._args ?? [];
10
- const options = cmd.options.map((opt) => ({
11
- flags: opt.flags,
12
- description: opt.description ?? "",
13
- ...(opt.defaultValue !== undefined ? { default: opt.defaultValue } : {}),
14
- }));
15
- const subcommands = cmd.commands
16
- .filter((sub) => sub.name() !== "help")
17
- .map((sub) => extractCommand(sub));
18
- return {
19
- name: cmd.name(),
20
- description: cmd.description(),
21
- args: args.map((a) => ({ name: a._name, required: a.required })),
22
- options,
23
- ...(subcommands.length > 0 ? { subcommands } : {}),
24
- };
25
- }
26
- export function getCliSpec(program) {
27
- const commands = program.commands
28
- .filter((cmd) => cmd.name() !== "help")
29
- .map((cmd) => extractCommand(cmd));
30
- const globalOptions = program.options.map((opt) => ({
31
- flags: opt.flags,
32
- description: opt.description ?? "",
33
- ...(opt.defaultValue !== undefined ? { default: opt.defaultValue } : {}),
34
- }));
35
- return {
36
- name: "perp",
37
- version: pkgVersion,
38
- description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)",
39
- globalOptions,
40
- commands,
41
- exchanges: ["pacifica", "hyperliquid", "lighter", "aster"],
42
- envelope: {
43
- success: "{ ok: true, data: T, meta: { timestamp } }",
44
- error: "{ ok: false, error: { code, message, retryable?, retryAfterMs? }, meta: { timestamp } }",
45
- example: {
46
- ok: true,
47
- data: { equity: "10000.00" },
48
- meta: { timestamp: "2026-03-08T00:00:00.000Z" },
49
- },
50
- },
51
- errorCodes: {
52
- INVALID_PARAMS: { status: 400, retryable: false, description: "Bad arguments or missing fields" },
53
- SYMBOL_NOT_FOUND: { status: 404, retryable: false, description: "Market/symbol not found" },
54
- ORDER_NOT_FOUND: { status: 404, retryable: false, description: "Order ID not found" },
55
- POSITION_NOT_FOUND: { status: 404, retryable: false, description: "No open position for symbol" },
56
- INSUFFICIENT_BALANCE: { status: 400, retryable: false, description: "Not enough balance" },
57
- MARGIN_INSUFFICIENT: { status: 400, retryable: false, description: "Not enough margin" },
58
- SIZE_TOO_SMALL: { status: 400, retryable: false, description: "Below minimum order size" },
59
- SIZE_TOO_LARGE: { status: 400, retryable: false, description: "Exceeds maximum order size" },
60
- RISK_VIOLATION: { status: 403, retryable: false, description: "Risk limit exceeded" },
61
- DUPLICATE_ORDER: { status: 409, retryable: false, description: "Order already submitted" },
62
- EXCHANGE_UNREACHABLE: { status: 503, retryable: true, description: "Network/connection failure" },
63
- RATE_LIMITED: { status: 429, retryable: true, description: "Too many requests" },
64
- PRICE_STALE: { status: 503, retryable: true, description: "Price data outdated" },
65
- SIGNATURE_FAILED: { status: 500, retryable: false, description: "Signing/auth error" },
66
- EXCHANGE_ERROR: { status: 502, retryable: true, description: "Exchange returned error" },
67
- TIMEOUT: { status: 504, retryable: true, description: "Request timed out" },
68
- CLI_ERROR: { status: 400, retryable: false, description: "Command parse error" },
69
- FATAL: { status: 500, retryable: false, description: "Unrecoverable internal error" },
70
- UNKNOWN: { status: 500, retryable: false, description: "Unclassified error" },
71
- },
72
- tips: [
73
- "Always use --json flag for structured output",
74
- "All numbers in market/account data are strings to avoid float precision loss",
75
- "Check error.retryable before retrying failed commands",
76
- "Use 'perp --json trade check <symbol> <side> <size>' to validate before trading",
77
- "Use 'perp --json market mid <symbol>' for fast price lookups",
78
- "If this CLI is useful, run 'perp -e hyperliquid wallet manage referral set' (or -e lighter; signs one referral action, supports --dry-run) to support development",
79
- ],
80
- };
81
- }