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
@@ -1,99 +1,7 @@
1
- import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch, withJsonErrors } from "../utils.js";
1
+ import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch } from "../utils.js";
2
2
  import chalk from "chalk";
3
- import { assessRisk } from "../risk.js";
4
3
  import { hasPacificaSdk, isDexCapable } from "../exchanges/capabilities.js";
5
4
  const EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
6
- async function fetchExchangeSnapshot(name, getAdapter) {
7
- try {
8
- const adapter = await getAdapter(name);
9
- const [balance, positions, orders] = await Promise.all([
10
- adapter.getBalance(),
11
- adapter.getPositions(),
12
- adapter.getOpenOrders(),
13
- ]);
14
- return {
15
- exchange: name,
16
- connected: true,
17
- balance,
18
- positions,
19
- openOrders: orders.length,
20
- };
21
- }
22
- catch (err) {
23
- return {
24
- exchange: name,
25
- connected: false,
26
- balance: null,
27
- positions: [],
28
- openOrders: 0,
29
- error: err instanceof Error ? err.message : String(err),
30
- };
31
- }
32
- }
33
- function buildSummary(snapshots) {
34
- let totalEquity = 0;
35
- let totalAvailable = 0;
36
- let totalMarginUsed = 0;
37
- let totalUnrealizedPnl = 0;
38
- let totalPositions = 0;
39
- let totalOpenOrders = 0;
40
- const allPositions = [];
41
- for (const snap of snapshots) {
42
- if (snap.balance) {
43
- totalEquity += Number(snap.balance.equity);
44
- totalAvailable += Number(snap.balance.available);
45
- totalMarginUsed += Number(snap.balance.marginUsed);
46
- totalUnrealizedPnl += Number(snap.balance.unrealizedPnl);
47
- }
48
- totalPositions += snap.positions.length;
49
- totalOpenOrders += snap.openOrders;
50
- for (const pos of snap.positions) {
51
- allPositions.push({ ...pos, exchange: snap.exchange });
52
- }
53
- }
54
- // Risk metrics
55
- const marginUtilization = totalEquity > 0 ? (totalMarginUsed / totalEquity) * 100 : 0;
56
- let largestPosition = null;
57
- for (const pos of allPositions) {
58
- const notional = Math.abs(Number(pos.size) * Number(pos.markPrice));
59
- if (!largestPosition || notional > largestPosition.notional) {
60
- largestPosition = { symbol: pos.symbol, exchange: pos.exchange, notional };
61
- }
62
- }
63
- const exchangeConcentration = snapshots
64
- .filter(s => s.balance && Number(s.balance.equity) > 0)
65
- .map(s => ({
66
- exchange: s.exchange,
67
- pct: totalEquity > 0 ? (Number(s.balance.equity) / totalEquity) * 100 : 0,
68
- }))
69
- .sort((a, b) => b.pct - a.pct);
70
- // Full risk assessment (reuses same data, no extra API calls)
71
- const riskBalances = snapshots
72
- .filter(s => s.balance)
73
- .map(s => ({ exchange: s.exchange, balance: s.balance }));
74
- const riskPositions = allPositions.map(p => ({ exchange: p.exchange, position: p }));
75
- const assessment = assessRisk(riskBalances, riskPositions);
76
- return {
77
- totalEquity,
78
- totalAvailable,
79
- totalMarginUsed,
80
- totalUnrealizedPnl,
81
- totalPositions,
82
- totalOpenOrders,
83
- exchanges: snapshots,
84
- positions: allPositions,
85
- riskMetrics: {
86
- marginUtilization,
87
- largestPosition,
88
- exchangeConcentration,
89
- },
90
- risk: {
91
- level: assessment.level,
92
- canTrade: assessment.canTrade,
93
- violations: assessment.violations,
94
- },
95
- };
96
- }
97
5
  function pac(adapter) {
98
6
  if (!hasPacificaSdk(adapter))
99
7
  throw new Error("Market settings are only available on Pacifica.");
@@ -101,231 +9,7 @@ function pac(adapter) {
101
9
  }
102
10
  export function registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange) {
103
11
  const account = program.command("account").description("Account commands");
104
- async function fetchExchangeBalance(exName, adapter) {
105
- const name = exName.toLowerCase();
106
- const [bal, fundingPayments] = await Promise.all([
107
- adapter.getBalance(),
108
- adapter.getFundingPayments(200).catch(() => []),
109
- ]);
110
- let spotBalances = [];
111
- try {
112
- if (name === "hyperliquid") {
113
- const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
114
- const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
115
- const hlSpot = new HyperliquidSpotAdapter(adapter);
116
- await hlSpot.init();
117
- const raw = await hlSpot.getSpotBalances();
118
- const markets = await hlSpot.getSpotMarkets();
119
- const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
120
- const stripSuffix = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
121
- spotBalances = raw
122
- .filter(b => Number(b.total) > 0)
123
- .map(b => {
124
- const base = stripSuffix(b.token);
125
- return {
126
- ...b,
127
- valueUsd: base === "USDC" ? Number(b.total) : (priceMap.get(base) ?? 0) * Number(b.total),
128
- };
129
- });
130
- }
131
- else if (name === "lighter") {
132
- const { LighterAdapter } = await import("../exchanges/lighter.js");
133
- const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
134
- const ltSpot = new LighterSpotAdapter(adapter);
135
- await ltSpot.init();
136
- const raw = await ltSpot.getSpotBalances();
137
- const markets = await ltSpot.getSpotMarkets();
138
- const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
139
- spotBalances = raw
140
- .filter(b => Number(b.total) > 0)
141
- .map(b => ({
142
- ...b,
143
- valueUsd: b.token === "USDC" || b.token === "USDC_SPOT"
144
- ? Number(b.total)
145
- : (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total),
146
- }));
147
- }
148
- }
149
- catch { /* spot not available */ }
150
- // HL unified account: perp equity already includes spot USDC balance.
151
- // Detect by checking if adapter is HL without a dex override.
152
- const isUnified = name === "hyperliquid" && (!isDexCapable(adapter) || !adapter.dex);
153
- const cutoff24h = Date.now() - 24 * 60 * 60 * 1000;
154
- const recent = fundingPayments.filter(f => f.time >= cutoff24h);
155
- const funding24h = recent.reduce((sum, f) => sum + Number(f.payment), 0);
156
- const totalSpotUsd = spotBalances.reduce((s, b) => s + (b.valueUsd ?? 0), 0);
157
- // For unified accounts, equity already contains USDC spot value.
158
- // Only add non-USDC spot token values to avoid double-counting.
159
- const nonUsdcSpotUsd = isUnified
160
- ? spotBalances
161
- .filter(b => {
162
- const tk = b.token.replace(/-SPOT$/i, "").toUpperCase();
163
- return tk !== "USDC";
164
- })
165
- .reduce((s, b) => s + (b.valueUsd ?? 0), 0)
166
- : totalSpotUsd;
167
- return {
168
- exchange: name,
169
- perp: bal,
170
- spot: spotBalances,
171
- funding24h,
172
- fundingPayments24h: recent.length,
173
- totalSpotValueUsd: totalSpotUsd,
174
- totalAccountValueUsd: Number(bal.equity) + nonUsdcSpotUsd,
175
- unifiedAccount: isUnified,
176
- };
177
- }
178
- function printSingleBalance(exName, r) {
179
- console.log(chalk.cyan.bold(`\n ${exName.toUpperCase()} Account Balance`));
180
- if (r.unifiedAccount)
181
- console.log(chalk.gray(" (Unified account — perp & spot share single balance)\n"));
182
- else
183
- console.log();
184
- console.log(chalk.white.bold(r.unifiedAccount ? " Account" : " Perp Account"));
185
- console.log(` Equity: $${formatUsd(r.perp.equity)}`);
186
- console.log(` Available: $${formatUsd(r.perp.available)}`);
187
- console.log(` Margin Used: $${formatUsd(r.perp.marginUsed)}`);
188
- console.log(` Unreal. PnL: ${formatPnl(r.perp.unrealizedPnl)}`);
189
- // For unified, show non-USDC spot holdings (USDC is already in equity)
190
- const displaySpot = r.unifiedAccount
191
- ? r.spot.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
192
- : r.spot;
193
- if (displaySpot.length > 0) {
194
- console.log(chalk.white.bold("\n Spot Holdings"));
195
- const rows = displaySpot.map(b => [
196
- chalk.white.bold(b.token),
197
- b.total,
198
- b.available,
199
- b.held !== "0" ? b.held : chalk.gray("-"),
200
- b.valueUsd ? `$${formatUsd(b.valueUsd)}` : chalk.gray("-"),
201
- ]);
202
- console.log(makeTable(["Token", "Total", "Available", "Held", "Value"], rows));
203
- }
204
- console.log(chalk.white.bold(" Funding (24h)"));
205
- console.log(` Payments: ${r.fundingPayments24h}`);
206
- console.log(` Total: ${formatPnl(r.funding24h)}`);
207
- // Only show Total breakdown when there are non-USDC spot tokens adding value
208
- const nonUsdcSpotValue = displaySpot.reduce((s, b) => s + (b.valueUsd ?? 0), 0);
209
- if (nonUsdcSpotValue > 0) {
210
- console.log(chalk.white.bold("\n Total"));
211
- console.log(` Equity: $${formatUsd(r.perp.equity)}`);
212
- console.log(` Spot (other): $${formatUsd(nonUsdcSpotValue)}`);
213
- console.log(` Total Value: $${formatUsd(r.totalAccountValueUsd)}`);
214
- }
215
- }
216
- account
217
- .command("balance")
218
- .description("Account overview: perp balance, spot holdings, and 24h funding")
219
- .action(async () => {
220
- // Detect if user explicitly set -e (single exchange mode)
221
- const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
222
- // Multi-exchange mode: show all exchanges
223
- if (!explicitExchange && getAdapterForExchange) {
224
- const results = [];
225
- const errors = {};
226
- await Promise.all(EXCHANGES.map(async (ex) => {
227
- try {
228
- const adapter = await getAdapterForExchange(ex);
229
- results.push(await fetchExchangeBalance(ex, adapter));
230
- }
231
- catch (err) {
232
- errors[ex] = err instanceof Error ? err.message : String(err);
233
- }
234
- }));
235
- // Sort by equity desc
236
- results.sort((a, b) => b.totalAccountValueUsd - a.totalAccountValueUsd);
237
- if (isJson()) {
238
- const grandTotal = results.reduce((s, r) => s + r.totalAccountValueUsd, 0);
239
- return printJson(jsonOk({
240
- exchanges: results,
241
- errors: Object.keys(errors).length > 0 ? errors : undefined,
242
- totalEquity: results.reduce((s, r) => s + Number(r.perp.equity), 0),
243
- totalSpotValueUsd: results.reduce((s, r) => {
244
- if (r.unifiedAccount) {
245
- return s + r.spot
246
- .filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
247
- .reduce((ss, b) => ss + (b.valueUsd ?? 0), 0);
248
- }
249
- return s + r.totalSpotValueUsd;
250
- }, 0),
251
- totalAccountValueUsd: grandTotal,
252
- totalFunding24h: results.reduce((s, r) => s + r.funding24h, 0),
253
- }));
254
- }
255
- console.log(chalk.cyan.bold("\n All Exchanges — Account Balance\n"));
256
- // Summary table
257
- // For unified accounts, Spot column shows non-USDC only (USDC already in Equity)
258
- const balRows = results.map(r => {
259
- const nonUsdcSpot = r.unifiedAccount
260
- ? r.spot
261
- .filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
262
- .reduce((s, b) => s + (b.valueUsd ?? 0), 0)
263
- : r.totalSpotValueUsd;
264
- return [
265
- chalk.white.bold(r.exchange) + (r.unifiedAccount ? chalk.gray(" ★") : ""),
266
- `$${formatUsd(r.perp.equity)}`,
267
- `$${formatUsd(r.perp.available)}`,
268
- `$${formatUsd(r.perp.marginUsed)}`,
269
- nonUsdcSpot > 0 ? `$${formatUsd(nonUsdcSpot)}` : chalk.gray("-"),
270
- formatPnl(r.funding24h),
271
- `$${formatUsd(r.totalAccountValueUsd)}`,
272
- ];
273
- });
274
- const grandEquity = results.reduce((s, r) => s + Number(r.perp.equity), 0);
275
- const grandSpot = results.reduce((s, r) => {
276
- if (r.unifiedAccount) {
277
- return s + r.spot
278
- .filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
279
- .reduce((ss, b) => ss + (b.valueUsd ?? 0), 0);
280
- }
281
- return s + r.totalSpotValueUsd;
282
- }, 0);
283
- const grandFunding = results.reduce((s, r) => s + r.funding24h, 0);
284
- const grandTotal = results.reduce((s, r) => s + r.totalAccountValueUsd, 0);
285
- balRows.push([
286
- chalk.cyan.bold("TOTAL"),
287
- chalk.cyan.bold(`$${formatUsd(grandEquity)}`),
288
- "",
289
- "",
290
- grandSpot > 0 ? chalk.cyan.bold(`$${formatUsd(grandSpot)}`) : "",
291
- formatPnl(grandFunding),
292
- chalk.cyan.bold(`$${formatUsd(grandTotal)}`),
293
- ]);
294
- console.log(makeTable(["Exchange", "Equity", "Available", "Margin", "Spot", "Funding 24h", "Total"], balRows));
295
- if (results.some(r => r.unifiedAccount)) {
296
- console.log(chalk.gray(" ★ Unified account — Equity includes spot USDC, Spot shows non-USDC tokens only\n"));
297
- }
298
- // Spot details per exchange (for unified, skip USDC as it's in equity)
299
- for (const r of results) {
300
- const displaySpot = r.unifiedAccount
301
- ? r.spot.filter(b => b.token.replace(/-SPOT$/i, "").toUpperCase() !== "USDC")
302
- : r.spot;
303
- if (displaySpot.length > 0) {
304
- console.log(chalk.white.bold(` ${r.exchange.toUpperCase()} Spot Holdings`));
305
- const rows = displaySpot.map(b => [
306
- chalk.white.bold(b.token),
307
- b.total,
308
- b.valueUsd ? `$${formatUsd(b.valueUsd)}` : chalk.gray("-"),
309
- ]);
310
- console.log(makeTable(["Token", "Total", "Value"], rows));
311
- }
312
- }
313
- // Errors
314
- for (const [ex, msg] of Object.entries(errors)) {
315
- console.log(chalk.gray(` ${ex}: ${msg}`));
316
- }
317
- console.log();
318
- return;
319
- }
320
- // Single exchange mode (explicit -e or no getAdapterForExchange)
321
- const adapter = await getAdapter();
322
- const exName = adapter.name.toLowerCase();
323
- const result = await fetchExchangeBalance(exName, adapter);
324
- if (isJson())
325
- return printJson(jsonOk(result));
326
- printSingleBalance(exName, result);
327
- console.log();
328
- });
12
+ // ── Single exchange balance fetch (used by both single + multi mode) ──
329
13
  // ── HIP-3 dex helper: fetch data from all deployed dexes in parallel ──
330
14
  async function fetchHip3Data(hlAdapter, fetcher) {
331
15
  const dexes = await hlAdapter.listDeployedDexes();
@@ -932,186 +616,4 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
932
616
  ]);
933
617
  console.log(makeTable(["ID", "Symbol", "Side", "Size", "Filled", "Duration"], rows));
934
618
  });
935
- // ── Portfolio (top-level command) ──
936
- if (getAdapterForExchange) {
937
- program
938
- .command("portfolio")
939
- .description("Cross-exchange portfolio overview with risk metrics")
940
- .option("--exchange <exchanges>", "Comma-separated exchanges to include (default: all)")
941
- .action(async (opts) => {
942
- await withJsonErrors(isJson(), async () => {
943
- const exchanges = opts.exchange
944
- ? opts.exchange.split(",").map(e => e.trim())
945
- : [...EXCHANGES];
946
- // Fetch snapshots + funding + spot ALL in parallel
947
- const fundingByExSym = new Map();
948
- const spotRows = [];
949
- let totalSpotValue = 0;
950
- const [snapshots] = await Promise.all([
951
- // Phase 1: balance + positions + orders
952
- Promise.all(exchanges.map(ex => fetchExchangeSnapshot(ex, getAdapterForExchange))),
953
- // Phase 2: funding payments (parallel with Phase 1)
954
- Promise.allSettled(exchanges.map(async (exName) => {
955
- try {
956
- const adapter = await getAdapterForExchange(exName);
957
- const payments = await adapter.getFundingPayments(200);
958
- for (const p of payments) {
959
- const sym = p.symbol.replace("-PERP", "").toUpperCase();
960
- const key = `${exName}:${sym}`;
961
- fundingByExSym.set(key, (fundingByExSym.get(key) ?? 0) + Number(p.payment));
962
- }
963
- }
964
- catch { /* skip */ }
965
- })),
966
- // Phase 3: spot balances (parallel with Phase 1+2)
967
- Promise.allSettled(exchanges.map(async (exName) => {
968
- try {
969
- const adapter = await getAdapterForExchange(exName);
970
- if (exName === "hyperliquid") {
971
- const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
972
- const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
973
- const hlSpot = new HyperliquidSpotAdapter(adapter);
974
- await hlSpot.init();
975
- const bals = await hlSpot.getSpotBalances();
976
- const markets = await hlSpot.getSpotMarkets();
977
- const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
978
- for (const b of bals) {
979
- const base = b.token.replace(/-SPOT$/i, "").toUpperCase();
980
- if (base === "USDC" || Number(b.total) <= 0)
981
- continue;
982
- const price = priceMap.get(base) ?? 0;
983
- const value = price * Number(b.total);
984
- if (value < 1)
985
- continue;
986
- totalSpotValue += value;
987
- spotRows.push([chalk.white.bold(base), chalk.gray("hyperliquid"), b.total, `$${formatUsd(price)}`, `$${formatUsd(value)}`]);
988
- }
989
- }
990
- else if (exName === "lighter") {
991
- const { LighterAdapter } = await import("../exchanges/lighter.js");
992
- const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
993
- const ltSpot = new LighterSpotAdapter(adapter);
994
- await ltSpot.init();
995
- const bals = await ltSpot.getSpotBalances();
996
- const markets = await ltSpot.getSpotMarkets();
997
- const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
998
- for (const b of bals) {
999
- if (b.token === "USDC" || b.token === "USDC_SPOT" || Number(b.total) <= 0)
1000
- continue;
1001
- const price = priceMap.get(b.token.toUpperCase()) ?? 0;
1002
- const value = price * Number(b.total);
1003
- if (value < 1)
1004
- continue;
1005
- totalSpotValue += value;
1006
- spotRows.push([chalk.white.bold(b.token), chalk.gray("lighter"), b.total, `$${formatUsd(price)}`, `$${formatUsd(value)}`]);
1007
- }
1008
- }
1009
- }
1010
- catch { /* spot not available */ }
1011
- })),
1012
- ]);
1013
- const summary = buildSummary(snapshots);
1014
- if (isJson()) {
1015
- return printJson(jsonOk(summary));
1016
- }
1017
- // ── Header ──
1018
- console.log(chalk.cyan.bold("\n Cross-Exchange Portfolio\n"));
1019
- // ── Balance Summary ──
1020
- console.log(chalk.white.bold(" Balances"));
1021
- const balRows = snapshots.map(s => {
1022
- if (!s.connected) {
1023
- return [chalk.white(s.exchange), chalk.red("disconnected"), "-", "-", "-", chalk.gray(s.error ?? "")];
1024
- }
1025
- const b = s.balance;
1026
- return [
1027
- chalk.white.bold(s.exchange),
1028
- `$${formatUsd(b.equity)}`,
1029
- `$${formatUsd(b.available)}`,
1030
- `$${formatUsd(b.marginUsed)}`,
1031
- formatPnl(b.unrealizedPnl),
1032
- chalk.green("connected"),
1033
- ];
1034
- });
1035
- balRows.push([
1036
- chalk.cyan.bold("TOTAL"),
1037
- chalk.cyan.bold(`$${formatUsd(summary.totalEquity)}`),
1038
- chalk.cyan.bold(`$${formatUsd(summary.totalAvailable)}`),
1039
- chalk.cyan.bold(`$${formatUsd(summary.totalMarginUsed)}`),
1040
- formatPnl(summary.totalUnrealizedPnl),
1041
- "",
1042
- ]);
1043
- console.log(makeTable(["Exchange", "Equity", "Available", "Margin Used", "uPnL", "Status"], balRows));
1044
- if (summary.positions.length > 0) {
1045
- console.log(chalk.white.bold("\n Open Positions"));
1046
- const posRows = summary.positions.map(p => {
1047
- const sideColor = p.side === "long" ? chalk.green : chalk.red;
1048
- const notional = Math.abs(Number(p.size) * Number(p.markPrice));
1049
- const sym = p.symbol.replace("-PERP", "").toUpperCase();
1050
- const funding = fundingByExSym.get(`${p.exchange}:${sym}`) ?? 0;
1051
- return [
1052
- chalk.white.bold(p.symbol),
1053
- chalk.gray(p.exchange),
1054
- sideColor(p.side.toUpperCase()),
1055
- p.size,
1056
- `$${formatUsd(p.entryPrice)}`,
1057
- `$${formatUsd(p.markPrice)}`,
1058
- formatPnl(p.unrealizedPnl),
1059
- formatPnl(funding),
1060
- `$${formatUsd(notional)}`,
1061
- `${p.leverage}x`,
1062
- ];
1063
- });
1064
- console.log(makeTable(["Symbol", "Exchange", "Side", "Size", "Entry", "Mark", "uPnL", "Funding", "Notional", "Lev"], posRows));
1065
- }
1066
- else {
1067
- console.log(chalk.gray("\n No open positions.\n"));
1068
- }
1069
- // ── Spot Holdings (already fetched in parallel above) ──
1070
- if (spotRows.length > 0) {
1071
- console.log(chalk.white.bold("\n Spot Holdings"));
1072
- console.log(makeTable(["Token", "Exchange", "Amount", "Price", "Value"], spotRows));
1073
- }
1074
- // ── Total (perp + spot) ──
1075
- if (totalSpotValue > 0) {
1076
- const totalValue = summary.totalEquity + totalSpotValue;
1077
- console.log(chalk.white.bold(" Total Account Value"));
1078
- console.log(` Perp Equity: $${formatUsd(summary.totalEquity)}`);
1079
- console.log(` Spot Value: $${formatUsd(totalSpotValue)}`);
1080
- console.log(` ${chalk.cyan.bold(`Total: $${formatUsd(totalValue)}`)}`);
1081
- }
1082
- // ── Risk Metrics ──
1083
- console.log(chalk.white.bold("\n Risk Metrics"));
1084
- const levelColor = {
1085
- low: chalk.green,
1086
- medium: chalk.yellow,
1087
- high: chalk.red,
1088
- critical: chalk.bgRed.white,
1089
- }[summary.risk.level];
1090
- console.log(` Risk Level: ${levelColor(summary.risk.level.toUpperCase())}`);
1091
- console.log(` Can Trade: ${summary.risk.canTrade ? chalk.green("YES") : chalk.red("NO")}`);
1092
- const mu = summary.riskMetrics.marginUtilization;
1093
- const muColor = mu < 30 ? chalk.green : mu < 60 ? chalk.yellow : chalk.red;
1094
- console.log(` Margin Utilization: ${muColor(`${mu.toFixed(1)}%`)}`);
1095
- if (summary.riskMetrics.largestPosition) {
1096
- const lp = summary.riskMetrics.largestPosition;
1097
- console.log(` Largest Position: ${lp.symbol} on ${lp.exchange} ($${formatUsd(lp.notional)})`);
1098
- }
1099
- if (summary.riskMetrics.exchangeConcentration.length > 0) {
1100
- console.log(` Exchange Allocation:`);
1101
- for (const ec of summary.riskMetrics.exchangeConcentration) {
1102
- const bar = "\u2588".repeat(Math.round(ec.pct / 5)) + "\u2591".repeat(20 - Math.round(ec.pct / 5));
1103
- console.log(` ${ec.exchange.padEnd(12)} ${bar} ${ec.pct.toFixed(1)}%`);
1104
- }
1105
- }
1106
- if (summary.risk.violations.length > 0) {
1107
- console.log(chalk.red.bold("\n Risk Violations"));
1108
- for (const v of summary.risk.violations) {
1109
- const sevColor = { low: chalk.green, medium: chalk.yellow, high: chalk.red, critical: chalk.bgRed.white }[v.severity];
1110
- console.log(` ${sevColor(v.severity.toUpperCase().padEnd(8))} ${v.message}`);
1111
- }
1112
- }
1113
- console.log();
1114
- });
1115
- });
1116
- }
1117
619
  }
@@ -1,3 +1,9 @@
1
1
  import { Command } from "commander";
2
- import type { ExchangeAdapter } from "../exchanges/index.js";
3
- export declare function registerAgentCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean): void;
2
+ /**
3
+ * Register the per-DEX agent-wallet management subtree (`approve` /
4
+ * `list` / `revoke` / `rotate` / `verify`) under the supplied parent
5
+ * `wallet` command. The function previously registered `agent` as a
6
+ * top-level command on `program`; the hard-cut consolidation in v0.11+
7
+ * relocates it to `wallet agent ...`.
8
+ */
9
+ export declare function registerWalletAgentCommands(walletCmd: Command, isJson: () => boolean): void;