perp-cli 0.14.2 → 0.15.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 +152 -13
- package/dist/agent-wallet/store.d.ts +3 -2
- package/dist/agent-wallet/store.js +2 -2
- package/dist/api/public/hyperliquid.js +4 -0
- package/dist/api/public/pacifica.js +83 -13
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/bot/apex/pulse.js +3 -0
- package/dist/bot/apex/radar.js +2 -1
- package/dist/bot/engine.d.ts +10 -1
- package/dist/bot/engine.js +14 -6
- package/dist/bot/indicators.d.ts +7 -0
- package/dist/bot/indicators.js +16 -0
- package/dist/bot/perp-order.d.ts +4 -4
- package/dist/bot/perp-order.js +5 -5
- package/dist/bot/quarantine.d.ts +1 -1
- package/dist/bot/quarantine.js +1 -1
- package/dist/bot/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +9 -9
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
- package/dist/bot/strategies/funding-arb-utils.js +43 -24
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
- package/dist/bot/strategies/liquidation-mm.js +8 -2
- package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
- package/dist/bot/strategies/momentum-breakout.js +11 -3
- package/dist/bot/strategies/spot-perp-arb-strategy.js +23 -12
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +1 -1
- package/dist/bot/strategy-types.d.ts +2 -1
- package/dist/bot/tui/BotDashboard.d.ts +1 -1
- package/dist/bot/tui/BotDashboard.js +1 -1
- package/dist/cache.js +12 -6
- package/dist/cli-program.d.ts +15 -0
- package/dist/cli-program.js +125 -5
- package/dist/commands/agent-import.d.ts +7 -0
- package/dist/commands/agent-import.js +30 -1
- package/dist/commands/agent.d.ts +6 -3
- package/dist/commands/agent.js +160 -12
- package/dist/commands/arb/index.js +4 -4
- package/dist/commands/arb-auto.js +21 -17
- package/dist/commands/arb.d.ts +12 -0
- package/dist/commands/arb.js +61 -143
- package/dist/commands/bot.js +1 -1
- package/dist/commands/builder.js +1 -1
- package/dist/commands/funds.js +113 -3
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +27 -7
- package/dist/commands/manage.js +23 -21
- package/dist/commands/market.js +168 -36
- package/dist/commands/options.d.ts +9 -0
- package/dist/commands/options.js +134 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +468 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.js +32 -47
- package/dist/commands/rebalance.d.ts +1 -0
- package/dist/commands/rebalance.js +15 -0
- package/dist/commands/risk.d.ts +11 -2
- package/dist/commands/risk.js +39 -44
- package/dist/commands/trade.d.ts +1 -0
- package/dist/commands/trade.js +63 -58
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +15 -4
- package/dist/constants.js +6 -0
- package/dist/dashboard/server.js +1 -1
- package/dist/dashboard/ui.js +1 -1
- package/dist/errors.d.ts +24 -1
- package/dist/errors.js +25 -3
- package/dist/exchanges/arcus/adapter.d.ts +7 -7
- package/dist/exchanges/arcus/adapter.js +12 -12
- package/dist/exchanges/arcus/agent.d.ts +1 -1
- package/dist/exchanges/arcus/agent.js +1 -1
- package/dist/exchanges/arcus/client.js +1 -1
- package/dist/exchanges/arcus/signing.d.ts +2 -3
- package/dist/exchanges/arcus/signing.js +1 -1
- package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
- package/dist/exchanges/arcus/spot-chain.js +1 -1
- package/dist/exchanges/arcus/spot-router.d.ts +1 -1
- package/dist/exchanges/arcus/spot-router.js +2 -2
- package/dist/exchanges/arcus/spot.d.ts +2 -3
- package/dist/exchanges/arcus/spot.js +2 -3
- package/dist/exchanges/aster-spot.d.ts +3 -3
- package/dist/exchanges/aster-spot.js +6 -9
- package/dist/exchanges/aster-typed-data.d.ts +1 -1
- package/dist/exchanges/aster-typed-data.js +1 -1
- package/dist/exchanges/aster.d.ts +9 -4
- package/dist/exchanges/aster.js +125 -55
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +1 -2
- package/dist/exchanges/canonical-symbols.js +26 -2
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +68 -0
- package/dist/exchanges/derive/options.js +360 -0
- package/dist/exchanges/extended/adapter.d.ts +82 -0
- package/dist/exchanges/extended/adapter.js +179 -0
- package/dist/exchanges/extended/client.d.ts +38 -0
- package/dist/exchanges/extended/client.js +88 -0
- package/dist/exchanges/extended/public.d.ts +130 -0
- package/dist/exchanges/extended/public.js +429 -0
- package/dist/exchanges/grvt/adapter.d.ts +86 -0
- package/dist/exchanges/grvt/adapter.js +206 -0
- package/dist/exchanges/grvt/client.d.ts +25 -0
- package/dist/exchanges/grvt/client.js +86 -0
- package/dist/exchanges/grvt/public.d.ts +151 -0
- package/dist/exchanges/grvt/public.js +438 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +5 -5
- package/dist/exchanges/hyperliquid.d.ts +22 -16
- package/dist/exchanges/hyperliquid.js +80 -55
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +97 -8
- package/dist/exchanges/interface.js +19 -2
- package/dist/exchanges/ioc-outcome.d.ts +1 -1
- package/dist/exchanges/ioc-outcome.js +1 -1
- package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
- package/dist/exchanges/legacy-trading-v2.js +7 -2
- package/dist/exchanges/lighter-public.d.ts +2 -2
- package/dist/exchanges/lighter-public.js +2 -2
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +3 -4
- package/dist/exchanges/lighter-spot.js +6 -7
- package/dist/exchanges/lighter.d.ts +11 -11
- package/dist/exchanges/lighter.js +28 -14
- package/dist/exchanges/ondo/adapter.d.ts +5 -6
- package/dist/exchanges/ondo/adapter.js +16 -14
- package/dist/exchanges/ondo/agent.d.ts +1 -1
- package/dist/exchanges/ondo/agent.js +1 -1
- package/dist/exchanges/ondo/client.d.ts +1 -1
- package/dist/exchanges/ondo/client.js +2 -2
- package/dist/exchanges/ondo/public.d.ts +8 -1
- package/dist/exchanges/ondo/public.js +13 -2
- package/dist/exchanges/ondo/spot.d.ts +2 -2
- package/dist/exchanges/ondo/spot.js +3 -3
- package/dist/exchanges/options-interface.d.ts +158 -0
- package/dist/exchanges/options-interface.js +17 -0
- package/dist/exchanges/options-venues.d.ts +12 -0
- package/dist/exchanges/options-venues.js +35 -0
- package/dist/exchanges/pacifica-atomic.d.ts +211 -0
- package/dist/exchanges/pacifica-atomic.js +440 -0
- package/dist/exchanges/pacifica-decimal.d.ts +10 -0
- package/dist/exchanges/pacifica-decimal.js +51 -0
- package/dist/exchanges/pacifica-presigned.d.ts +53 -0
- package/dist/exchanges/pacifica-presigned.js +141 -0
- package/dist/exchanges/pacifica-print.d.ts +200 -0
- package/dist/exchanges/pacifica-print.js +417 -0
- package/dist/exchanges/pacifica-spot.d.ts +2 -2
- package/dist/exchanges/pacifica-spot.js +3 -3
- package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
- package/dist/exchanges/pacifica-typed-data.js +1 -1
- package/dist/exchanges/pacifica-vaults.d.ts +121 -0
- package/dist/exchanges/pacifica-vaults.js +264 -0
- package/dist/exchanges/pacifica.d.ts +32 -5
- package/dist/exchanges/pacifica.js +114 -48
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/qfex/adapter.d.ts +18 -5
- package/dist/exchanges/qfex/adapter.js +32 -12
- package/dist/exchanges/qfex/agent.d.ts +1 -1
- package/dist/exchanges/qfex/agent.js +1 -1
- package/dist/exchanges/qfex/client.d.ts +1 -1
- package/dist/exchanges/qfex/client.js +1 -1
- package/dist/exchanges/qfex/errors.d.ts +1 -1
- package/dist/exchanges/qfex/errors.js +2 -2
- package/dist/exchanges/qfex/public.d.ts +3 -3
- package/dist/exchanges/qfex/public.js +3 -3
- package/dist/exchanges/qfex/signing.d.ts +2 -2
- package/dist/exchanges/qfex/signing.js +4 -5
- package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
- package/dist/exchanges/qfex/ws-transport.js +3 -3
- package/dist/exchanges/rate-limiter.d.ts +1 -1
- package/dist/exchanges/rate-limiter.js +5 -2
- package/dist/exchanges/registry.d.ts +1 -1
- package/dist/exchanges/registry.js +1 -1
- package/dist/exchanges/risex/adapter.d.ts +148 -0
- package/dist/exchanges/risex/adapter.js +796 -0
- package/dist/exchanges/risex/agent.d.ts +154 -0
- package/dist/exchanges/risex/agent.js +384 -0
- package/dist/exchanges/risex/client.d.ts +47 -0
- package/dist/exchanges/risex/client.js +174 -0
- package/dist/exchanges/risex/nonce.d.ts +43 -0
- package/dist/exchanges/risex/nonce.js +212 -0
- package/dist/exchanges/risex/public.d.ts +64 -0
- package/dist/exchanges/risex/public.js +150 -0
- package/dist/exchanges/risex/session.d.ts +71 -0
- package/dist/exchanges/risex/session.js +171 -0
- package/dist/exchanges/risex/typed-data.d.ts +173 -0
- package/dist/exchanges/risex/typed-data.js +240 -0
- package/dist/exchanges/risex/units.d.ts +29 -0
- package/dist/exchanges/risex/units.js +104 -0
- package/dist/exchanges/slippage.d.ts +1 -1
- package/dist/exchanges/slippage.js +1 -1
- package/dist/exchanges/spot-factory.d.ts +3 -3
- package/dist/exchanges/spot-factory.js +7 -7
- package/dist/exchanges/spot-interface.d.ts +5 -5
- package/dist/exchanges/spot-interface.js +2 -2
- package/dist/exchanges/spot-result.d.ts +3 -3
- package/dist/exchanges/spot-result.js +3 -3
- package/dist/exchanges/trading-v2.d.ts +3 -3
- package/dist/exchanges/trading-v2.js +9 -6
- package/dist/exchanges/txflow/adapter.d.ts +54 -0
- package/dist/exchanges/txflow/adapter.js +118 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +153 -0
- package/dist/exchanges/txflow/public.d.ts +76 -0
- package/dist/exchanges/txflow/public.js +284 -0
- package/dist/exchanges/unknown-symbol.d.ts +29 -0
- package/dist/exchanges/unknown-symbol.js +51 -0
- package/dist/exchanges/variational/adapter.d.ts +71 -0
- package/dist/exchanges/variational/adapter.js +137 -0
- package/dist/exchanges/variational/client.d.ts +21 -0
- package/dist/exchanges/variational/client.js +66 -0
- package/dist/exchanges/variational/public.d.ts +126 -0
- package/dist/exchanges/variational/public.js +430 -0
- package/dist/exchanges/venue-types.d.ts +6 -7
- package/dist/exchanges/venue-types.js +2 -2
- package/dist/exchanges/venue.d.ts +51 -8
- package/dist/exchanges/venue.js +54 -4
- package/dist/exchanges/venues/arcus.d.ts +1 -2
- package/dist/exchanges/venues/arcus.js +5 -4
- package/dist/exchanges/venues/aster.d.ts +2 -2
- package/dist/exchanges/venues/aster.js +6 -4
- package/dist/exchanges/venues/create-helpers.d.ts +1 -1
- package/dist/exchanges/venues/create-helpers.js +3 -3
- package/dist/exchanges/venues/extended.d.ts +21 -0
- package/dist/exchanges/venues/extended.js +96 -0
- package/dist/exchanges/venues/grvt.d.ts +30 -0
- package/dist/exchanges/venues/grvt.js +98 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
- package/dist/exchanges/venues/hyperliquid.js +10 -8
- package/dist/exchanges/venues/index.d.ts +7 -2
- package/dist/exchanges/venues/index.js +8 -2
- package/dist/exchanges/venues/lighter-family.d.ts +7 -9
- package/dist/exchanges/venues/lighter-family.js +9 -11
- package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
- package/dist/exchanges/venues/lighter-rh.js +7 -5
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +5 -3
- package/dist/exchanges/venues/ondo.d.ts +1 -1
- package/dist/exchanges/venues/ondo.js +5 -3
- package/dist/exchanges/venues/pacifica.d.ts +2 -2
- package/dist/exchanges/venues/pacifica.js +6 -4
- package/dist/exchanges/venues/qfex.d.ts +1 -1
- package/dist/exchanges/venues/qfex.js +4 -2
- package/dist/exchanges/venues/risex-release.d.ts +14 -0
- package/dist/exchanges/venues/risex-release.js +14 -0
- package/dist/exchanges/venues/risex.d.ts +31 -0
- package/dist/exchanges/venues/risex.js +116 -0
- package/dist/exchanges/venues/txflow.d.ts +18 -0
- package/dist/exchanges/venues/txflow.js +77 -0
- package/dist/exchanges/venues/variational.d.ts +21 -0
- package/dist/exchanges/venues/variational.js +85 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/funding/legacy-public.d.ts +1 -1
- package/dist/funding/legacy-public.js +9 -4
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +44 -4
- package/dist/funding/rates.js +56 -12
- package/dist/index.js +7 -6
- package/dist/jobs.d.ts +1 -0
- package/dist/jobs.js +25 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +1 -1
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +58 -0
- package/dist/mcp-server.js +113 -59
- package/dist/pacifica/client.d.ts +67 -4
- package/dist/pacifica/client.js +170 -8
- package/dist/pacifica/rate-limit.d.ts +144 -0
- package/dist/pacifica/rate-limit.js +267 -0
- package/dist/pacifica/types/order.d.ts +2 -1
- package/dist/plan-executor.js +7 -2
- package/dist/rebalance.js +1 -1
- package/dist/risk.d.ts +2 -1
- package/dist/risk.js +2 -4
- package/dist/settings.d.ts +32 -3
- package/dist/signer/ows-evm.d.ts +1 -1
- package/dist/signer/ows-evm.js +1 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/smart-order.js +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/dist/trade-validator.js +9 -4
- package/dist/utils.d.ts +12 -3
- package/dist/utils.js +11 -3
- package/dist/venue-health.d.ts +40 -0
- package/dist/venue-health.js +75 -0
- package/dist/withdraw-gate.d.ts +1 -1
- package/dist/withdraw-gate.js +1 -1
- package/dist/ws/index.d.ts +959 -0
- package/dist/ws/index.js +4295 -0
- package/dist/ws/node.d.ts +975 -0
- package/dist/ws/node.js +4366 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +58 -10
- package/skills/perp-cli/SKILL.md +62 -9
- package/skills/perp-cli/references/agent-operations.md +7 -0
- package/skills/perp-cli/references/commands.md +76 -5
- package/skills/perp-cli/references/ws-library.md +492 -0
package/dist/commands/arb.js
CHANGED
|
@@ -1,99 +1,13 @@
|
|
|
1
1
|
import { makeTable, formatUsd, formatPercent, formatPnl, printJson, jsonOk, withJsonErrors, logSettledRejections } from "../utils.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { toHourlyRate, annualizeHourlyRate, estimateHourlyFundingFromHourly } from "../funding.js";
|
|
4
|
-
import { fetchAllFundingRates, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
|
|
4
|
+
import { fetchAllFundingRates, fetchCanonicalPriceGroups, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
|
|
5
5
|
import { saveFundingSnapshot, getHistoricalRates, getCompoundedAnnualReturn, getExchangeCompoundingHours, } from "../funding-history.js";
|
|
6
6
|
import { getVenue, listVenuesWithRole, tryResolveVenueId } from "../exchanges/venue.js";
|
|
7
7
|
import "../exchanges/venues/index.js";
|
|
8
|
-
import {
|
|
8
|
+
import { MAX_PRICE_DEVIATION_PCT } from "../exchanges/index.js";
|
|
9
|
+
import { fetchPacificaPricesRaw, parsePacificaRaw, fetchHyperliquidAllMidsRaw, fetchLighterOrderBookDetailsRaw, } from "../shared-api.js";
|
|
9
10
|
import { scanDexArb } from "../dex-asset-map.js";
|
|
10
|
-
async function fetchPacificaRates() {
|
|
11
|
-
const assets = await fetchPacificaPrices();
|
|
12
|
-
return assets.map(p => ({
|
|
13
|
-
exchange: "pacifica",
|
|
14
|
-
symbol: p.symbol,
|
|
15
|
-
fundingRate: p.funding,
|
|
16
|
-
markPrice: p.mark,
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
19
|
-
async function fetchHyperliquidRates() {
|
|
20
|
-
const assets = await fetchHyperliquidMeta();
|
|
21
|
-
return assets.map(a => ({
|
|
22
|
-
exchange: "hyperliquid",
|
|
23
|
-
symbol: a.symbol,
|
|
24
|
-
fundingRate: a.funding,
|
|
25
|
-
markPrice: a.markPx,
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
async function fetchAsterRates() {
|
|
29
|
-
// SSOT rule #2: do NOT silently coerce missing markPrice / lastFundingRate
|
|
30
|
-
// to 0 — that would corrupt comparisons against other DEXs. Skip symbols
|
|
31
|
-
// whose Aster row is malformed instead. An endpoint failure PROPAGATES: the
|
|
32
|
-
// caller (handleBasisScan) runs every DEX under Promise.allSettled and reports
|
|
33
|
-
// the rejection on stderr — Aster is never silently absent from the scan.
|
|
34
|
-
const res = await fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex");
|
|
35
|
-
if (!res.ok)
|
|
36
|
-
throw new Error(`Aster premiumIndex HTTP ${res.status}`);
|
|
37
|
-
const premiums = await res.json();
|
|
38
|
-
if (!Array.isArray(premiums))
|
|
39
|
-
throw new Error("Aster premiumIndex returned a non-array response");
|
|
40
|
-
const out = [];
|
|
41
|
-
for (const p of premiums) {
|
|
42
|
-
const rawSym = String(p.symbol ?? "");
|
|
43
|
-
if (!rawSym.endsWith("USDT"))
|
|
44
|
-
continue;
|
|
45
|
-
const fundingRate = Number(p.lastFundingRate);
|
|
46
|
-
const markPrice = Number(p.markPrice);
|
|
47
|
-
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
out.push({
|
|
51
|
-
exchange: "aster",
|
|
52
|
-
symbol: rawSym.replace(/USDT$/, ""),
|
|
53
|
-
fundingRate,
|
|
54
|
-
markPrice,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
async function fetchLighterRates() {
|
|
60
|
-
// SSOT rule #2: error must propagate. Caller (handleBasisScan) uses
|
|
61
|
-
// Promise.allSettled to keep multi-DEX comparison alive when one DEX is
|
|
62
|
-
// down, with explicit stderr logging.
|
|
63
|
-
const [details, funding] = await Promise.all([
|
|
64
|
-
fetchLighterOrderBookDetails(),
|
|
65
|
-
fetchLighterFundingRates(),
|
|
66
|
-
]);
|
|
67
|
-
const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
|
|
68
|
-
const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
|
|
69
|
-
const rates = [];
|
|
70
|
-
for (const fr of funding) {
|
|
71
|
-
const symbol = fr.symbol || symMap.get(fr.marketId);
|
|
72
|
-
if (!symbol)
|
|
73
|
-
continue;
|
|
74
|
-
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
75
|
-
// price-source preference (NOT error fallback). Skip if neither resolves
|
|
76
|
-
// — never publish a 0 markPrice downstream.
|
|
77
|
-
const directMark = fr.markPrice;
|
|
78
|
-
const fallbackMark = priceMap.get(fr.marketId);
|
|
79
|
-
let markPrice;
|
|
80
|
-
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
81
|
-
markPrice = directMark;
|
|
82
|
-
}
|
|
83
|
-
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
84
|
-
markPrice = fallbackMark;
|
|
85
|
-
}
|
|
86
|
-
if (markPrice === undefined || !Number.isFinite(fr.rate))
|
|
87
|
-
continue;
|
|
88
|
-
rates.push({
|
|
89
|
-
exchange: "lighter",
|
|
90
|
-
symbol,
|
|
91
|
-
fundingRate: fr.rate,
|
|
92
|
-
markPrice,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return rates;
|
|
96
|
-
}
|
|
97
11
|
// annualize moved to ../funding.ts — use annualizeRate() / computeAnnualSpread()
|
|
98
12
|
export function registerArbCommands(program, isJson, getAdapterForExchange) {
|
|
99
13
|
const arb = program.command("arb").description("Funding rate arbitrage & basis trading");
|
|
@@ -291,12 +205,15 @@ function printSnapshotTable(snapshot) {
|
|
|
291
205
|
return r ? formatPercent(r.fundingRate) : chalk.gray("-");
|
|
292
206
|
}),
|
|
293
207
|
spreadColor(`${s.maxSpreadAnnual.toFixed(1)}%`), trend,
|
|
294
|
-
s.maxSpreadAnnual >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}` : chalk.gray("-"),
|
|
208
|
+
s.maxSpreadAnnual >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}${s.executable ? "" : chalk.gray(" (read-only)")}` : chalk.gray("-"),
|
|
295
209
|
formatAvgRate(bestRate?.historicalAvg?.avg8h), formatAvgRate(bestRate?.historicalAvg?.avg24h), formatAvgRate(bestRate?.historicalAvg?.avg7d),
|
|
296
210
|
];
|
|
297
211
|
});
|
|
298
|
-
console.log(makeTable(["Symbol", ...columns.map(v => v.displayName), "Spread", "24h", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
212
|
+
console.log(makeTable(["Symbol", ...columns.map(v => v.apiStatus.kind === "unofficial" ? `${v.displayName}*` : v.displayName), "Spread", "24h", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
299
213
|
console.log(chalk.gray(`\n ${snapshot.symbols.length} symbols compared across exchanges.\n`));
|
|
214
|
+
const unofficial = columns.filter(v => v.apiStatus.kind === "unofficial");
|
|
215
|
+
if (unofficial.length > 0)
|
|
216
|
+
console.log(chalk.yellow(` * ${unofficial.map(v => v.displayName).join(", ")}: UNOFFICIAL API (undocumented, read-only; may change or break). Unverified funding — shown only with --include-unverified.\n`));
|
|
300
217
|
}
|
|
301
218
|
function printFundingExchangeStatus(snapshot) {
|
|
302
219
|
const statuses = Object.entries(snapshot.exchangeStatus).map(([ex, status]) => {
|
|
@@ -317,7 +234,7 @@ function printDetailedComparison(comparison) {
|
|
|
317
234
|
}
|
|
318
235
|
const spreadColor = comparison.maxSpreadAnnual >= 30 ? chalk.green.bold : comparison.maxSpreadAnnual >= 10 ? chalk.yellow : chalk.white;
|
|
319
236
|
console.log(`\n Max Spread: ${spreadColor(`${comparison.maxSpreadAnnual.toFixed(1)}%`)} annual`);
|
|
320
|
-
console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}`);
|
|
237
|
+
console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}${comparison.executable ? "" : chalk.yellow(` — not executable: ${comparison.readOnlyLegs.join(", ")} is read-only in perp-cli`)}`);
|
|
321
238
|
console.log(` Est. Income: $${comparison.estHourlyIncomeUsd.toFixed(4)}/hr per $1K notional\n`);
|
|
322
239
|
}
|
|
323
240
|
// registerFundingSubcommands removed — use 'arb scan --rates/--compare/--history/--positions' instead
|
|
@@ -325,9 +242,13 @@ function printDetailedComparison(comparison) {
|
|
|
325
242
|
/** One-line stderr notice when rows of unverified-funding venues are shown (--include-unverified). */
|
|
326
243
|
function warnUnverifiedFunding(rates) {
|
|
327
244
|
const venues = [...new Set(rates.filter(r => !r.fundingVerified).map(r => r.exchange))];
|
|
328
|
-
if (venues.length
|
|
329
|
-
|
|
330
|
-
|
|
245
|
+
if (venues.length > 0) {
|
|
246
|
+
process.stderr.write(`[warn] ${venues.join(", ")}: funding rates are UNVERIFIED (unit not confirmed against funding payments; annualized values overstate venues whose funding accrues only during market hours). Shown on request — not for trading decisions.\n`);
|
|
247
|
+
}
|
|
248
|
+
const unofficial = [...new Set(rates.filter(r => r.apiStatus === "unofficial").map(r => r.exchange))];
|
|
249
|
+
if (unofficial.length > 0) {
|
|
250
|
+
process.stderr.write(`[warn] ${unofficial.join(", ")}: read through an UNOFFICIAL API (undocumented; may change or be blocked without notice). Rows carry apiStatus "unofficial".\n`);
|
|
251
|
+
}
|
|
331
252
|
}
|
|
332
253
|
export async function handleRates(isJson, opts) {
|
|
333
254
|
const minSpread = parseFloat(opts.minSpread);
|
|
@@ -373,59 +294,52 @@ export async function handleRates(isJson, opts) {
|
|
|
373
294
|
printSnapshotTable(snapshot);
|
|
374
295
|
printFundingExchangeStatus(snapshot);
|
|
375
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Cross-venue price basis — a PRICE comparison, independent of funding.
|
|
299
|
+
*
|
|
300
|
+
* Venue scope: every venue with the "trading" role (both legs must be executable), whether or not
|
|
301
|
+
* its funding rate is verified; market-data-only venues (TxFlow, unofficial API) are out. Prices
|
|
302
|
+
* come from each venue's market directory and are grouped by `canonical` symbol with the funding
|
|
303
|
+
* scan's guards (fetchCanonicalPriceGroups): unclassified / ambiguous tickers have no canonical and
|
|
304
|
+
* never pair, and a venue whose mark deviates more than MAX_PRICE_DEVIATION_PCT from the group
|
|
305
|
+
* median is dropped. The chosen cheap / rich pair must itself agree within MAX_PRICE_DEVIATION_PCT:
|
|
306
|
+
* a wider gap is a different asset under the same ticker (BB on Lighter 8.50 vs Aster 0.0091).
|
|
307
|
+
* A venue that fails is reported on stderr and is absent from the scan, never silently empty.
|
|
308
|
+
*/
|
|
376
309
|
export async function handleBasisScan(isJson, opts) {
|
|
377
310
|
if (!isJson())
|
|
378
311
|
console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
|
|
379
|
-
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
380
|
-
// alive when one DEX errors out, while rejection reasons get logged to
|
|
381
|
-
// stderr (explicit failure, never silent [] downgrade).
|
|
382
|
-
const settled = await Promise.allSettled([
|
|
383
|
-
fetchPacificaRates(),
|
|
384
|
-
fetchHyperliquidRates(),
|
|
385
|
-
fetchLighterRates(),
|
|
386
|
-
fetchAsterRates(),
|
|
387
|
-
]);
|
|
388
|
-
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter", "aster"], "basis-scan");
|
|
389
|
-
const pacRates = settled[0].status === "fulfilled" ? settled[0].value : [];
|
|
390
|
-
const hlRates = settled[1].status === "fulfilled" ? settled[1].value : [];
|
|
391
|
-
const ltRates = settled[2].status === "fulfilled" ? settled[2].value : [];
|
|
392
|
-
const astRates = settled[3].status === "fulfilled" ? settled[3].value : [];
|
|
393
|
-
const exchangePrices = new Map();
|
|
394
312
|
const filterSymbol = opts.symbol?.toUpperCase();
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (!exchangePrices.has(r.symbol))
|
|
401
|
-
exchangePrices.set(r.symbol, new Map());
|
|
402
|
-
exchangePrices.get(r.symbol).set(r.exchange, r.markPrice);
|
|
403
|
-
}
|
|
313
|
+
const snapshot = await fetchCanonicalPriceGroups(listVenuesWithRole("trading"), {
|
|
314
|
+
symbols: filterSymbol !== undefined ? [filterSymbol] : undefined, tag: "basis-scan",
|
|
315
|
+
});
|
|
316
|
+
for (const [venue, msg] of Object.entries(snapshot.exchangeErrors))
|
|
317
|
+
console.error(`[basis-scan] ${venue} fetch failed: ${msg}`);
|
|
404
318
|
const opps = [];
|
|
405
|
-
for (const [symbol,
|
|
406
|
-
if (
|
|
319
|
+
for (const [symbol, priced] of snapshot.groups) {
|
|
320
|
+
if (priced.length < 2)
|
|
407
321
|
continue;
|
|
408
|
-
let
|
|
409
|
-
for (const
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (price > maxPrice) {
|
|
415
|
-
maxPrice = price;
|
|
416
|
-
maxEx = ex;
|
|
417
|
-
}
|
|
322
|
+
let min = priced[0], max = priced[0];
|
|
323
|
+
for (const r of priced) {
|
|
324
|
+
if (r.markPrice < min.markPrice)
|
|
325
|
+
min = r;
|
|
326
|
+
if (r.markPrice > max.markPrice)
|
|
327
|
+
max = r;
|
|
418
328
|
}
|
|
419
|
-
if (
|
|
329
|
+
if (min.exchange === max.exchange)
|
|
420
330
|
continue;
|
|
421
|
-
const diff =
|
|
422
|
-
const avgPrice = (
|
|
331
|
+
const diff = max.markPrice - min.markPrice;
|
|
332
|
+
const avgPrice = (max.markPrice + min.markPrice) / 2;
|
|
423
333
|
const pctDiff = (diff / avgPrice) * 100;
|
|
334
|
+
if (!(pctDiff <= MAX_PRICE_DEVIATION_PCT)) {
|
|
335
|
+
console.error(`[basis-scan] excluded ${symbol} ${min.exchange}↔${max.exchange}: mark gap ${pctDiff.toFixed(1)}% > ${MAX_PRICE_DEVIATION_PCT}% (likely a different asset with the same ticker)`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
424
338
|
if (pctDiff * 365 >= parseFloat(opts.minBasis)) {
|
|
425
339
|
const priceObj = {};
|
|
426
|
-
for (const
|
|
427
|
-
priceObj[
|
|
428
|
-
opps.push({ symbol, prices: priceObj, priceDiff: diff, pctDiff, buyOn:
|
|
340
|
+
for (const r of priced)
|
|
341
|
+
priceObj[r.exchange] = r.markPrice;
|
|
342
|
+
opps.push({ symbol, prices: priceObj, priceDiff: diff, pctDiff, buyOn: min.exchange, sellOn: max.exchange });
|
|
429
343
|
}
|
|
430
344
|
}
|
|
431
345
|
opps.sort((a, b) => b.pctDiff - a.pctDiff);
|
|
@@ -439,12 +353,12 @@ export async function handleBasisScan(isJson, opts) {
|
|
|
439
353
|
console.log(chalk.gray(" Strategy: Buy cheap, sell expensive, profit from convergence.\n"));
|
|
440
354
|
const rows = opps.map((o) => [
|
|
441
355
|
chalk.white.bold(o.symbol),
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
o.prices.lighter ? `$${formatUsd(o.prices.lighter)}` : chalk.gray("-"),
|
|
356
|
+
`$${formatUsd(o.prices[o.buyOn])}`,
|
|
357
|
+
`$${formatUsd(o.prices[o.sellOn])}`,
|
|
445
358
|
`$${formatUsd(o.priceDiff)}`, `${o.pctDiff.toFixed(4)}%`, chalk.green(o.buyOn), chalk.red(o.sellOn),
|
|
359
|
+
chalk.gray(String(Object.keys(o.prices).length)),
|
|
446
360
|
]);
|
|
447
|
-
console.log(makeTable(["Symbol", "
|
|
361
|
+
console.log(makeTable(["Symbol", "Buy Price", "Sell Price", "Diff", "Basis %", "Buy On", "Sell On", "Venues"], rows));
|
|
448
362
|
}
|
|
449
363
|
export async function handleDexScan(isJson, opts) {
|
|
450
364
|
if (!isJson())
|
|
@@ -497,9 +411,13 @@ export async function handleCompare(isJson, symbol, includeUnverified = false) {
|
|
|
497
411
|
if (comparison)
|
|
498
412
|
warnUnverifiedFunding(comparison.rates);
|
|
499
413
|
if (!comparison) {
|
|
414
|
+
// A venue ticker whose canonical symbol differs (Variational US500-PERP = SPY): say where to look.
|
|
415
|
+
const hint = (await import("../exchanges/variational/public.js")).canonicalHintFor(symbol);
|
|
500
416
|
if (isJson())
|
|
501
|
-
return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false }));
|
|
417
|
+
return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false, ...(hint !== null ? { hint } : {}) }));
|
|
502
418
|
console.log(chalk.gray(` ${symbol.toUpperCase()} not found on at least 2 exchanges.\n`));
|
|
419
|
+
if (hint !== null)
|
|
420
|
+
console.log(chalk.yellow(` Hint: ${hint}\n`));
|
|
503
421
|
return;
|
|
504
422
|
}
|
|
505
423
|
if (isJson())
|
|
@@ -594,7 +512,7 @@ export async function handleFundingPositions(isJson, getAdapterForExchange, opts
|
|
|
594
512
|
}
|
|
595
513
|
const sz = Math.abs(Number(pos.size));
|
|
596
514
|
const nu = sz * mk;
|
|
597
|
-
const fh = fd ? fd.fundingHours : null; /* market basis first
|
|
515
|
+
const fh = fd ? fd.fundingHours : null; /* market basis first */
|
|
598
516
|
const hr = fh !== null ? fr / fh : toHourlyRate(fr, exchange);
|
|
599
517
|
const ap = annualizeHourlyRate(hr);
|
|
600
518
|
const hp = estimateHourlyFundingFromHourly(hr, nu, pos.side);
|
package/dist/commands/bot.js
CHANGED
|
@@ -419,7 +419,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
|
|
|
419
419
|
}
|
|
420
420
|
console.log();
|
|
421
421
|
});
|
|
422
|
-
// ── quarantine
|
|
422
|
+
// ── quarantine: positions the bot refuses to touch until a human clears them ──
|
|
423
423
|
bot
|
|
424
424
|
.command("quarantine")
|
|
425
425
|
.description("List quarantined symbols (unknown / unverified order outcome or unhedged leg) — blocked for every bot strategy, arb exec and arb auto (~/.perp/bot-quarantine.json)")
|
package/dist/commands/builder.js
CHANGED
|
@@ -283,7 +283,7 @@ export function registerWalletBuilderCommands(wallet, isJson, getAdapterForExcha
|
|
|
283
283
|
.action(async (venueArg, _opts, cmd) => {
|
|
284
284
|
await withJsonErrors(isJson(), async () => {
|
|
285
285
|
const network = networkOf(cmd);
|
|
286
|
-
const venues = venueArg !== undefined ? [getVenue(venueArg).id] : [...builderFeeVenues(), ...["lighter", "lighter-rh", "arcus"]];
|
|
286
|
+
const venues = venueArg !== undefined ? [getVenue(venueArg).id] : [...builderFeeVenues(), ...["lighter", "lighter-rh", "arcus", "risex", "txflow", "variational", "grvt", "extended"]];
|
|
287
287
|
const rows = [];
|
|
288
288
|
for (const v of venues) {
|
|
289
289
|
const configured = builderTargetOf(loadSettings().builder, v) !== null;
|
package/dist/commands/funds.js
CHANGED
|
@@ -7,7 +7,10 @@ import { registerFundsBridgeCommands } from "./bridge.js";
|
|
|
7
7
|
import { registerFundsRebalanceCommands } from "./rebalance.js";
|
|
8
8
|
import { PerpError, isConnectFailure, isDefiniteOrderFailure, isTransportFailure, orderOutcomeUnknown, parsePositiveDecimal } from "../errors.js";
|
|
9
9
|
import { cliCredentialOptsFrom, requireMasterWallet } from "../config.js";
|
|
10
|
-
import { getVenue, requireCapabilities, READ_ONLY_CREDENTIALS } from "../exchanges/venue.js";
|
|
10
|
+
import { getVenue, requireCapabilities, roleUnsupported, READ_ONLY_CREDENTIALS } from "../exchanges/venue.js";
|
|
11
|
+
import { RISEX_APP_URL, RISEX_FUNDS_APP_ONLY } from "../exchanges/venues/risex.js";
|
|
12
|
+
import { VARIATIONAL_READ_ONLY_REMEDIATION } from "../exchanges/venues/variational.js";
|
|
13
|
+
import { EXTENDED_READ_ONLY_REMEDIATION } from "../exchanges/venues/extended.js";
|
|
11
14
|
import { unsupported } from "../exchanges/trading-v2.js";
|
|
12
15
|
import { registerOnchainFundsCommands } from "./funds-onchain.js";
|
|
13
16
|
import { QFEX_BANKER_NETWORK, QFEX_WITHDRAW_DISABLED, QFEX_WITHDRAW_ENABLED, QFEX_WITHDRAW_NETWORK } from "../exchanges/venues/qfex.js";
|
|
@@ -857,6 +860,34 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
857
860
|
throw err;
|
|
858
861
|
}
|
|
859
862
|
});
|
|
863
|
+
supportsDryRun(faucet.command("risex"))
|
|
864
|
+
.description("[testnet tooling] Get the RISEx testnet faucet's 1000 test USDC (once per wallet; also registers the account; refused on mainnet; supports --dry-run). Refused while RISEx is read-only (trading in a later release)")
|
|
865
|
+
.action(async () => {
|
|
866
|
+
// Gated with RISEx trading (read-only release): the faucet is a venue write.
|
|
867
|
+
const risexDef = getVenue("risex");
|
|
868
|
+
if (!risexDef.roles.includes("trading"))
|
|
869
|
+
throw roleUnsupported(risexDef, "faucet");
|
|
870
|
+
const network = getNetwork();
|
|
871
|
+
if (network !== "testnet") {
|
|
872
|
+
throw unsupported("RISEx", `the test-USDC faucet on ${network}`, "the faucet exists only on testnet", "Run on testnet: perp -n testnet funds faucet risex");
|
|
873
|
+
}
|
|
874
|
+
const adapter = await getAdapterForExchange("risex");
|
|
875
|
+
const { RisexAdapter } = await import("../exchanges/risex/adapter.js");
|
|
876
|
+
if (!(adapter instanceof RisexAdapter))
|
|
877
|
+
throw new Error("risex venue did not produce a RisexAdapter");
|
|
878
|
+
const account = adapter.accountAddress();
|
|
879
|
+
if (isDryRun()) {
|
|
880
|
+
return printFundsPlan({ action: "deposit", venue: "risex", network, asset: "USDC (test)", amount: "1000", method: "testnet faucet POST /v1/account/deposit", account });
|
|
881
|
+
}
|
|
882
|
+
const res = await adapter.testnetFaucet();
|
|
883
|
+
logExecution({ type: "bridge", exchange: "risex", symbol: "USDC", side: "deposit", size: "1000", status: "success", dryRun: false, meta: { action: "faucet", network, account } });
|
|
884
|
+
if (isJson())
|
|
885
|
+
return printJson(jsonOk({ ...res, account, amount: "1000", network, method: "faucet" }));
|
|
886
|
+
console.log(chalk.green(`\n RISEx testnet faucet: 1000 test USDC requested for ${account}`));
|
|
887
|
+
if (res.txHash !== null)
|
|
888
|
+
console.log(` Tx: ${res.txHash}`);
|
|
889
|
+
console.log();
|
|
890
|
+
});
|
|
860
891
|
// ═══════════════════════════════════════════════════════
|
|
861
892
|
// WITHDRAW
|
|
862
893
|
// ═══════════════════════════════════════════════════════
|
|
@@ -1049,7 +1080,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1049
1080
|
throw err;
|
|
1050
1081
|
}
|
|
1051
1082
|
});
|
|
1052
|
-
// ── Arcus — uniform surface
|
|
1083
|
+
// ── Arcus — uniform surface: same <amount> + --to,
|
|
1053
1084
|
// same jsonOk(result), global --dry-run plans without sending. ──
|
|
1054
1085
|
// Ondo Perps: the API does not allow withdrawals (address-book destinations only; adding one is
|
|
1055
1086
|
// refused for API keys). The command stays so the refusal is explicit — also under --dry-run.
|
|
@@ -1059,6 +1090,42 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1059
1090
|
.action(async () => {
|
|
1060
1091
|
throw unsupported("Ondo Perps", "withdraw", "the Ondo Perps API does not allow withdrawals", "Withdraw from the Ondo Perps web app");
|
|
1061
1092
|
});
|
|
1093
|
+
// RISEx: deposits / withdrawals are app-only (owner decision). The commands stay so the refusal
|
|
1094
|
+
// is explicit — also under --dry-run, before any credential or request.
|
|
1095
|
+
supportsDryRun(withdraw.command("risex [amount]"))
|
|
1096
|
+
.description(`Not available: RISEx withdrawals are done in the RISEx app (${RISEX_APP_URL}) — refused, also with --dry-run`)
|
|
1097
|
+
.option("--to <address>", "Ignored — perp-cli does not move RISEx funds")
|
|
1098
|
+
.action(async () => {
|
|
1099
|
+
throw unsupported("risex", "withdraw", RISEX_FUNDS_APP_ONLY.reason, RISEX_FUNDS_APP_ONLY.remediation);
|
|
1100
|
+
});
|
|
1101
|
+
// TxFlow: market data only (unofficial read-only API).
|
|
1102
|
+
supportsDryRun(withdraw.command("txflow [amount]"))
|
|
1103
|
+
.description("Not available: TxFlow is market data only in perp-cli (unofficial read-only API) — refused")
|
|
1104
|
+
.option("--to <address>", "Ignored")
|
|
1105
|
+
.action(async () => {
|
|
1106
|
+
throw roleUnsupported(getVenue("txflow"), "withdraw");
|
|
1107
|
+
});
|
|
1108
|
+
// Variational: read-only (market data) in perp-cli; no public trading API yet.
|
|
1109
|
+
supportsDryRun(withdraw.command("variational [amount]"))
|
|
1110
|
+
.description("Not available: Variational is read-only (market data) in perp-cli — refused")
|
|
1111
|
+
.option("--to <address>", "Ignored")
|
|
1112
|
+
.action(async () => {
|
|
1113
|
+
throw roleUnsupported(getVenue("variational"), "withdraw");
|
|
1114
|
+
});
|
|
1115
|
+
// GRVT: read-only (market data) in this version.
|
|
1116
|
+
supportsDryRun(withdraw.command("grvt [amount]"))
|
|
1117
|
+
.description("Not available: GRVT is read-only (market data) in this version — refused, also with --dry-run")
|
|
1118
|
+
.option("--to <address>", "Ignored")
|
|
1119
|
+
.action(async () => {
|
|
1120
|
+
throw roleUnsupported(getVenue("grvt"), "withdraw");
|
|
1121
|
+
});
|
|
1122
|
+
// Extended: market data only in this version.
|
|
1123
|
+
supportsDryRun(withdraw.command("extended [amount]"))
|
|
1124
|
+
.description("Not available: Extended is market data only in this version — refused, also with --dry-run")
|
|
1125
|
+
.option("--to <address>", "Ignored")
|
|
1126
|
+
.action(async () => {
|
|
1127
|
+
throw roleUnsupported(getVenue("extended"), "withdraw");
|
|
1128
|
+
});
|
|
1062
1129
|
for (const venueId of ["arcus"]) {
|
|
1063
1130
|
const def = getVenue(venueId);
|
|
1064
1131
|
const asset = withdrawAssetOf(def);
|
|
@@ -1128,7 +1195,7 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1128
1195
|
}
|
|
1129
1196
|
});
|
|
1130
1197
|
}
|
|
1131
|
-
// ── QFEX
|
|
1198
|
+
// ── QFEX — custodial; deposit = address display, withdraw = banker POST
|
|
1132
1199
|
// gated by settings.withdraw.qfex inside QfexAdapter.withdraw(); switched off (QFEX_WITHDRAW_ENABLED). ──
|
|
1133
1200
|
/** QFEX adapter for the active network — the default imported key, or the one named by --agent. */
|
|
1134
1201
|
const qfexAdapterFor = async (agentName) => {
|
|
@@ -1206,6 +1273,31 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1206
1273
|
throw err;
|
|
1207
1274
|
}
|
|
1208
1275
|
});
|
|
1276
|
+
supportsDryRun(deposit.command("risex [amount]"))
|
|
1277
|
+
.description(`Not available: RISEx deposits are done in the RISEx app (${RISEX_APP_URL}) — refused, also with --dry-run`)
|
|
1278
|
+
.action(async () => {
|
|
1279
|
+
throw unsupported("risex", "deposit", RISEX_FUNDS_APP_ONLY.reason, RISEX_FUNDS_APP_ONLY.remediation);
|
|
1280
|
+
});
|
|
1281
|
+
supportsDryRun(deposit.command("txflow [amount]"))
|
|
1282
|
+
.description("Not available: TxFlow is market data only in perp-cli (unofficial read-only API) — refused")
|
|
1283
|
+
.action(async () => {
|
|
1284
|
+
throw roleUnsupported(getVenue("txflow"), "deposit");
|
|
1285
|
+
});
|
|
1286
|
+
supportsDryRun(deposit.command("variational [amount]"))
|
|
1287
|
+
.description("Not available: Variational is read-only (market data) in perp-cli — refused")
|
|
1288
|
+
.action(async () => {
|
|
1289
|
+
throw roleUnsupported(getVenue("variational"), "deposit");
|
|
1290
|
+
});
|
|
1291
|
+
supportsDryRun(deposit.command("grvt [amount]"))
|
|
1292
|
+
.description("Not available: GRVT is read-only (market data) in this version — refused, also with --dry-run")
|
|
1293
|
+
.action(async () => {
|
|
1294
|
+
throw roleUnsupported(getVenue("grvt"), "deposit");
|
|
1295
|
+
});
|
|
1296
|
+
supportsDryRun(deposit.command("extended [amount]"))
|
|
1297
|
+
.description("Not available: Extended is market data only in this version — refused, also with --dry-run")
|
|
1298
|
+
.action(async () => {
|
|
1299
|
+
throw roleUnsupported(getVenue("extended"), "deposit");
|
|
1300
|
+
});
|
|
1209
1301
|
supportsDryRun(deposit.command("qfex"))
|
|
1210
1302
|
.description("Show the QFEX deposit address for USDC on Arbitrum One (perp-cli sends nothing — transfer from your own wallet; supports --dry-run)")
|
|
1211
1303
|
// --chain, not --network: the root -n/--network (mainnet | testnet) would swallow a subcommand --network.
|
|
@@ -1258,6 +1350,14 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1258
1350
|
if (isNaN(amountNum) || amountNum <= 0)
|
|
1259
1351
|
throw new Error("Invalid amount");
|
|
1260
1352
|
const exchangeOpt = program.opts().exchange;
|
|
1353
|
+
// -e names a venue this command cannot move funds on: refuse instead of transferring on Hyperliquid.
|
|
1354
|
+
if (exchangeOpt !== undefined) {
|
|
1355
|
+
const def = getVenue(exchangeOpt);
|
|
1356
|
+
if (!def.roles.includes("account"))
|
|
1357
|
+
throw roleUnsupported(def, "transfer");
|
|
1358
|
+
if (def.id === "risex")
|
|
1359
|
+
throw unsupported("risex", "transfer", RISEX_FUNDS_APP_ONLY.reason, RISEX_FUNDS_APP_ONLY.remediation);
|
|
1360
|
+
}
|
|
1261
1361
|
if (exchangeOpt !== undefined && getVenue(exchangeOpt).id === "qfex") {
|
|
1262
1362
|
if (isDryRun()) {
|
|
1263
1363
|
return printFundsPlan({ action: "transfer", venue: "qfex", network: getNetwork(), asset: "USDC", amount, from: opts.from ?? "the key's account", destination });
|
|
@@ -1349,6 +1449,11 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1349
1449
|
qfex: { chain: "Arbitrum", token: "USDC", min: null, method: "address display only: perp funds deposit qfex, then send USDC from your wallet" },
|
|
1350
1450
|
aster: { chain: "BNB Chain (default) / Arbitrum", token: "USDT (default) / USDC", min: null, gas: "BNB / Arbitrum ETH", method: "approve + vault depositFor(token, forAddress, amount, broker 1) (mainnet only)", command: "perp funds deposit aster <amount> [--chain bsc|arbitrum] [--asset USDT|USDC]" },
|
|
1351
1451
|
ondo: { chain: "Arbitrum (default) / Ethereum", token: "USDC", min: null, gas: "ETH", method: "USDC transfer to the account's funding address (mainnet only)", command: "perp funds deposit ondo <amount> [--chain arbitrum|ethereum] [--probe <amount>]" },
|
|
1452
|
+
risex: { supported: false, note: `${RISEX_FUNDS_APP_ONLY.reason} — ${RISEX_FUNDS_APP_ONLY.remediation}`, testnet: getVenue("risex").roles.includes("trading") ? "perp -n testnet funds faucet risex (1000 test USDC, once)" : null },
|
|
1453
|
+
txflow: { supported: false, note: "TxFlow is market data only in perp-cli (unofficial read-only API)" },
|
|
1454
|
+
variational: { supported: false, note: VARIATIONAL_READ_ONLY_REMEDIATION },
|
|
1455
|
+
grvt: { supported: false, note: "GRVT is read-only (market data) in this version; trading is planned for a later release" },
|
|
1456
|
+
extended: { supported: false, note: EXTENDED_READ_ONLY_REMEDIATION },
|
|
1352
1457
|
},
|
|
1353
1458
|
withdraw: {
|
|
1354
1459
|
pacifica: { chain: "Solana", token: "USDC", speed: "~10s", fee: "none" },
|
|
@@ -1360,6 +1465,11 @@ export function registerFundsCommands(program, getAdapter, isJson, getNetwork, g
|
|
|
1360
1465
|
? { chain: chainLabel(QFEX_WITHDRAW_NETWORK), token: "USDC", command: "perp funds withdraw qfex <amount> --to <allowlisted address>", gate: "settings withdraw.qfex (allowlist, maxPerTx, maxPerDay)", note: "needs a key with the \"Deposit and withdraw\" permission" }
|
|
1361
1466
|
: { supported: false, note: `${QFEX_WITHDRAW_DISABLED.reason} — ${QFEX_WITHDRAW_DISABLED.remediation}` },
|
|
1362
1467
|
arcus: { chain: "Robinhood Chain", token: "USDG", command: "perp funds withdraw arcus <amount>" },
|
|
1468
|
+
risex: { supported: false, note: `${RISEX_FUNDS_APP_ONLY.reason} — ${RISEX_FUNDS_APP_ONLY.remediation}` },
|
|
1469
|
+
txflow: { supported: false, note: "TxFlow is market data only in perp-cli (unofficial read-only API)" },
|
|
1470
|
+
variational: { supported: false, note: VARIATIONAL_READ_ONLY_REMEDIATION },
|
|
1471
|
+
grvt: { supported: false, note: "GRVT is read-only (market data) in this version; trading is planned for a later release" },
|
|
1472
|
+
extended: { supported: false, note: EXTENDED_READ_ONLY_REMEDIATION },
|
|
1363
1473
|
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" },
|
|
1364
1474
|
},
|
|
1365
1475
|
}));
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface SetupSettingsChange {
|
|
|
47
47
|
export declare function applySetupSettings(change: SetupSettingsChange): Settings;
|
|
48
48
|
/** Real follow-up commands for the configured venues (all exist in this CLI). */
|
|
49
49
|
export declare function nextStepCommands(venues: VenueId[], network: Network): string[];
|
|
50
|
-
/** Plain-language slippage explanation
|
|
50
|
+
/** Plain-language slippage explanation. */
|
|
51
51
|
export declare function slippageExplanation(venues: VenueId[]): string[];
|
|
52
52
|
export type AgentStepResult = {
|
|
53
53
|
venue: VenueId;
|
package/dist/commands/init.js
CHANGED
|
@@ -9,8 +9,9 @@ import { PerpError, classifyError } from "../errors.js";
|
|
|
9
9
|
import { jsonOk, jsonError, printJson } from "../utils.js";
|
|
10
10
|
import { resolvePassphrase } from "../agent-wallet/passphrase.js";
|
|
11
11
|
import { loadOws } from "../signer/ows-loader.js";
|
|
12
|
-
import { getVenue, listVenuesWithRole, networkConfig, tryResolveVenueId } from "../exchanges/venue.js";
|
|
12
|
+
import { getVenue, listVenuesWithRole, networkConfig, roleUnsupported, tryResolveVenueId } from "../exchanges/venue.js";
|
|
13
13
|
import "../exchanges/venues/index.js";
|
|
14
|
+
import { VARIATIONAL_READ_ONLY_REMEDIATION } from "../exchanges/venues/variational.js";
|
|
14
15
|
import { maxDefaultSlippageBps, parseSlippageSettingBps } from "../exchanges/slippage.js";
|
|
15
16
|
import { agentApproveUnsupportedReason, approveVenueAgent, assertLighterSlot, isLighterFamilyVenue, deprecatedPassphraseOption, printApproveOutcome, promptHidden, verifyVenueAgent, warnDeprecatedPassphrase, } from "./agent.js";
|
|
16
17
|
import { importOwsWallet } from "./wallet.js";
|
|
@@ -127,7 +128,11 @@ export function setupVenues() {
|
|
|
127
128
|
/** Resolve one venue id/alias for setup: must be a trading venue with an environment on `network`. */
|
|
128
129
|
export function resolveSetupVenue(input, network) {
|
|
129
130
|
const id = tryResolveVenueId(input.trim());
|
|
130
|
-
|
|
131
|
+
// A registered venue without the trading role (release-gated RISEx, market-data-only TxFlow /
|
|
132
|
+
// Variational / GRVT) is a capability refusal, not an unknown name.
|
|
133
|
+
if (id !== null && !getVenue(id).roles.includes("trading"))
|
|
134
|
+
throw roleUnsupported(getVenue(id), "setup");
|
|
135
|
+
if (id === null) {
|
|
131
136
|
throw new PerpError("INVALID_PARAMS", `Unknown trading venue "${input}"`, {
|
|
132
137
|
remediation: `Use one of: ${setupVenues().map(v => v.id).join(", ")}`,
|
|
133
138
|
});
|
|
@@ -177,7 +182,7 @@ export function applySetupSettings(change) {
|
|
|
177
182
|
}
|
|
178
183
|
/** Agent names already registered for `venue` on `network` in settings. */
|
|
179
184
|
function registeredAgentNames(venue, network) {
|
|
180
|
-
if (venue === "ondo" || venue === "arcus" || venue === "qfex") {
|
|
185
|
+
if (venue === "ondo" || venue === "arcus" || venue === "qfex" || venue === "risex") {
|
|
181
186
|
const map = loadSettings().agents?.[venue];
|
|
182
187
|
return map ? Object.values(map).filter(m => m.network === network).map(m => m.agentName) : [];
|
|
183
188
|
}
|
|
@@ -186,11 +191,11 @@ function registeredAgentNames(venue, network) {
|
|
|
186
191
|
function rotateHint(venue) {
|
|
187
192
|
if (venue === "qfex")
|
|
188
193
|
return "perp wallet agent import qfex --replace";
|
|
189
|
-
return venue === "ondo" || venue === "arcus"
|
|
194
|
+
return venue === "ondo" || venue === "arcus" || venue === "risex"
|
|
190
195
|
? `perp wallet agent approve ${venue} --rotate`
|
|
191
196
|
: `perp wallet agent rotate ${venue}`;
|
|
192
197
|
}
|
|
193
|
-
/** Why the wizard cannot register an agent for `venue` on `network`; null when it can (uniform wording
|
|
198
|
+
/** Why the wizard cannot register an agent for `venue` on `network`; null when it can (uniform wording). */
|
|
194
199
|
function approveBlocker(venue, network) {
|
|
195
200
|
const reason = agentApproveUnsupportedReason(venue, network, "approve");
|
|
196
201
|
return reason === null ? null : `${getVenue(venue).displayName} agent management on ${network} not supported — ${reason}`;
|
|
@@ -215,7 +220,7 @@ export function nextStepCommands(venues, network) {
|
|
|
215
220
|
cmds.push("perp settings show");
|
|
216
221
|
return cmds;
|
|
217
222
|
}
|
|
218
|
-
/** Plain-language slippage explanation
|
|
223
|
+
/** Plain-language slippage explanation. */
|
|
219
224
|
export function slippageExplanation(venues) {
|
|
220
225
|
const lines = [
|
|
221
226
|
"Market orders are sent as immediate-or-cancel (IOC) limit orders. The limit price is the",
|
|
@@ -260,6 +265,19 @@ function signingSummary(p, master) {
|
|
|
260
265
|
return ["No signature. Your Ondo API key is stored encrypted in ~/.perp/ondo-agents/ and checked with one read-only account request."];
|
|
261
266
|
case "qfex":
|
|
262
267
|
return ["No signature. Your QFEX API key is stored encrypted in ~/.perp/qfex-agents/ and checked with read-only requests."];
|
|
268
|
+
case "risex":
|
|
269
|
+
return [
|
|
270
|
+
`EIP-712 RegisterSigner signed by master EVM ${master.evm} (+ VerifySigner by the new signer): registers a session signer (${expiry}).`,
|
|
271
|
+
`Then the new signer signs DisablePermission for every permission except trading, and ${master.evm} sends those transactions ON-CHAIN on RISE chain (needs RISE-chain ETH for gas): the signer can trade but never move funds, and only the master could re-enable a permission.`,
|
|
272
|
+
];
|
|
273
|
+
case "txflow":
|
|
274
|
+
return ["Nothing — TxFlow is market data only (unofficial read-only API)."];
|
|
275
|
+
case "variational":
|
|
276
|
+
return [`Nothing — ${VARIATIONAL_READ_ONLY_REMEDIATION}.`];
|
|
277
|
+
case "grvt":
|
|
278
|
+
return ["Nothing — GRVT is read-only (market data) in this version; trading is planned for a later release."];
|
|
279
|
+
case "extended":
|
|
280
|
+
return ["Nothing — Extended is read-only (market data) in this version; trading is planned for a later release."];
|
|
263
281
|
}
|
|
264
282
|
}
|
|
265
283
|
function failed(venue, err) {
|
|
@@ -944,7 +962,9 @@ export async function runNonInteractiveSetup(opts) {
|
|
|
944
962
|
let defaultVenue = null;
|
|
945
963
|
if (defaultRaw !== undefined) {
|
|
946
964
|
const id = tryResolveVenueId(defaultRaw);
|
|
947
|
-
if (id
|
|
965
|
+
if (id !== null && !getVenue(id).roles.includes("trading"))
|
|
966
|
+
throw roleUnsupported(getVenue(id), "setup");
|
|
967
|
+
if (id === null) {
|
|
948
968
|
throw new PerpError("INVALID_PARAMS", `Invalid --default-venue "${defaultRaw}"`, {
|
|
949
969
|
remediation: `Use one of: ${(venues.length > 0 ? venues : setupVenues().map(v => v.id)).join(", ")}`,
|
|
950
970
|
});
|