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
|
@@ -94,11 +94,13 @@ export function marginEntryBlocks(check, minMarginPct = 30) {
|
|
|
94
94
|
* @param maxSlippagePct - Max acceptable slippage (default: 0.3%)
|
|
95
95
|
* @returns USD size per leg, or 0 if not viable
|
|
96
96
|
*/
|
|
97
|
-
export async function computeAutoSize(longAdapter, shortAdapter, symbol, maxSlippagePct = 0.3
|
|
97
|
+
export async function computeAutoSize(longAdapter, shortAdapter, symbol, maxSlippagePct = 0.3,
|
|
98
|
+
/** The short venue's own symbol when it differs (kPEPE ↔ 1000PEPE); default `symbol`. */
|
|
99
|
+
shortSymbol = symbol) {
|
|
98
100
|
// Step 1: Get orderbooks from both sides
|
|
99
101
|
const [longOB, shortOB, longBal, shortBal] = await Promise.all([
|
|
100
102
|
longAdapter.getOrderbook(symbol),
|
|
101
|
-
shortAdapter.getOrderbook(
|
|
103
|
+
shortAdapter.getOrderbook(shortSymbol),
|
|
102
104
|
longAdapter.getBalance(),
|
|
103
105
|
shortAdapter.getBalance(),
|
|
104
106
|
]);
|
package/dist/dashboard/server.js
CHANGED
|
@@ -44,11 +44,11 @@ async function pollArbData() {
|
|
|
44
44
|
let exchangeStatus = {};
|
|
45
45
|
let exchangeErrors = {};
|
|
46
46
|
try {
|
|
47
|
-
const { fetchAllFundingRates } = await import("../funding/rates.js");
|
|
47
|
+
const { fetchAllFundingRates, isRankedComparison } = await import("../funding/rates.js");
|
|
48
48
|
const snapshot = await fetchAllFundingRates({ minSpread: 5 });
|
|
49
49
|
exchangeStatus = snapshot.exchangeStatus;
|
|
50
50
|
exchangeErrors = snapshot.exchangeErrors;
|
|
51
|
-
for (const sym of snapshot.symbols.slice(0, 20)) {
|
|
51
|
+
for (const sym of snapshot.symbols.filter(isRankedComparison).slice(0, 20)) {
|
|
52
52
|
opportunities.push({
|
|
53
53
|
symbol: sym.symbol,
|
|
54
54
|
longExchange: sym.longExchange,
|
|
@@ -100,7 +100,7 @@ async function pollArbData() {
|
|
|
100
100
|
const row = {
|
|
101
101
|
base,
|
|
102
102
|
dexes: assets.map((a) => {
|
|
103
|
-
// Venue ref: "hyperliquid" for native, "hl:<dex>" for HIP-3 (same basis
|
|
103
|
+
// Venue ref: "hyperliquid" for native, "hl:<dex>" for HIP-3 (same basis)
|
|
104
104
|
const ann = annualizeRate(a.fundingRate, a.dex === "hl" ? "hyperliquid" : `hl:${a.dex}`);
|
|
105
105
|
return { dex: a.dex, rate: a.fundingRate, annualizedPct: ann, markPrice: a.markPrice, oi: a.openInterest };
|
|
106
106
|
}),
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -235,6 +235,8 @@ function connect() {
|
|
|
235
235
|
// ── Helpers ──
|
|
236
236
|
function fmt(v, d=2) { const n=Number(v)||0; return n.toLocaleString('en-US',{minimumFractionDigits:d,maximumFractionDigits:d}); }
|
|
237
237
|
function usdOrDash(v, d=2) { return v==null ? '-' : '$'+fmt(v,d); }
|
|
238
|
+
// Market row cells: prices in the row's quote currency (QFEX SAMSUNG-KRW: "220,150.00 KRW", never $); OI is BASE units (never $); funding null = "-" (not 0%).
|
|
239
|
+
function marketCells(m) { const q=(typeof m.quoteAsset==='string'&&m.quoteAsset!=='USD')?escHtml(m.quoteAsset):null; const px=(v,d=2)=>v==null?'-':(q===null?'$'+fmt(v,d):fmt(v,d)+' '+q); const vol=m.volume24h==null&&typeof m.volume24hQuote==='string'?px(m.volume24hQuote,0):usdOrDash(m.volume24h,0); const base=escHtml(String(m.symbol).split(':').pop().replace(/-PERP$/i,'')); return { mark: px(m.markPrice), index: px(m.indexPrice), funding: m.fundingRate==null?'-':(Number(m.fundingRate)*100).toFixed(4)+'%', volume: vol, oi: m.openInterest==null?'-':Number(m.openInterest).toLocaleString('en-US',{maximumFractionDigits:4})+' '+base }; }
|
|
238
240
|
function pnlClass(v) { return Number(v)>=0?'pnl-pos':'pnl-neg'; }
|
|
239
241
|
function pnlSign(v) { const n=Number(v)||0; return n>=0?'+$'+fmt(Math.abs(n)):'-$'+fmt(Math.abs(n)); }
|
|
240
242
|
function rateClass(v) { const n=Number(v)||0; return n>0?'rate-positive':n<0?'rate-negative':'rate-neutral'; }
|
|
@@ -243,7 +245,8 @@ function spreadClass(v) { return v>=50?'spread-high':v>=20?'spread-mid':'spread-
|
|
|
243
245
|
const NA = '<span style="color:var(--yellow,#e5c07b)">unavailable</span>';
|
|
244
246
|
function usdOrNA(v, d=2) { return v==null ? NA : '$'+fmt(v,d); }
|
|
245
247
|
function pnlOrNA(v) { return v==null ? NA : pnlSign(v); }
|
|
246
|
-
|
|
248
|
+
// Every venue-supplied string (symbol, quoteAsset, dex, exchange, order fields) goes through this before innerHTML.
|
|
249
|
+
function escHtml(v) { return String(v).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]); }
|
|
247
250
|
|
|
248
251
|
// ── Portfolio Rendering ──
|
|
249
252
|
function renderTotals(t) {
|
|
@@ -325,16 +328,16 @@ function renderPanels(exchanges) {
|
|
|
325
328
|
const notional = p.markPrice == null ? null : Math.abs(Number(p.size)) * Number(p.markPrice);
|
|
326
329
|
const margin = notional == null ? null : p.leverage > 0 ? notional / p.leverage : notional;
|
|
327
330
|
const roe = margin != null && margin > 0 && p.unrealizedPnl != null ? (Number(p.unrealizedPnl) / margin * 100) : null;
|
|
328
|
-
return \`<tr>\${isHL?\`<td style="color:var(--purple);font-size:11px">\${dex}</td>\`:''}<td>\${sym}</td><td class="side-\${p.side}">\${p.side.toUpperCase()}</td><td>\${p.size}</td><td>\${usdOrNA(p.entryPrice)}</td><td>\${usdOrNA(p.markPrice)}</td><td>\${(p.liquidationPrice==null||p.liquidationPrice==='N/A')?'-':'$'+fmt(p.liquidationPrice)}</td><td style="color:var(--muted)">\${usdOrNA(notional)}</td><td class="\${pnlClass(p.unrealizedPnl)}">\${pnlOrNA(p.unrealizedPnl)}</td><td class="\${pnlClass(p.unrealizedPnl)}">\${roe==null?NA:(roe>=0?'+':'')+fmt(roe,1)+'%'}</td><td>\${p.leverage}x</td></tr>\`;
|
|
331
|
+
return \`<tr>\${isHL?\`<td style="color:var(--purple);font-size:11px">\${escHtml(dex)}</td>\`:''}<td>\${escHtml(sym)}</td><td class="side-\${escHtml(p.side)}">\${escHtml(String(p.side).toUpperCase())}</td><td>\${escHtml(p.size)}</td><td>\${usdOrNA(p.entryPrice)}</td><td>\${usdOrNA(p.markPrice)}</td><td>\${(p.liquidationPrice==null||p.liquidationPrice==='N/A')?'-':'$'+fmt(p.liquidationPrice)}</td><td style="color:var(--muted)">\${usdOrNA(notional)}</td><td class="\${pnlClass(p.unrealizedPnl)}">\${pnlOrNA(p.unrealizedPnl)}</td><td class="\${pnlClass(p.unrealizedPnl)}">\${roe==null?NA:(roe>=0?'+':'')+fmt(roe,1)+'%'}</td><td>\${p.leverage}x</td></tr>\`;
|
|
329
332
|
}).join('')}</tbody></table>\`:(posKnown?'<div class="empty-msg">No open positions</div>':'<div class="empty-msg">Positions '+NA+'</div>')}
|
|
330
333
|
<div class="section-title">Open Orders (\${ordKnown ? filteredOrders.length : '?'})</div>
|
|
331
334
|
\${filteredOrders.length?\`<table><thead><tr>\${isHL?'<th>DEX</th>':''}<th>Symbol</th><th>Side</th><th>Type</th><th>Price</th><th>Size</th><th>Filled</th><th>Status</th></tr></thead><tbody>\${filteredOrders.map(o=>{
|
|
332
335
|
const dex = getDex(o.symbol);
|
|
333
336
|
const sym = o.symbol.includes(':') ? o.symbol.split(':').slice(1).join(':') : o.symbol;
|
|
334
|
-
return \`<tr>\${isHL?\`<td style="color:var(--purple);font-size:11px">\${dex}</td>\`:''}<td>\${sym}</td><td class="side-\${o.side}">\${o.side.toUpperCase()}</td><td>\${o.type}</td><td>$\${fmt(o.price)}</td><td>\${o.size}</td><td>\${o.filled}</td><td>\${o.status}</td></tr>\`;
|
|
337
|
+
return \`<tr>\${isHL?\`<td style="color:var(--purple);font-size:11px">\${escHtml(dex)}</td>\`:''}<td>\${escHtml(sym)}</td><td class="side-\${escHtml(o.side)}">\${escHtml(String(o.side).toUpperCase())}</td><td>\${escHtml(o.type)}</td><td>$\${fmt(o.price)}</td><td>\${escHtml(o.size)}</td><td>\${escHtml(o.filled)}</td><td>\${escHtml(o.status)}</td></tr>\`;
|
|
335
338
|
}).join('')}</tbody></table>\`:(ordKnown?'<div class="empty-msg">No open orders</div>':'<div class="empty-msg">Open orders '+NA+'</div>')}
|
|
336
339
|
<div class="section-title">Markets (Top 10)</div>
|
|
337
|
-
\${ex.topMarkets.length?\`<table><thead><tr><th>Symbol</th><th>Mark</th><th>Index</th><th>Funding</th><th>24h Vol</th><th>OI</th><th>Max Lev</th></tr></thead><tbody>\${ex.topMarkets.map(m=>{const fr=Number(m.fundingRate);return\`<tr><td>\${m.symbol}</td><td
|
|
340
|
+
\${ex.topMarkets.length?\`<table><thead><tr><th>Symbol</th><th>Mark</th><th>Index</th><th>Funding</th><th>24h Vol</th><th>OI</th><th>Max Lev</th></tr></thead><tbody>\${ex.topMarkets.map(m=>{const fr=Number(m.fundingRate);const c=marketCells(m);return\`<tr><td>\${escHtml(m.symbol)}</td><td>\${c.mark}</td><td>\${c.index}</td><td class="\${rateClass(fr)}">\${c.funding}</td><td>\${c.volume}</td><td>\${c.oi}</td><td>\${m.maxLeverage==null?'-':m.maxLeverage+'x'}</td></tr>\`;}).join('')}</tbody></table>\`:'<div class="empty-msg">No market data</div>'}
|
|
338
341
|
</div>\`;
|
|
339
342
|
}).join('');
|
|
340
343
|
|
|
@@ -419,7 +422,7 @@ function renderArb() {
|
|
|
419
422
|
const barColor = o.spreadAnnual>=50?'var(--green)':o.spreadAnnual>=20?'var(--yellow)':'var(--muted)';
|
|
420
423
|
const bestMark = o.rates.reduce((best,r) => r.markPrice>0?r.markPrice:best, 0);
|
|
421
424
|
return \`<tr>
|
|
422
|
-
<td style="font-weight:600">\${o.symbol}</td>
|
|
425
|
+
<td style="font-weight:600">\${escHtml(o.symbol)}</td>
|
|
423
426
|
<td>
|
|
424
427
|
<div class="\${spreadClass(o.spreadAnnual)}">\${fmt(o.spreadAnnual,1)}%</div>
|
|
425
428
|
<div class="spread-bar"><div class="spread-bar-fill" style="width:\${barW}%;background:\${barColor}"></div></div>
|
|
@@ -514,7 +517,7 @@ function renderEvents() {
|
|
|
514
517
|
if (!events.length) { el.innerHTML='<div class="empty-msg">Waiting for events...</div>'; return; }
|
|
515
518
|
el.innerHTML = events.map(e => {
|
|
516
519
|
const cls = e.isCrit?'crit':e.isWarn?'warn':'';
|
|
517
|
-
return \`<div class="event"><span class="event-time">\${e.time}</span> <span class="event-type \${cls}">[\${e.type}]</span> <span>\${e.exchange}</span> <span style="color:var(--muted)">\${JSON.stringify(e.data).slice(0,120)}</span></div>\`;
|
|
520
|
+
return \`<div class="event"><span class="event-time">\${e.time}</span> <span class="event-type \${cls}">[\${e.type}]</span> <span>\${escHtml(e.exchange)}</span> <span style="color:var(--muted)">\${escHtml(JSON.stringify(e.data).slice(0,120))}</span></div>\`;
|
|
518
521
|
}).join('');
|
|
519
522
|
}
|
|
520
523
|
|
package/dist/errors.d.ts
CHANGED
|
@@ -158,7 +158,7 @@ export declare const ERROR_CODES: {
|
|
|
158
158
|
readonly status: 202;
|
|
159
159
|
readonly retryable: false;
|
|
160
160
|
};
|
|
161
|
-
/** The order was definitely NOT placed: the venue rejected it, or it failed before it was sent
|
|
161
|
+
/** The order was definitely NOT placed: the venue rejected it, or it failed before it was sent. */
|
|
162
162
|
readonly ORDER_REJECTED: {
|
|
163
163
|
readonly code: "ORDER_REJECTED";
|
|
164
164
|
readonly status: 422;
|
|
@@ -173,6 +173,29 @@ export declare const ERROR_CODES: {
|
|
|
173
173
|
readonly status: 422;
|
|
174
174
|
readonly retryable: false;
|
|
175
175
|
};
|
|
176
|
+
/** A venue frame could not be parsed or classified (never silently dropped). */
|
|
177
|
+
readonly WS_PROTOCOL_ERROR: {
|
|
178
|
+
readonly code: "WS_PROTOCOL_ERROR";
|
|
179
|
+
readonly status: 502;
|
|
180
|
+
readonly retryable: false;
|
|
181
|
+
};
|
|
182
|
+
readonly WS_AUTH_FAILED: {
|
|
183
|
+
readonly code: "WS_AUTH_FAILED";
|
|
184
|
+
readonly status: 401;
|
|
185
|
+
readonly retryable: false;
|
|
186
|
+
};
|
|
187
|
+
/** A subscription / connection budget (venue limit or caller budget) would be exceeded. */
|
|
188
|
+
readonly SUBSCRIPTION_LIMIT: {
|
|
189
|
+
readonly code: "SUBSCRIPTION_LIMIT";
|
|
190
|
+
readonly status: 400;
|
|
191
|
+
readonly retryable: false;
|
|
192
|
+
};
|
|
193
|
+
/** An event stream's bounded buffer overflowed; the subscription was closed. */
|
|
194
|
+
readonly STREAM_OVERFLOW: {
|
|
195
|
+
readonly code: "STREAM_OVERFLOW";
|
|
196
|
+
readonly status: 507;
|
|
197
|
+
readonly retryable: false;
|
|
198
|
+
};
|
|
176
199
|
readonly EXCHANGE_UNREACHABLE: {
|
|
177
200
|
readonly code: "EXCHANGE_UNREACHABLE";
|
|
178
201
|
readonly status: 503;
|
|
@@ -297,3 +320,8 @@ export declare function unknownActionRemediation(venue: string, action: string):
|
|
|
297
320
|
* and "1e3" are refused). Returns the trimmed string.
|
|
298
321
|
*/
|
|
299
322
|
export declare function parsePositiveDecimal(raw: string, what: string): string;
|
|
323
|
+
/**
|
|
324
|
+
* A keyless account read (positions, balance, orders, history): the one typed refusal every venue
|
|
325
|
+
* returns — NO_SIGNER_AVAILABLE with the setup remediation (never an UNKNOWN 500).
|
|
326
|
+
*/
|
|
327
|
+
export declare function noAccountKeyError(venue: string): PerpError;
|
package/dist/errors.js
CHANGED
|
@@ -25,7 +25,7 @@ export const ERROR_CODES = {
|
|
|
25
25
|
PASSPHRASE_REQUIRED: { code: "PASSPHRASE_REQUIRED", status: 401, retryable: false },
|
|
26
26
|
INVALID_PASSPHRASE: { code: "INVALID_PASSPHRASE", status: 401, retryable: false },
|
|
27
27
|
NOT_IMPLEMENTED: { code: "NOT_IMPLEMENTED", status: 501, retryable: false },
|
|
28
|
-
// Venue abstraction
|
|
28
|
+
// Venue abstraction
|
|
29
29
|
UNKNOWN_VENUE: { code: "UNKNOWN_VENUE", status: 400, retryable: false },
|
|
30
30
|
TIF_REQUIRED: { code: "TIF_REQUIRED", status: 400, retryable: false },
|
|
31
31
|
INVALID_TICK: { code: "INVALID_TICK", status: 400, retryable: false },
|
|
@@ -36,13 +36,21 @@ export const ERROR_CODES = {
|
|
|
36
36
|
ORDER_PARTIAL: { code: "ORDER_PARTIAL", status: 500, retryable: false },
|
|
37
37
|
/** The order was submitted but its outcome is not confirmed; retrying could double-trade. */
|
|
38
38
|
ORDER_PENDING: { code: "ORDER_PENDING", status: 202, retryable: false },
|
|
39
|
-
/** The order was definitely NOT placed: the venue rejected it, or it failed before it was sent
|
|
39
|
+
/** The order was definitely NOT placed: the venue rejected it, or it failed before it was sent. */
|
|
40
40
|
ORDER_REJECTED: { code: "ORDER_REJECTED", status: 422, retryable: false },
|
|
41
41
|
/**
|
|
42
42
|
* The venue explicitly refused a non-order account action (withdraw / transfer / approval) and
|
|
43
43
|
* executed nothing — distinct from a network / unknown outcome (EXCHANGE_ERROR, UNKNOWN).
|
|
44
44
|
*/
|
|
45
45
|
ACTION_REJECTED: { code: "ACTION_REJECTED", status: 422, retryable: false },
|
|
46
|
+
// WebSocket library (perp-cli/ws, src/ws/errors.ts keeps a parity-tested copy)
|
|
47
|
+
/** A venue frame could not be parsed or classified (never silently dropped). */
|
|
48
|
+
WS_PROTOCOL_ERROR: { code: "WS_PROTOCOL_ERROR", status: 502, retryable: false },
|
|
49
|
+
WS_AUTH_FAILED: { code: "WS_AUTH_FAILED", status: 401, retryable: false },
|
|
50
|
+
/** A subscription / connection budget (venue limit or caller budget) would be exceeded. */
|
|
51
|
+
SUBSCRIPTION_LIMIT: { code: "SUBSCRIPTION_LIMIT", status: 400, retryable: false },
|
|
52
|
+
/** An event stream's bounded buffer overflowed; the subscription was closed. */
|
|
53
|
+
STREAM_OVERFLOW: { code: "STREAM_OVERFLOW", status: 507, retryable: false },
|
|
46
54
|
// 5xx - System / transient errors
|
|
47
55
|
EXCHANGE_UNREACHABLE: { code: "EXCHANGE_UNREACHABLE", status: 503, retryable: true },
|
|
48
56
|
RATE_LIMITED: { code: "RATE_LIMITED", status: 429, retryable: true, retryAfterMs: 1000 },
|
|
@@ -92,6 +100,12 @@ export function classifyError(err, exchange) {
|
|
|
92
100
|
const ex = (err.structured.exchange ?? exchange);
|
|
93
101
|
return ex !== undefined ? { ...err.structured, exchange: ex } : { ...err.structured };
|
|
94
102
|
}
|
|
103
|
+
// Structural PerpError (perp-cli/ws WsError is a separate leaf class): keep its registered code.
|
|
104
|
+
if (err instanceof Error && isStructuredLike(err)) {
|
|
105
|
+
const s = err.structured;
|
|
106
|
+
const ex = s.exchange ?? exchange;
|
|
107
|
+
return ex !== undefined ? { ...s, exchange: ex } : { ...s };
|
|
108
|
+
}
|
|
95
109
|
const message = extractErrorMessage(err);
|
|
96
110
|
const lower = message.toLowerCase();
|
|
97
111
|
// Margin-specific checks first (before generic "insufficient" catch)
|
|
@@ -216,6 +230,14 @@ export function classifyError(err, exchange) {
|
|
|
216
230
|
}
|
|
217
231
|
return { ...ERROR_CODES.UNKNOWN, message };
|
|
218
232
|
}
|
|
233
|
+
function isStructuredLike(err) {
|
|
234
|
+
const s = err.structured;
|
|
235
|
+
if (s === null || typeof s !== "object")
|
|
236
|
+
return false;
|
|
237
|
+
const { code, message, status, retryable } = s;
|
|
238
|
+
return typeof code === "string" && Object.prototype.hasOwnProperty.call(ERROR_CODES, code)
|
|
239
|
+
&& typeof message === "string" && typeof status === "number" && typeof retryable === "boolean";
|
|
240
|
+
}
|
|
219
241
|
/** Custom error class that carries a structured error code */
|
|
220
242
|
export class PerpError extends Error {
|
|
221
243
|
structured;
|
|
@@ -235,7 +257,7 @@ export class PerpError extends Error {
|
|
|
235
257
|
};
|
|
236
258
|
}
|
|
237
259
|
}
|
|
238
|
-
// ── Order outcome classification
|
|
260
|
+
// ── Order outcome classification ──
|
|
239
261
|
/**
|
|
240
262
|
* Codes that mean "the order was definitely not placed / filled nothing": a validation
|
|
241
263
|
* or policy failure before sending (incl. PRICE_STALE: no price to build the order), or an
|
|
@@ -366,3 +388,13 @@ export function parsePositiveDecimal(raw, what) {
|
|
|
366
388
|
}
|
|
367
389
|
return s;
|
|
368
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* A keyless account read (positions, balance, orders, history): the one typed refusal every venue
|
|
393
|
+
* returns — NO_SIGNER_AVAILABLE with the setup remediation (never an UNKNOWN 500).
|
|
394
|
+
*/
|
|
395
|
+
export function noAccountKeyError(venue) {
|
|
396
|
+
return new PerpError("NO_SIGNER_AVAILABLE", `${venue}: no account key configured — account data unavailable`, {
|
|
397
|
+
exchange: venue,
|
|
398
|
+
remediation: `Set up an account for ${venue}: perp setup (or perp -e ${venue} wallet show to check)`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type AddressKind = "evm" | "solana";
|
|
2
|
+
export declare function isEvmAddress(value: string): boolean;
|
|
3
|
+
/** A base58 string that decodes to a 32-byte public key. */
|
|
4
|
+
export declare function isSolanaAddress(value: string): boolean;
|
|
5
|
+
/** Pacifica account / Print order / vault address: base58 32 bytes, else INVALID_PARAMS. */
|
|
6
|
+
export declare function requireSolanaAddress(value: string, field?: string): string;
|
|
7
|
+
/** `address` in the form `kind` expects (trimmed), else INVALID_PARAMS naming the expected form. */
|
|
8
|
+
export declare function requireAddressOfKind(venue: string, kind: AddressKind, address: string): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public account address formats (keyless reads by address): EVM `0x` + 40 hex (Hyperliquid,
|
|
3
|
+
* Lighter) and base58 Solana public keys (Pacifica). A wrong format is refused with
|
|
4
|
+
* INVALID_PARAMS before any request — the venues answer a foreign address with an empty success
|
|
5
|
+
* (Pacifica `success: true, []`), which would read as "no positions".
|
|
6
|
+
*/
|
|
7
|
+
import bs58 from "bs58";
|
|
8
|
+
import { PerpError } from "../errors.js";
|
|
9
|
+
const EVM_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
10
|
+
export function isEvmAddress(value) {
|
|
11
|
+
return EVM_RE.test(value.trim());
|
|
12
|
+
}
|
|
13
|
+
/** A base58 string that decodes to a 32-byte public key. */
|
|
14
|
+
export function isSolanaAddress(value) {
|
|
15
|
+
try {
|
|
16
|
+
return bs58.decode(value.trim()).length === 32;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Pacifica account / Print order / vault address: base58 32 bytes, else INVALID_PARAMS. */
|
|
23
|
+
export function requireSolanaAddress(value, field = "address") {
|
|
24
|
+
const a = value.trim();
|
|
25
|
+
if (!isSolanaAddress(a)) {
|
|
26
|
+
throw new PerpError("INVALID_PARAMS", `Not a Solana (base58) ${field}: "${value}"${isEvmAddress(a) ? " — that is an EVM address; Pacifica accounts are Solana addresses" : ""}`, {
|
|
27
|
+
exchange: "pacifica", remediation: "Pass the base58 Solana address you use on Pacifica",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return a;
|
|
31
|
+
}
|
|
32
|
+
/** `address` in the form `kind` expects (trimmed), else INVALID_PARAMS naming the expected form. */
|
|
33
|
+
export function requireAddressOfKind(venue, kind, address) {
|
|
34
|
+
const a = address.trim();
|
|
35
|
+
if (kind === "evm") {
|
|
36
|
+
if (isEvmAddress(a))
|
|
37
|
+
return a;
|
|
38
|
+
throw new PerpError("INVALID_PARAMS", `${venue} expects an EVM 0x address (0x + 40 hex)${isSolanaAddress(a) ? " — that is a Solana address" : ""}: "${address}"`, {
|
|
39
|
+
exchange: venue, remediation: `Pass the 0x address of the ${venue} account`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (isSolanaAddress(a))
|
|
43
|
+
return a;
|
|
44
|
+
throw new PerpError("INVALID_PARAMS", `${venue} expects a base58 Solana address${isEvmAddress(a) ? " — that is an EVM address" : ""}: "${address}"`, {
|
|
45
|
+
exchange: venue, remediation: `Pass the base58 Solana address of the ${venue} account`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Arcus adapter — native TradingV2 + legacy ExchangeAdapter shim
|
|
2
|
+
* Arcus adapter — native TradingV2 + legacy ExchangeAdapter shim.
|
|
3
3
|
*
|
|
4
4
|
* Signing: the agent API key is an Ed25519 key in OWS wallet
|
|
5
5
|
* `meta.agentWalletName` (raw signMessage on the Solana account — no prefix).
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* (master creds only, for spot swaps) every perp private call throws
|
|
8
8
|
* AGENT_NOT_REGISTERED from requireAccount().
|
|
9
9
|
*
|
|
10
|
-
* Order entry rules (docs.arcus.xyz
|
|
10
|
+
* Order entry rules (docs.arcus.xyz):
|
|
11
11
|
* - every order carries goodTilTime ≥ 1 month ahead (ARCUS_GOOD_TIL_HORIZON_MS),
|
|
12
12
|
* body in µs, signed `g` in ns;
|
|
13
13
|
* - market orders = IOC LIMIT at a protection price derived from mark and an
|
|
14
14
|
* explicit slippage bound, rejected locally when > 10 % from mark;
|
|
15
|
-
* - TIF
|
|
15
|
+
* - TIF: common GTC is sent as GTT with goodTil = now +
|
|
16
16
|
* ARCUS_GOOD_TIL_HORIZON_MS (recorded as execution.expiresAt); IOC / FOK / ALO
|
|
17
17
|
* are native; a limit order without a TIF is GTC;
|
|
18
18
|
* - clientId is always sent (orderAck async) and every OrderResult from a
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* an `entryTpsl` bundle.
|
|
23
23
|
*/
|
|
24
24
|
import type { EvmSigner } from "../../signer/interface.js";
|
|
25
|
-
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade } from "../interface.js";
|
|
25
|
+
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
|
|
26
26
|
import type { ResolvedSlippage } from "../slippage.js";
|
|
27
27
|
import { type TradingV2 } from "../trading-v2.js";
|
|
28
28
|
import type { VenueCreateContext, VenueDefinition } from "../venue.js";
|
|
@@ -37,6 +37,8 @@ export declare const ARCUS_GOOD_TIL_HORIZON_MS: number;
|
|
|
37
37
|
export declare const ARCUS_MAX_PROTECTION_DEVIATION = 0.1;
|
|
38
38
|
/** clientId max (36) minus the "-tp"/"-sl" suffix of entryTpsl child legs. */
|
|
39
39
|
export declare const ARCUS_TPSL_CLIENT_ID_MAX = 33;
|
|
40
|
+
/** /v1/trades and /v1/fundingRates take `limit` ≤ 1000 (docs; the venue answered 1000 rows for more). */
|
|
41
|
+
export declare const ARCUS_HISTORY_CAP = 1000;
|
|
40
42
|
export declare class ArcusAdapter implements ExchangeAdapter, TradingV2 {
|
|
41
43
|
readonly name: "arcus";
|
|
42
44
|
readonly venue: VenueDefinition;
|
|
@@ -57,6 +59,12 @@ export declare class ArcusAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
57
59
|
private accountQuery;
|
|
58
60
|
private loadMarkets;
|
|
59
61
|
private market;
|
|
62
|
+
/**
|
|
63
|
+
* A market-data read (book / trades / funding / candles) of a listed market that is not ONLINE
|
|
64
|
+
* is refused like `market info` / `market list` treat it (not listed): SYMBOL_NOT_FOUND naming
|
|
65
|
+
* the venue status — the venue answered `[]` or HTTP 404 ORDER_NOT_FOUND for it before.
|
|
66
|
+
*/
|
|
67
|
+
private onlineMarket;
|
|
60
68
|
private symbolOf;
|
|
61
69
|
/** Protection price: slippage from `ref`, aligned toward crossing on the tier tick, ≤ 10 % from `ref`. */
|
|
62
70
|
private protectionPrice;
|
|
@@ -92,7 +100,7 @@ export declare class ArcusAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
92
100
|
/**
|
|
93
101
|
* modifyOrder (docs modify-order / authentication).
|
|
94
102
|
*
|
|
95
|
-
* Size: the CLI's `req.size` is the new OPEN (unfilled) size
|
|
103
|
+
* Size: the CLI's `req.size` is the new OPEN (unfilled) size, while
|
|
96
104
|
* Arcus modifies take the order's new TOTAL size, filled quantity included
|
|
97
105
|
* (OrderRejectionReason MODIFY_SIZE_ALREADY_FILLED: "the order's new TOTAL size,
|
|
98
106
|
* filled quantity included"). So body `quantity` and signed `q` are
|
|
@@ -143,12 +151,8 @@ export declare class ArcusAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
143
151
|
bids: [string, string][];
|
|
144
152
|
asks: [string, string][];
|
|
145
153
|
}>;
|
|
146
|
-
getRecentTrades(symbol: string, limit?: number): Promise<
|
|
147
|
-
getFundingHistory(symbol: string, limit?: number): Promise<
|
|
148
|
-
time: number;
|
|
149
|
-
rate: string;
|
|
150
|
-
price: string | null;
|
|
151
|
-
}[]>;
|
|
154
|
+
getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
|
|
155
|
+
getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
|
|
152
156
|
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
|
|
153
157
|
private rawPositions;
|
|
154
158
|
getBalance(): Promise<ExchangeBalance>;
|
|
@@ -182,7 +186,7 @@ export declare class ArcusAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
182
186
|
}): Promise<unknown>;
|
|
183
187
|
private masterSigner;
|
|
184
188
|
private requireMasterKey;
|
|
185
|
-
/** Master EVM signer for on-chain actions outside the Arcus API (spot swaps
|
|
189
|
+
/** Master EVM signer for on-chain actions outside the Arcus API (spot swaps). */
|
|
186
190
|
masterEvmSigner(): Promise<EvmSigner>;
|
|
187
191
|
/** Sign an unsigned EVM transaction with the master key (digest derived here). Returns a 65-byte 0x signature. */
|
|
188
192
|
signMasterTransaction(tx: import("ethers").Transaction): Promise<string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Arcus adapter — native TradingV2 + legacy ExchangeAdapter shim
|
|
2
|
+
* Arcus adapter — native TradingV2 + legacy ExchangeAdapter shim.
|
|
3
3
|
*
|
|
4
4
|
* Signing: the agent API key is an Ed25519 key in OWS wallet
|
|
5
5
|
* `meta.agentWalletName` (raw signMessage on the Solana account — no prefix).
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* (master creds only, for spot swaps) every perp private call throws
|
|
8
8
|
* AGENT_NOT_REGISTERED from requireAccount().
|
|
9
9
|
*
|
|
10
|
-
* Order entry rules (docs.arcus.xyz
|
|
10
|
+
* Order entry rules (docs.arcus.xyz):
|
|
11
11
|
* - every order carries goodTilTime ≥ 1 month ahead (ARCUS_GOOD_TIL_HORIZON_MS),
|
|
12
12
|
* body in µs, signed `g` in ns;
|
|
13
13
|
* - market orders = IOC LIMIT at a protection price derived from mark and an
|
|
14
14
|
* explicit slippage bound, rejected locally when > 10 % from mark;
|
|
15
|
-
* - TIF
|
|
15
|
+
* - TIF: common GTC is sent as GTT with goodTil = now +
|
|
16
16
|
* ARCUS_GOOD_TIL_HORIZON_MS (recorded as execution.expiresAt); IOC / FOK / ALO
|
|
17
17
|
* are native; a limit order without a TIF is GTC;
|
|
18
18
|
* - clientId is always sent (orderAck async) and every OrderResult from a
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* an `entryTpsl` bundle.
|
|
23
23
|
*/
|
|
24
24
|
import { PerpError } from "../../errors.js";
|
|
25
|
+
import { isArcusOnlineMarket } from "../../ws/arcus-market.js";
|
|
25
26
|
import { OwsEvmSigner } from "../../signer/ows-evm.js";
|
|
26
27
|
import { OwsSolanaSigner } from "../../signer/ows-solana.js";
|
|
27
28
|
import { alignProtectionPrice, floorSize, mulDecimal, requirePositive, slippagePrice, subDecimal, toUnitsExact, unitsToString } from "../precision.js";
|
|
@@ -90,6 +91,15 @@ function finalityOf(s) {
|
|
|
90
91
|
return s === "filled" || s === "rejected" || s === "cancelled" ? "final" : "pending";
|
|
91
92
|
}
|
|
92
93
|
const usToMs = (us) => Math.floor(us / 1000);
|
|
94
|
+
/** /v1/trades and /v1/fundingRates take `limit` ≤ 1000 (docs; the venue answered 1000 rows for more). */
|
|
95
|
+
export const ARCUS_HISTORY_CAP = 1000;
|
|
96
|
+
function arcusLimit(what, limit) {
|
|
97
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > ARCUS_HISTORY_CAP) {
|
|
98
|
+
throw new PerpError("INVALID_PARAMS", `Arcus: ${what} limit must be 1..${ARCUS_HISTORY_CAP} (got ${limit})`, {
|
|
99
|
+
remediation: `Arcus serves at most ${ARCUS_HISTORY_CAP} rows per request`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
93
103
|
function sideLower(s) { return s === "BUY" ? "buy" : "sell"; }
|
|
94
104
|
function tifFromRead(t) {
|
|
95
105
|
// Reads report resting (GTT) orders as the legacy "GTC".
|
|
@@ -196,13 +206,13 @@ export class ArcusAdapter {
|
|
|
196
206
|
const meta = ctx.noAgent ? null : ctx.credentials.agent;
|
|
197
207
|
if (meta === null) {
|
|
198
208
|
if (!hasMaster) {
|
|
199
|
-
//
|
|
209
|
+
// no agent key and no master EVM key — nothing can sign.
|
|
200
210
|
throw new PerpError("NO_SIGNER_AVAILABLE", "No Arcus signer: no agent API key and no master EVM wallet / private key", {
|
|
201
211
|
remediation: "Spot swaps: pass --wallet <ows-wallet> (master EVM key). Perp trading: perp wallet agent approve arcus --master <ows-wallet>",
|
|
202
212
|
});
|
|
203
213
|
}
|
|
204
214
|
// Master-only (also --no-agent): spot swaps (master EVM signer) work; perp
|
|
205
|
-
// private calls throw AGENT_NOT_REGISTERED in requireAccount()
|
|
215
|
+
// private calls throw AGENT_NOT_REGISTERED in requireAccount().
|
|
206
216
|
this.client = new ArcusClient(ctx.network, null);
|
|
207
217
|
this.account = null;
|
|
208
218
|
return;
|
|
@@ -281,6 +291,21 @@ export class ArcusAdapter {
|
|
|
281
291
|
}
|
|
282
292
|
return m;
|
|
283
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* A market-data read (book / trades / funding / candles) of a listed market that is not ONLINE
|
|
296
|
+
* is refused like `market info` / `market list` treat it (not listed): SYMBOL_NOT_FOUND naming
|
|
297
|
+
* the venue status — the venue answered `[]` or HTTP 404 ORDER_NOT_FOUND for it before.
|
|
298
|
+
*/
|
|
299
|
+
async onlineMarket(symbol) {
|
|
300
|
+
const m = await this.market(symbol);
|
|
301
|
+
if (!isArcusOnlineMarket(m)) {
|
|
302
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Arcus: market ${m.marketDisplayName} is ${m.status} (not tradable; no live market data)`, {
|
|
303
|
+
exchange: "arcus", status: m.status, tradable: false,
|
|
304
|
+
remediation: "List live markets with: perp market list -e arcus",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return m;
|
|
308
|
+
}
|
|
284
309
|
async symbolOf(marketId) {
|
|
285
310
|
let m = (await this.loadMarkets(false)).byId.get(marketId);
|
|
286
311
|
if (!m)
|
|
@@ -462,7 +487,7 @@ export class ArcusAdapter {
|
|
|
462
487
|
return onlineArcusMarkets(raw).map(toArcusVenueMarket);
|
|
463
488
|
}
|
|
464
489
|
async placeOrder(input) {
|
|
465
|
-
//
|
|
490
|
+
// GTC is the uniform default for a limit order without a TIF.
|
|
466
491
|
const tifRequested = input.tif;
|
|
467
492
|
const req = input.type === "limit" && input.tif === null ? { ...input, tif: "GTC" } : input;
|
|
468
493
|
validateOrderRequest(this.venue, this.caps, req);
|
|
@@ -609,7 +634,7 @@ export class ArcusAdapter {
|
|
|
609
634
|
/**
|
|
610
635
|
* modifyOrder (docs modify-order / authentication).
|
|
611
636
|
*
|
|
612
|
-
* Size: the CLI's `req.size` is the new OPEN (unfilled) size
|
|
637
|
+
* Size: the CLI's `req.size` is the new OPEN (unfilled) size, while
|
|
613
638
|
* Arcus modifies take the order's new TOTAL size, filled quantity included
|
|
614
639
|
* (OrderRejectionReason MODIFY_SIZE_ALREADY_FILLED: "the order's new TOTAL size,
|
|
615
640
|
* filled quantity included"). So body `quantity` and signed `q` are
|
|
@@ -878,20 +903,22 @@ export class ArcusAdapter {
|
|
|
878
903
|
return (await this.getVenueMarkets()).map(toLegacyMarketInfo);
|
|
879
904
|
}
|
|
880
905
|
async getOrderbook(symbol) {
|
|
881
|
-
const m = await this.
|
|
906
|
+
const m = await this.onlineMarket(symbol);
|
|
882
907
|
const ob = await this.client.get(`/v1/l2OrderBook/${encodeURIComponent(m.marketDisplayName)}`, "l2OrderBook", { nLevels: ORDERBOOK_LEVELS });
|
|
883
908
|
return { bids: ob.bids, asks: ob.asks };
|
|
884
909
|
}
|
|
885
910
|
async getRecentTrades(symbol, limit = 50) {
|
|
886
|
-
|
|
911
|
+
arcusLimit("trades", limit);
|
|
912
|
+
const m = await this.onlineMarket(symbol);
|
|
887
913
|
const res = await this.client.get("/v1/trades", "trades", { market: m.marketDisplayName, limit });
|
|
888
|
-
// Public tape
|
|
889
|
-
return res.trades.map(t => ({ time: usToMs(t.timestamp), symbol: m.baseAsset, side: sideLower(t.side), price: t.price, size: t.size, fee:
|
|
914
|
+
// Public tape: no fee applies to the viewer → null, never "0".
|
|
915
|
+
return res.trades.map(t => ({ time: usToMs(t.timestamp), symbol: m.baseAsset, side: sideLower(t.side), price: t.price, size: t.size, fee: null }));
|
|
890
916
|
}
|
|
891
917
|
async getFundingHistory(symbol, limit = 100) {
|
|
892
|
-
|
|
918
|
+
arcusLimit("funding history", limit);
|
|
919
|
+
const m = await this.onlineMarket(symbol);
|
|
893
920
|
const res = await this.client.get("/v1/fundingRates", "fundingRates", { market: m.marketDisplayName, limit });
|
|
894
|
-
return res.fundingRates.map(f => ({ time: usToMs(f.time), rate: f.fundingRate, price: null }));
|
|
921
|
+
return res.fundingRates.map(f => ({ time: usToMs(f.time), rate: f.fundingRate, price: null, intervalHours: 1, rateKind: "lastSettled" }));
|
|
895
922
|
}
|
|
896
923
|
async getKlines(symbol, interval, startTime, endTime) {
|
|
897
924
|
if (!CANDLE_TIMEFRAMES.has(interval)) {
|
|
@@ -899,7 +926,7 @@ export class ArcusAdapter {
|
|
|
899
926
|
remediation: `Use one of: ${[...CANDLE_TIMEFRAMES].join(", ")}`,
|
|
900
927
|
});
|
|
901
928
|
}
|
|
902
|
-
const m = await this.
|
|
929
|
+
const m = await this.onlineMarket(symbol);
|
|
903
930
|
const span = CANDLE_WINDOW_BARS * CANDLE_MS[interval];
|
|
904
931
|
const windows = Math.max(1, Math.ceil((endTime - startTime) / span));
|
|
905
932
|
if (windows > CANDLE_MAX_WINDOWS) {
|
|
@@ -915,7 +942,7 @@ export class ArcusAdapter {
|
|
|
915
942
|
const res = await this.client.get("/v1/candles", "candles", { market: m.marketDisplayName, timeframe: interval, from: from * 1000, to: to * 1000 });
|
|
916
943
|
for (const c of res.candles) {
|
|
917
944
|
const time = usToMs(c.openTime);
|
|
918
|
-
byTime.set(time, { time, open: c.open, high: c.high, low: c.low, close: c.close, volume: c.volume, trades: c.tradeCount });
|
|
945
|
+
byTime.set(time, { time, open: c.open, high: c.high, low: c.low, close: c.close, volume: c.volume, quoteVolume: null, trades: c.tradeCount });
|
|
919
946
|
}
|
|
920
947
|
}
|
|
921
948
|
return [...byTime.values()].sort((x, y) => x.time - y.time);
|
|
@@ -995,7 +1022,7 @@ export class ArcusAdapter {
|
|
|
995
1022
|
out.push({ time: usToMs(p.time), symbol: await this.symbolOf(p.marketId), payment: p.payment });
|
|
996
1023
|
return out;
|
|
997
1024
|
}
|
|
998
|
-
// ── Legacy: trading shims
|
|
1025
|
+
// ── Legacy: trading shims ──
|
|
999
1026
|
/** `clientOrderId`: the caller's id when given; otherwise one is generated (orderAck async needs one). */
|
|
1000
1027
|
async marketOrder(symbol, side, size, opts) {
|
|
1001
1028
|
return this.placeOrder({
|
|
@@ -1036,7 +1063,7 @@ export class ArcusAdapter {
|
|
|
1036
1063
|
tif: isLimit ? "GTC" : null, // sent as GTT at the max horizon
|
|
1037
1064
|
reduceOnly: opts?.reduceOnly === true, // non-reduce-only → CAPABILITY_UNSUPPORTED in validation
|
|
1038
1065
|
clientOrderId: newClientOrderId(),
|
|
1039
|
-
// The caller's resolved bound (the adapter never reads settings
|
|
1066
|
+
// The caller's resolved bound (the adapter never reads settings);
|
|
1040
1067
|
// a stop-market without it → INVALID_PARAMS in placeOrder.
|
|
1041
1068
|
slippage: isLimit ? null : opts?.slippage ?? null,
|
|
1042
1069
|
tpsl: null,
|
|
@@ -1060,7 +1087,7 @@ export class ArcusAdapter {
|
|
|
1060
1087
|
});
|
|
1061
1088
|
}
|
|
1062
1089
|
}
|
|
1063
|
-
/** Master EVM signer for on-chain actions outside the Arcus API (spot swaps
|
|
1090
|
+
/** Master EVM signer for on-chain actions outside the Arcus API (spot swaps). */
|
|
1064
1091
|
async masterEvmSigner() {
|
|
1065
1092
|
this.requireMasterKey();
|
|
1066
1093
|
return this.masterSigner();
|
|
@@ -198,7 +198,7 @@ export class ArcusClient {
|
|
|
198
198
|
async send(method, url, endpoint, headers, body) {
|
|
199
199
|
// Timeout covers fetch + body read. A GET failure is definite (TIMEOUT /
|
|
200
200
|
// EXCHANGE_UNREACHABLE); a POST failure may have reached the venue → outcome
|
|
201
|
-
// unknown (ORDER_PENDING, never resubmit
|
|
201
|
+
// unknown (ORDER_PENDING, never resubmit).
|
|
202
202
|
const signal = AbortSignal.timeout(TIMEOUT_MS);
|
|
203
203
|
let res;
|
|
204
204
|
let text;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* - tradable: status === "ONLINE".
|
|
25
25
|
*/
|
|
26
26
|
import { PerpError } from "../../errors.js";
|
|
27
|
+
import { isArcusOnlineMarket } from "../../ws/arcus-market.js";
|
|
27
28
|
import { canonicalFor } from "../canonical-symbols.js";
|
|
28
29
|
import { arcusVenue } from "../venues/arcus.js";
|
|
29
30
|
import { ArcusClient } from "./client.js";
|
|
@@ -101,7 +102,7 @@ export function toArcusVenueMarket(m) {
|
|
|
101
102
|
maxLeverage: maxLeverageOf(m),
|
|
102
103
|
marginTiers: null,
|
|
103
104
|
session: sessionOf(m),
|
|
104
|
-
tradable: m
|
|
105
|
+
tradable: isArcusOnlineMarket(m),
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
/**
|
|
@@ -110,7 +111,7 @@ export function toArcusVenueMarket(m) {
|
|
|
110
111
|
* market / funding lists rather than listed at a $0 price and 0 % funding.
|
|
111
112
|
*/
|
|
112
113
|
export function onlineArcusMarkets(raw) {
|
|
113
|
-
return raw.filter(
|
|
114
|
+
return raw.filter(isArcusOnlineMarket);
|
|
114
115
|
}
|
|
115
116
|
async function fetchMarkets(net) {
|
|
116
117
|
const raw = await fetchArcusRawMarkets(new ArcusClient(net, null));
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* "everything else" rule, scheduleCancel — unverified):
|
|
15
15
|
* ed25519(timestampNs + action + canonicalJson(body))
|
|
16
16
|
*
|
|
17
|
-
* Time units
|
|
17
|
+
* Time units: `ct` and X-Timestamp are Unix NANOseconds;
|
|
18
18
|
* signed `g` is NANOseconds; the request body `goodTilTime` is a MICROsecond
|
|
19
19
|
* decimal string (g = goodTilTime × 1000).
|
|
20
20
|
*
|
|
@@ -78,8 +78,7 @@ export declare function placePayload(f: ArcusPlaceFields, untriggered: boolean):
|
|
|
78
78
|
* - authentication page: always `id`, plus `c` "only if the resting order was placed
|
|
79
79
|
* with a client id … the engine rejects a mismatch".
|
|
80
80
|
* Default follows the openapi spec ("idOnly": sign `id`, never `c`; the body sends
|
|
81
|
-
* orderId only). "idAndClientId" is the authentication-page variant. Settled by the
|
|
82
|
-
* docs/TEST-PLAN.md step "Arcus modify signature identity" on testnet.
|
|
81
|
+
* orderId only). "idAndClientId" is the authentication-page variant. Settled by the "Arcus modify signature identity" testnet check.
|
|
83
82
|
*/
|
|
84
83
|
export type ArcusModifyIdentity = "idOnly" | "idAndClientId";
|
|
85
84
|
export declare const ARCUS_MODIFY_SIGNED_IDENTITY: ArcusModifyIdentity;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* "everything else" rule, scheduleCancel — unverified):
|
|
15
15
|
* ed25519(timestampNs + action + canonicalJson(body))
|
|
16
16
|
*
|
|
17
|
-
* Time units
|
|
17
|
+
* Time units: `ct` and X-Timestamp are Unix NANOseconds;
|
|
18
18
|
* signed `g` is NANOseconds; the request body `goodTilTime` is a MICROsecond
|
|
19
19
|
* decimal string (g = goodTilTime × 1000).
|
|
20
20
|
*
|