perp-cli 0.12.15 → 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 +278 -31
- 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 +74 -84
- 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 +157 -682
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +14 -0
- package/dist/landing.js +36 -0
- 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 +338 -134
- package/skills/perp-cli/references/agent-operations.md +181 -92
- package/skills/perp-cli/references/commands.md +475 -132
- package/skills/perp-cli/references/strategies.md +44 -38
- package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
- package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
- 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 +33 -16
- 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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TradingV2 — normalized order entry for venues added under the venue
|
|
3
|
+
* abstraction (docs/DESIGN-venue-abstraction.md §3 L3, §10.7, §10.10).
|
|
4
|
+
*
|
|
5
|
+
* Method names deliberately do not overlap the legacy ExchangeAdapter trading
|
|
6
|
+
* methods, so an adapter can implement both without overloads.
|
|
7
|
+
*/
|
|
8
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
9
|
+
import { PerpError } from "../errors.js";
|
|
10
|
+
import { orderRefOf } from "./venue-types.js";
|
|
11
|
+
import { requirePositive } from "./precision.js";
|
|
12
|
+
export function isTradingV2(a) {
|
|
13
|
+
const r = a;
|
|
14
|
+
return !!r
|
|
15
|
+
&& typeof r.placeOrder === "function"
|
|
16
|
+
&& typeof r.cancelByRef === "function"
|
|
17
|
+
&& typeof r.getOrderStatus === "function"
|
|
18
|
+
&& typeof r.getVenueMarkets === "function";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Uniform "not supported" response (design §13.6, §13.10 #13): CAPABILITY_UNSUPPORTED
|
|
22
|
+
* with details { venue, feature, reason } and an alternative in `remediation`.
|
|
23
|
+
* `venue` is a venue id or display name; `feature` a short capability name
|
|
24
|
+
* ("limitOrder", "fok", "postOnly", "amend", "trading", "orderbook", ...).
|
|
25
|
+
*/
|
|
26
|
+
export function unsupported(venue, feature, reason, remediation) {
|
|
27
|
+
return new PerpError("CAPABILITY_UNSUPPORTED", `${venue} does not support ${feature} — ${reason}`, {
|
|
28
|
+
venue, feature, reason, remediation,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* An adapter whose reduce-only orders are currently refused (a venue whose reduce-only wire
|
|
33
|
+
* format is not set; none today) reports why. Callers that will need a reduce-only order
|
|
34
|
+
* LATER in a flow ask first — an entry carrying --tp/--sl is refused BEFORE the entry is sent
|
|
35
|
+
* (never "entry filled, protection failed"), and a close is refused before any read. Adapters
|
|
36
|
+
* without the method have no such gate.
|
|
37
|
+
*/
|
|
38
|
+
export function reduceOnlyGateOf(adapter) {
|
|
39
|
+
const fn = adapter?.reduceOnlyGate;
|
|
40
|
+
if (typeof fn !== "function")
|
|
41
|
+
return null;
|
|
42
|
+
const gate = fn.call(adapter);
|
|
43
|
+
return gate instanceof PerpError ? gate : null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An adapter whose STOP orders are currently refused (QFEX until stop acknowledgements can be
|
|
47
|
+
* correlated) reports why. `--tp/--sl` entries and `trade tpsl` ask first, so an entry is never
|
|
48
|
+
* sent when its protective legs would be refused. Adapters without the method have no such gate.
|
|
49
|
+
*/
|
|
50
|
+
export function stopOrderGateOf(adapter) {
|
|
51
|
+
const fn = adapter?.stopOrderGate;
|
|
52
|
+
if (typeof fn !== "function")
|
|
53
|
+
return null;
|
|
54
|
+
const gate = fn.call(adapter);
|
|
55
|
+
return gate instanceof PerpError ? gate : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* An adapter on which UNATTENDED automation is currently refused (QFEX while its stop orders are
|
|
59
|
+
* refused) reports why. Bots, plans, TWAP, DCA, grid, funding-arb, trailing stop and background
|
|
60
|
+
* jobs ask before they start; manual commands never ask. Adapters without the method have no
|
|
61
|
+
* such gate.
|
|
62
|
+
*/
|
|
63
|
+
export function automationGateOf(adapter) {
|
|
64
|
+
const fn = adapter?.automationGate;
|
|
65
|
+
if (typeof fn !== "function")
|
|
66
|
+
return null;
|
|
67
|
+
const gate = fn.call(adapter);
|
|
68
|
+
return gate instanceof PerpError ? gate : null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Refuse to START anything that trades unattended on a venue that cannot close or protect its
|
|
72
|
+
* positions. Bots, plans, TWAP, DCA, grid, funding-arb, trailing stop and background jobs all
|
|
73
|
+
* need a reduce-only order later (close / stop / drawdown exit): on a venue whose reduce-only
|
|
74
|
+
* orders are gated (reduceOnlyGateOf) they would open a position they cannot close, and on a
|
|
75
|
+
* venue that refuses automation (automationGateOf) they would run without a protective exit.
|
|
76
|
+
* Throws the gate before the first order.
|
|
77
|
+
*/
|
|
78
|
+
export function assertCanClosePositions(adapter, what) {
|
|
79
|
+
const reduceOnly = reduceOnlyGateOf(adapter);
|
|
80
|
+
if (reduceOnly !== null)
|
|
81
|
+
throw startRefusal(what, "it could open a position it cannot close", reduceOnly);
|
|
82
|
+
const automation = automationGateOf(adapter);
|
|
83
|
+
if (automation !== null)
|
|
84
|
+
throw startRefusal(what, "it would trade unattended", automation);
|
|
85
|
+
}
|
|
86
|
+
/** The gate's own code / details / remediation, with what was refused and why in front. */
|
|
87
|
+
export function startRefusal(what, why, gate) {
|
|
88
|
+
const remediation = gate.structured.remediation;
|
|
89
|
+
return new PerpError(gate.structured.code, `${what} refused: ${why} — ${gate.message}`, {
|
|
90
|
+
...(gate.structured.details === undefined ? {} : gate.structured.details),
|
|
91
|
+
...(remediation === undefined ? {} : { remediation }),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validate an order request against the venue's declared capabilities BEFORE
|
|
96
|
+
* anything is sent. Throws; never adjusts the request.
|
|
97
|
+
*/
|
|
98
|
+
export function validateOrderRequest(venue, caps, req) {
|
|
99
|
+
const isStop = req.type === "stopMarket" || req.type === "stopLimit";
|
|
100
|
+
const needsPrice = req.type === "limit" || req.type === "stopLimit";
|
|
101
|
+
const isMarketLike = req.type === "market" || req.type === "stopMarket";
|
|
102
|
+
if (needsPrice && req.price === null) {
|
|
103
|
+
throw new PerpError("INVALID_PARAMS", `${req.type} order requires a price`);
|
|
104
|
+
}
|
|
105
|
+
if (isStop && req.triggerPrice === null) {
|
|
106
|
+
throw new PerpError("INVALID_PARAMS", `${req.type} order requires a trigger price`);
|
|
107
|
+
}
|
|
108
|
+
if (!isStop && req.triggerPrice !== null) {
|
|
109
|
+
throw new PerpError("INVALID_PARAMS", `triggerPrice is only valid for stop orders`);
|
|
110
|
+
}
|
|
111
|
+
// A zero / negative / non-decimal price is never sent to the venue.
|
|
112
|
+
if (req.price !== null)
|
|
113
|
+
requirePositive(req.price, "price");
|
|
114
|
+
if (req.triggerPrice !== null)
|
|
115
|
+
requirePositive(req.triggerPrice, "triggerPrice");
|
|
116
|
+
// Callers resolve the common default (GTC, design §13.3) before building the request;
|
|
117
|
+
// a missing TIF here is a programming error, never a user-facing TIF_REQUIRED.
|
|
118
|
+
if (needsPrice && req.tif === null) {
|
|
119
|
+
throw new PerpError("INVALID_PARAMS", `${venue.displayName} ${req.type} order is missing its resolved time-in-force`);
|
|
120
|
+
}
|
|
121
|
+
if (req.tif !== null) {
|
|
122
|
+
if (isMarketLike) {
|
|
123
|
+
throw new PerpError("INVALID_PARAMS", `time-in-force is not valid for ${req.type} orders`);
|
|
124
|
+
}
|
|
125
|
+
if (!caps.timeInForce.includes(req.tif)) {
|
|
126
|
+
throw unsupported(venue.displayName, "timeInForce", `time-in-force ${req.tif} is not offered`, `Use one of: ${caps.timeInForce.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// A reduce-only limit order may only carry the TIFs the venue allows for it (Ondo: IOC only —
|
|
130
|
+
// a resting reduce-only order is refused there); refused before anything is sent.
|
|
131
|
+
if (req.reduceOnly && req.type === "limit" && req.tif !== null && caps.reduceOnlyTif !== null && !caps.reduceOnlyTif.includes(req.tif)) {
|
|
132
|
+
const alternative = `set a take-profit / stop-loss on the position instead: perp trade tpsl ${req.symbol} <long|short> --tp <price>${caps.tpsl !== null && caps.tpsl !== "positionLevel" ? " [--size <qty>]" : ""} -e ${venue.id}`;
|
|
133
|
+
throw unsupported(venue.displayName, "restingReduceOnly", `a reduce-only limit order can only be ${caps.reduceOnlyTif.join(" / ")} (got ${req.tif})`, `Use --tif ${caps.reduceOnlyTif.join(" | ")}, or ${alternative}`);
|
|
134
|
+
}
|
|
135
|
+
// Callers always pass resolveSlippage() output for market orders (§13.10 #1); a
|
|
136
|
+
// missing bound here is a programming error, never "send unbounded".
|
|
137
|
+
if (req.type === "market" && req.slippage === null) {
|
|
138
|
+
throw new PerpError("INVALID_PARAMS", `${venue.displayName} market order is missing its resolved slippage bound`);
|
|
139
|
+
}
|
|
140
|
+
if (req.slippage !== null && (!Number.isFinite(req.slippage.bps) || req.slippage.bps < 1 || req.slippage.bps > venue.maxSlippageBps)) {
|
|
141
|
+
throw new PerpError("INVALID_PARAMS", `slippage ${req.slippage.bps} bps is outside 1..${venue.maxSlippageBps} for ${venue.displayName}`);
|
|
142
|
+
}
|
|
143
|
+
if (caps.orderAck === "async" && req.clientOrderId === null) {
|
|
144
|
+
throw new PerpError("INVALID_PARAMS", `${venue.displayName} acknowledges orders asynchronously; a clientOrderId is required`);
|
|
145
|
+
}
|
|
146
|
+
if (req.clientOrderId !== null && !caps.clientOrderId) {
|
|
147
|
+
throw unsupported(venue.displayName, "clientOrderId", "the venue has no client order ids", "Omit the client order id");
|
|
148
|
+
}
|
|
149
|
+
if (req.tpsl !== null) {
|
|
150
|
+
if (req.tpsl.tp === null && req.tpsl.sl === null) {
|
|
151
|
+
throw new PerpError("INVALID_PARAMS", "tpsl given but neither tp nor sl is set");
|
|
152
|
+
}
|
|
153
|
+
if (caps.tpsl !== "attached" && caps.tpsl !== "batchGroup") {
|
|
154
|
+
throw unsupported(venue.displayName, "tpslAttached", "TP/SL cannot be attached to an entry order", `Place the entry, then run: perp trade tpsl -e ${venue.id}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (isStop && caps.tpsl === "batchGroup" && !req.reduceOnly) {
|
|
158
|
+
throw unsupported(venue.displayName, "openingStop", "stop orders cannot open a position", "Stop orders on this venue must be reduce-only");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Common TIF aliases (design §13.3): a common name the venue lists under its own
|
|
163
|
+
* spelling, and venue spellings accepted for the common name. Only same-meaning
|
|
164
|
+
* pairs: GTC ↔ GTT (resting until cancelled / venue expiry), ALO ↔ GTX (post-only).
|
|
165
|
+
*/
|
|
166
|
+
const TIF_ALIASES = {
|
|
167
|
+
GTC: ["GTT"],
|
|
168
|
+
GTT: ["GTC"],
|
|
169
|
+
ALO: ["GTX"],
|
|
170
|
+
GTX: ["ALO"],
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Resolve a CLI TIF input (common GTC/IOC/FOK/ALO or a venue alias GTT/GTX/TOB,
|
|
174
|
+
* case-insensitive) to a value the venue declares in its capabilities. Never
|
|
175
|
+
* substitutes a different meaning: FOK is never emulated (§13.3) and an
|
|
176
|
+
* unknown / unoffered value is the uniform CAPABILITY_UNSUPPORTED response.
|
|
177
|
+
*/
|
|
178
|
+
export function validateLegacyTif(venue, caps, tif) {
|
|
179
|
+
const upper = tif.trim().toUpperCase();
|
|
180
|
+
if (caps.timeInForce.includes(upper))
|
|
181
|
+
return upper;
|
|
182
|
+
for (const alias of TIF_ALIASES[upper] ?? []) {
|
|
183
|
+
if (caps.timeInForce.includes(alias))
|
|
184
|
+
return alias;
|
|
185
|
+
}
|
|
186
|
+
if (upper === "FOK") {
|
|
187
|
+
throw unsupported(venue.displayName, "fok", "fill-or-kill is not offered and is never emulated (a partial IOC fill cannot be undone)", `Use --tif IOC (partial fills possible) or one of: ${caps.timeInForce.join(", ")}`);
|
|
188
|
+
}
|
|
189
|
+
const feature = upper === "ALO" || upper === "GTX" ? "postOnly" : "timeInForce";
|
|
190
|
+
throw unsupported(venue.displayName, feature, `time-in-force ${tif} is not offered`, `Use one of: ${caps.timeInForce.join(", ")}`);
|
|
191
|
+
}
|
|
192
|
+
const FINAL_STATUSES = new Set(["filled", "rejected", "cancelled"]);
|
|
193
|
+
/**
|
|
194
|
+
* Poll getOrderStatus until the order reaches a final state, is resting ("open"),
|
|
195
|
+
* or the timeout elapses. On timeout returns the last observation with
|
|
196
|
+
* finality "pending" — never guesses a fill.
|
|
197
|
+
*
|
|
198
|
+
* `untilResting`: treat "open"/"partiallyFilled" as a stopping point (limit orders).
|
|
199
|
+
* `ack`: the placement acknowledgement of an async venue. When set, an
|
|
200
|
+
* ORDER_NOT_FOUND from getOrderStatus inside this loop means "acknowledged but
|
|
201
|
+
* not yet visible on the read path" (Arcus forwards to the engine before the
|
|
202
|
+
* order store has it) and the ack itself (status "accepted") stands as the
|
|
203
|
+
* observation. Outside this loop ORDER_NOT_FOUND is never reinterpreted.
|
|
204
|
+
*/
|
|
205
|
+
export async function awaitOrderFinal(adapter, symbol, ref, opts) {
|
|
206
|
+
if (!(opts.timeoutMs > 0) || !(opts.pollMs > 0)) {
|
|
207
|
+
throw new PerpError("INVALID_PARAMS", "awaitOrderFinal: timeoutMs and pollMs must be positive");
|
|
208
|
+
}
|
|
209
|
+
if (opts.ack !== null && opts.ack.status !== "accepted") {
|
|
210
|
+
throw new PerpError("INVALID_PARAMS", `awaitOrderFinal: ack must have status "accepted" (got ${opts.ack.status})`);
|
|
211
|
+
}
|
|
212
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
213
|
+
let last = null;
|
|
214
|
+
for (;;) {
|
|
215
|
+
try {
|
|
216
|
+
last = await adapter.getOrderStatus(symbol, ref);
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
if (opts.ack === null || !(e instanceof PerpError) || e.structured.code !== "ORDER_NOT_FOUND")
|
|
220
|
+
throw e;
|
|
221
|
+
last = opts.ack;
|
|
222
|
+
}
|
|
223
|
+
if (FINAL_STATUSES.has(last.status))
|
|
224
|
+
return { ...last, finality: "final" };
|
|
225
|
+
if (opts.untilResting && (last.status === "open" || last.status === "partiallyFilled")) {
|
|
226
|
+
return { ...last, finality: "final" };
|
|
227
|
+
}
|
|
228
|
+
if (Date.now() + opts.pollMs > deadline)
|
|
229
|
+
return { ...last, finality: "pending" };
|
|
230
|
+
await new Promise(r => setTimeout(r, opts.pollMs));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Generate a client order id: time-ordered, 20 chars max, [0-9a-z]. */
|
|
234
|
+
export function newClientOrderId(prefix = "p") {
|
|
235
|
+
const t = Date.now().toString(36);
|
|
236
|
+
const r = Math.floor(Math.random() * 36 ** 6).toString(36).padStart(6, "0");
|
|
237
|
+
return `${prefix}${t}${r}`.slice(0, 20);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Client order id in the venue's format: Lighter-family `clientOrderIndex` is a
|
|
241
|
+
* decimal integer ≤ 2^48 − 1 (ms timestamp × 100 + 2 random digits); QFEX and Pacifica take a
|
|
242
|
+
* UUID (QFEX: the format the venue itself generates; Pacifica: "Full UUID string"); Hyperliquid a
|
|
243
|
+
* 128-bit cloid ("0x" + 32 hex); every other venue (Aster: [.A-Z:/a-z0-9_-]{1,36}) takes
|
|
244
|
+
* newClientOrderId().
|
|
245
|
+
*/
|
|
246
|
+
export function newVenueClientOrderId(venue) {
|
|
247
|
+
if (venue.family === "lighter")
|
|
248
|
+
return String(Date.now() * 100 + Math.floor(Math.random() * 100));
|
|
249
|
+
if (venue.family === "qfex" || venue.family === "pacifica")
|
|
250
|
+
return randomUUID();
|
|
251
|
+
if (venue.family === "hyperliquid")
|
|
252
|
+
return `0x${randomBytes(16).toString("hex")}`;
|
|
253
|
+
return newClientOrderId();
|
|
254
|
+
}
|
|
255
|
+
/** Finality wait after an async (orderAck "async") placement ack. */
|
|
256
|
+
const ASYNC_ACK_TIMEOUT_MS = 15_000;
|
|
257
|
+
const ASYNC_ACK_POLL_MS = 1_000;
|
|
258
|
+
/**
|
|
259
|
+
* Place through TradingV2 and settle the outcome as far as the venue can prove it:
|
|
260
|
+
* async-ack venues are polled with awaitOrderFinal until final / resting (non-market)
|
|
261
|
+
* or ASYNC_ACK_TIMEOUT_MS. A non-final result carries a "do NOT resubmit" remediation;
|
|
262
|
+
* a failed status poll after the ack is never reported as a failed order.
|
|
263
|
+
*/
|
|
264
|
+
export async function placeAndConfirm(adapter, caps, req) {
|
|
265
|
+
const placed = await adapter.placeOrder(req);
|
|
266
|
+
const def = adapter.venue;
|
|
267
|
+
const listHint = `perp account orders -e ${def.id} / perp account positions -e ${def.id}`;
|
|
268
|
+
if (placed.finality === "final")
|
|
269
|
+
return { ...placed, remediation: null };
|
|
270
|
+
// The venue's own acknowledgement already reports a resting order (Arcus answers OPEN with the
|
|
271
|
+
// full remaining size; its order read can 404 for a moment after that): for a resting TIF that
|
|
272
|
+
// is the order's settled state — the same stopping point awaitOrderFinal uses (untilResting).
|
|
273
|
+
if (req.type !== "market" && req.tif !== "IOC" && req.tif !== "FOK" && (placed.status === "open" || placed.status === "partiallyFilled")) {
|
|
274
|
+
return { ...placed, finality: "final", remediation: null };
|
|
275
|
+
}
|
|
276
|
+
if (caps.orderAck !== "async" || (placed.orderId === null && placed.clientOrderId === null)) {
|
|
277
|
+
return { ...placed, remediation: `${def.displayName} acknowledged the order without a final status — do NOT resubmit; verify with: ${listHint}` };
|
|
278
|
+
}
|
|
279
|
+
const ref = placed.clientOrderId !== null ? `c:${placed.clientOrderId}` : placed.orderId;
|
|
280
|
+
const statusHint = `perp trade status ${ref} --symbol ${req.symbol} -e ${def.id}`;
|
|
281
|
+
let result;
|
|
282
|
+
try {
|
|
283
|
+
result = await awaitOrderFinal(adapter, req.symbol, orderRefOf(placed), {
|
|
284
|
+
timeoutMs: ASYNC_ACK_TIMEOUT_MS,
|
|
285
|
+
pollMs: ASYNC_ACK_POLL_MS,
|
|
286
|
+
untilResting: req.type !== "market",
|
|
287
|
+
ack: placed.status === "accepted" ? placed : null,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
292
|
+
return {
|
|
293
|
+
...placed,
|
|
294
|
+
remediation: `Order was accepted by ${def.displayName} but its status could not be confirmed (${msg}). Do NOT resubmit — check: ${statusHint}`,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (result.finality === "pending") {
|
|
298
|
+
return { ...result, remediation: `Order not final after ${ASYNC_ACK_TIMEOUT_MS / 1000}s — do NOT resubmit; check: ${statusHint}` };
|
|
299
|
+
}
|
|
300
|
+
return { ...result, remediation: null };
|
|
301
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Venue-level data contracts (docs/DESIGN-venue-abstraction.md §10).
|
|
3
|
+
*
|
|
4
|
+
* SSOT Rule #2: every "unknown" value is `T | null`, never optional, never a
|
|
5
|
+
* silently substituted default.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExchangeMarketInfo } from "./interface.js";
|
|
8
|
+
import type { ResolvedSlippage, SlippageSource } from "./slippage.js";
|
|
9
|
+
import type { AppliedBuilderFee } from "./builder-fee.js";
|
|
10
|
+
export type VenueId = "pacifica" | "hyperliquid" | "lighter" | "aster" | "lighter-rh" | "ondo" | "arcus" | "qfex";
|
|
11
|
+
export type LegacyVenueId = "pacifica" | "hyperliquid" | "lighter" | "aster";
|
|
12
|
+
export declare const VENUE_IDS: readonly VenueId[];
|
|
13
|
+
export declare const LEGACY_VENUE_IDS: readonly LegacyVenueId[];
|
|
14
|
+
export declare function isVenueId(s: string): s is VenueId;
|
|
15
|
+
export declare function isLegacyVenueId(s: string): s is LegacyVenueId;
|
|
16
|
+
export type Network = "mainnet" | "testnet";
|
|
17
|
+
/** How many hours one quoted funding rate covers (used for normalization). */
|
|
18
|
+
export type FundingRateBasis = {
|
|
19
|
+
kind: "perHours";
|
|
20
|
+
hours: number;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "annualized";
|
|
23
|
+
};
|
|
24
|
+
/** Convert a raw quoted rate to a per-hour rate. */
|
|
25
|
+
export declare function rateToHourly(rate: number, basis: FundingRateBasis): number;
|
|
26
|
+
export type AssetClass = "crypto" | "equity" | "index" | "etf" | "commodity" | "fx" | "preIpo";
|
|
27
|
+
export interface MarketSession {
|
|
28
|
+
/** Whether the underlying (e.g. US equity) market is open right now. */
|
|
29
|
+
underlyingOpen: boolean;
|
|
30
|
+
/** Venue-enforced price band while the underlying is closed (Ondo weekend band, Arcus). */
|
|
31
|
+
priceBand: {
|
|
32
|
+
lower: string;
|
|
33
|
+
upper: string;
|
|
34
|
+
} | null;
|
|
35
|
+
/** Initial-margin multiplier while the underlying is closed (Arcus 1.5x). */
|
|
36
|
+
marginMultiplier: number | null;
|
|
37
|
+
}
|
|
38
|
+
export interface VenueMarket {
|
|
39
|
+
venue: VenueId;
|
|
40
|
+
/** Venue-native identifier used on the wire ("TSLA-USD.P", "16", "BTC"). */
|
|
41
|
+
venueSymbol: string;
|
|
42
|
+
/** Display symbol used by the CLI (e.g. "TSLA", "BTC"). */
|
|
43
|
+
symbol: string;
|
|
44
|
+
/** Cross-venue matching key from canonical-symbols.ts; null = not matchable. */
|
|
45
|
+
canonical: string | null;
|
|
46
|
+
assetClass: AssetClass;
|
|
47
|
+
settle: string;
|
|
48
|
+
/**
|
|
49
|
+
* Currency prices are quoted in ("USD", "KRW", ...); null = this venue's data path does not
|
|
50
|
+
* record it. A non-USD quote is never traded through perp-cli (sizing, risk limits and
|
|
51
|
+
* notionals assume USD) — see QFEX, design §14.12 #7.
|
|
52
|
+
*/
|
|
53
|
+
quoteAsset: string | null;
|
|
54
|
+
markPrice: string;
|
|
55
|
+
indexPrice: string | null;
|
|
56
|
+
fundingRate: string | null;
|
|
57
|
+
fundingRateBasis: FundingRateBasis;
|
|
58
|
+
settlementIntervalHours: number;
|
|
59
|
+
openInterest: string | null;
|
|
60
|
+
volume24h: string | null;
|
|
61
|
+
/** null = venue publishes no tick (read-only venues). Trading venues must set it. */
|
|
62
|
+
tickSize: string | null;
|
|
63
|
+
/** null = venue publishes no lot step (read-only venues). Trading venues must set it. */
|
|
64
|
+
stepSize: string | null;
|
|
65
|
+
minSize: string | null;
|
|
66
|
+
minNotional: string | null;
|
|
67
|
+
/** null = venue publishes no leverage cap (read-only venues). */
|
|
68
|
+
maxLeverage: number | null;
|
|
69
|
+
marginTiers: Array<{
|
|
70
|
+
lowerBound: number;
|
|
71
|
+
maxLeverage: number;
|
|
72
|
+
}> | null;
|
|
73
|
+
session: MarketSession | null;
|
|
74
|
+
/** false = listed but not tradable right now (offline / paused / corporate action). */
|
|
75
|
+
tradable: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** Pure mapping used by new-venue adapters to serve the legacy getMarkets(). */
|
|
78
|
+
/** Tick/step/leverage required for order entry. Throws when the venue did not publish them. */
|
|
79
|
+
export declare function requireTradingPrecision(m: VenueMarket): {
|
|
80
|
+
tickSize: string;
|
|
81
|
+
stepSize: string;
|
|
82
|
+
maxLeverage: number;
|
|
83
|
+
};
|
|
84
|
+
export declare function toLegacyMarketInfo(m: VenueMarket): ExchangeMarketInfo;
|
|
85
|
+
export interface VenueFundingRate {
|
|
86
|
+
venue: VenueId;
|
|
87
|
+
venueSymbol: string;
|
|
88
|
+
symbol: string;
|
|
89
|
+
canonical: string | null;
|
|
90
|
+
rate: string;
|
|
91
|
+
basis: FundingRateBasis;
|
|
92
|
+
markPrice: string | null;
|
|
93
|
+
nextFundingTime: number | null;
|
|
94
|
+
}
|
|
95
|
+
/** CLI-level TIF inputs. "TOB" = Pacifica top-of-book, "GTX" = Aster post-only. */
|
|
96
|
+
export type TimeInForce = "GTC" | "GTT" | "IOC" | "FOK" | "ALO" | "TOB" | "GTX";
|
|
97
|
+
export type OrderType = "market" | "limit" | "stopMarket" | "stopLimit";
|
|
98
|
+
export interface OrderRequest {
|
|
99
|
+
symbol: string;
|
|
100
|
+
side: "buy" | "sell";
|
|
101
|
+
type: OrderType;
|
|
102
|
+
size: string;
|
|
103
|
+
/** Required for limit/stopLimit. */
|
|
104
|
+
price: string | null;
|
|
105
|
+
/** Required for stopMarket/stopLimit. */
|
|
106
|
+
triggerPrice: string | null;
|
|
107
|
+
triggerSource: "mark" | "last" | "mid" | null;
|
|
108
|
+
/** Required for limit/stopLimit; only null is allowed for market/stopMarket. */
|
|
109
|
+
tif: TimeInForce | null;
|
|
110
|
+
reduceOnly: boolean;
|
|
111
|
+
/** Required when the venue's orderAck is "async". */
|
|
112
|
+
clientOrderId: string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Resolved slippage bound (resolveSlippage(), design §13.10 #1). Required for
|
|
115
|
+
* type "market"; adapters whose stopMarket / TP-SL legs are protection-priced
|
|
116
|
+
* (Arcus) require it there too. null only for orders that carry no market leg.
|
|
117
|
+
*/
|
|
118
|
+
slippage: ResolvedSlippage | null;
|
|
119
|
+
tpsl: {
|
|
120
|
+
tp: string | null;
|
|
121
|
+
sl: string | null;
|
|
122
|
+
} | null;
|
|
123
|
+
}
|
|
124
|
+
export type OrderStatus = "accepted" | "open" | "partiallyFilled" | "filled" | "rejected" | "cancelled";
|
|
125
|
+
export interface OrderResult {
|
|
126
|
+
venue: VenueId;
|
|
127
|
+
symbol: string;
|
|
128
|
+
status: OrderStatus;
|
|
129
|
+
/** "pending" = not final yet (async venue ack, or awaitOrderFinal timed out). */
|
|
130
|
+
finality: "final" | "pending";
|
|
131
|
+
/**
|
|
132
|
+
* Invariant: orderId !== null || clientOrderId !== null — except for an order the
|
|
133
|
+
* venue acknowledged without any id (e.g. a legacy-venue trigger order); that
|
|
134
|
+
* result says so in execution.notes and orderRefOf() refuses it.
|
|
135
|
+
*/
|
|
136
|
+
orderId: string | null;
|
|
137
|
+
clientOrderId: string | null;
|
|
138
|
+
/** Filled base size; null = the venue response does not report it (never "0" as a stand-in). */
|
|
139
|
+
filledSize: string | null;
|
|
140
|
+
avgPrice: string | null;
|
|
141
|
+
/** How the venue adapter actually executed the request (design §13.1 #3, §13.10 #14). */
|
|
142
|
+
execution: OrderExecution;
|
|
143
|
+
venueRaw: unknown;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Transparent record of the venue-specific handling behind a uniform order
|
|
147
|
+
* (design §13.1 #3, §13.2, §13.10 #14). Every field is required; a value the
|
|
148
|
+
* venue / adapter did not report is null (SSOT Rule #2), never a guess.
|
|
149
|
+
*/
|
|
150
|
+
export interface OrderExecution {
|
|
151
|
+
/**
|
|
152
|
+
* Execution method, e.g. "protectedIoc" (IOC limit at a protection price),
|
|
153
|
+
* "venueSlippage" (venue applies a slippage percent), "limit", "trigger",
|
|
154
|
+
* "triggerLimit", "nativeAmend", "cancelReplace".
|
|
155
|
+
*/
|
|
156
|
+
method: string;
|
|
157
|
+
/** Slippage bound actually applied (after price-grid rounding when the adapter reports it). */
|
|
158
|
+
slippageBps: number | null;
|
|
159
|
+
slippageSource: SlippageSource | null;
|
|
160
|
+
/** Limit price sent with a market-type order; null = not echoed / computed by the venue. */
|
|
161
|
+
protectionPrice: string | null;
|
|
162
|
+
/** Reference price the protection price was derived from (mark / mid / trigger). */
|
|
163
|
+
referencePrice: string | null;
|
|
164
|
+
/** TIF as requested by the caller (common or venue alias). */
|
|
165
|
+
tifRequested: string | null;
|
|
166
|
+
/** Venue-native TIF actually sent (e.g. "Gtc", "GTT", "GTX"). */
|
|
167
|
+
tifSent: string | null;
|
|
168
|
+
/** Unix ms when a resting order expires (GTC mapped to GTT); null = no venue expiry. */
|
|
169
|
+
expiresAt: number | null;
|
|
170
|
+
/**
|
|
171
|
+
* Builder fee this order carried (settings builder.<venue> + confirmed approval); null = none.
|
|
172
|
+
* A configured fee that was NOT attached says why in `notes`.
|
|
173
|
+
*/
|
|
174
|
+
builderFee: AppliedBuilderFee | null;
|
|
175
|
+
notes: string[];
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Attach an execution record to a result built without one. Fields of `partial`
|
|
179
|
+
* that are absent are recorded as null (unknown), never defaulted to a value.
|
|
180
|
+
*/
|
|
181
|
+
export declare function withExecution(result: Omit<OrderResult, "execution">, method: string, partial: Partial<Omit<OrderExecution, "method">>): OrderResult;
|
|
182
|
+
export type OrderRef = {
|
|
183
|
+
kind: "orderId";
|
|
184
|
+
value: string;
|
|
185
|
+
} | {
|
|
186
|
+
kind: "clientOrderId";
|
|
187
|
+
value: string;
|
|
188
|
+
};
|
|
189
|
+
export interface AmendRequest {
|
|
190
|
+
symbol: string;
|
|
191
|
+
ref: OrderRef;
|
|
192
|
+
price: string;
|
|
193
|
+
size: string;
|
|
194
|
+
/**
|
|
195
|
+
* Time-in-force of the replacement. null = keep the resting order's own TIF
|
|
196
|
+
* (venues that can read it back, e.g. Arcus); cancel→replace venues that cannot
|
|
197
|
+
* read post-only back (Ondo) refuse null.
|
|
198
|
+
*/
|
|
199
|
+
tif: TimeInForce | null;
|
|
200
|
+
/**
|
|
201
|
+
* Allow an explicit `tif` that flips the post-only flag encoded in the order's generated
|
|
202
|
+
* client id (Ondo / Pacifica): without it such a conflict is INVALID_PARAMS (CLI --force-tif).
|
|
203
|
+
*/
|
|
204
|
+
forceTif?: boolean;
|
|
205
|
+
}
|
|
206
|
+
export interface CancelResult {
|
|
207
|
+
venue: VenueId;
|
|
208
|
+
/** Only orders whose cancel the venue result proves (per order). */
|
|
209
|
+
cancelled: OrderRef[];
|
|
210
|
+
failed: Array<{
|
|
211
|
+
ref: OrderRef | null;
|
|
212
|
+
error: string;
|
|
213
|
+
}>;
|
|
214
|
+
/** Cancels sent but not proven by the venue result — status pending; verify before acting again. */
|
|
215
|
+
pending?: Array<{
|
|
216
|
+
ref: OrderRef | null;
|
|
217
|
+
reason: string;
|
|
218
|
+
remediation: string;
|
|
219
|
+
}>;
|
|
220
|
+
venueRaw: unknown;
|
|
221
|
+
}
|
|
222
|
+
export interface LeverageResult {
|
|
223
|
+
venue: VenueId;
|
|
224
|
+
symbol: string;
|
|
225
|
+
leverage: number;
|
|
226
|
+
marginMode: "cross" | "isolated";
|
|
227
|
+
venueRaw: unknown;
|
|
228
|
+
}
|
|
229
|
+
export declare function orderRefOf(r: Pick<OrderResult, "orderId" | "clientOrderId">): OrderRef;
|
|
230
|
+
/** Parse a CLI order reference: "c:<id>" → clientOrderId, otherwise orderId. */
|
|
231
|
+
export declare function parseOrderRef(s: string): OrderRef;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Venue-level data contracts (docs/DESIGN-venue-abstraction.md §10).
|
|
3
|
+
*
|
|
4
|
+
* SSOT Rule #2: every "unknown" value is `T | null`, never optional, never a
|
|
5
|
+
* silently substituted default.
|
|
6
|
+
*/
|
|
7
|
+
export const VENUE_IDS = [
|
|
8
|
+
"pacifica", "hyperliquid", "lighter", "aster",
|
|
9
|
+
"lighter-rh", "ondo", "arcus", "qfex",
|
|
10
|
+
];
|
|
11
|
+
export const LEGACY_VENUE_IDS = ["pacifica", "hyperliquid", "lighter", "aster"];
|
|
12
|
+
export function isVenueId(s) {
|
|
13
|
+
return VENUE_IDS.includes(s);
|
|
14
|
+
}
|
|
15
|
+
export function isLegacyVenueId(s) {
|
|
16
|
+
return LEGACY_VENUE_IDS.includes(s);
|
|
17
|
+
}
|
|
18
|
+
/** Convert a raw quoted rate to a per-hour rate. */
|
|
19
|
+
export function rateToHourly(rate, basis) {
|
|
20
|
+
if (basis.kind === "perHours")
|
|
21
|
+
return rate / basis.hours;
|
|
22
|
+
return rate / (24 * 365);
|
|
23
|
+
}
|
|
24
|
+
function decimalsOf(step) {
|
|
25
|
+
const s = step.includes("e") || step.includes("E") ? Number(step).toFixed(20).replace(/0+$/, "") : step;
|
|
26
|
+
const dot = s.indexOf(".");
|
|
27
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
28
|
+
}
|
|
29
|
+
/** Pure mapping used by new-venue adapters to serve the legacy getMarkets(). */
|
|
30
|
+
/** Tick/step/leverage required for order entry. Throws when the venue did not publish them. */
|
|
31
|
+
export function requireTradingPrecision(m) {
|
|
32
|
+
if (m.tickSize === null || m.stepSize === null || m.maxLeverage === null) {
|
|
33
|
+
throw new Error(`${m.venue} ${m.symbol}: tick size / step size / max leverage not published — market is not tradable via API`);
|
|
34
|
+
}
|
|
35
|
+
return { tickSize: m.tickSize, stepSize: m.stepSize, maxLeverage: m.maxLeverage };
|
|
36
|
+
}
|
|
37
|
+
export function toLegacyMarketInfo(m) {
|
|
38
|
+
const p = requireTradingPrecision(m);
|
|
39
|
+
const hours = m.fundingRateBasis.kind === "perHours" ? m.fundingRateBasis.hours : null;
|
|
40
|
+
return {
|
|
41
|
+
symbol: m.symbol,
|
|
42
|
+
markPrice: m.markPrice,
|
|
43
|
+
indexPrice: m.indexPrice,
|
|
44
|
+
fundingRate: m.fundingRate,
|
|
45
|
+
volume24h: m.volume24h,
|
|
46
|
+
openInterest: m.openInterest,
|
|
47
|
+
maxLeverage: p.maxLeverage,
|
|
48
|
+
sizeDecimals: decimalsOf(p.stepSize),
|
|
49
|
+
stepSize: p.stepSize,
|
|
50
|
+
fundingHours: hours,
|
|
51
|
+
marginTiers: m.marginTiers,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Attach an execution record to a result built without one. Fields of `partial`
|
|
56
|
+
* that are absent are recorded as null (unknown), never defaulted to a value.
|
|
57
|
+
*/
|
|
58
|
+
export function withExecution(result, method, partial) {
|
|
59
|
+
return {
|
|
60
|
+
...result,
|
|
61
|
+
execution: {
|
|
62
|
+
method,
|
|
63
|
+
slippageBps: partial.slippageBps ?? null,
|
|
64
|
+
slippageSource: partial.slippageSource ?? null,
|
|
65
|
+
protectionPrice: partial.protectionPrice ?? null,
|
|
66
|
+
referencePrice: partial.referencePrice ?? null,
|
|
67
|
+
tifRequested: partial.tifRequested ?? null,
|
|
68
|
+
tifSent: partial.tifSent ?? null,
|
|
69
|
+
expiresAt: partial.expiresAt ?? null,
|
|
70
|
+
builderFee: partial.builderFee ?? null,
|
|
71
|
+
notes: partial.notes ?? [],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function orderRefOf(r) {
|
|
76
|
+
if (r.orderId !== null)
|
|
77
|
+
return { kind: "orderId", value: r.orderId };
|
|
78
|
+
if (r.clientOrderId !== null)
|
|
79
|
+
return { kind: "clientOrderId", value: r.clientOrderId };
|
|
80
|
+
throw new Error("OrderResult invariant violated: neither orderId nor clientOrderId is set");
|
|
81
|
+
}
|
|
82
|
+
/** Parse a CLI order reference: "c:<id>" → clientOrderId, otherwise orderId. */
|
|
83
|
+
export function parseOrderRef(s) {
|
|
84
|
+
if (s.startsWith("c:"))
|
|
85
|
+
return { kind: "clientOrderId", value: s.slice(2) };
|
|
86
|
+
return { kind: "orderId", value: s };
|
|
87
|
+
}
|