perp-cli 0.9.8 → 0.12.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 +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- package/dist/bot/engine.js +23 -4
- package/dist/bot/presets.js +3 -3
- package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
- package/dist/bot/strategies/funding-arb-strategy.js +41 -176
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +20 -16
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +1122 -176
- package/dist/config.d.ts +9 -0
- package/dist/config.js +55 -9
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +88 -34
- package/dist/exchanges/lighter-spot.js +19 -5
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +213 -31
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- package/dist/guardrail/contracts.d.ts +14 -0
- package/dist/guardrail/contracts.js +72 -0
- package/dist/guardrail/perp-guardrail.d.ts +21 -0
- package/dist/guardrail/perp-guardrail.js +145 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +307 -362
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +92 -88
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +69 -3
- package/dist/settings.js +3 -2
- package/dist/signer/index.d.ts +2 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/ows-evm.d.ts +32 -0
- package/dist/signer/ows-evm.js +86 -0
- package/dist/signer/ows-loader.d.ts +2 -0
- package/dist/signer/ows-loader.js +6 -0
- package/dist/signer/ows-solana.d.ts +25 -0
- package/dist/signer/ows-solana.js +56 -0
- package/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +4 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
package/dist/exchanges/aster.js
CHANGED
|
@@ -1,36 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Aster DEX adapter —
|
|
2
|
+
* Aster DEX adapter — EIP-712 three-tier signer routing (v3.2).
|
|
3
|
+
*
|
|
4
|
+
* Signer priority:
|
|
5
|
+
* Tier 1 — Agent OWS wallet (registered + not expired + no --no-agent)
|
|
6
|
+
* Tier 2 — OWS master wallet (--ows / owsActiveWallet)
|
|
7
|
+
* Tier 3 — PK direct (--private-key / PRIVATE_KEY / ASTER_PRIVATE_KEY)
|
|
8
|
+
*
|
|
9
|
+
* HMAC paths fully removed (see Step 3 plan — all Binance-compat HMAC code deleted).
|
|
10
|
+
*
|
|
3
11
|
* Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
|
|
4
12
|
* Base: https://fapi.asterdex.com
|
|
5
13
|
*/
|
|
6
|
-
import {
|
|
14
|
+
import { isExpired } from "../agent-wallet/expiry.js";
|
|
15
|
+
import { classifyError, PerpError } from "../errors.js";
|
|
16
|
+
import { buildOrderTypedData } from "./aster-typed-data.js";
|
|
17
|
+
// ── AsterAdapter ──────────────────────────────────────────────────────────────
|
|
7
18
|
export class AsterAdapter {
|
|
8
19
|
name = "aster";
|
|
9
20
|
chain = "bnb";
|
|
10
21
|
aliases = ["ast"];
|
|
11
|
-
_apiKey;
|
|
12
|
-
_apiSecret;
|
|
13
22
|
_baseUrl;
|
|
14
23
|
_testnet;
|
|
24
|
+
// Signer tiers
|
|
25
|
+
_agentMeta;
|
|
26
|
+
_agentSigner; // Tier 1
|
|
27
|
+
_masterSigner; // Tier 2
|
|
28
|
+
_pkSigner; // Tier 3
|
|
29
|
+
_useNoAgent = false;
|
|
30
|
+
/** PK held between ctor and init() since LocalEvmSigner.create() is async */
|
|
31
|
+
_pendingPk;
|
|
32
|
+
// Cache
|
|
15
33
|
_marketsCache = null;
|
|
16
34
|
_marketsCacheTime = 0;
|
|
17
35
|
_fundingHoursCache = new Map();
|
|
18
36
|
_accountCache = null;
|
|
19
37
|
_positionsCache = null;
|
|
20
38
|
_ordersCache = null;
|
|
21
|
-
static CACHE_TTL = 30_000;
|
|
22
|
-
static ACCOUNT_CACHE_TTL = 5_000;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
static CACHE_TTL = 30_000;
|
|
40
|
+
static ACCOUNT_CACHE_TTL = 5_000;
|
|
41
|
+
// Nonce counter — ensures microsecond-precision uniqueness within process
|
|
42
|
+
_nonceCounter = 0;
|
|
43
|
+
constructor(privateKey, testnet = false) {
|
|
26
44
|
this._testnet = testnet;
|
|
27
45
|
this._baseUrl = testnet
|
|
28
46
|
? (process.env.ASTER_TESTNET_URL || "https://testnet.asterdex.com")
|
|
29
47
|
: "https://fapi.asterdex.com";
|
|
48
|
+
if (privateKey) {
|
|
49
|
+
this._pendingPk = privateKey;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ── Lifecycle ──
|
|
53
|
+
async init() {
|
|
54
|
+
// Verify connectivity by fetching server time
|
|
55
|
+
await this._publicGet("/fapi/v1/time");
|
|
56
|
+
// Build LocalEvmSigner from ctor PK now that we're in an async context
|
|
57
|
+
if (this._pendingPk) {
|
|
58
|
+
const { LocalEvmSigner } = await import("../signer/index.js");
|
|
59
|
+
this._pkSigner = await LocalEvmSigner.create(this._pendingPk);
|
|
60
|
+
this._pendingPk = undefined;
|
|
61
|
+
}
|
|
30
62
|
}
|
|
63
|
+
// ── Signer injection ──
|
|
64
|
+
setAgent(meta, strategy) {
|
|
65
|
+
this._agentMeta = meta;
|
|
66
|
+
this._agentSigner = strategy;
|
|
67
|
+
}
|
|
68
|
+
setMasterSigner(signer) {
|
|
69
|
+
this._masterSigner = signer;
|
|
70
|
+
}
|
|
71
|
+
setPkSigner(signer) {
|
|
72
|
+
this._pkSigner = signer;
|
|
73
|
+
}
|
|
74
|
+
setNoAgent(noAgent) {
|
|
75
|
+
this._useNoAgent = noAgent;
|
|
76
|
+
}
|
|
77
|
+
// ── Status accessors ──
|
|
31
78
|
get isReadOnly() {
|
|
32
|
-
return !this.
|
|
79
|
+
return !this._agentSigner && !this._masterSigner && !this._pkSigner;
|
|
80
|
+
}
|
|
81
|
+
get activeSignerTier() {
|
|
82
|
+
try {
|
|
83
|
+
return this._resolveSigner().tier;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
33
88
|
}
|
|
89
|
+
// ── Symbol helpers ──
|
|
34
90
|
/** CLI symbol → Aster API symbol (ETH → ETHUSDT) */
|
|
35
91
|
_toApi(symbol) {
|
|
36
92
|
const s = symbol.toUpperCase().replace(/-PERP$/, "");
|
|
@@ -42,12 +98,8 @@ export class AsterAdapter {
|
|
|
42
98
|
_fromApi(symbol) {
|
|
43
99
|
return symbol.replace(/USDT$/, "").replace(/BUSD$/, "");
|
|
44
100
|
}
|
|
45
|
-
async init() {
|
|
46
|
-
// Verify connectivity by fetching server time
|
|
47
|
-
await this._publicGet("/fapi/v1/time");
|
|
48
|
-
}
|
|
49
101
|
// ── Market Data ──
|
|
50
|
-
/** Get funding interval for a symbol (lazy bootstrap
|
|
102
|
+
/** Get funding interval for a symbol (lazy bootstrap). */
|
|
51
103
|
async getFundingHours(symbol) {
|
|
52
104
|
const key = symbol.toUpperCase();
|
|
53
105
|
const cached = this._fundingHoursCache.get(key);
|
|
@@ -64,11 +116,9 @@ export class AsterAdapter {
|
|
|
64
116
|
}
|
|
65
117
|
}
|
|
66
118
|
catch { /* non-critical */ }
|
|
67
|
-
// Don't cache a default — let strategy skip this symbol until real data is available
|
|
68
119
|
return undefined;
|
|
69
120
|
}
|
|
70
121
|
async getMarkets() {
|
|
71
|
-
// Return cached result if fresh
|
|
72
122
|
if (this._marketsCache && Date.now() - this._marketsCacheTime < AsterAdapter.CACHE_TTL) {
|
|
73
123
|
return this._marketsCache;
|
|
74
124
|
}
|
|
@@ -80,7 +130,6 @@ export class AsterAdapter {
|
|
|
80
130
|
for (const t of tickers ?? []) {
|
|
81
131
|
tickerMap.set(String(t.symbol), t);
|
|
82
132
|
}
|
|
83
|
-
// Also fetch premium index for mark/index/funding
|
|
84
133
|
let premiumMap = new Map();
|
|
85
134
|
try {
|
|
86
135
|
const premiums = await this._publicGet("/fapi/v1/premiumIndex");
|
|
@@ -89,17 +138,13 @@ export class AsterAdapter {
|
|
|
89
138
|
catch { /* non-critical */ }
|
|
90
139
|
const tradingSymbols = (info?.symbols ?? [])
|
|
91
140
|
.filter((s) => String(s.contractType) === "PERPETUAL" && String(s.status) === "TRADING");
|
|
92
|
-
|
|
93
|
-
const result = tradingSymbols
|
|
94
|
-
.map((s) => {
|
|
141
|
+
const result = tradingSymbols.map((s) => {
|
|
95
142
|
const sym = String(s.symbol);
|
|
96
143
|
const ticker = tickerMap.get(sym);
|
|
97
144
|
const premium = premiumMap.get(sym);
|
|
98
145
|
const maxLev = Number(s.maxLeverage ?? 50);
|
|
99
146
|
const lotFilter = s.filters
|
|
100
147
|
?.find(f => f.filterType === "LOT_SIZE");
|
|
101
|
-
// Use cached funding interval if available (populated by getFundingHours()).
|
|
102
|
-
// If not yet bootstrapped, fundingHours = undefined → strategy must skip this symbol.
|
|
103
148
|
const fundingHours = this._fundingHoursCache.get(this._fromApi(sym));
|
|
104
149
|
return {
|
|
105
150
|
symbol: this._fromApi(sym),
|
|
@@ -173,7 +218,9 @@ export class AsterAdapter {
|
|
|
173
218
|
if (this._accountCache && Date.now() - this._accountCache.time < AsterAdapter.ACCOUNT_CACHE_TTL) {
|
|
174
219
|
return this._accountCache.data;
|
|
175
220
|
}
|
|
176
|
-
const
|
|
221
|
+
const r = this._resolveSigner();
|
|
222
|
+
// v3: /fapi/v2/account is HMAC-only; v3 EIP-712 uses /fapi/v3/accountWithJoinMargin.
|
|
223
|
+
const account = await this._signedGetEip712("/fapi/v3/accountWithJoinMargin", {}, r);
|
|
177
224
|
const totalWallet = Number(account.totalWalletBalance ?? 0);
|
|
178
225
|
const unrealizedPnl = Number(account.totalUnrealizedProfit ?? 0);
|
|
179
226
|
const available = Number(account.availableBalance ?? 0);
|
|
@@ -191,19 +238,35 @@ export class AsterAdapter {
|
|
|
191
238
|
if (this._positionsCache && Date.now() - this._positionsCache.time < AsterAdapter.ACCOUNT_CACHE_TTL) {
|
|
192
239
|
return this._positionsCache.data;
|
|
193
240
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
241
|
+
const r = this._resolveSigner();
|
|
242
|
+
// v3: /fapi/v2/positionRisk has no v3 equivalent. Positions live inside
|
|
243
|
+
// /fapi/v3/accountWithJoinMargin under `positions`. Mark price + liquidation
|
|
244
|
+
// price are NOT included there — fetch from the public premiumIndex.
|
|
245
|
+
const account = await this._signedGetEip712("/fapi/v3/accountWithJoinMargin", {}, r);
|
|
246
|
+
const positions = account.positions ?? [];
|
|
247
|
+
const open = positions.filter((p) => Number(p.positionAmt ?? 0) !== 0);
|
|
248
|
+
// Fetch mark prices for the open symbols (best-effort; non-fatal on failure)
|
|
249
|
+
const markMap = new Map();
|
|
250
|
+
if (open.length > 0) {
|
|
251
|
+
try {
|
|
252
|
+
const premiums = await this._publicGet("/fapi/v1/premiumIndex");
|
|
253
|
+
for (const p of premiums ?? []) {
|
|
254
|
+
markMap.set(String(p.symbol), String(p.markPrice ?? "0"));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch { /* non-critical */ }
|
|
258
|
+
}
|
|
259
|
+
const result = open.map((p) => {
|
|
198
260
|
const amt = Number(p.positionAmt ?? 0);
|
|
261
|
+
const apiSym = String(p.symbol ?? "");
|
|
199
262
|
return {
|
|
200
|
-
symbol: this._fromApi(
|
|
263
|
+
symbol: this._fromApi(apiSym),
|
|
201
264
|
side: amt > 0 ? "long" : "short",
|
|
202
265
|
size: String(Math.abs(amt)),
|
|
203
266
|
entryPrice: String(p.entryPrice ?? "0"),
|
|
204
|
-
markPrice:
|
|
205
|
-
liquidationPrice:
|
|
206
|
-
unrealizedPnl: String(p.
|
|
267
|
+
markPrice: markMap.get(apiSym) ?? "0",
|
|
268
|
+
liquidationPrice: "0", // v3 accountWithJoinMargin does not expose this
|
|
269
|
+
unrealizedPnl: String(p.unrealizedProfit ?? "0"),
|
|
207
270
|
leverage: Number(p.leverage ?? 1),
|
|
208
271
|
};
|
|
209
272
|
});
|
|
@@ -214,7 +277,8 @@ export class AsterAdapter {
|
|
|
214
277
|
if (this._ordersCache && Date.now() - this._ordersCache.time < AsterAdapter.ACCOUNT_CACHE_TTL) {
|
|
215
278
|
return this._ordersCache.data;
|
|
216
279
|
}
|
|
217
|
-
const
|
|
280
|
+
const r = this._resolveSigner();
|
|
281
|
+
const orders = await this._signedGetEip712("/fapi/v3/openOrders", {}, r);
|
|
218
282
|
const result = (orders ?? []).map((o) => ({
|
|
219
283
|
orderId: String(o.orderId ?? ""),
|
|
220
284
|
symbol: this._fromApi(String(o.symbol ?? "")),
|
|
@@ -229,22 +293,21 @@ export class AsterAdapter {
|
|
|
229
293
|
return result;
|
|
230
294
|
}
|
|
231
295
|
async getOrderHistory(limit = 30) {
|
|
232
|
-
// allOrders requires symbol — get from positions or recent trades
|
|
233
296
|
const positions = await this.getPositions();
|
|
234
297
|
const apiSymbols = new Set(positions.map(p => this._toApi(p.symbol)));
|
|
235
298
|
if (apiSymbols.size === 0) {
|
|
236
|
-
// Fallback: query the most common perp symbols when no open positions exist
|
|
237
299
|
for (const s of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
|
|
238
300
|
apiSymbols.add(s);
|
|
239
301
|
}
|
|
240
302
|
}
|
|
303
|
+
const r = this._resolveSigner();
|
|
241
304
|
const allOrders = [];
|
|
242
305
|
for (const sym of apiSymbols) {
|
|
243
306
|
try {
|
|
244
|
-
const orders = await this.
|
|
307
|
+
const orders = await this._signedGetEip712("/fapi/v3/allOrders", {
|
|
245
308
|
symbol: sym,
|
|
246
309
|
limit: String(limit),
|
|
247
|
-
});
|
|
310
|
+
}, r);
|
|
248
311
|
for (const o of orders ?? []) {
|
|
249
312
|
allOrders.push({
|
|
250
313
|
orderId: String(o.orderId ?? ""),
|
|
@@ -267,13 +330,14 @@ export class AsterAdapter {
|
|
|
267
330
|
const apiSymbols = new Set(positions.map(p => this._toApi(p.symbol)));
|
|
268
331
|
if (apiSymbols.size === 0)
|
|
269
332
|
apiSymbols.add("BTCUSDT");
|
|
333
|
+
const r = this._resolveSigner();
|
|
270
334
|
const allTrades = [];
|
|
271
335
|
for (const sym of apiSymbols) {
|
|
272
336
|
try {
|
|
273
|
-
const trades = await this.
|
|
337
|
+
const trades = await this._signedGetEip712("/fapi/v3/userTrades", {
|
|
274
338
|
symbol: sym,
|
|
275
339
|
limit: String(limit),
|
|
276
|
-
});
|
|
340
|
+
}, r);
|
|
277
341
|
for (const t of trades ?? []) {
|
|
278
342
|
allTrades.push({
|
|
279
343
|
time: Number(t.time ?? 0),
|
|
@@ -290,10 +354,11 @@ export class AsterAdapter {
|
|
|
290
354
|
return allTrades.sort((a, b) => b.time - a.time).slice(0, limit);
|
|
291
355
|
}
|
|
292
356
|
async getFundingPayments(limit = 200) {
|
|
293
|
-
const
|
|
357
|
+
const r = this._resolveSigner();
|
|
358
|
+
const data = await this._signedGetEip712("/fapi/v3/income", {
|
|
294
359
|
incomeType: "FUNDING_FEE",
|
|
295
360
|
limit: String(limit),
|
|
296
|
-
});
|
|
361
|
+
}, r);
|
|
297
362
|
return (data ?? []).map((f) => ({
|
|
298
363
|
time: Number(f.time ?? 0),
|
|
299
364
|
symbol: this._fromApi(String(f.symbol ?? "")),
|
|
@@ -302,6 +367,7 @@ export class AsterAdapter {
|
|
|
302
367
|
}
|
|
303
368
|
// ── Trading ──
|
|
304
369
|
async marketOrder(symbol, side, size, opts) {
|
|
370
|
+
const r = this._resolveSigner();
|
|
305
371
|
const apiSymbol = this._toApi(symbol);
|
|
306
372
|
const params = {
|
|
307
373
|
symbol: apiSymbol,
|
|
@@ -311,25 +377,23 @@ export class AsterAdapter {
|
|
|
311
377
|
};
|
|
312
378
|
if (opts?.reduceOnly)
|
|
313
379
|
params.reduceOnly = "true";
|
|
314
|
-
const result = await this.
|
|
315
|
-
const
|
|
316
|
-
const executedQty = Number(
|
|
317
|
-
const orderId = String(
|
|
318
|
-
|
|
319
|
-
if (executedQty > 0 && r.status === "FILLED")
|
|
380
|
+
const result = await this._signedPostEip712("/fapi/v3/order", params, r);
|
|
381
|
+
const ro = result;
|
|
382
|
+
const executedQty = Number(ro.executedQty ?? 0);
|
|
383
|
+
const orderId = String(ro.orderId ?? "");
|
|
384
|
+
if (executedQty > 0 && ro.status === "FILLED")
|
|
320
385
|
return result;
|
|
321
|
-
|
|
322
|
-
if (r.status === "NEW" && orderId) {
|
|
386
|
+
if (ro.status === "NEW" && orderId) {
|
|
323
387
|
for (let i = 0; i < 3; i++) {
|
|
324
|
-
await new Promise(
|
|
388
|
+
await new Promise(res => setTimeout(res, 1000));
|
|
325
389
|
try {
|
|
326
|
-
const check = await this.
|
|
390
|
+
const check = await this._signedGetEip712("/fapi/v3/order", {
|
|
327
391
|
symbol: apiSymbol,
|
|
328
392
|
orderId,
|
|
329
|
-
});
|
|
393
|
+
}, r);
|
|
330
394
|
const filledQty = Number(check.executedQty ?? 0);
|
|
331
395
|
if (filledQty > 0)
|
|
332
|
-
return check;
|
|
396
|
+
return check;
|
|
333
397
|
if (check.status === "CANCELED" || check.status === "EXPIRED" || check.status === "REJECTED") {
|
|
334
398
|
throw new Error(`Market ${side} ${symbol}: order ${check.status} (orderId: ${orderId})`);
|
|
335
399
|
}
|
|
@@ -337,23 +401,21 @@ export class AsterAdapter {
|
|
|
337
401
|
catch (e) {
|
|
338
402
|
if (e instanceof Error && e.message.startsWith("Market "))
|
|
339
403
|
throw e;
|
|
340
|
-
// query failed, retry
|
|
341
404
|
}
|
|
342
405
|
}
|
|
343
|
-
// Still not filled after 3s — cancel and throw
|
|
344
406
|
try {
|
|
345
|
-
await this.
|
|
407
|
+
await this._signedDeleteEip712("/fapi/v3/order", { symbol: apiSymbol, orderId }, r);
|
|
346
408
|
}
|
|
347
409
|
catch { /* best effort cancel */ }
|
|
348
410
|
throw new Error(`Market ${side} ${symbol}: order not filled after 3s, cancelled (orderId: ${orderId})`);
|
|
349
411
|
}
|
|
350
|
-
// executedQty === 0 with non-NEW status
|
|
351
412
|
if (executedQty === 0) {
|
|
352
|
-
throw new Error(`Market ${side} ${symbol}: order accepted but 0 filled (status: ${
|
|
413
|
+
throw new Error(`Market ${side} ${symbol}: order accepted but 0 filled (status: ${ro.status}, orderId: ${orderId})`);
|
|
353
414
|
}
|
|
354
415
|
return result;
|
|
355
416
|
}
|
|
356
417
|
async limitOrder(symbol, side, price, size, opts) {
|
|
418
|
+
const r = this._resolveSigner();
|
|
357
419
|
const params = {
|
|
358
420
|
symbol: this._toApi(symbol),
|
|
359
421
|
side: side.toUpperCase(),
|
|
@@ -364,7 +426,7 @@ export class AsterAdapter {
|
|
|
364
426
|
};
|
|
365
427
|
if (opts?.reduceOnly)
|
|
366
428
|
params.reduceOnly = "true";
|
|
367
|
-
return this.
|
|
429
|
+
return this._signedPostEip712("/fapi/v3/order", params, r);
|
|
368
430
|
}
|
|
369
431
|
async editOrder(symbol, orderId, price, size) {
|
|
370
432
|
// Aster has no atomic edit — cancel + replace
|
|
@@ -375,44 +437,44 @@ export class AsterAdapter {
|
|
|
375
437
|
return this.limitOrder(symbol, side, price, size);
|
|
376
438
|
}
|
|
377
439
|
async cancelOrder(symbol, orderId) {
|
|
378
|
-
|
|
440
|
+
const r = this._resolveSigner();
|
|
441
|
+
return this._signedDeleteEip712("/fapi/v3/order", {
|
|
379
442
|
symbol: this._toApi(symbol),
|
|
380
443
|
orderId,
|
|
381
|
-
});
|
|
444
|
+
}, r);
|
|
382
445
|
}
|
|
383
446
|
async cancelAllOrders(symbol) {
|
|
447
|
+
const r = this._resolveSigner();
|
|
384
448
|
if (!symbol) {
|
|
385
|
-
// Cancel all for all symbols with open orders
|
|
386
449
|
const orders = await this.getOpenOrders();
|
|
387
450
|
const apiSymbols = new Set(orders.map(o => this._toApi(o.symbol)));
|
|
388
451
|
const results = [];
|
|
389
452
|
for (const sym of apiSymbols) {
|
|
390
|
-
results.push(await this.
|
|
453
|
+
results.push(await this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: sym }, r));
|
|
391
454
|
}
|
|
392
455
|
return results;
|
|
393
456
|
}
|
|
394
|
-
return this.
|
|
457
|
+
return this._signedDeleteEip712("/fapi/v3/allOpenOrders", { symbol: this._toApi(symbol) }, r);
|
|
395
458
|
}
|
|
396
459
|
// ── Risk ──
|
|
397
460
|
async setLeverage(symbol, leverage, marginMode) {
|
|
398
|
-
|
|
461
|
+
const r = this._resolveSigner();
|
|
399
462
|
if (marginMode) {
|
|
400
463
|
try {
|
|
401
|
-
await this.
|
|
464
|
+
await this._signedPostEip712("/fapi/v3/marginType", {
|
|
402
465
|
symbol: this._toApi(symbol),
|
|
403
466
|
marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
catch {
|
|
407
|
-
// May fail if already set — non-critical
|
|
467
|
+
}, r);
|
|
408
468
|
}
|
|
469
|
+
catch { /* may fail if already set */ }
|
|
409
470
|
}
|
|
410
|
-
return this.
|
|
471
|
+
return this._signedPostEip712("/fapi/v3/leverage", {
|
|
411
472
|
symbol: this._toApi(symbol),
|
|
412
473
|
leverage: String(leverage),
|
|
413
|
-
});
|
|
474
|
+
}, r);
|
|
414
475
|
}
|
|
415
476
|
async stopOrder(symbol, side, size, triggerPrice, opts) {
|
|
477
|
+
const r = this._resolveSigner();
|
|
416
478
|
const params = {
|
|
417
479
|
symbol: this._toApi(symbol),
|
|
418
480
|
side: side.toUpperCase(),
|
|
@@ -426,26 +488,155 @@ export class AsterAdapter {
|
|
|
426
488
|
}
|
|
427
489
|
if (opts?.reduceOnly)
|
|
428
490
|
params.reduceOnly = "true";
|
|
429
|
-
return this.
|
|
491
|
+
return this._signedPostEip712("/fapi/v3/order", params, r);
|
|
430
492
|
}
|
|
431
|
-
// ── Withdraw (
|
|
493
|
+
// ── Withdraw (master-only: never delegates to agent or PK) ──
|
|
432
494
|
async withdraw(amount, _destination, _opts) {
|
|
433
|
-
|
|
495
|
+
if (!this._masterSigner) {
|
|
496
|
+
throw new PerpError("NO_SIGNER_AVAILABLE", "Aster withdrawal requires OWS master wallet (--ows). Agent and PK signers cannot authorize withdrawals.", {
|
|
497
|
+
remediation: "Use: perp -e aster --ows <walletName> funds withdraw",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
// Aster withdrawal via spot/wallet API (not futures API) — not yet implemented.
|
|
434
501
|
throw new Error("Aster withdrawal requires the spot API (not available in futures mode). Use the Aster web UI to withdraw.");
|
|
435
502
|
}
|
|
436
|
-
// ──
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
503
|
+
// ── Private: signer resolution ────────────────────────────────────────────
|
|
504
|
+
_resolveSigner() {
|
|
505
|
+
// Tier 1: agent (when registered, not expired, --no-agent NOT set)
|
|
506
|
+
if (!this._useNoAgent && this._agentSigner && this._agentMeta) {
|
|
507
|
+
if (isExpired(this._agentMeta)) {
|
|
508
|
+
// Skip Tier 1 only if Tier 2/3 available; otherwise throw AGENT_EXPIRED
|
|
509
|
+
if (!this._masterSigner && !this._pkSigner) {
|
|
510
|
+
throw new PerpError("AGENT_EXPIRED", "Agent wallet has expired", {
|
|
511
|
+
remediation: "perp wallet agent approve aster --rotate",
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
// fall through to Tier 2/3
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
return {
|
|
518
|
+
tier: "agent",
|
|
519
|
+
signer: this._agentSigner,
|
|
520
|
+
signerAddress: this._agentMeta.agentEvmAddress,
|
|
521
|
+
userAddress: this._agentMeta.userEvmAddress,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Tier 2: OWS master
|
|
526
|
+
if (this._masterSigner) {
|
|
527
|
+
const addr = this._masterSigner.getAddress();
|
|
528
|
+
return { tier: "master", signer: this._masterSigner, signerAddress: addr, userAddress: addr };
|
|
529
|
+
}
|
|
530
|
+
// Tier 3: PK direct
|
|
531
|
+
if (this._pkSigner) {
|
|
532
|
+
const addr = this._pkSigner.getAddress();
|
|
533
|
+
return { tier: "pk", signer: this._pkSigner, signerAddress: addr, userAddress: addr };
|
|
534
|
+
}
|
|
535
|
+
// No signer at any tier
|
|
536
|
+
throw new PerpError("NO_SIGNER_AVAILABLE", "No signing path configured for Aster", {
|
|
537
|
+
remediation: "Run one of: (a) perp wallet agent approve aster --master <wallet>; (b) perp wallet generate && perp -e aster --ows <wallet>; (c) export ASTER_PRIVATE_KEY=0x...",
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
// ── Private: nonce ────────────────────────────────────────────────────────
|
|
541
|
+
_nextNonce() {
|
|
542
|
+
const ms = Date.now() * 1000 + (++this._nonceCounter % 1000);
|
|
543
|
+
return String(ms);
|
|
544
|
+
}
|
|
545
|
+
// ── Private: EIP-712 signed HTTP helpers ─────────────────────────────────
|
|
546
|
+
/**
|
|
547
|
+
* Build the canonical signed-request URL per Aster v3 spec.
|
|
548
|
+
*
|
|
549
|
+
* Reference: https://github.com/asterdex/api-docs (V3, EN, send_by_url).
|
|
550
|
+
* The Python reference adds keys to the dict in this exact order:
|
|
551
|
+
* nonce, user, signer
|
|
552
|
+
* then URL-encodes the dict to form the EIP-712 `msg`. After signing,
|
|
553
|
+
* `&signature=` is appended to the same query string. Authority verification
|
|
554
|
+
* reconstructs `msg` from the URL query params minus `signature`, so the
|
|
555
|
+
* signed dict and the URL query string must be byte-identical except for
|
|
556
|
+
* the appended signature.
|
|
557
|
+
*
|
|
558
|
+
* `signatureChainId` is NOT a v3 parameter — it's an artifact of an older
|
|
559
|
+
* scheme; including it breaks signature verification on every call.
|
|
560
|
+
*/
|
|
561
|
+
async _buildSignedQueryString(params, resolved) {
|
|
562
|
+
// Insertion order (matches Python reference: nonce, user, signer last).
|
|
563
|
+
const fullParams = {
|
|
564
|
+
...params,
|
|
565
|
+
nonce: this._nextNonce(),
|
|
566
|
+
user: resolved.userAddress,
|
|
567
|
+
signer: resolved.signerAddress,
|
|
568
|
+
};
|
|
569
|
+
const typed = buildOrderTypedData(fullParams);
|
|
570
|
+
// Sign via the appropriate signer type
|
|
571
|
+
const sigRaw = await resolved.signer.signTypedData(typed.domain, typed.types, typed.message);
|
|
572
|
+
// Normalize: AgentSigningStrategy returns { signature, r, s, v }; EvmSigner returns string
|
|
573
|
+
const sigHex = typeof sigRaw === "string" ? sigRaw : sigRaw.signature;
|
|
574
|
+
// typed.message.msg is the URL-encoded dict in the same order; reuse it
|
|
575
|
+
// to guarantee the byte-identical query string is sent on the wire.
|
|
576
|
+
return `${typed.message.msg}&signature=${sigHex}`;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Sign and POST to a path using EIP-712 Domain B.
|
|
580
|
+
*/
|
|
581
|
+
async _signedPostEip712(path, params, resolved) {
|
|
582
|
+
const qs = await this._buildSignedQueryString(params, resolved);
|
|
583
|
+
const url = `${this._baseUrl}${path}?${qs}`;
|
|
584
|
+
const res = await fetch(url, {
|
|
585
|
+
method: "POST",
|
|
586
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
587
|
+
body: "",
|
|
588
|
+
});
|
|
589
|
+
return this._handleEip712Response(res, "POST", path);
|
|
447
590
|
}
|
|
448
|
-
/**
|
|
591
|
+
/**
|
|
592
|
+
* Sign and GET from a path using EIP-712 Domain B.
|
|
593
|
+
*/
|
|
594
|
+
async _signedGetEip712(path, params, resolved) {
|
|
595
|
+
const qs = await this._buildSignedQueryString(params, resolved);
|
|
596
|
+
const url = `${this._baseUrl}${path}?${qs}`;
|
|
597
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
598
|
+
const res = await fetch(url);
|
|
599
|
+
const result = await this._handleResponse(res, "GET", path, attempt);
|
|
600
|
+
if (result !== null)
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
603
|
+
throw new Error(`GET ${path} failed: max retries exceeded`);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Sign and DELETE from a path using EIP-712 Domain B.
|
|
607
|
+
*/
|
|
608
|
+
async _signedDeleteEip712(path, params, resolved) {
|
|
609
|
+
const qs = await this._buildSignedQueryString(params, resolved);
|
|
610
|
+
const url = `${this._baseUrl}${path}?${qs}`;
|
|
611
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
612
|
+
const res = await fetch(url, { method: "DELETE" });
|
|
613
|
+
const result = await this._handleResponse(res, "DELETE", path, attempt);
|
|
614
|
+
if (result !== null)
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
throw new Error(`DELETE ${path} failed: max retries exceeded`);
|
|
618
|
+
}
|
|
619
|
+
// ── Private: HTTP response handling ──────────────────────────────────────
|
|
620
|
+
async _handleEip712Response(res, method, path) {
|
|
621
|
+
if (res.status === 429) {
|
|
622
|
+
const retryAfter = parseInt(res.headers.get("Retry-After") || "5", 10);
|
|
623
|
+
await new Promise(r => setTimeout(r, Math.min(retryAfter * 1000, 30000)));
|
|
624
|
+
throw new Error(`${method} ${path} rate limited`);
|
|
625
|
+
}
|
|
626
|
+
if (!res.ok) {
|
|
627
|
+
const text = await res.text().catch(() => "");
|
|
628
|
+
const s = classifyError(new Error(`${method} ${path} failed (${res.status}): ${text.slice(0, 200)}`), "aster");
|
|
629
|
+
throw new PerpError(s.code, s.message, { exchange: s.exchange });
|
|
630
|
+
}
|
|
631
|
+
const json = await res.json();
|
|
632
|
+
if (json.code !== undefined && json.code !== "000000" && json.code !== 200) {
|
|
633
|
+
const rawMsg = typeof json.msg === "string" ? json.msg : JSON.stringify(json);
|
|
634
|
+
const s = classifyError(new Error(rawMsg), "aster");
|
|
635
|
+
throw new PerpError(s.code, s.message, { exchange: s.exchange });
|
|
636
|
+
}
|
|
637
|
+
return json;
|
|
638
|
+
}
|
|
639
|
+
/** Handle 429 rate limit with retry — used by GET/DELETE multi-attempt loops */
|
|
449
640
|
async _handleResponse(res, method, path, attempt = 0) {
|
|
450
641
|
if (res.status === 429) {
|
|
451
642
|
if (attempt >= 2)
|
|
@@ -475,53 +666,4 @@ export class AsterAdapter {
|
|
|
475
666
|
}
|
|
476
667
|
throw new Error(`GET ${path} failed: max retries exceeded`);
|
|
477
668
|
}
|
|
478
|
-
async _signedGet(path, params = {}) {
|
|
479
|
-
if (this.isReadOnly)
|
|
480
|
-
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
481
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
482
|
-
const signed = this._sign({ ...params });
|
|
483
|
-
const url = `${this._baseUrl}${path}?${signed}`;
|
|
484
|
-
const res = await fetch(url, {
|
|
485
|
-
headers: { "X-MBX-APIKEY": this._apiKey },
|
|
486
|
-
});
|
|
487
|
-
const result = await this._handleResponse(res, "GET", path, attempt);
|
|
488
|
-
if (result !== null)
|
|
489
|
-
return result;
|
|
490
|
-
}
|
|
491
|
-
throw new Error(`GET ${path} failed: max retries exceeded`);
|
|
492
|
-
}
|
|
493
|
-
async _signedPost(path, params = {}) {
|
|
494
|
-
if (this.isReadOnly)
|
|
495
|
-
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
496
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
497
|
-
const signed = this._sign({ ...params });
|
|
498
|
-
const res = await fetch(`${this._baseUrl}${path}`, {
|
|
499
|
-
method: "POST",
|
|
500
|
-
headers: {
|
|
501
|
-
"X-MBX-APIKEY": this._apiKey,
|
|
502
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
503
|
-
},
|
|
504
|
-
body: signed,
|
|
505
|
-
});
|
|
506
|
-
const result = await this._handleResponse(res, "POST", path, attempt);
|
|
507
|
-
if (result !== null)
|
|
508
|
-
return result;
|
|
509
|
-
}
|
|
510
|
-
throw new Error(`POST ${path} failed: max retries exceeded`);
|
|
511
|
-
}
|
|
512
|
-
async _signedDelete(path, params = {}) {
|
|
513
|
-
if (this.isReadOnly)
|
|
514
|
-
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
515
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
516
|
-
const signed = this._sign({ ...params });
|
|
517
|
-
const res = await fetch(`${this._baseUrl}${path}?${signed}`, {
|
|
518
|
-
method: "DELETE",
|
|
519
|
-
headers: { "X-MBX-APIKEY": this._apiKey },
|
|
520
|
-
});
|
|
521
|
-
const result = await this._handleResponse(res, "DELETE", path, attempt);
|
|
522
|
-
if (result !== null)
|
|
523
|
-
return result;
|
|
524
|
-
}
|
|
525
|
-
throw new Error(`DELETE ${path} failed: max retries exceeded`);
|
|
526
|
-
}
|
|
527
669
|
}
|