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/agent.js
CHANGED
|
@@ -9,7 +9,8 @@ import chalk from "chalk";
|
|
|
9
9
|
import bs58 from "bs58";
|
|
10
10
|
import { loadSettings } from "../settings.js";
|
|
11
11
|
import { getAgent, setAgent, deleteAgent, listAgents, acquireLock, releaseLock, getVenueAgent, getVenueAgentForNetwork, setVenueAgent, deleteVenueAgent, listVenueAgents, agentNetworkOf, recordedAgentNetworkOf, assertAgentNetwork, getAgentForNetwork, agentSourceOf, } from "../agent-wallet/store.js";
|
|
12
|
-
import { getVenue, networkConfig, resolveVenueId } from "../exchanges/venue.js";
|
|
12
|
+
import { getVenue, networkConfig, resolveVenueId, roleUnsupported } from "../exchanges/venue.js";
|
|
13
|
+
import { RISEX_APP_URL } from "../exchanges/venues/risex.js";
|
|
13
14
|
import { listConfiguredAccountVenues } from "../config.js";
|
|
14
15
|
import "../exchanges/venues/index.js";
|
|
15
16
|
import { LIGHTER_RH_RESERVED_KEY_INDEXES } from "../exchanges/venues/lighter-family.js";
|
|
@@ -114,7 +115,7 @@ function readPipedStdin() {
|
|
|
114
115
|
});
|
|
115
116
|
}
|
|
116
117
|
/**
|
|
117
|
-
* Ondo API key id + secret, from stdin only (
|
|
118
|
+
* Ondo API key id + secret, from stdin only (never flags).
|
|
118
119
|
* TTY: key id prompt, then hidden secret prompt. Piped: line 1 = key id, line 2 = secret.
|
|
119
120
|
*/
|
|
120
121
|
/** Lighter ChangePubKey settlement window when confirming a slot (docs example: 30 s, 1 s apart; kept under the 60 req/min budget). */
|
|
@@ -445,7 +446,7 @@ async function verifyQfex(network, opts) {
|
|
|
445
446
|
};
|
|
446
447
|
}
|
|
447
448
|
/**
|
|
448
|
-
* Confirmation of the account a QFEX key was resolved to
|
|
449
|
+
* Confirmation of the account a QFEX key was resolved to. In a terminal: a
|
|
449
450
|
* y/N prompt showing the UUID. Without one (piped stdin, --json): only an explicit --account-id
|
|
450
451
|
* that equals the resolved account counts — the resolved UUID is printed on stderr either way.
|
|
451
452
|
*/
|
|
@@ -471,6 +472,40 @@ export function confirmImportAccount(explicitAccountId, json) {
|
|
|
471
472
|
}
|
|
472
473
|
};
|
|
473
474
|
}
|
|
475
|
+
/** RISEx: session-key status on the venue and the on-chain permission bitmap (read-only). */
|
|
476
|
+
async function verifyRisex(network, opts) {
|
|
477
|
+
const meta = opts.agentName !== undefined ? getVenueAgent("risex", opts.agentName) : getVenueAgentForNetwork("risex", network);
|
|
478
|
+
// Read-only release (trading role gated): approve / revoke are refused, so point to the RISEx app.
|
|
479
|
+
const tradingGated = !getVenue("risex").roles.includes("trading");
|
|
480
|
+
if (!meta) {
|
|
481
|
+
throw new PerpError("AGENT_NOT_REGISTERED", `No RISEx signer registered for ${network}`, {
|
|
482
|
+
remediation: tradingGated
|
|
483
|
+
? `RISEx is read-only in this perp-cli release (trading in a later release): manage signers in the RISEx app (${RISEX_APP_URL})`
|
|
484
|
+
: `perp${network === "testnet" ? " -n testnet" : ""} wallet agent approve risex --master <ows-wallet>`,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const { verifyRisexAgent } = await import("../exchanges/risex/agent.js");
|
|
488
|
+
const v = await verifyRisexAgent(meta);
|
|
489
|
+
const warnings = [];
|
|
490
|
+
if (!v.active)
|
|
491
|
+
warnings.push(`RISEx signer ${meta.signerAddress} status is ${String(v.status)} (1 = Active)`);
|
|
492
|
+
if (v.expired)
|
|
493
|
+
warnings.push(`RISEx signer ${meta.signerAddress} expired at ${meta.expiresAt}`);
|
|
494
|
+
if (v.active && (!v.tradingOnly || meta.permissions.tradingOnly !== true)) {
|
|
495
|
+
// An active signer that can do more than trade is a failure, never ok:true.
|
|
496
|
+
throw new PerpError("POLICY_DENIED", `RISEx signer ${meta.signerAddress} ("${meta.agentName}") is NOT restricted to trading-only (on-chain ${JSON.stringify(v.permissions)}, bitmap ${String(v.bitmap)}; recorded tradingOnly=${String(meta.permissions.tradingOnly)})${v.reason !== null ? ` — ${v.reason}` : ""}`, {
|
|
497
|
+
exchange: "risex", signer: meta.signerAddress, permissions: v.permissions, bitmap: v.bitmap,
|
|
498
|
+
remediation: tradingGated
|
|
499
|
+
? `Revoke it in the RISEx app (${RISEX_APP_URL}); then remove the local record: perp${network === "testnet" ? " -n testnet" : ""} wallet agent revoke risex ${meta.agentName} --force`
|
|
500
|
+
: `Revoke it: perp${network === "testnet" ? " -n testnet" : ""} wallet agent revoke risex ${meta.agentName}, then approve a new one`,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
registered: v.active, count: 1,
|
|
505
|
+
items: [{ agentName: meta.agentName, signer: meta.signerAddress, account: meta.accountAddress, network: meta.network, status: v.status, permissions: v.permissions, tradingOnly: v.tradingOnly, expiresAt: meta.expiresAt }],
|
|
506
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
474
509
|
/** Dispatch a verify to the venue-specific implementation. */
|
|
475
510
|
async function verifyVenue(ex, network, opts) {
|
|
476
511
|
if (ex === "aster")
|
|
@@ -487,8 +522,10 @@ async function verifyVenue(ex, network, opts) {
|
|
|
487
522
|
return verifyArcus(network, opts);
|
|
488
523
|
if (ex === "qfex")
|
|
489
524
|
return verifyQfex(network, opts);
|
|
525
|
+
if (ex === "risex")
|
|
526
|
+
return verifyRisex(network, opts);
|
|
490
527
|
throw new PerpError("NOT_IMPLEMENTED", `${ex} has no agents to verify`, {
|
|
491
|
-
remediation: "perp wallet agent verify <aster|hyperliquid|pacifica|lighter|lighter-rh|ondo|arcus|qfex> [agentName]",
|
|
528
|
+
remediation: "perp wallet agent verify <aster|hyperliquid|pacifica|lighter|lighter-rh|ondo|arcus|qfex|risex> [agentName]",
|
|
492
529
|
});
|
|
493
530
|
}
|
|
494
531
|
/**
|
|
@@ -509,6 +546,9 @@ export function agentApproveUnsupportedReason(venue, network, op) {
|
|
|
509
546
|
}
|
|
510
547
|
/** Throws the uniform CAPABILITY_UNSUPPORTED error when agentApproveUnsupportedReason() is non-null. */
|
|
511
548
|
export function assertAgentApproveSupported(venue, network, op) {
|
|
549
|
+
const def = getVenue(venue);
|
|
550
|
+
if (!def.roles.includes("trading"))
|
|
551
|
+
throw roleUnsupported(def, `agent ${op}`);
|
|
512
552
|
if (venue === "qfex") {
|
|
513
553
|
// Programmatic key creation needs a website session (SIWE + MFA) — not a CLI path.
|
|
514
554
|
throw unsupported("qfex", "agentApprove", "API keys are created (and revoked) on the QFEX website, behind 2FA", `Create the key in QFEX Developer Settings, then import it: perp${network === "testnet" ? " -n testnet" : ""} wallet agent import qfex`);
|
|
@@ -540,6 +580,11 @@ export function defaultAgentName(venue, network) {
|
|
|
540
580
|
case "ondo": return `perp-cli-ondo${suffix}`;
|
|
541
581
|
case "arcus": return `perp-cli-arcus${suffix}`;
|
|
542
582
|
case "qfex": return `perp-cli-qfex${suffix}`;
|
|
583
|
+
case "risex": return `perp-cli-risex${suffix}`;
|
|
584
|
+
case "txflow": return `perp-cli-txflow${suffix}`;
|
|
585
|
+
case "variational": return `perp-cli-variational${suffix}`;
|
|
586
|
+
case "grvt": return `perp-cli-grvt${suffix}`;
|
|
587
|
+
case "extended": return `perp-cli-extended${suffix}`;
|
|
543
588
|
}
|
|
544
589
|
}
|
|
545
590
|
/**
|
|
@@ -622,7 +667,7 @@ function requirePassphrase(passphrase) {
|
|
|
622
667
|
export async function approveVenueAgent(p) {
|
|
623
668
|
assertAgentApproveSupported(p.venue, p.network, "approve");
|
|
624
669
|
const agentName = p.agentName ?? defaultAgentName(p.venue, p.network);
|
|
625
|
-
// Ondo: HMAC key entered on stdin only
|
|
670
|
+
// Ondo: HMAC key entered on stdin only
|
|
626
671
|
if (p.venue === "ondo") {
|
|
627
672
|
assertVenueAgentNameFree("ondo", agentName, p.network);
|
|
628
673
|
if (getVenueAgent("ondo", agentName) && !p.rotate) {
|
|
@@ -636,7 +681,7 @@ export async function approveVenueAgent(p) {
|
|
|
636
681
|
setVenueAgent("ondo", meta);
|
|
637
682
|
return { venue: "ondo", meta };
|
|
638
683
|
}
|
|
639
|
-
// Arcus: OWS Ed25519 agent + EIP-712 createApiKey
|
|
684
|
+
// Arcus: OWS Ed25519 agent + EIP-712 createApiKey
|
|
640
685
|
if (p.venue === "arcus") {
|
|
641
686
|
const masterName = requireMaster(p.master);
|
|
642
687
|
assertVenueAgentNameFree("arcus", agentName, p.network);
|
|
@@ -660,6 +705,53 @@ export async function approveVenueAgent(p) {
|
|
|
660
705
|
releaseLock("arcus");
|
|
661
706
|
}
|
|
662
707
|
}
|
|
708
|
+
// RISEx: master-signed RegisterSigner, then an on-chain restriction to trading-only.
|
|
709
|
+
if (p.venue === "risex") {
|
|
710
|
+
const masterName = requireMaster(p.master);
|
|
711
|
+
assertVenueAgentNameFree("risex", agentName, p.network);
|
|
712
|
+
if (p.canWithdraw || p.canSpot) {
|
|
713
|
+
throw new PerpError("INVALID_PARAMS", "RISEx signers registered by perp-cli are always restricted to trading only (no fund movement, no spot)", {
|
|
714
|
+
remediation: "Drop --can-withdraw / --can-spot",
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const expiresAt = parseExpiresInParam(p.expiresIn);
|
|
718
|
+
const { approveRisexAgent, revokeRisexAgent, risexAgentWalletName, risexRotatePreflight, risexSignerStatus } = await import("../exchanges/risex/agent.js");
|
|
719
|
+
acquireLock("risex");
|
|
720
|
+
try {
|
|
721
|
+
const existing = getVenueAgent("risex", agentName);
|
|
722
|
+
if (existing !== null) {
|
|
723
|
+
if (!p.rotate) {
|
|
724
|
+
throw new PerpError("INVALID_PARAMS", `RISEx signer "${agentName}" already exists`, {
|
|
725
|
+
remediation: `Replace it (revoke the old signer, register a fresh one): perp${p.network === "testnet" ? " -n testnet" : ""} wallet agent approve risex --rotate — or choose another --agent-name`,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
// Rotate: the new signer's checks (pins, maintenance, master gas) pass first, then a
|
|
729
|
+
// venue-confirmed revoke of the old signer, then drop its key, then approve.
|
|
730
|
+
await risexRotatePreflight(masterName, p.passphrase, p.network);
|
|
731
|
+
// A kept record whose signer never registered (or is already revoked) has nothing to
|
|
732
|
+
// revoke on the venue: it is only forgotten locally. An Active one is revoked first.
|
|
733
|
+
const status = await risexSignerStatus(existing);
|
|
734
|
+
if (status === 1)
|
|
735
|
+
await revokeRisexAgent({ meta: existing, masterOwsWallet: masterName, passphrase: p.passphrase });
|
|
736
|
+
try {
|
|
737
|
+
loadOws().deleteWallet(existing.agentWalletName);
|
|
738
|
+
}
|
|
739
|
+
catch { /* already gone */ }
|
|
740
|
+
deleteVenueAgent("risex", agentName);
|
|
741
|
+
}
|
|
742
|
+
else if (p.rotate && loadOws().listWallets().some((w) => w.name === risexAgentWalletName(masterName, p.network))) {
|
|
743
|
+
throw new PerpError("INVALID_PARAMS", `OWS wallet "${risexAgentWalletName(masterName, p.network)}" exists but no RISEx signer record uses it`, {
|
|
744
|
+
remediation: `If no RISEx signer uses its key, delete it (ows wallet delete ${risexAgentWalletName(masterName, p.network)}) and re-run the approve`,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
const meta = await approveRisexAgent({ agentName, masterOwsWallet: masterName, passphrase: p.passphrase, network: p.network, expiresAt });
|
|
748
|
+
setVenueAgent("risex", meta);
|
|
749
|
+
return { venue: "risex", meta };
|
|
750
|
+
}
|
|
751
|
+
finally {
|
|
752
|
+
releaseLock("risex");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
663
755
|
const expiresAt = parseExpiresInParam(p.expiresIn);
|
|
664
756
|
const masterName = requireMaster(p.master);
|
|
665
757
|
const expiresAtIso = expiresAt.toISOString();
|
|
@@ -993,6 +1085,16 @@ export function printApproveOutcome(outcome) {
|
|
|
993
1085
|
console.log(` Valid Until: ${chalk.cyan(outcome.meta.validUntil)}`);
|
|
994
1086
|
console.log();
|
|
995
1087
|
return;
|
|
1088
|
+
case "risex":
|
|
1089
|
+
console.log(chalk.green.bold("\n RISEx signer registered and restricted to trading-only!\n"));
|
|
1090
|
+
console.log(` Name: ${chalk.cyan(outcome.meta.agentName)}`);
|
|
1091
|
+
console.log(` Signer: ${chalk.cyan(outcome.meta.signerAddress)}`);
|
|
1092
|
+
console.log(` Account: ${chalk.cyan(outcome.meta.accountAddress)}`);
|
|
1093
|
+
console.log(` Expires: ${chalk.cyan(outcome.meta.expiresAt)}`);
|
|
1094
|
+
console.log(` Permissions: ${chalk.cyan("trading only")} ${chalk.gray(`(on-chain; disabled ${outcome.meta.permissions.disabled.join(", ")}; verified ${outcome.meta.permissions.verifiedAt})`)}`);
|
|
1095
|
+
console.log(chalk.gray(" Deposits / withdrawals: RISEx app only (perp-cli never moves RISEx funds)."));
|
|
1096
|
+
console.log();
|
|
1097
|
+
return;
|
|
996
1098
|
case "lighter":
|
|
997
1099
|
case "lighter-rh":
|
|
998
1100
|
console.log(chalk.green.bold(`\n ${getVenue(outcome.venue).displayName} Agent approved successfully!\n`));
|
|
@@ -1043,7 +1145,7 @@ export function registerWalletAgentCommands(walletCmd, isJson) {
|
|
|
1043
1145
|
// ── agent approve <exchange> ── register an agent wallet ──────────────────
|
|
1044
1146
|
agent
|
|
1045
1147
|
.command("approve <exchange>")
|
|
1046
|
-
.description("Register an agent wallet / API key for an exchange (aster, hyperliquid, pacifica, lighter, lighter-rh, ondo, arcus)")
|
|
1148
|
+
.description("Register an agent wallet / API key for an exchange (aster, hyperliquid, pacifica, lighter, lighter-rh, ondo, arcus, risex — risex: a trading-only session signer, restricted on-chain)")
|
|
1047
1149
|
.option("--master <name>", "Master OWS wallet name")
|
|
1048
1150
|
.option("--agent-name <name>", "Agent name (default: perp-cli-aster)")
|
|
1049
1151
|
.option("--expires-in <duration>", "Expiry: 30d, 90d, 180d, 1y, or ISO-8601 datetime (default: 90d)")
|
|
@@ -1087,7 +1189,8 @@ export function registerWalletAgentCommands(walletCmd, isJson) {
|
|
|
1087
1189
|
assertAgentApproveSupported(venue, network, "approve");
|
|
1088
1190
|
let master = opts.master ?? null;
|
|
1089
1191
|
let agentName = opts.agentName ?? null;
|
|
1090
|
-
|
|
1192
|
+
// RISEx signers default to 30 days (owner decision; the venue maximum is not documented).
|
|
1193
|
+
let expiresIn = opts.expiresIn ?? (venue === "risex" ? "30d" : "90d");
|
|
1091
1194
|
let canPerp = opts.canPerp ?? true;
|
|
1092
1195
|
let canSpot = opts.canSpot ?? false;
|
|
1093
1196
|
let canWithdraw = opts.canWithdraw ?? false;
|
|
@@ -1218,6 +1321,9 @@ Examples:
|
|
|
1218
1321
|
}
|
|
1219
1322
|
const network = networkOf(command);
|
|
1220
1323
|
const venue = resolveVenueId(exchange);
|
|
1324
|
+
// TxFlow (market data only) / RISEx (perp-cli-registered, restricted signers only): refused before any stdin read.
|
|
1325
|
+
const { assertImportSupported } = await import("./agent-import.js");
|
|
1326
|
+
assertImportSupported(venue, network);
|
|
1221
1327
|
const int = (flag, raw) => {
|
|
1222
1328
|
if (raw === undefined)
|
|
1223
1329
|
return null;
|
|
@@ -1296,14 +1402,14 @@ Examples:
|
|
|
1296
1402
|
for (const { exchange: ex, meta } of listVenueAgents()) {
|
|
1297
1403
|
if (venueFilter !== null && venueFilter !== ex)
|
|
1298
1404
|
continue;
|
|
1299
|
-
const expiresAt = meta.kind === "arcus" ? meta.validUntil : null;
|
|
1405
|
+
const expiresAt = meta.kind === "arcus" ? meta.validUntil : meta.kind === "risex" ? meta.expiresAt : null;
|
|
1300
1406
|
const status = expiresAt !== null && expiresAt !== "" && new Date(expiresAt).getTime() < now ? "expired" : "active";
|
|
1301
1407
|
rows.push({
|
|
1302
1408
|
name: meta.agentName,
|
|
1303
1409
|
exchange: ex,
|
|
1304
1410
|
network: meta.network,
|
|
1305
1411
|
source: agentSourceOf(ex, meta),
|
|
1306
|
-
evmAddress: meta.kind === "arcus" ? meta.ownerAddress : "",
|
|
1412
|
+
evmAddress: meta.kind === "arcus" ? meta.ownerAddress : meta.kind === "risex" ? meta.signerAddress : "",
|
|
1307
1413
|
// Ondo keys carry no expiry; shown as "—".
|
|
1308
1414
|
expiresAt: expiresAt ?? "",
|
|
1309
1415
|
status,
|
|
@@ -1373,9 +1479,11 @@ Examples:
|
|
|
1373
1479
|
network = networkOf(command);
|
|
1374
1480
|
// An imported key is only ever removed locally, so it needs no venue support for revoke
|
|
1375
1481
|
// (e.g. an imported Aster testnet key).
|
|
1376
|
-
const rec = exchange === "ondo" || exchange === "arcus" || exchange === "qfex" ? getVenueAgent(exchange, agentName) : getAgent(exchange, agentName);
|
|
1482
|
+
const rec = exchange === "ondo" || exchange === "arcus" || exchange === "qfex" || exchange === "risex" ? getVenueAgent(exchange, agentName) : getAgent(exchange, agentName);
|
|
1377
1483
|
// QFEX keys only exist as imports: a missing record is "already removed", never an approve-capability error.
|
|
1378
|
-
|
|
1484
|
+
// A release-gated venue (RISEx in 0.15.0) still lets --force / --local-only clear local state: nothing is signed or sent.
|
|
1485
|
+
const localCleanupOfGated = localOnly && getVenue(exchange).gatedRoles !== null;
|
|
1486
|
+
if (exchange !== "qfex" && !localCleanupOfGated && (rec === null || agentSourceOf(exchange, rec) !== "imported"))
|
|
1379
1487
|
assertAgentApproveSupported(exchange, network, "revoke");
|
|
1380
1488
|
}
|
|
1381
1489
|
catch (e) {
|
|
@@ -1425,6 +1533,46 @@ Examples:
|
|
|
1425
1533
|
console.log(chalk.green(`Agent "${agentName}" on qfex removed locally.`) + chalk.yellow(`\n ${note}`));
|
|
1426
1534
|
return;
|
|
1427
1535
|
}
|
|
1536
|
+
// ── RISEx: RevokeSigner signed by the master wallet (gasless REST); then the key is deleted ──
|
|
1537
|
+
if (exchange === "risex") {
|
|
1538
|
+
const risexMeta = getVenueAgent("risex", agentName);
|
|
1539
|
+
if (!risexMeta) {
|
|
1540
|
+
if (useJson)
|
|
1541
|
+
printJson({ ok: true, data: { alreadyRevoked: true } });
|
|
1542
|
+
else
|
|
1543
|
+
console.log(`Agent "${agentName}" on risex not found locally — already revoked.`);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (risexMeta.network !== network) {
|
|
1547
|
+
throw new PerpError("AGENT_NOT_REGISTERED", `RISEx signer "${agentName}" is registered on ${risexMeta.network}, not ${network}`, {
|
|
1548
|
+
remediation: risexMeta.network === "mainnet" ? "Run without -n testnet to revoke it" : "Run with -n testnet to revoke it",
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
let txHash = null;
|
|
1552
|
+
if (!localOnly) {
|
|
1553
|
+
const masterName = opts.master ?? loadSettings().owsActiveWallet;
|
|
1554
|
+
if (!masterName) {
|
|
1555
|
+
throw new PerpError("INVALID_PARAMS", "Master wallet name is required to revoke a RISEx signer", {
|
|
1556
|
+
remediation: "Use --master <name>, or --force to clear local state only (then revoke it in the RISEx app)",
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
const passphrase = await resolvePassphrase({ flag: passphraseFlag });
|
|
1560
|
+
const { revokeRisexAgent } = await import("../exchanges/risex/agent.js");
|
|
1561
|
+
txHash = (await revokeRisexAgent({ meta: risexMeta, masterOwsWallet: masterName, passphrase })).txHash;
|
|
1562
|
+
}
|
|
1563
|
+
try {
|
|
1564
|
+
loadOws().deleteWallet(risexMeta.agentWalletName);
|
|
1565
|
+
}
|
|
1566
|
+
catch { /* already gone */ }
|
|
1567
|
+
deleteVenueAgent("risex", agentName);
|
|
1568
|
+
if (useJson)
|
|
1569
|
+
printJson(jsonOk({ revoked: !localOnly, local: localOnly, txHash }));
|
|
1570
|
+
else
|
|
1571
|
+
console.log(localOnly
|
|
1572
|
+
? chalk.yellow(`Agent "${agentName}" on risex removed locally only — revoke signer ${risexMeta.signerAddress} in the RISEx app.`)
|
|
1573
|
+
: chalk.green(`Agent "${agentName}" on risex revoked successfully.`));
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1428
1576
|
// ── Arcus: EIP-712 RevokeApiKey signed by the master wallet ──
|
|
1429
1577
|
if (exchange === "arcus") {
|
|
1430
1578
|
const arcusMeta = getVenueAgent("arcus", agentName);
|
|
@@ -420,7 +420,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
420
420
|
};
|
|
421
421
|
// Collect spot balances per exchange
|
|
422
422
|
const spotBalancesByExchange = new Map();
|
|
423
|
-
// Bot/arb spot scope
|
|
423
|
+
// Bot/arb spot scope: configured venues with a separate spot balance + transfer.
|
|
424
424
|
const configuredVenues = new Set(await listConfiguredAccountVenues());
|
|
425
425
|
for (const def of listBotSpotVenues()) {
|
|
426
426
|
if (!configuredVenues.has(def.id))
|
|
@@ -802,7 +802,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
802
802
|
await dropStaleStateEntry(sym, opts.yes === true, dryRun);
|
|
803
803
|
return;
|
|
804
804
|
}
|
|
805
|
-
// Global quarantine
|
|
805
|
+
// Global quarantine: an unknown / unhedged outcome was recorded for
|
|
806
806
|
// this symbol. A human may still close it, but only knowingly (--force).
|
|
807
807
|
const quarantined = quarantineEntryFor(sym, (m) => console.error(chalk.red(m)));
|
|
808
808
|
if (quarantined !== null) {
|
|
@@ -846,7 +846,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
846
846
|
console.log(chalk.cyan(` [DRY-RUN] Would close spot-perp ${sym}: sell spot on ${spotExch} + buy back the perp short on ${perpExch}`));
|
|
847
847
|
return;
|
|
848
848
|
}
|
|
849
|
-
// One implementation for spot-perp close (fill-checked legs
|
|
849
|
+
// One implementation for spot-perp close (fill-checked legs): arb-auto's handler.
|
|
850
850
|
if (handleSpotClose === undefined)
|
|
851
851
|
throw new Error("spot-perp close handler is not registered (registerArbAutoCommands not called)");
|
|
852
852
|
await handleSpotClose(sym, { spotExch, perpExch }, getAdapterForExchange, isJson);
|
|
@@ -982,7 +982,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
982
982
|
// Close the legs sequentially (long, then short), reduce-only and verified, each
|
|
983
983
|
// sized from the venue's own position size. No retries: a definite failure of the
|
|
984
984
|
// long close changes nothing; an unknown outcome or a leg left open after the
|
|
985
|
-
// other closed quarantines the symbol
|
|
985
|
+
// other closed quarantines the symbol.
|
|
986
986
|
const results = [];
|
|
987
987
|
const closeRes = await closePerpPairVerified({
|
|
988
988
|
source: "arb-close", sym,
|
|
@@ -20,7 +20,7 @@ import { loadArbState, addPosition as persistAddPosition, removePosition as pers
|
|
|
20
20
|
import { handleRates, handleBasisScan, handleDexScan, handleCompare, handleFundingHistory, handleFundingPositions, fetchAllPrices, printGapTable, printTrackSummary, formatGapPrice, } from "./arb.js";
|
|
21
21
|
// ── Fee-Adjusted Net Spread Calculation ──
|
|
22
22
|
import { getTakerFee } from "../constants.js";
|
|
23
|
-
import { assertFundingArbVenue, getVenue, listVenuesWithRole, networkConfig, resolveVenueId, tryResolveVenueId } from "../exchanges/venue.js";
|
|
23
|
+
import { assertFundingArbVenue, getVenue, listVenuesWithRole, networkConfig, resolveVenueId, roleUnsupported, tryResolveVenueId } from "../exchanges/venue.js";
|
|
24
24
|
import { sharedApiNetwork } from "../api/public/urls.js";
|
|
25
25
|
import { findSpotQuoteBalance, readSpotQuoteAvailableFresh, requireBotSpotVenue, requireSpotAdapter, spotNetworkOf, spotPairSymbol, spotSharesPerpMargin, sweepSpotQuoteToPerp, transferQuoteToPerp, transferQuoteToSpot } from "../exchanges/spot-factory.js";
|
|
26
26
|
import { fillSizeString, readSpotBalancesFresh, sizeDecimalsOf, spotLotStepOf, spotMarketFill } from "../exchanges/spot-result.js";
|
|
@@ -821,7 +821,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
821
821
|
if (stop.requested())
|
|
822
822
|
break; // stopping: no new close sequence
|
|
823
823
|
const pos = openPositions[i];
|
|
824
|
-
// Quarantined
|
|
824
|
+
// Quarantined: hands off until a human clears it.
|
|
825
825
|
if (isQuarantined(pos.symbol, (m) => console.log(chalk.red(m))))
|
|
826
826
|
continue;
|
|
827
827
|
// Per-position isolation: one position's failing exit check (e.g. an
|
|
@@ -877,7 +877,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
877
877
|
try {
|
|
878
878
|
const longAdapter = await getAdapterForExchange(pos.longExchange);
|
|
879
879
|
const shortAdapter = await getAdapterForExchange(pos.shortExchange);
|
|
880
|
-
// Sequential, reduce-only, verified
|
|
880
|
+
// Sequential, reduce-only, verified: long first, then
|
|
881
881
|
// short. A definite long-close failure changes nothing → retried next
|
|
882
882
|
// cycle, quarantined after MAX_PAIR_CLOSE_FAILURES in a row. An unknown
|
|
883
883
|
// outcome, a partial close or an unhedged leg quarantines (no retry).
|
|
@@ -1114,7 +1114,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1114
1114
|
skip(snap.symbol, "already open");
|
|
1115
1115
|
continue;
|
|
1116
1116
|
}
|
|
1117
|
-
// Global quarantine
|
|
1117
|
+
// Global quarantine: unknown / unhedged outcome on this symbol somewhere.
|
|
1118
1118
|
if (isQuarantined(snap.symbol, (m) => console.log(chalk.red(m)))) {
|
|
1119
1119
|
skip(snap.symbol, "quarantined");
|
|
1120
1120
|
continue;
|
|
@@ -1234,7 +1234,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1234
1234
|
try {
|
|
1235
1235
|
const longAdapter = await getAdapterForExchange(longExchange);
|
|
1236
1236
|
const shortAdapter = await getAdapterForExchange(shortExchange);
|
|
1237
|
-
// Sequential verified legs
|
|
1237
|
+
// Sequential verified legs: long, then short sized to the
|
|
1238
1238
|
// long's verified fill; reduce-only rollback / trim; unknown → quarantine.
|
|
1239
1239
|
legsStarted = true;
|
|
1240
1240
|
const res = await openPerpPairVerified({
|
|
@@ -1445,7 +1445,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1445
1445
|
.option("--duration <minutes>", "Track duration in minutes (with --track)", "10")
|
|
1446
1446
|
.option("--beep", "Beep on large gaps (with --gaps)")
|
|
1447
1447
|
.option("--check-liquidity", "Check orderbook depth in live mode (slower)")
|
|
1448
|
-
.option("--include-unverified", "With --rates / --compare: also show venues whose funding rate is unverified (qfex) — display only, never ranked for trading")
|
|
1448
|
+
.option("--include-unverified", "With --rates / --compare: also show venues whose funding rate is unverified (qfex; txflow — unofficial API; variational; grvt — indicative rate; extended) — display only, never ranked for trading")
|
|
1449
1449
|
.action(async (opts) => {
|
|
1450
1450
|
// ── Price gap modes ──
|
|
1451
1451
|
if (opts.gaps || opts.track || opts.alert) {
|
|
@@ -1857,6 +1857,8 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1857
1857
|
for (const ex of [longExch, shortExch]) {
|
|
1858
1858
|
const venue = getVenue(ex);
|
|
1859
1859
|
if (!venue.roles.includes("trading")) {
|
|
1860
|
+
if (venue.gatedRoles !== null)
|
|
1861
|
+
throw roleUnsupported(venue, "trading");
|
|
1860
1862
|
throw unsupported(venue.displayName, "trading", "no trading API — it cannot be an arb leg", `Use a trading venue: ${listVenuesWithRole("trading").map(v => v.id).join(", ")}`);
|
|
1861
1863
|
}
|
|
1862
1864
|
}
|
|
@@ -1867,7 +1869,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1867
1869
|
console.log(chalk.red(" Long and short exchange must be different."));
|
|
1868
1870
|
return;
|
|
1869
1871
|
}
|
|
1870
|
-
// Global quarantine
|
|
1872
|
+
// Global quarantine: an unknown / unhedged outcome on this symbol
|
|
1871
1873
|
// anywhere blocks new trades until a human clears it.
|
|
1872
1874
|
const quarantined = quarantineEntryFor(sym, (m) => console.error(m));
|
|
1873
1875
|
if (quarantined !== null) {
|
|
@@ -1875,7 +1877,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1875
1877
|
remediation: `${quarantined.remediation} — then: perp strategy quarantine-clear ${sym}`,
|
|
1876
1878
|
});
|
|
1877
1879
|
}
|
|
1878
|
-
// A venue whose funding rate is unverified is never a funding-arb leg
|
|
1880
|
+
// A venue whose funding rate is unverified is never a funding-arb leg.
|
|
1879
1881
|
assertFundingArbVenue(longExch);
|
|
1880
1882
|
assertFundingArbVenue(shortExch);
|
|
1881
1883
|
const longAdapter = await getAdapterForExchange(longExch);
|
|
@@ -1991,7 +1993,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
1991
1993
|
}
|
|
1992
1994
|
if (!isJson())
|
|
1993
1995
|
console.log(chalk.gray(` Executing legs sequentially (long, then short sized to the verified long fill)...\n`));
|
|
1994
|
-
// 5. Sequential verified legs
|
|
1996
|
+
// 5. Sequential verified legs. The second leg is sized to the
|
|
1995
1997
|
// first leg's VERIFIED fill, any shortfall is trimmed off the long
|
|
1996
1998
|
// (reduce-only, verified — always, --reconcile is implied), a definite short
|
|
1997
1999
|
// failure rolls the long back reduce-only, and an unknown outcome (incl. a
|
|
@@ -2070,12 +2072,14 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
2070
2072
|
// Resolve exchange aliases
|
|
2071
2073
|
spotExch = resolveVenueId(spotExch);
|
|
2072
2074
|
perpExch = resolveVenueId(perpExch);
|
|
2073
|
-
// Validate spot exchange: bot/arb spot scope
|
|
2075
|
+
// Validate spot exchange: bot/arb spot scope — throws CAPABILITY_UNSUPPORTED
|
|
2074
2076
|
// for venues without spot or outside the scope (pacifica shared margin, arcus swaps).
|
|
2075
2077
|
const spotDef = requireBotSpotVenue(spotExch.replace(/-spot$/, ""));
|
|
2076
2078
|
spotExch = spotDef.id;
|
|
2077
2079
|
perpExch = tryResolveVenueId(perpExch) ?? perpExch;
|
|
2078
2080
|
if (!getVenue(perpExch).roles.includes("trading")) {
|
|
2081
|
+
if (getVenue(perpExch).gatedRoles !== null)
|
|
2082
|
+
throw roleUnsupported(getVenue(perpExch), "trading");
|
|
2079
2083
|
throw unsupported(perpExch, "trading", "no trading API — it cannot be the perp leg", `Use a trading venue: ${listVenuesWithRole("trading").map(v => v.id).join(", ")}`);
|
|
2080
2084
|
}
|
|
2081
2085
|
assertFundingArbVenue(perpExch);
|
|
@@ -2093,7 +2097,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
2093
2097
|
return;
|
|
2094
2098
|
console.log(chalk.red(` ${msg}`));
|
|
2095
2099
|
};
|
|
2096
|
-
// Global quarantine
|
|
2100
|
+
// Global quarantine: checked before anything is read or moved.
|
|
2097
2101
|
const quarantined = quarantineEntryFor(sym, (m) => console.error(m));
|
|
2098
2102
|
if (quarantined !== null) {
|
|
2099
2103
|
throw new PerpError("RISK_VIOLATION", `${sym} is quarantined (${quarantined.strategy}: ${quarantined.reason})`, {
|
|
@@ -2273,7 +2277,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
2273
2277
|
// 7. Execute both legs. Same exchange shares a nonce → sequential; else parallel.
|
|
2274
2278
|
if (!isJson())
|
|
2275
2279
|
console.log(chalk.gray(` Executing ${sameVenue ? "sequentially" : "both legs simultaneously"}...\n`));
|
|
2276
|
-
// The perp leg goes through placePerpVerified
|
|
2280
|
+
// The perp leg goes through placePerpVerified: a fill is the measured
|
|
2277
2281
|
// position delta; an unknown outcome is ORDER_PENDING (quarantine, never roll back).
|
|
2278
2282
|
let spotResult;
|
|
2279
2283
|
let perpResult;
|
|
@@ -2523,7 +2527,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
2523
2527
|
remediation: `Pass both: perp arb close ${sym} --spot-exch <spot venue> --perp-exch <perp venue>`,
|
|
2524
2528
|
});
|
|
2525
2529
|
}
|
|
2526
|
-
// Load adapters (bot/arb spot scope
|
|
2530
|
+
// Load adapters (bot/arb spot scope)
|
|
2527
2531
|
const spotDef = requireBotSpotVenue(spotExch.replace(/-spot$/, ""));
|
|
2528
2532
|
const spotAdapter = await requireSpotAdapter(spotDef.id, await getAdapter(spotDef.id));
|
|
2529
2533
|
const perpAdapter = await getAdapter(perpExch);
|
|
@@ -2551,12 +2555,12 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
2551
2555
|
// Read before anything moves — a failure aborts the close.
|
|
2552
2556
|
const preCloseQuote = await readSpotQuoteAvailableFresh(spotAdapter);
|
|
2553
2557
|
// Same exchange → sequential (nonce collision), different → parallel.
|
|
2554
|
-
// The spot leg's REAL fill decides the outcome
|
|
2558
|
+
// The spot leg's REAL fill decides the outcome: only a confirmed spot sale
|
|
2555
2559
|
// counts as closed; legs are re-matched so no naked short / unhedged spot is left.
|
|
2556
2560
|
const sameVenue = spotDef.id === (tryResolveVenueId(perpExch) ?? perpExch);
|
|
2557
2561
|
const closeDec = sizeDecimalsOf(closeSize);
|
|
2558
|
-
// The perp buy-back and every re-match order go through placePerpVerified
|
|
2559
|
-
//
|
|
2562
|
+
// The perp buy-back and every re-match order go through placePerpVerified:
|
|
2563
|
+
// only a measured position change counts; an unknown outcome stops the close.
|
|
2560
2564
|
let spotResult;
|
|
2561
2565
|
let perpResult;
|
|
2562
2566
|
if (sameVenue) {
|
|
@@ -3073,7 +3077,7 @@ export function registerArbAutoCommands(program, getAdapterForExchange, isJson,
|
|
|
3073
3077
|
(p.long.dex === pos.shortDex && p.short.dex === pos.longDex)));
|
|
3074
3078
|
const currentSpread = current?.annualSpread ?? 0;
|
|
3075
3079
|
if (currentSpread <= closeSpread || !current) {
|
|
3076
|
-
// Quarantined
|
|
3080
|
+
// Quarantined: hands off until a human clears it.
|
|
3077
3081
|
if (isQuarantined(pos.underlying, (m) => console.error(chalk.red(m))))
|
|
3078
3082
|
continue;
|
|
3079
3083
|
const reason = !current ? "pair disappeared" : `spread ${currentSpread.toFixed(1)}% <= ${closeSpread}%`;
|
package/dist/commands/arb.d.ts
CHANGED
|
@@ -29,6 +29,18 @@ export declare function handleRates(isJson: () => boolean, opts: {
|
|
|
29
29
|
top?: string;
|
|
30
30
|
includeUnverified?: boolean;
|
|
31
31
|
}): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Cross-venue price basis — a PRICE comparison, independent of funding.
|
|
34
|
+
*
|
|
35
|
+
* Venue scope: every venue with the "trading" role (both legs must be executable), whether or not
|
|
36
|
+
* its funding rate is verified; market-data-only venues (TxFlow, unofficial API) are out. Prices
|
|
37
|
+
* come from each venue's market directory and are grouped by `canonical` symbol with the funding
|
|
38
|
+
* scan's guards (fetchCanonicalPriceGroups): unclassified / ambiguous tickers have no canonical and
|
|
39
|
+
* never pair, and a venue whose mark deviates more than MAX_PRICE_DEVIATION_PCT from the group
|
|
40
|
+
* median is dropped. The chosen cheap / rich pair must itself agree within MAX_PRICE_DEVIATION_PCT:
|
|
41
|
+
* a wider gap is a different asset under the same ticker (BB on Lighter 8.50 vs Aster 0.0091).
|
|
42
|
+
* A venue that fails is reported on stderr and is absent from the scan, never silently empty.
|
|
43
|
+
*/
|
|
32
44
|
export declare function handleBasisScan(isJson: () => boolean, opts: {
|
|
33
45
|
minBasis: string;
|
|
34
46
|
symbol?: string;
|