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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Decimal-string precision helpers for order entry
|
|
2
|
+
* Decimal-string precision helpers for order entry.
|
|
3
3
|
*
|
|
4
4
|
* All math is done on scaled BigInt to avoid float drift. Rules:
|
|
5
5
|
* - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
|
|
@@ -24,6 +24,8 @@ export declare function requirePositive(value: string, what: string): void;
|
|
|
24
24
|
export declare function requireOnTick(price: string, tickSize: string): string;
|
|
25
25
|
/** Exact (a + b) / 2 of two plain decimal strings (no float arithmetic: 0.1 and 0.2 → "0.15"). */
|
|
26
26
|
export declare function midDecimal(a: string, b: string): string;
|
|
27
|
+
/** Exact decimal a + b (plain decimal strings). */
|
|
28
|
+
export declare function addDecimal(a: string, b: string): string;
|
|
27
29
|
/** Exact decimal a − b (plain decimal strings). */
|
|
28
30
|
export declare function subDecimal(a: string, b: string): string;
|
|
29
31
|
/** Exact decimal a × b (plain decimal strings). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Decimal-string precision helpers for order entry
|
|
2
|
+
* Decimal-string precision helpers for order entry.
|
|
3
3
|
*
|
|
4
4
|
* All math is done on scaled BigInt to avoid float drift. Rules:
|
|
5
5
|
* - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
|
|
@@ -119,6 +119,13 @@ export function midDecimal(a, b) {
|
|
|
119
119
|
const scale = Math.max(x.scale, y.scale) + 1; // one more digit holds the possible ".5"
|
|
120
120
|
return format((rescale(x, scale) + rescale(y, scale)) / 2n, scale);
|
|
121
121
|
}
|
|
122
|
+
/** Exact decimal a + b (plain decimal strings). */
|
|
123
|
+
export function addDecimal(a, b) {
|
|
124
|
+
const x = parseDecimal(a);
|
|
125
|
+
const y = parseDecimal(b);
|
|
126
|
+
const scale = Math.max(x.scale, y.scale);
|
|
127
|
+
return format(rescale(x, scale) + rescale(y, scale), scale);
|
|
128
|
+
}
|
|
122
129
|
/** Exact decimal a − b (plain decimal strings). */
|
|
123
130
|
export function subDecimal(a, b) {
|
|
124
131
|
const x = parseDecimal(a);
|
|
@@ -195,7 +202,7 @@ export async function lotStepOf(adapter, symbol) {
|
|
|
195
202
|
m = bare[0];
|
|
196
203
|
}
|
|
197
204
|
if (m === undefined) {
|
|
198
|
-
throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name });
|
|
205
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name, remediation: `List markets: perp -e ${adapter.name} market list` });
|
|
199
206
|
}
|
|
200
207
|
if (m.stepSize !== null)
|
|
201
208
|
return m.stepSize;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Network, type PublicBalanceResult, type PublicPositionsOptions } from "./venue.js";
|
|
2
|
+
import type { LighterVenueId } from "./venues/lighter-family.js";
|
|
3
|
+
/**
|
|
4
|
+
* Hyperliquid: info `clearinghouseState` (native perp dex), `userAbstraction` (the mode decides
|
|
5
|
+
* the equity source, exactly as the keyed getBalance) and `spotClearinghouseState` (spot rows;
|
|
6
|
+
* in unified / portfolio mode also the collateral), plus `meta.collateralToken` when the mode needs
|
|
7
|
+
* it. HIP-3: `clearinghouseState {dex}` for every dex of `perpDexs` (paced: hlDexFanOut); a dex
|
|
8
|
+
* balance is that dex's own perp clearinghouse, listed when its account value is non-zero. A dex
|
|
9
|
+
* (or the dex list) that fails is an `errors` entry ("hip3:<dex>") — unknown, never zero; the
|
|
10
|
+
* native, mode or spot read failing fails the venue.
|
|
11
|
+
*/
|
|
12
|
+
export declare function hyperliquidBalanceByAddress(net: Network, address: string): Promise<PublicBalanceResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address; each
|
|
15
|
+
* account's balance (lighterBalanceOf) and spot assets (lighterSpotBalanceRows) are one
|
|
16
|
+
* `accounts` row. `accountIndex` narrows to one account (not one of the address's →
|
|
17
|
+
* INVALID_PARAMS); no account → empty `accounts` + note.
|
|
18
|
+
*/
|
|
19
|
+
export declare function lighterBalanceByAddress(venue: LighterVenueId, net: Network, address: string, opts: PublicPositionsOptions): Promise<PublicBalanceResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Pacifica: `GET /account?account=` (balance + spot_balances), `/positions?account=` and
|
|
22
|
+
* `/info/prices` — pacificaBalanceOf with the keyed extras (usdcBalance, borrowedUsdc,
|
|
23
|
+
* pendingInterest, availableToWithdraw). The address is validated as base58 32 bytes by the caller.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pacificaBalanceByAddress(net: Network, address: string): Promise<PublicBalanceResult>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Balances of ANY account by its public address, keyless (`perp account balance --address`, MCP
|
|
3
|
+
* get_balance_by_address). Wired into the registry through VenuePublicData.fetchBalanceByAddress;
|
|
4
|
+
* the balance comes from the same functions as the keyed adapters' getBalance (hlBalanceOf /
|
|
5
|
+
* lighterBalanceOf / pacificaBalanceOf) and the spot rows from the keyed spot mappers
|
|
6
|
+
* (hlSpotBalanceRows / lighterSpotBalanceRows / pacificaSpotBalanceRows). Read-only GETs / info
|
|
7
|
+
* POSTs; nothing is signed, no key is loaded.
|
|
8
|
+
*/
|
|
9
|
+
import { classifyError, PerpError } from "../errors.js";
|
|
10
|
+
import { getVenue, networkConfig } from "./venue.js";
|
|
11
|
+
import { chooseLighterAccounts, hlDexFanOut, hlDexNamesOf, lighterAccountIndexOf } from "./public-positions.js";
|
|
12
|
+
function errorOf(e, venue) {
|
|
13
|
+
const s = classifyError(e, venue);
|
|
14
|
+
return { code: s.code, message: s.message };
|
|
15
|
+
}
|
|
16
|
+
/** A venue read failure as a typed error (the public HTTP helpers throw plain Errors on HTTP status). */
|
|
17
|
+
async function typed(venue, read) {
|
|
18
|
+
try {
|
|
19
|
+
return await read();
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
if (e instanceof PerpError)
|
|
23
|
+
throw e;
|
|
24
|
+
const s = classifyError(e, venue);
|
|
25
|
+
throw new PerpError(s.code === "UNKNOWN" ? "EXCHANGE_ERROR" : s.code, s.message, { exchange: venue });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const EMPTY = { balance: null, spot: null, accounts: null, dexes: null, accountMode: null };
|
|
29
|
+
/** spotClearinghouseState `balances`, every row with a coin and numeric total / hold (unreadable → EXCHANGE_ERROR, never "0"). */
|
|
30
|
+
function hlSpotBalancesOf(state) {
|
|
31
|
+
const balances = state?.balances;
|
|
32
|
+
if (!Array.isArray(balances))
|
|
33
|
+
throw new PerpError("EXCHANGE_ERROR", "hyperliquid spotClearinghouseState: no balances array", { exchange: "hyperliquid" });
|
|
34
|
+
for (const b of balances) {
|
|
35
|
+
const num = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v));
|
|
36
|
+
if (typeof b?.coin !== "string" || !num(b.total) || !num(b.hold)) {
|
|
37
|
+
throw new PerpError("EXCHANGE_ERROR", `hyperliquid spotClearinghouseState: unreadable balance row ${JSON.stringify(b).slice(0, 160)}`, { exchange: "hyperliquid" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return balances;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Hyperliquid: info `clearinghouseState` (native perp dex), `userAbstraction` (the mode decides
|
|
44
|
+
* the equity source, exactly as the keyed getBalance) and `spotClearinghouseState` (spot rows;
|
|
45
|
+
* in unified / portfolio mode also the collateral), plus `meta.collateralToken` when the mode needs
|
|
46
|
+
* it. HIP-3: `clearinghouseState {dex}` for every dex of `perpDexs` (paced: hlDexFanOut); a dex
|
|
47
|
+
* balance is that dex's own perp clearinghouse, listed when its account value is non-zero. A dex
|
|
48
|
+
* (or the dex list) that fails is an `errors` entry ("hip3:<dex>") — unknown, never zero; the
|
|
49
|
+
* native, mode or spot read failing fails the venue.
|
|
50
|
+
*/
|
|
51
|
+
export async function hyperliquidBalanceByAddress(net, address) {
|
|
52
|
+
const { legacyPublicJson } = await import("../funding/legacy-public.js");
|
|
53
|
+
const { legacyPublicBaseUrl } = await import("../api/public/urls.js");
|
|
54
|
+
const { hlAbstractionModeOf, hlBalanceOf, hlSpotUsdPricesOf } = await import("./hyperliquid.js");
|
|
55
|
+
const { hlSpotBalanceRows } = await import("./hyperliquid-spot.js");
|
|
56
|
+
const url = `${legacyPublicBaseUrl("hyperliquid", net)}/info`;
|
|
57
|
+
const post = (body, label) => legacyPublicJson("hyperliquid", url, label, {
|
|
58
|
+
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
|
|
59
|
+
});
|
|
60
|
+
const errors = {};
|
|
61
|
+
const [native, modeRaw, spotState, dexList] = await Promise.all([
|
|
62
|
+
typed("hyperliquid", () => post({ type: "clearinghouseState", user: address }, "info clearinghouseState")),
|
|
63
|
+
typed("hyperliquid", () => post({ type: "userAbstraction", user: address }, "info userAbstraction")),
|
|
64
|
+
typed("hyperliquid", () => post({ type: "spotClearinghouseState", user: address }, "info spotClearinghouseState")),
|
|
65
|
+
post({ type: "perpDexs" }, "info perpDexs").then(hlDexNamesOf, (e) => { errors.hip3 = errorOf(e, "hyperliquid"); return []; }),
|
|
66
|
+
]);
|
|
67
|
+
const mode = hlAbstractionModeOf(modeRaw);
|
|
68
|
+
const balances = hlSpotBalancesOf(spotState);
|
|
69
|
+
let token = null;
|
|
70
|
+
const collateralToken = () => (token ??= typed("hyperliquid", () => post({ type: "meta" }, "info meta")).then((meta) => {
|
|
71
|
+
const idx = meta?.collateralToken;
|
|
72
|
+
if (typeof idx !== "number" || !Number.isInteger(idx) || idx < 0) {
|
|
73
|
+
throw new PerpError("EXCHANGE_ERROR", `Hyperliquid meta has no collateralToken (got ${JSON.stringify(idx)})`, { exchange: "hyperliquid" });
|
|
74
|
+
}
|
|
75
|
+
return idx;
|
|
76
|
+
}));
|
|
77
|
+
const nativeState = native;
|
|
78
|
+
const { balance, notes } = await hlBalanceOf(nativeState, mode, "", {
|
|
79
|
+
collateralToken,
|
|
80
|
+
spotCollateralRow: async () => {
|
|
81
|
+
const t = await collateralToken();
|
|
82
|
+
return { row: balances.find(b => Number(b.token) === t) ?? null, balances, token: t, portfolioMarginRatio: spotState.portfolioMarginRatio };
|
|
83
|
+
},
|
|
84
|
+
spotUsdPrices: async () => hlSpotUsdPricesOf(await typed("hyperliquid", () => post({ type: "spotMetaAndAssetCtxs" }, "info spotMetaAndAssetCtxs"))),
|
|
85
|
+
});
|
|
86
|
+
const unified = mode === "unified" || mode === "portfolio";
|
|
87
|
+
const out = notes.map(n => n.trim());
|
|
88
|
+
if (unified) {
|
|
89
|
+
const av = nativeState.marginSummary?.accountValue;
|
|
90
|
+
const wd = nativeState.withdrawable;
|
|
91
|
+
const perp = [typeof av === "string" ? `accountValue ${av}` : null, typeof wd === "string" ? `withdrawable ${wd}` : null].filter(x => x !== null).join(", ");
|
|
92
|
+
out.push(`account mode ${mode}: equity / available come from the spot clearinghouse (the collateral balance already moves with perp PnL), as in the keyed balance; the native perp clearinghouse${perp !== "" ? ` (${perp})` : ""} is a per-dex view and is not added`);
|
|
93
|
+
}
|
|
94
|
+
// HIP-3 dex balances: the dex's own perp clearinghouse (standard semantics — the mode-dependent
|
|
95
|
+
// spot collateral is the top-level balance, never repeated per dex).
|
|
96
|
+
const dexes = {};
|
|
97
|
+
const noSpotRead = () => { throw new PerpError("EXCHANGE_ERROR", "internal: a HIP-3 dex balance read asked for spot state", { exchange: "hyperliquid" }); };
|
|
98
|
+
const perDex = await hlDexFanOut(net, dexList, dex => post({ type: "clearinghouseState", user: address, dex }, `info clearinghouseState dex ${dex}`));
|
|
99
|
+
for (const [i, res] of perDex.entries()) {
|
|
100
|
+
const dex = dexList[i];
|
|
101
|
+
if (res.status === "rejected") {
|
|
102
|
+
errors[`hip3:${dex}`] = errorOf(res.reason, "hyperliquid");
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const b = (await hlBalanceOf(res.value, "standard", dex, { collateralToken: noSpotRead, spotCollateralRow: noSpotRead, spotUsdPrices: noSpotRead })).balance;
|
|
107
|
+
if (Number(b.equity) !== 0)
|
|
108
|
+
dexes[dex] = b;
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
errors[`hip3:${dex}`] = errorOf(e, "hyperliquid");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { balance, spot: hlSpotBalanceRows({ balances }, mode), accounts: null, dexes, accountMode: mode, errors, notes: out };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address; each
|
|
118
|
+
* account's balance (lighterBalanceOf) and spot assets (lighterSpotBalanceRows) are one
|
|
119
|
+
* `accounts` row. `accountIndex` narrows to one account (not one of the address's →
|
|
120
|
+
* INVALID_PARAMS); no account → empty `accounts` + note.
|
|
121
|
+
*/
|
|
122
|
+
export async function lighterBalanceByAddress(venue, net, address, opts) {
|
|
123
|
+
const { fetchLighterAccountsByL1 } = await import("./lighter-public.js");
|
|
124
|
+
const { lighterBalanceOf } = await import("./lighter.js");
|
|
125
|
+
const { lighterSpotBalanceRows } = await import("./lighter-spot.js");
|
|
126
|
+
const collateral = networkConfig(getVenue(venue), net).collateral[0];
|
|
127
|
+
if (collateral === undefined)
|
|
128
|
+
throw new PerpError("INVALID_PARAMS", `${venue} network config has no collateral`);
|
|
129
|
+
const accounts = await fetchLighterAccountsByL1(venue, net, address);
|
|
130
|
+
if (accounts === null || accounts.length === 0) {
|
|
131
|
+
return { ...EMPTY, accounts: [], errors: {}, notes: [`no ${venue === "lighter" ? "Lighter" : "Lighter RH"} account for this address`] };
|
|
132
|
+
}
|
|
133
|
+
const rows = chooseLighterAccounts(venue, accounts, opts.accountIndex).map(a => ({
|
|
134
|
+
accountIndex: lighterAccountIndexOf(a),
|
|
135
|
+
balance: lighterBalanceOf(a, venue, collateral),
|
|
136
|
+
spot: lighterSpotBalanceRows(a, venue, collateral),
|
|
137
|
+
}));
|
|
138
|
+
return { ...EMPTY, accounts: rows, errors: {}, notes: [] };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Pacifica: `GET /account?account=` (balance + spot_balances), `/positions?account=` and
|
|
142
|
+
* `/info/prices` — pacificaBalanceOf with the keyed extras (usdcBalance, borrowedUsdc,
|
|
143
|
+
* pendingInterest, availableToWithdraw). The address is validated as base58 32 bytes by the caller.
|
|
144
|
+
*/
|
|
145
|
+
export async function pacificaBalanceByAddress(net, address) {
|
|
146
|
+
const { PacificaClient } = await import("../pacifica/client.js");
|
|
147
|
+
const { pacificaBalanceOf } = await import("./pacifica.js");
|
|
148
|
+
const { pacificaSpotBalanceRows } = await import("./pacifica-spot.js");
|
|
149
|
+
const client = new PacificaClient({ network: net });
|
|
150
|
+
const [info, positions, prices] = await typed("pacifica", () => Promise.all([client.getAccount(address), client.getPositions(address), client.getPrices()]));
|
|
151
|
+
return {
|
|
152
|
+
...EMPTY,
|
|
153
|
+
balance: pacificaBalanceOf(info, positions, prices),
|
|
154
|
+
spot: pacificaSpotBalanceRows(info),
|
|
155
|
+
errors: {},
|
|
156
|
+
notes: [],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Network, type PublicPositionsOptions, type PublicPositionsResult } from "./venue.js";
|
|
2
|
+
import type { LighterVenueId } from "./venues/lighter-family.js";
|
|
3
|
+
/** Requests of a HIP-3 per-dex fan-out in flight at once. */
|
|
4
|
+
export declare const HL_DEX_FANOUT_CONCURRENCY = 4;
|
|
5
|
+
/** Burst of the fan-out's token bucket, in HL weight units (clearinghouseState weighs 2 → 10 requests). */
|
|
6
|
+
export declare const HL_DEX_FANOUT_BURST = 20;
|
|
7
|
+
/**
|
|
8
|
+
* One `read` per HIP-3 dex, paced: at most HL_DEX_FANOUT_CONCURRENCY in flight, and every request
|
|
9
|
+
* takes its weight from a token bucket refilled at the venue's documented rate
|
|
10
|
+
* (hyperliquidVenue.rateLimit: 1200 weight / min = 20 / s; clearinghouseState weight 2) with a
|
|
11
|
+
* small burst (HL_DEX_FANOUT_BURST) instead of the full minute's budget. HL mainnet lists ~10
|
|
12
|
+
* dexes (no wait); testnet ~272, where an unpaced burst got HTTP 429 on 62 of 272 reads at
|
|
13
|
+
* concurrency 4 — paced, the fan-out takes ~25 s instead. A failed read stays that dex's
|
|
14
|
+
* (settled per dex); nothing is retried.
|
|
15
|
+
*/
|
|
16
|
+
export declare function hlDexFanOut<T>(net: Network, dexes: readonly string[], read: (dex: string) => Promise<T>): Promise<PromiseSettledResult<T>[]>;
|
|
17
|
+
/** Hyperliquid `perpDexs` → HIP-3 dex names (the first entry, null, is the native dex). */
|
|
18
|
+
export declare function hlDexNamesOf(j: unknown): string[];
|
|
19
|
+
/** Account index of a Lighter account object. */
|
|
20
|
+
export declare function lighterAccountIndexOf(a: Record<string, unknown>): number;
|
|
21
|
+
/** The accounts of an address a read covers: every one, or the one of `accountIndex` (not one of the address's → INVALID_PARAMS). */
|
|
22
|
+
export declare function chooseLighterAccounts(venue: LighterVenueId, accounts: Array<Record<string, unknown>>, accountIndex: number | undefined): Array<Record<string, unknown>>;
|
|
23
|
+
/**
|
|
24
|
+
* Hyperliquid: info `clearinghouseState` {user} for the native perp dex plus {user, dex} for every
|
|
25
|
+
* HIP-3 dex of `perpDexs` (one request each, paced: hlDexFanOut). Native rows carry `dex: null`, HIP-3 rows their dex
|
|
26
|
+
* and the prefixed coin ("xyz:GOLD"), as the HIP-3 market rows. A dex (or the dex list) that
|
|
27
|
+
* fails is an `errors` entry ("hip3:<dex>") — its rows are unknown, never silently dropped; the
|
|
28
|
+
* native read failing fails the venue.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hyperliquidPositionsByAddress(net: Network, address: string): Promise<PublicPositionsResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address
|
|
33
|
+
* (sub-accounts); each account's rows are tagged with its `accountIndex`. The venue keeps a
|
|
34
|
+
* zero-size row for every market an account ever touched: those are dropped by the mapper.
|
|
35
|
+
* `accountIndex` narrows to one account (not one of the address's → INVALID_PARAMS).
|
|
36
|
+
*/
|
|
37
|
+
export declare function lighterPositionsByAddress(venue: LighterVenueId, net: Network, address: string, opts: PublicPositionsOptions): Promise<PublicPositionsResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Pacifica: `GET /positions?account=` marked with `/info/prices`; unresolved leverage from the
|
|
40
|
+
* account's public `/account/settings`, then the market max (`/info`). The address is validated
|
|
41
|
+
* as base58 32 bytes by the caller (an EVM address would be answered `success: true, []`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function pacificaPositionsByAddress(net: Network, address: string): Promise<PublicPositionsResult>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open positions of ANY account by its public address, keyless (`perp account positions
|
|
3
|
+
* --address`, MCP get_positions_by_address). Wired into the registry through
|
|
4
|
+
* VenuePublicData.fetchPositionsByAddress (+ addressKind); the rows come from the same mappers as
|
|
5
|
+
* the keyed adapters' getPositions (hlPositionRows / lighterPositionRows / pacificaPositionRows),
|
|
6
|
+
* with plain symbols. Read-only GETs / info POSTs; nothing is signed, no key is loaded.
|
|
7
|
+
*/
|
|
8
|
+
import { classifyError, PerpError } from "../errors.js";
|
|
9
|
+
import { getVenue } from "./venue.js";
|
|
10
|
+
import { acquireRate } from "./rate-limiter.js";
|
|
11
|
+
function errorOf(e, venue) {
|
|
12
|
+
const s = classifyError(e, venue);
|
|
13
|
+
return { code: s.code, message: s.message };
|
|
14
|
+
}
|
|
15
|
+
/** A venue read failure as a typed error (the public HTTP helpers throw plain Errors on HTTP status). */
|
|
16
|
+
async function typed(venue, read) {
|
|
17
|
+
try {
|
|
18
|
+
return await read();
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
if (e instanceof PerpError)
|
|
22
|
+
throw e;
|
|
23
|
+
const s = classifyError(e, venue);
|
|
24
|
+
throw new PerpError(s.code === "UNKNOWN" ? "EXCHANGE_ERROR" : s.code, s.message, { exchange: venue });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Requests of a HIP-3 per-dex fan-out in flight at once. */
|
|
28
|
+
export const HL_DEX_FANOUT_CONCURRENCY = 4;
|
|
29
|
+
/** Burst of the fan-out's token bucket, in HL weight units (clearinghouseState weighs 2 → 10 requests). */
|
|
30
|
+
export const HL_DEX_FANOUT_BURST = 20;
|
|
31
|
+
/**
|
|
32
|
+
* One `read` per HIP-3 dex, paced: at most HL_DEX_FANOUT_CONCURRENCY in flight, and every request
|
|
33
|
+
* takes its weight from a token bucket refilled at the venue's documented rate
|
|
34
|
+
* (hyperliquidVenue.rateLimit: 1200 weight / min = 20 / s; clearinghouseState weight 2) with a
|
|
35
|
+
* small burst (HL_DEX_FANOUT_BURST) instead of the full minute's budget. HL mainnet lists ~10
|
|
36
|
+
* dexes (no wait); testnet ~272, where an unpaced burst got HTTP 429 on 62 of 272 reads at
|
|
37
|
+
* concurrency 4 — paced, the fan-out takes ~25 s instead. A failed read stays that dex's
|
|
38
|
+
* (settled per dex); nothing is retried.
|
|
39
|
+
*/
|
|
40
|
+
export async function hlDexFanOut(net, dexes, read) {
|
|
41
|
+
const venuePolicy = getVenue("hyperliquid").rateLimit;
|
|
42
|
+
const policy = { capacity: HL_DEX_FANOUT_BURST, refillPerSec: venuePolicy.refillPerSec, weights: venuePolicy.weights };
|
|
43
|
+
const out = new Array(dexes.length);
|
|
44
|
+
let next = 0;
|
|
45
|
+
const worker = async () => {
|
|
46
|
+
while (next < dexes.length) {
|
|
47
|
+
const i = next++;
|
|
48
|
+
try {
|
|
49
|
+
await acquireRate(`hyperliquid:hip3-fanout:${net}`, policy, "clearinghouseState");
|
|
50
|
+
out[i] = { status: "fulfilled", value: await read(dexes[i]) };
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
out[i] = { status: "rejected", reason: e };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
await Promise.all(Array.from({ length: Math.min(HL_DEX_FANOUT_CONCURRENCY, dexes.length) }, worker));
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/** Hyperliquid `perpDexs` → HIP-3 dex names (the first entry, null, is the native dex). */
|
|
61
|
+
export function hlDexNamesOf(j) {
|
|
62
|
+
if (!Array.isArray(j))
|
|
63
|
+
throw new PerpError("EXCHANGE_ERROR", "hyperliquid perpDexs: expected an array", { exchange: "hyperliquid" });
|
|
64
|
+
return j.flatMap(d => (d !== null && typeof d === "object" && typeof d.name === "string" ? [d.name] : []));
|
|
65
|
+
}
|
|
66
|
+
/** Account index of a Lighter account object. */
|
|
67
|
+
export function lighterAccountIndexOf(a) {
|
|
68
|
+
return Number(a.account_index ?? a.index);
|
|
69
|
+
}
|
|
70
|
+
/** The accounts of an address a read covers: every one, or the one of `accountIndex` (not one of the address's → INVALID_PARAMS). */
|
|
71
|
+
export function chooseLighterAccounts(venue, accounts, accountIndex) {
|
|
72
|
+
if (accountIndex === undefined)
|
|
73
|
+
return accounts;
|
|
74
|
+
const chosen = accounts.filter(a => lighterAccountIndexOf(a) === accountIndex);
|
|
75
|
+
if (chosen.length === 0) {
|
|
76
|
+
throw new PerpError("INVALID_PARAMS", `${venue}: account index ${accountIndex} is not an account of this address (accounts: ${accounts.map(lighterAccountIndexOf).join(", ")})`, {
|
|
77
|
+
exchange: venue, remediation: `Use one of: ${accounts.map(lighterAccountIndexOf).join(", ")} — or drop --account-index for every account`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return chosen;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Hyperliquid: info `clearinghouseState` {user} for the native perp dex plus {user, dex} for every
|
|
84
|
+
* HIP-3 dex of `perpDexs` (one request each, paced: hlDexFanOut). Native rows carry `dex: null`, HIP-3 rows their dex
|
|
85
|
+
* and the prefixed coin ("xyz:GOLD"), as the HIP-3 market rows. A dex (or the dex list) that
|
|
86
|
+
* fails is an `errors` entry ("hip3:<dex>") — its rows are unknown, never silently dropped; the
|
|
87
|
+
* native read failing fails the venue.
|
|
88
|
+
*/
|
|
89
|
+
export async function hyperliquidPositionsByAddress(net, address) {
|
|
90
|
+
const { legacyPublicJson } = await import("../funding/legacy-public.js");
|
|
91
|
+
const { legacyPublicBaseUrl } = await import("../api/public/urls.js");
|
|
92
|
+
const { hlPositionRows } = await import("./hyperliquid.js");
|
|
93
|
+
const url = `${legacyPublicBaseUrl("hyperliquid", net)}/info`;
|
|
94
|
+
const post = (body, label) => legacyPublicJson("hyperliquid", url, label, {
|
|
95
|
+
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
|
|
96
|
+
});
|
|
97
|
+
const errors = {};
|
|
98
|
+
const [native, dexList] = await Promise.all([
|
|
99
|
+
typed("hyperliquid", () => post({ type: "clearinghouseState", user: address }, "info clearinghouseState")),
|
|
100
|
+
post({ type: "perpDexs" }, "info perpDexs").then(hlDexNamesOf, (e) => { errors.hip3 = errorOf(e, "hyperliquid"); return []; }),
|
|
101
|
+
]);
|
|
102
|
+
const positions = hlPositionRows(native, coin => coin).map(r => ({ ...r, dex: null }));
|
|
103
|
+
const perDex = await hlDexFanOut(net, dexList, dex => post({ type: "clearinghouseState", user: address, dex }, `info clearinghouseState dex ${dex}`));
|
|
104
|
+
perDex.forEach((res, i) => {
|
|
105
|
+
const dex = dexList[i];
|
|
106
|
+
if (res.status === "rejected") {
|
|
107
|
+
errors[`hip3:${dex}`] = errorOf(res.reason, "hyperliquid");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
positions.push(...hlPositionRows(res.value, coin => (coin.includes(":") ? coin : `${dex}:${coin}`)).map(r => ({ ...r, dex })));
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
errors[`hip3:${dex}`] = errorOf(e, "hyperliquid");
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return { positions, errors, notes: [] };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Lighter / Lighter RH: `GET /account?by=l1_address` lists every account of the address
|
|
121
|
+
* (sub-accounts); each account's rows are tagged with its `accountIndex`. The venue keeps a
|
|
122
|
+
* zero-size row for every market an account ever touched: those are dropped by the mapper.
|
|
123
|
+
* `accountIndex` narrows to one account (not one of the address's → INVALID_PARAMS).
|
|
124
|
+
*/
|
|
125
|
+
export async function lighterPositionsByAddress(venue, net, address, opts) {
|
|
126
|
+
const { fetchLighterAccountsByL1, fetchLighterPriceDecimals } = await import("./lighter-public.js");
|
|
127
|
+
const { lighterPositionRows } = await import("./lighter.js");
|
|
128
|
+
const accounts = await fetchLighterAccountsByL1(venue, net, address);
|
|
129
|
+
if (accounts === null || accounts.length === 0)
|
|
130
|
+
return { positions: [], errors: {}, notes: [`no ${venue === "lighter" ? "Lighter" : "Lighter RH"} account for this address`] };
|
|
131
|
+
const chosen = chooseLighterAccounts(venue, accounts, opts.accountIndex);
|
|
132
|
+
const open = (a) => (Array.isArray(a.positions) ? a.positions : []).some(p => Number(p.position) !== 0);
|
|
133
|
+
// Price decimals only when there is a mark to format (an account without positions costs one read).
|
|
134
|
+
const decimals = chosen.some(open) ? await fetchLighterPriceDecimals(venue, net) : new Map();
|
|
135
|
+
const positions = chosen.flatMap(a => lighterPositionRows(a.positions, venue, sym => decimals.get(sym.toUpperCase())).map(r => ({ ...r, accountIndex: lighterAccountIndexOf(a) })));
|
|
136
|
+
return { positions, errors: {}, notes: [] };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Pacifica: `GET /positions?account=` marked with `/info/prices`; unresolved leverage from the
|
|
140
|
+
* account's public `/account/settings`, then the market max (`/info`). The address is validated
|
|
141
|
+
* as base58 32 bytes by the caller (an EVM address would be answered `success: true, []`).
|
|
142
|
+
*/
|
|
143
|
+
export async function pacificaPositionsByAddress(net, address) {
|
|
144
|
+
const { PacificaClient } = await import("../pacifica/client.js");
|
|
145
|
+
const { pacificaPositionRows } = await import("./pacifica.js");
|
|
146
|
+
const { legacyPublicData } = await import("../funding/legacy-public.js");
|
|
147
|
+
const client = new PacificaClient({ network: net });
|
|
148
|
+
const [rows, prices] = await typed("pacifica", () => Promise.all([client.getPositions(address), client.getPrices()]));
|
|
149
|
+
const positions = await pacificaPositionRows(rows, prices, {
|
|
150
|
+
accountSettings: () => client.getAccountSettings(address),
|
|
151
|
+
maxLeverage: async () => new Map((await legacyPublicData("pacifica").fetchMarkets(net)).map(m => [m.symbol, m.maxLeverage])),
|
|
152
|
+
});
|
|
153
|
+
return { positions, errors: {}, notes: [] };
|
|
154
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
|
|
3
|
-
* (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
|
|
4
3
|
*
|
|
5
4
|
* Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
|
|
6
5
|
* Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
|
|
@@ -42,7 +41,7 @@
|
|
|
42
41
|
import { PerpError } from "../../errors.js";
|
|
43
42
|
import type { WithdrawGate } from "../../settings.js";
|
|
44
43
|
import { type BuilderFeeGate } from "../builder-fee.js";
|
|
45
|
-
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade } from "../interface.js";
|
|
44
|
+
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
|
|
46
45
|
import type { IocOutcome } from "../ioc-outcome.js";
|
|
47
46
|
import type { ResolvedSlippage } from "../slippage.js";
|
|
48
47
|
import type { TradingV2 } from "../trading-v2.js";
|
|
@@ -52,6 +51,20 @@ import { type QfexReduceOnlyWire } from "./signing.js";
|
|
|
52
51
|
import { qfexPendingRemediation, type QfexOrderResponse } from "./ws-transport.js";
|
|
53
52
|
/** A mark price entry older than this (its own venue `time`) is not used as an order reference. */
|
|
54
53
|
export declare const QFEX_MARK_MAX_AGE_MS = 10000;
|
|
54
|
+
/**
|
|
55
|
+
* Candle volumes. GET /candles returns `usdVolume` (populated) and `baseTokenVolume`, which the
|
|
56
|
+
* venue leaves "0" on traded candles (live 2026-10-14, CL-USD 1HOUR: baseTokenVolume "0" with
|
|
57
|
+
* usdVolume "52534.48506" on every row; startingOpenInterest / orderbookMidPrice* are "0" too).
|
|
58
|
+
* A base "0" next to a non-zero USD volume is a placeholder → null (unknown), never 0; when
|
|
59
|
+
* both are zero the candle really had no volume.
|
|
60
|
+
*/
|
|
61
|
+
export declare function qfexCandleVolumes(c: {
|
|
62
|
+
baseTokenVolume: unknown;
|
|
63
|
+
usdVolume: unknown;
|
|
64
|
+
}): {
|
|
65
|
+
volume: string | null;
|
|
66
|
+
quoteVolume: string;
|
|
67
|
+
};
|
|
55
68
|
/** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
|
|
56
69
|
export declare function qfexClientOrderId(): string;
|
|
57
70
|
/**
|
|
@@ -64,6 +77,22 @@ export declare function qfexClientOrderId(): string;
|
|
|
64
77
|
* quantity) are quantized to the instrument tick / lot instead (fromWireNumber).
|
|
65
78
|
*/
|
|
66
79
|
export declare function plainNumber(n: unknown, what: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* A QFEX `market list` / `market info` row: the legacy shape plus listing state and quote currency.
|
|
82
|
+
* Prices are in `quoteAsset`; `volume24h` is USD (null for a non-USD quote — no FX rate published),
|
|
83
|
+
* `volume24hQuote` the venue's 24h notional in `quoteAsset`.
|
|
84
|
+
*/
|
|
85
|
+
export type QfexMarketRow = ExchangeMarketInfo & {
|
|
86
|
+
tradable: boolean;
|
|
87
|
+
status: QfexListedStatus;
|
|
88
|
+
quoteAsset: string;
|
|
89
|
+
volume24hQuote: string | null;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Why a LISTED (ACTIVE) market is or is not tradable through perp-cli: "active" = tradable;
|
|
93
|
+
* "nonUsdQuote" = quoted in KRW / JPY / CNY / SEK / HKD — readable, never ordered (USD sizing).
|
|
94
|
+
*/
|
|
95
|
+
export type QfexListedStatus = "active" | "nonUsdQuote";
|
|
67
96
|
export interface QfexAddOrderWire {
|
|
68
97
|
symbol: string;
|
|
69
98
|
side: "BUY" | "SELL";
|
|
@@ -130,6 +159,12 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
130
159
|
private session;
|
|
131
160
|
private instrumentMap;
|
|
132
161
|
private instrumentOf;
|
|
162
|
+
/**
|
|
163
|
+
* Instrument of a per-symbol read (book / funding / kline / info). Unknown → SYMBOL_NOT_FOUND; a
|
|
164
|
+
* listed market whose /refdata status is not ACTIVE (DELISTED, INACTIVE) → SYMBOL_NOT_FOUND with
|
|
165
|
+
* that status: it is not a live market. A non-USD ACTIVE market is readable (only orders refuse it).
|
|
166
|
+
*/
|
|
167
|
+
private readableInstrument;
|
|
133
168
|
/** Refuse a market perp-cli must not send orders to, and an order type / TIF the symbol does not list. */
|
|
134
169
|
private requireOrderable;
|
|
135
170
|
/** Mark of ONE symbol from a single-symbol MDS subscription; PRICE_STALE when absent or older than QFEX_MARK_MAX_AGE_MS. */
|
|
@@ -215,7 +250,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
215
250
|
/**
|
|
216
251
|
* A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
|
|
217
252
|
* on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
|
|
218
|
-
* unknown (ORDER_PENDING,
|
|
253
|
+
* unknown (ORDER_PENDING, never re-sent). It is NOT cancelled here: a cancel
|
|
219
254
|
* is a second mutating command with its own unknown outcome and cannot undo a fill.
|
|
220
255
|
*/
|
|
221
256
|
private reduceOnlyEchoError;
|
|
@@ -225,7 +260,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
225
260
|
private orderResultOf;
|
|
226
261
|
/**
|
|
227
262
|
* Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
|
|
228
|
-
* terminal frame was received
|
|
263
|
+
* terminal frame was received. A pending result is never an outcome.
|
|
229
264
|
*/
|
|
230
265
|
iocOutcomeOf(raw: unknown): IocOutcome | null;
|
|
231
266
|
amendOrder(req: AmendRequest): Promise<OrderResult>;
|
|
@@ -248,19 +283,40 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
248
283
|
private settleUnknownStopCancel;
|
|
249
284
|
private settledByHistory;
|
|
250
285
|
applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
|
|
251
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Legacy rows plus `tradable` (ACTIVE and USD-quoted, qfexTradable), the /refdata `status`
|
|
288
|
+
* (ACTIVE / INACTIVE / DELISTED) and `quoteAsset` (prices of KRW / JPY / CNY / SEK / HKD markets
|
|
289
|
+
* are in that currency, not USD).
|
|
290
|
+
*/
|
|
291
|
+
getMarkets(): Promise<QfexMarketRow[]>;
|
|
292
|
+
private marketRow;
|
|
293
|
+
/**
|
|
294
|
+
* `market info` of one ACTIVE market. fundingRate is the CURRENT /md/contracts `funding_rate`
|
|
295
|
+
* (the rate `market funding` / `arb scan --rates --include-unverified` show), labelled fundingVerified false: QFEX funding
|
|
296
|
+
* is unverified (Q4, display only). Market lists keep fundingRate null so no scan / bot reads it.
|
|
297
|
+
*/
|
|
298
|
+
getMarketInfo(symbol: string): Promise<QfexMarketRow & {
|
|
299
|
+
fundingVerified: false;
|
|
300
|
+
fundingNote: string;
|
|
301
|
+
marketClosed: boolean | null;
|
|
302
|
+
}>;
|
|
303
|
+
/** `quoteAsset`: level prices are in the market's quote currency (KRW for SAMSUNG-KRW). */
|
|
252
304
|
getOrderbook(symbol: string): Promise<{
|
|
253
305
|
bids: [string, string][];
|
|
254
306
|
asks: [string, string][];
|
|
307
|
+
quoteAsset: string;
|
|
255
308
|
}>;
|
|
256
|
-
getRecentTrades(): Promise<
|
|
257
|
-
/**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
309
|
+
getRecentTrades(): Promise<ExchangePublicTrade[]>;
|
|
310
|
+
/**
|
|
311
|
+
* Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24).
|
|
312
|
+
* A window outside the market's funding hours (qfexFundingWindowClosed) is `rate: null,
|
|
313
|
+
* marketClosed: true` — the venue prints 0 there, which is "no funding", not a 0 rate.
|
|
314
|
+
*/
|
|
315
|
+
getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
|
|
316
|
+
/** Rows carry `quoteAsset`: OHLC prices are in the market's quote currency (KRW for SAMSUNG-KRW). */
|
|
317
|
+
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<Array<ExchangeKline & {
|
|
318
|
+
quoteAsset: string;
|
|
319
|
+
}>>;
|
|
264
320
|
private userPositions;
|
|
265
321
|
/** Equity is the venue's own figure (GET /user/subaccounts/equity) — never recomputed from balance fields. */
|
|
266
322
|
getBalance(): Promise<ExchangeBalance>;
|