perp-cli 0.12.11 → 0.12.12
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/dist/commands/alerts.js +1 -1
- package/dist/exchanges/aster.d.ts +23 -8
- package/dist/exchanges/aster.js +68 -28
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +2 -2
package/dist/commands/alerts.js
CHANGED
|
@@ -271,7 +271,7 @@ export function registerAlertCommands(program, isJson) {
|
|
|
271
271
|
// Pass through env vars for keys
|
|
272
272
|
// L2 agent key + indices now live in ~/.perp keystore + settings.agents (SSOT Rule #3),
|
|
273
273
|
// not env. Only Tier 3 master PKs need passthrough.
|
|
274
|
-
const envKeys = ["LIGHTER_PRIVATE_KEY", "HL_PRIVATE_KEY", "PACIFICA_PRIVATE_KEY", "PRIVATE_KEY"];
|
|
274
|
+
const envKeys = ["LIGHTER_PRIVATE_KEY", "HL_PRIVATE_KEY", "PACIFICA_PRIVATE_KEY", "ASTER_PRIVATE_KEY", "PRIVATE_KEY"];
|
|
275
275
|
const envStr = envKeys.filter(k => process.env[k]).map(k => `${k}='${process.env[k]}'`).join(" ");
|
|
276
276
|
const cmd = `${envStr} ${nodeCmd} ${cliPath} ${args}`;
|
|
277
277
|
execSync(`tmux new-session -d -s ${session} '${cmd.replace(/'/g, "'\\''")}'`);
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Aster DEX adapter — EIP-712
|
|
2
|
+
* Aster DEX adapter — EIP-712 agent-only signer routing (v3.3).
|
|
3
3
|
*
|
|
4
|
-
* Signer
|
|
5
|
-
* Tier 1 — Agent OWS wallet (registered + not expired + no --no-agent)
|
|
6
|
-
* Tier 2 — OWS master
|
|
7
|
-
* Tier 3 — PK direct (
|
|
4
|
+
* Signer policy:
|
|
5
|
+
* Tier 1 — Agent OWS wallet (registered + not expired + no --no-agent) — REQUIRED
|
|
6
|
+
* Tier 2 — OWS master — NOT_SUPPORTED (venue rejects master self-signing)
|
|
7
|
+
* Tier 3 — PK direct — NOT_SUPPORTED (venue rejects master self-signing)
|
|
8
|
+
*
|
|
9
|
+
* Aster V3 spec requires `signer` to be a registered API_WALLET (agent). The
|
|
10
|
+
* master wallet is never a valid signer — venue returns
|
|
11
|
+
* `code:-1000 msg:"Signature check failed"` when user==signer==master, even
|
|
12
|
+
* though the EIP-712 envelope is well-formed. Master/PK signers are still
|
|
13
|
+
* accepted via setMasterSigner/setPkSigner (used during approveAgent flow),
|
|
14
|
+
* but signed read/trade requests must use Tier 1.
|
|
8
15
|
*
|
|
9
16
|
* HMAC paths fully removed (see Step 3 plan — all Binance-compat HMAC code deleted).
|
|
10
17
|
*
|
|
@@ -116,9 +123,9 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
116
123
|
* signed dict and the URL query string must be byte-identical except for
|
|
117
124
|
* the appended signature.
|
|
118
125
|
*
|
|
119
|
-
* Both `user` and `signer` are ALWAYS emitted as distinct query fields
|
|
120
|
-
*
|
|
121
|
-
*
|
|
126
|
+
* Both `user` and `signer` are ALWAYS emitted as distinct query fields.
|
|
127
|
+
* In production (Tier 1 only), `user` is the master and `signer` is the
|
|
128
|
+
* registered agent address. Aster's authority server checks both fields.
|
|
122
129
|
*
|
|
123
130
|
* `signatureChainId` is NOT a v3 parameter — it's an artifact of an older
|
|
124
131
|
* scheme; including it breaks signature verification on every call.
|
|
@@ -137,12 +144,20 @@ export declare class AsterAdapter implements ExchangeAdapter {
|
|
|
137
144
|
* Unlike public GETs, signed GETs route through _handleAsterResponse so
|
|
138
145
|
* venue JSON error envelopes ({code, msg}) are not silently treated as
|
|
139
146
|
* success (SSOT Rule #2).
|
|
147
|
+
*
|
|
148
|
+
* Retries up to 3 attempts with exponential backoff (2s/4s/8s) on HTTP 429
|
|
149
|
+
* rate-limit. Each retry rebuilds a fresh signed query string (new nonce
|
|
150
|
+
* + signature) — Aster reuses recent nonces strictly, so reusing a stale
|
|
151
|
+
* one would itself be rejected. Non-429 venue errors throw immediately.
|
|
140
152
|
*/
|
|
141
153
|
private _signedGetEip712;
|
|
142
154
|
/**
|
|
143
155
|
* Sign and DELETE from a path using EIP-712 Domain B.
|
|
156
|
+
* Same retry behavior as _signedGetEip712.
|
|
144
157
|
*/
|
|
145
158
|
private _signedDeleteEip712;
|
|
159
|
+
/** Shared GET/DELETE retry loop with fresh-nonce per attempt. */
|
|
160
|
+
private _signedRequestWithRetry;
|
|
146
161
|
/**
|
|
147
162
|
* Unified response validator for ALL signed Aster requests.
|
|
148
163
|
*
|
package/dist/exchanges/aster.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Aster DEX adapter — EIP-712
|
|
2
|
+
* Aster DEX adapter — EIP-712 agent-only signer routing (v3.3).
|
|
3
3
|
*
|
|
4
|
-
* Signer
|
|
5
|
-
* Tier 1 — Agent OWS wallet (registered + not expired + no --no-agent)
|
|
6
|
-
* Tier 2 — OWS master
|
|
7
|
-
* Tier 3 — PK direct (
|
|
4
|
+
* Signer policy:
|
|
5
|
+
* Tier 1 — Agent OWS wallet (registered + not expired + no --no-agent) — REQUIRED
|
|
6
|
+
* Tier 2 — OWS master — NOT_SUPPORTED (venue rejects master self-signing)
|
|
7
|
+
* Tier 3 — PK direct — NOT_SUPPORTED (venue rejects master self-signing)
|
|
8
|
+
*
|
|
9
|
+
* Aster V3 spec requires `signer` to be a registered API_WALLET (agent). The
|
|
10
|
+
* master wallet is never a valid signer — venue returns
|
|
11
|
+
* `code:-1000 msg:"Signature check failed"` when user==signer==master, even
|
|
12
|
+
* though the EIP-712 envelope is well-formed. Master/PK signers are still
|
|
13
|
+
* accepted via setMasterSigner/setPkSigner (used during approveAgent flow),
|
|
14
|
+
* but signed read/trade requests must use Tier 1.
|
|
8
15
|
*
|
|
9
16
|
* HMAC paths fully removed (see Step 3 plan — all Binance-compat HMAC code deleted).
|
|
10
17
|
*
|
|
@@ -528,18 +535,19 @@ export class AsterAdapter {
|
|
|
528
535
|
};
|
|
529
536
|
}
|
|
530
537
|
}
|
|
531
|
-
// Tier 2:
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
//
|
|
540
|
-
if (this._pkSigner) {
|
|
541
|
-
|
|
542
|
-
|
|
538
|
+
// Tier 2/3: master self-signing is not supported by the venue. Per Aster
|
|
539
|
+
// V3 spec the `signer` field MUST be a registered API_WALLET (agent);
|
|
540
|
+
// sending `user==signer==master` is rejected with
|
|
541
|
+
// `code:-1000 msg:"Signature check failed"` even though the EIP-712
|
|
542
|
+
// envelope is well-formed. SSOT Rule #2: throw with remediation rather
|
|
543
|
+
// than silently dispatching a request the venue will reject.
|
|
544
|
+
//
|
|
545
|
+
// Reference: HypurrQuant_FE AsterPerpAdapter.ts throws if agent isn't
|
|
546
|
+
// configured; never attempts master self-signing.
|
|
547
|
+
if (this._masterSigner || this._pkSigner) {
|
|
548
|
+
throw new PerpError("NOT_IMPLEMENTED", "Aster requires a registered agent — master self-signing is not supported by venue", {
|
|
549
|
+
remediation: "perp wallet agent approve aster --master <wallet>",
|
|
550
|
+
});
|
|
543
551
|
}
|
|
544
552
|
// No signer at any tier
|
|
545
553
|
throw new PerpError("NO_SIGNER_AVAILABLE", "No signing path configured for Aster", {
|
|
@@ -564,9 +572,9 @@ export class AsterAdapter {
|
|
|
564
572
|
* signed dict and the URL query string must be byte-identical except for
|
|
565
573
|
* the appended signature.
|
|
566
574
|
*
|
|
567
|
-
* Both `user` and `signer` are ALWAYS emitted as distinct query fields
|
|
568
|
-
*
|
|
569
|
-
*
|
|
575
|
+
* Both `user` and `signer` are ALWAYS emitted as distinct query fields.
|
|
576
|
+
* In production (Tier 1 only), `user` is the master and `signer` is the
|
|
577
|
+
* registered agent address. Aster's authority server checks both fields.
|
|
570
578
|
*
|
|
571
579
|
* `signatureChainId` is NOT a v3 parameter — it's an artifact of an older
|
|
572
580
|
* scheme; including it breaks signature verification on every call.
|
|
@@ -610,21 +618,53 @@ export class AsterAdapter {
|
|
|
610
618
|
* Unlike public GETs, signed GETs route through _handleAsterResponse so
|
|
611
619
|
* venue JSON error envelopes ({code, msg}) are not silently treated as
|
|
612
620
|
* success (SSOT Rule #2).
|
|
621
|
+
*
|
|
622
|
+
* Retries up to 3 attempts with exponential backoff (2s/4s/8s) on HTTP 429
|
|
623
|
+
* rate-limit. Each retry rebuilds a fresh signed query string (new nonce
|
|
624
|
+
* + signature) — Aster reuses recent nonces strictly, so reusing a stale
|
|
625
|
+
* one would itself be rejected. Non-429 venue errors throw immediately.
|
|
613
626
|
*/
|
|
614
627
|
async _signedGetEip712(path, params, resolved) {
|
|
615
|
-
|
|
616
|
-
const url = `${this._baseUrl}${path}?${qs}`;
|
|
617
|
-
const res = await fetch(url);
|
|
618
|
-
return this._handleAsterResponse(res, "GET", path);
|
|
628
|
+
return this._signedRequestWithRetry("GET", path, params, resolved);
|
|
619
629
|
}
|
|
620
630
|
/**
|
|
621
631
|
* Sign and DELETE from a path using EIP-712 Domain B.
|
|
632
|
+
* Same retry behavior as _signedGetEip712.
|
|
622
633
|
*/
|
|
623
634
|
async _signedDeleteEip712(path, params, resolved) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
635
|
+
return this._signedRequestWithRetry("DELETE", path, params, resolved);
|
|
636
|
+
}
|
|
637
|
+
/** Shared GET/DELETE retry loop with fresh-nonce per attempt. */
|
|
638
|
+
async _signedRequestWithRetry(method, path, params, resolved) {
|
|
639
|
+
const MAX_ATTEMPTS = 3;
|
|
640
|
+
let lastErr;
|
|
641
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
642
|
+
// Rebuild signed query string with fresh nonce on each attempt.
|
|
643
|
+
const qs = await this._buildSignedQueryString(params, resolved);
|
|
644
|
+
const url = `${this._baseUrl}${path}?${qs}`;
|
|
645
|
+
const res = await fetch(url, method === "DELETE" ? { method: "DELETE" } : undefined);
|
|
646
|
+
// 429 → backoff + retry. All other paths (including non-429 venue
|
|
647
|
+
// errors wrapped in HTTP 200 with code≠0) throw immediately via
|
|
648
|
+
// _handleAsterResponse — no silent fallback (Rule #2).
|
|
649
|
+
if (res.status === 429 && attempt < MAX_ATTEMPTS - 1) {
|
|
650
|
+
const backoffMs = 2000 * Math.pow(2, attempt); // 2s, 4s, 8s
|
|
651
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
return await this._handleAsterResponse(res, method, path);
|
|
656
|
+
}
|
|
657
|
+
catch (e) {
|
|
658
|
+
lastErr = e;
|
|
659
|
+
if (res.status === 429 && attempt < MAX_ATTEMPTS - 1) {
|
|
660
|
+
const backoffMs = 2000 * Math.pow(2, attempt);
|
|
661
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
throw e;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
throw lastErr;
|
|
628
668
|
}
|
|
629
669
|
// ── Private: HTTP response handling ──────────────────────────────────────
|
|
630
670
|
/**
|
package/package.json
CHANGED
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*)"
|
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
7
|
author: hypurrquant
|
|
8
|
-
version: "0.12.
|
|
8
|
+
version: "0.12.12"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# perp-cli Agent Guide
|
|
@@ -41,7 +41,7 @@ Multi-DEX perpetual futures CLI + MCP server — Pacifica (Solana), Hyperliquid
|
|
|
41
41
|
## Install
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
perp --version 2>/dev/null # check if exists (must be >= 0.12.
|
|
44
|
+
perp --version 2>/dev/null # check if exists (must be >= 0.12.12)
|
|
45
45
|
npm install -g perp-cli@latest 2>/dev/null || npx -y perp-cli@latest --json --version
|
|
46
46
|
```
|
|
47
47
|
|