perp-cli 0.14.3 → 0.16.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.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +138 -405
- package/README.zh.md +243 -0
- 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/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +85 -15
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- 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/quote-rules.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 +12 -5
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
- package/dist/bot/strategies/funding-arb-utils.js +53 -20
- 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.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +3 -3
- 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 +126 -6
- package/dist/commands/account.js +168 -24
- 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 +23 -81
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +175 -97
- package/dist/commands/arb.d.ts +20 -0
- package/dist/commands/arb.js +107 -163
- package/dist/commands/backtest.js +23 -21
- 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.d.ts +48 -0
- package/dist/commands/market.js +395 -100
- package/dist/commands/options.d.ts +14 -0
- package/dist/commands/options.js +151 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +455 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +83 -55
- 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 +67 -62
- 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/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +3 -3
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +35 -3
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +16 -12
- package/dist/exchanges/arcus/adapter.js +45 -18
- 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/public.js +3 -2
- 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 +30 -10
- package/dist/exchanges/aster.js +153 -53
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +8 -2
- package/dist/exchanges/canonical-symbols.js +46 -2
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +73 -0
- package/dist/exchanges/derive/options.js +379 -0
- package/dist/exchanges/extended/adapter.d.ts +96 -0
- package/dist/exchanges/extended/adapter.js +241 -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 +133 -0
- package/dist/exchanges/extended/public.js +436 -0
- package/dist/exchanges/grvt/adapter.d.ts +100 -0
- package/dist/exchanges/grvt/adapter.js +283 -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-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
- package/dist/exchanges/hyperliquid-spot.js +24 -16
- package/dist/exchanges/hyperliquid.d.ts +199 -22
- package/dist/exchanges/hyperliquid.js +550 -202
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +157 -13
- package/dist/exchanges/interface.js +47 -0
- 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 +67 -12
- package/dist/exchanges/lighter-public.js +250 -82
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +10 -5
- package/dist/exchanges/lighter-spot.js +105 -97
- package/dist/exchanges/lighter.d.ts +80 -21
- package/dist/exchanges/lighter.js +312 -105
- package/dist/exchanges/ondo/adapter.d.ts +38 -13
- package/dist/exchanges/ondo/adapter.js +86 -27
- 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 +35 -3
- package/dist/exchanges/ondo/public.js +75 -32
- 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/outcome-interface.d.ts +4 -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 +8 -2
- package/dist/exchanges/pacifica-spot.js +26 -18
- 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 +130 -0
- package/dist/exchanges/pacifica-vaults.js +277 -0
- package/dist/exchanges/pacifica.d.ts +108 -10
- package/dist/exchanges/pacifica.js +428 -171
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +69 -13
- package/dist/exchanges/qfex/adapter.js +102 -17
- 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 +65 -12
- package/dist/exchanges/qfex/public.js +131 -25
- 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 +3 -3
- 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 +160 -0
- package/dist/exchanges/risex/adapter.js +842 -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 +8 -8
- 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 +61 -0
- package/dist/exchanges/txflow/adapter.js +150 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +155 -0
- package/dist/exchanges/txflow/public.d.ts +87 -0
- package/dist/exchanges/txflow/public.js +355 -0
- package/dist/exchanges/unknown-symbol.d.ts +34 -0
- package/dist/exchanges/unknown-symbol.js +56 -0
- package/dist/exchanges/variational/adapter.d.ts +67 -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 +52 -10
- package/dist/exchanges/venue-types.js +82 -11
- package/dist/exchanges/venue.d.ts +150 -11
- package/dist/exchanges/venue.js +126 -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 +19 -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 +20 -8
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +18 -6
- 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 +15 -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 +131 -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 +6 -4
- package/dist/funding/legacy-public.js +42 -41
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +95 -6
- package/dist/funding/rates.js +99 -19
- 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/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +64 -0
- package/dist/mcp-server.js +211 -68
- package/dist/pacifica/client.d.ts +69 -5
- package/dist/pacifica/client.js +181 -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/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- 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 +36 -63
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +22 -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/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +980 -0
- package/dist/ws/index.js +4460 -0
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +996 -0
- package/dist/ws/node.js +4554 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +71 -10
- package/skills/perp-cli/SKILL.md +153 -36
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +22 -12
- package/skills/perp-cli/references/commands.md +143 -17
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +523 -0
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
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);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { isDryRun, networkOf, supportsDryRun } from "../../cli-flags.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { makeTable, formatUsd, formatPnl, printJson, jsonOk
|
|
3
|
+
import { makeTable, formatUsd, formatPnl, printJson, jsonOk } from "../../utils.js";
|
|
4
4
|
import { SPOT_PERP_TOKEN_MAP } from "../../exchanges/index.js";
|
|
5
5
|
import { readExecutionLog, logExecution } from "../../execution-log.js";
|
|
6
6
|
import { toHourlyRate } from "../../funding.js";
|
|
7
|
-
import {
|
|
7
|
+
import { fetchVenuesRates } from "../../funding/rates.js";
|
|
8
|
+
import { sharedApiNetwork } from "../../api/public/urls.js";
|
|
8
9
|
import { computeBasisRisk } from "../../arb-utils.js";
|
|
9
10
|
import { removePosition as persistRemovePosition, getPositions as getArbStatePositions, loadArbState } from "../../arb-state.js";
|
|
10
11
|
import { fetchBalancesWithFailures, computeRebalancePlan } from "../../rebalance.js";
|
|
@@ -15,7 +16,7 @@ import { listConfiguredAccountVenues } from "../../config.js";
|
|
|
15
16
|
import { closePerpPairVerified, handleSpotClose } from "../arb-auto.js";
|
|
16
17
|
import { quarantineEntryFor } from "../../bot/quarantine.js";
|
|
17
18
|
import { PerpError, classifyError, parsePositiveDecimal } from "../../errors.js";
|
|
18
|
-
import { listVenuesWithRole, resolveVenueId } from "../../exchanges/venue.js";
|
|
19
|
+
import { getVenue, listVenuesWithRole, resolveVenueId } from "../../exchanges/venue.js";
|
|
19
20
|
// ── Helpers ──
|
|
20
21
|
import { DEFAULT_TAKER_FEE as TAKER_FEE } from "../../constants.js";
|
|
21
22
|
/**
|
|
@@ -108,81 +109,22 @@ function entryHourly(e) {
|
|
|
108
109
|
return e.fundingHours !== null ? e.rate / e.fundingHours : toHourlyRate(e.rate, e.exchange);
|
|
109
110
|
}
|
|
110
111
|
async function fetchFundingRatesMap() {
|
|
112
|
+
// The SAME rate rows as `arb scan --rates` (fetchVenuesRates): canonical-keyed (unclassified /
|
|
113
|
+
// ambiguous tickers have no row), each with its own basis (fundingHours) and rateKind. A failed
|
|
114
|
+
// venue is reported on stderr (explicit, never silent).
|
|
115
|
+
const venues = ["pacifica", "hyperliquid", "lighter", "aster"].map(id => getVenue(id));
|
|
116
|
+
const { rates: fetched, exchangeErrors } = await fetchVenuesRates(venues, sharedApiNetwork(), false);
|
|
117
|
+
// Never a leg on an unverified rate (row label; the venue filter already drops them).
|
|
118
|
+
const rates = fetched.filter(r => r.fundingVerified);
|
|
119
|
+
for (const [ex, msg] of Object.entries(exchangeErrors))
|
|
120
|
+
process.stderr.write(`[arb] ${ex}: funding rates unavailable — ${msg}\n`);
|
|
111
121
|
const rateMap = new Map();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
fetchLighterOrderBookDetails(),
|
|
119
|
-
fetchLighterFundingRates(),
|
|
120
|
-
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
|
|
121
|
-
fetch("https://fapi.asterdex.com/fapi/v3/fundingInfo").then(r => r.json()),
|
|
122
|
-
]);
|
|
123
|
-
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter:orderbook", "lighter:funding", "aster:premiumIndex", "aster:fundingInfo"], "arb");
|
|
124
|
-
const pacAssets = settled[0].status === "fulfilled" ? settled[0].value : [];
|
|
125
|
-
const hlAssets = settled[1].status === "fulfilled" ? settled[1].value : [];
|
|
126
|
-
const ltDetails = settled[2].status === "fulfilled" ? settled[2].value : [];
|
|
127
|
-
const ltFunding = settled[3].status === "fulfilled" ? settled[3].value : [];
|
|
128
|
-
// Aster funding basis is per symbol: a rate is usable only with its fundingInfo interval.
|
|
129
|
-
const asterPremiums = settled[4].status === "fulfilled" && settled[5].status === "fulfilled" ? settled[4].value : [];
|
|
130
|
-
const asterInfo = settled[5].status === "fulfilled" ? settled[5].value : [];
|
|
131
|
-
const asterHours = new Map();
|
|
132
|
-
for (const row of Array.isArray(asterInfo) ? asterInfo : []) {
|
|
133
|
-
const h = Number(row.fundingIntervalHours);
|
|
134
|
-
if (typeof row.symbol === "string" && Number.isFinite(h) && h > 0)
|
|
135
|
-
asterHours.set(row.symbol, h);
|
|
136
|
-
}
|
|
137
|
-
const addRate = (sym, exchange, rate, markPrice, fundingHours = null) => {
|
|
138
|
-
if (!sym)
|
|
139
|
-
return;
|
|
140
|
-
if (!rateMap.has(sym))
|
|
141
|
-
rateMap.set(sym, []);
|
|
142
|
-
rateMap.get(sym).push({ exchange, rate, markPrice, fundingHours });
|
|
143
|
-
};
|
|
144
|
-
for (const p of pacAssets)
|
|
145
|
-
addRate(p.symbol, "pacifica", p.funding, p.mark);
|
|
146
|
-
for (const h of hlAssets)
|
|
147
|
-
addRate(h.symbol, "hyperliquid", h.funding, h.markPx);
|
|
148
|
-
// Lighter: join details + funding by marketId
|
|
149
|
-
// SSOT rule #2: skip rows whose mark price cannot be resolved (no 0 fallback).
|
|
150
|
-
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
151
|
-
// price-source preference (NOT error fallback).
|
|
152
|
-
const ltPriceMap = new Map(ltDetails.map(d => [d.marketId, d.lastTradePrice]));
|
|
153
|
-
const ltSymMap = new Map(ltDetails.map(d => [d.marketId, d.symbol]));
|
|
154
|
-
for (const fr of ltFunding) {
|
|
155
|
-
const sym = fr.symbol || ltSymMap.get(fr.marketId) || "";
|
|
156
|
-
if (!sym)
|
|
157
|
-
continue;
|
|
158
|
-
const directMark = fr.markPrice;
|
|
159
|
-
const fallbackMark = ltPriceMap.get(fr.marketId);
|
|
160
|
-
let mp;
|
|
161
|
-
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
162
|
-
mp = directMark;
|
|
163
|
-
}
|
|
164
|
-
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
165
|
-
mp = fallbackMark;
|
|
166
|
-
}
|
|
167
|
-
if (mp === undefined || !Number.isFinite(fr.rate))
|
|
168
|
-
continue;
|
|
169
|
-
addRate(sym, "lighter", fr.rate, mp);
|
|
170
|
-
}
|
|
171
|
-
// Aster: premiumIndex
|
|
172
|
-
// SSOT rule #2: skip rows missing markPrice / lastFundingRate instead of
|
|
173
|
-
// 0-coercing — a 0 markPrice would corrupt downstream sizing.
|
|
174
|
-
for (const p of asterPremiums) {
|
|
175
|
-
const rawSym = String(p.symbol ?? "");
|
|
176
|
-
if (!rawSym.endsWith("USDT"))
|
|
177
|
-
continue;
|
|
178
|
-
const fundingRate = Number(p.lastFundingRate);
|
|
179
|
-
const markPrice = Number(p.markPrice);
|
|
180
|
-
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0)
|
|
181
|
-
continue;
|
|
182
|
-
const hours = asterHours.get(rawSym);
|
|
183
|
-
if (hours === undefined)
|
|
184
|
-
continue; // not in /fapi/v3/fundingInfo → cannot normalize
|
|
185
|
-
addRate(rawSym.replace(/USDT$/, ""), "aster", fundingRate, markPrice, hours);
|
|
122
|
+
for (const r of rates) {
|
|
123
|
+
if (r.basis.kind !== "perHours")
|
|
124
|
+
continue; // these four venues quote per hours
|
|
125
|
+
if (!rateMap.has(r.canonical))
|
|
126
|
+
rateMap.set(r.canonical, []);
|
|
127
|
+
rateMap.get(r.canonical).push({ exchange: r.exchange, rate: r.fundingRate, markPrice: r.markPrice, fundingHours: r.basis.hours });
|
|
186
128
|
}
|
|
187
129
|
return rateMap;
|
|
188
130
|
}
|
|
@@ -420,7 +362,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
420
362
|
};
|
|
421
363
|
// Collect spot balances per exchange
|
|
422
364
|
const spotBalancesByExchange = new Map();
|
|
423
|
-
// Bot/arb spot scope
|
|
365
|
+
// Bot/arb spot scope: configured venues with a separate spot balance + transfer.
|
|
424
366
|
const configuredVenues = new Set(await listConfiguredAccountVenues());
|
|
425
367
|
for (const def of listBotSpotVenues()) {
|
|
426
368
|
if (!configuredVenues.has(def.id))
|
|
@@ -802,7 +744,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
802
744
|
await dropStaleStateEntry(sym, opts.yes === true, dryRun);
|
|
803
745
|
return;
|
|
804
746
|
}
|
|
805
|
-
// Global quarantine
|
|
747
|
+
// Global quarantine: an unknown / unhedged outcome was recorded for
|
|
806
748
|
// this symbol. A human may still close it, but only knowingly (--force).
|
|
807
749
|
const quarantined = quarantineEntryFor(sym, (m) => console.error(chalk.red(m)));
|
|
808
750
|
if (quarantined !== null) {
|
|
@@ -846,7 +788,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
846
788
|
console.log(chalk.cyan(` [DRY-RUN] Would close spot-perp ${sym}: sell spot on ${spotExch} + buy back the perp short on ${perpExch}`));
|
|
847
789
|
return;
|
|
848
790
|
}
|
|
849
|
-
// One implementation for spot-perp close (fill-checked legs
|
|
791
|
+
// One implementation for spot-perp close (fill-checked legs): arb-auto's handler.
|
|
850
792
|
if (handleSpotClose === undefined)
|
|
851
793
|
throw new Error("spot-perp close handler is not registered (registerArbAutoCommands not called)");
|
|
852
794
|
await handleSpotClose(sym, { spotExch, perpExch }, getAdapterForExchange, isJson);
|
|
@@ -982,7 +924,7 @@ export function registerArbManageCommands(program, getAdapterForExchange, isJson
|
|
|
982
924
|
// Close the legs sequentially (long, then short), reduce-only and verified, each
|
|
983
925
|
// sized from the venue's own position size. No retries: a definite failure of the
|
|
984
926
|
// long close changes nothing; an unknown outcome or a leg left open after the
|
|
985
|
-
// other closed quarantines the symbol
|
|
927
|
+
// other closed quarantines the symbol.
|
|
986
928
|
const results = [];
|
|
987
929
|
const closeRes = await closePerpPairVerified({
|
|
988
930
|
source: "arb-close", sym,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Network } from "../exchanges/venue-types.js";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import type { ExchangeAdapter } from "../exchanges/index.js";
|
|
4
|
+
import type { FundingRateKind } from "../exchanges/interface.js";
|
|
4
5
|
interface FundingSnapshot {
|
|
5
6
|
symbol: string;
|
|
6
7
|
pacRate: number;
|
|
@@ -17,7 +18,24 @@ interface FundingSnapshot {
|
|
|
17
18
|
astMarkPrice: number;
|
|
18
19
|
/** Aster per-symbol funding interval (= basis and settlement interval) from /fapi/v3/fundingInfo; null when Aster has no rate. */
|
|
19
20
|
astHours: number | null;
|
|
21
|
+
/** Which rate each paired venue's value is (arb scan rateKind): "lastSettled" | "accruing" | "predicted" | null. */
|
|
22
|
+
rateKinds?: Record<string, FundingRateKind | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Each venue's OWN symbol for this canonical row (Hyperliquid kPEPE, Lighter 1000PEPE): orders,
|
|
25
|
+
* position matching and closes use it, never the canonical key. Absent = the venue trades `symbol`.
|
|
26
|
+
*/
|
|
27
|
+
venueSymbols?: Record<string, string>;
|
|
20
28
|
}
|
|
29
|
+
/** The venue symbol a snapshot / position leg trades on `exchange` (the canonical key when the venue uses it). */
|
|
30
|
+
export declare function legSymbol(row: {
|
|
31
|
+
symbol: string;
|
|
32
|
+
venueSymbols?: Record<string, string>;
|
|
33
|
+
}, exchange: string): string;
|
|
34
|
+
/** --symbols matches a row by its canonical key or any venue's own symbol (kPEPE or 1000PEPE). */
|
|
35
|
+
export declare function matchesSymbolFilter(row: {
|
|
36
|
+
symbol: string;
|
|
37
|
+
venueSymbols?: Record<string, string>;
|
|
38
|
+
}, filter: readonly string[]): boolean;
|
|
21
39
|
export interface LegOrderMinimums {
|
|
22
40
|
exchange: string;
|
|
23
41
|
minSize: string | null;
|
|
@@ -216,10 +234,16 @@ export declare function dexCloseBlockedBy(pos: {
|
|
|
216
234
|
longDex: string;
|
|
217
235
|
shortDex: string;
|
|
218
236
|
}, failedDexes: Record<string, string>): string | null;
|
|
237
|
+
export declare function fetchFundingSpreads(): Promise<FundingSnapshot[]>;
|
|
219
238
|
export declare let handleSpotClose: ((sym: string, opts: {
|
|
220
239
|
spotExch?: string;
|
|
221
240
|
perpExch?: string;
|
|
222
241
|
}, getAdapterForExchange: (exchange: string) => Promise<import("../exchanges/interface.js").ExchangeAdapter>, isJson: () => boolean) => Promise<void>) | undefined;
|
|
242
|
+
/**
|
|
243
|
+
* `arb auto` flag validation: every numeric flag must be a finite number in its range, `--size` a
|
|
244
|
+
* number or "auto", `--settle-strategy` one of block | aggressive | off. INVALID_PARAMS otherwise.
|
|
245
|
+
*/
|
|
246
|
+
export declare function assertArbAutoFlags(opts: Record<string, unknown>): void;
|
|
223
247
|
export declare function registerArbAutoCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean, getHLAdapterForDex?: (dex: string) => Promise<import("../exchanges/hyperliquid.js").HyperliquidAdapter>): void;
|
|
224
248
|
export declare let handleArbPnlRef: (() => Promise<void>) | undefined;
|
|
225
249
|
/**
|