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
package/dist/exchanges/aster.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
|
|
19
19
|
* Base: https://fapi.asterdex.com
|
|
20
20
|
*/
|
|
21
|
+
import { nonEmptyOrListed } from "./unknown-symbol.js";
|
|
21
22
|
import { accountCacheEpoch } from "../cache.js";
|
|
22
23
|
import { isExpired } from "../agent-wallet/expiry.js";
|
|
23
24
|
import { classifyError, DEFINITE_ORDER_FAILURE_CODES, isTransportFailure, orderNotSent, orderOutcomeUnknown, PerpError, sendFailure } from "../errors.js";
|
|
@@ -30,7 +31,7 @@ import { approvalFromMax, bpsToRate, builderGateOf, NO_BUILDER, rateToBps, requi
|
|
|
30
31
|
import { ASTER_CLIENT_ID_RE, ASTER_FUNDS_CHAINS, asterVenue } from "./venues/aster.js";
|
|
31
32
|
import { assertAsterWithdrawAmount, assertAsterWithdrawSignature, parseAsterWithdrawLimits, requireAsterFundsNetwork, } from "./aster-funds.js";
|
|
32
33
|
import { nextAsterNonce } from "./aster-nonce.js";
|
|
33
|
-
/** Common TIF
|
|
34
|
+
/** Common TIF → Aster perp timeInForce. Live exchangeInfo lists GTC/IOC/GTX/HIDDEN — no FOK. */
|
|
34
35
|
function asterTif(raw) {
|
|
35
36
|
const t = raw.toUpperCase();
|
|
36
37
|
if (t === "GTC" || t === "IOC" || t === "GTX")
|
|
@@ -72,6 +73,16 @@ export function asterIocOutcome(raw) {
|
|
|
72
73
|
const filledSize = plainQty(r.executedQty);
|
|
73
74
|
return filledSize === null ? null : { filledSize, final: true };
|
|
74
75
|
}
|
|
76
|
+
/** The numeric `code` of an Aster error body ({"code":-1121,"msg":"Invalid symbol."}); null when absent. */
|
|
77
|
+
function asterErrorCode(body) {
|
|
78
|
+
try {
|
|
79
|
+
const j = JSON.parse(body);
|
|
80
|
+
return typeof j.code === "number" ? j.code : null;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null; // not JSON (e.g. an HTML error page): no venue code
|
|
84
|
+
}
|
|
85
|
+
}
|
|
75
86
|
/**
|
|
76
87
|
* Order-placement refusals by documented venue code (asterdex/api-docs V3 futures "Error
|
|
77
88
|
* Codes"; "Codes are universal,but messages can vary"). [PerpError code, docs name, remediation].
|
|
@@ -283,7 +294,7 @@ export class AsterAdapter {
|
|
|
283
294
|
return null;
|
|
284
295
|
}
|
|
285
296
|
}
|
|
286
|
-
// ── Spot host access (
|
|
297
|
+
// ── Spot host access (used by AsterSpotAdapter) ──
|
|
287
298
|
/**
|
|
288
299
|
* Aster spot REST host. Spot shares the perp EIP-712 Domain B signing
|
|
289
300
|
* (chainId 1666 mainnet / 714 testnet) and the per-agent nonce space.
|
|
@@ -466,7 +477,7 @@ export class AsterAdapter {
|
|
|
466
477
|
this._ingestPriceFilters(info.symbols);
|
|
467
478
|
}
|
|
468
479
|
if (!this._priceFilters.has(apiSymbol)) {
|
|
469
|
-
throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster" });
|
|
480
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
|
|
470
481
|
}
|
|
471
482
|
const f = this._priceFilters.get(apiSymbol);
|
|
472
483
|
if (!f) {
|
|
@@ -475,7 +486,7 @@ export class AsterAdapter {
|
|
|
475
486
|
return f;
|
|
476
487
|
}
|
|
477
488
|
/**
|
|
478
|
-
* Declared protection-price reference
|
|
489
|
+
* Declared protection-price reference: the venue mark price from
|
|
479
490
|
* GET /fapi/v3/premiumIndex — the same price PERCENT_PRICE is evaluated against.
|
|
480
491
|
* No runtime substitute (no mid / last fallback): unavailable → PRICE_STALE.
|
|
481
492
|
*/
|
|
@@ -555,7 +566,7 @@ export class AsterAdapter {
|
|
|
555
566
|
side: t.isBuyerMaker ? "sell" : "buy",
|
|
556
567
|
price: String(t.price ?? "0"),
|
|
557
568
|
size: String(t.qty ?? "0"),
|
|
558
|
-
fee:
|
|
569
|
+
fee: null, // the public tape carries no fee
|
|
559
570
|
}));
|
|
560
571
|
}
|
|
561
572
|
async getFundingHistory(symbol, limit = 10) {
|
|
@@ -566,7 +577,8 @@ export class AsterAdapter {
|
|
|
566
577
|
if (!Array.isArray(data)) {
|
|
567
578
|
throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate history is not a list (${JSON.stringify(data).slice(0, 120)})`, { exchange: "aster" });
|
|
568
579
|
}
|
|
569
|
-
|
|
580
|
+
// `200 []` for an unlisted symbol (live 2026-10-14): checked against the market directory.
|
|
581
|
+
return nonEmptyOrListed(this, symbol, data.map((d) => {
|
|
570
582
|
const time = Number(d.fundingTime);
|
|
571
583
|
if (d.fundingTime === undefined || d.fundingTime === null || !Number.isFinite(time) || time <= 0) {
|
|
572
584
|
throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate row without a numeric fundingTime: ${JSON.stringify(d).slice(0, 160)}`, { exchange: "aster" });
|
|
@@ -576,7 +588,7 @@ export class AsterAdapter {
|
|
|
576
588
|
rate: venueRateField(d.fundingRate, "fundingRate fundingRate", "aster"),
|
|
577
589
|
price: d.markPrice ? String(d.markPrice) : null,
|
|
578
590
|
};
|
|
579
|
-
});
|
|
591
|
+
}), this._fromApi(this._toApi(symbol)));
|
|
580
592
|
}
|
|
581
593
|
/**
|
|
582
594
|
* Candles over [startTime, endTime]. GET /fapi/v3/klines returns at most 1500 bars per request
|
|
@@ -607,10 +619,11 @@ export class AsterAdapter {
|
|
|
607
619
|
for (const k of data) {
|
|
608
620
|
const time = Number(k[0]);
|
|
609
621
|
const trades = Number(k[8]);
|
|
610
|
-
|
|
611
|
-
|
|
622
|
+
const numeric = (v) => (typeof v === "string" || typeof v === "number") && String(v).trim() !== "" && Number.isFinite(Number(v));
|
|
623
|
+
if (!Number.isFinite(time) || !Number.isFinite(trades) || !numeric(k[5]) || !numeric(k[7])) {
|
|
624
|
+
throw new PerpError("EXCHANGE_ERROR", `Aster kline row without open time / volume / quote volume / trade count: ${JSON.stringify(k).slice(0, 120)}`, { exchange: "aster" });
|
|
612
625
|
}
|
|
613
|
-
out.push({ time, open: String(k[1]), high: String(k[2]), low: String(k[3]), close: String(k[4]), volume: String(k[5]), trades });
|
|
626
|
+
out.push({ time, open: String(k[1]), high: String(k[2]), low: String(k[3]), close: String(k[4]), volume: String(k[5]), quoteVolume: String(k[7]), trades });
|
|
614
627
|
}
|
|
615
628
|
if (data.length < ASTER_KLINE_PAGE)
|
|
616
629
|
break;
|
|
@@ -975,7 +988,7 @@ export class AsterAdapter {
|
|
|
975
988
|
}
|
|
976
989
|
// ── Trading ──
|
|
977
990
|
async marketOrder(symbol, side, size, opts) {
|
|
978
|
-
//
|
|
991
|
+
// Never native MARKET (no price cap) — an IOC LIMIT at the
|
|
979
992
|
// protection price (mark ± opts.slippage.bps, tick-aligned toward crossing, inside
|
|
980
993
|
// PRICE_FILTER / PERCENT_PRICE). newOrderRespType=RESULT makes the response carry the
|
|
981
994
|
// final IOC status (FILLED / EXPIRED) and executedQty.
|
|
@@ -1072,7 +1085,7 @@ export class AsterAdapter {
|
|
|
1072
1085
|
const clientOrderId = opts?.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
|
|
1073
1086
|
let params;
|
|
1074
1087
|
try {
|
|
1075
|
-
// Common TIF names
|
|
1088
|
+
// Common TIF names: GTC / IOC / ALO→GTX (GTX accepted as alias); FOK unsupported.
|
|
1076
1089
|
const tif = asterTif(opts?.tif ?? "GTC");
|
|
1077
1090
|
// Price must already be on PRICE_FILTER.tickSize — validated, never rounded (INVALID_TICK).
|
|
1078
1091
|
const px = requireOnTick(price, (await this._symbolPriceFilters(apiSymbol)).tickSize);
|
|
@@ -1104,7 +1117,7 @@ export class AsterAdapter {
|
|
|
1104
1117
|
/**
|
|
1105
1118
|
* Native modify (PUT /fapi/v3/order, V3 futures docs "Modify Order"): LIMIT orders
|
|
1106
1119
|
* only; TIF (incl. GTX) and reduceOnly stay on the order. `size` is the NEW UNFILLED
|
|
1107
|
-
* quantity
|
|
1120
|
+
* quantity; the venue's `quantity` is the order total, so
|
|
1108
1121
|
* quantity = size + executedQty (read just before). A fill landing between the read
|
|
1109
1122
|
* and the PUT can only shrink the remainder, never over-size it. A venue rejection
|
|
1110
1123
|
* leaves the original order untouched (docs).
|
|
@@ -1757,6 +1770,11 @@ export class AsterAdapter {
|
|
|
1757
1770
|
if (!res.ok) {
|
|
1758
1771
|
const text = await res.text();
|
|
1759
1772
|
const clean = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
1773
|
+
// -1121 BAD_SYMBOL ("Invalid symbol.", live on /fapi/v3/depth for an unlisted symbol): the
|
|
1774
|
+
// symbol does not exist — SYMBOL_NOT_FOUND, not an UNKNOWN 500.
|
|
1775
|
+
if (asterErrorCode(text) === -1121) {
|
|
1776
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Aster: ${clean} [${method} ${path}]`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
|
|
1777
|
+
}
|
|
1760
1778
|
throw new Error(`${method} ${path} failed (${res.status}): ${clean}`);
|
|
1761
1779
|
}
|
|
1762
1780
|
return res.json();
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* `builder: { b: <address>, f: <tenths of a bp> }`; max 0.1% perps / 1% spot; status
|
|
22
22
|
* info `{ type: "maxBuilderFee", user, builder }`. HIP-3 dexes are not covered by the
|
|
23
23
|
* docs → never attached there.
|
|
24
|
-
* - pacifica https://pacifica.
|
|
24
|
+
* - pacifica https://docs.pacifica.fi/programs/builder-program.md
|
|
25
25
|
* approve_builder_code { builder_code, max_fee_rate } signed by the account; per-order
|
|
26
26
|
* `builder_code` on create_market / create / stop/create / positions/tpsl. TWAP
|
|
27
27
|
* (create_twap_order) does not document builder_code → never sent there.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* `builder: { b: <address>, f: <tenths of a bp> }`; max 0.1% perps / 1% spot; status
|
|
22
22
|
* info `{ type: "maxBuilderFee", user, builder }`. HIP-3 dexes are not covered by the
|
|
23
23
|
* docs → never attached there.
|
|
24
|
-
* - pacifica https://pacifica.
|
|
24
|
+
* - pacifica https://docs.pacifica.fi/programs/builder-program.md
|
|
25
25
|
* approve_builder_code { builder_code, max_fee_rate } signed by the account; per-order
|
|
26
26
|
* `builder_code` on create_market / create / stop/create / positions/tpsl. TWAP
|
|
27
27
|
* (create_twap_order) does not document builder_code → never sent there.
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
*/
|
|
50
50
|
import { PerpError, classifyError, isDefiniteOrderFailure, isTransportFailure, isUnknownOutcomeServerError } from "../errors.js";
|
|
51
51
|
import { isVenueId } from "./venue-types.js";
|
|
52
|
+
import { VARIATIONAL_READ_ONLY_REMEDIATION } from "./venues/variational.js";
|
|
52
53
|
export const BUILDER_FEE_CAPABILITIES = {
|
|
53
54
|
hyperliquid: {
|
|
54
55
|
approval: "master", identity: "address", perpCapBps: 10, spotCapBps: 100, hip3: false, testnet: true,
|
|
@@ -56,7 +57,7 @@ export const BUILDER_FEE_CAPABILITIES = {
|
|
|
56
57
|
},
|
|
57
58
|
pacifica: {
|
|
58
59
|
approval: "master", identity: "code", perpCapBps: 10, spotCapBps: null, hip3: null, testnet: true,
|
|
59
|
-
feeStepBps: 0.01, feeKind: "max", docs: "https://pacifica.
|
|
60
|
+
feeStepBps: 0.01, feeKind: "max", docs: "https://docs.pacifica.fi/programs/builder-program.md",
|
|
60
61
|
},
|
|
61
62
|
aster: {
|
|
62
63
|
approval: "master", identity: "address", perpCapBps: 10, spotCapBps: null, hip3: null, testnet: false,
|
|
@@ -73,11 +74,21 @@ export const BUILDER_FEE_CAPABILITIES = {
|
|
|
73
74
|
lighter: null,
|
|
74
75
|
"lighter-rh": null,
|
|
75
76
|
arcus: null,
|
|
77
|
+
risex: null,
|
|
78
|
+
txflow: null,
|
|
79
|
+
variational: null,
|
|
80
|
+
grvt: null,
|
|
81
|
+
extended: null,
|
|
76
82
|
};
|
|
77
83
|
/** Why a venue has no builder-fee support (shown in CAPABILITY_UNSUPPORTED). */
|
|
78
84
|
export const BUILDER_FEE_UNSUPPORTED = {
|
|
79
85
|
lighter: "not yet implemented in perp-cli — the official lighter-go signer can sign ApproveIntegrator and the per-order integrator fields, but perp-cli sends them as 0 (https://apidocs.lighter.xyz/docs/partner-integration)",
|
|
80
86
|
"lighter-rh": "not yet implemented in perp-cli — Lighter RH uses the same official lighter-go signer; partner-attribution fields are sent as 0 (https://apidocs.rh.lighter.xyz/docs/partner-attribution.md)",
|
|
87
|
+
risex: "not implemented yet in perp-cli (RISEx builder codes are planned; note the venue's \"bps\" field is parts-per-million)",
|
|
88
|
+
txflow: "TxFlow is integrated through an unofficial read-only API (market data only)",
|
|
89
|
+
variational: VARIATIONAL_READ_ONLY_REMEDIATION,
|
|
90
|
+
grvt: "GRVT is read-only (market data) in this version; trading is planned for a later release",
|
|
91
|
+
extended: "Extended is available for market data only in this version (trading is planned for a later release)",
|
|
81
92
|
arcus: "Arcus has no perps builder fee; its integrator fee applies to the spot router only and needs a partner key from Arcus (https://docs.arcus.xyz/concepts/spot-router)",
|
|
82
93
|
};
|
|
83
94
|
export function builderFeeUnsupported(venue) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-venue canonical symbols — the single mapping source used to decide
|
|
3
|
-
* that two markets on different venues are the same underlying
|
|
4
|
-
* (design §4 L4, §10.9).
|
|
3
|
+
* that two markets on different venues are the same underlying.
|
|
5
4
|
*
|
|
6
5
|
* Rules by asset class:
|
|
7
6
|
* - crypto: ticker identity after the adapter strips its quote suffix, plus
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-venue canonical symbols — the single mapping source used to decide
|
|
3
|
-
* that two markets on different venues are the same underlying
|
|
4
|
-
* (design §4 L4, §10.9).
|
|
3
|
+
* that two markets on different venues are the same underlying.
|
|
5
4
|
*
|
|
6
5
|
* Rules by asset class:
|
|
7
6
|
* - crypto: ticker identity after the adapter strips its quote suffix, plus
|
|
@@ -47,19 +46,44 @@ export const EXPLICIT_CANONICAL = {
|
|
|
47
46
|
"hl:xyz|SILVER": "SILVER",
|
|
48
47
|
"qfex|GOLD": "GOLD",
|
|
49
48
|
"qfex|SILVER": "SILVER",
|
|
49
|
+
"risex|XAU": "GOLD",
|
|
50
|
+
"risex|XAG": "SILVER",
|
|
51
|
+
"txflow|XAU": "GOLD",
|
|
52
|
+
"txflow|XAG": "SILVER",
|
|
53
|
+
"variational|XAU": "GOLD",
|
|
54
|
+
"variational|XAG": "SILVER",
|
|
55
|
+
"grvt|XAU": "GOLD",
|
|
56
|
+
"grvt|XAG": "SILVER",
|
|
57
|
+
"extended|XAU": "GOLD",
|
|
58
|
+
"extended|XAG": "SILVER",
|
|
50
59
|
// Crude oil (WTI)
|
|
51
60
|
"ondo|WTI": "CRUDE_OIL_WTI",
|
|
52
61
|
"hl:xyz|CL": "CRUDE_OIL_WTI",
|
|
53
62
|
"hl:flx|OIL": "CRUDE_OIL_WTI",
|
|
54
63
|
// QFEX CL blends expiring futures; roll-week gaps are caught by the cross-venue price filter.
|
|
55
64
|
"qfex|CL": "CRUDE_OIL_WTI",
|
|
65
|
+
"risex|CL": "CRUDE_OIL_WTI",
|
|
66
|
+
// Brent crude
|
|
67
|
+
"risex|BZ": "CRUDE_OIL_BRENT",
|
|
68
|
+
"txflow|BZ": "CRUDE_OIL_BRENT",
|
|
69
|
+
"txflow|CL": "CRUDE_OIL_WTI",
|
|
70
|
+
"variational|BZ": "CRUDE_OIL_BRENT",
|
|
71
|
+
"variational|CL": "CRUDE_OIL_WTI",
|
|
72
|
+
"grvt|BZ": "CRUDE_OIL_BRENT",
|
|
73
|
+
"grvt|CL": "CRUDE_OIL_WTI",
|
|
74
|
+
"extended|WTI": "CRUDE_OIL_WTI",
|
|
75
|
+
"extended|XBR": "CRUDE_OIL_BRENT",
|
|
56
76
|
// US equity indices
|
|
57
77
|
"ondo|US500": "SPX",
|
|
58
78
|
"ondo|US100": "NDX",
|
|
59
79
|
"qfex|US500": "SPX",
|
|
60
80
|
"qfex|US100": "NDX",
|
|
81
|
+
// Extended SPX500m / TECH100m: S&P 500 / Nasdaq-100 index perps (uiName SPX-USD / NDX-USD).
|
|
82
|
+
"extended|SPX500M": "SPX",
|
|
83
|
+
"extended|TECH100M": "NDX",
|
|
61
84
|
// FX
|
|
62
85
|
"ondo|USDJPY": "USDJPY",
|
|
86
|
+
"extended|USDJPY": "USDJPY",
|
|
63
87
|
};
|
|
64
88
|
/** Pairs verified to be DIFFERENT products despite similar names. Never match. */
|
|
65
89
|
export const NEVER_MATCH = new Set([
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive (derive.xyz, formerly Lyra) v3 public JSON-RPC over HTTP — official API, no auth.
|
|
3
|
+
*
|
|
4
|
+
* - `POST <base>/public/<method>` with the params object as the body → `{id, result}` or
|
|
5
|
+
* `{id, error: {code, message, data}}` (docs.derive.xyz/error-codes.md; live 2026-10-18).
|
|
6
|
+
* - Errors are all PerpErrors, each with a remediation: transport failure → EXCHANGE_UNREACHABLE;
|
|
7
|
+
* HTTP 429 or RPC -32000..-32099 → RATE_LIMITED (retry hint from the RPC `data` "Retry after {ms} ms",
|
|
8
|
+
* else the `retry-after` header); any other RPC error → EXCHANGE_ERROR. A JSON-RPC -32602 is a
|
|
9
|
+
* Derive-side rejection too: user input is validated before any request, so it is never reported as
|
|
10
|
+
* the user's INVALID_PARAMS. Every RPC error carries `details.rpcCode` / `rpcData` so callers can
|
|
11
|
+
* classify the venue code (12001 instrument_not_found → SYMBOL_NOT_FOUND is done by the caller,
|
|
12
|
+
* which knows what was asked).
|
|
13
|
+
* - Public budget (docs rate-limits.md, mainnet reference): 5 TPS per IP, FIXED 5 s window of 25
|
|
14
|
+
* points, 1 point per request. The local bucket (burst 5, 4/s) allows at most 5 + 4·5 = 25 in any 5 s.
|
|
15
|
+
*/
|
|
16
|
+
import { PerpError } from "../../errors.js";
|
|
17
|
+
import type { RateLimitPolicy } from "../venue.js";
|
|
18
|
+
import type { Network } from "../venue-types.js";
|
|
19
|
+
export declare const DERIVE_REST_URLS: Readonly<Record<Network, string>>;
|
|
20
|
+
export declare const DERIVE_HTTP_TIMEOUT_MS = 15000;
|
|
21
|
+
export declare const DERIVE_RATE_LIMIT: RateLimitPolicy;
|
|
22
|
+
/** Derive application code for an unknown instrument / currency (`instrument_not_found`). */
|
|
23
|
+
export declare const DERIVE_INSTRUMENT_NOT_FOUND = 12001;
|
|
24
|
+
/** JSON-RPC invalid params — Derive also uses it for "Ticker not found for instrument" (live 2026-10-18). */
|
|
25
|
+
export declare const DERIVE_INVALID_PARAMS = -32602;
|
|
26
|
+
/** Shape drift: an explicit EXCHANGE_ERROR, never a guessed value. */
|
|
27
|
+
export declare function deriveShapeError(what: string): PerpError;
|
|
28
|
+
/** The RPC error code a Derive call failed with, or null (not an RPC error). */
|
|
29
|
+
export declare function deriveRpcCode(e: unknown): number | null;
|
|
30
|
+
export declare class DeriveClient {
|
|
31
|
+
readonly network: Network;
|
|
32
|
+
private readonly baseUrl;
|
|
33
|
+
private readonly rateLimit;
|
|
34
|
+
constructor(network: Network, baseUrl?: string, rateLimit?: RateLimitPolicy);
|
|
35
|
+
/** `public/<method>`; resolves with `result`, every failure is a PerpError. */
|
|
36
|
+
call<T = unknown>(method: string, params: Record<string, unknown>): Promise<T>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive (derive.xyz, formerly Lyra) v3 public JSON-RPC over HTTP — official API, no auth.
|
|
3
|
+
*
|
|
4
|
+
* - `POST <base>/public/<method>` with the params object as the body → `{id, result}` or
|
|
5
|
+
* `{id, error: {code, message, data}}` (docs.derive.xyz/error-codes.md; live 2026-10-18).
|
|
6
|
+
* - Errors are all PerpErrors, each with a remediation: transport failure → EXCHANGE_UNREACHABLE;
|
|
7
|
+
* HTTP 429 or RPC -32000..-32099 → RATE_LIMITED (retry hint from the RPC `data` "Retry after {ms} ms",
|
|
8
|
+
* else the `retry-after` header); any other RPC error → EXCHANGE_ERROR. A JSON-RPC -32602 is a
|
|
9
|
+
* Derive-side rejection too: user input is validated before any request, so it is never reported as
|
|
10
|
+
* the user's INVALID_PARAMS. Every RPC error carries `details.rpcCode` / `rpcData` so callers can
|
|
11
|
+
* classify the venue code (12001 instrument_not_found → SYMBOL_NOT_FOUND is done by the caller,
|
|
12
|
+
* which knows what was asked).
|
|
13
|
+
* - Public budget (docs rate-limits.md, mainnet reference): 5 TPS per IP, FIXED 5 s window of 25
|
|
14
|
+
* points, 1 point per request. The local bucket (burst 5, 4/s) allows at most 5 + 4·5 = 25 in any 5 s.
|
|
15
|
+
*/
|
|
16
|
+
import { PerpError, extractErrorMessage } from "../../errors.js";
|
|
17
|
+
import { acquireRate, rateLimitedError } from "../rate-limiter.js";
|
|
18
|
+
export const DERIVE_REST_URLS = {
|
|
19
|
+
mainnet: "https://api.derive.xyz/v3",
|
|
20
|
+
testnet: "https://testnet.api.derive.xyz/v3",
|
|
21
|
+
};
|
|
22
|
+
export const DERIVE_HTTP_TIMEOUT_MS = 15_000;
|
|
23
|
+
export const DERIVE_RATE_LIMIT = { capacity: 5, refillPerSec: 4, weights: null };
|
|
24
|
+
/** Derive application code for an unknown instrument / currency (`instrument_not_found`). */
|
|
25
|
+
export const DERIVE_INSTRUMENT_NOT_FOUND = 12001;
|
|
26
|
+
/** JSON-RPC invalid params — Derive also uses it for "Ticker not found for instrument" (live 2026-10-18). */
|
|
27
|
+
export const DERIVE_INVALID_PARAMS = -32602;
|
|
28
|
+
const UNREACHABLE_REMEDIATION = "Check the network / api.derive.xyz status and retry";
|
|
29
|
+
const RATE_LIMIT_REMEDIATION = "Derive's public budget is 5 requests/s per IP (25 per 5 s window); wait and retry";
|
|
30
|
+
/** Shape drift: an explicit EXCHANGE_ERROR, never a guessed value. */
|
|
31
|
+
export function deriveShapeError(what) {
|
|
32
|
+
return new PerpError("EXCHANGE_ERROR", `Derive API returned an unexpected shape: ${what}`, {
|
|
33
|
+
exchange: "derive",
|
|
34
|
+
remediation: "Derive's v3 API response changed; perp-cli needs an update to read it",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** The RPC error code a Derive call failed with, or null (not an RPC error). */
|
|
38
|
+
export function deriveRpcCode(e) {
|
|
39
|
+
if (!(e instanceof PerpError))
|
|
40
|
+
return null;
|
|
41
|
+
const code = e.structured.details?.rpcCode;
|
|
42
|
+
return typeof code === "number" ? code : null;
|
|
43
|
+
}
|
|
44
|
+
export class DeriveClient {
|
|
45
|
+
network;
|
|
46
|
+
baseUrl;
|
|
47
|
+
rateLimit;
|
|
48
|
+
constructor(network, baseUrl = DERIVE_REST_URLS[network], rateLimit = DERIVE_RATE_LIMIT) {
|
|
49
|
+
this.network = network;
|
|
50
|
+
this.baseUrl = baseUrl;
|
|
51
|
+
this.rateLimit = rateLimit;
|
|
52
|
+
}
|
|
53
|
+
/** `public/<method>`; resolves with `result`, every failure is a PerpError. */
|
|
54
|
+
async call(method, params) {
|
|
55
|
+
await acquireRate(`derive:${this.baseUrl}`, this.rateLimit, method);
|
|
56
|
+
const what = `Derive public/${method}`;
|
|
57
|
+
let res;
|
|
58
|
+
try {
|
|
59
|
+
res = await fetch(`${this.baseUrl}/public/${method}`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "content-type": "application/json" },
|
|
62
|
+
body: JSON.stringify(params),
|
|
63
|
+
signal: AbortSignal.timeout(DERIVE_HTTP_TIMEOUT_MS),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
throw new PerpError("EXCHANGE_UNREACHABLE", `${what}: ${extractErrorMessage(e)}`, { exchange: "derive", remediation: UNREACHABLE_REMEDIATION });
|
|
68
|
+
}
|
|
69
|
+
const text = await res.text().catch((e) => {
|
|
70
|
+
throw new PerpError("EXCHANGE_UNREACHABLE", `${what}: response body unreadable (${extractErrorMessage(e)})`, {
|
|
71
|
+
exchange: "derive", remediation: UNREACHABLE_REMEDIATION,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
let body;
|
|
75
|
+
try {
|
|
76
|
+
body = JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
body = undefined;
|
|
80
|
+
}
|
|
81
|
+
const rpcError = body !== null && typeof body === "object" ? body.error : undefined;
|
|
82
|
+
const hasRpcError = rpcError !== undefined && rpcError !== null;
|
|
83
|
+
if (res.status === 429) {
|
|
84
|
+
// The RPC body's "Retry after N ms" is preferred; the retry-after header only when the body has none.
|
|
85
|
+
const fromRpc = hasRpcError ? retryAfterMsOf(rpcError) : null;
|
|
86
|
+
if (fromRpc !== null)
|
|
87
|
+
throw rateLimited(`${what}: HTTP 429`, fromRpc, rpcCodeOf(rpcError));
|
|
88
|
+
throw rateLimitedError("derive", res.headers.get("retry-after"), text);
|
|
89
|
+
}
|
|
90
|
+
if (hasRpcError)
|
|
91
|
+
throw rpcErrorOf(what, rpcError);
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
throw new PerpError("EXCHANGE_ERROR", `${what}: HTTP ${res.status} ${text.slice(0, 200)}`, {
|
|
94
|
+
exchange: "derive", status: res.status, remediation: "Derive answered with an HTTP error; check api.derive.xyz status and retry",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (body === undefined)
|
|
98
|
+
throw deriveShapeError(`${method} returned a non-JSON body`);
|
|
99
|
+
if (body === null || typeof body !== "object" || !("result" in body))
|
|
100
|
+
throw deriveShapeError(`${method} returned no result`);
|
|
101
|
+
return body.result;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function rpcErrorOf(what, err) {
|
|
105
|
+
if (err === null || typeof err !== "object")
|
|
106
|
+
return deriveShapeError(`${what}: error is not an object`);
|
|
107
|
+
const { code, message, data } = err;
|
|
108
|
+
if (typeof code !== "number" || typeof message !== "string")
|
|
109
|
+
return deriveShapeError(`${what}: error without code / message`);
|
|
110
|
+
const detail = typeof data === "string" && data !== "" && data !== message ? `${message} (${data})` : message;
|
|
111
|
+
if (code <= -32000 && code >= -32099)
|
|
112
|
+
return rateLimited(`${what}: ${detail}`, retryAfterMsOf(err), code);
|
|
113
|
+
return new PerpError("EXCHANGE_ERROR", `${what}: request rejected by Derive: ${detail} [code ${code}]`, {
|
|
114
|
+
exchange: "derive", rpcCode: code, rpcData: typeof data === "string" ? data : null,
|
|
115
|
+
remediation: "Retry later; if it persists, Derive's API changed and perp-cli needs an update",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function rateLimited(message, retryAfterMs, rpcCode) {
|
|
119
|
+
return new PerpError("RATE_LIMITED", message, {
|
|
120
|
+
exchange: "derive",
|
|
121
|
+
...(rpcCode !== null ? { rpcCode } : {}),
|
|
122
|
+
...(retryAfterMs !== null ? { retryAfterMs } : {}),
|
|
123
|
+
remediation: RATE_LIMIT_REMEDIATION,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function rpcCodeOf(err) {
|
|
127
|
+
const code = err !== null && typeof err === "object" ? err.code : undefined;
|
|
128
|
+
return typeof code === "number" ? code : null;
|
|
129
|
+
}
|
|
130
|
+
/** "Retry after {ms} ms" from an RPC error's `data`, or null. */
|
|
131
|
+
function retryAfterMsOf(err) {
|
|
132
|
+
const data = err !== null && typeof err === "object" ? err.data : undefined;
|
|
133
|
+
const m = typeof data === "string" ? /Retry after (\d+) ?ms/i.exec(data) : null;
|
|
134
|
+
return m !== null ? Number(m[1]) : null;
|
|
135
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive options — read-only `OptionsVenue` over the v3 public API (official, no auth).
|
|
3
|
+
*
|
|
4
|
+
* Reads (docs.derive.xyz/openapi.json, shapes checked live 2026-10-18):
|
|
5
|
+
* - `get_all_currencies` → currencies with an `option` asset (underlyings).
|
|
6
|
+
* - `get_all_instruments {instrument_type:"option", currency, expired:false, page_size:1000, page}`
|
|
7
|
+
* → instruments; `option_details {expiry (unix s), strike, option_type "C"|"P"}`. Paginated
|
|
8
|
+
* (`pagination.num_pages`, page_size ≤ 1000). `is_active:false` = listed for a future activation
|
|
9
|
+
* (`scheduled_activation`), not tradable and with no ticker — left out of expiries / chains.
|
|
10
|
+
* An unknown currency is RPC 12001.
|
|
11
|
+
* - `get_tickers {instrument_type:"option", currency, expiry_date:"YYYYMMDD"}` → `{tickers: {name: TickerSlim}}`;
|
|
12
|
+
* expiry_date is required for options. ONE call per chain (never one per instrument).
|
|
13
|
+
* - `get_instrument` + `get_ticker` for a single quote (unknown instrument = RPC 12001).
|
|
14
|
+
*
|
|
15
|
+
* Ticker encoding (live): an empty book side is price "0" / size "0" → `null` side; an IV the
|
|
16
|
+
* pricer could not compute (no quote, or a quote below intrinsic) is "0" → `null`; 24 h high / low
|
|
17
|
+
* are "0" when there were no trades → `null`. Greeks are passed through verbatim (a delta of 0 is real).
|
|
18
|
+
*/
|
|
19
|
+
import type { Network } from "../venue-types.js";
|
|
20
|
+
import { type OptionChain, type OptionChainQuery, type OptionExpiries, type OptionExpiry, type OptionKind, type OptionQuote, type OptionsVenue, type OptionUnderlyings } from "../options-interface.js";
|
|
21
|
+
import { DeriveClient } from "./client.js";
|
|
22
|
+
/** One option instrument, as perp-cli uses it. */
|
|
23
|
+
export interface DeriveOptionInstrument {
|
|
24
|
+
name: string;
|
|
25
|
+
underlying: string;
|
|
26
|
+
quoteCurrency: string;
|
|
27
|
+
kind: OptionKind;
|
|
28
|
+
strike: string;
|
|
29
|
+
expiryMs: number;
|
|
30
|
+
/** UTC YYYY-MM-DD of the expiry. */
|
|
31
|
+
expiry: string;
|
|
32
|
+
isActive: boolean;
|
|
33
|
+
/** ms epoch of `scheduled_activation`. */
|
|
34
|
+
activationMs: number;
|
|
35
|
+
}
|
|
36
|
+
/** ms epoch → UTC YYYY-MM-DD. */
|
|
37
|
+
export declare function utcDate(ms: number): string;
|
|
38
|
+
/** User expiry, exactly "2026-12-25" or "20261225" (not mixed) → YYYY-MM-DD; anything else INVALID_PARAMS. */
|
|
39
|
+
export declare function normalizeExpiry(input: string): string;
|
|
40
|
+
/** User underlying → venue currency (upper case); INVALID_PARAMS for anything that is not a ticker. */
|
|
41
|
+
export declare function normalizeUnderlying(input: string): string;
|
|
42
|
+
export declare function parseDeriveInstrument(raw: unknown): DeriveOptionInstrument;
|
|
43
|
+
/** A ticker_slim snapshot (get_ticker / get_tickers value) of `inst` → OptionQuote. */
|
|
44
|
+
export declare function parseDeriveOptionTicker(raw: unknown, inst: DeriveOptionInstrument): OptionQuote;
|
|
45
|
+
/** Distinct active expiries of `instruments`, soonest first. */
|
|
46
|
+
export declare function expiriesOf(instruments: readonly DeriveOptionInstrument[]): OptionExpiry[];
|
|
47
|
+
/** The `n` strikes closest to `price` (ties → lower strike), returned ascending. */
|
|
48
|
+
export declare function strikesAround(strikes: readonly string[], price: string, n: number): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Chain for one expiry from its instruments and the `get_tickers` map. Rows come from the listed
|
|
51
|
+
* instruments; a listed instrument without a ticker leaves its side null and is named in
|
|
52
|
+
* `missingTickers`. Index / forward are those of the newest ticker (largest `t`) — one ticker's values,
|
|
53
|
+
* not an average over the expiry.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildChain(underlying: string, expiry: OptionExpiry, selection: OptionChain["expirySelection"], instruments: readonly DeriveOptionInstrument[], tickersRaw: unknown, strikesAroundAtm: number | null): OptionChain;
|
|
56
|
+
export declare class DeriveOptionsVenue implements OptionsVenue {
|
|
57
|
+
private readonly client;
|
|
58
|
+
readonly id = "derive";
|
|
59
|
+
readonly displayName = "Derive";
|
|
60
|
+
constructor(client: DeriveClient);
|
|
61
|
+
static forNetwork(network: Network): DeriveOptionsVenue;
|
|
62
|
+
listUnderlyings(): Promise<OptionUnderlyings>;
|
|
63
|
+
/** Every non-expired option instrument of `underlying` (all pages). 12001 → SYMBOL_NOT_FOUND. */
|
|
64
|
+
instruments(underlying: string): Promise<DeriveOptionInstrument[]>;
|
|
65
|
+
listExpiries(underlyingInput: string): Promise<OptionExpiries>;
|
|
66
|
+
getChain(q: OptionChainQuery): Promise<OptionChain>;
|
|
67
|
+
getQuote(instrumentInput: string): Promise<OptionQuote>;
|
|
68
|
+
}
|