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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QFEX venue definition (design §14.1 as amended by §14.12).
|
|
3
|
+
*
|
|
4
|
+
* TradFi perpetuals (equities, indices, commodities; no spot) on a custodial, off-chain
|
|
5
|
+
* matching engine. All markets are cross-margin, margin asset USDC. Orders go over the Trade
|
|
6
|
+
* WebSocket only; account reads, leverage and transfers over REST; deposits / withdrawals over
|
|
7
|
+
* the separate "banker" host. Hot-path key = HMAC API key created on the QFEX website (2FA),
|
|
8
|
+
* stored in ~/.perp/qfex-agents/<publicKey>.json (SSOT Rule #3). There is no wallet signature.
|
|
9
|
+
* Not registered here; the registry module owns registerVenue().
|
|
10
|
+
*/
|
|
11
|
+
import type { VenueDefinition } from "../venue.js";
|
|
12
|
+
import type { Network } from "../venue-types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Hosts per network. VenueNetworkConfig carries only two (rest + ws), so the MDS and banker
|
|
15
|
+
* hosts live here. Testnet = QFEX "UAT": "identical API and behaviour, separate exchange
|
|
16
|
+
* instance with no real funds" (official CLI README "Environments").
|
|
17
|
+
*/
|
|
18
|
+
export declare const QFEX_ENDPOINTS: Readonly<Record<Network, {
|
|
19
|
+
rest: string;
|
|
20
|
+
tradeWs: string;
|
|
21
|
+
mdsWs: string;
|
|
22
|
+
banker: string;
|
|
23
|
+
}>>;
|
|
24
|
+
/** Chain QFEX USDC deposits / withdrawals use in perp-cli (banker network ARBITRUM_ONE). */
|
|
25
|
+
export declare const QFEX_WITHDRAW_NETWORK = "arbitrum";
|
|
26
|
+
/**
|
|
27
|
+
* API withdrawals are switched off (owner decision 2026-10-13): `funds withdraw qfex` and
|
|
28
|
+
* QfexAdapter.withdraw refuse with CAPABILITY_UNSUPPORTED. The implementation (banker POST
|
|
29
|
+
* /withdraw behind settings.withdraw.qfex) is kept; flip this to re-enable it.
|
|
30
|
+
*/
|
|
31
|
+
export declare const QFEX_WITHDRAW_ENABLED: boolean;
|
|
32
|
+
/** The uniform refusal while QFEX_WITHDRAW_ENABLED is false. */
|
|
33
|
+
export declare const QFEX_WITHDRAW_DISABLED: {
|
|
34
|
+
readonly reason: "QFEX withdrawals are disabled in perp-cli for now";
|
|
35
|
+
readonly remediation: "Withdraw from the QFEX web app";
|
|
36
|
+
};
|
|
37
|
+
/** Banker `network` value for QFEX_WITHDRAW_NETWORK. */
|
|
38
|
+
export declare const QFEX_BANKER_NETWORK = "ARBITRUM_ONE";
|
|
39
|
+
export declare const qfexVenue: VenueDefinition;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QFEX venue definition (design §14.1 as amended by §14.12).
|
|
3
|
+
*
|
|
4
|
+
* TradFi perpetuals (equities, indices, commodities; no spot) on a custodial, off-chain
|
|
5
|
+
* matching engine. All markets are cross-margin, margin asset USDC. Orders go over the Trade
|
|
6
|
+
* WebSocket only; account reads, leverage and transfers over REST; deposits / withdrawals over
|
|
7
|
+
* the separate "banker" host. Hot-path key = HMAC API key created on the QFEX website (2FA),
|
|
8
|
+
* stored in ~/.perp/qfex-agents/<publicKey>.json (SSOT Rule #3). There is no wallet signature.
|
|
9
|
+
* Not registered here; the registry module owns registerVenue().
|
|
10
|
+
*/
|
|
11
|
+
import { BUILDER_FEE_CAPABILITIES } from "../builder-fee.js";
|
|
12
|
+
import { QFEX_MODIFY_KEEPS_CLIENT_ID, QFEX_RESTING_REDUCE_ONLY_VERIFIED } from "../qfex/signing.js";
|
|
13
|
+
/**
|
|
14
|
+
* Hosts per network. VenueNetworkConfig carries only two (rest + ws), so the MDS and banker
|
|
15
|
+
* hosts live here. Testnet = QFEX "UAT": "identical API and behaviour, separate exchange
|
|
16
|
+
* instance with no real funds" (official CLI README "Environments").
|
|
17
|
+
*/
|
|
18
|
+
export const QFEX_ENDPOINTS = {
|
|
19
|
+
mainnet: { rest: "https://api.qfex.com", tradeWs: "wss://trade.qfex.com", mdsWs: "wss://mds.qfex.com", banker: "https://banker.qfex.com" },
|
|
20
|
+
testnet: { rest: "https://api.qfex.io", tradeWs: "wss://trade.qfex.io", mdsWs: "wss://mds.qfex.io", banker: "https://banker.qfex.io" },
|
|
21
|
+
};
|
|
22
|
+
/** Chain QFEX USDC deposits / withdrawals use in perp-cli (banker network ARBITRUM_ONE). */
|
|
23
|
+
export const QFEX_WITHDRAW_NETWORK = "arbitrum";
|
|
24
|
+
/**
|
|
25
|
+
* API withdrawals are switched off (owner decision 2026-10-13): `funds withdraw qfex` and
|
|
26
|
+
* QfexAdapter.withdraw refuse with CAPABILITY_UNSUPPORTED. The implementation (banker POST
|
|
27
|
+
* /withdraw behind settings.withdraw.qfex) is kept; flip this to re-enable it.
|
|
28
|
+
*/
|
|
29
|
+
export const QFEX_WITHDRAW_ENABLED = false;
|
|
30
|
+
/** The uniform refusal while QFEX_WITHDRAW_ENABLED is false. */
|
|
31
|
+
export const QFEX_WITHDRAW_DISABLED = { reason: "QFEX withdrawals are disabled in perp-cli for now", remediation: "Withdraw from the QFEX web app" };
|
|
32
|
+
/** Banker `network` value for QFEX_WITHDRAW_NETWORK. */
|
|
33
|
+
export const QFEX_BANKER_NETWORK = "ARBITRUM_ONE";
|
|
34
|
+
/** Lazy: public.ts (and `ws`) is only loaded when market data is actually requested. */
|
|
35
|
+
const publicData = {
|
|
36
|
+
async fetchFundingRates(net) {
|
|
37
|
+
return (await import("../qfex/public.js")).qfexPublicData.fetchFundingRates(net);
|
|
38
|
+
},
|
|
39
|
+
async fetchMarkets(net) {
|
|
40
|
+
return (await import("../qfex/public.js")).qfexPublicData.fetchMarkets(net);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
function network(net) {
|
|
44
|
+
return {
|
|
45
|
+
restUrl: QFEX_ENDPOINTS[net].rest,
|
|
46
|
+
wsUrl: QFEX_ENDPOINTS[net].tradeWs,
|
|
47
|
+
explorerUrl: null,
|
|
48
|
+
signingChainId: null,
|
|
49
|
+
settlementChain: "offchain",
|
|
50
|
+
collateral: ["USDC"],
|
|
51
|
+
collateralAssetIndex: null,
|
|
52
|
+
rpcUrl: null,
|
|
53
|
+
depositPins: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export const qfexVenue = {
|
|
57
|
+
id: "qfex",
|
|
58
|
+
aliases: ["qf"],
|
|
59
|
+
displayName: "QFEX",
|
|
60
|
+
family: "qfex",
|
|
61
|
+
// Custodial: there is no owner-wallet signing path at all.
|
|
62
|
+
masterKeyKind: "none",
|
|
63
|
+
agentKeyKind: "hmac",
|
|
64
|
+
secretStorage: { kind: "perp-keystore", dir: "qfex-agents" },
|
|
65
|
+
networks: { mainnet: network("mainnet"), testnet: network("testnet") },
|
|
66
|
+
// Docs /qfex/funding: settled every 60 minutes.
|
|
67
|
+
fundingRateBasis: { kind: "perHours", hours: 1 },
|
|
68
|
+
settlementIntervalHours: 1,
|
|
69
|
+
// Q4: the unit of `funding_rate` (and funding_rate vs next_funding_rate) is not verified
|
|
70
|
+
// against settled payments, and equity funding accrues only ~120 of 168 hours a week, so
|
|
71
|
+
// hourly × 8760 overstates it. Until both are settled QFEX is not a funding-arb venue.
|
|
72
|
+
fundingVerified: false,
|
|
73
|
+
roles: ["marketData", "account", "trading"],
|
|
74
|
+
capabilities: {
|
|
75
|
+
// The venue offers a native MARKET order (bounded only by the price band — docs /qfex/risk-limits
|
|
76
|
+
// "Dynamic price bands": 1.65%–10% by product preset and session; "integrations should use
|
|
77
|
+
// that live value rather than hardcoding a percentage"); the
|
|
78
|
+
// adapter sends every market order as an IOC LIMIT at the tick-aligned protection price
|
|
79
|
+
// (mark ± resolved slippage, design §13.2) — the same declaration as Ondo.
|
|
80
|
+
marketOrder: "native",
|
|
81
|
+
// ALO = order_type "ALO" + GTC.
|
|
82
|
+
timeInForce: ["GTC", "IOC", "FOK", "ALO"],
|
|
83
|
+
// Reduce-only (Q2, production 2026-10-05: `reduce_only` is a JSON boolean; rejected with no
|
|
84
|
+
// position, but an order larger than the position is accepted and RESTS at full size). The
|
|
85
|
+
// adapter enforces side and size against the live position before each send, which covers
|
|
86
|
+
// an immediate order only — a resting one is refused until match-time capping is proven
|
|
87
|
+
// (QFEX_RESTING_REDUCE_ONLY_VERIFIED, qfex/signing.ts).
|
|
88
|
+
reduceOnlyTif: QFEX_RESTING_REDUCE_ONLY_VERIFIED === true ? null : ["IOC"],
|
|
89
|
+
clientOrderId: true,
|
|
90
|
+
// The ACK arrives first; fills / rejections follow as separate events. Client order ids
|
|
91
|
+
// are GENERATED only (venue ids are not unique) — a user --client-id is refused.
|
|
92
|
+
orderAck: "async",
|
|
93
|
+
// Attached take_profit / stop_loss exists on add_order, but the stop quantity on a partial
|
|
94
|
+
// or zero-fill entry and the id of the resulting stop are unverified (Q7) → separate
|
|
95
|
+
// reduce-only TAKE_PROFIT / STOP_LOSS stop orders sized to the verified fill (Q21 passed on
|
|
96
|
+
// production 2026-10-12: the stop ACK echoes the client id — qfex/signing.ts QFEX_STOP_CLIENT_ID_ECHO).
|
|
97
|
+
tpsl: "separateOrders",
|
|
98
|
+
// modify_order returns a NEW order id; the MODIFIED frame keeps the client id (Q5, production
|
|
99
|
+
// 2026-10-12 — QFEX_MODIFY_KEEPS_CLIENT_ID) → native amend correlated by client id.
|
|
100
|
+
amend: QFEX_MODIFY_KEEPS_CLIENT_ID === true ? "native" : null,
|
|
101
|
+
batchPlaceMax: null,
|
|
102
|
+
marginModes: ["cross"],
|
|
103
|
+
// TRON USDT and USD bank rails are out of scope. API withdrawals are off (QFEX_WITHDRAW_ENABLED).
|
|
104
|
+
withdrawAssets: QFEX_WITHDRAW_ENABLED ? ["USDC"] : null,
|
|
105
|
+
},
|
|
106
|
+
// Venue budget: 12,000 weight / 60 s per USER across all connections and keys (general and
|
|
107
|
+
// cancel buckets separate). This process takes 25% (50/s, burst 100); the rest is left to the
|
|
108
|
+
// user's other processes, the QFEX web app and the official CLI. Weights: docs /websocket/rate.
|
|
109
|
+
rateLimit: {
|
|
110
|
+
capacity: 100,
|
|
111
|
+
refillPerSec: 50,
|
|
112
|
+
weights: {
|
|
113
|
+
add_order: 1, cancel_order: 1, modify_order: 1, get_order: 2, get_user_orders: 5, cancel_all_orders: 2,
|
|
114
|
+
get_user_trades: 0.5, subscribe: 0.1, unsubscribe: 0.1, set_user_leverage: 0.1, cancel_stop_order: 1,
|
|
115
|
+
modify_stop_order: 1,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
// Docs /qfex/faq + prohibited-jurisdictions ("including but not limited to"). Warn-only.
|
|
119
|
+
restrictedRegions: ["US", "GB", "ES", "UA", "SY", "IR", "RU", "KP", "MM", "BY", "VE", "SD"],
|
|
120
|
+
symbolIdentity: "explicit",
|
|
121
|
+
publicData,
|
|
122
|
+
spot: null,
|
|
123
|
+
// Widest documented price band (equities at the open: 10%). The order's own band is checked before sending.
|
|
124
|
+
maxSlippageBps: 1000,
|
|
125
|
+
builderFee: BUILDER_FEE_CAPABILITIES["qfex"],
|
|
126
|
+
create: async (ctx) => {
|
|
127
|
+
const { QfexAdapter } = await import("../qfex/adapter.js");
|
|
128
|
+
const adapter = new QfexAdapter(qfexVenue, ctx);
|
|
129
|
+
// Builder code: only settings builder.qfex (off by default); no approval step. It rides on
|
|
130
|
+
// the connection's auth, so --no-builder / testnet use a session without it.
|
|
131
|
+
const { loadSettings } = await import("../../settings.js");
|
|
132
|
+
const { resolveBuilderGate } = await import("../builder-fee.js");
|
|
133
|
+
const settings = loadSettings();
|
|
134
|
+
adapter.builderFee = resolveBuilderGate("qfex", ctx, settings.builder, null, () => "");
|
|
135
|
+
// Withdraw gate (settings.withdraw.qfex): absent = withdraw() refuses.
|
|
136
|
+
const gate = settings.withdraw.qfex;
|
|
137
|
+
adapter.withdrawGate = gate === undefined ? null : gate;
|
|
138
|
+
return adapter;
|
|
139
|
+
},
|
|
140
|
+
};
|
package/dist/execution-log.js
CHANGED
|
@@ -11,13 +11,21 @@ function genId() {
|
|
|
11
11
|
}
|
|
12
12
|
/** Append an execution record to the log */
|
|
13
13
|
export function logExecution(record) {
|
|
14
|
-
ensureDir();
|
|
15
14
|
const full = {
|
|
16
15
|
id: genId(),
|
|
17
16
|
timestamp: new Date().toISOString(),
|
|
18
17
|
...record,
|
|
19
18
|
};
|
|
20
|
-
|
|
19
|
+
// The log is a record OF an outcome, never part of it: a write failure (disk full, read-only
|
|
20
|
+
// home, permissions) must not turn a filled order into a thrown "failed" one. It is reported
|
|
21
|
+
// on stderr and the caller continues with the real result.
|
|
22
|
+
try {
|
|
23
|
+
ensureDir();
|
|
24
|
+
appendFileSync(LOG_FILE, JSON.stringify(full) + "\n", { mode: 0o600 });
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
process.stderr.write(`[perp] Warning: could not write the execution log (${LOG_FILE}): ${e instanceof Error ? e.message : String(e)} — the ${record.type} on ${record.exchange} was recorded as "${record.status}" in memory only\n`);
|
|
28
|
+
}
|
|
21
29
|
return full;
|
|
22
30
|
}
|
|
23
31
|
/** Read execution log with optional filters */
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public market data for the legacy venues without a native `getVenueMarkets`
|
|
3
|
+
* (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
|
|
4
|
+
* exchanges/lighter-public.ts.
|
|
5
|
+
*
|
|
6
|
+
* Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
|
|
7
|
+
* never from the mutable `*_API_URL` globals. A network without a known public
|
|
8
|
+
* API throws NOT_IMPLEMENTED.
|
|
9
|
+
*
|
|
10
|
+
* FUNDING BASIS / SETTLEMENT
|
|
11
|
+
* pacifica, hyperliquid: the venue definition (fundingBasisOf / settlementIntervalOf).
|
|
12
|
+
* aster: per symbol from GET /fapi/v3/fundingInfo `fundingIntervalHours`
|
|
13
|
+
* (live 2026-09-30: 1h/2h/4h/8h mixed). One quoted rate covers one interval,
|
|
14
|
+
* so basis hours == settlement hours. A symbol missing from fundingInfo is
|
|
15
|
+
* excluded with a warning (its period would be a guess).
|
|
16
|
+
*
|
|
17
|
+
* ASSET CLASS
|
|
18
|
+
* hyperliquid core: crypto (HIP-3 dexes are not served here).
|
|
19
|
+
* pacifica: crypto-centric; PACIFICA_NON_CRYPTO lists every non-crypto perp
|
|
20
|
+
* listed on 2026-09-30.
|
|
21
|
+
* aster: exchangeInfo `underlyingSubType` (STOCK / ETF / Commodities / pre-launch).
|
|
22
|
+
* AMBIGUOUS tickers are excluded with a warning so they cannot produce a false
|
|
23
|
+
* cross-venue match (SPX = the SPX6900 token, whose ticker equals the S&P 500
|
|
24
|
+
* canonical key).
|
|
25
|
+
*
|
|
26
|
+
* Fields a venue does not publish on this path are null (HL has no fixed tick
|
|
27
|
+
* size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
|
|
28
|
+
*/
|
|
29
|
+
import type { VenuePublicData } from "../exchanges/venue.js";
|
|
30
|
+
export type LegacyPublicVenue = "pacifica" | "hyperliquid" | "aster";
|
|
31
|
+
export declare function legacyPublicData(venue: LegacyPublicVenue): VenuePublicData;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public market data for the legacy venues without a native `getVenueMarkets`
|
|
3
|
+
* (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
|
|
4
|
+
* exchanges/lighter-public.ts.
|
|
5
|
+
*
|
|
6
|
+
* Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
|
|
7
|
+
* never from the mutable `*_API_URL` globals. A network without a known public
|
|
8
|
+
* API throws NOT_IMPLEMENTED.
|
|
9
|
+
*
|
|
10
|
+
* FUNDING BASIS / SETTLEMENT
|
|
11
|
+
* pacifica, hyperliquid: the venue definition (fundingBasisOf / settlementIntervalOf).
|
|
12
|
+
* aster: per symbol from GET /fapi/v3/fundingInfo `fundingIntervalHours`
|
|
13
|
+
* (live 2026-09-30: 1h/2h/4h/8h mixed). One quoted rate covers one interval,
|
|
14
|
+
* so basis hours == settlement hours. A symbol missing from fundingInfo is
|
|
15
|
+
* excluded with a warning (its period would be a guess).
|
|
16
|
+
*
|
|
17
|
+
* ASSET CLASS
|
|
18
|
+
* hyperliquid core: crypto (HIP-3 dexes are not served here).
|
|
19
|
+
* pacifica: crypto-centric; PACIFICA_NON_CRYPTO lists every non-crypto perp
|
|
20
|
+
* listed on 2026-09-30.
|
|
21
|
+
* aster: exchangeInfo `underlyingSubType` (STOCK / ETF / Commodities / pre-launch).
|
|
22
|
+
* AMBIGUOUS tickers are excluded with a warning so they cannot produce a false
|
|
23
|
+
* cross-venue match (SPX = the SPX6900 token, whose ticker equals the S&P 500
|
|
24
|
+
* canonical key).
|
|
25
|
+
*
|
|
26
|
+
* Fields a venue does not publish on this path are null (HL has no fixed tick
|
|
27
|
+
* size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
|
|
28
|
+
*/
|
|
29
|
+
import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
|
|
30
|
+
import { PerpError } from "../errors.js";
|
|
31
|
+
import { venueRateString } from "../utils/numeric.js";
|
|
32
|
+
import { assertOk } from "../api/public/_http.js";
|
|
33
|
+
import { legacyPublicBaseUrl } from "../api/public/urls.js";
|
|
34
|
+
import { canonicalFor } from "../exchanges/canonical-symbols.js";
|
|
35
|
+
import { fundingBasisOf, settlementIntervalOf } from "../exchanges/venue.js";
|
|
36
|
+
// ── Classification ──
|
|
37
|
+
const AMBIGUOUS = new Set(["SPX"]);
|
|
38
|
+
/** Pacifica non-crypto perps (live /info 2026-09-30). BP / CHIP: ticker collides with a listed equity/ETF. */
|
|
39
|
+
const PACIFICA_NON_CRYPTO = {
|
|
40
|
+
NVDA: "equity", TSLA: "equity", GOOGL: "equity", PLTR: "equity", HOOD: "equity", CRCL: "equity",
|
|
41
|
+
SPCX: "equity", SKHYNIX: "equity", SAMSUNG: "equity", MSTR: "equity", MU: "equity", SNDK: "equity",
|
|
42
|
+
META: "equity",
|
|
43
|
+
DRAM: "etf", URNM: "etf",
|
|
44
|
+
XAU: "commodity", XAG: "commodity", CL: "commodity", COPPER: "commodity", NATGAS: "commodity",
|
|
45
|
+
PLATINUM: "commodity",
|
|
46
|
+
USDJPY: "fx", EURUSD: "fx",
|
|
47
|
+
SP500: "index",
|
|
48
|
+
};
|
|
49
|
+
const PACIFICA_AMBIGUOUS = new Set(["BP", "CHIP"]);
|
|
50
|
+
function classifyPacifica(symbol) {
|
|
51
|
+
const up = symbol.toUpperCase();
|
|
52
|
+
if (AMBIGUOUS.has(up) || PACIFICA_AMBIGUOUS.has(up))
|
|
53
|
+
return null;
|
|
54
|
+
return PACIFICA_NON_CRYPTO[up] ?? "crypto"; // crypto-centric venue: non-crypto is listed explicitly
|
|
55
|
+
}
|
|
56
|
+
function classifyHyperliquid(symbol) {
|
|
57
|
+
return AMBIGUOUS.has(symbol.toUpperCase()) ? null : "crypto";
|
|
58
|
+
}
|
|
59
|
+
function classifyAster(symbol, subTypes) {
|
|
60
|
+
if (AMBIGUOUS.has(symbol.toUpperCase()))
|
|
61
|
+
return null;
|
|
62
|
+
if (subTypes.includes("pre-launch"))
|
|
63
|
+
return "preIpo";
|
|
64
|
+
if (subTypes.includes("ETF"))
|
|
65
|
+
return "etf";
|
|
66
|
+
if (subTypes.includes("STOCK"))
|
|
67
|
+
return "equity";
|
|
68
|
+
if (subTypes.includes("Commodities"))
|
|
69
|
+
return "commodity";
|
|
70
|
+
return "crypto";
|
|
71
|
+
}
|
|
72
|
+
function warnExcluded(venue, what, symbols) {
|
|
73
|
+
if (symbols.length > 0) {
|
|
74
|
+
console.warn(`[${venue}] excluded ${symbols.length} ${what}: ${symbols.join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ── HTTP ──
|
|
78
|
+
async function getJson(venue, url, label, init) {
|
|
79
|
+
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15_000) });
|
|
80
|
+
await assertOk(res, `${venue} ${label}`);
|
|
81
|
+
const text = await res.text();
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(text);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: invalid JSON`, { body: text.slice(0, 500) });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function arrayField(venue, label, v) {
|
|
90
|
+
if (!Array.isArray(v))
|
|
91
|
+
throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: expected an array`);
|
|
92
|
+
return v;
|
|
93
|
+
}
|
|
94
|
+
function numStr(v) {
|
|
95
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
96
|
+
return String(v);
|
|
97
|
+
if (typeof v === "string" && v.length > 0 && Number.isFinite(Number(v)))
|
|
98
|
+
return v;
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/** A funding rate field: a JSON number without float noise (venueRateString), a numeric string verbatim; else null. */
|
|
102
|
+
function rateStr(v) {
|
|
103
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
104
|
+
return venueRateString(v);
|
|
105
|
+
return numStr(v);
|
|
106
|
+
}
|
|
107
|
+
function positiveNumStr(v) {
|
|
108
|
+
const s = numStr(v);
|
|
109
|
+
return s !== null && Number(s) > 0 ? s : null;
|
|
110
|
+
}
|
|
111
|
+
function stepFromDecimals(d) {
|
|
112
|
+
return d === 0 ? "1" : `0.${"0".repeat(d - 1)}1`;
|
|
113
|
+
}
|
|
114
|
+
// ── Pacifica ──
|
|
115
|
+
async function pacificaRows(net) {
|
|
116
|
+
const base = legacyPublicBaseUrl("pacifica", net);
|
|
117
|
+
const [infoJson, pricesJson] = await Promise.all([
|
|
118
|
+
getJson("pacifica", `${base}/info`, "info"),
|
|
119
|
+
getJson("pacifica", `${base}/info/prices`, "info/prices"),
|
|
120
|
+
]);
|
|
121
|
+
const info = arrayField("pacifica", "info", infoJson.data);
|
|
122
|
+
const prices = arrayField("pacifica", "info/prices", pricesJson.data);
|
|
123
|
+
const priceBySymbol = new Map(prices.map(p => [String(p.symbol), p]));
|
|
124
|
+
const basis = fundingBasisOf("pacifica");
|
|
125
|
+
const settlement = settlementIntervalOf("pacifica");
|
|
126
|
+
const out = [];
|
|
127
|
+
const unclassified = [];
|
|
128
|
+
const unpriced = [];
|
|
129
|
+
for (const m of info) {
|
|
130
|
+
// /info lists spot markets too (e.g. SOL-USDC). A row without the field cannot be classified.
|
|
131
|
+
if (typeof m.instrument_type !== "string") {
|
|
132
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica /info row ${String(m.symbol)} has no instrument_type`, { exchange: "pacifica" });
|
|
133
|
+
}
|
|
134
|
+
if (m.instrument_type === "spot")
|
|
135
|
+
continue;
|
|
136
|
+
const symbol = String(m.symbol ?? "");
|
|
137
|
+
const assetClass = classifyPacifica(symbol);
|
|
138
|
+
if (assetClass === null) {
|
|
139
|
+
unclassified.push(symbol);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const p = priceBySymbol.get(symbol);
|
|
143
|
+
const mark = p ? positiveNumStr(p.mark) : null;
|
|
144
|
+
if (!p || mark === null) {
|
|
145
|
+
unpriced.push(symbol);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const maxLeverage = Number(m.max_leverage);
|
|
149
|
+
out.push({
|
|
150
|
+
market: {
|
|
151
|
+
venue: "pacifica",
|
|
152
|
+
venueSymbol: symbol,
|
|
153
|
+
symbol,
|
|
154
|
+
canonical: canonicalFor("pacifica", symbol, assetClass),
|
|
155
|
+
assetClass,
|
|
156
|
+
settle: "USDC",
|
|
157
|
+
quoteAsset: null,
|
|
158
|
+
markPrice: mark,
|
|
159
|
+
indexPrice: positiveNumStr(p.oracle),
|
|
160
|
+
fundingRate: rateStr(p.next_funding),
|
|
161
|
+
fundingRateBasis: basis,
|
|
162
|
+
settlementIntervalHours: settlement,
|
|
163
|
+
openInterest: numStr(p.open_interest),
|
|
164
|
+
volume24h: numStr(p.volume_24h),
|
|
165
|
+
tickSize: positiveNumStr(m.tick_size),
|
|
166
|
+
stepSize: positiveNumStr(m.lot_size),
|
|
167
|
+
minSize: null,
|
|
168
|
+
minNotional: positiveNumStr(m.min_order_size),
|
|
169
|
+
maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
|
|
170
|
+
marginTiers: null,
|
|
171
|
+
session: null,
|
|
172
|
+
tradable: true,
|
|
173
|
+
},
|
|
174
|
+
nextFundingTime: null, // Pacifica publishes no next-funding timestamp
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
warnExcluded("pacifica", "unclassified/ambiguous market(s)", unclassified);
|
|
178
|
+
warnExcluded("pacifica", "market(s) without a mark price", unpriced);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
// ── Hyperliquid core ──
|
|
182
|
+
async function hyperliquidRows(net) {
|
|
183
|
+
const base = legacyPublicBaseUrl("hyperliquid", net);
|
|
184
|
+
const json = await getJson("hyperliquid", `${base}/info`, "info metaAndAssetCtxs", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({ type: "metaAndAssetCtxs" }),
|
|
188
|
+
});
|
|
189
|
+
if (!Array.isArray(json) || json.length < 2) {
|
|
190
|
+
throw new PerpError("EXCHANGE_ERROR", "hyperliquid metaAndAssetCtxs: expected [meta, assetCtxs]");
|
|
191
|
+
}
|
|
192
|
+
const universe = arrayField("hyperliquid", "meta.universe", json[0].universe);
|
|
193
|
+
const ctxs = arrayField("hyperliquid", "assetCtxs", json[1]);
|
|
194
|
+
if (ctxs.length !== universe.length) {
|
|
195
|
+
throw new PerpError("EXCHANGE_ERROR", `hyperliquid metaAndAssetCtxs: ${universe.length} assets vs ${ctxs.length} contexts`);
|
|
196
|
+
}
|
|
197
|
+
const basis = fundingBasisOf("hyperliquid");
|
|
198
|
+
const settlement = settlementIntervalOf("hyperliquid");
|
|
199
|
+
const out = [];
|
|
200
|
+
const unclassified = [];
|
|
201
|
+
const unpriced = [];
|
|
202
|
+
universe.forEach((a, i) => {
|
|
203
|
+
const ctx = ctxs[i];
|
|
204
|
+
const symbol = String(a.name ?? "");
|
|
205
|
+
const assetClass = classifyHyperliquid(symbol);
|
|
206
|
+
if (assetClass === null) {
|
|
207
|
+
unclassified.push(symbol);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const mark = positiveNumStr(ctx.markPx);
|
|
211
|
+
if (mark === null) {
|
|
212
|
+
unpriced.push(symbol);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const szDecimals = Number(a.szDecimals);
|
|
216
|
+
const maxLeverage = Number(a.maxLeverage);
|
|
217
|
+
out.push({
|
|
218
|
+
market: {
|
|
219
|
+
venue: "hyperliquid",
|
|
220
|
+
venueSymbol: symbol,
|
|
221
|
+
symbol,
|
|
222
|
+
canonical: canonicalFor("hyperliquid", symbol, assetClass),
|
|
223
|
+
assetClass,
|
|
224
|
+
settle: "USDC",
|
|
225
|
+
quoteAsset: null,
|
|
226
|
+
markPrice: mark,
|
|
227
|
+
indexPrice: positiveNumStr(ctx.oraclePx),
|
|
228
|
+
fundingRate: rateStr(ctx.funding),
|
|
229
|
+
fundingRateBasis: basis,
|
|
230
|
+
settlementIntervalHours: settlement,
|
|
231
|
+
openInterest: numStr(ctx.openInterest),
|
|
232
|
+
volume24h: numStr(ctx.dayNtlVlm),
|
|
233
|
+
tickSize: null, // HL prices use a significant-figure rule, not a fixed tick
|
|
234
|
+
stepSize: Number.isInteger(szDecimals) && szDecimals >= 0 ? stepFromDecimals(szDecimals) : null,
|
|
235
|
+
minSize: null,
|
|
236
|
+
minNotional: HL_MIN_ORDER_VALUE_USD,
|
|
237
|
+
maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
|
|
238
|
+
marginTiers: null,
|
|
239
|
+
session: null,
|
|
240
|
+
tradable: a.isDelisted !== true,
|
|
241
|
+
},
|
|
242
|
+
nextFundingTime: null, // not published in metaAndAssetCtxs
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
warnExcluded("hyperliquid", "unclassified/ambiguous market(s)", unclassified);
|
|
246
|
+
warnExcluded("hyperliquid", "market(s) without a mark price", unpriced);
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
// ── Aster ──
|
|
250
|
+
function asterFilter(filters, type, field) {
|
|
251
|
+
if (!Array.isArray(filters))
|
|
252
|
+
return null;
|
|
253
|
+
const f = filters.find(x => x.filterType === type);
|
|
254
|
+
return f ? positiveNumStr(f[field]) : null;
|
|
255
|
+
}
|
|
256
|
+
async function asterRows(net) {
|
|
257
|
+
const base = legacyPublicBaseUrl("aster", net);
|
|
258
|
+
const [infoJson, premiumJson, tickerJson, fundingInfoJson] = await Promise.all([
|
|
259
|
+
getJson("aster", `${base}/fapi/v1/exchangeInfo`, "exchangeInfo"),
|
|
260
|
+
getJson("aster", `${base}/fapi/v1/premiumIndex`, "premiumIndex"),
|
|
261
|
+
getJson("aster", `${base}/fapi/v1/ticker/24hr`, "ticker/24hr"),
|
|
262
|
+
getJson("aster", `${base}/fapi/v3/fundingInfo`, "fundingInfo"),
|
|
263
|
+
]);
|
|
264
|
+
const symbols = arrayField("aster", "exchangeInfo.symbols", infoJson.symbols);
|
|
265
|
+
const premiumBySymbol = new Map(arrayField("aster", "premiumIndex", premiumJson).map(p => [String(p.symbol), p]));
|
|
266
|
+
const tickerBySymbol = new Map(arrayField("aster", "ticker/24hr", tickerJson).map(t => [String(t.symbol), t]));
|
|
267
|
+
const intervalBySymbol = new Map();
|
|
268
|
+
for (const f of arrayField("aster", "fundingInfo", fundingInfoJson)) {
|
|
269
|
+
const h = Number(f.fundingIntervalHours);
|
|
270
|
+
if (Number.isFinite(h) && h > 0)
|
|
271
|
+
intervalBySymbol.set(String(f.symbol), h);
|
|
272
|
+
}
|
|
273
|
+
const out = [];
|
|
274
|
+
const unclassified = [];
|
|
275
|
+
const unpriced = [];
|
|
276
|
+
const noInterval = [];
|
|
277
|
+
for (const s of symbols) {
|
|
278
|
+
if (s.contractType !== "PERPETUAL" || s.quoteAsset !== "USDT")
|
|
279
|
+
continue;
|
|
280
|
+
const venueSymbol = String(s.symbol);
|
|
281
|
+
const symbol = String(s.baseAsset);
|
|
282
|
+
const subTypes = Array.isArray(s.underlyingSubType) ? s.underlyingSubType.map(String) : [];
|
|
283
|
+
const assetClass = classifyAster(symbol, subTypes);
|
|
284
|
+
if (assetClass === null) {
|
|
285
|
+
unclassified.push(venueSymbol);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const p = premiumBySymbol.get(venueSymbol);
|
|
289
|
+
const mark = p ? positiveNumStr(p.markPrice) : null;
|
|
290
|
+
if (!p || mark === null) {
|
|
291
|
+
unpriced.push(venueSymbol);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const hours = intervalBySymbol.get(venueSymbol);
|
|
295
|
+
if (hours === undefined) {
|
|
296
|
+
noInterval.push(venueSymbol);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (typeof s.marginAsset !== "string" || s.marginAsset.length === 0) {
|
|
300
|
+
throw new PerpError("EXCHANGE_ERROR", `aster exchangeInfo: ${venueSymbol} has no marginAsset`);
|
|
301
|
+
}
|
|
302
|
+
const next = Number(p.nextFundingTime);
|
|
303
|
+
const t = tickerBySymbol.get(venueSymbol);
|
|
304
|
+
out.push({
|
|
305
|
+
market: {
|
|
306
|
+
venue: "aster",
|
|
307
|
+
venueSymbol,
|
|
308
|
+
symbol,
|
|
309
|
+
canonical: canonicalFor("aster", symbol, assetClass),
|
|
310
|
+
assetClass,
|
|
311
|
+
settle: s.marginAsset,
|
|
312
|
+
quoteAsset: s.quoteAsset,
|
|
313
|
+
markPrice: mark,
|
|
314
|
+
indexPrice: positiveNumStr(p.indexPrice),
|
|
315
|
+
fundingRate: rateStr(p.lastFundingRate),
|
|
316
|
+
fundingRateBasis: { kind: "perHours", hours },
|
|
317
|
+
settlementIntervalHours: hours,
|
|
318
|
+
openInterest: null, // per-symbol endpoint only
|
|
319
|
+
volume24h: t ? numStr(t.quoteVolume) : null,
|
|
320
|
+
tickSize: asterFilter(s.filters, "PRICE_FILTER", "tickSize"),
|
|
321
|
+
stepSize: asterFilter(s.filters, "LOT_SIZE", "stepSize"),
|
|
322
|
+
minSize: asterFilter(s.filters, "LOT_SIZE", "minQty"),
|
|
323
|
+
minNotional: asterFilter(s.filters, "MIN_NOTIONAL", "notional"),
|
|
324
|
+
maxLeverage: null, // leverage brackets require a signed call
|
|
325
|
+
marginTiers: null,
|
|
326
|
+
session: null,
|
|
327
|
+
tradable: s.status === "TRADING",
|
|
328
|
+
},
|
|
329
|
+
nextFundingTime: Number.isFinite(next) && next > 0 ? next : null,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
warnExcluded("aster", "unclassified/ambiguous market(s)", unclassified);
|
|
333
|
+
warnExcluded("aster", "market(s) without a mark price", unpriced);
|
|
334
|
+
warnExcluded("aster", "market(s) missing from fundingInfo", noInterval);
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
// ── VenuePublicData ──
|
|
338
|
+
function rowsFor(venue, net) {
|
|
339
|
+
switch (venue) {
|
|
340
|
+
case "pacifica": return pacificaRows(net);
|
|
341
|
+
case "hyperliquid": return hyperliquidRows(net);
|
|
342
|
+
case "aster": return asterRows(net);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
export function legacyPublicData(venue) {
|
|
346
|
+
return {
|
|
347
|
+
async fetchMarkets(net) {
|
|
348
|
+
return (await rowsFor(venue, net)).map(r => r.market);
|
|
349
|
+
},
|
|
350
|
+
async fetchFundingRates(net) {
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const { market: m, nextFundingTime } of await rowsFor(venue, net)) {
|
|
353
|
+
if (!m.tradable || m.fundingRate === null)
|
|
354
|
+
continue;
|
|
355
|
+
out.push({
|
|
356
|
+
venue: m.venue,
|
|
357
|
+
venueSymbol: m.venueSymbol,
|
|
358
|
+
symbol: m.symbol,
|
|
359
|
+
canonical: m.canonical,
|
|
360
|
+
rate: m.fundingRate,
|
|
361
|
+
basis: m.fundingRateBasis,
|
|
362
|
+
markPrice: m.markPrice,
|
|
363
|
+
nextFundingTime,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|