perp-cli 0.14.3 → 0.16.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.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +138 -405
- package/README.zh.md +243 -0
- 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/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +85 -15
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- 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/quote-rules.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 +12 -5
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
- package/dist/bot/strategies/funding-arb-utils.js +53 -20
- 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.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +3 -3
- 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 +126 -6
- package/dist/commands/account.js +168 -24
- 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 +23 -81
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +175 -97
- package/dist/commands/arb.d.ts +20 -0
- package/dist/commands/arb.js +107 -163
- package/dist/commands/backtest.js +23 -21
- 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.d.ts +48 -0
- package/dist/commands/market.js +395 -100
- package/dist/commands/options.d.ts +14 -0
- package/dist/commands/options.js +151 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +455 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +83 -55
- 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 +67 -62
- 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/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +3 -3
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +35 -3
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +16 -12
- package/dist/exchanges/arcus/adapter.js +45 -18
- 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/public.js +3 -2
- 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 +30 -10
- package/dist/exchanges/aster.js +153 -53
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +8 -2
- package/dist/exchanges/canonical-symbols.js +46 -2
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +73 -0
- package/dist/exchanges/derive/options.js +379 -0
- package/dist/exchanges/extended/adapter.d.ts +96 -0
- package/dist/exchanges/extended/adapter.js +241 -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 +133 -0
- package/dist/exchanges/extended/public.js +436 -0
- package/dist/exchanges/grvt/adapter.d.ts +100 -0
- package/dist/exchanges/grvt/adapter.js +283 -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-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
- package/dist/exchanges/hyperliquid-spot.js +24 -16
- package/dist/exchanges/hyperliquid.d.ts +199 -22
- package/dist/exchanges/hyperliquid.js +550 -202
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +157 -13
- package/dist/exchanges/interface.js +47 -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 +67 -12
- package/dist/exchanges/lighter-public.js +250 -82
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +10 -5
- package/dist/exchanges/lighter-spot.js +105 -97
- package/dist/exchanges/lighter.d.ts +80 -21
- package/dist/exchanges/lighter.js +312 -105
- package/dist/exchanges/ondo/adapter.d.ts +38 -13
- package/dist/exchanges/ondo/adapter.js +86 -27
- 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 +35 -3
- package/dist/exchanges/ondo/public.js +75 -32
- 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/outcome-interface.d.ts +4 -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 +8 -2
- package/dist/exchanges/pacifica-spot.js +26 -18
- 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 +130 -0
- package/dist/exchanges/pacifica-vaults.js +277 -0
- package/dist/exchanges/pacifica.d.ts +108 -10
- package/dist/exchanges/pacifica.js +428 -171
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +69 -13
- package/dist/exchanges/qfex/adapter.js +102 -17
- 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 +65 -12
- package/dist/exchanges/qfex/public.js +131 -25
- 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 +3 -3
- 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 +160 -0
- package/dist/exchanges/risex/adapter.js +842 -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 +8 -8
- 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 +61 -0
- package/dist/exchanges/txflow/adapter.js +150 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +155 -0
- package/dist/exchanges/txflow/public.d.ts +87 -0
- package/dist/exchanges/txflow/public.js +355 -0
- package/dist/exchanges/unknown-symbol.d.ts +34 -0
- package/dist/exchanges/unknown-symbol.js +56 -0
- package/dist/exchanges/variational/adapter.d.ts +67 -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 +52 -10
- package/dist/exchanges/venue-types.js +82 -11
- package/dist/exchanges/venue.d.ts +150 -11
- package/dist/exchanges/venue.js +126 -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 +19 -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 +20 -8
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +18 -6
- 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 +15 -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 +131 -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 +6 -4
- package/dist/funding/legacy-public.js +42 -41
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +95 -6
- package/dist/funding/rates.js +99 -19
- 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/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +64 -0
- package/dist/mcp-server.js +211 -68
- package/dist/pacifica/client.d.ts +69 -5
- package/dist/pacifica/client.js +181 -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/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- 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 +36 -63
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +22 -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/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +980 -0
- package/dist/ws/index.js +4460 -0
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +996 -0
- package/dist/ws/node.js +4554 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +71 -10
- package/skills/perp-cli/SKILL.md +153 -36
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +22 -12
- package/skills/perp-cli/references/commands.md +143 -17
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +523 -0
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
package/dist/exchanges/aster.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
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";
|
|
22
|
+
import { asterCliSymbol, isAsterListedPerpetual } from "../ws/aster-market.js";
|
|
23
|
+
import { decimalsOf } from "./venue-types.js";
|
|
21
24
|
import { accountCacheEpoch } from "../cache.js";
|
|
22
25
|
import { isExpired } from "../agent-wallet/expiry.js";
|
|
23
26
|
import { classifyError, DEFINITE_ORDER_FAILURE_CODES, isTransportFailure, orderNotSent, orderOutcomeUnknown, PerpError, sendFailure } from "../errors.js";
|
|
@@ -30,7 +33,7 @@ import { approvalFromMax, bpsToRate, builderGateOf, NO_BUILDER, rateToBps, requi
|
|
|
30
33
|
import { ASTER_CLIENT_ID_RE, ASTER_FUNDS_CHAINS, asterVenue } from "./venues/aster.js";
|
|
31
34
|
import { assertAsterWithdrawAmount, assertAsterWithdrawSignature, parseAsterWithdrawLimits, requireAsterFundsNetwork, } from "./aster-funds.js";
|
|
32
35
|
import { nextAsterNonce } from "./aster-nonce.js";
|
|
33
|
-
/** Common TIF
|
|
36
|
+
/** Common TIF → Aster perp timeInForce. Live exchangeInfo lists GTC/IOC/GTX/HIDDEN — no FOK. */
|
|
34
37
|
function asterTif(raw) {
|
|
35
38
|
const t = raw.toUpperCase();
|
|
36
39
|
if (t === "GTC" || t === "IOC" || t === "GTX")
|
|
@@ -72,6 +75,16 @@ export function asterIocOutcome(raw) {
|
|
|
72
75
|
const filledSize = plainQty(r.executedQty);
|
|
73
76
|
return filledSize === null ? null : { filledSize, final: true };
|
|
74
77
|
}
|
|
78
|
+
/** The numeric `code` of an Aster error body ({"code":-1121,"msg":"Invalid symbol."}); null when absent. */
|
|
79
|
+
function asterErrorCode(body) {
|
|
80
|
+
try {
|
|
81
|
+
const j = JSON.parse(body);
|
|
82
|
+
return typeof j.code === "number" ? j.code : null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null; // not JSON (e.g. an HTML error page): no venue code
|
|
86
|
+
}
|
|
87
|
+
}
|
|
75
88
|
/**
|
|
76
89
|
* Order-placement refusals by documented venue code (asterdex/api-docs V3 futures "Error
|
|
77
90
|
* Codes"; "Codes are universal,but messages can vary"). [PerpError code, docs name, remediation].
|
|
@@ -163,6 +176,11 @@ export const ASTER_INCOME_MAX_PAGES = 60;
|
|
|
163
176
|
const ASTER_KLINE_PAGE = 1500;
|
|
164
177
|
/** Page cap for one range (150 000 bars). */
|
|
165
178
|
const ASTER_KLINE_MAX_PAGES = 100;
|
|
179
|
+
/**
|
|
180
|
+
* Kline intervals verified live 2026-10-19 (/fapi/v3/klines BTCUSDT: each answers bars of that
|
|
181
|
+
* length; "7m" / "2d" → -1120 "Invalid interval."). Anything else is refused before a request.
|
|
182
|
+
*/
|
|
183
|
+
export const ASTER_KLINE_INTERVALS = new Set(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"]);
|
|
166
184
|
export class AsterAdapter {
|
|
167
185
|
name = "aster";
|
|
168
186
|
chain = "bnb";
|
|
@@ -186,6 +204,8 @@ export class AsterAdapter {
|
|
|
186
204
|
/** API symbol → price filters; null = listed but PRICE_FILTER / PERCENT_PRICE unreadable. */
|
|
187
205
|
_priceFilters = new Map();
|
|
188
206
|
_priceFiltersLoadedAt = 0;
|
|
207
|
+
/** The in-flight exchangeInfo load shared by concurrent first uses (_ensureDirectory). */
|
|
208
|
+
_directoryLoad = null;
|
|
189
209
|
_accountCache = null;
|
|
190
210
|
_positionsCache = null;
|
|
191
211
|
_ordersCache = null;
|
|
@@ -283,7 +303,7 @@ export class AsterAdapter {
|
|
|
283
303
|
return null;
|
|
284
304
|
}
|
|
285
305
|
}
|
|
286
|
-
// ── Spot host access (
|
|
306
|
+
// ── Spot host access (used by AsterSpotAdapter) ──
|
|
287
307
|
/**
|
|
288
308
|
* Aster spot REST host. Spot shares the perp EIP-712 Domain B signing
|
|
289
309
|
* (chainId 1666 mainnet / 714 testnet) and the per-agent nonce space.
|
|
@@ -316,12 +336,23 @@ export class AsterAdapter {
|
|
|
316
336
|
return this._agentMeta;
|
|
317
337
|
}
|
|
318
338
|
// ── Symbol helpers ──
|
|
319
|
-
/**
|
|
339
|
+
/**
|
|
340
|
+
* CLI symbol → Aster API symbol through the LOADED market directory (exchangeInfo): the exact
|
|
341
|
+
* venue symbol first (BTCUSDT, BTCUSD1, BTCU — a non-USDT market's CLI symbol is its venue
|
|
342
|
+
* symbol, asterCliSymbol), then the USDT market of a base (ETH → ETHUSDT, USD1 → USD1USDT),
|
|
343
|
+
* else SYMBOL_NOT_FOUND — never a guessed suffix. Callers load the directory first (_apiSymbol).
|
|
344
|
+
*/
|
|
320
345
|
_toApi(symbol) {
|
|
321
346
|
const s = symbol.toUpperCase().replace(/-PERP$/, "");
|
|
322
|
-
|
|
347
|
+
// A bare base names its USDT market first (X → XUSDT), even if a non-USDT venue symbol is
|
|
348
|
+
// spelled X too; an exact venue symbol otherwise (BTCUSD1, BTCU, XUSDT).
|
|
349
|
+
if (this._priceFilters.has(`${s}USDT`))
|
|
350
|
+
return `${s}USDT`;
|
|
351
|
+
if (this._priceFilters.has(s))
|
|
323
352
|
return s;
|
|
324
|
-
|
|
353
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Aster has no market ${s} (neither the venue symbol ${s} nor ${s}USDT is listed)`, {
|
|
354
|
+
exchange: "aster", remediation: "List markets: perp -e aster market list",
|
|
355
|
+
});
|
|
325
356
|
}
|
|
326
357
|
/** Aster API symbol → CLI symbol (ETHUSDT → ETH) */
|
|
327
358
|
_fromApi(symbol) {
|
|
@@ -397,10 +428,9 @@ export class AsterAdapter {
|
|
|
397
428
|
}
|
|
398
429
|
const premiumMap = new Map(premiums.map(p => [String(p.symbol), p]));
|
|
399
430
|
this._ingestPriceFilters(info.symbols);
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
const tradingSymbols = info.symbols
|
|
403
|
-
.filter((s) => String(s.contractType) === "PERPETUAL" && String(s.status) === "TRADING" && String(s.quoteAsset) === "USDT");
|
|
431
|
+
// Every listed perpetual, any quote (the WS directory's set): USDT-quoted ones by base (ETH),
|
|
432
|
+
// USD1 / U quoted ones by venue symbol (BTCUSD1, BTCU — asterCliSymbol), with quoteAsset.
|
|
433
|
+
const tradingSymbols = info.symbols.filter((s) => isAsterListedPerpetual(s));
|
|
404
434
|
const result = tradingSymbols.flatMap((s) => {
|
|
405
435
|
const sym = String(s.symbol);
|
|
406
436
|
const ticker = tickerMap.get(sym);
|
|
@@ -412,17 +442,24 @@ export class AsterAdapter {
|
|
|
412
442
|
const maxLev = null;
|
|
413
443
|
const lotFilter = s.filters
|
|
414
444
|
?.find(f => f.filterType === "LOT_SIZE");
|
|
445
|
+
const cliSymbol = asterCliSymbol({ symbol: sym, baseAsset: String(s.baseAsset), quoteAsset: s.quoteAsset });
|
|
415
446
|
const fundingHours = this._fundingHoursCache.get(this._fromApi(sym));
|
|
416
447
|
return [{
|
|
417
|
-
symbol:
|
|
448
|
+
symbol: cliSymbol,
|
|
449
|
+
venueSymbol: sym,
|
|
450
|
+
quoteAsset: typeof s.quoteAsset === "string" ? s.quoteAsset : null,
|
|
418
451
|
markPrice: String(premium.markPrice),
|
|
419
452
|
indexPrice: premium?.indexPrice != null ? String(premium.indexPrice) : null,
|
|
420
453
|
fundingRate: premium?.lastFundingRate != null ? String(premium.lastFundingRate) : null,
|
|
454
|
+
// premiumIndex lastFundingRate moves during the period (same value as WS markPrice@arr `r`: accruing).
|
|
455
|
+
fundingRateKind: premium?.lastFundingRate != null ? "accruing" : null,
|
|
421
456
|
volume24h: ticker?.quoteVolume != null ? String(ticker.quoteVolume) : null,
|
|
422
457
|
// /fapi/v3/exchangeInfo and ticker/24hr carry no open interest.
|
|
423
458
|
openInterest: null,
|
|
424
459
|
maxLeverage: maxLev,
|
|
425
|
-
|
|
460
|
+
// The LOT_SIZE step is the authoritative grid (quantityPrecision can disagree with it: CFX
|
|
461
|
+
// step "1", precision 1); its decimals, the public directory's rule (venueMarketRow).
|
|
462
|
+
sizeDecimals: lotFilter?.stepSize != null ? decimalsOf(String(lotFilter.stepSize)) : s.quantityPrecision != null ? Number(s.quantityPrecision) : null,
|
|
426
463
|
stepSize: lotFilter?.stepSize != null ? String(lotFilter.stepSize) : null,
|
|
427
464
|
fundingHours: fundingHours ?? null,
|
|
428
465
|
marginTiers: null,
|
|
@@ -457,16 +494,38 @@ export class AsterAdapter {
|
|
|
457
494
|
this._priceFiltersLoadedAt = Date.now();
|
|
458
495
|
}
|
|
459
496
|
/** PRICE_FILTER / PERCENT_PRICE of an API symbol (exchangeInfo, cached CACHE_TTL). */
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
497
|
+
/** exchangeInfo symbols (the market directory) loaded / refreshed per CACHE_TTL. */
|
|
498
|
+
/**
|
|
499
|
+
* exchangeInfo symbols (the market directory), refreshed per CACHE_TTL. Concurrent first uses
|
|
500
|
+
* share ONE in-flight request; a failed load is not cached (the next call retries).
|
|
501
|
+
*/
|
|
502
|
+
async _ensureDirectory() {
|
|
503
|
+
if (this._priceFiltersLoadedAt && Date.now() - this._priceFiltersLoadedAt < AsterAdapter.CACHE_TTL)
|
|
504
|
+
return;
|
|
505
|
+
if (this._directoryLoad === null) {
|
|
506
|
+
this._directoryLoad = (async () => {
|
|
507
|
+
const info = await this._publicGet("/fapi/v3/exchangeInfo");
|
|
508
|
+
if (!Array.isArray(info?.symbols)) {
|
|
509
|
+
throw new PerpError("EXCHANGE_ERROR", "Aster /fapi/v3/exchangeInfo returned no symbols", { exchange: "aster" });
|
|
510
|
+
}
|
|
511
|
+
this._ingestPriceFilters(info.symbols);
|
|
512
|
+
})().finally(() => { this._directoryLoad = null; });
|
|
467
513
|
}
|
|
514
|
+
return this._directoryLoad;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* CLI symbol → Aster API symbol, resolved through the market directory (_toApi), loaded on first
|
|
518
|
+
* use and cached CACHE_TTL — so the answer never depends on what was read before (a cold
|
|
519
|
+
* adapter resolves BTCU, USD1 → USD1USDT, an unknown symbol is SYMBOL_NOT_FOUND).
|
|
520
|
+
*/
|
|
521
|
+
async _apiSymbol(symbol) {
|
|
522
|
+
await this._ensureDirectory();
|
|
523
|
+
return this._toApi(symbol);
|
|
524
|
+
}
|
|
525
|
+
async _symbolPriceFilters(apiSymbol) {
|
|
526
|
+
await this._ensureDirectory();
|
|
468
527
|
if (!this._priceFilters.has(apiSymbol)) {
|
|
469
|
-
throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster" });
|
|
528
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Aster market ${apiSymbol} not found in exchangeInfo`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
|
|
470
529
|
}
|
|
471
530
|
const f = this._priceFilters.get(apiSymbol);
|
|
472
531
|
if (!f) {
|
|
@@ -475,12 +534,12 @@ export class AsterAdapter {
|
|
|
475
534
|
return f;
|
|
476
535
|
}
|
|
477
536
|
/**
|
|
478
|
-
* Declared protection-price reference
|
|
537
|
+
* Declared protection-price reference: the venue mark price from
|
|
479
538
|
* GET /fapi/v3/premiumIndex — the same price PERCENT_PRICE is evaluated against.
|
|
480
539
|
* No runtime substitute (no mid / last fallback): unavailable → PRICE_STALE.
|
|
481
540
|
*/
|
|
482
541
|
async referencePrice(symbol) {
|
|
483
|
-
const apiSymbol = this.
|
|
542
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
484
543
|
let mark;
|
|
485
544
|
try {
|
|
486
545
|
const res = await this._publicGet("/fapi/v3/premiumIndex", { symbol: apiSymbol });
|
|
@@ -525,7 +584,7 @@ export class AsterAdapter {
|
|
|
525
584
|
* against PRICE_FILTER / PERCENT_PRICE. appliedBps = the bound after tick alignment.
|
|
526
585
|
*/
|
|
527
586
|
async computeProtectionPrice(symbol, side, slippage) {
|
|
528
|
-
const apiSymbol = this.
|
|
587
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
529
588
|
const f = await this._symbolPriceFilters(apiSymbol);
|
|
530
589
|
const ref = await this.referencePrice(symbol);
|
|
531
590
|
const protectionPrice = alignProtectionPrice(slippagePrice(ref, side, slippage.bps), f.tickSize, side);
|
|
@@ -538,7 +597,7 @@ export class AsterAdapter {
|
|
|
538
597
|
return asterIocOutcome(raw);
|
|
539
598
|
}
|
|
540
599
|
async getOrderbook(symbol) {
|
|
541
|
-
const res = await this._publicGet("/fapi/v3/depth", { symbol: this.
|
|
600
|
+
const res = await this._publicGet("/fapi/v3/depth", { symbol: await this._apiSymbol(symbol), limit: "50" });
|
|
542
601
|
return {
|
|
543
602
|
bids: res?.bids ?? [],
|
|
544
603
|
asks: res?.asks ?? [],
|
|
@@ -546,27 +605,33 @@ export class AsterAdapter {
|
|
|
546
605
|
}
|
|
547
606
|
async getRecentTrades(symbol, limit = 20) {
|
|
548
607
|
const trades = await this._publicGet("/fapi/v3/trades", {
|
|
549
|
-
symbol: this.
|
|
608
|
+
symbol: await this._apiSymbol(symbol),
|
|
550
609
|
limit: String(limit),
|
|
551
610
|
});
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
611
|
+
if (!Array.isArray(trades))
|
|
612
|
+
throw new PerpError("EXCHANGE_ERROR", `Aster trades is not a list (${JSON.stringify(trades).slice(0, 120)})`, { exchange: "aster" });
|
|
613
|
+
// The tape rows carry no symbol: the normalized venue symbol (was the raw argument, "BTC-PERP").
|
|
614
|
+
const sym = this._fromApi(await this._apiSymbol(symbol));
|
|
615
|
+
return trades.map((t) => {
|
|
616
|
+
if (typeof t.time !== "number" || typeof t.price !== "string" || typeof t.qty !== "string" || typeof t.isBuyerMaker !== "boolean") {
|
|
617
|
+
throw new PerpError("EXCHANGE_ERROR", `Aster trade row without time / price / qty / isBuyerMaker: ${JSON.stringify(t).slice(0, 160)}`, { exchange: "aster" });
|
|
618
|
+
}
|
|
619
|
+
return { time: t.time, symbol: sym, side: t.isBuyerMaker ? "sell" : "buy", price: t.price, size: t.qty, fee: null }; // the public tape carries no fee
|
|
620
|
+
});
|
|
560
621
|
}
|
|
561
622
|
async getFundingHistory(symbol, limit = 10) {
|
|
562
623
|
const data = await this._publicGet("/fapi/v3/fundingRate", {
|
|
563
|
-
symbol: this.
|
|
624
|
+
symbol: await this._apiSymbol(symbol),
|
|
564
625
|
limit: String(limit),
|
|
565
626
|
});
|
|
566
627
|
if (!Array.isArray(data)) {
|
|
567
628
|
throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate history is not a list (${JSON.stringify(data).slice(0, 120)})`, { exchange: "aster" });
|
|
568
629
|
}
|
|
569
|
-
|
|
630
|
+
// Per-market interval (/fapi/v3/fundingInfo); null when the venue lists none for the symbol.
|
|
631
|
+
const hours = await this.getFundingHours(symbol);
|
|
632
|
+
const intervalHours = hours === undefined ? null : hours;
|
|
633
|
+
// `200 []` for an unlisted symbol (live 2026-10-14): checked against the market directory.
|
|
634
|
+
return nonEmptyOrListed(this, symbol, data.map((d) => {
|
|
570
635
|
const time = Number(d.fundingTime);
|
|
571
636
|
if (d.fundingTime === undefined || d.fundingTime === null || !Number.isFinite(time) || time <= 0) {
|
|
572
637
|
throw new PerpError("EXCHANGE_ERROR", `Aster fundingRate row without a numeric fundingTime: ${JSON.stringify(d).slice(0, 160)}`, { exchange: "aster" });
|
|
@@ -575,8 +640,10 @@ export class AsterAdapter {
|
|
|
575
640
|
time,
|
|
576
641
|
rate: venueRateField(d.fundingRate, "fundingRate fundingRate", "aster"),
|
|
577
642
|
price: d.markPrice ? String(d.markPrice) : null,
|
|
643
|
+
intervalHours,
|
|
644
|
+
rateKind: "lastSettled",
|
|
578
645
|
};
|
|
579
|
-
});
|
|
646
|
+
}), this._fromApi(await this._apiSymbol(symbol)));
|
|
580
647
|
}
|
|
581
648
|
/**
|
|
582
649
|
* Candles over [startTime, endTime]. GET /fapi/v3/klines returns at most 1500 bars per request
|
|
@@ -584,6 +651,11 @@ export class AsterAdapter {
|
|
|
584
651
|
* range is never cut to the first page. More than ASTER_KLINE_MAX_PAGES pages → INVALID_PARAMS.
|
|
585
652
|
*/
|
|
586
653
|
async getKlines(symbol, interval, startTime, endTime) {
|
|
654
|
+
if (!ASTER_KLINE_INTERVALS.has(interval)) {
|
|
655
|
+
throw new PerpError("INVALID_PARAMS", `Aster: unsupported candle interval "${interval}"`, {
|
|
656
|
+
remediation: `Use one of: ${[...ASTER_KLINE_INTERVALS].join(", ")}`,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
587
659
|
if (!(startTime <= endTime)) {
|
|
588
660
|
throw new PerpError("INVALID_PARAMS", `Aster klines: start ${startTime} is after end ${endTime}`, {
|
|
589
661
|
remediation: "Pass a --start that is before --end",
|
|
@@ -598,7 +670,7 @@ export class AsterAdapter {
|
|
|
598
670
|
});
|
|
599
671
|
}
|
|
600
672
|
const data = await this._publicGet("/fapi/v3/klines", {
|
|
601
|
-
symbol: this.
|
|
673
|
+
symbol: await this._apiSymbol(symbol), interval,
|
|
602
674
|
startTime: String(from), endTime: String(endTime), limit: String(ASTER_KLINE_PAGE),
|
|
603
675
|
});
|
|
604
676
|
if (!Array.isArray(data)) {
|
|
@@ -607,10 +679,11 @@ export class AsterAdapter {
|
|
|
607
679
|
for (const k of data) {
|
|
608
680
|
const time = Number(k[0]);
|
|
609
681
|
const trades = Number(k[8]);
|
|
610
|
-
|
|
611
|
-
|
|
682
|
+
const numeric = (v) => (typeof v === "string" || typeof v === "number") && String(v).trim() !== "" && Number.isFinite(Number(v));
|
|
683
|
+
if (!Number.isFinite(time) || !Number.isFinite(trades) || !numeric(k[5]) || !numeric(k[7])) {
|
|
684
|
+
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
685
|
}
|
|
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 });
|
|
686
|
+
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
687
|
}
|
|
615
688
|
if (data.length < ASTER_KLINE_PAGE)
|
|
616
689
|
break;
|
|
@@ -975,12 +1048,12 @@ export class AsterAdapter {
|
|
|
975
1048
|
}
|
|
976
1049
|
// ── Trading ──
|
|
977
1050
|
async marketOrder(symbol, side, size, opts) {
|
|
978
|
-
//
|
|
1051
|
+
// Never native MARKET (no price cap) — an IOC LIMIT at the
|
|
979
1052
|
// protection price (mark ± opts.slippage.bps, tick-aligned toward crossing, inside
|
|
980
1053
|
// PRICE_FILTER / PERCENT_PRICE). newOrderRespType=RESULT makes the response carry the
|
|
981
1054
|
// final IOC status (FILLED / EXPIRED) and executedQty.
|
|
982
1055
|
const r = this._resolveSigner();
|
|
983
|
-
const apiSymbol = this.
|
|
1056
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
984
1057
|
const clientOrderId = opts.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
|
|
985
1058
|
let protectionPrice;
|
|
986
1059
|
let protection;
|
|
@@ -1068,11 +1141,11 @@ export class AsterAdapter {
|
|
|
1068
1141
|
}
|
|
1069
1142
|
async limitOrder(symbol, side, price, size, opts) {
|
|
1070
1143
|
const r = this._resolveSigner();
|
|
1071
|
-
const apiSymbol = this.
|
|
1144
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
1072
1145
|
const clientOrderId = opts?.clientOrderId === undefined ? null : requireAsterClientOrderId(opts.clientOrderId);
|
|
1073
1146
|
let params;
|
|
1074
1147
|
try {
|
|
1075
|
-
// Common TIF names
|
|
1148
|
+
// Common TIF names: GTC / IOC / ALO→GTX (GTX accepted as alias); FOK unsupported.
|
|
1076
1149
|
const tif = asterTif(opts?.tif ?? "GTC");
|
|
1077
1150
|
// Price must already be on PRICE_FILTER.tickSize — validated, never rounded (INVALID_TICK).
|
|
1078
1151
|
const px = requireOnTick(price, (await this._symbolPriceFilters(apiSymbol)).tickSize);
|
|
@@ -1104,14 +1177,14 @@ export class AsterAdapter {
|
|
|
1104
1177
|
/**
|
|
1105
1178
|
* Native modify (PUT /fapi/v3/order, V3 futures docs "Modify Order"): LIMIT orders
|
|
1106
1179
|
* only; TIF (incl. GTX) and reduceOnly stay on the order. `size` is the NEW UNFILLED
|
|
1107
|
-
* quantity
|
|
1180
|
+
* quantity; the venue's `quantity` is the order total, so
|
|
1108
1181
|
* quantity = size + executedQty (read just before). A fill landing between the read
|
|
1109
1182
|
* and the PUT can only shrink the remainder, never over-size it. A venue rejection
|
|
1110
1183
|
* leaves the original order untouched (docs).
|
|
1111
1184
|
*/
|
|
1112
1185
|
async editOrder(symbol, orderId, price, size) {
|
|
1113
1186
|
const r = this._resolveSigner();
|
|
1114
|
-
const apiSymbol = this.
|
|
1187
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
1115
1188
|
const rawOrders = await this._signedGetEip712("/fapi/v3/openOrders", {}, r);
|
|
1116
1189
|
const existing = (rawOrders ?? []).find(o => String(o.orderId ?? "") === orderId);
|
|
1117
1190
|
if (!existing) {
|
|
@@ -1158,7 +1231,7 @@ export class AsterAdapter {
|
|
|
1158
1231
|
const label = ref.kind === "orderId" ? ref.value : `c:${ref.value}`;
|
|
1159
1232
|
let row;
|
|
1160
1233
|
try {
|
|
1161
|
-
row = await this._signedGetEip712("/fapi/v3/order", { symbol: this.
|
|
1234
|
+
row = await this._signedGetEip712("/fapi/v3/order", { symbol: await this._apiSymbol(symbol), ...key }, this._resolveSigner());
|
|
1162
1235
|
}
|
|
1163
1236
|
catch (e) {
|
|
1164
1237
|
if (e instanceof PerpError && e.structured.details?.venueCode === -2013) {
|
|
@@ -1177,7 +1250,7 @@ export class AsterAdapter {
|
|
|
1177
1250
|
const r = this._resolveSigner();
|
|
1178
1251
|
try {
|
|
1179
1252
|
return await this._signedDeleteEip712("/fapi/v3/order", {
|
|
1180
|
-
symbol: this.
|
|
1253
|
+
symbol: await this._apiSymbol(symbol),
|
|
1181
1254
|
...key,
|
|
1182
1255
|
}, r);
|
|
1183
1256
|
}
|
|
@@ -1198,6 +1271,7 @@ export class AsterAdapter {
|
|
|
1198
1271
|
const r = this._resolveSigner();
|
|
1199
1272
|
if (!symbol) {
|
|
1200
1273
|
const orders = await this.getOpenOrders();
|
|
1274
|
+
await this._ensureDirectory();
|
|
1201
1275
|
const apiSymbols = new Set(orders.map(o => this._toApi(o.symbol)));
|
|
1202
1276
|
const results = [];
|
|
1203
1277
|
for (const sym of apiSymbols) {
|
|
@@ -1205,7 +1279,7 @@ export class AsterAdapter {
|
|
|
1205
1279
|
}
|
|
1206
1280
|
return results;
|
|
1207
1281
|
}
|
|
1208
|
-
return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: this.
|
|
1282
|
+
return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: await this._apiSymbol(symbol) }, r);
|
|
1209
1283
|
}
|
|
1210
1284
|
// ── Risk ──
|
|
1211
1285
|
async setLeverage(symbol, leverage, marginMode) {
|
|
@@ -1213,7 +1287,7 @@ export class AsterAdapter {
|
|
|
1213
1287
|
if (marginMode) {
|
|
1214
1288
|
try {
|
|
1215
1289
|
await this._signedPostEip712("/fapi/v3/marginType", {
|
|
1216
|
-
symbol: this.
|
|
1290
|
+
symbol: await this._apiSymbol(symbol),
|
|
1217
1291
|
marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
|
|
1218
1292
|
}, r);
|
|
1219
1293
|
}
|
|
@@ -1252,7 +1326,7 @@ export class AsterAdapter {
|
|
|
1252
1326
|
}
|
|
1253
1327
|
}
|
|
1254
1328
|
return this._signedPostEip712("/fapi/v3/leverage", {
|
|
1255
|
-
symbol: this.
|
|
1329
|
+
symbol: await this._apiSymbol(symbol),
|
|
1256
1330
|
leverage: String(leverage),
|
|
1257
1331
|
}, r);
|
|
1258
1332
|
}
|
|
@@ -1271,7 +1345,7 @@ export class AsterAdapter {
|
|
|
1271
1345
|
*/
|
|
1272
1346
|
async stopOrder(symbol, side, size, triggerPrice, opts) {
|
|
1273
1347
|
const r = this._resolveSigner();
|
|
1274
|
-
const apiSymbol = this.
|
|
1348
|
+
const apiSymbol = await this._apiSymbol(symbol);
|
|
1275
1349
|
const what = `stop ${side} ${size} ${symbol} @ ${triggerPrice}`;
|
|
1276
1350
|
let params;
|
|
1277
1351
|
try {
|
|
@@ -1757,6 +1831,11 @@ export class AsterAdapter {
|
|
|
1757
1831
|
if (!res.ok) {
|
|
1758
1832
|
const text = await res.text();
|
|
1759
1833
|
const clean = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
1834
|
+
// -1121 BAD_SYMBOL ("Invalid symbol.", live on /fapi/v3/depth for an unlisted symbol): the
|
|
1835
|
+
// symbol does not exist — SYMBOL_NOT_FOUND, not an UNKNOWN 500.
|
|
1836
|
+
if (asterErrorCode(text) === -1121) {
|
|
1837
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `Aster: ${clean} [${method} ${path}]`, { exchange: "aster", remediation: "List markets: perp -e aster market list" });
|
|
1838
|
+
}
|
|
1760
1839
|
throw new Error(`${method} ${path} failed (${res.status}): ${clean}`);
|
|
1761
1840
|
}
|
|
1762
1841
|
return res.json();
|
|
@@ -1768,10 +1847,31 @@ export class AsterAdapter {
|
|
|
1768
1847
|
const url = `${this._baseUrl}${path}${qs ? `?${qs}` : ""}`;
|
|
1769
1848
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1770
1849
|
const res = await fetch(url);
|
|
1771
|
-
|
|
1850
|
+
let result;
|
|
1851
|
+
try {
|
|
1852
|
+
result = await this._handleResponse(res, "GET", path, attempt);
|
|
1853
|
+
}
|
|
1854
|
+
catch (e) {
|
|
1855
|
+
if (e instanceof PerpError)
|
|
1856
|
+
throw e;
|
|
1857
|
+
// A public read never surfaces as UNKNOWN: rate limit / server-side / other HTTP failure.
|
|
1858
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1859
|
+
throw new PerpError(res.status === 429 ? "RATE_LIMITED" : res.status >= 500 ? "EXCHANGE_UNREACHABLE" : "EXCHANGE_ERROR", `Aster ${msg}`, {
|
|
1860
|
+
exchange: "aster", status: res.status, remediation: res.status === 429 || res.status >= 500 ? "Retry later" : "Check the request (symbol / parameters)",
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
// A parameter rejection arrives as HTTP 200 with an error body (live 2026-10-19,
|
|
1864
|
+
// fundingRate limit=1001: {"status":"ERROR","type":"VALID","code":"000002","errorData":{"limit":"…"}}).
|
|
1865
|
+
if (result !== null && typeof result === "object" && !Array.isArray(result) && result.status === "ERROR") {
|
|
1866
|
+
const r = result;
|
|
1867
|
+
const detail = JSON.stringify(r.errorData ?? r).slice(0, 200);
|
|
1868
|
+
throw new PerpError(r.type === "VALID" ? "INVALID_PARAMS" : "EXCHANGE_ERROR", `Aster ${path} rejected the request: ${detail}`, {
|
|
1869
|
+
exchange: "aster", venueCode: r.code, remediation: "Check the request parameters (e.g. --limit 1..1000)",
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1772
1872
|
if (result !== null)
|
|
1773
1873
|
return result;
|
|
1774
1874
|
}
|
|
1775
|
-
throw new
|
|
1875
|
+
throw new PerpError("RATE_LIMITED", `Aster GET ${path}: rate limited after 3 attempts`, { exchange: "aster", remediation: "Retry later" });
|
|
1776
1876
|
}
|
|
1777
1877
|
}
|
|
@@ -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
|
|
@@ -30,3 +29,10 @@ export declare function isNeverMatch(a: string, b: string): boolean;
|
|
|
30
29
|
* @param symbol plain symbol as reported by the adapter (no quote suffix, no dex prefix)
|
|
31
30
|
*/
|
|
32
31
|
export declare function canonicalFor(venueKey: VenueId | `hl:${string}`, symbol: string, assetClass: AssetClass): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical key for a symbol a USER typed (no venue / asset class known — `arb scan --compare`):
|
|
34
|
+
* a "-PERP" suffix is dropped, a denomination alias maps to its canonical ticker (kPEPE →
|
|
35
|
+
* 1000PEPE, UBTC → BTC), and a venue ticker of the explicit table maps to its canonical key when
|
|
36
|
+
* every venue that lists it agrees (XAU → GOLD, CL → CRUDE_OIL_WTI). Anything else: upper-cased.
|
|
37
|
+
*/
|
|
38
|
+
export declare function canonicalQuerySymbol(symbol: string): string;
|
|
@@ -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([
|
|
@@ -92,5 +116,25 @@ export function canonicalFor(venueKey, symbol, assetClass) {
|
|
|
92
116
|
case "fx":
|
|
93
117
|
case "preIpo":
|
|
94
118
|
return EXPLICIT_CANONICAL[`${venueKey}|${up}`] ?? null;
|
|
119
|
+
case "unclassified":
|
|
120
|
+
return null; // ambiguous ticker: listed, never matched across venues
|
|
95
121
|
}
|
|
96
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Canonical key for a symbol a USER typed (no venue / asset class known — `arb scan --compare`):
|
|
125
|
+
* a "-PERP" suffix is dropped, a denomination alias maps to its canonical ticker (kPEPE →
|
|
126
|
+
* 1000PEPE, UBTC → BTC), and a venue ticker of the explicit table maps to its canonical key when
|
|
127
|
+
* every venue that lists it agrees (XAU → GOLD, CL → CRUDE_OIL_WTI). Anything else: upper-cased.
|
|
128
|
+
*/
|
|
129
|
+
export function canonicalQuerySymbol(symbol) {
|
|
130
|
+
const up = symbol.trim().toUpperCase().replace(/-PERP$/, "");
|
|
131
|
+
const alias = DENOMINATION_ALIASES[up];
|
|
132
|
+
if (alias !== undefined)
|
|
133
|
+
return alias;
|
|
134
|
+
const targets = new Set();
|
|
135
|
+
for (const [key, canonical] of Object.entries(EXPLICIT_CANONICAL)) {
|
|
136
|
+
if (key.slice(key.indexOf("|") + 1) === up)
|
|
137
|
+
targets.add(canonical);
|
|
138
|
+
}
|
|
139
|
+
return targets.size === 1 ? [...targets][0] : up;
|
|
140
|
+
}
|
|
@@ -29,6 +29,8 @@ export interface TriggerOrderCapable {
|
|
|
29
29
|
export interface DexCapable {
|
|
30
30
|
readonly dex: string;
|
|
31
31
|
setDex(dex: string): void;
|
|
32
|
+
/** An independent adapter for `dex` with this account configuration and its own market state (never mutates this one). */
|
|
33
|
+
forDex(dex: string): ExchangeAdapter & DexCapable;
|
|
32
34
|
listDeployedDexes(): Promise<{
|
|
33
35
|
name: string;
|
|
34
36
|
deployer: string;
|
|
@@ -18,7 +18,8 @@ export function isTriggerOrderCapable(adapter) {
|
|
|
18
18
|
}
|
|
19
19
|
export function isDexCapable(adapter) {
|
|
20
20
|
return typeof adapter.setDex === "function"
|
|
21
|
-
&& typeof adapter.listDeployedDexes === "function"
|
|
21
|
+
&& typeof adapter.listDeployedDexes === "function"
|
|
22
|
+
&& typeof adapter.forDex === "function";
|
|
22
23
|
}
|
|
23
24
|
export function isSubAccountCapable(adapter) {
|
|
24
25
|
return typeof adapter.createSubAccount === "function";
|