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/config.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ export type Exchange = "pacifica" | "hyperliquid" | "lighter" | "aster";
|
|
|
5
5
|
* Returns null if no key is configured.
|
|
6
6
|
*/
|
|
7
7
|
export declare function tryLoadPrivateKey(exchange: Exchange, pkOverride?: string, walletName?: string): Promise<string | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve an OWS wallet name to a signer-ready marker.
|
|
10
|
+
* Returns the OWS wallet name prefixed with "ows:" so callers can detect OWS mode.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveOwsWallet(walletName: string): string | null;
|
|
13
|
+
/** Check if a key string represents an OWS wallet reference. */
|
|
14
|
+
export declare function isOwsKey(key: string): boolean;
|
|
15
|
+
/** Extract OWS wallet name from an ows: prefixed key string. */
|
|
16
|
+
export declare function getOwsWalletName(key: string): string;
|
|
8
17
|
export declare function loadPrivateKey(exchange: Exchange, pkOverride?: string, walletName?: string): Promise<string>;
|
|
9
18
|
export declare function parseSolanaKeypair(input: string): Keypair;
|
|
10
19
|
export declare function isEvmPrivateKey(input: string): boolean;
|
package/dist/config.js
CHANGED
|
@@ -3,6 +3,8 @@ import bs58 from "bs58";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { resolve } from "path";
|
|
5
5
|
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
const _require = createRequire(import.meta.url);
|
|
6
8
|
/**
|
|
7
9
|
* Try to load a private key without throwing if none is found.
|
|
8
10
|
* Returns null if no key is configured.
|
|
@@ -15,9 +17,40 @@ export async function tryLoadPrivateKey(exchange, pkOverride, walletName) {
|
|
|
15
17
|
return null;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve an OWS wallet name to a signer-ready marker.
|
|
22
|
+
* Returns the OWS wallet name prefixed with "ows:" so callers can detect OWS mode.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveOwsWallet(walletName) {
|
|
25
|
+
try {
|
|
26
|
+
const { getWallet } = _require("@open-wallet-standard/core");
|
|
27
|
+
getWallet(walletName); // throws if not found
|
|
28
|
+
return `ows:${walletName}`;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Check if a key string represents an OWS wallet reference. */
|
|
35
|
+
export function isOwsKey(key) {
|
|
36
|
+
return key.startsWith("ows:");
|
|
37
|
+
}
|
|
38
|
+
/** Extract OWS wallet name from an ows: prefixed key string. */
|
|
39
|
+
export function getOwsWalletName(key) {
|
|
40
|
+
return key.slice(4);
|
|
41
|
+
}
|
|
18
42
|
export async function loadPrivateKey(exchange, pkOverride, walletName) {
|
|
19
|
-
//
|
|
43
|
+
// 0a. OWS wallet (--ows flag sets walletName with "ows:" prefix)
|
|
44
|
+
if (walletName?.startsWith("ows:")) {
|
|
45
|
+
return walletName; // pass through — adapters detect the ows: prefix
|
|
46
|
+
}
|
|
47
|
+
// 0b. Wallet name override (--wallet flag) — check OWS first, then local
|
|
20
48
|
if (walletName) {
|
|
49
|
+
// Try OWS vault
|
|
50
|
+
const owsKey = resolveOwsWallet(walletName);
|
|
51
|
+
if (owsKey)
|
|
52
|
+
return owsKey;
|
|
53
|
+
// Fall back to local wallets
|
|
21
54
|
const { getWalletKeyByName } = await import("./commands/wallet.js");
|
|
22
55
|
const key = getWalletKeyByName(walletName);
|
|
23
56
|
if (!key)
|
|
@@ -27,7 +60,20 @@ export async function loadPrivateKey(exchange, pkOverride, walletName) {
|
|
|
27
60
|
// 1. CLI flag
|
|
28
61
|
if (pkOverride)
|
|
29
62
|
return pkOverride;
|
|
30
|
-
// 2.
|
|
63
|
+
// 2. OWS active wallet (highest priority after explicit flags)
|
|
64
|
+
try {
|
|
65
|
+
const { loadSettings } = await import("./settings.js");
|
|
66
|
+
const settings = loadSettings();
|
|
67
|
+
if (settings.owsActiveWallet) {
|
|
68
|
+
const owsKey = resolveOwsWallet(settings.owsActiveWallet);
|
|
69
|
+
if (owsKey)
|
|
70
|
+
return owsKey;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// settings module not available, skip
|
|
75
|
+
}
|
|
76
|
+
// 3. Exchange-specific env vars
|
|
31
77
|
const envMap = {
|
|
32
78
|
pacifica: ["PACIFICA_PRIVATE_KEY", "pk"],
|
|
33
79
|
hyperliquid: ["HYPERLIQUID_PRIVATE_KEY", "HL_PRIVATE_KEY"],
|
|
@@ -38,10 +84,10 @@ export async function loadPrivateKey(exchange, pkOverride, walletName) {
|
|
|
38
84
|
if (process.env[envVar])
|
|
39
85
|
return process.env[envVar];
|
|
40
86
|
}
|
|
41
|
-
//
|
|
87
|
+
// 4. Generic fallback
|
|
42
88
|
if (process.env.PRIVATE_KEY)
|
|
43
89
|
return process.env.PRIVATE_KEY;
|
|
44
|
-
//
|
|
90
|
+
// 5. Legacy: Active wallet from wallets.json
|
|
45
91
|
try {
|
|
46
92
|
const { getActiveWalletKey } = await import("./commands/wallet.js");
|
|
47
93
|
const walletKey = getActiveWalletKey(exchange);
|
|
@@ -56,12 +102,12 @@ export async function loadPrivateKey(exchange, pkOverride, walletName) {
|
|
|
56
102
|
if (existsSync(keyFile)) {
|
|
57
103
|
return readFileSync(keyFile, "utf-8").trim();
|
|
58
104
|
}
|
|
59
|
-
throw new Error(`No
|
|
60
|
-
` Quick start: ${chalk.cyan("perp
|
|
105
|
+
throw new Error(`No wallet configured for ${exchange}.\n\n` +
|
|
106
|
+
` Quick start: ${chalk.cyan("perp wallet generate")}\n\n` +
|
|
61
107
|
` Or manually:\n` +
|
|
62
|
-
` perp wallet
|
|
63
|
-
` ${
|
|
64
|
-
` --private-key <key>
|
|
108
|
+
` perp wallet import <key> Import key to OWS vault\n` +
|
|
109
|
+
` perp wallet set ${exchange} <key> Legacy: set env var\n` +
|
|
110
|
+
` --private-key <key> Per-command override`);
|
|
65
111
|
}
|
|
66
112
|
export function parseSolanaKeypair(input) {
|
|
67
113
|
// Try base58 first
|
package/dist/errors.d.ts
CHANGED
|
@@ -50,6 +50,62 @@ export declare const ERROR_CODES: {
|
|
|
50
50
|
readonly status: 409;
|
|
51
51
|
readonly retryable: false;
|
|
52
52
|
};
|
|
53
|
+
readonly NO_SIGNER_AVAILABLE: {
|
|
54
|
+
readonly code: "NO_SIGNER_AVAILABLE";
|
|
55
|
+
readonly status: 401;
|
|
56
|
+
readonly retryable: false;
|
|
57
|
+
};
|
|
58
|
+
readonly AGENT_NOT_REGISTERED: {
|
|
59
|
+
readonly code: "AGENT_NOT_REGISTERED";
|
|
60
|
+
readonly status: 401;
|
|
61
|
+
readonly retryable: false;
|
|
62
|
+
};
|
|
63
|
+
readonly AGENT_EXPIRED: {
|
|
64
|
+
readonly code: "AGENT_EXPIRED";
|
|
65
|
+
readonly status: 401;
|
|
66
|
+
readonly retryable: false;
|
|
67
|
+
};
|
|
68
|
+
readonly POLICY_DENIED: {
|
|
69
|
+
readonly code: "POLICY_DENIED";
|
|
70
|
+
readonly status: 403;
|
|
71
|
+
readonly retryable: false;
|
|
72
|
+
};
|
|
73
|
+
readonly KEY_NOT_FOUND: {
|
|
74
|
+
readonly code: "KEY_NOT_FOUND";
|
|
75
|
+
readonly status: 404;
|
|
76
|
+
readonly retryable: false;
|
|
77
|
+
};
|
|
78
|
+
readonly WALLET_LOCKED: {
|
|
79
|
+
readonly code: "WALLET_LOCKED";
|
|
80
|
+
readonly status: 423;
|
|
81
|
+
readonly retryable: false;
|
|
82
|
+
};
|
|
83
|
+
readonly APPROVE_PARTIAL: {
|
|
84
|
+
readonly code: "APPROVE_PARTIAL";
|
|
85
|
+
readonly status: 500;
|
|
86
|
+
readonly retryable: false;
|
|
87
|
+
};
|
|
88
|
+
readonly APPROVE_FAILED: {
|
|
89
|
+
readonly code: "APPROVE_FAILED";
|
|
90
|
+
readonly status: 500;
|
|
91
|
+
readonly retryable: false;
|
|
92
|
+
};
|
|
93
|
+
readonly LOCK_HELD: {
|
|
94
|
+
readonly code: "LOCK_HELD";
|
|
95
|
+
readonly status: 423;
|
|
96
|
+
readonly retryable: true;
|
|
97
|
+
readonly retryAfterMs: 5000;
|
|
98
|
+
};
|
|
99
|
+
readonly PASSPHRASE_REQUIRED: {
|
|
100
|
+
readonly code: "PASSPHRASE_REQUIRED";
|
|
101
|
+
readonly status: 401;
|
|
102
|
+
readonly retryable: false;
|
|
103
|
+
};
|
|
104
|
+
readonly NOT_IMPLEMENTED: {
|
|
105
|
+
readonly code: "NOT_IMPLEMENTED";
|
|
106
|
+
readonly status: 501;
|
|
107
|
+
readonly retryable: false;
|
|
108
|
+
};
|
|
53
109
|
readonly EXCHANGE_UNREACHABLE: {
|
|
54
110
|
readonly code: "EXCHANGE_UNREACHABLE";
|
|
55
111
|
readonly status: 503;
|
|
@@ -96,7 +152,15 @@ export interface StructuredError {
|
|
|
96
152
|
retryAfterMs?: number;
|
|
97
153
|
exchange?: string;
|
|
98
154
|
details?: Record<string, unknown>;
|
|
155
|
+
/** Actionable hint for automated callers (AC-19) */
|
|
156
|
+
remediation?: string;
|
|
99
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Extract a human-readable message from any thrown value.
|
|
160
|
+
* Handles Error instances, plain objects with a `.message` field (string OR object),
|
|
161
|
+
* and circular-safe stringification fallback.
|
|
162
|
+
*/
|
|
163
|
+
export declare function extractErrorMessage(err: unknown): string;
|
|
100
164
|
/**
|
|
101
165
|
* Classify an error from any exchange into a structured error code.
|
|
102
166
|
* Pattern-matches on error messages to detect known error types.
|
package/dist/errors.js
CHANGED
|
@@ -11,6 +11,18 @@ export const ERROR_CODES = {
|
|
|
11
11
|
SIZE_TOO_LARGE: { code: "SIZE_TOO_LARGE", status: 400, retryable: false },
|
|
12
12
|
RISK_VIOLATION: { code: "RISK_VIOLATION", status: 403, retryable: false },
|
|
13
13
|
DUPLICATE_ORDER: { code: "DUPLICATE_ORDER", status: 409, retryable: false },
|
|
14
|
+
// Agent-wallet error codes (OWS / Phase 2a)
|
|
15
|
+
NO_SIGNER_AVAILABLE: { code: "NO_SIGNER_AVAILABLE", status: 401, retryable: false },
|
|
16
|
+
AGENT_NOT_REGISTERED: { code: "AGENT_NOT_REGISTERED", status: 401, retryable: false },
|
|
17
|
+
AGENT_EXPIRED: { code: "AGENT_EXPIRED", status: 401, retryable: false },
|
|
18
|
+
POLICY_DENIED: { code: "POLICY_DENIED", status: 403, retryable: false },
|
|
19
|
+
KEY_NOT_FOUND: { code: "KEY_NOT_FOUND", status: 404, retryable: false },
|
|
20
|
+
WALLET_LOCKED: { code: "WALLET_LOCKED", status: 423, retryable: false },
|
|
21
|
+
APPROVE_PARTIAL: { code: "APPROVE_PARTIAL", status: 500, retryable: false },
|
|
22
|
+
APPROVE_FAILED: { code: "APPROVE_FAILED", status: 500, retryable: false },
|
|
23
|
+
LOCK_HELD: { code: "LOCK_HELD", status: 423, retryable: true, retryAfterMs: 5000 },
|
|
24
|
+
PASSPHRASE_REQUIRED: { code: "PASSPHRASE_REQUIRED", status: 401, retryable: false },
|
|
25
|
+
NOT_IMPLEMENTED: { code: "NOT_IMPLEMENTED", status: 501, retryable: false },
|
|
14
26
|
// 5xx - System / transient errors
|
|
15
27
|
EXCHANGE_UNREACHABLE: { code: "EXCHANGE_UNREACHABLE", status: 503, retryable: true },
|
|
16
28
|
RATE_LIMITED: { code: "RATE_LIMITED", status: 429, retryable: true, retryAfterMs: 1000 },
|
|
@@ -20,12 +32,37 @@ export const ERROR_CODES = {
|
|
|
20
32
|
TIMEOUT: { code: "TIMEOUT", status: 504, retryable: true },
|
|
21
33
|
UNKNOWN: { code: "UNKNOWN", status: 500, retryable: false },
|
|
22
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Extract a human-readable message from any thrown value.
|
|
37
|
+
* Handles Error instances, plain objects with a `.message` field (string OR object),
|
|
38
|
+
* and circular-safe stringification fallback.
|
|
39
|
+
*/
|
|
40
|
+
export function extractErrorMessage(err) {
|
|
41
|
+
if (err instanceof Error)
|
|
42
|
+
return err.message;
|
|
43
|
+
if (err && typeof err === "object") {
|
|
44
|
+
const m = err.message;
|
|
45
|
+
if (typeof m === "string")
|
|
46
|
+
return m;
|
|
47
|
+
if (m !== undefined) {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.stringify(m);
|
|
50
|
+
}
|
|
51
|
+
catch { /* circular */ }
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return JSON.stringify(err);
|
|
55
|
+
}
|
|
56
|
+
catch { /* circular */ }
|
|
57
|
+
}
|
|
58
|
+
return String(err);
|
|
59
|
+
}
|
|
23
60
|
/**
|
|
24
61
|
* Classify an error from any exchange into a structured error code.
|
|
25
62
|
* Pattern-matches on error messages to detect known error types.
|
|
26
63
|
*/
|
|
27
64
|
export function classifyError(err, exchange) {
|
|
28
|
-
const message =
|
|
65
|
+
const message = extractErrorMessage(err);
|
|
29
66
|
const lower = message.toLowerCase();
|
|
30
67
|
// Margin-specific checks first (before generic "insufficient" catch)
|
|
31
68
|
if (lower.includes("margin") && (lower.includes("insufficient") || lower.includes("not enough"))) {
|
|
@@ -43,6 +80,39 @@ export function classifyError(err, exchange) {
|
|
|
43
80
|
if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("etimedout")) {
|
|
44
81
|
return { ...ERROR_CODES.TIMEOUT, message, exchange };
|
|
45
82
|
}
|
|
83
|
+
// OWS / agent-wallet specific matchers — MUST precede generic "sign" / "not found" blocks
|
|
84
|
+
// so "wallet is locked, cannot sign" → WALLET_LOCKED (not SIGNATURE_FAILED)
|
|
85
|
+
// and "key not found" → KEY_NOT_FOUND (not SYMBOL_NOT_FOUND / default)
|
|
86
|
+
if (lower.includes("policy denied") || (lower.includes("policy") && lower.includes("denied"))) {
|
|
87
|
+
return { ...ERROR_CODES.POLICY_DENIED, message, exchange };
|
|
88
|
+
}
|
|
89
|
+
if (lower.includes("wallet is locked") || lower.includes("wallet locked")) {
|
|
90
|
+
return { ...ERROR_CODES.WALLET_LOCKED, message, exchange };
|
|
91
|
+
}
|
|
92
|
+
if (lower.includes("key not found") || lower.includes("api key not found")) {
|
|
93
|
+
return { ...ERROR_CODES.KEY_NOT_FOUND, message, exchange };
|
|
94
|
+
}
|
|
95
|
+
if (lower.includes("no signer") || lower.includes("signer unavailable") || lower.includes("no auth")) {
|
|
96
|
+
return { ...ERROR_CODES.NO_SIGNER_AVAILABLE, message, exchange };
|
|
97
|
+
}
|
|
98
|
+
if (lower.includes("agent not registered")) {
|
|
99
|
+
return { ...ERROR_CODES.AGENT_NOT_REGISTERED, message, exchange };
|
|
100
|
+
}
|
|
101
|
+
if (lower.includes("agent expired") || lower.includes("agent has expired")) {
|
|
102
|
+
return { ...ERROR_CODES.AGENT_EXPIRED, message, exchange };
|
|
103
|
+
}
|
|
104
|
+
if (lower.includes("lock held") || lower.includes("lockfile held")) {
|
|
105
|
+
return { ...ERROR_CODES.LOCK_HELD, message, exchange };
|
|
106
|
+
}
|
|
107
|
+
if (lower.includes("passphrase required") || lower.includes("passphrase missing")) {
|
|
108
|
+
return { ...ERROR_CODES.PASSPHRASE_REQUIRED, message, exchange };
|
|
109
|
+
}
|
|
110
|
+
if (lower.includes("partial") && (lower.includes("approve") || lower.includes("approval"))) {
|
|
111
|
+
return { ...ERROR_CODES.APPROVE_PARTIAL, message, exchange };
|
|
112
|
+
}
|
|
113
|
+
if (lower.includes("approve_failed") || (lower.includes("approve failed") && lower.includes("clean"))) {
|
|
114
|
+
return { ...ERROR_CODES.APPROVE_FAILED, message, exchange };
|
|
115
|
+
}
|
|
46
116
|
if (lower.includes("not found") && (lower.includes("symbol") || lower.includes("market") || lower.includes("asset"))) {
|
|
47
117
|
return { ...ERROR_CODES.SYMBOL_NOT_FOUND, message, exchange };
|
|
48
118
|
}
|
|
@@ -79,6 +149,16 @@ export class PerpError extends Error {
|
|
|
79
149
|
constructor(code, message, details) {
|
|
80
150
|
super(message);
|
|
81
151
|
this.name = "PerpError";
|
|
82
|
-
|
|
152
|
+
// Lift `remediation` from details to top-level StructuredError field (AC-19)
|
|
153
|
+
const remediation = details?.remediation;
|
|
154
|
+
const strippedDetails = details ? { ...details } : undefined;
|
|
155
|
+
if (strippedDetails)
|
|
156
|
+
delete strippedDetails.remediation;
|
|
157
|
+
this.structured = {
|
|
158
|
+
...ERROR_CODES[code],
|
|
159
|
+
message,
|
|
160
|
+
...(remediation !== undefined ? { remediation } : {}),
|
|
161
|
+
...(strippedDetails && Object.keys(strippedDetails).length > 0 ? { details: strippedDetails } : {}),
|
|
162
|
+
};
|
|
83
163
|
}
|
|
84
164
|
}
|
package/dist/event-stream.js
CHANGED
|
@@ -48,9 +48,20 @@ export async function startEventStream(adapter, opts) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
// Closed positions
|
|
51
|
+
// SSOT rule #2: emit realizedPnl explicitly so position-history can
|
|
52
|
+
// require the field. We can't reach back into the exchange to fetch the
|
|
53
|
+
// true realized P&L from a polling stream — but the last unrealized
|
|
54
|
+
// snapshot before the position disappears is the closest proxy and
|
|
55
|
+
// becomes realized at close. Surfacing it under realizedPnl makes the
|
|
56
|
+
// contract explicit.
|
|
51
57
|
for (const [sym, prev] of prevPositions) {
|
|
52
58
|
if (!currentPositions.has(sym)) {
|
|
53
|
-
emit({
|
|
59
|
+
emit({
|
|
60
|
+
type: "position_closed",
|
|
61
|
+
exchange: adapter.name,
|
|
62
|
+
timestamp: ts,
|
|
63
|
+
data: { ...prev, realizedPnl: prev.unrealizedPnl },
|
|
64
|
+
});
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
67
|
// ── Liquidation warnings ──
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aster V3 EIP-712 typed-data builders.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no side effects, no fs/fetch/env reads.
|
|
5
|
+
* All field ordering and serialization matches the HypurrQuant_FE mainnet
|
|
6
|
+
* production reference: AsterPerpAdapter.ts:456-700.
|
|
7
|
+
*
|
|
8
|
+
* Key conventions:
|
|
9
|
+
* - Domain A (chainId=56) — master-signed ops: approveAgent, delAgent, approveBuilder
|
|
10
|
+
* - Domain B (chainId=1666) — agent-signed per-order ops
|
|
11
|
+
* - uint256 fields serialized as decimal strings (never BigInt) to survive JSON.stringify
|
|
12
|
+
* - EIP-712 message keys are PascalCase; HTTP query-string keys are camelCase
|
|
13
|
+
*/
|
|
14
|
+
/** Domain A: used for master-signed ops (ApproveAgent, DelAgent, ApproveBuilder). */
|
|
15
|
+
export declare const ASTER_DOMAIN_A: {
|
|
16
|
+
readonly name: "AsterSignTransaction";
|
|
17
|
+
readonly version: "1";
|
|
18
|
+
readonly chainId: 56;
|
|
19
|
+
readonly verifyingContract: "0x0000000000000000000000000000000000000000";
|
|
20
|
+
};
|
|
21
|
+
/** Domain B: used for agent-signed per-request ops (order, cancel, leverage). */
|
|
22
|
+
export declare const ASTER_DOMAIN_B: {
|
|
23
|
+
readonly chainId: 1666;
|
|
24
|
+
readonly name: "AsterSignTransaction";
|
|
25
|
+
readonly version: "1";
|
|
26
|
+
readonly verifyingContract: "0x0000000000000000000000000000000000000000";
|
|
27
|
+
};
|
|
28
|
+
/** EIP-712 types for Domain B order signing — single msg string (URL-encoded query string). */
|
|
29
|
+
export declare const ORDER_TYPES: {
|
|
30
|
+
readonly Message: readonly [{
|
|
31
|
+
readonly name: "msg";
|
|
32
|
+
readonly type: "string";
|
|
33
|
+
}];
|
|
34
|
+
};
|
|
35
|
+
type Eip712Field = {
|
|
36
|
+
name: string;
|
|
37
|
+
type: string;
|
|
38
|
+
};
|
|
39
|
+
type AsterChain = "Mainnet" | "Testnet";
|
|
40
|
+
export interface ApproveAgentParams {
|
|
41
|
+
user: `0x${string}`;
|
|
42
|
+
agentAddress: `0x${string}`;
|
|
43
|
+
agentName: string;
|
|
44
|
+
/** Unix milliseconds timestamp when the agent expires. Serialized as decimal string. */
|
|
45
|
+
expiredMs: number | string;
|
|
46
|
+
canPerpTrade: boolean;
|
|
47
|
+
canSpotTrade: boolean;
|
|
48
|
+
canWithdraw: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Optional IP whitelist. Include (even as "") when defined and non-null.
|
|
51
|
+
* Undefined/null = field omitted from EIP-712 struct.
|
|
52
|
+
*/
|
|
53
|
+
ipWhitelist?: string;
|
|
54
|
+
/** Optional builder address. When present, Builder + MaxFeeRate are included. */
|
|
55
|
+
builder?: string;
|
|
56
|
+
/** Required when builder is set. */
|
|
57
|
+
maxFeeRate?: string;
|
|
58
|
+
/** Optional builder name. Independently gated by its own undefined-check. */
|
|
59
|
+
builderName?: string;
|
|
60
|
+
/** Microsecond-precision nonce (Date.now() * 1000 + counter). Serialized as decimal string. */
|
|
61
|
+
nonceMicros: number | string;
|
|
62
|
+
asterChain?: AsterChain;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build the EIP-712 typed-data payload for `POST /fapi/v3/approveAgent`.
|
|
66
|
+
*
|
|
67
|
+
* Field order in types array and message object matches HypurrQuant_FE
|
|
68
|
+
* AsterPerpAdapter.ts:481-533 exactly:
|
|
69
|
+
* AgentName, AgentAddress, [IpWhitelist?,] Expired, CanSpotTrade, CanPerpTrade,
|
|
70
|
+
* CanWithdraw, [Builder?, MaxFeeRate?, BuilderName?,] AsterChain, User, Nonce
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildApproveAgentTypedData(params: ApproveAgentParams): {
|
|
73
|
+
domain: typeof ASTER_DOMAIN_A;
|
|
74
|
+
types: {
|
|
75
|
+
ApproveAgent: Eip712Field[];
|
|
76
|
+
};
|
|
77
|
+
primaryType: "ApproveAgent";
|
|
78
|
+
message: Record<string, unknown>;
|
|
79
|
+
};
|
|
80
|
+
export interface DelAgentParams {
|
|
81
|
+
user: `0x${string}`;
|
|
82
|
+
agentAddress: `0x${string}`;
|
|
83
|
+
nonceMicros: number | string;
|
|
84
|
+
asterChain?: AsterChain;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the EIP-712 typed-data payload for `DELETE /fapi/v3/agent`.
|
|
88
|
+
*
|
|
89
|
+
* TODO: confirm DelAgent struct vs Aster docs at Step 0b spike.
|
|
90
|
+
* HypurrQuant_FE does not implement a revoke/delAgent method — this is a
|
|
91
|
+
* best-effort field set mirroring the ApproveAgent terminal fields (AgentAddress,
|
|
92
|
+
* AsterChain, User, Nonce) without the permission booleans.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildDelAgentTypedData(params: DelAgentParams): {
|
|
95
|
+
domain: typeof ASTER_DOMAIN_A;
|
|
96
|
+
types: {
|
|
97
|
+
DelAgent: Eip712Field[];
|
|
98
|
+
};
|
|
99
|
+
primaryType: "DelAgent";
|
|
100
|
+
message: Record<string, unknown>;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Encode order parameters as a URL-encoded query string.
|
|
104
|
+
*
|
|
105
|
+
* Deterministic key order = insertion order of `orderParams` (JS object property
|
|
106
|
+
* iteration order). Caller must pre-populate `user` and `nonce` (microseconds)
|
|
107
|
+
* in the dict; this function does NOT inject defaults.
|
|
108
|
+
*
|
|
109
|
+
* Uses `URLSearchParams` for encoding consistency with Python
|
|
110
|
+
* `urllib.parse.urlencode`.
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildOrderQueryStringMsg(orderParams: Record<string, string | number | boolean>): string;
|
|
113
|
+
/**
|
|
114
|
+
* Build the EIP-712 typed-data payload for agent-signed order requests.
|
|
115
|
+
*
|
|
116
|
+
* Domain B (chainId=1666), primaryType="Message", single `msg` field containing
|
|
117
|
+
* the URL-encoded query string produced by buildOrderQueryStringMsg.
|
|
118
|
+
*/
|
|
119
|
+
export declare function buildOrderTypedData(orderParams: Record<string, string | number | boolean>): {
|
|
120
|
+
domain: typeof ASTER_DOMAIN_B;
|
|
121
|
+
types: typeof ORDER_TYPES;
|
|
122
|
+
primaryType: "Message";
|
|
123
|
+
message: {
|
|
124
|
+
msg: string;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
export interface ApproveBuilderParams {
|
|
128
|
+
builder: string;
|
|
129
|
+
maxFeeRate: string;
|
|
130
|
+
builderName?: string;
|
|
131
|
+
user: `0x${string}`;
|
|
132
|
+
nonceMicros: number | string;
|
|
133
|
+
asterChain?: AsterChain;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build the EIP-712 typed-data payload for `POST /fapi/v3/approveBuilder`.
|
|
137
|
+
*
|
|
138
|
+
* Domain A (chainId=56), primaryType="ApproveBuilder".
|
|
139
|
+
* Default-path field order from HypurrQuant_FE AsterPerpAdapter.ts:630-651:
|
|
140
|
+
* Builder, MaxFeeRate, AsterChain, User, Nonce
|
|
141
|
+
* `BuilderName` is a forward-compatible extension (HQ_FE omits it); included
|
|
142
|
+
* only when explicitly provided so the default path stays byte-equivalent to
|
|
143
|
+
* the production reference.
|
|
144
|
+
*
|
|
145
|
+
* Must be called BEFORE approveAgent so Aster fee attribution is set up
|
|
146
|
+
* before the first order.
|
|
147
|
+
*/
|
|
148
|
+
export declare function buildApproveBuilderTypedData(params: ApproveBuilderParams): {
|
|
149
|
+
domain: typeof ASTER_DOMAIN_A;
|
|
150
|
+
types: {
|
|
151
|
+
ApproveBuilder: Eip712Field[];
|
|
152
|
+
};
|
|
153
|
+
primaryType: "ApproveBuilder";
|
|
154
|
+
message: Record<string, unknown>;
|
|
155
|
+
};
|
|
156
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aster V3 EIP-712 typed-data builders.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions — no side effects, no fs/fetch/env reads.
|
|
5
|
+
* All field ordering and serialization matches the HypurrQuant_FE mainnet
|
|
6
|
+
* production reference: AsterPerpAdapter.ts:456-700.
|
|
7
|
+
*
|
|
8
|
+
* Key conventions:
|
|
9
|
+
* - Domain A (chainId=56) — master-signed ops: approveAgent, delAgent, approveBuilder
|
|
10
|
+
* - Domain B (chainId=1666) — agent-signed per-order ops
|
|
11
|
+
* - uint256 fields serialized as decimal strings (never BigInt) to survive JSON.stringify
|
|
12
|
+
* - EIP-712 message keys are PascalCase; HTTP query-string keys are camelCase
|
|
13
|
+
*/
|
|
14
|
+
// ── Domain constants ──────────────────────────────────────────────────────────
|
|
15
|
+
/** Domain A: used for master-signed ops (ApproveAgent, DelAgent, ApproveBuilder). */
|
|
16
|
+
export const ASTER_DOMAIN_A = {
|
|
17
|
+
name: "AsterSignTransaction",
|
|
18
|
+
version: "1",
|
|
19
|
+
chainId: 56,
|
|
20
|
+
verifyingContract: "0x0000000000000000000000000000000000000000",
|
|
21
|
+
};
|
|
22
|
+
/** Domain B: used for agent-signed per-request ops (order, cancel, leverage). */
|
|
23
|
+
export const ASTER_DOMAIN_B = {
|
|
24
|
+
...ASTER_DOMAIN_A,
|
|
25
|
+
chainId: 1666,
|
|
26
|
+
};
|
|
27
|
+
/** EIP-712 types for Domain B order signing — single msg string (URL-encoded query string). */
|
|
28
|
+
export const ORDER_TYPES = {
|
|
29
|
+
Message: [{ name: "msg", type: "string" }],
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Build the EIP-712 typed-data payload for `POST /fapi/v3/approveAgent`.
|
|
33
|
+
*
|
|
34
|
+
* Field order in types array and message object matches HypurrQuant_FE
|
|
35
|
+
* AsterPerpAdapter.ts:481-533 exactly:
|
|
36
|
+
* AgentName, AgentAddress, [IpWhitelist?,] Expired, CanSpotTrade, CanPerpTrade,
|
|
37
|
+
* CanWithdraw, [Builder?, MaxFeeRate?, BuilderName?,] AsterChain, User, Nonce
|
|
38
|
+
*/
|
|
39
|
+
export function buildApproveAgentTypedData(params) {
|
|
40
|
+
const ipWhitelistIncluded = params.ipWhitelist !== undefined && params.ipWhitelist !== null;
|
|
41
|
+
const builderIncluded = params.builder !== undefined && params.builder !== null;
|
|
42
|
+
// Build types array — insertion order must match message object order.
|
|
43
|
+
const agentTypes = [
|
|
44
|
+
{ name: "AgentName", type: "string" },
|
|
45
|
+
{ name: "AgentAddress", type: "string" },
|
|
46
|
+
];
|
|
47
|
+
if (ipWhitelistIncluded) {
|
|
48
|
+
agentTypes.push({ name: "IpWhitelist", type: "string" });
|
|
49
|
+
}
|
|
50
|
+
agentTypes.push({ name: "Expired", type: "uint256" }, { name: "CanSpotTrade", type: "bool" }, { name: "CanPerpTrade", type: "bool" }, { name: "CanWithdraw", type: "bool" });
|
|
51
|
+
if (builderIncluded) {
|
|
52
|
+
agentTypes.push({ name: "Builder", type: "string" });
|
|
53
|
+
agentTypes.push({ name: "MaxFeeRate", type: "string" });
|
|
54
|
+
if (params.builderName !== undefined) {
|
|
55
|
+
agentTypes.push({ name: "BuilderName", type: "string" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
agentTypes.push({ name: "AsterChain", type: "string" }, { name: "User", type: "string" }, { name: "Nonce", type: "uint256" });
|
|
59
|
+
// Build message object — same insertion order as types array.
|
|
60
|
+
const message = {
|
|
61
|
+
AgentName: params.agentName,
|
|
62
|
+
AgentAddress: params.agentAddress,
|
|
63
|
+
};
|
|
64
|
+
if (ipWhitelistIncluded) {
|
|
65
|
+
message["IpWhitelist"] = params.ipWhitelist;
|
|
66
|
+
}
|
|
67
|
+
// uint256 → decimal string (BigInt throws in JSON.stringify inside providers)
|
|
68
|
+
message["Expired"] = String(params.expiredMs);
|
|
69
|
+
message["CanSpotTrade"] = params.canSpotTrade;
|
|
70
|
+
message["CanPerpTrade"] = params.canPerpTrade;
|
|
71
|
+
message["CanWithdraw"] = params.canWithdraw;
|
|
72
|
+
if (builderIncluded) {
|
|
73
|
+
message["Builder"] = params.builder;
|
|
74
|
+
message["MaxFeeRate"] = params.maxFeeRate;
|
|
75
|
+
if (params.builderName !== undefined) {
|
|
76
|
+
message["BuilderName"] = params.builderName;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
message["AsterChain"] = params.asterChain ?? "Mainnet";
|
|
80
|
+
message["User"] = params.user;
|
|
81
|
+
message["Nonce"] = String(params.nonceMicros);
|
|
82
|
+
return {
|
|
83
|
+
domain: ASTER_DOMAIN_A,
|
|
84
|
+
types: { ApproveAgent: agentTypes },
|
|
85
|
+
primaryType: "ApproveAgent",
|
|
86
|
+
message,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the EIP-712 typed-data payload for `DELETE /fapi/v3/agent`.
|
|
91
|
+
*
|
|
92
|
+
* TODO: confirm DelAgent struct vs Aster docs at Step 0b spike.
|
|
93
|
+
* HypurrQuant_FE does not implement a revoke/delAgent method — this is a
|
|
94
|
+
* best-effort field set mirroring the ApproveAgent terminal fields (AgentAddress,
|
|
95
|
+
* AsterChain, User, Nonce) without the permission booleans.
|
|
96
|
+
*/
|
|
97
|
+
export function buildDelAgentTypedData(params) {
|
|
98
|
+
const delTypes = [
|
|
99
|
+
{ name: "AgentAddress", type: "string" },
|
|
100
|
+
{ name: "AsterChain", type: "string" },
|
|
101
|
+
{ name: "User", type: "string" },
|
|
102
|
+
{ name: "Nonce", type: "uint256" },
|
|
103
|
+
];
|
|
104
|
+
const message = {
|
|
105
|
+
AgentAddress: params.agentAddress,
|
|
106
|
+
AsterChain: params.asterChain ?? "Mainnet",
|
|
107
|
+
User: params.user,
|
|
108
|
+
Nonce: String(params.nonceMicros),
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
domain: ASTER_DOMAIN_A,
|
|
112
|
+
types: { DelAgent: delTypes },
|
|
113
|
+
primaryType: "DelAgent",
|
|
114
|
+
message,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// ── buildOrderQueryStringMsg ──────────────────────────────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* Encode order parameters as a URL-encoded query string.
|
|
120
|
+
*
|
|
121
|
+
* Deterministic key order = insertion order of `orderParams` (JS object property
|
|
122
|
+
* iteration order). Caller must pre-populate `user` and `nonce` (microseconds)
|
|
123
|
+
* in the dict; this function does NOT inject defaults.
|
|
124
|
+
*
|
|
125
|
+
* Uses `URLSearchParams` for encoding consistency with Python
|
|
126
|
+
* `urllib.parse.urlencode`.
|
|
127
|
+
*/
|
|
128
|
+
export function buildOrderQueryStringMsg(orderParams) {
|
|
129
|
+
const sp = new URLSearchParams();
|
|
130
|
+
for (const [key, value] of Object.entries(orderParams)) {
|
|
131
|
+
sp.append(key, String(value));
|
|
132
|
+
}
|
|
133
|
+
return sp.toString();
|
|
134
|
+
}
|
|
135
|
+
// ── buildOrderTypedData ───────────────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Build the EIP-712 typed-data payload for agent-signed order requests.
|
|
138
|
+
*
|
|
139
|
+
* Domain B (chainId=1666), primaryType="Message", single `msg` field containing
|
|
140
|
+
* the URL-encoded query string produced by buildOrderQueryStringMsg.
|
|
141
|
+
*/
|
|
142
|
+
export function buildOrderTypedData(orderParams) {
|
|
143
|
+
const msg = buildOrderQueryStringMsg(orderParams);
|
|
144
|
+
return {
|
|
145
|
+
domain: ASTER_DOMAIN_B,
|
|
146
|
+
types: ORDER_TYPES,
|
|
147
|
+
primaryType: "Message",
|
|
148
|
+
message: { msg },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build the EIP-712 typed-data payload for `POST /fapi/v3/approveBuilder`.
|
|
153
|
+
*
|
|
154
|
+
* Domain A (chainId=56), primaryType="ApproveBuilder".
|
|
155
|
+
* Default-path field order from HypurrQuant_FE AsterPerpAdapter.ts:630-651:
|
|
156
|
+
* Builder, MaxFeeRate, AsterChain, User, Nonce
|
|
157
|
+
* `BuilderName` is a forward-compatible extension (HQ_FE omits it); included
|
|
158
|
+
* only when explicitly provided so the default path stays byte-equivalent to
|
|
159
|
+
* the production reference.
|
|
160
|
+
*
|
|
161
|
+
* Must be called BEFORE approveAgent so Aster fee attribution is set up
|
|
162
|
+
* before the first order.
|
|
163
|
+
*/
|
|
164
|
+
export function buildApproveBuilderTypedData(params) {
|
|
165
|
+
const builderTypes = [
|
|
166
|
+
{ name: "Builder", type: "string" },
|
|
167
|
+
{ name: "MaxFeeRate", type: "string" },
|
|
168
|
+
];
|
|
169
|
+
if (params.builderName !== undefined) {
|
|
170
|
+
builderTypes.push({ name: "BuilderName", type: "string" });
|
|
171
|
+
}
|
|
172
|
+
builderTypes.push({ name: "AsterChain", type: "string" }, { name: "User", type: "string" }, { name: "Nonce", type: "uint256" });
|
|
173
|
+
const message = {
|
|
174
|
+
Builder: params.builder,
|
|
175
|
+
MaxFeeRate: params.maxFeeRate,
|
|
176
|
+
};
|
|
177
|
+
if (params.builderName !== undefined) {
|
|
178
|
+
message["BuilderName"] = params.builderName;
|
|
179
|
+
}
|
|
180
|
+
message["AsterChain"] = params.asterChain ?? "Mainnet";
|
|
181
|
+
message["User"] = params.user;
|
|
182
|
+
message["Nonce"] = String(params.nonceMicros);
|
|
183
|
+
return {
|
|
184
|
+
domain: ASTER_DOMAIN_A,
|
|
185
|
+
types: { ApproveBuilder: builderTypes },
|
|
186
|
+
primaryType: "ApproveBuilder",
|
|
187
|
+
message,
|
|
188
|
+
};
|
|
189
|
+
}
|