perp-cli 0.12.17 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -27
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +303 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +70 -92
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +248 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +925 -245
- package/dist/commands/history.js +102 -54
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +357 -459
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +240 -6
- package/dist/exchanges/aster.js +1139 -195
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +152 -0
- package/dist/exchanges/ondo/adapter.js +811 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +95 -0
- package/dist/exchanges/ondo/funds.js +198 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +293 -4
- package/dist/exchanges/pacifica.js +1044 -90
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1386 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +29 -0
- package/dist/exchanges/venues/ondo.js +136 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +39 -0
- package/dist/exchanges/venues/qfex.js +140 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +821 -403
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/onchain-balances.d.ts +40 -0
- package/dist/onchain-balances.js +173 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +159 -55
- package/skills/perp-cli/references/agent-operations.md +112 -58
- package/skills/perp-cli/references/commands.md +269 -73
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +31 -15
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
package/dist/dashboard/ui.js
CHANGED
|
@@ -234,6 +234,7 @@ function connect() {
|
|
|
234
234
|
|
|
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
|
+
function usdOrDash(v, d=2) { return v==null ? '-' : '$'+fmt(v,d); }
|
|
237
238
|
function pnlClass(v) { return Number(v)>=0?'pnl-pos':'pnl-neg'; }
|
|
238
239
|
function pnlSign(v) { const n=Number(v)||0; return n>=0?'+$'+fmt(Math.abs(n)):'-$'+fmt(Math.abs(n)); }
|
|
239
240
|
function rateClass(v) { const n=Number(v)||0; return n>0?'rate-positive':n<0?'rate-negative':'rate-neutral'; }
|
|
@@ -310,7 +311,7 @@ function renderPanels(exchanges) {
|
|
|
310
311
|
const notional = Math.abs(Number(p.size)) * Number(p.markPrice);
|
|
311
312
|
const margin = p.leverage > 0 ? notional / p.leverage : notional;
|
|
312
313
|
const roe = margin > 0 ? (Number(p.unrealizedPnl) / margin * 100) : 0;
|
|
313
|
-
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>$\${fmt(p.entryPrice)}</td><td>$\${fmt(p.markPrice)}</td><td>\${p.liquidationPrice==='N/A'?'
|
|
314
|
+
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>$\${fmt(p.entryPrice)}</td><td>$\${fmt(p.markPrice)}</td><td>\${(p.liquidationPrice==null||p.liquidationPrice==='N/A')?'-':'$'+fmt(p.liquidationPrice)}</td><td style="color:var(--muted)">$\${fmt(notional)}</td><td class="\${pnlClass(p.unrealizedPnl)}">\${pnlSign(p.unrealizedPnl)}</td><td class="\${pnlClass(p.unrealizedPnl)}">\${roe>=0?'+':''}\${fmt(roe,1)}%</td><td>\${p.leverage}x</td></tr>\`;
|
|
314
315
|
}).join('')}</tbody></table>\`:'<div class="empty-msg">No open positions</div>'}
|
|
315
316
|
<div class="section-title">Open Orders (\${filteredOrders.length})</div>
|
|
316
317
|
\${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=>{
|
|
@@ -319,7 +320,7 @@ function renderPanels(exchanges) {
|
|
|
319
320
|
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>\`;
|
|
320
321
|
}).join('')}</tbody></table>\`:'<div class="empty-msg">No open orders</div>'}
|
|
321
322
|
<div class="section-title">Markets (Top 10)</div>
|
|
322
|
-
\${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>$\${fmt(m.markPrice)}</td><td
|
|
323
|
+
\${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>$\${fmt(m.markPrice)}</td><td>\${usdOrDash(m.indexPrice)}</td><td class="\${rateClass(fr)}">\${(fr*100).toFixed(4)}%</td><td>\${usdOrDash(m.volume24h,0)}</td><td>\${usdOrDash(m.openInterest,0)}</td><td>\${m.maxLeverage}x</td></tr>\`;}).join('')}</tbody></table>\`:'<div class="empty-msg">No market data</div>'}
|
|
323
324
|
</div>\`;
|
|
324
325
|
}).join('');
|
|
325
326
|
|
|
@@ -345,9 +346,10 @@ function renderPanels(exchanges) {
|
|
|
345
346
|
function renderArb() {
|
|
346
347
|
if (!arbData) { $('#arb-table').innerHTML='<div class="empty-msg">Loading arb data...</div>'; return; }
|
|
347
348
|
|
|
348
|
-
// Status
|
|
349
|
+
// Status (hover shows the venue's error message)
|
|
350
|
+
const escapeHtml = (v) => String(v).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'})[c]);
|
|
349
351
|
const statusHtml = Object.entries(arbData.exchangeStatus||{}).map(([ex,st])=>
|
|
350
|
-
\`<span class="exchange-status"><span class="dot-sm \${st==='ok'?'dot-ok':'dot-err'}"></span>\${ex}</span>\`
|
|
352
|
+
\`<span class="exchange-status" title="\${escapeHtml((arbData.exchangeErrors||{})[ex]||'')}"><span class="dot-sm \${st==='ok'?'dot-ok':'dot-err'}"></span>\${ex}</span>\`
|
|
351
353
|
).join('');
|
|
352
354
|
$('#arb-status').innerHTML = statusHtml;
|
|
353
355
|
|
|
@@ -223,7 +223,7 @@ class HyperliquidFeed extends ExchangeWsFeed {
|
|
|
223
223
|
size: String(Math.abs(szi)),
|
|
224
224
|
entryPrice: pos.entryPx ?? "0",
|
|
225
225
|
markPrice: String(pos.positionValue ? (Number(pos.positionValue) / Math.abs(szi)).toFixed(2) : "0"),
|
|
226
|
-
liquidationPrice: pos.liquidationPx ??
|
|
226
|
+
liquidationPrice: pos.liquidationPx ?? null, // HL sends null when there is no liquidation price
|
|
227
227
|
unrealizedPnl: pos.unrealizedPnl ?? "0",
|
|
228
228
|
leverage: Number(ap.leverage?.value ?? 1),
|
|
229
229
|
};
|
|
@@ -241,6 +241,7 @@ class HyperliquidFeed extends ExchangeWsFeed {
|
|
|
241
241
|
filled: "0",
|
|
242
242
|
status: "open",
|
|
243
243
|
type: String(o.orderType ?? "limit"),
|
|
244
|
+
triggerPrice: o.isTrigger === true && Number(o.triggerPx) > 0 ? String(o.triggerPx) : null,
|
|
244
245
|
}));
|
|
245
246
|
}
|
|
246
247
|
this.emitUpdate();
|
|
@@ -407,7 +408,7 @@ class PacificaFeed extends ExchangeWsFeed {
|
|
|
407
408
|
size: String(size),
|
|
408
409
|
entryPrice: String(entryPrice),
|
|
409
410
|
markPrice: mark ?? "0",
|
|
410
|
-
liquidationPrice: String(p.l
|
|
411
|
+
liquidationPrice: p.l != null ? String(p.l) : p.liquidation_price != null ? String(p.liquidation_price) : null,
|
|
411
412
|
unrealizedPnl: pnl.toFixed(4),
|
|
412
413
|
leverage: Number(p.leverage ?? 1),
|
|
413
414
|
};
|
|
@@ -428,6 +429,7 @@ class PacificaFeed extends ExchangeWsFeed {
|
|
|
428
429
|
filled: String(o.f ?? o.filled ?? "0"),
|
|
429
430
|
status: "open",
|
|
430
431
|
type: String(o.ot ?? o.order_type ?? ""),
|
|
432
|
+
triggerPrice: Number(o.sp ?? o.stop_price) > 0 ? String(o.sp ?? o.stop_price) : null,
|
|
431
433
|
}));
|
|
432
434
|
this.emitUpdate();
|
|
433
435
|
}
|
|
@@ -437,8 +439,8 @@ class PacificaFeed extends ExchangeWsFeed {
|
|
|
437
439
|
super.close();
|
|
438
440
|
}
|
|
439
441
|
}
|
|
440
|
-
// ── Lighter WS Feed ──
|
|
441
|
-
// WS:
|
|
442
|
+
// ── Lighter WS Feed (lighter, lighter-rh) ──
|
|
443
|
+
// WS: the adapter's venue+network config (venues/lighter-family.ts); null → REST
|
|
442
444
|
// Auth: signer.createAuthToken() passed as `auth` field in subscribe messages
|
|
443
445
|
// Channels: account_all_positions/{index} (auth), account_all_orders/{index} (auth),
|
|
444
446
|
// user_stats/{index} (public — balance info)
|
|
@@ -449,7 +451,16 @@ class LighterFeed extends ExchangeWsFeed {
|
|
|
449
451
|
const adapter = exchange.adapter;
|
|
450
452
|
this.accountIndex = Number(adapter._accountIndex ?? -1);
|
|
451
453
|
}
|
|
452
|
-
|
|
454
|
+
/** Venue+network WS URL from the adapter's network config; null = none published. */
|
|
455
|
+
get configuredWsUrl() {
|
|
456
|
+
return this.exchange.adapter.network.wsUrl;
|
|
457
|
+
}
|
|
458
|
+
get wsUrl() {
|
|
459
|
+
const url = this.configuredWsUrl;
|
|
460
|
+
if (url === null)
|
|
461
|
+
throw new Error(`${this.exchange.name}: no WebSocket URL for this network`);
|
|
462
|
+
return url;
|
|
463
|
+
}
|
|
453
464
|
async getAuthToken() {
|
|
454
465
|
try {
|
|
455
466
|
const adapter = this.exchange.adapter;
|
|
@@ -464,7 +475,7 @@ class LighterFeed extends ExchangeWsFeed {
|
|
|
464
475
|
}
|
|
465
476
|
}
|
|
466
477
|
async connect() {
|
|
467
|
-
if (this.accountIndex < 0) {
|
|
478
|
+
if (this.accountIndex < 0 || this.configuredWsUrl === null) {
|
|
468
479
|
this.startRestFallback();
|
|
469
480
|
return;
|
|
470
481
|
}
|
|
@@ -566,7 +577,7 @@ class LighterFeed extends ExchangeWsFeed {
|
|
|
566
577
|
markPrice: posSize !== 0
|
|
567
578
|
? String((Number(p.position_value ?? 0) / Math.abs(posSize)).toFixed(4))
|
|
568
579
|
: "0",
|
|
569
|
-
liquidationPrice: String(p.liquidation_price
|
|
580
|
+
liquidationPrice: p.liquidation_price ? String(p.liquidation_price) : null,
|
|
570
581
|
unrealizedPnl: String(p.unrealized_pnl ?? "0"),
|
|
571
582
|
leverage: Number(p.initial_margin_fraction ?? 0) > 0
|
|
572
583
|
? Math.round(10000 / Number(p.initial_margin_fraction))
|
|
@@ -592,6 +603,7 @@ class LighterFeed extends ExchangeWsFeed {
|
|
|
592
603
|
filled: String(o.filled_base_amount ?? "0"),
|
|
593
604
|
status: "open",
|
|
594
605
|
type: String(o.type ?? "limit"),
|
|
606
|
+
triggerPrice: Number(o.trigger_price) > 0 ? String(o.trigger_price) : null,
|
|
595
607
|
}));
|
|
596
608
|
this.emitUpdate();
|
|
597
609
|
}
|
|
@@ -613,7 +625,7 @@ export class WsFeedManager {
|
|
|
613
625
|
else if (ex.name === "pacifica") {
|
|
614
626
|
feed = new PacificaFeed(ex, handler);
|
|
615
627
|
}
|
|
616
|
-
else if (ex.name === "lighter") {
|
|
628
|
+
else if (ex.name === "lighter" || ex.name === "lighter-rh") {
|
|
617
629
|
feed = new LighterFeed(ex, handler);
|
|
618
630
|
}
|
|
619
631
|
else {
|
package/dist/dex-asset-map.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* SILVERJM ≠ SILVER
|
|
14
14
|
* URNM ≠ URANIUM
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
import { HYPERLIQUID_API_URL, sharedApiNetwork } from "./api/public/urls.js";
|
|
17
17
|
/** Known aliases: different symbol → same underlying */
|
|
18
18
|
const ALIASES = {
|
|
19
19
|
// Crude oil (WTI benchmark)
|
|
@@ -45,7 +45,8 @@ function isBlacklisted(a, b) {
|
|
|
45
45
|
return BLACKLIST_PAIRS.has(key1) || BLACKLIST_PAIRS.has(key2);
|
|
46
46
|
}
|
|
47
47
|
async function hlInfoPost(body) {
|
|
48
|
-
|
|
48
|
+
// Selected network (review L7): HYPERLIQUID_API_URL follows -n via setSharedApiNetwork (live binding).
|
|
49
|
+
const res = await fetch(HYPERLIQUID_API_URL, {
|
|
49
50
|
method: "POST",
|
|
50
51
|
headers: { "Content-Type": "application/json" },
|
|
51
52
|
body: JSON.stringify(body),
|
|
@@ -59,7 +60,8 @@ async function hlInfoPost(body) {
|
|
|
59
60
|
*/
|
|
60
61
|
export async function fetchAllDexAssets() {
|
|
61
62
|
const { withCache, TTL_MARKET } = await import("./cache.js");
|
|
62
|
-
|
|
63
|
+
// Network in the key: a mainnet snapshot must never answer a testnet run (L7).
|
|
64
|
+
return withCache(`pub:hl:${sharedApiNetwork()}:allDexAssets`, TTL_MARKET, () => _fetchAllDexAssetsLive());
|
|
63
65
|
}
|
|
64
66
|
async function _fetchAllDexAssetsLive() {
|
|
65
67
|
const allMetas = await hlInfoPost({ type: "allPerpMetas" });
|
package/dist/errors.d.ts
CHANGED
|
@@ -50,6 +50,11 @@ export declare const ERROR_CODES: {
|
|
|
50
50
|
readonly status: 409;
|
|
51
51
|
readonly retryable: false;
|
|
52
52
|
};
|
|
53
|
+
readonly DEPOSIT_REQUIRED: {
|
|
54
|
+
readonly code: "DEPOSIT_REQUIRED";
|
|
55
|
+
readonly status: 403;
|
|
56
|
+
readonly retryable: false;
|
|
57
|
+
};
|
|
53
58
|
readonly NO_SIGNER_AVAILABLE: {
|
|
54
59
|
readonly code: "NO_SIGNER_AVAILABLE";
|
|
55
60
|
readonly status: 401;
|
|
@@ -101,11 +106,73 @@ export declare const ERROR_CODES: {
|
|
|
101
106
|
readonly status: 401;
|
|
102
107
|
readonly retryable: false;
|
|
103
108
|
};
|
|
109
|
+
readonly INVALID_PASSPHRASE: {
|
|
110
|
+
readonly code: "INVALID_PASSPHRASE";
|
|
111
|
+
readonly status: 401;
|
|
112
|
+
readonly retryable: false;
|
|
113
|
+
};
|
|
104
114
|
readonly NOT_IMPLEMENTED: {
|
|
105
115
|
readonly code: "NOT_IMPLEMENTED";
|
|
106
116
|
readonly status: 501;
|
|
107
117
|
readonly retryable: false;
|
|
108
118
|
};
|
|
119
|
+
readonly UNKNOWN_VENUE: {
|
|
120
|
+
readonly code: "UNKNOWN_VENUE";
|
|
121
|
+
readonly status: 400;
|
|
122
|
+
readonly retryable: false;
|
|
123
|
+
};
|
|
124
|
+
readonly TIF_REQUIRED: {
|
|
125
|
+
readonly code: "TIF_REQUIRED";
|
|
126
|
+
readonly status: 400;
|
|
127
|
+
readonly retryable: false;
|
|
128
|
+
};
|
|
129
|
+
readonly INVALID_TICK: {
|
|
130
|
+
readonly code: "INVALID_TICK";
|
|
131
|
+
readonly status: 400;
|
|
132
|
+
readonly retryable: false;
|
|
133
|
+
};
|
|
134
|
+
readonly NO_ENV_TIER: {
|
|
135
|
+
readonly code: "NO_ENV_TIER";
|
|
136
|
+
readonly status: 401;
|
|
137
|
+
readonly retryable: false;
|
|
138
|
+
};
|
|
139
|
+
readonly NO_BRIDGE_ROUTE: {
|
|
140
|
+
readonly code: "NO_BRIDGE_ROUTE";
|
|
141
|
+
readonly status: 400;
|
|
142
|
+
readonly retryable: false;
|
|
143
|
+
};
|
|
144
|
+
readonly CAPABILITY_UNSUPPORTED: {
|
|
145
|
+
readonly code: "CAPABILITY_UNSUPPORTED";
|
|
146
|
+
readonly status: 400;
|
|
147
|
+
readonly retryable: false;
|
|
148
|
+
};
|
|
149
|
+
/** Part of a multi-leg placement was accepted and part failed; retrying would duplicate the accepted legs. */
|
|
150
|
+
readonly ORDER_PARTIAL: {
|
|
151
|
+
readonly code: "ORDER_PARTIAL";
|
|
152
|
+
readonly status: 500;
|
|
153
|
+
readonly retryable: false;
|
|
154
|
+
};
|
|
155
|
+
/** The order was submitted but its outcome is not confirmed; retrying could double-trade. */
|
|
156
|
+
readonly ORDER_PENDING: {
|
|
157
|
+
readonly code: "ORDER_PENDING";
|
|
158
|
+
readonly status: 202;
|
|
159
|
+
readonly retryable: false;
|
|
160
|
+
};
|
|
161
|
+
/** The order was definitely NOT placed: the venue rejected it, or it failed before it was sent (design §12.3). */
|
|
162
|
+
readonly ORDER_REJECTED: {
|
|
163
|
+
readonly code: "ORDER_REJECTED";
|
|
164
|
+
readonly status: 422;
|
|
165
|
+
readonly retryable: false;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* The venue explicitly refused a non-order account action (withdraw / transfer / approval) and
|
|
169
|
+
* executed nothing — distinct from a network / unknown outcome (EXCHANGE_ERROR, UNKNOWN).
|
|
170
|
+
*/
|
|
171
|
+
readonly ACTION_REJECTED: {
|
|
172
|
+
readonly code: "ACTION_REJECTED";
|
|
173
|
+
readonly status: 422;
|
|
174
|
+
readonly retryable: false;
|
|
175
|
+
};
|
|
109
176
|
readonly EXCHANGE_UNREACHABLE: {
|
|
110
177
|
readonly code: "EXCHANGE_UNREACHABLE";
|
|
111
178
|
readonly status: 503;
|
|
@@ -175,3 +242,58 @@ export declare class PerpError extends Error {
|
|
|
175
242
|
readonly structured: StructuredError;
|
|
176
243
|
constructor(code: ErrorCode, message: string, details?: Record<string, unknown>);
|
|
177
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Codes that mean "the order was definitely not placed / filled nothing": a validation
|
|
247
|
+
* or policy failure before sending (incl. PRICE_STALE: no price to build the order), or an
|
|
248
|
+
* explicit venue rejection. Every other code
|
|
249
|
+
* (EXCHANGE_UNREACHABLE, TIMEOUT, EXCHANGE_ERROR, RATE_LIMITED, UNKNOWN, …) raised by
|
|
250
|
+
* an order call leaves the outcome unknown.
|
|
251
|
+
*/
|
|
252
|
+
export declare const DEFINITE_ORDER_FAILURE_CODES: ReadonlySet<ErrorCode>;
|
|
253
|
+
/**
|
|
254
|
+
* true when `e` is a PerpError that says the order was definitely not placed: by its code, or —
|
|
255
|
+
* whatever the code — by `details.sent === false`, the thrower's explicit statement that it
|
|
256
|
+
* refused locally and sent nothing (QFEX: POSITION_NOT_FOUND from the pre-send position read).
|
|
257
|
+
* POSITION_NOT_FOUND is deliberately NOT in DEFINITE_ORDER_FAILURE_CODES: classifyError() also
|
|
258
|
+
* derives it from free message text ("position" + "not found"), and Aster / Lighter test such
|
|
259
|
+
* derived codes against the set — a text match is not proof that nothing was placed.
|
|
260
|
+
*/
|
|
261
|
+
export declare function isDefiniteOrderFailure(e: unknown): boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Failure BEFORE an order was sent (resolution, validation, pricing, nonce, signing):
|
|
264
|
+
* definitely not placed. A PerpError that already carries a definite code is kept.
|
|
265
|
+
*/
|
|
266
|
+
export declare function orderNotSent(venue: string, what: string, e: unknown): PerpError;
|
|
267
|
+
/**
|
|
268
|
+
* Failure AFTER an order was sent (transport error, timeout, 5xx, unreadable body):
|
|
269
|
+
* the outcome is unknown — ORDER_PENDING (non-retryable, never resubmit).
|
|
270
|
+
*/
|
|
271
|
+
export declare function orderOutcomeUnknown(venue: string, what: string, e: unknown, remediation?: string): PerpError;
|
|
272
|
+
/**
|
|
273
|
+
* A plain Error an adapter throws AFTER the order request reached the venue with an
|
|
274
|
+
* HTTP 5xx: Hyperliquid `HL exchange API error (5xx): …` (exchange POST, unparseable
|
|
275
|
+
* body) and Pacifica `POST <path> failed (5xx): …`. The outcome is unknown, never a
|
|
276
|
+
* rejection.
|
|
277
|
+
*/
|
|
278
|
+
export declare function isUnknownOutcomeServerError(e: unknown): boolean;
|
|
279
|
+
/** A thrown value that looks like a transport failure (fetch/network/timeout/unparseable body). */
|
|
280
|
+
export declare function isTransportFailure(e: unknown): boolean;
|
|
281
|
+
/**
|
|
282
|
+
* A fetch failure where no connection was ever established (DNS failure, connection refused,
|
|
283
|
+
* connect timeout — read from the error's `code` / `cause` chain): the request was not sent.
|
|
284
|
+
*/
|
|
285
|
+
export declare function isConnectFailure(e: unknown): boolean;
|
|
286
|
+
/**
|
|
287
|
+
* Failure of the request that SENDS an order / cancel / withdraw / transfer / leverage change.
|
|
288
|
+
* No connection was established (isConnectFailure) → not sent (definite). Anything else
|
|
289
|
+
* (connection reset, timeout, unreadable body, 5xx) → outcome unknown: ORDER_PENDING, never the
|
|
290
|
+
* retryable EXCHANGE_UNREACHABLE / TIMEOUT. A PerpError that is already definite is kept.
|
|
291
|
+
*/
|
|
292
|
+
export declare function sendFailure(venue: string, what: string, e: unknown, remediation?: string): PerpError;
|
|
293
|
+
/** Remediation for an unknown-outcome account action (not an order). */
|
|
294
|
+
export declare function unknownActionRemediation(venue: string, action: string): string;
|
|
295
|
+
/**
|
|
296
|
+
* Strict positive decimal amount ("10", "10.5"; no sign, exponent, or trailing text — "10abc"
|
|
297
|
+
* and "1e3" are refused). Returns the trimmed string.
|
|
298
|
+
*/
|
|
299
|
+
export declare function parsePositiveDecimal(raw: string, what: string): string;
|
package/dist/errors.js
CHANGED
|
@@ -11,6 +11,7 @@ export const ERROR_CODES = {
|
|
|
11
11
|
SIZE_TOO_LARGE: { code: "SIZE_TOO_LARGE", status: 400, retryable: false },
|
|
12
12
|
RISK_VIOLATION: { code: "RISK_VIOLATION", status: 403, retryable: false },
|
|
13
13
|
DUPLICATE_ORDER: { code: "DUPLICATE_ORDER", status: 409, retryable: false },
|
|
14
|
+
DEPOSIT_REQUIRED: { code: "DEPOSIT_REQUIRED", status: 403, retryable: false },
|
|
14
15
|
// Agent-wallet error codes (OWS / Phase 2a)
|
|
15
16
|
NO_SIGNER_AVAILABLE: { code: "NO_SIGNER_AVAILABLE", status: 401, retryable: false },
|
|
16
17
|
AGENT_NOT_REGISTERED: { code: "AGENT_NOT_REGISTERED", status: 401, retryable: false },
|
|
@@ -22,7 +23,26 @@ export const ERROR_CODES = {
|
|
|
22
23
|
APPROVE_FAILED: { code: "APPROVE_FAILED", status: 500, retryable: false },
|
|
23
24
|
LOCK_HELD: { code: "LOCK_HELD", status: 423, retryable: true, retryAfterMs: 5000 },
|
|
24
25
|
PASSPHRASE_REQUIRED: { code: "PASSPHRASE_REQUIRED", status: 401, retryable: false },
|
|
26
|
+
INVALID_PASSPHRASE: { code: "INVALID_PASSPHRASE", status: 401, retryable: false },
|
|
25
27
|
NOT_IMPLEMENTED: { code: "NOT_IMPLEMENTED", status: 501, retryable: false },
|
|
28
|
+
// Venue abstraction (docs/DESIGN-venue-abstraction.md §10)
|
|
29
|
+
UNKNOWN_VENUE: { code: "UNKNOWN_VENUE", status: 400, retryable: false },
|
|
30
|
+
TIF_REQUIRED: { code: "TIF_REQUIRED", status: 400, retryable: false },
|
|
31
|
+
INVALID_TICK: { code: "INVALID_TICK", status: 400, retryable: false },
|
|
32
|
+
NO_ENV_TIER: { code: "NO_ENV_TIER", status: 401, retryable: false },
|
|
33
|
+
NO_BRIDGE_ROUTE: { code: "NO_BRIDGE_ROUTE", status: 400, retryable: false },
|
|
34
|
+
CAPABILITY_UNSUPPORTED: { code: "CAPABILITY_UNSUPPORTED", status: 400, retryable: false },
|
|
35
|
+
/** Part of a multi-leg placement was accepted and part failed; retrying would duplicate the accepted legs. */
|
|
36
|
+
ORDER_PARTIAL: { code: "ORDER_PARTIAL", status: 500, retryable: false },
|
|
37
|
+
/** The order was submitted but its outcome is not confirmed; retrying could double-trade. */
|
|
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 (design §12.3). */
|
|
40
|
+
ORDER_REJECTED: { code: "ORDER_REJECTED", status: 422, retryable: false },
|
|
41
|
+
/**
|
|
42
|
+
* The venue explicitly refused a non-order account action (withdraw / transfer / approval) and
|
|
43
|
+
* executed nothing — distinct from a network / unknown outcome (EXCHANGE_ERROR, UNKNOWN).
|
|
44
|
+
*/
|
|
45
|
+
ACTION_REJECTED: { code: "ACTION_REJECTED", status: 422, retryable: false },
|
|
26
46
|
// 5xx - System / transient errors
|
|
27
47
|
EXCHANGE_UNREACHABLE: { code: "EXCHANGE_UNREACHABLE", status: 503, retryable: true },
|
|
28
48
|
RATE_LIMITED: { code: "RATE_LIMITED", status: 429, retryable: true, retryAfterMs: 1000 },
|
|
@@ -117,12 +137,55 @@ export function classifyError(err, exchange) {
|
|
|
117
137
|
if (lower.includes("passphrase required") || lower.includes("passphrase missing")) {
|
|
118
138
|
return { ...ERROR_CODES.PASSPHRASE_REQUIRED, message, exchange };
|
|
119
139
|
}
|
|
140
|
+
// OWS vault decrypt failure (NAPI binding throws "decryption failed: aead::Error"
|
|
141
|
+
// when the AEAD tag check fails — wrong passphrase or tampered ciphertext).
|
|
142
|
+
// Distinct from PASSPHRASE_REQUIRED (which means no passphrase was supplied):
|
|
143
|
+
// here a passphrase WAS supplied but did not unlock the vault.
|
|
144
|
+
if (lower.includes("aead::error") || lower.includes("decryption failed") || lower.includes("decryption error")) {
|
|
145
|
+
return {
|
|
146
|
+
...ERROR_CODES.INVALID_PASSPHRASE,
|
|
147
|
+
message,
|
|
148
|
+
exchange,
|
|
149
|
+
remediation: "Re-check OWS passphrase. Confirm with: perp --json wallet show --passphrase $PP. If wallet was rekeyed, restore from backup or re-import.",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
120
152
|
if (lower.includes("partial") && (lower.includes("approve") || lower.includes("approval"))) {
|
|
121
153
|
return { ...ERROR_CODES.APPROVE_PARTIAL, message, exchange };
|
|
122
154
|
}
|
|
123
155
|
if (lower.includes("approve_failed") || (lower.includes("approve failed") && lower.includes("clean"))) {
|
|
124
156
|
return { ...ERROR_CODES.APPROVE_FAILED, message, exchange };
|
|
125
157
|
}
|
|
158
|
+
// Aster V3 gates every authenticated endpoint behind a first main-wallet
|
|
159
|
+
// deposit as of 2026-09-01 (venue code -5050 DEPOSIT_REQUIRED, message
|
|
160
|
+
// "This function can only be used after deposit."). Without this branch the
|
|
161
|
+
// message falls through to a bare EXCHANGE_ERROR and the user never learns
|
|
162
|
+
// that a deposit — not a key/permission problem — is what blocks them. The
|
|
163
|
+
// agent-wallet and builder endpoints are exempt, so onboarding succeeds and
|
|
164
|
+
// the first read is what fails.
|
|
165
|
+
if (lower.includes("after deposit") || lower.includes("deposit_required") || lower.includes("-5050")) {
|
|
166
|
+
return {
|
|
167
|
+
...ERROR_CODES.DEPOSIT_REQUIRED,
|
|
168
|
+
message,
|
|
169
|
+
exchange,
|
|
170
|
+
remediation: "This venue requires a completed deposit to the main wallet before any authenticated endpoint works. " +
|
|
171
|
+
"Deposit with: perp funds deposit aster <amount> (USDT on BNB Chain; --chain arbitrum / --asset USDC also work), " +
|
|
172
|
+
"or from the exchange UI, then retry. Public market data and agent-wallet setup are unaffected.",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Aster -4061: the account is in Hedge Mode, where `positionSide` is
|
|
176
|
+
// mandatory and `reduceOnly` is forbidden. This adapter always sends one-way
|
|
177
|
+
// shape, so every order is rejected until the account switches back. Without
|
|
178
|
+
// this branch the user sees a bare "position side does not match" with no
|
|
179
|
+
// indication that the ACCOUNT MODE is the thing to change.
|
|
180
|
+
if (lower.includes("-4061") || lower.includes("position side does not match") || lower.includes("position_side_not_match")) {
|
|
181
|
+
return {
|
|
182
|
+
...ERROR_CODES.INVALID_PARAMS,
|
|
183
|
+
message,
|
|
184
|
+
exchange,
|
|
185
|
+
remediation: "The account appears to be in Hedge Mode, which this adapter does not support (it sends one-way orders). " +
|
|
186
|
+
"Switch to One-way Mode in the exchange UI, or via POST /fapi/v3/positionSide/dual with dualSidePosition=false, then retry.",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
126
189
|
if (lower.includes("not found") && (lower.includes("symbol") || lower.includes("market") || lower.includes("asset"))) {
|
|
127
190
|
return { ...ERROR_CODES.SYMBOL_NOT_FOUND, message, exchange };
|
|
128
191
|
}
|
|
@@ -172,3 +235,134 @@ export class PerpError extends Error {
|
|
|
172
235
|
};
|
|
173
236
|
}
|
|
174
237
|
}
|
|
238
|
+
// ── Order outcome classification (design §12.3) ──
|
|
239
|
+
/**
|
|
240
|
+
* Codes that mean "the order was definitely not placed / filled nothing": a validation
|
|
241
|
+
* or policy failure before sending (incl. PRICE_STALE: no price to build the order), or an
|
|
242
|
+
* explicit venue rejection. Every other code
|
|
243
|
+
* (EXCHANGE_UNREACHABLE, TIMEOUT, EXCHANGE_ERROR, RATE_LIMITED, UNKNOWN, …) raised by
|
|
244
|
+
* an order call leaves the outcome unknown.
|
|
245
|
+
*/
|
|
246
|
+
export const DEFINITE_ORDER_FAILURE_CODES = new Set([
|
|
247
|
+
"INVALID_PARAMS", "SYMBOL_NOT_FOUND", "INSUFFICIENT_BALANCE", "MARGIN_INSUFFICIENT",
|
|
248
|
+
"SIZE_TOO_SMALL", "SIZE_TOO_LARGE", "RISK_VIOLATION", "DEPOSIT_REQUIRED",
|
|
249
|
+
"NO_SIGNER_AVAILABLE", "AGENT_NOT_REGISTERED", "AGENT_EXPIRED", "POLICY_DENIED",
|
|
250
|
+
"KEY_NOT_FOUND", "WALLET_LOCKED", "PASSPHRASE_REQUIRED", "INVALID_PASSPHRASE",
|
|
251
|
+
"NOT_IMPLEMENTED", "UNKNOWN_VENUE", "TIF_REQUIRED", "INVALID_TICK",
|
|
252
|
+
"NO_ENV_TIER", "CAPABILITY_UNSUPPORTED", "SIGNATURE_FAILED", "ORDER_REJECTED", "ACTION_REJECTED",
|
|
253
|
+
// No usable price to build/check the order — raised only before an order is sent.
|
|
254
|
+
"PRICE_STALE",
|
|
255
|
+
]);
|
|
256
|
+
/**
|
|
257
|
+
* true when `e` is a PerpError that says the order was definitely not placed: by its code, or —
|
|
258
|
+
* whatever the code — by `details.sent === false`, the thrower's explicit statement that it
|
|
259
|
+
* refused locally and sent nothing (QFEX: POSITION_NOT_FOUND from the pre-send position read).
|
|
260
|
+
* POSITION_NOT_FOUND is deliberately NOT in DEFINITE_ORDER_FAILURE_CODES: classifyError() also
|
|
261
|
+
* derives it from free message text ("position" + "not found"), and Aster / Lighter test such
|
|
262
|
+
* derived codes against the set — a text match is not proof that nothing was placed.
|
|
263
|
+
*/
|
|
264
|
+
export function isDefiniteOrderFailure(e) {
|
|
265
|
+
if (!(e instanceof PerpError))
|
|
266
|
+
return false;
|
|
267
|
+
return DEFINITE_ORDER_FAILURE_CODES.has(e.structured.code) || e.structured.details?.sent === false;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Failure BEFORE an order was sent (resolution, validation, pricing, nonce, signing):
|
|
271
|
+
* definitely not placed. A PerpError that already carries a definite code is kept.
|
|
272
|
+
*/
|
|
273
|
+
export function orderNotSent(venue, what, e) {
|
|
274
|
+
// By CODE only: a not-sent error with a retryable code (TIMEOUT / EXCHANGE_UNREACHABLE with
|
|
275
|
+
// details.sent === false) is still re-typed, so an order call never surfaces a retryable code.
|
|
276
|
+
if (e instanceof PerpError && DEFINITE_ORDER_FAILURE_CODES.has(e.structured.code))
|
|
277
|
+
return e;
|
|
278
|
+
return new PerpError("ORDER_REJECTED", `${venue} ${what} not sent: ${extractErrorMessage(e)}`, { exchange: venue });
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Failure AFTER an order was sent (transport error, timeout, 5xx, unreadable body):
|
|
282
|
+
* the outcome is unknown — ORDER_PENDING (non-retryable, never resubmit).
|
|
283
|
+
*/
|
|
284
|
+
export function orderOutcomeUnknown(venue, what, e, remediation) {
|
|
285
|
+
if (e instanceof PerpError && e.structured.code === "ORDER_PENDING")
|
|
286
|
+
return e;
|
|
287
|
+
return new PerpError("ORDER_PENDING", `${venue} ${what}: outcome unknown — ${extractErrorMessage(e)}`, {
|
|
288
|
+
exchange: venue,
|
|
289
|
+
remediation: remediation ?? `Do NOT resubmit — the order may have been placed. Check open orders, balances and positions on ${venue}`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* A plain Error an adapter throws AFTER the order request reached the venue with an
|
|
294
|
+
* HTTP 5xx: Hyperliquid `HL exchange API error (5xx): …` (exchange POST, unparseable
|
|
295
|
+
* body) and Pacifica `POST <path> failed (5xx): …`. The outcome is unknown, never a
|
|
296
|
+
* rejection.
|
|
297
|
+
*/
|
|
298
|
+
export function isUnknownOutcomeServerError(e) {
|
|
299
|
+
if (e instanceof PerpError)
|
|
300
|
+
return false;
|
|
301
|
+
return /^HL exchange API error \(5\d\d\)|^POST \S+ failed \(5\d\d\)/.test(extractErrorMessage(e));
|
|
302
|
+
}
|
|
303
|
+
/** A thrown value that looks like a transport failure (fetch/network/timeout/unparseable body). */
|
|
304
|
+
export function isTransportFailure(e) {
|
|
305
|
+
if (e instanceof PerpError)
|
|
306
|
+
return false;
|
|
307
|
+
if (e instanceof TypeError || e instanceof SyntaxError)
|
|
308
|
+
return true;
|
|
309
|
+
const name = e instanceof Error ? e.name : "";
|
|
310
|
+
if (name === "AbortError" || name === "TimeoutError")
|
|
311
|
+
return true;
|
|
312
|
+
// "network" alone is NOT a transport signal: definite refusals such as "wrong network" /
|
|
313
|
+
// "unsupported network" mention it too. Only transport phrasings ("network error",
|
|
314
|
+
// "NetworkError when attempting to fetch", "network request failed", …) count.
|
|
315
|
+
return /fetch failed|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|socket hang up|\bnetwork ?(error|request failed|failure|unreachable|is unreachable|connection lost)\b|timed? ?out/i.test(extractErrorMessage(e));
|
|
316
|
+
}
|
|
317
|
+
const CONNECT_FAILURE_CODES = new Set(["ENOTFOUND", "EAI_AGAIN", "ECONNREFUSED", "UND_ERR_CONNECT_TIMEOUT", "ENETUNREACH", "EHOSTUNREACH"]);
|
|
318
|
+
/**
|
|
319
|
+
* A fetch failure where no connection was ever established (DNS failure, connection refused,
|
|
320
|
+
* connect timeout — read from the error's `code` / `cause` chain): the request was not sent.
|
|
321
|
+
*/
|
|
322
|
+
export function isConnectFailure(e) {
|
|
323
|
+
const seen = new Set();
|
|
324
|
+
const walk = (x) => {
|
|
325
|
+
if (x === null || typeof x !== "object" || seen.has(x))
|
|
326
|
+
return false;
|
|
327
|
+
seen.add(x);
|
|
328
|
+
const o = x;
|
|
329
|
+
if (typeof o.code === "string" && CONNECT_FAILURE_CODES.has(o.code))
|
|
330
|
+
return true;
|
|
331
|
+
if (Array.isArray(o.errors) && o.errors.length > 0 && o.errors.every(walk))
|
|
332
|
+
return true;
|
|
333
|
+
return walk(o.cause);
|
|
334
|
+
};
|
|
335
|
+
return walk(e);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Failure of the request that SENDS an order / cancel / withdraw / transfer / leverage change.
|
|
339
|
+
* No connection was established (isConnectFailure) → not sent (definite). Anything else
|
|
340
|
+
* (connection reset, timeout, unreadable body, 5xx) → outcome unknown: ORDER_PENDING, never the
|
|
341
|
+
* retryable EXCHANGE_UNREACHABLE / TIMEOUT. A PerpError that is already definite is kept.
|
|
342
|
+
*/
|
|
343
|
+
export function sendFailure(venue, what, e, remediation) {
|
|
344
|
+
if (e instanceof PerpError && DEFINITE_ORDER_FAILURE_CODES.has(e.structured.code))
|
|
345
|
+
return e;
|
|
346
|
+
if (isDefiniteOrderFailure(e))
|
|
347
|
+
return orderNotSent(venue, what, e);
|
|
348
|
+
if (isConnectFailure(e))
|
|
349
|
+
return orderNotSent(venue, what, e);
|
|
350
|
+
return orderOutcomeUnknown(venue, what, e, remediation);
|
|
351
|
+
}
|
|
352
|
+
/** Remediation for an unknown-outcome account action (not an order). */
|
|
353
|
+
export function unknownActionRemediation(venue, action) {
|
|
354
|
+
return `Do NOT resubmit — the ${action} may have been executed. Check balances, positions and open orders on ${venue} first`;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Strict positive decimal amount ("10", "10.5"; no sign, exponent, or trailing text — "10abc"
|
|
358
|
+
* and "1e3" are refused). Returns the trimmed string.
|
|
359
|
+
*/
|
|
360
|
+
export function parsePositiveDecimal(raw, what) {
|
|
361
|
+
const s = String(raw).trim();
|
|
362
|
+
if (!/^(\d+(\.\d+)?|\.\d+)$/.test(s) || !(Number(s) > 0)) {
|
|
363
|
+
throw new PerpError("INVALID_PARAMS", `${what} must be a positive decimal number (got "${raw}")`, {
|
|
364
|
+
remediation: "Pass a plain decimal amount, e.g. 10 or 10.5",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return s;
|
|
368
|
+
}
|