perp-cli 0.12.17 → 0.14.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.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 +286 -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 +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- 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 +316 -337
- 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 +219 -6
- package/dist/exchanges/aster.js +1023 -153
- 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 +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -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 +122 -0
- package/dist/exchanges/ondo/funds.js +290 -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 +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- 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 +1383 -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 +34 -0
- package/dist/exchanges/venues/ondo.js +140 -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 +28 -0
- package/dist/exchanges/venues/qfex.js +132 -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 +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -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 +142 -55
- package/skills/perp-cli/references/agent-operations.md +110 -56
- package/skills/perp-cli/references/commands.md +266 -71
- 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
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Composes with HyperliquidAdapter for signing and API access.
|
|
8
8
|
*/
|
|
9
|
-
import type { SpotAdapter, SpotMarketInfo, SpotBalance } from "./spot-interface.js";
|
|
9
|
+
import type { SpotAdapter, SpotMarketInfo, SpotBalance, SpotHoldingPrice, SpotHoldingPricesCapable } from "./spot-interface.js";
|
|
10
|
+
import type { ResolvedSlippage } from "./slippage.js";
|
|
10
11
|
import type { HyperliquidAdapter } from "./hyperliquid.js";
|
|
11
|
-
export declare class HyperliquidSpotAdapter implements SpotAdapter {
|
|
12
|
+
export declare class HyperliquidSpotAdapter implements SpotAdapter, SpotHoldingPricesCapable {
|
|
12
13
|
readonly name = "hyperliquid";
|
|
13
14
|
private _hl;
|
|
14
15
|
private _spotAssetMap;
|
|
@@ -16,29 +17,67 @@ export declare class HyperliquidSpotAdapter implements SpotAdapter {
|
|
|
16
17
|
private _spotDecimals;
|
|
17
18
|
private _spotCanonical;
|
|
18
19
|
private _initialized;
|
|
20
|
+
private _outcome;
|
|
19
21
|
private _cachedMetaCtx;
|
|
20
22
|
constructor(hlAdapter: HyperliquidAdapter);
|
|
21
23
|
/** Load spot metadata from the Hyperliquid spot API */
|
|
22
24
|
init(): Promise<void>;
|
|
23
25
|
private _loadSpotMeta;
|
|
24
26
|
getSpotMarkets(): Promise<SpotMarketInfo[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Prices of held HIP-4 outcome tokens (`+<encoding>` balance rows — not spot markets) for valuation:
|
|
29
|
+
* the outcome side's mid in the outcome's quote token (HyperliquidOutcomeAdapter.getBalanceCoinPrices).
|
|
30
|
+
* Other tokens are not returned (they stay unpriced).
|
|
31
|
+
*/
|
|
32
|
+
getHoldingPrices(tokens: readonly string[]): Promise<SpotHoldingPrice[]>;
|
|
25
33
|
getSpotOrderbook(symbol: string): Promise<{
|
|
26
34
|
bids: [string, string][];
|
|
27
35
|
asks: [string, string][];
|
|
28
36
|
}>;
|
|
29
37
|
getSpotBalances(): Promise<SpotBalance[]>;
|
|
30
|
-
|
|
38
|
+
/** Uncached read (bypasses the shared acct: TTL cache) — fill measurement / verification. */
|
|
39
|
+
getSpotBalancesFresh(): Promise<SpotBalance[]>;
|
|
40
|
+
private _spotBalances;
|
|
41
|
+
spotMarketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
|
|
42
|
+
slippage: ResolvedSlippage;
|
|
43
|
+
}): Promise<unknown>;
|
|
44
|
+
/** Definite IOC outcome of a raw spot order response (same contract as HyperliquidAdapter.iocOutcomeOf). */
|
|
45
|
+
iocOutcomeOf(raw: unknown): {
|
|
46
|
+
filledSize: string;
|
|
47
|
+
final: true;
|
|
48
|
+
} | null;
|
|
31
49
|
spotLimitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
|
|
32
50
|
tif?: string;
|
|
33
51
|
}): Promise<unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Open spot orders (info `openOrders`, spot coins only: "@<spotIndex>" or "PURR/USDC").
|
|
54
|
+
* Symbols are "BASE/QUOTE" from spot meta; a coin missing from meta → EXCHANGE_ERROR (never guessed).
|
|
55
|
+
*/
|
|
56
|
+
getSpotOpenOrders(symbol: string | null): Promise<Array<{
|
|
57
|
+
orderId: string;
|
|
58
|
+
symbol: string;
|
|
59
|
+
side: "buy" | "sell";
|
|
60
|
+
price: string;
|
|
61
|
+
size: string;
|
|
62
|
+
filled: string;
|
|
63
|
+
}>>;
|
|
34
64
|
spotCancelOrder(symbol: string, orderId: string): Promise<unknown>;
|
|
35
65
|
/**
|
|
36
66
|
* Transfer USDC between perp and spot accounts.
|
|
37
|
-
* HL uses separate balances — spot buys require USDC in the spot account.
|
|
38
|
-
* Uses SDK's transferBetweenSpotAndPerp (userSignedAction, not L1 action).
|
|
67
|
+
* HL uses separate balances (standard mode) — spot buys require USDC in the spot account.
|
|
39
68
|
*/
|
|
40
69
|
transferUsdcToSpot(amount: number): Promise<unknown>;
|
|
41
70
|
transferUsdcToPerp(amount: number): Promise<unknown>;
|
|
71
|
+
/**
|
|
72
|
+
* Spot↔perp USDC move by the active signer tier: an agent signs the L1
|
|
73
|
+
* `agentSendAsset` (agents cannot sign user-signed actions); the master signs
|
|
74
|
+
* `usdClassTransfer`. No signer → NO_SIGNER_AVAILABLE from the adapter.
|
|
75
|
+
*/
|
|
76
|
+
private _moveUsdc;
|
|
77
|
+
/** SpotTransferCapable (§12.4 #4): USDC only; an `err` response is thrown, not returned. */
|
|
78
|
+
transferToSpot(amount: string, asset: string): Promise<unknown>;
|
|
79
|
+
transferToPerp(amount: string, asset: string): Promise<unknown>;
|
|
80
|
+
private _checkedTransfer;
|
|
42
81
|
/** Get the spot asset index (without the 10000 offset) */
|
|
43
82
|
getSpotIndex(base: string): number | undefined;
|
|
44
83
|
/** Get spot size decimals for a base token — from API data, not hardcoded */
|
|
@@ -6,19 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Composes with HyperliquidAdapter for signing and API access.
|
|
8
8
|
*/
|
|
9
|
+
import { HyperliquidOutcomeAdapter } from "./hyperliquid-outcome.js";
|
|
9
10
|
import { PERP_TO_SPOT_MAP, SPOT_PERP_TOKEN_MAP } from "./spot-interface.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return Number(sig5.toFixed(priceDec)).toString();
|
|
18
|
-
}
|
|
19
|
-
/** HL official size rounding */
|
|
20
|
-
function hlRoundSize(sz, szDecimals) {
|
|
21
|
-
return sz.toFixed(szDecimals);
|
|
11
|
+
import { assertHlAppliedBps, hlIocOutcome, hlProtectionPrice, hlRequirePriceOnGrid, hlSizeStep, hlTif } from "./hyperliquid.js";
|
|
12
|
+
import { floorSize } from "./precision.js";
|
|
13
|
+
import { PerpError, isDefiniteOrderFailure, isTransportFailure, orderNotSent, orderOutcomeUnknown } from "../errors.js";
|
|
14
|
+
import { invalidateCache } from "../cache.js";
|
|
15
|
+
/** HL size: floored to the szDecimals lot (never rounded up past the request); 0 → SIZE_TOO_SMALL. */
|
|
16
|
+
function hlRoundSize(size, szDecimals) {
|
|
17
|
+
return floorSize(size, hlSizeStep(szDecimals));
|
|
22
18
|
}
|
|
23
19
|
export class HyperliquidSpotAdapter {
|
|
24
20
|
name = "hyperliquid";
|
|
@@ -28,6 +24,7 @@ export class HyperliquidSpotAdapter {
|
|
|
28
24
|
_spotDecimals = new Map();
|
|
29
25
|
_spotCanonical = new Map(); // "PURR" → true, "UBTC" → false
|
|
30
26
|
_initialized = false;
|
|
27
|
+
_outcome = null;
|
|
31
28
|
_cachedMetaCtx = null;
|
|
32
29
|
constructor(hlAdapter) {
|
|
33
30
|
this._hl = hlAdapter;
|
|
@@ -72,10 +69,11 @@ export class HyperliquidSpotAdapter {
|
|
|
72
69
|
continue;
|
|
73
70
|
this._spotAssetMap.set(key, u.index);
|
|
74
71
|
this._spotAssetReverse.set(u.index, key);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
// Rule #2: no assumed szDecimals — a token without one has no decimals entry and
|
|
73
|
+
// its orders are refused (SYMBOL_NOT_FOUND) instead of being sized on a guess.
|
|
74
|
+
const szDecimals = tokenSzDecimals.get(baseTokenIndex);
|
|
75
|
+
if (szDecimals !== undefined)
|
|
76
|
+
this._spotDecimals.set(key, { size: szDecimals, price: Math.max(0, 8 - szDecimals) }); // HL spot price grid: max 8 − szDecimals decimals
|
|
79
77
|
this._spotCanonical.set(key, u.isCanonical === true);
|
|
80
78
|
}
|
|
81
79
|
}
|
|
@@ -85,48 +83,67 @@ export class HyperliquidSpotAdapter {
|
|
|
85
83
|
}
|
|
86
84
|
async getSpotMarkets() {
|
|
87
85
|
await this.init();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const tokenSzDec = new Map();
|
|
96
|
-
for (const t of metaCtx[0].tokens ?? []) {
|
|
97
|
-
tokenNames.set(t.index, t.name);
|
|
98
|
-
if (t.szDecimals !== undefined)
|
|
99
|
-
tokenSzDec.set(t.index, t.szDecimals);
|
|
100
|
-
}
|
|
101
|
-
// Fetch accurate mid prices from allMids (markPx in metaCtx is unreliable for spot)
|
|
102
|
-
const allMids = await this._infoPost({ type: "allMids" });
|
|
103
|
-
const usdcTokenIndex = metaCtx[0].tokens?.find(t => t.name === "USDC")?.index ?? 0;
|
|
104
|
-
const markets = [];
|
|
105
|
-
for (const u of metaCtx[0].universe) {
|
|
106
|
-
// Only USDC-quoted pairs
|
|
107
|
-
if (u.tokens[1] !== usdcTokenIndex)
|
|
108
|
-
continue;
|
|
109
|
-
const baseToken = tokenNames.get(u.tokens[0]) ?? "";
|
|
110
|
-
if (!baseToken)
|
|
111
|
-
continue;
|
|
112
|
-
// Price from allMids using universe.name (e.g., "PURR/USDC" or "@107")
|
|
113
|
-
const midPrice = allMids[u.name] ?? "0";
|
|
114
|
-
const spotDec = this._spotDecimals.get(baseToken.toUpperCase());
|
|
115
|
-
markets.push({
|
|
116
|
-
symbol: `${baseToken}/USDC`,
|
|
117
|
-
baseToken,
|
|
118
|
-
quoteToken: "USDC",
|
|
119
|
-
markPrice: midPrice,
|
|
120
|
-
volume24h: "0",
|
|
121
|
-
sizeDecimals: spotDec?.size ?? tokenSzDec.get(u.tokens[0]) ?? 2,
|
|
122
|
-
priceDecimals: spotDec?.price ?? 6,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return markets;
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
86
|
+
// Rule #2: no catch→[]. A failed spotMetaAndAssetCtxs/allMids fetch must throw,
|
|
87
|
+
// not be coerced to an empty markets list (which reads as "this venue has no spot
|
|
88
|
+
// markets" and silently disables spot-perp arb discovery/sizing).
|
|
89
|
+
// Reuse cached metaCtx from init, or fetch fresh if cache expired.
|
|
90
|
+
const metaCtx = this._cachedMetaCtx
|
|
91
|
+
?? await this._infoPost({ type: "spotMetaAndAssetCtxs" });
|
|
92
|
+
if (!metaCtx?.[0]?.universe)
|
|
128
93
|
return [];
|
|
94
|
+
const tokenNames = new Map();
|
|
95
|
+
const tokenSzDec = new Map();
|
|
96
|
+
for (const t of metaCtx[0].tokens ?? []) {
|
|
97
|
+
tokenNames.set(t.index, t.name);
|
|
98
|
+
if (t.szDecimals !== undefined)
|
|
99
|
+
tokenSzDec.set(t.index, t.szDecimals);
|
|
100
|
+
}
|
|
101
|
+
// Fetch accurate mid prices from allMids (markPx in metaCtx is unreliable for spot)
|
|
102
|
+
const allMids = await this._infoPost({ type: "allMids" });
|
|
103
|
+
const usdcTokenIndex = metaCtx[0].tokens?.find(t => t.name === "USDC")?.index ?? 0;
|
|
104
|
+
const markets = [];
|
|
105
|
+
for (const [ui, u] of metaCtx[0].universe.entries()) {
|
|
106
|
+
// Only USDC-quoted pairs
|
|
107
|
+
if (u.tokens[1] !== usdcTokenIndex)
|
|
108
|
+
continue;
|
|
109
|
+
const baseToken = tokenNames.get(u.tokens[0]) ?? "";
|
|
110
|
+
if (!baseToken)
|
|
111
|
+
continue;
|
|
112
|
+
// Price from allMids using universe.name (e.g., "PURR/USDC" or "@107")
|
|
113
|
+
const midPrice = allMids[u.name] ?? "0";
|
|
114
|
+
const spotDec = this._spotDecimals.get(baseToken.toUpperCase());
|
|
115
|
+
const sizeDecimals = spotDec?.size ?? tokenSzDec.get(u.tokens[0]);
|
|
116
|
+
// Rule #2: a market without a published szDecimals is not listed (never sized on a guess).
|
|
117
|
+
if (sizeDecimals === undefined)
|
|
118
|
+
continue;
|
|
119
|
+
markets.push({
|
|
120
|
+
symbol: `${baseToken}/USDC`,
|
|
121
|
+
baseToken,
|
|
122
|
+
quoteToken: "USDC",
|
|
123
|
+
markPrice: midPrice,
|
|
124
|
+
// asset ctxs are index-aligned with universe; dayNtlVlm = 24h quote volume
|
|
125
|
+
volume24h: metaCtx[1]?.[ui]?.dayNtlVlm !== undefined ? String(metaCtx[1][ui].dayNtlVlm) : null,
|
|
126
|
+
sizeDecimals,
|
|
127
|
+
// HL spot price grid: at most 8 − szDecimals decimals (venue rule) — derived, never a fixed guess.
|
|
128
|
+
priceDecimals: spotDec !== undefined ? spotDec.price : Math.max(0, 8 - sizeDecimals),
|
|
129
|
+
});
|
|
129
130
|
}
|
|
131
|
+
return markets;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Prices of held HIP-4 outcome tokens (`+<encoding>` balance rows — not spot markets) for valuation:
|
|
135
|
+
* the outcome side's mid in the outcome's quote token (HyperliquidOutcomeAdapter.getBalanceCoinPrices).
|
|
136
|
+
* Other tokens are not returned (they stay unpriced).
|
|
137
|
+
*/
|
|
138
|
+
async getHoldingPrices(tokens) {
|
|
139
|
+
const coins = tokens.filter(t => /^\+\d+$/.test(t));
|
|
140
|
+
if (coins.length === 0)
|
|
141
|
+
return [];
|
|
142
|
+
if (this._outcome === null)
|
|
143
|
+
this._outcome = new HyperliquidOutcomeAdapter(this._hl);
|
|
144
|
+
return (await this._outcome.getBalanceCoinPrices(coins)).map(p => ({
|
|
145
|
+
token: p.coin, quoteToken: p.quoteToken, markPrice: p.quoteToken === null ? null : p.mid,
|
|
146
|
+
}));
|
|
130
147
|
}
|
|
131
148
|
async getSpotOrderbook(symbol) {
|
|
132
149
|
await this.init();
|
|
@@ -151,86 +168,245 @@ export class HyperliquidSpotAdapter {
|
|
|
151
168
|
};
|
|
152
169
|
}
|
|
153
170
|
async getSpotBalances() {
|
|
171
|
+
return this._spotBalances(false);
|
|
172
|
+
}
|
|
173
|
+
/** Uncached read (bypasses the shared acct: TTL cache) — fill measurement / verification. */
|
|
174
|
+
async getSpotBalancesFresh() {
|
|
175
|
+
return this._spotBalances(true);
|
|
176
|
+
}
|
|
177
|
+
async _spotBalances(fresh) {
|
|
178
|
+
// Rule #2: no catch→[]. A failed spotClearinghouseState fetch must surface
|
|
179
|
+
// honestly — an empty list is indistinguishable from a genuinely empty wallet
|
|
180
|
+
// and silently drives wrong USDC-transfer sizing / false post-fill verification
|
|
181
|
+
// in the funding-arb bot (and a misleading empty holdings table).
|
|
182
|
+
// Reuse cached spot clearinghouse state (shared with getBalance() — saves 1 API call).
|
|
183
|
+
const state = await this._hl._getSpotClearinghouseState({ fresh });
|
|
184
|
+
const balances = (state?.balances ?? []);
|
|
185
|
+
// Unified HL accounts: spot USDC IS the perp margin (§12.4 #8) — mark it shared.
|
|
186
|
+
// HIP-3 dex-scoped adapters report dex equity, which never contains spot USDC.
|
|
187
|
+
const unified = this._hl.isUnifiedAccount === true && this._hl.dex === "";
|
|
188
|
+
return balances.map((b) => ({
|
|
189
|
+
token: String(b.coin ?? ""),
|
|
190
|
+
total: String(b.total ?? "0"),
|
|
191
|
+
available: String(Number(b.total ?? 0) - Number(b.hold ?? 0)),
|
|
192
|
+
held: String(b.hold ?? "0"),
|
|
193
|
+
entryNtl: b.entryNtl !== undefined ? String(b.entryNtl) : undefined,
|
|
194
|
+
shared: unified && String(b.coin ?? "") === "USDC",
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
async spotMarketOrder(symbol, side, size, opts) {
|
|
198
|
+
const what = `spot ${side} ${size} ${symbol}`;
|
|
199
|
+
// Pre-send (resolution, reference price): a failure is a definite "not placed".
|
|
200
|
+
let base;
|
|
201
|
+
let order;
|
|
202
|
+
let protection;
|
|
154
203
|
try {
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
204
|
+
await this.init();
|
|
205
|
+
const resolved = this._resolveBase(symbol);
|
|
206
|
+
base = resolved.base;
|
|
207
|
+
// Reference = l2Book mid (the same single declared source as HL perps, §13.10 #5).
|
|
208
|
+
// Either side missing → PRICE_STALE (no one-sided "mid").
|
|
209
|
+
const book = await this.getSpotOrderbook(symbol);
|
|
210
|
+
const bestAsk = Number(book.asks[0]?.[0]);
|
|
211
|
+
const bestBid = Number(book.bids[0]?.[0]);
|
|
212
|
+
if (!(bestAsk > 0) || !(bestBid > 0) || !Number.isFinite(bestAsk) || !Number.isFinite(bestBid)) {
|
|
213
|
+
throw new PerpError("PRICE_STALE", `Hyperliquid spot ${base}: no two-sided l2Book to derive the mid reference price`, { exchange: "hyperliquid" });
|
|
214
|
+
}
|
|
215
|
+
const midPrice = (bestAsk + bestBid) / 2;
|
|
216
|
+
// Protection = mid ± the resolved slippage bound (design §13.2), aligned toward
|
|
217
|
+
// crossing on HL's spot grid (5 sig figs, max 8 − szDecimals decimals).
|
|
218
|
+
const isBuy = side === "buy";
|
|
219
|
+
const szDec = this.getSpotDecimals(base).size;
|
|
220
|
+
const { protectionPrice, appliedBps } = hlProtectionPrice(midPrice, side, opts.slippage.bps, szDec, 8);
|
|
221
|
+
await assertHlAppliedBps(appliedBps, `spot ${base} protection ${protectionPrice} vs reference ${midPrice}`);
|
|
222
|
+
protection = { referencePrice: String(midPrice), protectionPrice, appliedBps };
|
|
223
|
+
order = { assetIndex: 10000 + resolved.spotIndex, isBuy, price: protectionPrice, size: hlRoundSize(size, szDec) };
|
|
165
224
|
}
|
|
166
|
-
catch {
|
|
167
|
-
|
|
225
|
+
catch (e) {
|
|
226
|
+
throw orderNotSent("hyperliquid", what, e);
|
|
227
|
+
}
|
|
228
|
+
// Send. A transport failure / unreadable body after the request may have placed
|
|
229
|
+
// the order → ORDER_PENDING. Any other error is the venue's explicit
|
|
230
|
+
// {"status":"err"} rejection or a signing failure before the request → definite.
|
|
231
|
+
let result;
|
|
232
|
+
try {
|
|
233
|
+
result = await this._rawPlaceSpotOrder({ ...order, orderType: { limit: { tif: "Ioc" } } });
|
|
168
234
|
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
if (isTransportFailure(e) || (e instanceof Error && e.message.startsWith("HL exchange API error ("))) {
|
|
237
|
+
throw orderOutcomeUnknown("hyperliquid", what, e);
|
|
238
|
+
}
|
|
239
|
+
if (e instanceof PerpError)
|
|
240
|
+
throw isDefiniteOrderFailure(e) ? e : orderOutcomeUnknown("hyperliquid", what, e);
|
|
241
|
+
throw new PerpError("ORDER_REJECTED", `hyperliquid ${what} rejected: ${e instanceof Error ? e.message : String(e)}`, { exchange: "hyperliquid" });
|
|
242
|
+
}
|
|
243
|
+
// Validate fill — HL returns status "ok" even for 0-fill IoC orders
|
|
244
|
+
this._validateOrderFill(result, base, side, order.size);
|
|
245
|
+
// The protection sent rides along (`protection`, read by the CLI's uniform spot output);
|
|
246
|
+
// `sentSize` = the size actually sent (floored to szDecimals).
|
|
247
|
+
return { ...result, protection, sentSize: order.size };
|
|
169
248
|
}
|
|
170
|
-
|
|
249
|
+
/** Definite IOC outcome of a raw spot order response (same contract as HyperliquidAdapter.iocOutcomeOf). */
|
|
250
|
+
iocOutcomeOf(raw) {
|
|
251
|
+
return hlIocOutcome(raw);
|
|
252
|
+
}
|
|
253
|
+
async spotLimitOrder(symbol, side, price, size, opts) {
|
|
171
254
|
await this.init();
|
|
172
255
|
const { base, spotIndex } = this._resolveBase(symbol);
|
|
173
256
|
const assetIndex = 10000 + spotIndex;
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
257
|
+
// TIF aliases (§13.3): GTC/IOC/ALO or native spellings; FOK → CAPABILITY_UNSUPPORTED.
|
|
258
|
+
const tif = hlTif(opts?.tif ?? "GTC");
|
|
259
|
+
const szDec = this.getSpotDecimals(base).size;
|
|
260
|
+
const what = `spot limit ${side} ${size} ${base} @ ${price}`;
|
|
261
|
+
// Pre-send: off-grid price → INVALID_TICK (refused, never rounded); size floored to the lot.
|
|
262
|
+
const sentPrice = hlRequirePriceOnGrid(price, szDec, 8, "spot limit price");
|
|
263
|
+
const sentSize = hlRoundSize(size, szDec);
|
|
264
|
+
// Same send contract as spotMarketOrder: a transport failure after the request may
|
|
265
|
+
// have placed the order → ORDER_PENDING; a {"status":"err"} body → definite rejection.
|
|
266
|
+
let result;
|
|
267
|
+
try {
|
|
268
|
+
result = await this._rawPlaceSpotOrder({
|
|
269
|
+
assetIndex,
|
|
270
|
+
isBuy: side === "buy",
|
|
271
|
+
price: sentPrice,
|
|
272
|
+
size: sentSize,
|
|
273
|
+
orderType: { limit: { tif } },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
if (isTransportFailure(e) || (e instanceof Error && e.message.startsWith("HL exchange API error ("))) {
|
|
278
|
+
throw orderOutcomeUnknown("hyperliquid", what, e);
|
|
279
|
+
}
|
|
280
|
+
if (e instanceof PerpError)
|
|
281
|
+
throw isDefiniteOrderFailure(e) ? e : orderOutcomeUnknown("hyperliquid", what, e);
|
|
282
|
+
throw new PerpError("ORDER_REJECTED", `hyperliquid ${what} rejected: ${e instanceof Error ? e.message : String(e)}`, { exchange: "hyperliquid" });
|
|
283
|
+
}
|
|
284
|
+
// Venue-final like perps: statuses[0] = resting {oid} (open) | filled {totalSz, avgPx, oid}
|
|
285
|
+
// | error (e.g. "Insufficient spot balance asset=10000") → ORDER_REJECTED, nothing placed.
|
|
286
|
+
// A missing status after an accepted request → EXCHANGE_ERROR (HyperliquidAdapter._validateVenueAck).
|
|
287
|
+
this._hl._validateVenueAck(result, what);
|
|
196
288
|
return result;
|
|
197
289
|
}
|
|
198
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Open spot orders (info `openOrders`, spot coins only: "@<spotIndex>" or "PURR/USDC").
|
|
292
|
+
* Symbols are "BASE/QUOTE" from spot meta; a coin missing from meta → EXCHANGE_ERROR (never guessed).
|
|
293
|
+
*/
|
|
294
|
+
async getSpotOpenOrders(symbol) {
|
|
199
295
|
await this.init();
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
296
|
+
const meta = this._cachedMetaCtx?.[0];
|
|
297
|
+
if (meta === undefined)
|
|
298
|
+
throw new PerpError("EXCHANGE_ERROR", "Hyperliquid spot meta not loaded", { exchange: "hyperliquid" });
|
|
299
|
+
const tokenName = new Map(meta.tokens.map(t => [t.index, t.name]));
|
|
300
|
+
const pairName = (spotIndex) => {
|
|
301
|
+
const u = meta.universe.find(x => x.index === spotIndex);
|
|
302
|
+
const b = u === undefined ? undefined : tokenName.get(u.tokens[0]);
|
|
303
|
+
const q = u === undefined ? undefined : tokenName.get(u.tokens[1]);
|
|
304
|
+
if (b === undefined || q === undefined)
|
|
305
|
+
throw new PerpError("EXCHANGE_ERROR", `Hyperliquid spot open order on unknown spot index ${spotIndex}`, { exchange: "hyperliquid" });
|
|
306
|
+
return `${b.toUpperCase()}/${q.toUpperCase()}`;
|
|
307
|
+
};
|
|
308
|
+
const rows = await this._infoPost({ type: "openOrders", user: this._hl.address });
|
|
309
|
+
if (!Array.isArray(rows))
|
|
310
|
+
throw new PerpError("EXCHANGE_ERROR", "Hyperliquid openOrders did not return an array", { exchange: "hyperliquid" });
|
|
311
|
+
const wantBase = symbol === null ? null : symbol.toUpperCase().split("/")[0];
|
|
312
|
+
const out = [];
|
|
313
|
+
for (const r of rows) {
|
|
314
|
+
const coin = String(r.coin ?? "");
|
|
315
|
+
let pair;
|
|
316
|
+
if (/^@\d+$/.test(coin))
|
|
317
|
+
pair = pairName(Number(coin.slice(1)));
|
|
318
|
+
else if (coin.includes("/"))
|
|
319
|
+
pair = coin.toUpperCase();
|
|
320
|
+
else
|
|
321
|
+
continue; // perp order
|
|
322
|
+
if (wantBase !== null && pair.split("/")[0] !== wantBase)
|
|
323
|
+
continue;
|
|
324
|
+
const orig = String(r.origSz ?? "");
|
|
325
|
+
const left = String(r.sz ?? "");
|
|
326
|
+
if (!/^\d+(\.\d+)?$/.test(orig) || !/^\d+(\.\d+)?$/.test(left)) {
|
|
327
|
+
throw new PerpError("EXCHANGE_ERROR", `Hyperliquid open order ${String(r.oid)}: unexpected size fields`, { exchange: "hyperliquid" });
|
|
328
|
+
}
|
|
329
|
+
const dec = Math.max((orig.split(".")[1] ?? "").length, (left.split(".")[1] ?? "").length);
|
|
330
|
+
out.push({
|
|
331
|
+
orderId: String(r.oid),
|
|
332
|
+
symbol: pair,
|
|
333
|
+
side: r.side === "B" ? "buy" : "sell",
|
|
334
|
+
price: String(r.limitPx),
|
|
335
|
+
size: orig,
|
|
336
|
+
filled: (Number(orig) - Number(left)).toFixed(dec),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
214
340
|
}
|
|
215
341
|
async spotCancelOrder(symbol, orderId) {
|
|
216
342
|
await this.init();
|
|
217
343
|
const { spotIndex } = this._resolveBase(symbol);
|
|
218
344
|
const assetIndex = 10000 + spotIndex;
|
|
345
|
+
const oid = Number(orderId);
|
|
346
|
+
if (!/^\d+$/.test(orderId) || !Number.isSafeInteger(oid)) {
|
|
347
|
+
throw new PerpError("INVALID_PARAMS", `Hyperliquid order id must be a decimal integer (got "${orderId}"); nothing was cancelled`, {
|
|
348
|
+
exchange: "hyperliquid", remediation: `Look up the order id: perp -e hyperliquid trade spot-orders ${symbol}`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
// statuses[0] "success" → cancelled; {error} → ORDER_REJECTED (trade.ts spotCancelResult).
|
|
219
352
|
return this._signAndSendAction({
|
|
220
353
|
type: "cancel",
|
|
221
|
-
cancels: [{ a: assetIndex, o:
|
|
354
|
+
cancels: [{ a: assetIndex, o: oid }],
|
|
222
355
|
});
|
|
223
356
|
}
|
|
224
357
|
/**
|
|
225
358
|
* Transfer USDC between perp and spot accounts.
|
|
226
|
-
* HL uses separate balances — spot buys require USDC in the spot account.
|
|
227
|
-
* Uses SDK's transferBetweenSpotAndPerp (userSignedAction, not L1 action).
|
|
359
|
+
* HL uses separate balances (standard mode) — spot buys require USDC in the spot account.
|
|
228
360
|
*/
|
|
229
361
|
async transferUsdcToSpot(amount) {
|
|
230
|
-
return this.
|
|
362
|
+
return this._moveUsdc(String(amount), false);
|
|
231
363
|
}
|
|
232
364
|
async transferUsdcToPerp(amount) {
|
|
233
|
-
return this.
|
|
365
|
+
return this._moveUsdc(String(amount), true);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Spot↔perp USDC move by the active signer tier: an agent signs the L1
|
|
369
|
+
* `agentSendAsset` (agents cannot sign user-signed actions); the master signs
|
|
370
|
+
* `usdClassTransfer`. No signer → NO_SIGNER_AVAILABLE from the adapter.
|
|
371
|
+
*/
|
|
372
|
+
async _moveUsdc(amount, toPerp) {
|
|
373
|
+
// The master's usdClassTransfer moves USDC between spot and the NATIVE perp dex only. On a
|
|
374
|
+
// HIP-3 dex adapter it would fund the wrong dex, so it is refused (the agent's
|
|
375
|
+
// agentSendAsset names the dex explicitly).
|
|
376
|
+
if (this._hl.dex !== "" && this._hl.activeSignerTier !== "agent") {
|
|
377
|
+
throw new PerpError("NOT_IMPLEMENTED", `Hyperliquid spot↔perp transfer for HIP-3 dex "${this._hl.dex}" is not implemented for the master wallet (usdClassTransfer only funds the native perp dex)`, {
|
|
378
|
+
exchange: "hyperliquid",
|
|
379
|
+
remediation: `Use an agent wallet (perp wallet agent approve hyperliquid), or move the collateral to dex "${this._hl.dex}" in the Hyperliquid app`,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
return await (this._hl.activeSignerTier === "agent"
|
|
384
|
+
? this._hl.agentSendAsset(amount, toPerp)
|
|
385
|
+
: this._hl.usdClassTransfer(amount, toPerp));
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
invalidateCache("acct"); // perp and spot balances both moved
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/** SpotTransferCapable (§12.4 #4): USDC only; an `err` response is thrown, not returned. */
|
|
392
|
+
async transferToSpot(amount, asset) {
|
|
393
|
+
return this._checkedTransfer(amount, asset, false);
|
|
394
|
+
}
|
|
395
|
+
async transferToPerp(amount, asset) {
|
|
396
|
+
return this._checkedTransfer(amount, asset, true);
|
|
397
|
+
}
|
|
398
|
+
async _checkedTransfer(amount, asset, toPerp) {
|
|
399
|
+
if (asset !== "USDC") {
|
|
400
|
+
throw new PerpError("INVALID_PARAMS", `Hyperliquid spot↔perp transfer only moves USDC (got ${asset})`, { exchange: "hyperliquid" });
|
|
401
|
+
}
|
|
402
|
+
const n = Number(amount);
|
|
403
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
404
|
+
throw new PerpError("INVALID_PARAMS", `Transfer amount must be positive (got ${amount})`);
|
|
405
|
+
const res = await this._moveUsdc(amount, toPerp);
|
|
406
|
+
if (res?.status === "err") {
|
|
407
|
+
throw new PerpError("EXCHANGE_ERROR", `Hyperliquid ${toPerp ? "spot→perp" : "perp→spot"} transfer rejected: ${String(res.response)}`, { exchange: "hyperliquid" });
|
|
408
|
+
}
|
|
409
|
+
return res;
|
|
234
410
|
}
|
|
235
411
|
/** Get the spot asset index (without the 10000 offset) */
|
|
236
412
|
getSpotIndex(base) {
|
|
@@ -241,7 +417,10 @@ export class HyperliquidSpotAdapter {
|
|
|
241
417
|
const dec = this._spotDecimals.get(base.toUpperCase());
|
|
242
418
|
if (dec)
|
|
243
419
|
return dec;
|
|
244
|
-
throw new
|
|
420
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Hyperliquid spot ${base.toUpperCase()}: no szDecimals in spot meta — refusing to size the order on a guess`, {
|
|
421
|
+
exchange: "hyperliquid",
|
|
422
|
+
remediation: "Retry (spot meta may not have loaded); if it persists the token is not tradeable via this CLI",
|
|
423
|
+
});
|
|
245
424
|
}
|
|
246
425
|
/**
|
|
247
426
|
* Resolve a symbol to its actual spot base token and index.
|
|
@@ -282,16 +461,21 @@ export class HyperliquidSpotAdapter {
|
|
|
282
461
|
const r = result;
|
|
283
462
|
const statuses = r?.response?.data?.statuses;
|
|
284
463
|
if (!statuses || statuses.length === 0) {
|
|
285
|
-
|
|
464
|
+
// Accepted request without a per-order status: the outcome is unknown.
|
|
465
|
+
throw orderOutcomeUnknown("hyperliquid", `spot ${side} ${base}`, new Error("no order status in response"));
|
|
286
466
|
}
|
|
287
467
|
const st = statuses[0];
|
|
288
468
|
if (st.error) {
|
|
289
|
-
throw new
|
|
469
|
+
throw new PerpError("ORDER_REJECTED", `Spot ${side} ${base}: ${st.error}`, { exchange: "hyperliquid" });
|
|
290
470
|
}
|
|
291
471
|
const filled = st.filled;
|
|
472
|
+
if (st.resting !== undefined) {
|
|
473
|
+
// An IoC never rests; a resting status means the order is live — do not resubmit.
|
|
474
|
+
throw orderOutcomeUnknown("hyperliquid", `spot ${side} ${base}`, new Error("IoC order reported resting"));
|
|
475
|
+
}
|
|
292
476
|
if (!filled || Number(filled.totalSz ?? 0) === 0) {
|
|
293
|
-
//
|
|
294
|
-
throw new
|
|
477
|
+
// 0-fill IoC: cancelled by the venue, nothing filled (definite).
|
|
478
|
+
throw new PerpError("ORDER_REJECTED", `Spot ${side} ${base}: order not filled (0 of ${requestedSize} ${base}). Check price/liquidity.`, { exchange: "hyperliquid" });
|
|
295
479
|
}
|
|
296
480
|
}
|
|
297
481
|
async _rawPlaceSpotOrder(opts) {
|
|
@@ -317,7 +501,14 @@ export class HyperliquidSpotAdapter {
|
|
|
317
501
|
return this._signAndSendAction(action);
|
|
318
502
|
}
|
|
319
503
|
async _signAndSendAction(action) {
|
|
320
|
-
|
|
504
|
+
try {
|
|
505
|
+
return await this._hl.exchangeAction(action);
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
// Spot balances / open orders changed (or may have): later reads in this or another
|
|
509
|
+
// process (the acct: disk cache is shared) must not serve the pre-order snapshot.
|
|
510
|
+
invalidateCache("acct");
|
|
511
|
+
}
|
|
321
512
|
}
|
|
322
513
|
async _infoPost(body) {
|
|
323
514
|
const baseUrl = this._hl.isTestnet
|