perp-cli 0.14.0 → 0.14.1
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/cli-program.js +19 -2
- package/dist/commands/funds-onchain.d.ts +1 -1
- package/dist/commands/funds-onchain.js +9 -47
- package/dist/commands/funds.js +34 -48
- package/dist/commands/history.js +30 -9
- package/dist/commands/wallet.js +74 -155
- package/dist/exchanges/aster.d.ts +21 -0
- package/dist/exchanges/aster.js +117 -43
- package/dist/exchanges/ondo/adapter.d.ts +4 -18
- package/dist/exchanges/ondo/adapter.js +5 -29
- package/dist/exchanges/ondo/funds.d.ts +1 -28
- package/dist/exchanges/ondo/funds.js +2 -94
- package/dist/exchanges/pacifica.d.ts +5 -0
- package/dist/exchanges/pacifica.js +32 -15
- package/dist/exchanges/qfex/adapter.js +4 -1
- package/dist/exchanges/venues/ondo.d.ts +0 -5
- package/dist/exchanges/venues/ondo.js +2 -6
- package/dist/exchanges/venues/qfex.d.ts +11 -0
- package/dist/exchanges/venues/qfex.js +10 -2
- package/dist/mcp-server.js +2 -2
- package/dist/onchain-balances.d.ts +40 -0
- package/dist/onchain-balances.js +173 -0
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +20 -3
- package/skills/perp-cli/references/agent-operations.md +2 -2
- package/skills/perp-cli/references/commands.md +7 -6
package/dist/cli-program.js
CHANGED
|
@@ -195,8 +195,25 @@ export function buildProgram(opts) {
|
|
|
195
195
|
function getExchange() {
|
|
196
196
|
return resolveVenueId(program.opts().exchange);
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The `--dex` value for the single `-e` venue. HIP-3 dexes exist only on Hyperliquid, so a
|
|
200
|
+
* `--dex` with another `-e` venue is refused instead of being dropped (the command would
|
|
201
|
+
* otherwise read / trade that venue's own markets while the user asked for a HIP-3 dex).
|
|
202
|
+
* Multi-venue commands (arb, portfolio) apply `--dex` to their Hyperliquid leg only and do
|
|
203
|
+
* not go through this check.
|
|
204
|
+
*/
|
|
205
|
+
function dexForExchange() {
|
|
206
|
+
const dex = program.opts().dex ?? null;
|
|
207
|
+
if (dex !== null && getExchange() !== "hyperliquid") {
|
|
208
|
+
const venue = program.getOptionValueSource("exchange") === "cli" ? `-e ${getExchange()}` : `the default exchange (${getExchange()})`;
|
|
209
|
+
throw new PerpError("INVALID_PARAMS", `--dex ${dex} is a Hyperliquid HIP-3 dex; ${venue} has no HIP-3 dexes`, {
|
|
210
|
+
remediation: `Use -e hyperliquid --dex ${dex}, or drop --dex for ${getExchange()}.`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return dex;
|
|
214
|
+
}
|
|
198
215
|
async function getAdapter() {
|
|
199
|
-
_adapter = await createVenueAdapter(getExchange(),
|
|
216
|
+
_adapter = await createVenueAdapter(getExchange(), dexForExchange());
|
|
200
217
|
return _adapter;
|
|
201
218
|
}
|
|
202
219
|
// Sync wrapper for commands that need adapter (lazy init)
|
|
@@ -220,7 +237,7 @@ export function buildProgram(opts) {
|
|
|
220
237
|
}
|
|
221
238
|
// Public market-data adapters (read-only; no credential resolution).
|
|
222
239
|
async function getPublicAdapter() {
|
|
223
|
-
return createVenueAdapter(getExchange(),
|
|
240
|
+
return createVenueAdapter(getExchange(), dexForExchange(), true);
|
|
224
241
|
}
|
|
225
242
|
async function getPublicAdapterForExchange(rawExchange) {
|
|
226
243
|
return createVenueAdapter(rawExchange, program.opts().dex ?? null, true);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `funds deposit aster|ondo`, `funds withdraw aster
|
|
2
|
+
* `funds deposit aster|ondo`, `funds withdraw aster` — fund movements
|
|
3
3
|
* whose on-chain leg is signed by the OWS master wallet (src/evm-master-tx.ts).
|
|
4
4
|
*
|
|
5
5
|
* Every command: `--dry-run` prints the plan and returns before any key, adapter or network is
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `funds deposit aster|ondo`, `funds withdraw aster
|
|
2
|
+
* `funds deposit aster|ondo`, `funds withdraw aster` — fund movements
|
|
3
3
|
* whose on-chain leg is signed by the OWS master wallet (src/evm-master-tx.ts).
|
|
4
4
|
*
|
|
5
5
|
* Every command: `--dry-run` prints the plan and returns before any key, adapter or network is
|
|
@@ -12,7 +12,7 @@ import { cliCredentialOptsFrom } from "../config.js";
|
|
|
12
12
|
import { PerpError, parsePositiveDecimal } from "../errors.js";
|
|
13
13
|
import { logExecution } from "../execution-log.js";
|
|
14
14
|
import { unsupported } from "../exchanges/trading-v2.js";
|
|
15
|
-
import { getVenue
|
|
15
|
+
import { getVenue } from "../exchanges/venue.js";
|
|
16
16
|
import { ASTER_FUNDS_CHAINS } from "../exchanges/venues/aster.js";
|
|
17
17
|
import { ONDO_DEPOSIT_CHAINS } from "../exchanges/venues/ondo.js";
|
|
18
18
|
import { jsonOk, printJson } from "../utils.js";
|
|
@@ -235,52 +235,14 @@ export function registerOnchainFundsCommands(ctx) {
|
|
|
235
235
|
}
|
|
236
236
|
});
|
|
237
237
|
// ═══ Address book (Ondo withdrawal destinations) ═══
|
|
238
|
-
|
|
238
|
+
// Ondo Perps withdrawals are web-app only (the API does not allow them), so an address-book
|
|
239
|
+
// entry made here would serve nothing. The command stays so the refusal is explicit.
|
|
240
|
+
const addressBook = funds.command("address-book").description("Not available: the Ondo Perps withdrawal address book is managed in the web app");
|
|
239
241
|
supportsDryRun(addressBook.command("add <venue> <address>"))
|
|
240
|
-
.description("
|
|
241
|
-
.option("--label <text>", "
|
|
242
|
-
.action(async (venue
|
|
242
|
+
.description("Not available: Ondo Perps withdrawals and their address book are web-app only (refused, also with --dry-run)")
|
|
243
|
+
.option("--label <text>", "Ignored")
|
|
244
|
+
.action(async (venue) => {
|
|
243
245
|
const def = getVenue(venue);
|
|
244
|
-
|
|
245
|
-
throw unsupported(def.displayName, "address-book", "only Ondo Perps has a withdrawal address book", "perp funds address-book add ondo <address>");
|
|
246
|
-
}
|
|
247
|
-
const net = getNetwork();
|
|
248
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
249
|
-
throw new PerpError("INVALID_PARAMS", `"${address}" is not an EVM address`);
|
|
250
|
-
const plan = {
|
|
251
|
-
venue: "ondo", network: net, address, label: opts.label === undefined ? null : opts.label,
|
|
252
|
-
steps: "POST /v1/auth/erc-4361/address_book/get_challenge → personal_sign with the OWS master wallet → POST …/complete_challenge",
|
|
253
|
-
};
|
|
254
|
-
if (isDryRun()) {
|
|
255
|
-
if (isJson())
|
|
256
|
-
return printJson(jsonOk({ dryRun: true, sent: false, action: "address-book add", ...plan }));
|
|
257
|
-
console.log(chalk.yellow(`\n [dry-run] Would add ${address} to the Ondo Perps address book (${net})`));
|
|
258
|
-
console.log(` steps: ${plan.steps}`);
|
|
259
|
-
console.log(chalk.gray(" Nothing was signed or sent.\n"));
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
const { loadMasterEvmSigner } = await import("../evm-master-tx.js");
|
|
263
|
-
const signer = await loadMasterEvmSigner("ondo", creds(), "Adding an Ondo address book entry");
|
|
264
|
-
if (signer.getAddress().toLowerCase() !== address.toLowerCase()) {
|
|
265
|
-
throw new PerpError("INVALID_PARAMS", `The CLI only adds the master wallet's own address (${signer.getAddress()}) to the Ondo address book; ${address} is a different address`, {
|
|
266
|
-
remediation: "Add other addresses in the Ondo Perps web app (Withdraw → address book)",
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
const { OndoAdapter } = await import("../exchanges/ondo/adapter.js");
|
|
270
|
-
const adapter = await getAdapterForExchange("ondo");
|
|
271
|
-
if (!(adapter instanceof OndoAdapter))
|
|
272
|
-
throw new Error("ondo venue did not produce an OndoAdapter");
|
|
273
|
-
const { addOndoAddressBookEntry } = await import("../exchanges/ondo/funds.js");
|
|
274
|
-
const res = await addOndoAddressBookEntry(adapter.restClient, {
|
|
275
|
-
wallet: signer.getAddress(), restUrl: networkConfig(def, net).restUrl, label: plan.label, signMessage: m => signer.signMessage(m),
|
|
276
|
-
});
|
|
277
|
-
if (isJson())
|
|
278
|
-
return printJson(jsonOk({ venue: "ondo", network: net, ...res }));
|
|
279
|
-
if (res.alreadyListed)
|
|
280
|
-
console.log(chalk.green(`\n ${res.address} is already on the Ondo Perps address book.\n`));
|
|
281
|
-
else if (res.listed)
|
|
282
|
-
console.log(chalk.green(`\n Added ${res.address} to the Ondo Perps address book.\n`));
|
|
283
|
-
else
|
|
284
|
-
console.log(chalk.yellow(`\n Ondo accepted the challenge, but ${res.address} is not listed yet (a cooldown may apply). Check the Ondo Perps app before withdrawing.\n`));
|
|
246
|
+
throw unsupported(def.displayName, "address-book", "the Ondo Perps API does not allow withdrawals, so the CLI keeps no withdrawal address book", "Manage withdrawals and the address book in the Ondo Perps web app");
|
|
285
247
|
});
|
|
286
248
|
}
|
package/dist/commands/funds.js
CHANGED
|
@@ -10,8 +10,7 @@ import { cliCredentialOptsFrom, requireMasterWallet } from "../config.js";
|
|
|
10
10
|
import { getVenue, requireCapabilities, READ_ONLY_CREDENTIALS } from "../exchanges/venue.js";
|
|
11
11
|
import { unsupported } from "../exchanges/trading-v2.js";
|
|
12
12
|
import { registerOnchainFundsCommands } from "./funds-onchain.js";
|
|
13
|
-
import {
|
|
14
|
-
import { QFEX_BANKER_NETWORK, QFEX_WITHDRAW_NETWORK } from "../exchanges/venues/qfex.js";
|
|
13
|
+
import { QFEX_BANKER_NETWORK, QFEX_WITHDRAW_DISABLED, QFEX_WITHDRAW_ENABLED, QFEX_WITHDRAW_NETWORK } from "../exchanges/venues/qfex.js";
|
|
15
14
|
import { loadSettings } from "../settings.js";
|
|
16
15
|
import { assertWithdrawAllowed, forgetWithdrawAttempt, recordWithdrawAttempt } from "../withdraw-gate.js";
|
|
17
16
|
import "../exchanges/venues/index.js";
|
|
@@ -105,10 +104,9 @@ function withdrawAssetOf(def) {
|
|
|
105
104
|
}
|
|
106
105
|
/** Withdrawal chain of venues that settle off-chain and pay out on a public chain. */
|
|
107
106
|
const OFFCHAIN_WITHDRAW_CHAIN = {
|
|
108
|
-
ondo: ONDO_WITHDRAW_NETWORK,
|
|
109
107
|
qfex: QFEX_WITHDRAW_NETWORK,
|
|
110
108
|
};
|
|
111
|
-
/** Chain a venue's withdrawals arrive on (
|
|
109
|
+
/** Chain a venue's withdrawals arrive on (QFEX settles off-chain and pays out on Arbitrum). */
|
|
112
110
|
function withdrawChainOf(def, net) {
|
|
113
111
|
const payout = OFFCHAIN_WITHDRAW_CHAIN[def.id];
|
|
114
112
|
if (payout !== undefined)
|
|
@@ -1044,14 +1042,20 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1044
1042
|
throw err;
|
|
1045
1043
|
}
|
|
1046
1044
|
});
|
|
1047
|
-
// ──
|
|
1045
|
+
// ── Arcus — uniform surface (design §13.1): same <amount> + --to,
|
|
1048
1046
|
// same jsonOk(result), global --dry-run plans without sending. ──
|
|
1049
|
-
|
|
1047
|
+
// Ondo Perps: the API does not allow withdrawals (address-book destinations only; adding one is
|
|
1048
|
+
// refused for API keys). The command stays so the refusal is explicit — also under --dry-run.
|
|
1049
|
+
supportsDryRun(withdraw.command("ondo [amount]"))
|
|
1050
|
+
.description("Not available: Ondo Perps withdrawals are web-app only (refused, also with --dry-run)")
|
|
1051
|
+
.option("--to <address>", "Ignored — the API cannot withdraw")
|
|
1052
|
+
.action(async () => {
|
|
1053
|
+
throw unsupported("Ondo Perps", "withdraw", "the Ondo Perps API does not allow withdrawals", "Withdraw from the Ondo Perps web app");
|
|
1054
|
+
});
|
|
1055
|
+
for (const venueId of ["arcus"]) {
|
|
1050
1056
|
const def = getVenue(venueId);
|
|
1051
1057
|
const asset = withdrawAssetOf(def);
|
|
1052
|
-
const toHelp =
|
|
1053
|
-
? "Destination EVM address (required; must be on the Ondo address book — see: perp funds address-book add ondo <address>)"
|
|
1054
|
-
: "Destination EVM address (must equal the Arcus account owner — the default)";
|
|
1058
|
+
const toHelp = "Destination EVM address (must equal the Arcus account owner — the default)";
|
|
1055
1059
|
supportsDryRun(withdraw.command(`${venueId} <amount>`))
|
|
1056
1060
|
.description(`Withdraw ${asset} from ${def.displayName} to your ${chainLabel(withdrawChainOf(def, "mainnet"))} wallet (supports --dry-run)`)
|
|
1057
1061
|
.option("--to <address>", toHelp)
|
|
@@ -1065,27 +1069,16 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1065
1069
|
const adapter = await getAdapterForExchange(venueId);
|
|
1066
1070
|
if (!isWithdrawCapable(adapter))
|
|
1067
1071
|
throw new Error(`${def.id} adapter has no withdraw()`);
|
|
1068
|
-
|
|
1069
|
-
if (
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
else {
|
|
1078
|
-
const { ArcusAdapter } = await import("../exchanges/arcus/adapter.js");
|
|
1079
|
-
if (!(adapter instanceof ArcusAdapter))
|
|
1080
|
-
throw new Error("arcus venue did not produce an ArcusAdapter");
|
|
1081
|
-
const owner = adapter.withdrawDestination;
|
|
1082
|
-
if (opts.to !== undefined && opts.to.toLowerCase() !== owner.toLowerCase()) {
|
|
1083
|
-
throw new PerpError("INVALID_PARAMS", `Arcus only withdraws to the account owner ${owner}`, {
|
|
1084
|
-
remediation: `Omit --to (defaults to ${owner})`,
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
destination = owner;
|
|
1072
|
+
const { ArcusAdapter } = await import("../exchanges/arcus/adapter.js");
|
|
1073
|
+
if (!(adapter instanceof ArcusAdapter))
|
|
1074
|
+
throw new Error("arcus venue did not produce an ArcusAdapter");
|
|
1075
|
+
const owner = adapter.withdrawDestination;
|
|
1076
|
+
if (opts.to !== undefined && opts.to.toLowerCase() !== owner.toLowerCase()) {
|
|
1077
|
+
throw new PerpError("INVALID_PARAMS", `Arcus only withdraws to the account owner ${owner}`, {
|
|
1078
|
+
remediation: `Omit --to (defaults to ${owner})`,
|
|
1079
|
+
});
|
|
1088
1080
|
}
|
|
1081
|
+
const destination = owner;
|
|
1089
1082
|
const plan = { venue: def.id, network: net, asset, amount: String(amountNum), destination, chain };
|
|
1090
1083
|
if (isDryRun()) {
|
|
1091
1084
|
logExecution({
|
|
@@ -1100,16 +1093,6 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1100
1093
|
console.log(chalk.gray(" Nothing was sent.\n"));
|
|
1101
1094
|
return;
|
|
1102
1095
|
}
|
|
1103
|
-
if (venueId === "ondo") {
|
|
1104
|
-
// Address-book preflight (read-only): Ondo refuses destinations that are not listed.
|
|
1105
|
-
const { OndoAdapter } = await import("../exchanges/ondo/adapter.js");
|
|
1106
|
-
if (!(adapter instanceof OndoAdapter))
|
|
1107
|
-
throw new Error("ondo venue did not produce an OndoAdapter");
|
|
1108
|
-
const book = await adapter.addressBook();
|
|
1109
|
-
if (!book.some(a => a.toLowerCase() === destination.toLowerCase())) {
|
|
1110
|
-
throw (await import("../exchanges/ondo/funds.js")).ondoAddressNotOnBook(destination, book);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
1096
|
if (!isJson()) {
|
|
1114
1097
|
console.log(chalk.cyan(`\n Withdrawing ${formatUsd(amountNum)} ${asset} from ${def.displayName}...\n`));
|
|
1115
1098
|
console.log(` Destination: ${destination} (${chain})`);
|
|
@@ -1139,7 +1122,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1139
1122
|
});
|
|
1140
1123
|
}
|
|
1141
1124
|
// ── QFEX (design §14.6, §14.12 #8) — custodial; deposit = address display, withdraw = banker POST
|
|
1142
|
-
// gated by settings.withdraw.qfex inside QfexAdapter.withdraw(). ──
|
|
1125
|
+
// gated by settings.withdraw.qfex inside QfexAdapter.withdraw(); switched off (QFEX_WITHDRAW_ENABLED). ──
|
|
1143
1126
|
/** QFEX adapter for the active network — the default imported key, or the one named by --agent. */
|
|
1144
1127
|
const qfexAdapterFor = async (agentName) => {
|
|
1145
1128
|
const { QfexAdapter: Cls } = await import("../exchanges/qfex/adapter.js");
|
|
@@ -1164,11 +1147,15 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1164
1147
|
throw new Error("qfex venue did not produce a QfexAdapter");
|
|
1165
1148
|
return adapter;
|
|
1166
1149
|
};
|
|
1167
|
-
supportsDryRun(withdraw.command("qfex <amount>"))
|
|
1168
|
-
.description(
|
|
1150
|
+
supportsDryRun(withdraw.command(QFEX_WITHDRAW_ENABLED ? "qfex <amount>" : "qfex [amount]"))
|
|
1151
|
+
.description(QFEX_WITHDRAW_ENABLED
|
|
1152
|
+
? "Withdraw USDC from QFEX to an allowlisted Arbitrum One address (needs settings withdraw.qfex and a key with the \"Deposit and withdraw\" permission; supports --dry-run)"
|
|
1153
|
+
: "Not available: QFEX withdrawals are disabled in perp-cli for now — use the QFEX web app (refused, also with --dry-run)")
|
|
1169
1154
|
.option("--to <address>", "Destination EVM address on Arbitrum One (required; must be on the settings withdraw.qfex allowlist)")
|
|
1170
1155
|
.option("--agent <name>", "Imported QFEX key to use (a key with the \"Deposit and withdraw\" permission, bound to the primary account)")
|
|
1171
1156
|
.action(async (amount, opts) => {
|
|
1157
|
+
if (!QFEX_WITHDRAW_ENABLED)
|
|
1158
|
+
throw unsupported("QFEX", "withdraw", QFEX_WITHDRAW_DISABLED.reason, QFEX_WITHDRAW_DISABLED.remediation);
|
|
1172
1159
|
const def = getVenue("qfex");
|
|
1173
1160
|
const net = getNetwork();
|
|
1174
1161
|
if (!/^\d+(\.\d+)?$/.test(amount) || !(Number(amount) > 0)) {
|
|
@@ -1247,7 +1234,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1247
1234
|
console.log(chalk.yellow(`\n ${info.warning}`));
|
|
1248
1235
|
console.log(chalk.gray(" perp-cli did not send anything — transfer from your own wallet or exchange.\n"));
|
|
1249
1236
|
});
|
|
1250
|
-
// ── Aster deposit / withdraw, Ondo deposit
|
|
1237
|
+
// ── Aster deposit / withdraw, Ondo deposit (OWS-master on-chain legs; address book refused) ──
|
|
1251
1238
|
registerOnchainFundsCommands({
|
|
1252
1239
|
program, funds, deposit, withdraw, isJson, isDryRun, getNetwork, getAdapterForExchange,
|
|
1253
1240
|
requireMaster, printFundsPlan, failStatus: fundsFailStatus,
|
|
@@ -1361,8 +1348,8 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1361
1348
|
hyperliquid: { chain: "Arbitrum", token: "USDC", speed: "minutes", fee: "$1" },
|
|
1362
1349
|
lighter: { chain: "Ethereum L1", token: "USDC", speed: "~12 hours (standard), minutes (fast)", fee: "varies" },
|
|
1363
1350
|
"lighter-rh": { chain: "Robinhood Chain", token: "USDG", command: "perp -e lighter-rh funds withdraw lighter <amount>" },
|
|
1364
|
-
ondo: {
|
|
1365
|
-
qfex: {
|
|
1351
|
+
ondo: { supported: false, note: "the Ondo Perps API does not allow withdrawals — withdraw in the Ondo Perps web app" },
|
|
1352
|
+
qfex: { supported: false, note: "QFEX withdrawals are disabled in perp-cli for now — withdraw in the QFEX web app" },
|
|
1366
1353
|
arcus: { chain: "Robinhood Chain", token: "USDG", command: "perp funds withdraw arcus <amount>" },
|
|
1367
1354
|
aster: { chain: "BNB Chain (default) / Arbitrum", token: "USDT (default) / USDC", command: "perp funds withdraw aster <amount> [--chain bsc|arbitrum] [--asset USDT|USDC]", fee: "POST /fapi/v3/aster/user-withdraw-info (taken from the amount)", requires: "mainnet; API wallet approved with --can-withdraw --ip-whitelist; destination = the account's own address" },
|
|
1368
1355
|
},
|
|
@@ -1411,9 +1398,8 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1411
1398
|
console.log(` Command: ${chalk.green("perp -e lt funds withdraw lighter <amount>")}`);
|
|
1412
1399
|
console.log(chalk.white.bold("\n Lighter RH") + chalk.gray(" → Robinhood Chain (USDG)"));
|
|
1413
1400
|
console.log(` Command: ${chalk.green("perp -e lighter-rh funds withdraw lighter <amount>")}`);
|
|
1414
|
-
console.log(chalk.white.bold("\n Ondo Perps") + chalk.gray("
|
|
1415
|
-
console.log(
|
|
1416
|
-
console.log(` Address book: ${chalk.green("perp funds address-book add ondo <your master address>")}`);
|
|
1401
|
+
console.log(chalk.white.bold("\n Ondo Perps") + chalk.gray(" — not available over the API"));
|
|
1402
|
+
console.log(chalk.gray(" Withdraw in the Ondo Perps web app."));
|
|
1417
1403
|
console.log(chalk.white.bold("\n Arcus") + chalk.gray(" → Robinhood Chain (USDG, to the account owner)"));
|
|
1418
1404
|
console.log(` Command: ${chalk.green("perp funds withdraw arcus <amount>")}`);
|
|
1419
1405
|
console.log(chalk.white.bold("\n Aster") + chalk.gray(" → BNB Chain / Arbitrum (USDT, to the account's own address; mainnet only)"));
|
package/dist/commands/history.js
CHANGED
|
@@ -647,24 +647,42 @@ function registerAnalyticsSubcommands(parent, getAdapterForExchange, isJson) {
|
|
|
647
647
|
let totalEquity = 0;
|
|
648
648
|
let totalUPnl = 0;
|
|
649
649
|
const symbolSet = new Set();
|
|
650
|
+
// A venue whose read failed is reported here and left out of the totals — never counted as 0.
|
|
651
|
+
const errors = {};
|
|
652
|
+
const errText = (e) => (e instanceof Error ? e.message : String(e));
|
|
650
653
|
await Promise.allSettled(exchanges.map(async (ex) => {
|
|
654
|
+
let adapter;
|
|
651
655
|
try {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
656
|
+
adapter = await getAdapterForExchange(ex);
|
|
657
|
+
}
|
|
658
|
+
catch (e) {
|
|
659
|
+
errors[`${ex}.adapter`] = errText(e);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
const balance = await adapter.getBalance();
|
|
658
664
|
totalEquity += Number(balance.equity);
|
|
659
665
|
totalUPnl += Number(balance.unrealizedPnl);
|
|
660
|
-
|
|
666
|
+
}
|
|
667
|
+
catch (e) {
|
|
668
|
+
errors[`${ex}.balance`] = errText(e);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const [trades, funding] = await Promise.allSettled([adapter.getTradeHistory(50), adapter.getFundingPayments(50)]);
|
|
672
|
+
if (trades.status === "fulfilled") {
|
|
673
|
+
for (const t of trades.value) {
|
|
661
674
|
totalFees += Number(t.fee);
|
|
662
675
|
symbolSet.add(t.symbol);
|
|
663
676
|
}
|
|
664
|
-
|
|
677
|
+
}
|
|
678
|
+
else
|
|
679
|
+
errors[`${ex}.trades`] = errText(trades.reason);
|
|
680
|
+
if (funding.status === "fulfilled") {
|
|
681
|
+
for (const f of funding.value)
|
|
665
682
|
totalFunding += Number(f.payment);
|
|
666
683
|
}
|
|
667
|
-
|
|
684
|
+
else
|
|
685
|
+
errors[`${ex}.funding`] = errText(funding.reason);
|
|
668
686
|
}));
|
|
669
687
|
const report = {
|
|
670
688
|
period: opts.since ?? "all time",
|
|
@@ -691,6 +709,7 @@ function registerAnalyticsSubcommands(parent, getAdapterForExchange, isJson) {
|
|
|
691
709
|
? (stats.totalTrades / Math.max(1, (Date.now() - new Date(records[records.length - 1]?.timestamp ?? Date.now()).getTime()) / 86400000)).toFixed(1)
|
|
692
710
|
: "0",
|
|
693
711
|
},
|
|
712
|
+
errors,
|
|
694
713
|
};
|
|
695
714
|
if (isJson())
|
|
696
715
|
return printJson(jsonOk(report));
|
|
@@ -707,6 +726,8 @@ function registerAnalyticsSubcommands(parent, getAdapterForExchange, isJson) {
|
|
|
707
726
|
console.log(` Trading Fees: ${chalk.red(`-$${formatUsd(totalFees)}`)}`);
|
|
708
727
|
console.log(` Funding Income: ${formatPnl(totalFunding)}`);
|
|
709
728
|
console.log(` Net: ${formatPnl(totalFunding - totalFees)}`);
|
|
729
|
+
for (const [k, msg] of Object.entries(errors))
|
|
730
|
+
console.log(chalk.yellow(` ⚠ ${k} unavailable (left out of the totals): ${msg}`));
|
|
710
731
|
console.log();
|
|
711
732
|
});
|
|
712
733
|
});
|