perp-cli 0.14.3 → 0.15.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 +152 -13
- package/dist/agent-wallet/store.d.ts +3 -2
- package/dist/agent-wallet/store.js +2 -2
- package/dist/api/public/hyperliquid.js +4 -0
- package/dist/api/public/pacifica.js +83 -13
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/bot/apex/pulse.js +3 -0
- package/dist/bot/apex/radar.js +2 -1
- package/dist/bot/engine.d.ts +10 -1
- package/dist/bot/engine.js +14 -6
- package/dist/bot/indicators.d.ts +7 -0
- package/dist/bot/indicators.js +16 -0
- package/dist/bot/perp-order.d.ts +4 -4
- package/dist/bot/perp-order.js +5 -5
- package/dist/bot/quarantine.d.ts +1 -1
- package/dist/bot/quarantine.js +1 -1
- package/dist/bot/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +2 -2
- package/dist/bot/strategies/funding-arb-utils.d.ts +1 -1
- package/dist/bot/strategies/funding-arb-utils.js +5 -5
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
- package/dist/bot/strategies/liquidation-mm.js +8 -2
- package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
- package/dist/bot/strategies/momentum-breakout.js +11 -3
- package/dist/bot/strategies/spot-perp-arb-strategy.js +2 -2
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +1 -1
- package/dist/bot/strategy-types.d.ts +2 -1
- package/dist/bot/tui/BotDashboard.d.ts +1 -1
- package/dist/bot/tui/BotDashboard.js +1 -1
- package/dist/cache.js +12 -6
- package/dist/cli-program.d.ts +15 -0
- package/dist/cli-program.js +125 -5
- package/dist/commands/agent-import.d.ts +7 -0
- package/dist/commands/agent-import.js +30 -1
- package/dist/commands/agent.d.ts +6 -3
- package/dist/commands/agent.js +160 -12
- package/dist/commands/arb/index.js +4 -4
- package/dist/commands/arb-auto.js +21 -17
- package/dist/commands/arb.d.ts +12 -0
- package/dist/commands/arb.js +61 -143
- package/dist/commands/bot.js +1 -1
- package/dist/commands/builder.js +1 -1
- package/dist/commands/funds.js +113 -3
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +27 -7
- package/dist/commands/manage.js +23 -21
- package/dist/commands/market.js +167 -35
- package/dist/commands/options.d.ts +9 -0
- package/dist/commands/options.js +134 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +468 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.js +32 -47
- package/dist/commands/rebalance.d.ts +1 -0
- package/dist/commands/rebalance.js +15 -0
- package/dist/commands/risk.d.ts +11 -2
- package/dist/commands/risk.js +39 -44
- package/dist/commands/trade.d.ts +1 -0
- package/dist/commands/trade.js +63 -58
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +15 -4
- package/dist/constants.js +6 -0
- package/dist/dashboard/server.js +1 -1
- package/dist/errors.d.ts +24 -1
- package/dist/errors.js +25 -3
- package/dist/exchanges/arcus/adapter.d.ts +7 -7
- package/dist/exchanges/arcus/adapter.js +12 -12
- package/dist/exchanges/arcus/agent.d.ts +1 -1
- package/dist/exchanges/arcus/agent.js +1 -1
- package/dist/exchanges/arcus/client.js +1 -1
- package/dist/exchanges/arcus/signing.d.ts +2 -3
- package/dist/exchanges/arcus/signing.js +1 -1
- package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
- package/dist/exchanges/arcus/spot-chain.js +1 -1
- package/dist/exchanges/arcus/spot-router.d.ts +1 -1
- package/dist/exchanges/arcus/spot-router.js +2 -2
- package/dist/exchanges/arcus/spot.d.ts +2 -3
- package/dist/exchanges/arcus/spot.js +2 -3
- package/dist/exchanges/aster-spot.d.ts +3 -3
- package/dist/exchanges/aster-spot.js +6 -9
- package/dist/exchanges/aster-typed-data.d.ts +1 -1
- package/dist/exchanges/aster-typed-data.js +1 -1
- package/dist/exchanges/aster.d.ts +4 -4
- package/dist/exchanges/aster.js +31 -13
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +1 -2
- package/dist/exchanges/canonical-symbols.js +26 -2
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +68 -0
- package/dist/exchanges/derive/options.js +360 -0
- package/dist/exchanges/extended/adapter.d.ts +82 -0
- package/dist/exchanges/extended/adapter.js +179 -0
- package/dist/exchanges/extended/client.d.ts +38 -0
- package/dist/exchanges/extended/client.js +88 -0
- package/dist/exchanges/extended/public.d.ts +130 -0
- package/dist/exchanges/extended/public.js +429 -0
- package/dist/exchanges/grvt/adapter.d.ts +86 -0
- package/dist/exchanges/grvt/adapter.js +206 -0
- package/dist/exchanges/grvt/client.d.ts +25 -0
- package/dist/exchanges/grvt/client.js +86 -0
- package/dist/exchanges/grvt/public.d.ts +151 -0
- package/dist/exchanges/grvt/public.js +438 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +5 -5
- package/dist/exchanges/hyperliquid.d.ts +22 -12
- package/dist/exchanges/hyperliquid.js +80 -34
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +94 -6
- package/dist/exchanges/interface.js +16 -0
- package/dist/exchanges/ioc-outcome.d.ts +1 -1
- package/dist/exchanges/ioc-outcome.js +1 -1
- package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
- package/dist/exchanges/legacy-trading-v2.js +7 -2
- package/dist/exchanges/lighter-public.d.ts +2 -2
- package/dist/exchanges/lighter-public.js +2 -2
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +3 -4
- package/dist/exchanges/lighter-spot.js +6 -7
- package/dist/exchanges/lighter.d.ts +11 -11
- package/dist/exchanges/lighter.js +28 -14
- package/dist/exchanges/ondo/adapter.d.ts +5 -6
- package/dist/exchanges/ondo/adapter.js +16 -14
- package/dist/exchanges/ondo/agent.d.ts +1 -1
- package/dist/exchanges/ondo/agent.js +1 -1
- package/dist/exchanges/ondo/client.d.ts +1 -1
- package/dist/exchanges/ondo/client.js +2 -2
- package/dist/exchanges/ondo/public.d.ts +8 -1
- package/dist/exchanges/ondo/public.js +13 -2
- package/dist/exchanges/ondo/spot.d.ts +2 -2
- package/dist/exchanges/ondo/spot.js +3 -3
- package/dist/exchanges/options-interface.d.ts +158 -0
- package/dist/exchanges/options-interface.js +17 -0
- package/dist/exchanges/options-venues.d.ts +12 -0
- package/dist/exchanges/options-venues.js +35 -0
- package/dist/exchanges/pacifica-atomic.d.ts +211 -0
- package/dist/exchanges/pacifica-atomic.js +440 -0
- package/dist/exchanges/pacifica-decimal.d.ts +10 -0
- package/dist/exchanges/pacifica-decimal.js +51 -0
- package/dist/exchanges/pacifica-presigned.d.ts +53 -0
- package/dist/exchanges/pacifica-presigned.js +141 -0
- package/dist/exchanges/pacifica-print.d.ts +200 -0
- package/dist/exchanges/pacifica-print.js +417 -0
- package/dist/exchanges/pacifica-spot.d.ts +2 -2
- package/dist/exchanges/pacifica-spot.js +3 -3
- package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
- package/dist/exchanges/pacifica-typed-data.js +1 -1
- package/dist/exchanges/pacifica-vaults.d.ts +121 -0
- package/dist/exchanges/pacifica-vaults.js +264 -0
- package/dist/exchanges/pacifica.d.ts +32 -5
- package/dist/exchanges/pacifica.js +114 -48
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/qfex/adapter.d.ts +18 -5
- package/dist/exchanges/qfex/adapter.js +32 -12
- package/dist/exchanges/qfex/agent.d.ts +1 -1
- package/dist/exchanges/qfex/agent.js +1 -1
- package/dist/exchanges/qfex/client.d.ts +1 -1
- package/dist/exchanges/qfex/client.js +1 -1
- package/dist/exchanges/qfex/errors.d.ts +1 -1
- package/dist/exchanges/qfex/errors.js +2 -2
- package/dist/exchanges/qfex/public.d.ts +3 -3
- package/dist/exchanges/qfex/public.js +3 -3
- package/dist/exchanges/qfex/signing.d.ts +2 -2
- package/dist/exchanges/qfex/signing.js +4 -5
- package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
- package/dist/exchanges/qfex/ws-transport.js +3 -3
- package/dist/exchanges/rate-limiter.d.ts +1 -1
- package/dist/exchanges/rate-limiter.js +5 -2
- package/dist/exchanges/registry.d.ts +1 -1
- package/dist/exchanges/registry.js +1 -1
- package/dist/exchanges/risex/adapter.d.ts +148 -0
- package/dist/exchanges/risex/adapter.js +796 -0
- package/dist/exchanges/risex/agent.d.ts +154 -0
- package/dist/exchanges/risex/agent.js +384 -0
- package/dist/exchanges/risex/client.d.ts +47 -0
- package/dist/exchanges/risex/client.js +174 -0
- package/dist/exchanges/risex/nonce.d.ts +43 -0
- package/dist/exchanges/risex/nonce.js +212 -0
- package/dist/exchanges/risex/public.d.ts +64 -0
- package/dist/exchanges/risex/public.js +150 -0
- package/dist/exchanges/risex/session.d.ts +71 -0
- package/dist/exchanges/risex/session.js +171 -0
- package/dist/exchanges/risex/typed-data.d.ts +173 -0
- package/dist/exchanges/risex/typed-data.js +240 -0
- package/dist/exchanges/risex/units.d.ts +29 -0
- package/dist/exchanges/risex/units.js +104 -0
- package/dist/exchanges/slippage.d.ts +1 -1
- package/dist/exchanges/slippage.js +1 -1
- package/dist/exchanges/spot-factory.d.ts +3 -3
- package/dist/exchanges/spot-factory.js +7 -7
- package/dist/exchanges/spot-interface.d.ts +5 -5
- package/dist/exchanges/spot-interface.js +2 -2
- package/dist/exchanges/spot-result.d.ts +3 -3
- package/dist/exchanges/spot-result.js +3 -3
- package/dist/exchanges/trading-v2.d.ts +3 -3
- package/dist/exchanges/trading-v2.js +9 -6
- package/dist/exchanges/txflow/adapter.d.ts +54 -0
- package/dist/exchanges/txflow/adapter.js +118 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +153 -0
- package/dist/exchanges/txflow/public.d.ts +76 -0
- package/dist/exchanges/txflow/public.js +284 -0
- package/dist/exchanges/unknown-symbol.d.ts +29 -0
- package/dist/exchanges/unknown-symbol.js +51 -0
- package/dist/exchanges/variational/adapter.d.ts +71 -0
- package/dist/exchanges/variational/adapter.js +137 -0
- package/dist/exchanges/variational/client.d.ts +21 -0
- package/dist/exchanges/variational/client.js +66 -0
- package/dist/exchanges/variational/public.d.ts +126 -0
- package/dist/exchanges/variational/public.js +430 -0
- package/dist/exchanges/venue-types.d.ts +6 -7
- package/dist/exchanges/venue-types.js +2 -2
- package/dist/exchanges/venue.d.ts +51 -8
- package/dist/exchanges/venue.js +54 -4
- package/dist/exchanges/venues/arcus.d.ts +1 -2
- package/dist/exchanges/venues/arcus.js +5 -4
- package/dist/exchanges/venues/aster.d.ts +2 -2
- package/dist/exchanges/venues/aster.js +6 -4
- package/dist/exchanges/venues/create-helpers.d.ts +1 -1
- package/dist/exchanges/venues/create-helpers.js +3 -3
- package/dist/exchanges/venues/extended.d.ts +21 -0
- package/dist/exchanges/venues/extended.js +96 -0
- package/dist/exchanges/venues/grvt.d.ts +30 -0
- package/dist/exchanges/venues/grvt.js +98 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
- package/dist/exchanges/venues/hyperliquid.js +10 -8
- package/dist/exchanges/venues/index.d.ts +7 -2
- package/dist/exchanges/venues/index.js +8 -2
- package/dist/exchanges/venues/lighter-family.d.ts +7 -9
- package/dist/exchanges/venues/lighter-family.js +9 -11
- package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
- package/dist/exchanges/venues/lighter-rh.js +7 -5
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +5 -3
- package/dist/exchanges/venues/ondo.d.ts +1 -1
- package/dist/exchanges/venues/ondo.js +5 -3
- package/dist/exchanges/venues/pacifica.d.ts +2 -2
- package/dist/exchanges/venues/pacifica.js +6 -4
- package/dist/exchanges/venues/qfex.d.ts +1 -1
- package/dist/exchanges/venues/qfex.js +4 -2
- package/dist/exchanges/venues/risex-release.d.ts +14 -0
- package/dist/exchanges/venues/risex-release.js +14 -0
- package/dist/exchanges/venues/risex.d.ts +31 -0
- package/dist/exchanges/venues/risex.js +116 -0
- package/dist/exchanges/venues/txflow.d.ts +18 -0
- package/dist/exchanges/venues/txflow.js +77 -0
- package/dist/exchanges/venues/variational.d.ts +21 -0
- package/dist/exchanges/venues/variational.js +85 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/funding/legacy-public.d.ts +1 -1
- package/dist/funding/legacy-public.js +9 -4
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +44 -4
- package/dist/funding/rates.js +56 -12
- package/dist/index.js +7 -6
- package/dist/jobs.d.ts +1 -0
- package/dist/jobs.js +25 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +1 -1
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +58 -0
- package/dist/mcp-server.js +113 -59
- package/dist/pacifica/client.d.ts +67 -4
- package/dist/pacifica/client.js +170 -8
- package/dist/pacifica/rate-limit.d.ts +144 -0
- package/dist/pacifica/rate-limit.js +267 -0
- package/dist/pacifica/types/order.d.ts +2 -1
- package/dist/plan-executor.js +7 -2
- package/dist/rebalance.js +1 -1
- package/dist/risk.d.ts +2 -1
- package/dist/risk.js +2 -4
- package/dist/settings.d.ts +32 -3
- package/dist/signer/ows-evm.d.ts +1 -1
- package/dist/signer/ows-evm.js +1 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/smart-order.js +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +1 -1
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +12 -3
- package/dist/utils.js +11 -3
- package/dist/venue-health.d.ts +40 -0
- package/dist/venue-health.js +75 -0
- package/dist/withdraw-gate.d.ts +1 -1
- package/dist/withdraw-gate.js +1 -1
- package/dist/ws/index.d.ts +959 -0
- package/dist/ws/index.js +4295 -0
- package/dist/ws/node.d.ts +975 -0
- package/dist/ws/node.js +4366 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +58 -10
- package/skills/perp-cli/SKILL.md +62 -9
- package/skills/perp-cli/references/agent-operations.md +7 -0
- package/skills/perp-cli/references/commands.md +76 -5
- package/skills/perp-cli/references/ws-library.md +492 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Decimal-string precision helpers for order entry
|
|
2
|
+
* Decimal-string precision helpers for order entry.
|
|
3
3
|
*
|
|
4
4
|
* All math is done on scaled BigInt to avoid float drift. Rules:
|
|
5
5
|
* - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
|
|
@@ -24,6 +24,8 @@ export declare function requirePositive(value: string, what: string): void;
|
|
|
24
24
|
export declare function requireOnTick(price: string, tickSize: string): string;
|
|
25
25
|
/** Exact (a + b) / 2 of two plain decimal strings (no float arithmetic: 0.1 and 0.2 → "0.15"). */
|
|
26
26
|
export declare function midDecimal(a: string, b: string): string;
|
|
27
|
+
/** Exact decimal a + b (plain decimal strings). */
|
|
28
|
+
export declare function addDecimal(a: string, b: string): string;
|
|
27
29
|
/** Exact decimal a − b (plain decimal strings). */
|
|
28
30
|
export declare function subDecimal(a: string, b: string): string;
|
|
29
31
|
/** Exact decimal a × b (plain decimal strings). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Decimal-string precision helpers for order entry
|
|
2
|
+
* Decimal-string precision helpers for order entry.
|
|
3
3
|
*
|
|
4
4
|
* All math is done on scaled BigInt to avoid float drift. Rules:
|
|
5
5
|
* - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
|
|
@@ -119,6 +119,13 @@ export function midDecimal(a, b) {
|
|
|
119
119
|
const scale = Math.max(x.scale, y.scale) + 1; // one more digit holds the possible ".5"
|
|
120
120
|
return format((rescale(x, scale) + rescale(y, scale)) / 2n, scale);
|
|
121
121
|
}
|
|
122
|
+
/** Exact decimal a + b (plain decimal strings). */
|
|
123
|
+
export function addDecimal(a, b) {
|
|
124
|
+
const x = parseDecimal(a);
|
|
125
|
+
const y = parseDecimal(b);
|
|
126
|
+
const scale = Math.max(x.scale, y.scale);
|
|
127
|
+
return format(rescale(x, scale) + rescale(y, scale), scale);
|
|
128
|
+
}
|
|
122
129
|
/** Exact decimal a − b (plain decimal strings). */
|
|
123
130
|
export function subDecimal(a, b) {
|
|
124
131
|
const x = parseDecimal(a);
|
|
@@ -195,7 +202,7 @@ export async function lotStepOf(adapter, symbol) {
|
|
|
195
202
|
m = bare[0];
|
|
196
203
|
}
|
|
197
204
|
if (m === undefined) {
|
|
198
|
-
throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name });
|
|
205
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name, remediation: `List markets: perp -e ${adapter.name} market list` });
|
|
199
206
|
}
|
|
200
207
|
if (m.stepSize !== null)
|
|
201
208
|
return m.stepSize;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
|
|
3
|
-
* (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
|
|
4
3
|
*
|
|
5
4
|
* Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
|
|
6
5
|
* Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
|
|
@@ -42,7 +41,7 @@
|
|
|
42
41
|
import { PerpError } from "../../errors.js";
|
|
43
42
|
import type { WithdrawGate } from "../../settings.js";
|
|
44
43
|
import { type BuilderFeeGate } from "../builder-fee.js";
|
|
45
|
-
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade } from "../interface.js";
|
|
44
|
+
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
|
|
46
45
|
import type { IocOutcome } from "../ioc-outcome.js";
|
|
47
46
|
import type { ResolvedSlippage } from "../slippage.js";
|
|
48
47
|
import type { TradingV2 } from "../trading-v2.js";
|
|
@@ -52,6 +51,20 @@ import { type QfexReduceOnlyWire } from "./signing.js";
|
|
|
52
51
|
import { qfexPendingRemediation, type QfexOrderResponse } from "./ws-transport.js";
|
|
53
52
|
/** A mark price entry older than this (its own venue `time`) is not used as an order reference. */
|
|
54
53
|
export declare const QFEX_MARK_MAX_AGE_MS = 10000;
|
|
54
|
+
/**
|
|
55
|
+
* Candle volumes. GET /candles returns `usdVolume` (populated) and `baseTokenVolume`, which the
|
|
56
|
+
* venue leaves "0" on traded candles (live 2026-10-14, CL-USD 1HOUR: baseTokenVolume "0" with
|
|
57
|
+
* usdVolume "52534.48506" on every row; startingOpenInterest / orderbookMidPrice* are "0" too).
|
|
58
|
+
* A base "0" next to a non-zero USD volume is a placeholder → null (unknown), never 0; when
|
|
59
|
+
* both are zero the candle really had no volume.
|
|
60
|
+
*/
|
|
61
|
+
export declare function qfexCandleVolumes(c: {
|
|
62
|
+
baseTokenVolume: unknown;
|
|
63
|
+
usdVolume: unknown;
|
|
64
|
+
}): {
|
|
65
|
+
volume: string | null;
|
|
66
|
+
quoteVolume: string;
|
|
67
|
+
};
|
|
55
68
|
/** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
|
|
56
69
|
export declare function qfexClientOrderId(): string;
|
|
57
70
|
/**
|
|
@@ -215,7 +228,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
215
228
|
/**
|
|
216
229
|
* A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
|
|
217
230
|
* on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
|
|
218
|
-
* unknown (ORDER_PENDING,
|
|
231
|
+
* unknown (ORDER_PENDING, never re-sent). It is NOT cancelled here: a cancel
|
|
219
232
|
* is a second mutating command with its own unknown outcome and cannot undo a fill.
|
|
220
233
|
*/
|
|
221
234
|
private reduceOnlyEchoError;
|
|
@@ -225,7 +238,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
225
238
|
private orderResultOf;
|
|
226
239
|
/**
|
|
227
240
|
* Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
|
|
228
|
-
* terminal frame was received
|
|
241
|
+
* terminal frame was received. A pending result is never an outcome.
|
|
229
242
|
*/
|
|
230
243
|
iocOutcomeOf(raw: unknown): IocOutcome | null;
|
|
231
244
|
amendOrder(req: AmendRequest): Promise<OrderResult>;
|
|
@@ -253,7 +266,7 @@ export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
|
253
266
|
bids: [string, string][];
|
|
254
267
|
asks: [string, string][];
|
|
255
268
|
}>;
|
|
256
|
-
getRecentTrades(): Promise<
|
|
269
|
+
getRecentTrades(): Promise<ExchangePublicTrade[]>;
|
|
257
270
|
/** Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24). */
|
|
258
271
|
getFundingHistory(symbol: string, limit?: number): Promise<{
|
|
259
272
|
time: number;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
|
|
3
|
-
* (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
|
|
4
3
|
*
|
|
5
4
|
* Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
|
|
6
5
|
* Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
|
|
@@ -78,6 +77,24 @@ const STOP_TYPES = new Set(["TAKE_PROFIT", "STOP_LOSS"]);
|
|
|
78
77
|
const KLINE_RESOLUTION = {
|
|
79
78
|
"1m": "1MIN", "5m": "5MINS", "15m": "15MINS", "30m": "30MINS", "1h": "1HOUR", "4h": "4HOURS", "1d": "1DAY",
|
|
80
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Candle volumes. GET /candles returns `usdVolume` (populated) and `baseTokenVolume`, which the
|
|
82
|
+
* venue leaves "0" on traded candles (live 2026-10-14, CL-USD 1HOUR: baseTokenVolume "0" with
|
|
83
|
+
* usdVolume "52534.48506" on every row; startingOpenInterest / orderbookMidPrice* are "0" too).
|
|
84
|
+
* A base "0" next to a non-zero USD volume is a placeholder → null (unknown), never 0; when
|
|
85
|
+
* both are zero the candle really had no volume.
|
|
86
|
+
*/
|
|
87
|
+
export function qfexCandleVolumes(c) {
|
|
88
|
+
const decimal = (v, what) => {
|
|
89
|
+
if (typeof v !== "string" || v.trim() === "" || !Number.isFinite(Number(v))) {
|
|
90
|
+
throw new PerpError("EXCHANGE_ERROR", `QFEX candles: ${what} is not a decimal string (${JSON.stringify(v)})`, { exchange: "qfex" });
|
|
91
|
+
}
|
|
92
|
+
return v;
|
|
93
|
+
};
|
|
94
|
+
const quoteVolume = decimal(c.usdVolume, "usdVolume");
|
|
95
|
+
const base = decimal(c.baseTokenVolume, "baseTokenVolume");
|
|
96
|
+
return { volume: Number(base) === 0 && Number(quoteVolume) !== 0 ? null : base, quoteVolume };
|
|
97
|
+
}
|
|
81
98
|
/** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
|
|
82
99
|
export function qfexClientOrderId() {
|
|
83
100
|
return randomUUID();
|
|
@@ -262,7 +279,7 @@ export class QfexAdapter {
|
|
|
262
279
|
const venueSymbol = qfexVenueSymbol(symbol);
|
|
263
280
|
const i = (await this.instrumentMap()).get(venueSymbol);
|
|
264
281
|
if (i === undefined) {
|
|
265
|
-
throw new PerpError("SYMBOL_NOT_FOUND", `QFEX has no market ${venueSymbol}`, { exchange: "qfex", remediation: "List markets
|
|
282
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `QFEX has no market ${venueSymbol}`, { exchange: "qfex", remediation: "List markets: perp -e qfex market list" });
|
|
266
283
|
}
|
|
267
284
|
return i;
|
|
268
285
|
}
|
|
@@ -339,7 +356,7 @@ export class QfexAdapter {
|
|
|
339
356
|
stopOrderGate() {
|
|
340
357
|
if (QFEX_STOP_CLIENT_ID_ECHO === true)
|
|
341
358
|
return null;
|
|
342
|
-
return unsupported("qfex", "stopOrder", "the stop order acknowledgement is not documented to echo a client order id, so a stop cannot be correlated safely (Q21)", "Set stops / TP / SL on the QFEX web app until
|
|
359
|
+
return unsupported("qfex", "stopOrder", "the stop order acknowledgement is not documented to echo a client order id, so a stop cannot be correlated safely (Q21)", "Set stops / TP / SL on the QFEX web app until QFEX item Q21 passes");
|
|
343
360
|
}
|
|
344
361
|
/**
|
|
345
362
|
* One reduce-only stop-market order (add_order with order_type TAKE_PROFIT / STOP_LOSS).
|
|
@@ -571,7 +588,7 @@ export class QfexAdapter {
|
|
|
571
588
|
/**
|
|
572
589
|
* A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
|
|
573
590
|
* on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
|
|
574
|
-
* unknown (ORDER_PENDING,
|
|
591
|
+
* unknown (ORDER_PENDING, never re-sent). It is NOT cancelled here: a cancel
|
|
575
592
|
* is a second mutating command with its own unknown outcome and cannot undo a fill.
|
|
576
593
|
*/
|
|
577
594
|
reduceOnlyEchoError(i, orderId, clientOrderId, echo, venueRaw) {
|
|
@@ -746,7 +763,7 @@ export class QfexAdapter {
|
|
|
746
763
|
}
|
|
747
764
|
if (isZeroDecimal(filled)) {
|
|
748
765
|
if (ctx.immediate) {
|
|
749
|
-
// A terminal frame with nothing filled — IOC_CANCELLED or CANCELLED alike
|
|
766
|
+
// A terminal frame with nothing filled — IOC_CANCELLED or CANCELLED alike.
|
|
750
767
|
throw new PerpError("ORDER_REJECTED", `QFEX ${i.venueSymbol}: the ${t.time_in_force} order filled nothing and was cancelled (${t.status})`, {
|
|
751
768
|
exchange: "qfex", reason: "iocNoFill", venueStatus: t.status, orderId: add.orderId, clientOrderId: add.clientOrderId, venueRaw,
|
|
752
769
|
remediation: "Nothing was filled. Widen --slippage-bps (within the price band), reduce the size, or place a resting limit order",
|
|
@@ -781,7 +798,7 @@ export class QfexAdapter {
|
|
|
781
798
|
}
|
|
782
799
|
/**
|
|
783
800
|
* Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
|
|
784
|
-
* terminal frame was received
|
|
801
|
+
* terminal frame was received. A pending result is never an outcome.
|
|
785
802
|
*/
|
|
786
803
|
iocOutcomeOf(raw) {
|
|
787
804
|
const r = raw;
|
|
@@ -1098,7 +1115,9 @@ export class QfexAdapter {
|
|
|
1098
1115
|
return (await this.getVenueMarkets()).map(toLegacyMarketInfo);
|
|
1099
1116
|
}
|
|
1100
1117
|
async getOrderbook(symbol) {
|
|
1101
|
-
|
|
1118
|
+
// Resolved against refdata first: an unknown symbol is SYMBOL_NOT_FOUND, not the venue's
|
|
1119
|
+
// HTTP 404 surfacing as a retryable EXCHANGE_ERROR.
|
|
1120
|
+
const venueSymbol = (await this.instrumentOf(symbol)).venueSymbol;
|
|
1102
1121
|
const book = await this.client.publicGet(`/md/orderbook/${encodeURIComponent(venueSymbol)}`, null);
|
|
1103
1122
|
// The venue lists empty price levels (quantity "0"); they are not part of the book.
|
|
1104
1123
|
const side = (v, what) => listOf(v, what)
|
|
@@ -1111,7 +1130,7 @@ export class QfexAdapter {
|
|
|
1111
1130
|
}
|
|
1112
1131
|
/** Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24). */
|
|
1113
1132
|
async getFundingHistory(symbol, limit) {
|
|
1114
|
-
const venueSymbol =
|
|
1133
|
+
const venueSymbol = (await this.instrumentOf(symbol)).venueSymbol;
|
|
1115
1134
|
const hours = limit === undefined ? 24 : limit;
|
|
1116
1135
|
if (!Number.isInteger(hours) || hours < 1)
|
|
1117
1136
|
throw new PerpError("INVALID_PARAMS", `funding history limit must be a positive integer of hours (got ${hours})`);
|
|
@@ -1135,14 +1154,15 @@ export class QfexAdapter {
|
|
|
1135
1154
|
if (resolution === undefined) {
|
|
1136
1155
|
throw new PerpError("INVALID_PARAMS", `Unsupported kline interval "${interval}" on QFEX`, { remediation: `Use one of: ${Object.keys(KLINE_RESOLUTION).join(", ")}` });
|
|
1137
1156
|
}
|
|
1138
|
-
const
|
|
1157
|
+
const venueSymbol = (await this.instrumentOf(symbol)).venueSymbol;
|
|
1158
|
+
const body = await this.client.publicGet(`/candles/${encodeURIComponent(venueSymbol)}`, {
|
|
1139
1159
|
resolution, fromISO: new Date(startTime).toISOString(), toISO: new Date(endTime).toISOString(),
|
|
1140
1160
|
});
|
|
1141
1161
|
return listOf(body.candles, "candles").map(c => {
|
|
1142
1162
|
const time = Date.parse(c.startedAt);
|
|
1143
1163
|
if (!Number.isFinite(time))
|
|
1144
1164
|
throw new PerpError("EXCHANGE_ERROR", `QFEX candles: invalid startedAt "${c.startedAt}"`);
|
|
1145
|
-
return { time, open: c.open, high: c.high, low: c.low, close: c.close,
|
|
1165
|
+
return { time, open: c.open, high: c.high, low: c.low, close: c.close, ...qfexCandleVolumes(c), trades: null };
|
|
1146
1166
|
}).sort((a, b) => a.time - b.time);
|
|
1147
1167
|
}
|
|
1148
1168
|
// ── Legacy ExchangeAdapter: account ──
|
|
@@ -1258,7 +1278,7 @@ export class QfexAdapter {
|
|
|
1258
1278
|
async getFundingPayments() {
|
|
1259
1279
|
throw unsupported("qfex", "fundingPayments", "funding payments are GET /executions rows, but the execution_type that marks them is not documented (Q10)", "See funding in the QFEX web app; net funding per position is in: perp account positions -e qfex --json");
|
|
1260
1280
|
}
|
|
1261
|
-
// ── Legacy ExchangeAdapter: trading (shim → TradingV2
|
|
1281
|
+
// ── Legacy ExchangeAdapter: trading (shim → TradingV2). Client ids are generated here. ──
|
|
1262
1282
|
async marketOrder(symbol, side, size, opts) {
|
|
1263
1283
|
return this.placeOrder({
|
|
1264
1284
|
symbol, side, type: "market", size, price: null, triggerPrice: null, triggerSource: null, tif: null,
|
|
@@ -1294,7 +1314,7 @@ export class QfexAdapter {
|
|
|
1294
1314
|
reduceOnly: opts?.reduceOnly === true, clientOrderId: qfexClientOrderId(), slippage: null, tpsl: null,
|
|
1295
1315
|
});
|
|
1296
1316
|
}
|
|
1297
|
-
// ── Funds
|
|
1317
|
+
// ── Funds ──
|
|
1298
1318
|
/**
|
|
1299
1319
|
* Deposit address for USDC on Arbitrum One (banker GET /address). The address is returned
|
|
1300
1320
|
* only when the venue confirms exactly that network and asset — anything else is refused,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX API key import
|
|
2
|
+
* QFEX API key import.
|
|
3
3
|
*
|
|
4
4
|
* Keys are created on the QFEX website (Developer Settings, 2FA) — there is no programmatic
|
|
5
5
|
* approval. The public key + secret are entered via hidden prompt / stdin only (never flags,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX API key import
|
|
2
|
+
* QFEX API key import.
|
|
3
3
|
*
|
|
4
4
|
* Keys are created on the QFEX website (Developer Settings, 2FA) — there is no programmatic
|
|
5
5
|
* approval. The public key + secret are entered via hidden prompt / stdin only (never flags,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX raw REST client
|
|
2
|
+
* QFEX raw REST client: raw fetch + node:crypto HMAC, no SDK.
|
|
3
3
|
*
|
|
4
4
|
* Two hosts: the API (`api.qfex.*` — market data, account reads, leverage, subaccount
|
|
5
5
|
* transfer) and the banker (`banker.qfex.*` — deposit address, withdraw). Both take the same
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX raw REST client
|
|
2
|
+
* QFEX raw REST client: raw fetch + node:crypto HMAC, no SDK.
|
|
3
3
|
*
|
|
4
4
|
* Two hosts: the API (`api.qfex.*` — market data, account reads, leverage, subaccount
|
|
5
5
|
* transfer) and the banker (`banker.qfex.*` — deposit address, withdraw). Both take the same
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX venue status / error → PerpError
|
|
2
|
+
* QFEX venue status / error → PerpError.
|
|
3
3
|
*
|
|
4
4
|
* Three sources:
|
|
5
5
|
* - order_response.status engine verdict on one order (definite once correlated);
|
|
@@ -90,7 +90,7 @@ const STATUS_REMEDIATION = {
|
|
|
90
90
|
CANNOT_MODIFY_PARTIAL_FILL: "A partially filled order cannot be modified — cancel it and place a new order",
|
|
91
91
|
CANNOT_MODIFY_ALO_WOULD_CROSS: "The new price would cross the book — choose a passive price, or cancel and place a LIMIT order",
|
|
92
92
|
REJECTED_MARKET_CLOSED: "The market is closed for this order right now — check the session: perp market info <symbol> -e qfex",
|
|
93
|
-
INVALID_CLIENT_ORDER_ID: "The venue refused the generated client order id — report it (
|
|
93
|
+
INVALID_CLIENT_ORDER_ID: "The venue refused the generated client order id — report it (QFEX item Q18)",
|
|
94
94
|
};
|
|
95
95
|
/** "… retry after 200ms" / "… retry after 2 seconds" → ms; null when the message carries no hint. */
|
|
96
96
|
export function parseRetryAfterMs(message) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX public market data
|
|
2
|
+
* QFEX public market data.
|
|
3
3
|
*
|
|
4
4
|
* Sources (all public):
|
|
5
5
|
* GET /refdata instruments: tick, lot, min/max quantity, price band, status,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 24h notional volume
|
|
9
9
|
* GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
|
|
10
10
|
*
|
|
11
|
-
* Coupling
|
|
11
|
+
* Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
|
|
12
12
|
* Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
|
|
13
13
|
* fails fetchFundingRates, never market listing or order entry.
|
|
14
14
|
*
|
|
@@ -89,7 +89,7 @@ export declare function qfexPlainSymbol(venueSymbol: string): string;
|
|
|
89
89
|
export declare function qfexVenueSymbol(symbol: string): string;
|
|
90
90
|
export declare function qfexAssetClass(row: Pick<QfexRefdataRow, "symbol" | "product_category" | "base_asset">): AssetClass;
|
|
91
91
|
export declare function toQfexInstrument(raw: unknown): QfexInstrument;
|
|
92
|
-
/** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE
|
|
92
|
+
/** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
|
|
93
93
|
export declare function qfexTradable(i: Pick<QfexInstrument, "status" | "quoteAsset">): boolean;
|
|
94
94
|
/**
|
|
95
95
|
* Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX public market data
|
|
2
|
+
* QFEX public market data.
|
|
3
3
|
*
|
|
4
4
|
* Sources (all public):
|
|
5
5
|
* GET /refdata instruments: tick, lot, min/max quantity, price band, status,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 24h notional volume
|
|
9
9
|
* GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
|
|
10
10
|
*
|
|
11
|
-
* Coupling
|
|
11
|
+
* Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
|
|
12
12
|
* Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
|
|
13
13
|
* fails fetchFundingRates, never market listing or order entry.
|
|
14
14
|
*
|
|
@@ -103,7 +103,7 @@ export function toQfexInstrument(raw) {
|
|
|
103
103
|
marketHours: hours !== null && typeof hours === "object" && !Array.isArray(hours) ? hours : null,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
-
/** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE
|
|
106
|
+
/** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
|
|
107
107
|
export function qfexTradable(i) {
|
|
108
108
|
return i.status === "ACTIVE" && i.quoteAsset === "USD";
|
|
109
109
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX request signing + wire-number conversion
|
|
2
|
+
* QFEX request signing + wire-number conversion.
|
|
3
3
|
*
|
|
4
4
|
* Auth (docs /websocket/channels/trade/authenticate, OpenAPI securitySchemes):
|
|
5
5
|
* signature = hex(HMAC-SHA256(secret, `${nonce}:${unix_ts}`))
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* fromWireNumber are the only places a decimal string becomes a float or the reverse.
|
|
13
13
|
*
|
|
14
14
|
* Wire facts that needed a live check are single constants here; while one is unset every code
|
|
15
|
-
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response
|
|
15
|
+
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response.
|
|
16
16
|
*/
|
|
17
17
|
import { PerpError } from "../../errors.js";
|
|
18
18
|
export interface QfexCredentials {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX request signing + wire-number conversion
|
|
2
|
+
* QFEX request signing + wire-number conversion.
|
|
3
3
|
*
|
|
4
4
|
* Auth (docs /websocket/channels/trade/authenticate, OpenAPI securitySchemes):
|
|
5
5
|
* signature = hex(HMAC-SHA256(secret, `${nonce}:${unix_ts}`))
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* fromWireNumber are the only places a decimal string becomes a float or the reverse.
|
|
13
13
|
*
|
|
14
14
|
* Wire facts that needed a live check are single constants here; while one is unset every code
|
|
15
|
-
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response
|
|
15
|
+
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response.
|
|
16
16
|
*/
|
|
17
17
|
import { createHmac, randomBytes } from "node:crypto";
|
|
18
18
|
import { PerpError } from "../../errors.js";
|
|
@@ -108,8 +108,7 @@ export const QFEX_MODIFY_KEEPS_CLIENT_ID = true;
|
|
|
108
108
|
* (it may be the user id). true → a mismatch is ORDER_PENDING.
|
|
109
109
|
*/
|
|
110
110
|
export const QFEX_USER_ID_IS_ACCOUNT = null;
|
|
111
|
-
const Q2_REMEDIATION = "Reduce / close the position on the QFEX web app. perp-cli sends reduce-only orders on QFEX only with the wire format of item Q2 "
|
|
112
|
-
+ "(docs/TEST-PLAN.md) set";
|
|
111
|
+
const Q2_REMEDIATION = "Reduce / close the position on the QFEX web app. perp-cli sends reduce-only orders on QFEX only with the wire format of item Q2 set";
|
|
113
112
|
/** The uniform refusal of every reduce-only path when the wire format is unset; null when it is set. */
|
|
114
113
|
export function qfexReduceOnlyGate(wire) {
|
|
115
114
|
if (wire !== null)
|
|
@@ -121,7 +120,7 @@ export function qfexAutomationGate(verified) {
|
|
|
121
120
|
if (verified === true)
|
|
122
121
|
return null;
|
|
123
122
|
return unsupported("qfex", "automation", "funding is unverified (Q4) and a resting reduce-only order the market reaches while flat is not proven harmless (Q2), and no automated flow places its own protective stop yet", "Trade QFEX with the manual commands (perp trade market / limit / stop / tpsl / close / reduce / close-all / flatten -e qfex) and watch the position yourself; "
|
|
124
|
-
+ "automation opens once
|
|
123
|
+
+ "automation opens once QFEX items Q2 / Q4 are settled");
|
|
125
124
|
}
|
|
126
125
|
/** `reduce_only` value for the wire in the verified format. */
|
|
127
126
|
export function reduceOnlyWireValue(wire, reduceOnly) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX Trade WebSocket transport
|
|
2
|
+
* QFEX Trade WebSocket transport.
|
|
3
3
|
*
|
|
4
4
|
* The venue has no request id. Correlation therefore rests on four rules:
|
|
5
5
|
* 1. ONE command at a time per connection (FIFO) — reads share the queue.
|
|
@@ -94,7 +94,7 @@ export interface QfexFillResponse {
|
|
|
94
94
|
export declare function orderResponseOf(frame: QfexFrame): QfexOrderResponse | null;
|
|
95
95
|
export declare function fillResponseOf(frame: QfexFrame): QfexFillResponse | null;
|
|
96
96
|
/**
|
|
97
|
-
* Error frame in any documented shape
|
|
97
|
+
* Error frame in any documented shape:
|
|
98
98
|
* { "err": { error_code, message, incoming_message } } /websocket/errors + AsyncAPI
|
|
99
99
|
* { "type": "Err", error_code, message, incoming_message } /websocket/rate
|
|
100
100
|
* { "type": "error", error_code, message } the docs' own client samples
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX Trade WebSocket transport
|
|
2
|
+
* QFEX Trade WebSocket transport.
|
|
3
3
|
*
|
|
4
4
|
* The venue has no request id. Correlation therefore rests on four rules:
|
|
5
5
|
* 1. ONE command at a time per connection (FIFO) — reads share the queue.
|
|
@@ -66,7 +66,7 @@ export function fillResponseOf(frame) {
|
|
|
66
66
|
return r;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Error frame in any documented shape
|
|
69
|
+
* Error frame in any documented shape:
|
|
70
70
|
* { "err": { error_code, message, incoming_message } } /websocket/errors + AsyncAPI
|
|
71
71
|
* { "type": "Err", error_code, message, incoming_message } /websocket/rate
|
|
72
72
|
* { "type": "error", error_code, message } the docs' own client samples
|
|
@@ -86,7 +86,7 @@ export function errFrameOf(frame) {
|
|
|
86
86
|
: null;
|
|
87
87
|
return { errorCode: body.error_code, message: typeof body.message === "string" ? body.message : null, echo };
|
|
88
88
|
}
|
|
89
|
-
/** Auth outcome frame: both documented success shapes are accepted
|
|
89
|
+
/** Auth outcome frame: both documented success shapes are accepted. */
|
|
90
90
|
function authVerdictOf(frame) {
|
|
91
91
|
if (typeof frame.authenticated === "boolean")
|
|
92
92
|
return frame.authenticated;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-process token-bucket rate limiter keyed by venue
|
|
2
|
+
* Per-process token-bucket rate limiter keyed by venue.
|
|
3
3
|
*
|
|
4
4
|
* Limitation (documented): this only coordinates requests inside one process.
|
|
5
5
|
* CLI + MCP server + bots running at once can still exceed a venue's limit.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-process token-bucket rate limiter keyed by venue
|
|
2
|
+
* Per-process token-bucket rate limiter keyed by venue.
|
|
3
3
|
*
|
|
4
4
|
* Limitation (documented): this only coordinates requests inside one process.
|
|
5
5
|
* CLI + MCP server + bots running at once can still exceed a venue's limit.
|
|
@@ -19,7 +19,10 @@ class TokenBucket {
|
|
|
19
19
|
}
|
|
20
20
|
refill() {
|
|
21
21
|
const now = Date.now();
|
|
22
|
-
|
|
22
|
+
// Clamped: a wall clock stepping backwards (NTP, VM resume, fake timers) must not drive the bucket
|
|
23
|
+
// negative and stall every request until the clock catches up.
|
|
24
|
+
const elapsedMs = Math.max(0, now - this.last);
|
|
25
|
+
this.tokens = Math.min(this.policy.capacity, this.tokens + (elapsedMs / 1000) * this.policy.refillPerSec);
|
|
23
26
|
this.last = now;
|
|
24
27
|
}
|
|
25
28
|
acquire(weight) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapter registry — legacy name/alias API, now derived from the venue
|
|
3
|
-
* definitions (src/exchanges/venues
|
|
3
|
+
* definitions (src/exchanges/venues/). New venues are added by
|
|
4
4
|
* registering a VenueDefinition, not here.
|
|
5
5
|
*/
|
|
6
6
|
import "./venues/index.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapter registry — legacy name/alias API, now derived from the venue
|
|
3
|
-
* definitions (src/exchanges/venues
|
|
3
|
+
* definitions (src/exchanges/venues/). New venues are added by
|
|
4
4
|
* registering a VenueDefinition, not here.
|
|
5
5
|
*/
|
|
6
6
|
import { getVenue, listVenues, tryResolveVenueId } from "./venue.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RISEx adapter — market data (public), account reads (public by address) and trading through the
|
|
3
|
+
* registered, trading-only session signer (EIP-712 VerifyWitness permits; the server submits the
|
|
4
|
+
* on-chain transaction and pays gas).
|
|
5
|
+
*
|
|
6
|
+
* Account address: the signer's account (agent meta), else the master wallet's address (reads
|
|
7
|
+
* only, nothing is signed with it here). Writes need the signer.
|
|
8
|
+
*
|
|
9
|
+
* Order safety:
|
|
10
|
+
* - every refusal (market flags, maintenance, precision, pins) happens before a nonce is
|
|
11
|
+
* allocated or anything is signed;
|
|
12
|
+
* - every permit consumes a fresh (anchor, bit) pair that is persisted BEFORE the send, so a
|
|
13
|
+
* pair is never reused, even when the outcome is unknown;
|
|
14
|
+
* - a client order id is always attached (generated uint64 when the caller gives none);
|
|
15
|
+
* - an unknown send outcome → one read-only reconciliation (permit nonce consumed on-chain? the
|
|
16
|
+
* client order id in open orders / order history?) → a proven result carries notes
|
|
17
|
+
* ["reconciled"]; otherwise ORDER_PENDING ("do NOT resubmit") and the symbol is quarantined.
|
|
18
|
+
* The permit deadline is short (RISEX_PERMIT_TTL_S), so an unproven permit expires quickly.
|
|
19
|
+
*/
|
|
20
|
+
import { PerpError } from "../../errors.js";
|
|
21
|
+
import type { RisexAgentMeta } from "../../settings.js";
|
|
22
|
+
import type { EvmSigner } from "../../signer/interface.js";
|
|
23
|
+
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
|
|
24
|
+
import type { ResolvedSlippage } from "../slippage.js";
|
|
25
|
+
import { type TradingV2 } from "../trading-v2.js";
|
|
26
|
+
import type { VenueCreateContext, VenueDefinition } from "../venue.js";
|
|
27
|
+
import { type AmendRequest, type CancelResult, type LeverageResult, type Network, type OrderRef, type OrderRequest, type OrderResult, type VenueMarket } from "../venue-types.js";
|
|
28
|
+
import { type RisexChainReader } from "./session.js";
|
|
29
|
+
/** Permit lifetime: long enough for the server to submit, short enough that an unproven permit expires soon. */
|
|
30
|
+
export declare const RISEX_PERMIT_TTL_S = 120;
|
|
31
|
+
/** How long a successful on-chain trading-only proof is trusted before the next permit re-checks it. */
|
|
32
|
+
export declare const RISEX_TRADING_ONLY_TTL_MS = 60000;
|
|
33
|
+
export interface RisexAdapterDeps {
|
|
34
|
+
/** Account the reads and permits are for; null = public market data only. */
|
|
35
|
+
account: string | null;
|
|
36
|
+
/** Trading-only session signer; null = no trading. */
|
|
37
|
+
signer: EvmSigner | null;
|
|
38
|
+
/** Agent meta the signer came from (for messages); null when none. */
|
|
39
|
+
meta: RisexAgentMeta | null;
|
|
40
|
+
/** Why trading is unavailable when `signer` is null (the uniform refusal). */
|
|
41
|
+
noSignerReason: PerpError | null;
|
|
42
|
+
nonceDir?: string;
|
|
43
|
+
chain?: RisexChainReader;
|
|
44
|
+
sleep?: (ms: number) => Promise<void>;
|
|
45
|
+
/** Quarantine hook for unknown outcomes (default: bot/quarantine.ts). */
|
|
46
|
+
quarantine?: (symbol: string, reason: string, remediation: string) => void;
|
|
47
|
+
}
|
|
48
|
+
export declare class RisexAdapter implements ExchangeAdapter, TradingV2 {
|
|
49
|
+
readonly name: "risex";
|
|
50
|
+
readonly aliases: readonly ["rise"];
|
|
51
|
+
readonly venue: VenueDefinition;
|
|
52
|
+
readonly network: Network;
|
|
53
|
+
private readonly client;
|
|
54
|
+
private readonly deps;
|
|
55
|
+
private markets;
|
|
56
|
+
constructor(def: VenueDefinition, network: Network, deps: RisexAdapterDeps);
|
|
57
|
+
/** Resolve the account and the trading-only signer from the create context (OWS only). */
|
|
58
|
+
static create(def: VenueDefinition, ctx: VenueCreateContext): Promise<RisexAdapter>;
|
|
59
|
+
/**
|
|
60
|
+
* Trading capabilities — also the adapter-level release gate: while the trading role is gated
|
|
61
|
+
* (risex-release.ts) this throws CAPABILITY_UNSUPPORTED before any validation, nonce or
|
|
62
|
+
* signature. Read paths never touch it, so the adapter still serves market data / account reads.
|
|
63
|
+
*/
|
|
64
|
+
private get caps();
|
|
65
|
+
private requireAccount;
|
|
66
|
+
private requireSigner;
|
|
67
|
+
private sleep;
|
|
68
|
+
private rawMarkets;
|
|
69
|
+
/** Active market by symbol ("BTC", "BTC/USDC", "BTC-PERP") or market id. */
|
|
70
|
+
private market;
|
|
71
|
+
private symbolOfId;
|
|
72
|
+
/**
|
|
73
|
+
* The on-chain trading-only proof, re-checked at most every RISEX_TRADING_ONLY_TTL_MS before a
|
|
74
|
+
* permit (long-lived adapters — perp-mcp, bots — must notice a contract upgrade). A failed proof
|
|
75
|
+
* (refusal or RPC error) is never cached.
|
|
76
|
+
*/
|
|
77
|
+
private tradingOnlyProof;
|
|
78
|
+
private permit;
|
|
79
|
+
private chainRead;
|
|
80
|
+
private quarantine;
|
|
81
|
+
/** The account reads / permits are for (signer's account, else the master address). */
|
|
82
|
+
accountAddress(): string;
|
|
83
|
+
/** Testnet faucet: 1000 test USDC, once per wallet (also registers the account). Refused on mainnet. */
|
|
84
|
+
testnetFaucet(): Promise<{
|
|
85
|
+
txHash: string | null;
|
|
86
|
+
}>;
|
|
87
|
+
getVenueMarkets(): Promise<VenueMarket[]>;
|
|
88
|
+
/** Legacy rows plus the venue's market-level order restrictions. */
|
|
89
|
+
getMarkets(): Promise<Array<ExchangeMarketInfo & {
|
|
90
|
+
marketId: string;
|
|
91
|
+
reduceOnlyMarket: boolean;
|
|
92
|
+
postOnlyMarket: boolean;
|
|
93
|
+
tradable: boolean;
|
|
94
|
+
}>>;
|
|
95
|
+
getOrderbook(symbol: string): Promise<{
|
|
96
|
+
bids: [string, string][];
|
|
97
|
+
asks: [string, string][];
|
|
98
|
+
}>;
|
|
99
|
+
getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
|
|
100
|
+
getFundingHistory(symbol: string, limit?: number): Promise<{
|
|
101
|
+
time: number;
|
|
102
|
+
rate: string;
|
|
103
|
+
price: string | null;
|
|
104
|
+
}[]>;
|
|
105
|
+
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
|
|
106
|
+
private portfolio;
|
|
107
|
+
getBalance(): Promise<ExchangeBalance>;
|
|
108
|
+
getPositions(): Promise<ExchangePosition[]>;
|
|
109
|
+
private openOrdersRaw;
|
|
110
|
+
private historyOrders;
|
|
111
|
+
private toExchangeOrder;
|
|
112
|
+
/** On-chain open orders, enriched with the indexer's decimals / fills (one retry for indexer lag). */
|
|
113
|
+
getOpenOrders(): Promise<ExchangeOrder[]>;
|
|
114
|
+
getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
|
|
115
|
+
getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
|
|
116
|
+
/** `payment` is positive when RECEIVED (RISEx `funding_payment` is positive when paid — negated here). */
|
|
117
|
+
getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
|
|
118
|
+
placeOrder(req: OrderRequest): Promise<OrderResult>;
|
|
119
|
+
/** One read-only reconciliation of an unknown place outcome. */
|
|
120
|
+
private reconcilePlace;
|
|
121
|
+
amendOrder(_req: AmendRequest): Promise<OrderResult>;
|
|
122
|
+
cancelByRef(symbol: string, ref: OrderRef): Promise<CancelResult>;
|
|
123
|
+
cancelAllV2(symbol: string | null): Promise<CancelResult>;
|
|
124
|
+
applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
|
|
125
|
+
private sendAccountAction;
|
|
126
|
+
getOrderStatus(symbol: string, ref: OrderRef): Promise<OrderResult>;
|
|
127
|
+
marketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
|
|
128
|
+
reduceOnly: boolean;
|
|
129
|
+
slippage: ResolvedSlippage;
|
|
130
|
+
clientOrderId?: string;
|
|
131
|
+
}): Promise<unknown>;
|
|
132
|
+
limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
|
|
133
|
+
reduceOnly?: boolean;
|
|
134
|
+
tif?: string;
|
|
135
|
+
clientOrderId?: string;
|
|
136
|
+
}): Promise<unknown>;
|
|
137
|
+
editOrder(): Promise<unknown>;
|
|
138
|
+
cancelOrder(symbol: string, orderId: string): Promise<unknown>;
|
|
139
|
+
cancelAllOrders(symbol?: string): Promise<unknown>;
|
|
140
|
+
setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
|
|
141
|
+
stopOrder(): Promise<unknown>;
|
|
142
|
+
/** TP/SL entries are refused before the entry is sent (not implemented yet). */
|
|
143
|
+
stopOrderGate(): PerpError;
|
|
144
|
+
/** Bots / TWAP / grid / funding-arb legs stay off until RISEx trading is verified on mainnet. */
|
|
145
|
+
automationGate(): PerpError;
|
|
146
|
+
}
|
|
147
|
+
/** Candle intervals → ms (the venue takes the interval in ns). */
|
|
148
|
+
export declare const KLINE_INTERVAL_MS: Readonly<Record<string, number>>;
|