perp-cli 0.4.9 → 0.4.10
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.
- package/dist/commands/account.js +112 -45
- package/package.json +1 -1
package/dist/commands/account.js
CHANGED
|
@@ -101,30 +101,21 @@ function pac(adapter) {
|
|
|
101
101
|
}
|
|
102
102
|
export function registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange) {
|
|
103
103
|
const account = program.command("account").description("Account commands");
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
.alias("info")
|
|
107
|
-
.description("Account overview: perp balance, spot holdings, and 24h funding")
|
|
108
|
-
.action(async () => {
|
|
109
|
-
const adapter = await getAdapter();
|
|
110
|
-
const exName = adapter.name.toLowerCase();
|
|
111
|
-
// Fetch perp balance + funding in parallel
|
|
104
|
+
async function fetchExchangeBalance(exName, adapter) {
|
|
105
|
+
const name = exName.toLowerCase();
|
|
112
106
|
const [bal, fundingPayments] = await Promise.all([
|
|
113
107
|
adapter.getBalance(),
|
|
114
108
|
adapter.getFundingPayments(200).catch(() => []),
|
|
115
109
|
]);
|
|
116
|
-
// Spot balances (HL and LT only)
|
|
117
110
|
let spotBalances = [];
|
|
118
111
|
try {
|
|
119
|
-
if (
|
|
112
|
+
if (name === "hyperliquid") {
|
|
120
113
|
const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
|
|
121
114
|
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
122
115
|
await hlSpot.init();
|
|
123
116
|
const raw = await hlSpot.getSpotBalances();
|
|
124
|
-
// Get spot prices for USD valuation
|
|
125
117
|
const markets = await hlSpot.getSpotMarkets();
|
|
126
118
|
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
127
|
-
// HL spot API returns token names with -SPOT suffix (e.g., "HYPE-SPOT", "USDC-SPOT")
|
|
128
119
|
const stripSuffix = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
|
|
129
120
|
spotBalances = raw
|
|
130
121
|
.filter(b => Number(b.total) > 0)
|
|
@@ -136,7 +127,7 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
136
127
|
};
|
|
137
128
|
});
|
|
138
129
|
}
|
|
139
|
-
else if (
|
|
130
|
+
else if (name === "lighter") {
|
|
140
131
|
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
141
132
|
const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
|
|
142
133
|
const ltSpot = new LighterSpotAdapter(adapter);
|
|
@@ -155,33 +146,30 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
155
146
|
}
|
|
156
147
|
}
|
|
157
148
|
catch { /* spot not available */ }
|
|
158
|
-
// 24h funding summary
|
|
159
149
|
const cutoff24h = Date.now() - 24 * 60 * 60 * 1000;
|
|
160
150
|
const recent = fundingPayments.filter(f => f.time >= cutoff24h);
|
|
161
151
|
const funding24h = recent.reduce((sum, f) => sum + Number(f.payment), 0);
|
|
162
|
-
// Total spot value
|
|
163
152
|
const totalSpotUsd = spotBalances.reduce((s, b) => s + (b.valueUsd ?? 0), 0);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
153
|
+
return {
|
|
154
|
+
exchange: name,
|
|
155
|
+
perp: bal,
|
|
156
|
+
spot: spotBalances,
|
|
157
|
+
funding24h,
|
|
158
|
+
fundingPayments24h: recent.length,
|
|
159
|
+
totalSpotValueUsd: totalSpotUsd,
|
|
160
|
+
totalAccountValueUsd: Number(bal.equity) + totalSpotUsd,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function printSingleBalance(exName, r) {
|
|
175
164
|
console.log(chalk.cyan.bold(`\n ${exName.toUpperCase()} Account Balance\n`));
|
|
176
165
|
console.log(chalk.white.bold(" Perp Account"));
|
|
177
|
-
console.log(` Equity: $${formatUsd(
|
|
178
|
-
console.log(` Available: $${formatUsd(
|
|
179
|
-
console.log(` Margin Used: $${formatUsd(
|
|
180
|
-
console.log(` Unreal. PnL: ${formatPnl(
|
|
181
|
-
|
|
182
|
-
if (spotBalances.length > 0) {
|
|
166
|
+
console.log(` Equity: $${formatUsd(r.perp.equity)}`);
|
|
167
|
+
console.log(` Available: $${formatUsd(r.perp.available)}`);
|
|
168
|
+
console.log(` Margin Used: $${formatUsd(r.perp.marginUsed)}`);
|
|
169
|
+
console.log(` Unreal. PnL: ${formatPnl(r.perp.unrealizedPnl)}`);
|
|
170
|
+
if (r.spot.length > 0) {
|
|
183
171
|
console.log(chalk.white.bold("\n Spot Holdings"));
|
|
184
|
-
const rows =
|
|
172
|
+
const rows = r.spot.map(b => [
|
|
185
173
|
chalk.white.bold(b.token),
|
|
186
174
|
b.total,
|
|
187
175
|
b.available,
|
|
@@ -190,21 +178,100 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
|
|
|
190
178
|
]);
|
|
191
179
|
console.log(makeTable(["Token", "Total", "Available", "Held", "Value"], rows));
|
|
192
180
|
}
|
|
193
|
-
else if (exName === "hyperliquid" || exName === "lighter") {
|
|
194
|
-
console.log(chalk.gray("\n No spot holdings.\n"));
|
|
195
|
-
}
|
|
196
|
-
// ── Funding 24h ──
|
|
197
181
|
console.log(chalk.white.bold(" Funding (24h)"));
|
|
198
|
-
console.log(` Payments: ${
|
|
199
|
-
console.log(` Total: ${formatPnl(funding24h)}`);
|
|
200
|
-
|
|
201
|
-
const totalValue = Number(bal.equity) + totalSpotUsd;
|
|
202
|
-
if (totalSpotUsd > 0) {
|
|
182
|
+
console.log(` Payments: ${r.fundingPayments24h}`);
|
|
183
|
+
console.log(` Total: ${formatPnl(r.funding24h)}`);
|
|
184
|
+
if (r.totalSpotValueUsd > 0) {
|
|
203
185
|
console.log(chalk.white.bold("\n Total"));
|
|
204
|
-
console.log(` Perp Equity: $${formatUsd(
|
|
205
|
-
console.log(` Spot Value: $${formatUsd(
|
|
206
|
-
console.log(` Total Value: $${formatUsd(
|
|
186
|
+
console.log(` Perp Equity: $${formatUsd(r.perp.equity)}`);
|
|
187
|
+
console.log(` Spot Value: $${formatUsd(r.totalSpotValueUsd)}`);
|
|
188
|
+
console.log(` Total Value: $${formatUsd(r.totalAccountValueUsd)}`);
|
|
207
189
|
}
|
|
190
|
+
}
|
|
191
|
+
account
|
|
192
|
+
.command("balance")
|
|
193
|
+
.alias("info")
|
|
194
|
+
.description("Account overview: perp balance, spot holdings, and 24h funding")
|
|
195
|
+
.action(async () => {
|
|
196
|
+
// Detect if user explicitly set -e (single exchange mode)
|
|
197
|
+
const explicitExchange = program.getOptionValueSource?.("exchange") === "cli";
|
|
198
|
+
// Multi-exchange mode: show all exchanges
|
|
199
|
+
if (!explicitExchange && getAdapterForExchange) {
|
|
200
|
+
const results = [];
|
|
201
|
+
const errors = {};
|
|
202
|
+
await Promise.all(EXCHANGES.map(async (ex) => {
|
|
203
|
+
try {
|
|
204
|
+
const adapter = await getAdapterForExchange(ex);
|
|
205
|
+
results.push(await fetchExchangeBalance(ex, adapter));
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
errors[ex] = err instanceof Error ? err.message : String(err);
|
|
209
|
+
}
|
|
210
|
+
}));
|
|
211
|
+
// Sort by equity desc
|
|
212
|
+
results.sort((a, b) => b.totalAccountValueUsd - a.totalAccountValueUsd);
|
|
213
|
+
if (isJson()) {
|
|
214
|
+
const grandTotal = results.reduce((s, r) => s + r.totalAccountValueUsd, 0);
|
|
215
|
+
return printJson(jsonOk({
|
|
216
|
+
exchanges: results,
|
|
217
|
+
errors: Object.keys(errors).length > 0 ? errors : undefined,
|
|
218
|
+
totalEquity: results.reduce((s, r) => s + Number(r.perp.equity), 0),
|
|
219
|
+
totalSpotValueUsd: results.reduce((s, r) => s + r.totalSpotValueUsd, 0),
|
|
220
|
+
totalAccountValueUsd: grandTotal,
|
|
221
|
+
totalFunding24h: results.reduce((s, r) => s + r.funding24h, 0),
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
console.log(chalk.cyan.bold("\n All Exchanges — Account Balance\n"));
|
|
225
|
+
// Summary table
|
|
226
|
+
const balRows = results.map(r => [
|
|
227
|
+
chalk.white.bold(r.exchange),
|
|
228
|
+
`$${formatUsd(r.perp.equity)}`,
|
|
229
|
+
`$${formatUsd(r.perp.available)}`,
|
|
230
|
+
`$${formatUsd(r.perp.marginUsed)}`,
|
|
231
|
+
r.totalSpotValueUsd > 0 ? `$${formatUsd(r.totalSpotValueUsd)}` : chalk.gray("-"),
|
|
232
|
+
formatPnl(r.funding24h),
|
|
233
|
+
`$${formatUsd(r.totalAccountValueUsd)}`,
|
|
234
|
+
]);
|
|
235
|
+
const grandEquity = results.reduce((s, r) => s + Number(r.perp.equity), 0);
|
|
236
|
+
const grandSpot = results.reduce((s, r) => s + r.totalSpotValueUsd, 0);
|
|
237
|
+
const grandFunding = results.reduce((s, r) => s + r.funding24h, 0);
|
|
238
|
+
const grandTotal = results.reduce((s, r) => s + r.totalAccountValueUsd, 0);
|
|
239
|
+
balRows.push([
|
|
240
|
+
chalk.cyan.bold("TOTAL"),
|
|
241
|
+
chalk.cyan.bold(`$${formatUsd(grandEquity)}`),
|
|
242
|
+
"",
|
|
243
|
+
"",
|
|
244
|
+
grandSpot > 0 ? chalk.cyan.bold(`$${formatUsd(grandSpot)}`) : "",
|
|
245
|
+
formatPnl(grandFunding),
|
|
246
|
+
chalk.cyan.bold(`$${formatUsd(grandTotal)}`),
|
|
247
|
+
]);
|
|
248
|
+
console.log(makeTable(["Exchange", "Equity", "Available", "Margin", "Spot", "Funding 24h", "Total"], balRows));
|
|
249
|
+
// Spot details per exchange
|
|
250
|
+
for (const r of results) {
|
|
251
|
+
if (r.spot.length > 0) {
|
|
252
|
+
console.log(chalk.white.bold(` ${r.exchange.toUpperCase()} Spot Holdings`));
|
|
253
|
+
const rows = r.spot.map(b => [
|
|
254
|
+
chalk.white.bold(b.token),
|
|
255
|
+
b.total,
|
|
256
|
+
b.valueUsd ? `$${formatUsd(b.valueUsd)}` : chalk.gray("-"),
|
|
257
|
+
]);
|
|
258
|
+
console.log(makeTable(["Token", "Total", "Value"], rows));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Errors
|
|
262
|
+
for (const [ex, msg] of Object.entries(errors)) {
|
|
263
|
+
console.log(chalk.gray(` ${ex}: ${msg}`));
|
|
264
|
+
}
|
|
265
|
+
console.log();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Single exchange mode (explicit -e or no getAdapterForExchange)
|
|
269
|
+
const adapter = await getAdapter();
|
|
270
|
+
const exName = adapter.name.toLowerCase();
|
|
271
|
+
const result = await fetchExchangeBalance(exName, adapter);
|
|
272
|
+
if (isJson())
|
|
273
|
+
return printJson(jsonOk(result));
|
|
274
|
+
printSingleBalance(exName, result);
|
|
208
275
|
console.log();
|
|
209
276
|
});
|
|
210
277
|
account
|