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.
- package/README.md +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +18 -14
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +756 -28
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +84 -34
- package/dist/exchanges/lighter-spot.js +14 -3
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +199 -27
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- package/dist/guardrail/perp-guardrail.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +255 -360
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +85 -83
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +67 -3
- package/dist/settings.js +1 -2
- package/dist/signer/ows-evm.d.ts +14 -0
- package/dist/signer/ows-evm.js +28 -9
- package/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +2 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/ows.d.ts +0 -2
- package/dist/commands/ows.js +0 -887
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
package/dist/commands/alerts.js
CHANGED
|
@@ -269,7 +269,9 @@ export function registerAlertCommands(program, isJson) {
|
|
|
269
269
|
"--cooldown", String(cooldownMin),
|
|
270
270
|
].join(" ");
|
|
271
271
|
// Pass through env vars for keys
|
|
272
|
-
|
|
272
|
+
// L2 agent key + indices now live in ~/.perp keystore + settings.agents (SSOT Rule #3),
|
|
273
|
+
// not env. Only Tier 3 master PKs need passthrough.
|
|
274
|
+
const envKeys = ["LIGHTER_PRIVATE_KEY", "HL_PRIVATE_KEY", "PACIFICA_PRIVATE_KEY", "PRIVATE_KEY"];
|
|
273
275
|
const envStr = envKeys.filter(k => process.env[k]).map(k => `${k}='${process.env[k]}'`).join(" ");
|
|
274
276
|
const cmd = `${envStr} ${nodeCmd} ${cliPath} ${args}`;
|
|
275
277
|
execSync(`tmux new-session -d -s ${session} '${cmd.replace(/'/g, "'\\''")}'`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { makeTable, formatUsd, formatPnl, printJson, jsonOk } from "../../utils.js";
|
|
2
|
+
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, logSettledRejections } from "../../utils.js";
|
|
3
3
|
import { SPOT_PERP_TOKEN_MAP } from "../../exchanges/index.js";
|
|
4
4
|
import { readExecutionLog, logExecution } from "../../execution-log.js";
|
|
5
5
|
import { toHourlyRate, computeAnnualSpread } from "../../funding.js";
|
|
@@ -26,13 +26,22 @@ function formatDuration(ms) {
|
|
|
26
26
|
}
|
|
27
27
|
async function fetchFundingRatesMap() {
|
|
28
28
|
const rateMap = new Map();
|
|
29
|
-
|
|
29
|
+
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
30
|
+
// alive when one DEX errors out, while rejection reasons get logged to
|
|
31
|
+
// stderr (explicit failure, never silent .catch(() => [])).
|
|
32
|
+
const settled = await Promise.allSettled([
|
|
30
33
|
fetchPacificaPrices(),
|
|
31
34
|
fetchHyperliquidMeta(),
|
|
32
35
|
fetchLighterOrderBookDetails(),
|
|
33
36
|
fetchLighterFundingRates(),
|
|
34
|
-
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json())
|
|
37
|
+
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
|
|
35
38
|
]);
|
|
39
|
+
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter:orderbook", "lighter:funding", "aster:premiumIndex"], "arb");
|
|
40
|
+
const pacAssets = settled[0].status === "fulfilled" ? settled[0].value : [];
|
|
41
|
+
const hlAssets = settled[1].status === "fulfilled" ? settled[1].value : [];
|
|
42
|
+
const ltDetails = settled[2].status === "fulfilled" ? settled[2].value : [];
|
|
43
|
+
const ltFunding = settled[3].status === "fulfilled" ? settled[3].value : [];
|
|
44
|
+
const asterPremiums = settled[4].status === "fulfilled" ? settled[4].value : [];
|
|
36
45
|
const addRate = (sym, exchange, rate, markPrice) => {
|
|
37
46
|
if (!sym)
|
|
38
47
|
return;
|
|
@@ -45,19 +54,40 @@ async function fetchFundingRatesMap() {
|
|
|
45
54
|
for (const h of hlAssets)
|
|
46
55
|
addRate(h.symbol, "hyperliquid", h.funding, h.markPx);
|
|
47
56
|
// Lighter: join details + funding by marketId
|
|
57
|
+
// SSOT rule #2: skip rows whose mark price cannot be resolved (no 0 fallback).
|
|
58
|
+
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
59
|
+
// price-source preference (NOT error fallback).
|
|
48
60
|
const ltPriceMap = new Map(ltDetails.map(d => [d.marketId, d.lastTradePrice]));
|
|
49
61
|
const ltSymMap = new Map(ltDetails.map(d => [d.marketId, d.symbol]));
|
|
50
62
|
for (const fr of ltFunding) {
|
|
51
63
|
const sym = fr.symbol || ltSymMap.get(fr.marketId) || "";
|
|
52
|
-
|
|
64
|
+
if (!sym)
|
|
65
|
+
continue;
|
|
66
|
+
const directMark = fr.markPrice;
|
|
67
|
+
const fallbackMark = ltPriceMap.get(fr.marketId);
|
|
68
|
+
let mp;
|
|
69
|
+
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
70
|
+
mp = directMark;
|
|
71
|
+
}
|
|
72
|
+
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
73
|
+
mp = fallbackMark;
|
|
74
|
+
}
|
|
75
|
+
if (mp === undefined || !Number.isFinite(fr.rate))
|
|
76
|
+
continue;
|
|
53
77
|
addRate(sym, "lighter", fr.rate, mp);
|
|
54
78
|
}
|
|
55
79
|
// Aster: premiumIndex
|
|
56
|
-
|
|
80
|
+
// SSOT rule #2: skip rows missing markPrice / lastFundingRate instead of
|
|
81
|
+
// 0-coercing — a 0 markPrice would corrupt downstream sizing.
|
|
82
|
+
for (const p of asterPremiums) {
|
|
57
83
|
const rawSym = String(p.symbol ?? "");
|
|
58
84
|
if (!rawSym.endsWith("USDT"))
|
|
59
85
|
continue;
|
|
60
|
-
|
|
86
|
+
const fundingRate = Number(p.lastFundingRate);
|
|
87
|
+
const markPrice = Number(p.markPrice);
|
|
88
|
+
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0)
|
|
89
|
+
continue;
|
|
90
|
+
addRate(rawSym.replace(/USDT$/, ""), "aster", fundingRate, markPrice);
|
|
61
91
|
}
|
|
62
92
|
return rateMap;
|
|
63
93
|
}
|
|
@@ -1428,7 +1458,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
1428
1458
|
console.log(chalk.yellow("\n [DRY RUN] No transfers executed.\n"));
|
|
1429
1459
|
}
|
|
1430
1460
|
else {
|
|
1431
|
-
console.log(chalk.yellow("\n To execute, use: perp rebalance execute --auto-bridge\n"));
|
|
1461
|
+
console.log(chalk.yellow("\n To execute, use: perp funds rebalance execute --auto-bridge\n"));
|
|
1432
1462
|
}
|
|
1433
1463
|
});
|
|
1434
1464
|
// ── printFundingEarned helper (used by arb status --funding) ──
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { formatUsd, printJson, jsonOk, makeTable } from "../utils.js";
|
|
2
|
+
import { formatUsd, printJson, jsonOk, makeTable, logSettledRejections } from "../utils.js";
|
|
3
3
|
import { computeExecutableSize } from "../liquidity.js";
|
|
4
4
|
import { computeMatchedSize, computeSpotPerpMatchedSize } from "../arb/index.js";
|
|
5
5
|
import { computeAnnualSpread, toHourlyRate } from "../funding.js";
|
|
@@ -105,17 +105,30 @@ export function isSpreadReversed(longExchange, shortExchange, snapshot) {
|
|
|
105
105
|
return longHourly > shortHourly;
|
|
106
106
|
}
|
|
107
107
|
async function fetchFundingSpreads() {
|
|
108
|
-
|
|
108
|
+
// SSOT rule #2: each DEX fetch is independent. allSettled lets us keep the
|
|
109
|
+
// multi-DEX comparison alive when one DEX errors out, while the rejection
|
|
110
|
+
// reasons get logged to stderr (explicit failure, never silent).
|
|
111
|
+
const settled = await Promise.allSettled([
|
|
109
112
|
fetchPacificaPricesRaw(),
|
|
110
113
|
fetchHyperliquidMetaRaw(),
|
|
111
114
|
fetchLighterOrderBookDetailsRaw(),
|
|
112
115
|
fetchLighterFundingRatesRaw(),
|
|
113
|
-
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json())
|
|
116
|
+
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
|
|
114
117
|
]);
|
|
118
|
+
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter:orderbook", "lighter:funding", "aster:premiumIndex"], "arb-auto");
|
|
119
|
+
const pacRes = settled[0].status === "fulfilled" ? settled[0].value : null;
|
|
120
|
+
const hlRes = settled[1].status === "fulfilled" ? settled[1].value : null;
|
|
121
|
+
const ltDetailsRes = settled[2].status === "fulfilled" ? settled[2].value : null;
|
|
122
|
+
const ltFundingRes = settled[3].status === "fulfilled" ? settled[3].value : null;
|
|
123
|
+
const astPremiums = settled[4].status === "fulfilled" ? settled[4].value : [];
|
|
115
124
|
const { rates: pacRates, prices: pacPrices } = parsePacificaRaw(pacRes);
|
|
116
125
|
const { rates: hlRates, prices: hlPrices } = parseHyperliquidMetaRaw(hlRes);
|
|
117
126
|
const { rates: ltRates, prices: ltPrices } = parseLighterRaw(ltDetailsRes, ltFundingRes);
|
|
118
|
-
// Aster: parse premiumIndex
|
|
127
|
+
// Aster: parse premiumIndex.
|
|
128
|
+
// SSOT rule #2: never silently coerce missing markPrice/lastFundingRate to 0
|
|
129
|
+
// — a 0 markPrice corrupts every downstream sizing calculation, and a 0
|
|
130
|
+
// funding rate flips the symbol off the arb radar without explanation.
|
|
131
|
+
// Skip the symbol entirely if either field is missing or non-finite.
|
|
119
132
|
const astRates = new Map();
|
|
120
133
|
const astPrices = new Map();
|
|
121
134
|
for (const p of astPremiums ?? []) {
|
|
@@ -123,8 +136,15 @@ async function fetchFundingSpreads() {
|
|
|
123
136
|
if (!rawSym.endsWith("USDT"))
|
|
124
137
|
continue;
|
|
125
138
|
const sym = rawSym.replace(/USDT$/, "");
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
const fundingRate = Number(p.lastFundingRate);
|
|
140
|
+
const markPrice = Number(p.markPrice);
|
|
141
|
+
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0) {
|
|
142
|
+
// Aster premiumIndex omitted or returned non-numeric values for this
|
|
143
|
+
// symbol; skip rather than poison downstream math with a 0.
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
astRates.set(sym, fundingRate);
|
|
147
|
+
astPrices.set(sym, markPrice);
|
|
128
148
|
}
|
|
129
149
|
const snapshots = [];
|
|
130
150
|
const allSymbols = new Set([...pacRates.keys(), ...hlRates.keys(), ...ltRates.keys(), ...astRates.keys()]);
|
|
@@ -246,8 +266,8 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
246
266
|
console.log(chalk.green(`\n Funding arb bot started in background.`));
|
|
247
267
|
console.log(` ID: ${chalk.white.bold(job.id)}`);
|
|
248
268
|
console.log(` Min spread: ${opts.minSpread}% | Size: $${opts.size}`);
|
|
249
|
-
console.log(` Logs: ${chalk.gray(`perp
|
|
250
|
-
console.log(` Stop: ${chalk.gray(`perp
|
|
269
|
+
console.log(` Logs: ${chalk.gray(`perp background logs ${job.id}`)}`);
|
|
270
|
+
console.log(` Stop: ${chalk.gray(`perp background stop ${job.id}`)}\n`);
|
|
251
271
|
return;
|
|
252
272
|
}
|
|
253
273
|
const minSpread = parseFloat(opts.minSpread);
|
|
@@ -1470,10 +1490,23 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1470
1490
|
const shortOk = shortResult.status === "fulfilled";
|
|
1471
1491
|
if (longOk && shortOk) {
|
|
1472
1492
|
// 6. Verify both positions actually exist (some exchanges return success but silently reject)
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1493
|
+
// SSOT rule #2: distinguish "API error" from "no position opened".
|
|
1494
|
+
// Previously `.catch(() => [])` collapsed both into the same code path,
|
|
1495
|
+
// so a transient verify-side network blip would be misclassified as a
|
|
1496
|
+
// silent exchange reject and trigger an unnecessary rollback.
|
|
1497
|
+
const [longSettled, shortSettled] = await Promise.allSettled([
|
|
1498
|
+
longAdapter.getPositions(),
|
|
1499
|
+
shortAdapter.getPositions(),
|
|
1476
1500
|
]);
|
|
1501
|
+
if (longSettled.status === "rejected" || shortSettled.status === "rejected") {
|
|
1502
|
+
const longErr = longSettled.status === "rejected" ? (longSettled.reason instanceof Error ? longSettled.reason.message : String(longSettled.reason)) : "ok";
|
|
1503
|
+
const shortErr = shortSettled.status === "rejected" ? (shortSettled.reason instanceof Error ? shortSettled.reason.message : String(shortSettled.reason)) : "ok";
|
|
1504
|
+
throw new Error(`arb-auto: cannot verify post-fill positions for ${sym} ` +
|
|
1505
|
+
`(long ${longExch}: ${longErr}; short ${shortExch}: ${shortErr}). ` +
|
|
1506
|
+
`Refusing to silently misclassify API failure as a silent reject.`);
|
|
1507
|
+
}
|
|
1508
|
+
const longPositions = longSettled.value;
|
|
1509
|
+
const shortPositions = shortSettled.value;
|
|
1477
1510
|
const longPos = longPositions.find(p => p.symbol.toUpperCase().startsWith(sym));
|
|
1478
1511
|
const shortPos = shortPositions.find(p => p.symbol.toUpperCase().startsWith(sym));
|
|
1479
1512
|
if (!longPos && !shortPos) {
|
package/dist/commands/arb.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeTable, formatUsd, formatPercent, formatPnl, printJson, jsonOk, withJsonErrors } from "../utils.js";
|
|
1
|
+
import { makeTable, formatUsd, formatPercent, formatPnl, printJson, jsonOk, withJsonErrors, logSettledRejections } from "../utils.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { annualizeRate, toHourlyRate, estimateHourlyFunding, annualizeHourlyRate } from "../funding.js";
|
|
4
4
|
import { fetchAllFundingRates, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
|
|
@@ -25,47 +25,76 @@ async function fetchHyperliquidRates() {
|
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
27
|
async function fetchAsterRates() {
|
|
28
|
+
// SSOT rule #2: do NOT silently coerce missing markPrice / lastFundingRate
|
|
29
|
+
// to 0 — that would corrupt comparisons against other DEXs. Skip symbols
|
|
30
|
+
// whose Aster row is malformed instead. The outer try/catch is intentional:
|
|
31
|
+
// if the entire endpoint fails, the arb command can still compare the
|
|
32
|
+
// remaining DEXs (graceful degradation is an explicit caller-facing
|
|
33
|
+
// behavior, not a silent data substitution).
|
|
28
34
|
try {
|
|
29
35
|
const premiums = await fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json());
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fundingRate
|
|
36
|
-
markPrice
|
|
37
|
-
nextFunding
|
|
38
|
-
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const p of premiums ?? []) {
|
|
38
|
+
const rawSym = String(p.symbol ?? "");
|
|
39
|
+
if (!rawSym.endsWith("USDT"))
|
|
40
|
+
continue;
|
|
41
|
+
const fundingRate = Number(p.lastFundingRate);
|
|
42
|
+
const markPrice = Number(p.markPrice);
|
|
43
|
+
const nextFunding = Number(p.nextFundingTime);
|
|
44
|
+
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
out.push({
|
|
48
|
+
exchange: "aster",
|
|
49
|
+
symbol: rawSym.replace(/USDT$/, ""),
|
|
50
|
+
fundingRate,
|
|
51
|
+
markPrice,
|
|
52
|
+
nextFunding: Number.isFinite(nextFunding) ? nextFunding : 0,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
39
56
|
}
|
|
40
57
|
catch {
|
|
41
58
|
return [];
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
61
|
async function fetchLighterRates() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
// SSOT rule #2: error must propagate. Caller (handleBasisScan) uses
|
|
63
|
+
// Promise.allSettled to keep multi-DEX comparison alive when one DEX is
|
|
64
|
+
// down, with explicit stderr logging.
|
|
65
|
+
const [details, funding] = await Promise.all([
|
|
66
|
+
fetchLighterOrderBookDetails(),
|
|
67
|
+
fetchLighterFundingRates(),
|
|
68
|
+
]);
|
|
69
|
+
const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
|
|
70
|
+
const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
|
|
71
|
+
const rates = [];
|
|
72
|
+
for (const fr of funding) {
|
|
73
|
+
const symbol = fr.symbol || symMap.get(fr.marketId);
|
|
74
|
+
if (!symbol)
|
|
75
|
+
continue;
|
|
76
|
+
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
77
|
+
// price-source preference (NOT error fallback). Skip if neither resolves
|
|
78
|
+
// — never publish a 0 markPrice downstream.
|
|
79
|
+
const directMark = fr.markPrice;
|
|
80
|
+
const fallbackMark = priceMap.get(fr.marketId);
|
|
81
|
+
let markPrice;
|
|
82
|
+
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
83
|
+
markPrice = directMark;
|
|
63
84
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
86
|
+
markPrice = fallbackMark;
|
|
87
|
+
}
|
|
88
|
+
if (markPrice === undefined || !Number.isFinite(fr.rate))
|
|
89
|
+
continue;
|
|
90
|
+
rates.push({
|
|
91
|
+
exchange: "lighter",
|
|
92
|
+
symbol,
|
|
93
|
+
fundingRate: fr.rate,
|
|
94
|
+
markPrice,
|
|
95
|
+
});
|
|
68
96
|
}
|
|
97
|
+
return rates;
|
|
69
98
|
}
|
|
70
99
|
// annualize moved to ../funding.ts — use annualizeRate() / computeAnnualSpread()
|
|
71
100
|
export function registerArbCommands(program, isJson, getAdapterForExchange) {
|
|
@@ -74,11 +103,18 @@ export function registerArbCommands(program, isJson, getAdapterForExchange) {
|
|
|
74
103
|
// → consolidated into 'arb scan' (in arb-auto.ts) with routing options
|
|
75
104
|
}
|
|
76
105
|
export async function fetchAllPrices() {
|
|
77
|
-
|
|
106
|
+
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
107
|
+
// alive when one DEX errors out, while rejection reasons get logged to
|
|
108
|
+
// stderr (explicit failure, never silent).
|
|
109
|
+
const settled = await Promise.allSettled([
|
|
78
110
|
fetchPacificaPricesRaw(),
|
|
79
111
|
fetchHyperliquidAllMidsRaw(),
|
|
80
112
|
fetchLighterOrderBookDetailsRaw(),
|
|
81
113
|
]);
|
|
114
|
+
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter:orderbook"], "arb");
|
|
115
|
+
const pacRes = settled[0].status === "fulfilled" ? settled[0].value : null;
|
|
116
|
+
const hlRes = settled[1].status === "fulfilled" ? settled[1].value : null;
|
|
117
|
+
const ltRes = settled[2].status === "fulfilled" ? settled[2].value : null;
|
|
82
118
|
const { prices: pacPrices } = parsePacificaRaw(pacRes);
|
|
83
119
|
const hlPrices = new Map();
|
|
84
120
|
if (hlRes && typeof hlRes === "object" && !Array.isArray(hlRes)) {
|
|
@@ -320,7 +356,20 @@ export async function handleRates(isJson, opts) {
|
|
|
320
356
|
export async function handleBasisScan(isJson, opts) {
|
|
321
357
|
if (!isJson())
|
|
322
358
|
console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
|
|
323
|
-
|
|
359
|
+
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
360
|
+
// alive when one DEX errors out, while rejection reasons get logged to
|
|
361
|
+
// stderr (explicit failure, never silent [] downgrade).
|
|
362
|
+
const settled = await Promise.allSettled([
|
|
363
|
+
fetchPacificaRates(),
|
|
364
|
+
fetchHyperliquidRates(),
|
|
365
|
+
fetchLighterRates(),
|
|
366
|
+
fetchAsterRates(),
|
|
367
|
+
]);
|
|
368
|
+
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter", "aster"], "basis-scan");
|
|
369
|
+
const pacRates = settled[0].status === "fulfilled" ? settled[0].value : [];
|
|
370
|
+
const hlRates = settled[1].status === "fulfilled" ? settled[1].value : [];
|
|
371
|
+
const ltRates = settled[2].status === "fulfilled" ? settled[2].value : [];
|
|
372
|
+
const astRates = settled[3].status === "fulfilled" ? settled[3].value : [];
|
|
324
373
|
const exchangePrices = new Map();
|
|
325
374
|
const filterSymbol = opts.symbol?.toUpperCase();
|
|
326
375
|
for (const r of [...pacRates, ...hlRates, ...ltRates, ...astRates]) {
|
package/dist/commands/bot.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function registerStrategyCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, getAdapterFor: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
|
package/dist/commands/bot.js
CHANGED
|
@@ -3,8 +3,9 @@ import { printJson, jsonOk, jsonError } from "../utils.js";
|
|
|
3
3
|
import { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, runBot, PRESETS, getPreset, getPresetsByStrategy } from "../bot/index.js";
|
|
4
4
|
import { updateJobState } from "../jobs.js";
|
|
5
5
|
import { runTWAP, runFundingArb, runGrid, runDCA, runTrailingStop } from "../strategies/index.js";
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { registerStrategyPlanCommands } from "./plan.js";
|
|
7
|
+
export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJson) {
|
|
8
|
+
const bot = program.command("strategy").description("Trading strategies — long-running bot algorithms + one-shot scripted plans");
|
|
8
9
|
// ── bot start <config> ──
|
|
9
10
|
bot
|
|
10
11
|
.command("start <config>")
|
|
@@ -312,9 +313,9 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
312
313
|
}
|
|
313
314
|
console.log();
|
|
314
315
|
}
|
|
315
|
-
console.log(chalk.gray(` Usage: perp
|
|
316
|
-
console.log(chalk.gray(` perp
|
|
317
|
-
console.log(chalk.gray(` perp
|
|
316
|
+
console.log(chalk.gray(` Usage: perp strategy preset <name> <symbol>`));
|
|
317
|
+
console.log(chalk.gray(` perp strategy preset grid-standard ETH`));
|
|
318
|
+
console.log(chalk.gray(` perp strategy preset arb-conservative --background\n`));
|
|
318
319
|
});
|
|
319
320
|
// ── bot preset <name> <symbol> ──
|
|
320
321
|
bot
|
|
@@ -327,7 +328,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
327
328
|
const preset = getPreset(name);
|
|
328
329
|
if (!preset) {
|
|
329
330
|
console.error(chalk.red(`\n Unknown preset: "${name}"`));
|
|
330
|
-
console.error(chalk.gray(` Run 'perp
|
|
331
|
+
console.error(chalk.gray(` Run 'perp strategy preset-list' to see available presets.\n`));
|
|
331
332
|
return;
|
|
332
333
|
}
|
|
333
334
|
const sym = symbol?.toUpperCase() ?? "ETH";
|
|
@@ -401,6 +402,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
401
402
|
// ── bot list-strategies ──
|
|
402
403
|
bot
|
|
403
404
|
.command("list-strategies")
|
|
405
|
+
.alias("list")
|
|
404
406
|
.description("List all available trading strategies")
|
|
405
407
|
.action(async () => {
|
|
406
408
|
await import("../bot/engine.js");
|
|
@@ -510,7 +512,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
510
512
|
// ── bot run <strategy> <symbol> ──
|
|
511
513
|
bot
|
|
512
514
|
.command("run [strategy] [symbol]")
|
|
513
|
-
.description("Run a strategy (use 'perp
|
|
515
|
+
.description("Run a strategy (use 'perp strategy list-strategies' to see all). Symbol optional for multi-symbol strategies.")
|
|
514
516
|
.option("--config <path>", "YAML/JSON config file")
|
|
515
517
|
.option("--headless", "Run without TUI dashboard")
|
|
516
518
|
.option("--param <key=value>", "Strategy parameter (repeatable)", (val, acc) => [...acc, val], [])
|
|
@@ -531,7 +533,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
531
533
|
console.log(chalk.cyan.bold("\n Available Strategies:\n"));
|
|
532
534
|
for (const s of available)
|
|
533
535
|
console.log(` ${chalk.green(s)}`);
|
|
534
|
-
console.log(chalk.gray(`\n Usage: perp
|
|
536
|
+
console.log(chalk.gray(`\n Usage: perp strategy run <strategy> [symbol]\n`));
|
|
535
537
|
return;
|
|
536
538
|
}
|
|
537
539
|
const sym = symbol?.toUpperCase() || "ALL";
|
|
@@ -547,7 +549,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
547
549
|
return process.exit(1);
|
|
548
550
|
}
|
|
549
551
|
console.error(chalk.red(`\n Error: The '${strategyName}' strategy requires a <symbol> argument.`));
|
|
550
|
-
console.error(chalk.gray(` Usage: perp
|
|
552
|
+
console.error(chalk.gray(` Usage: perp strategy run ${strategyName} <symbol>\n`));
|
|
551
553
|
return process.exit(1);
|
|
552
554
|
}
|
|
553
555
|
// Load or build config
|
|
@@ -656,7 +658,7 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
656
658
|
{ name: "eth-dca-dip", type: "dca", file: "~/.perp/bots/eth-dca.yaml", config: { exchange: "hyperliquid", symbol: "ETH", strategy: { type: "dca", amount: 0.01, interval_sec: 3600, total_orders: 24 }, entry_conditions: [{ type: "price_below", value: 2500 }], exit_conditions: [{ type: "price_above", value: 2800 }], risk: { max_drawdown: 100, max_daily_loss: 50 } } },
|
|
657
659
|
{ name: "funding-arb", type: "funding-arb", file: "~/.perp/bots/funding-arb.yaml", config: { exchange: "hyperliquid", symbol: "ETH", strategy: { type: "funding-arb", min_spread: 20, close_spread: 5, size_usd: 100, max_positions: 3, exchanges: ["pacifica", "hyperliquid"] }, entry_conditions: [{ type: "always", value: 0 }], risk: { max_drawdown: 200, max_daily_loss: 50 } } },
|
|
658
660
|
],
|
|
659
|
-
usage: ["perp
|
|
661
|
+
usage: ["perp strategy start ~/.perp/bots/eth-grid.yaml", "perp strategy start ~/.perp/bots/eth-grid.yaml --background"],
|
|
660
662
|
}));
|
|
661
663
|
return;
|
|
662
664
|
}
|
|
@@ -670,11 +672,13 @@ export function registerBotCommands(program, getAdapter, getAdapterFor, isJson)
|
|
|
670
672
|
console.log(chalk.white.bold(" 3. Funding Arb Bot"));
|
|
671
673
|
console.log(chalk.gray(` Save to ~/.perp/bots/funding-arb.yaml:\n`));
|
|
672
674
|
console.log(`${ARB_EXAMPLE}\n`);
|
|
673
|
-
console.log(chalk.gray(` Usage: perp
|
|
674
|
-
console.log(chalk.gray(` perp
|
|
675
|
+
console.log(chalk.gray(` Usage: perp strategy start ~/.perp/bots/eth-grid.yaml`));
|
|
676
|
+
console.log(chalk.gray(` perp strategy start ~/.perp/bots/eth-grid.yaml --background\n`));
|
|
675
677
|
});
|
|
676
678
|
// Strategy subcommands: twap, funding-arb, grid, dca, trailing-stop
|
|
677
679
|
registerRunSubcommands(bot, getAdapter, getAdapterFor, isJson);
|
|
680
|
+
// Nested scripted plans: `perp strategy plan {validate|execute|example}`
|
|
681
|
+
registerStrategyPlanCommands(bot, getAdapter, isJson);
|
|
678
682
|
}
|
|
679
683
|
function makeLog() {
|
|
680
684
|
return (msg) => {
|
|
@@ -692,8 +696,8 @@ function resolveOutputMode(isJson, headless) {
|
|
|
692
696
|
function printBotJobStarted(name, jobId) {
|
|
693
697
|
console.log(chalk.green(`\n Bot "${name}" started in background.`));
|
|
694
698
|
console.log(` Job ID: ${chalk.white.bold(jobId)}`);
|
|
695
|
-
console.log(` Logs: ${chalk.gray(`perp
|
|
696
|
-
console.log(` Stop: ${chalk.gray(`perp
|
|
699
|
+
console.log(` Logs: ${chalk.gray(`perp background logs ${jobId} -f`)}`);
|
|
700
|
+
console.log(` Stop: ${chalk.gray(`perp background stop ${jobId}`)}\n`);
|
|
697
701
|
}
|
|
698
702
|
// ── Example YAML configs ──
|
|
699
703
|
const GRID_EXAMPLE = chalk.gray(` name: eth-grid-bot
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function registerFundsBridgeCommands(parent: Command, isJson: () => boolean): void;
|
package/dist/commands/bridge.js
CHANGED
|
@@ -2,8 +2,8 @@ import chalk from "chalk";
|
|
|
2
2
|
import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
|
|
3
3
|
import { logExecution } from "../execution-log.js";
|
|
4
4
|
import { executeBestBridge, getAllQuotes, checkDebridgeStatus, checkBridgeGasBalance, CHAIN_IDS, USDC_ADDRESSES, EXCHANGE_TO_CHAIN, } from "../bridge-engine.js";
|
|
5
|
-
export function
|
|
6
|
-
const bridge =
|
|
5
|
+
export function registerFundsBridgeCommands(parent, isJson) {
|
|
6
|
+
const bridge = parent.command("bridge").description("Cross-chain USDC bridge (multi-provider: cctp, relay, debridge — auto-cheapest)");
|
|
7
7
|
// ── bridge chains ──
|
|
8
8
|
bridge
|
|
9
9
|
.command("chains")
|
|
@@ -79,7 +79,7 @@ export function registerBridgeCommands(program, isJson) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
console.log();
|
|
82
|
-
console.log(chalk.gray(` Execute with: perp bridge send --from ${srcChain} --to ${dstChain} --amount ${opts.amount}`));
|
|
82
|
+
console.log(chalk.gray(` Execute with: perp funds bridge send --from ${srcChain} --to ${dstChain} --amount ${opts.amount}`));
|
|
83
83
|
console.log(chalk.gray(` Pick provider: --provider cctp|relay|debridge\n`));
|
|
84
84
|
});
|
|
85
85
|
// ── bridge send ──
|
|
@@ -374,7 +374,7 @@ export function registerBridgeCommands(program, isJson) {
|
|
|
374
374
|
console.log(chalk.green(` USDC arrived on ${dstExchange}!`));
|
|
375
375
|
}
|
|
376
376
|
console.log(chalk.gray(`\n After arrival, deposit into ${dstExchange}:`));
|
|
377
|
-
console.log(chalk.gray(` perp deposit ${dstExchange} ${opts.amount}\n`));
|
|
377
|
+
console.log(chalk.gray(` perp funds deposit ${dstExchange} ${opts.amount}\n`));
|
|
378
378
|
});
|
|
379
379
|
// ── bridge status ──
|
|
380
380
|
bridge
|
package/dist/commands/funds.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
3
3
|
import type { Network } from "../pacifica/index.js";
|
|
4
|
-
export declare function registerFundsCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network): void;
|
|
4
|
+
export declare function registerFundsCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, getNetwork: () => Network, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>): void;
|
package/dist/commands/funds.js
CHANGED
|
@@ -2,6 +2,8 @@ import chalk from "chalk";
|
|
|
2
2
|
import { printJson, formatUsd, jsonOk, jsonError } from "../utils.js";
|
|
3
3
|
import { logExecution } from "../execution-log.js";
|
|
4
4
|
import { hasPacificaSdk, hasEvmAddress, isWithdrawCapable, isUsdTransferCapable, hasLighterAccount } from "../exchanges/capabilities.js";
|
|
5
|
+
import { registerFundsBridgeCommands } from "./bridge.js";
|
|
6
|
+
import { registerFundsRebalanceCommands } from "./rebalance.js";
|
|
5
7
|
const DEFAULT_RELAYER = "http://localhost:3100";
|
|
6
8
|
function getRelayerUrl() {
|
|
7
9
|
return process.env.PERP_RELAYER_URL || DEFAULT_RELAYER;
|
|
@@ -15,8 +17,8 @@ async function relayerAvailable() {
|
|
|
15
17
|
return false;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
|
-
export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
19
|
-
const funds = program.command("funds").description("
|
|
20
|
+
export function registerFundsCommands(program, getAdapter, isJson, getNetwork, getAdapterForExchange) {
|
|
21
|
+
const funds = program.command("funds").description("Fund movement — deposit, withdraw, transfer, bridge, rebalance");
|
|
20
22
|
// ═══════════════════════════════════════════════════════
|
|
21
23
|
// DEPOSIT
|
|
22
24
|
// ═══════════════════════════════════════════════════════
|
|
@@ -669,69 +671,6 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
669
671
|
console.log(` To: ${destination}\n`);
|
|
670
672
|
});
|
|
671
673
|
// ═══════════════════════════════════════════════════════
|
|
672
|
-
// BRIDGE (CCTP)
|
|
673
|
-
// ═══════════════════════════════════════════════════════
|
|
674
|
-
funds
|
|
675
|
-
.command("bridge")
|
|
676
|
-
.description("Bridge USDC between chains via CCTP V2. See also: perp bridge (deBridge DLN)")
|
|
677
|
-
.requiredOption("--from <chain>", "Source chain (arbitrum, ethereum)")
|
|
678
|
-
.requiredOption("--to <chain>", "Destination chain (arbitrum, ethereum, solana)")
|
|
679
|
-
.requiredOption("--amount <amount>", "USDC amount")
|
|
680
|
-
.requiredOption("--recipient <address>", "Recipient address on destination chain")
|
|
681
|
-
.action(async (opts) => {
|
|
682
|
-
const amountNum = parseFloat(opts.amount);
|
|
683
|
-
if (isNaN(amountNum) || amountNum <= 0)
|
|
684
|
-
throw new Error("Invalid amount");
|
|
685
|
-
if (await relayerAvailable()) {
|
|
686
|
-
if (!isJson())
|
|
687
|
-
console.log(chalk.cyan(`\n Bridging $${formatUsd(amountNum)} USDC via CCTP V2 (${opts.from} → ${opts.to})...\n`));
|
|
688
|
-
const res = await fetch(`${getRelayerUrl()}/bridge/cctp`, {
|
|
689
|
-
method: "POST",
|
|
690
|
-
headers: { "Content-Type": "application/json" },
|
|
691
|
-
body: JSON.stringify({
|
|
692
|
-
fromChain: opts.from,
|
|
693
|
-
toChain: opts.to,
|
|
694
|
-
amount: amountNum,
|
|
695
|
-
recipient: opts.recipient,
|
|
696
|
-
}),
|
|
697
|
-
});
|
|
698
|
-
const result = (await res.json());
|
|
699
|
-
if (!res.ok)
|
|
700
|
-
throw new Error(String(result.error || "CCTP bridge failed"));
|
|
701
|
-
if (isJson())
|
|
702
|
-
return printJson(jsonOk(result));
|
|
703
|
-
console.log(chalk.green(` Burn TX submitted!`));
|
|
704
|
-
console.log(` TX Hash: ${result.txHash}`);
|
|
705
|
-
console.log(` Message Hash: ${result.messageHash}`);
|
|
706
|
-
console.log(chalk.gray(`\n Waiting for Circle attestation (~1-3 min)...`));
|
|
707
|
-
console.log(chalk.gray(` Check: perp funds bridge-status --hash ${result.messageHash}\n`));
|
|
708
|
-
}
|
|
709
|
-
else {
|
|
710
|
-
if (isJson()) {
|
|
711
|
-
printJson(jsonError("RELAYER_UNAVAILABLE", "CCTP bridge requires relayer server. Start: cd packages/relayer && pnpm start"));
|
|
712
|
-
process.exit(1);
|
|
713
|
-
}
|
|
714
|
-
console.error(chalk.red("\n CCTP bridge requires relayer server."));
|
|
715
|
-
console.error(chalk.gray(" Start: cd packages/relayer && pnpm start\n"));
|
|
716
|
-
process.exit(1);
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
funds
|
|
720
|
-
.command("bridge-status")
|
|
721
|
-
.description("Check CCTP bridge status")
|
|
722
|
-
.requiredOption("--hash <messageHash>", "Message hash from bridge TX")
|
|
723
|
-
.action(async (opts) => {
|
|
724
|
-
const res = await fetch(`${getRelayerUrl()}/bridge/cctp/status/${opts.hash}`);
|
|
725
|
-
const result = (await res.json());
|
|
726
|
-
if (isJson())
|
|
727
|
-
return printJson(jsonOk(result));
|
|
728
|
-
console.log(chalk.cyan.bold("\n CCTP Bridge Status\n"));
|
|
729
|
-
console.log(` Status: ${result.status === "complete" ? chalk.green("complete") : chalk.yellow(String(result.status))}`);
|
|
730
|
-
if (result.attestation)
|
|
731
|
-
console.log(` Attestation: ${chalk.gray("received")}`);
|
|
732
|
-
console.log();
|
|
733
|
-
});
|
|
734
|
-
// ═══════════════════════════════════════════════════════
|
|
735
674
|
// INFO (combined deposit + withdraw)
|
|
736
675
|
// ═══════════════════════════════════════════════════════
|
|
737
676
|
funds
|
|
@@ -791,9 +730,20 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork) {
|
|
|
791
730
|
console.log(chalk.white.bold(" Internal Transfer") + chalk.gray(" (Hyperliquid → Hyperliquid)"));
|
|
792
731
|
console.log(` Speed: instant | Fee: none`);
|
|
793
732
|
console.log(` Command: ${chalk.green("perp -e hl funds transfer <amount> <address>")}`);
|
|
794
|
-
console.log(chalk.white.bold("\n
|
|
795
|
-
console.log(` Routes: Arbitrum
|
|
796
|
-
console.log(`
|
|
733
|
+
console.log(chalk.white.bold("\n Cross-chain Bridge") + chalk.gray(" (multi-provider: cctp, relay, debridge)"));
|
|
734
|
+
console.log(` Routes: Solana, Arbitrum, Base, Avalanche, Ethereum`);
|
|
735
|
+
console.log(` Quote: ${chalk.green("perp funds bridge quote --from <chain> --to <chain> --amount <n>")}`);
|
|
736
|
+
console.log(` Send: ${chalk.green("perp funds bridge send --from <chain> --to <chain> --amount <n>")}`);
|
|
737
|
+
console.log(` Between exchanges: ${chalk.green("perp funds bridge exchange --from <ex> --to <ex> --amount <n>")}`);
|
|
738
|
+
console.log(chalk.white.bold("\n Inter-exchange Rebalance") + chalk.gray(" (orchestrated withdraw → bridge → deposit)"));
|
|
739
|
+
console.log(` Check: ${chalk.green("perp funds rebalance check")}`);
|
|
740
|
+
console.log(` Plan: ${chalk.green("perp funds rebalance plan")}`);
|
|
741
|
+
console.log(` Execute: ${chalk.green("perp funds rebalance execute --auto-bridge")}`);
|
|
797
742
|
console.log(chalk.gray("\n Use --no-relay to skip relayer and pay gas yourself.\n"));
|
|
798
743
|
});
|
|
744
|
+
// ═══════════════════════════════════════════════════════
|
|
745
|
+
// NESTED SUBTREES — bridge (multi-provider) + rebalance
|
|
746
|
+
// ═══════════════════════════════════════════════════════
|
|
747
|
+
registerFundsBridgeCommands(funds, isJson);
|
|
748
|
+
registerFundsRebalanceCommands(funds, getAdapterForExchange, isJson);
|
|
799
749
|
}
|
package/dist/commands/jobs.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function registerBackgroundCommands(program: Command, isJson: () => boolean): void;
|