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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { loadOws } from "./ows-loader.js";
|
|
2
|
+
/**
|
|
3
|
+
* EVM signer backed by Open Wallet Standard.
|
|
4
|
+
* Keys never leave the OWS encrypted vault.
|
|
5
|
+
*/
|
|
6
|
+
export class OwsEvmSigner {
|
|
7
|
+
_address;
|
|
8
|
+
_walletName;
|
|
9
|
+
_passphrase;
|
|
10
|
+
constructor(walletName, address, passphrase) {
|
|
11
|
+
this._walletName = walletName;
|
|
12
|
+
this._address = address;
|
|
13
|
+
this._passphrase = passphrase;
|
|
14
|
+
}
|
|
15
|
+
static create(walletName, passphrase = "") {
|
|
16
|
+
const ows = loadOws();
|
|
17
|
+
const wallet = ows.getWallet(walletName);
|
|
18
|
+
const evmAccount = wallet.accounts.find((a) => a.chainId.startsWith("eip155:"));
|
|
19
|
+
if (!evmAccount) {
|
|
20
|
+
throw new Error(`OWS wallet "${walletName}" has no EVM account`);
|
|
21
|
+
}
|
|
22
|
+
return new OwsEvmSigner(walletName, evmAccount.address, passphrase);
|
|
23
|
+
}
|
|
24
|
+
getAddress() {
|
|
25
|
+
return this._address;
|
|
26
|
+
}
|
|
27
|
+
async signTypedData(domain, types, value) {
|
|
28
|
+
const ows = loadOws();
|
|
29
|
+
// Build EIP-712 structure expected by OWS
|
|
30
|
+
const typedData = JSON.stringify({
|
|
31
|
+
types: {
|
|
32
|
+
EIP712Domain: Object.keys(domain).map((key) => ({
|
|
33
|
+
name: key,
|
|
34
|
+
type: inferEip712DomainType(key),
|
|
35
|
+
})),
|
|
36
|
+
...types,
|
|
37
|
+
},
|
|
38
|
+
primaryType: Object.keys(types)[0],
|
|
39
|
+
domain,
|
|
40
|
+
message: value,
|
|
41
|
+
});
|
|
42
|
+
const result = ows.signTypedData(this._walletName, "ethereum", typedData, this._passphrase);
|
|
43
|
+
return canonicalizeOwsSignature(result);
|
|
44
|
+
}
|
|
45
|
+
async signMessage(message) {
|
|
46
|
+
const ows = loadOws();
|
|
47
|
+
const msgStr = typeof message === "string"
|
|
48
|
+
? message
|
|
49
|
+
: Buffer.from(message).toString("hex");
|
|
50
|
+
const encoding = typeof message === "string" ? "utf8" : "hex";
|
|
51
|
+
const result = ows.signMessage(this._walletName, "ethereum", msgStr, this._passphrase, encoding);
|
|
52
|
+
return canonicalizeOwsSignature(result);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Normalize OWS signature output to canonical 65-byte hex (0x-prefixed).
|
|
57
|
+
*
|
|
58
|
+
* OWS may return the signature in two shapes depending on version:
|
|
59
|
+
* (A) 65-byte hex with v already embedded — use as-is
|
|
60
|
+
* (B) 64-byte hex (r+s only) + separate recoveryId — append v
|
|
61
|
+
*
|
|
62
|
+
* For shape B, recoveryId may be either canonical (27/28) or raw (0/1).
|
|
63
|
+
* If raw, add 27 to canonicalize.
|
|
64
|
+
*/
|
|
65
|
+
export function canonicalizeOwsSignature(result) {
|
|
66
|
+
const sigHex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
|
|
67
|
+
if (sigHex.length === 130) {
|
|
68
|
+
// Already 65-byte sig with embedded v — trust OWS
|
|
69
|
+
return `0x${sigHex}`;
|
|
70
|
+
}
|
|
71
|
+
if (sigHex.length !== 128) {
|
|
72
|
+
throw new Error(`Unexpected OWS signature length: ${sigHex.length} hex chars (expected 128 or 130)`);
|
|
73
|
+
}
|
|
74
|
+
// 64-byte r+s — append v from recoveryId
|
|
75
|
+
const rid = result.recoveryId ?? 0;
|
|
76
|
+
const v = rid >= 27 ? rid : rid + 27; // accept canonical or raw recoveryId
|
|
77
|
+
return `0x${sigHex}${v.toString(16).padStart(2, "0")}`;
|
|
78
|
+
}
|
|
79
|
+
function inferEip712DomainType(key) {
|
|
80
|
+
switch (key) {
|
|
81
|
+
case "chainId": return "uint256";
|
|
82
|
+
case "verifyingContract": return "address";
|
|
83
|
+
case "salt": return "bytes32";
|
|
84
|
+
default: return "string";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SolanaSigner } from "./interface.js";
|
|
2
|
+
/**
|
|
3
|
+
* Solana signer backed by Open Wallet Standard.
|
|
4
|
+
* Keys never leave the OWS encrypted vault.
|
|
5
|
+
*/
|
|
6
|
+
export declare class OwsSolanaSigner implements SolanaSigner {
|
|
7
|
+
private _address;
|
|
8
|
+
private _walletName;
|
|
9
|
+
private _passphrase;
|
|
10
|
+
private constructor();
|
|
11
|
+
static create(walletName: string, passphrase?: string): OwsSolanaSigner;
|
|
12
|
+
getPublicKeyBase58(): string;
|
|
13
|
+
signMessage(message: Uint8Array): Promise<Uint8Array>;
|
|
14
|
+
signTransaction(tx: {
|
|
15
|
+
sign(keypair: unknown): void;
|
|
16
|
+
}): void;
|
|
17
|
+
partialSignTransaction(tx: {
|
|
18
|
+
partialSign(keypair: unknown): void;
|
|
19
|
+
}): void;
|
|
20
|
+
/**
|
|
21
|
+
* Sign raw Solana transaction bytes via OWS.
|
|
22
|
+
* The caller is responsible for serializing/deserializing the transaction.
|
|
23
|
+
*/
|
|
24
|
+
signTransactionBytes(txBytes: Uint8Array): Uint8Array;
|
|
25
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { loadOws } from "./ows-loader.js";
|
|
2
|
+
/**
|
|
3
|
+
* Solana signer backed by Open Wallet Standard.
|
|
4
|
+
* Keys never leave the OWS encrypted vault.
|
|
5
|
+
*/
|
|
6
|
+
export class OwsSolanaSigner {
|
|
7
|
+
_address;
|
|
8
|
+
_walletName;
|
|
9
|
+
_passphrase;
|
|
10
|
+
constructor(walletName, address, passphrase) {
|
|
11
|
+
this._walletName = walletName;
|
|
12
|
+
this._address = address;
|
|
13
|
+
this._passphrase = passphrase;
|
|
14
|
+
}
|
|
15
|
+
static create(walletName, passphrase = "") {
|
|
16
|
+
const ows = loadOws();
|
|
17
|
+
const wallet = ows.getWallet(walletName);
|
|
18
|
+
const solAccount = wallet.accounts.find((a) => a.chainId.startsWith("solana:"));
|
|
19
|
+
if (!solAccount) {
|
|
20
|
+
throw new Error(`OWS wallet "${walletName}" has no Solana account`);
|
|
21
|
+
}
|
|
22
|
+
return new OwsSolanaSigner(walletName, solAccount.address, passphrase);
|
|
23
|
+
}
|
|
24
|
+
getPublicKeyBase58() {
|
|
25
|
+
return this._address;
|
|
26
|
+
}
|
|
27
|
+
async signMessage(message) {
|
|
28
|
+
const ows = loadOws();
|
|
29
|
+
const msgHex = Buffer.from(message).toString("hex");
|
|
30
|
+
const result = ows.signMessage(this._walletName, "solana", msgHex, this._passphrase, "hex");
|
|
31
|
+
// Convert hex signature to Uint8Array
|
|
32
|
+
const sigHex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
|
|
33
|
+
return Uint8Array.from(Buffer.from(sigHex, "hex"));
|
|
34
|
+
}
|
|
35
|
+
signTransaction(tx) {
|
|
36
|
+
// OWS doesn't expose raw keypairs. For Solana transactions that require
|
|
37
|
+
// a keypair, we serialize -> sign via OWS -> inject signature back.
|
|
38
|
+
throw new Error("OWS signer does not support in-place tx.sign(). " +
|
|
39
|
+
"Use signTransactionBytes() or the deposit relayer instead.");
|
|
40
|
+
}
|
|
41
|
+
partialSignTransaction(tx) {
|
|
42
|
+
throw new Error("OWS signer does not support in-place tx.partialSign(). " +
|
|
43
|
+
"Use signTransactionBytes() or the deposit relayer instead.");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sign raw Solana transaction bytes via OWS.
|
|
47
|
+
* The caller is responsible for serializing/deserializing the transaction.
|
|
48
|
+
*/
|
|
49
|
+
signTransactionBytes(txBytes) {
|
|
50
|
+
const ows = loadOws();
|
|
51
|
+
const txHex = Buffer.from(txBytes).toString("hex");
|
|
52
|
+
const result = ows.signTransaction(this._walletName, "solana", txHex, this._passphrase);
|
|
53
|
+
const sigHex = result.signature.startsWith("0x") ? result.signature.slice(2) : result.signature;
|
|
54
|
+
return Uint8Array.from(Buffer.from(sigHex, "hex"));
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/smart-order.d.ts
CHANGED
|
@@ -16,13 +16,19 @@
|
|
|
16
16
|
* - Saves 5-50+ bps vs raw market orders on thin books
|
|
17
17
|
* - Predictable execution price
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* SSOT rule #2: silent fallback is forbidden. The opt-in `fallback: true` flag
|
|
20
|
+
* is preserved for callers that explicitly want best-effort market substitution
|
|
21
|
+
* after IOC reject, but the DEFAULT is `false` — failure must surface as a
|
|
22
|
+
* thrown error, never a silent market sweep.
|
|
20
23
|
*/
|
|
21
24
|
import type { ExchangeAdapter } from "./exchanges/index.js";
|
|
22
25
|
export interface SmartOrderOpts {
|
|
23
26
|
/** Extra ticks of tolerance beyond best price. Default: 1 */
|
|
24
27
|
tickTolerance?: number;
|
|
25
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Opt-in: fall back to market order if IOC limit fails.
|
|
30
|
+
* SSOT rule #2 default: `false` — failure throws unless caller explicitly opts in.
|
|
31
|
+
*/
|
|
26
32
|
fallback?: boolean;
|
|
27
33
|
/** Reduce only flag for closing positions. Default: false */
|
|
28
34
|
reduceOnly?: boolean;
|
package/dist/smart-order.js
CHANGED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* - Saves 5-50+ bps vs raw market orders on thin books
|
|
17
17
|
* - Predictable execution price
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* SSOT rule #2: silent fallback is forbidden. The opt-in `fallback: true` flag
|
|
20
|
+
* is preserved for callers that explicitly want best-effort market substitution
|
|
21
|
+
* after IOC reject, but the DEFAULT is `false` — failure must surface as a
|
|
22
|
+
* thrown error, never a silent market sweep.
|
|
20
23
|
*/
|
|
21
24
|
/**
|
|
22
25
|
* Infer tick size from orderbook prices.
|
|
@@ -77,12 +80,38 @@ function hasOrderError(result) {
|
|
|
77
80
|
return true;
|
|
78
81
|
return false;
|
|
79
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Best-effort extraction of the embedded reject reason for the thrown error
|
|
85
|
+
* message. Falls back to JSON.stringify so the caller always gets actionable
|
|
86
|
+
* context.
|
|
87
|
+
*/
|
|
88
|
+
function describeOrderError(result) {
|
|
89
|
+
if (!result || typeof result !== "object")
|
|
90
|
+
return String(result);
|
|
91
|
+
const r = result;
|
|
92
|
+
const resp = r.response;
|
|
93
|
+
const data = resp?.data;
|
|
94
|
+
const statuses = data?.statuses;
|
|
95
|
+
if (Array.isArray(statuses) && statuses.length > 0 && statuses[0].error) {
|
|
96
|
+
return String(statuses[0].error);
|
|
97
|
+
}
|
|
98
|
+
if (typeof r.error === "string")
|
|
99
|
+
return r.error;
|
|
100
|
+
if (typeof r.message === "string")
|
|
101
|
+
return r.message;
|
|
102
|
+
try {
|
|
103
|
+
return JSON.stringify(result);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return String(result);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
80
109
|
/**
|
|
81
110
|
* Execute a smart order at best bid/ask + 1 tick.
|
|
82
111
|
* Drop-in replacement for `adapter.marketOrder()`.
|
|
83
112
|
*/
|
|
84
113
|
export async function smartOrder(adapter, symbol, side, size, opts = {}) {
|
|
85
|
-
const { tickTolerance = 1, fallback =
|
|
114
|
+
const { tickTolerance = 1, fallback = false, reduceOnly = false } = opts;
|
|
86
115
|
// 1. Fetch orderbook
|
|
87
116
|
const book = await adapter.getOrderbook(symbol);
|
|
88
117
|
// 2. Validate book has liquidity on the relevant side
|
|
@@ -111,15 +140,21 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
|
|
|
111
140
|
});
|
|
112
141
|
// Check for exchange-level rejection inside a "success" response
|
|
113
142
|
// (e.g., Hyperliquid returns { status: "ok", response: { data: { statuses: [{ error: "..." }] } } })
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
// SSOT rule #2: when fallback is opt-out (default), an embedded reject
|
|
144
|
+
// must throw — otherwise the caller would silently receive a "limit_ioc"
|
|
145
|
+
// result for a rejected order and treat the failure as success.
|
|
146
|
+
if (hasOrderError(result)) {
|
|
147
|
+
if (fallback) {
|
|
148
|
+
const fallbackResult = await adapter.marketOrder(symbol, side, size);
|
|
149
|
+
return {
|
|
150
|
+
result: fallbackResult,
|
|
151
|
+
method: "market_fallback",
|
|
152
|
+
price: formattedPrice,
|
|
153
|
+
bestBookPrice: bestEntry[0],
|
|
154
|
+
tickSize: tick.toFixed(decimals),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`smartOrder: exchange rejected IOC limit (${describeOrderError(result)})`);
|
|
123
158
|
}
|
|
124
159
|
return {
|
|
125
160
|
result,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { symbolMatch } from "../utils.js";
|
|
1
|
+
import { symbolMatch, logSettledRejections } from "../utils.js";
|
|
2
2
|
import { updateJobState } from "../jobs.js";
|
|
3
3
|
import { fetchAllBalances, computeRebalancePlan, hasEnoughBalance } from "../rebalance.js";
|
|
4
4
|
import { checkArbLiquidity } from "../liquidity.js";
|
|
@@ -6,6 +6,9 @@ import { computeAnnualSpread } from "../funding.js";
|
|
|
6
6
|
import { fetchPacificaPrices, fetchHyperliquidMeta, fetchLighterOrderBookDetails, fetchLighterFundingRates, } from "../shared-api.js";
|
|
7
7
|
import { computeMatchedSize, reconcileArbFills } from "../arb-sizing.js";
|
|
8
8
|
async function fetchAllRates() {
|
|
9
|
+
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
10
|
+
// alive when one DEX errors out, while rejection reasons get logged to
|
|
11
|
+
// stderr (explicit failure, never silent).
|
|
9
12
|
const [pacRates, hlRates, ltRates] = await Promise.allSettled([
|
|
10
13
|
fetchPacificaPrices().then(assets => assets.map(p => ({
|
|
11
14
|
exchange: "pacifica", symbol: p.symbol, fundingRate: p.funding, markPrice: p.mark,
|
|
@@ -20,14 +23,32 @@ async function fetchAllRates() {
|
|
|
20
23
|
]);
|
|
21
24
|
const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
|
|
22
25
|
const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
symbol
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const fr of funding) {
|
|
28
|
+
const symbol = fr.symbol || symMap.get(fr.marketId) || "";
|
|
29
|
+
if (!symbol)
|
|
30
|
+
continue;
|
|
31
|
+
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
32
|
+
// price source preference (NOT an error fallback).
|
|
33
|
+
const directMark = fr.markPrice;
|
|
34
|
+
const fallbackMark = priceMap.get(fr.marketId);
|
|
35
|
+
let markPrice;
|
|
36
|
+
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
37
|
+
markPrice = directMark;
|
|
38
|
+
}
|
|
39
|
+
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
40
|
+
markPrice = fallbackMark;
|
|
41
|
+
}
|
|
42
|
+
// SSOT rule #2: skip rows whose mark price cannot be resolved at all
|
|
43
|
+
// — never publish a 0 mark price downstream.
|
|
44
|
+
if (markPrice === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
out.push({ exchange: "lighter", symbol, fundingRate: fr.rate, markPrice });
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
29
49
|
})(),
|
|
30
50
|
]);
|
|
51
|
+
logSettledRejections([pacRates, hlRates, ltRates], ["pacifica", "hyperliquid", "lighter"], "funding-arb");
|
|
31
52
|
return [
|
|
32
53
|
...(pacRates.status === "fulfilled" ? pacRates.value : []),
|
|
33
54
|
...(hlRates.status === "fulfilled" ? hlRates.value : []),
|
|
@@ -141,7 +162,7 @@ export async function runFundingArb(adapters, params, jobId, log = console.log)
|
|
|
141
162
|
for (const move of plan.moves) {
|
|
142
163
|
log(`[ARB] $${move.amount} ${move.from} → ${move.to}`);
|
|
143
164
|
}
|
|
144
|
-
log(`[ARB] Run 'perp rebalance execute' to rebalance.`);
|
|
165
|
+
log(`[ARB] Run 'perp funds rebalance execute' to rebalance.`);
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
}
|
package/dist/trade-validator.js
CHANGED
|
@@ -10,13 +10,26 @@ export async function validateTrade(adapter, params) {
|
|
|
10
10
|
const checks = [];
|
|
11
11
|
const warnings = [];
|
|
12
12
|
const sym = params.symbol.toUpperCase();
|
|
13
|
-
// Fetch market data, balance, positions, orderbook in parallel
|
|
13
|
+
// Fetch market data, balance, positions, orderbook in parallel.
|
|
14
|
+
// SSOT rule #2: errors must propagate, never silently substitute defaults.
|
|
15
|
+
// getMarkets retains a tracked-error wrapper so the failed-load case surfaces
|
|
16
|
+
// a structured symbol_valid check (not a generic exception).
|
|
17
|
+
let marketsError = null;
|
|
14
18
|
const [markets, balance, positions, orderbook] = await Promise.all([
|
|
15
|
-
adapter.getMarkets().catch(() =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
adapter.getMarkets().catch((e) => {
|
|
20
|
+
marketsError = e instanceof Error ? e.message : String(e);
|
|
21
|
+
return [];
|
|
22
|
+
}),
|
|
23
|
+
adapter.getBalance(),
|
|
24
|
+
adapter.getPositions(),
|
|
25
|
+
params.type !== "limit"
|
|
26
|
+
? adapter.getOrderbook(sym)
|
|
27
|
+
: Promise.resolve({ bids: [], asks: [] }),
|
|
19
28
|
]);
|
|
29
|
+
if (markets.length === 0 && marketsError) {
|
|
30
|
+
checks.push({ check: "symbol_valid", passed: false, message: `Failed to load markets from ${adapter.name}: ${marketsError}` });
|
|
31
|
+
return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
|
|
32
|
+
}
|
|
20
33
|
// 1. Symbol validity (handle BTC vs BTC-PERP suffix variants)
|
|
21
34
|
const market = markets.find(m => {
|
|
22
35
|
const ms = m.symbol.toUpperCase();
|
|
@@ -30,9 +43,21 @@ export async function validateTrade(adapter, params) {
|
|
|
30
43
|
return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
|
|
31
44
|
}
|
|
32
45
|
const markPrice = Number(market.markPrice);
|
|
46
|
+
if (params.type === "limit" && (params.price === undefined || !Number.isFinite(params.price))) {
|
|
47
|
+
throw new Error(`Limit order requires explicit price (received ${params.price}); refusing silent mark-price substitution.`);
|
|
48
|
+
}
|
|
49
|
+
if (markPrice <= 0 && params.price === undefined) {
|
|
50
|
+
throw new Error(`No mark price available for ${sym} on ${adapter.name} and no override price supplied; cannot validate trade.`);
|
|
51
|
+
}
|
|
33
52
|
const price = params.price ?? markPrice;
|
|
34
53
|
const notional = params.size * price;
|
|
54
|
+
if (params.leverage !== undefined && (!Number.isFinite(params.leverage) || params.leverage <= 0)) {
|
|
55
|
+
throw new Error(`Leverage must be a positive number (received ${params.leverage}).`);
|
|
56
|
+
}
|
|
35
57
|
const leverage = params.leverage ?? market.maxLeverage;
|
|
58
|
+
if (!Number.isFinite(leverage) || leverage <= 0) {
|
|
59
|
+
throw new Error(`No usable leverage for ${sym} (resolved=${leverage}); supply params.leverage explicitly.`);
|
|
60
|
+
}
|
|
36
61
|
const marginRequired = notional / leverage;
|
|
37
62
|
// 2. Balance check
|
|
38
63
|
const available = Number(balance.available);
|
package/dist/utils.d.ts
CHANGED
|
@@ -38,3 +38,13 @@ export declare function jsonError(code: string, message: string, meta?: Partial<
|
|
|
38
38
|
* In JSON mode, errors are returned as JSON instead of crashing.
|
|
39
39
|
*/
|
|
40
40
|
export declare function withJsonErrors<T>(isJson: boolean, fn: () => Promise<T>, exchange?: string): Promise<T | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Log each rejected outcome from Promise.allSettled to stderr with a
|
|
43
|
+
* per-source label. SSOT rule #2 helper: turn a quiet rejection inside a
|
|
44
|
+
* multi-DEX comparison into an explicit one-line failure on stderr.
|
|
45
|
+
*
|
|
46
|
+
* @param settled Outcomes from Promise.allSettled.
|
|
47
|
+
* @param labels Source label per index (e.g. ["pacifica", "hyperliquid", ...]).
|
|
48
|
+
* @param prefix Bracketed prefix prepended to each line (e.g. "arb-auto").
|
|
49
|
+
*/
|
|
50
|
+
export declare function logSettledRejections(settled: PromiseSettledResult<unknown>[], labels: string[], prefix: string): void;
|
package/dist/utils.js
CHANGED
|
@@ -221,3 +221,22 @@ export async function withJsonErrors(isJson, fn, exchange) {
|
|
|
221
221
|
return undefined;
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Log each rejected outcome from Promise.allSettled to stderr with a
|
|
226
|
+
* per-source label. SSOT rule #2 helper: turn a quiet rejection inside a
|
|
227
|
+
* multi-DEX comparison into an explicit one-line failure on stderr.
|
|
228
|
+
*
|
|
229
|
+
* @param settled Outcomes from Promise.allSettled.
|
|
230
|
+
* @param labels Source label per index (e.g. ["pacifica", "hyperliquid", ...]).
|
|
231
|
+
* @param prefix Bracketed prefix prepended to each line (e.g. "arb-auto").
|
|
232
|
+
*/
|
|
233
|
+
export function logSettledRejections(settled, labels, prefix) {
|
|
234
|
+
for (let i = 0; i < settled.length; i++) {
|
|
235
|
+
const r = settled[i];
|
|
236
|
+
if (r.status === "rejected") {
|
|
237
|
+
const reason = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
238
|
+
// eslint-disable-next-line no-console
|
|
239
|
+
console.error(`[${prefix}] ${labels[i] ?? `index-${i}`} fetch failed: ${reason}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perp-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
|
|
5
5
|
"bin": {
|
|
6
6
|
"perp": "dist/index.js",
|
|
7
|
-
"perp-mcp": "dist/mcp-server.js"
|
|
7
|
+
"perp-mcp": "dist/mcp-server.js",
|
|
8
|
+
"perp-guardrail": "dist/guardrail/perp-guardrail.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
47
48
|
"@msgpack/msgpack": "^3.1.3",
|
|
49
|
+
"@open-wallet-standard/core": "1.3.2",
|
|
48
50
|
"@solana/web3.js": "^1.98.0",
|
|
49
51
|
"@types/react": "^19.2.14",
|
|
50
52
|
"bs58": "^6.0.0",
|
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -99,20 +99,30 @@ perp --json arb close ETH # sells spot + buys back perp
|
|
|
99
99
|
|
|
100
100
|
Spot exchanges: HL, LT. Pacifica and Aster are perp-only.
|
|
101
101
|
|
|
102
|
-
##
|
|
102
|
+
## Strategy Engine (19 bot algorithms + nested scripted plans)
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
perp
|
|
106
|
-
perp
|
|
107
|
-
perp
|
|
108
|
-
perp
|
|
109
|
-
perp
|
|
110
|
-
perp
|
|
111
|
-
perp
|
|
112
|
-
perp
|
|
113
|
-
perp
|
|
114
|
-
perp
|
|
115
|
-
perp
|
|
105
|
+
perp strategy list-strategies # list all 19 strategies
|
|
106
|
+
perp strategy run <strategy> [symbol] # run any registered strategy
|
|
107
|
+
perp strategy run funding-auto # multi-exchange funding arb
|
|
108
|
+
perp strategy apex [symbol] # APEX orchestrator (Radar+Pulse+Guard)
|
|
109
|
+
perp strategy reflect # performance analysis (win rate, fees, PnL)
|
|
110
|
+
perp strategy preset-list # list strategy presets
|
|
111
|
+
perp strategy preset <name> [symbol] # run from preset
|
|
112
|
+
perp strategy start <config.yaml> # start from YAML/JSON config
|
|
113
|
+
perp strategy quick-grid <symbol> # quick grid bot
|
|
114
|
+
perp strategy quick-dca <symbol> <side> <amount> <interval>
|
|
115
|
+
perp strategy quick-arb # quick funding arb
|
|
116
|
+
|
|
117
|
+
# Scripted plans (one-shot multi-step execution)
|
|
118
|
+
perp strategy plan example # show plan format
|
|
119
|
+
perp --json strategy plan validate <file> # validate before run
|
|
120
|
+
perp --json strategy plan execute <file> --dry-run # dry-run plan
|
|
121
|
+
|
|
122
|
+
# Background process supervisor (tmux)
|
|
123
|
+
perp background list # list running jobs
|
|
124
|
+
perp background stop <id> # stop a job
|
|
125
|
+
perp background logs <id> -f # follow logs
|
|
116
126
|
```
|
|
117
127
|
|
|
118
128
|
**Strategies:** grid, dca, funding-arb, funding-arb-v2, funding-auto, basis-arb, simple-mm, engine-mm, avellaneda-mm, regime-mm, grid-mm, liquidation-mm, momentum-breakout, mean-reversion, aggressive-taker, hedge-agent, rfq-agent, claude-agent, apex
|
|
@@ -136,7 +146,6 @@ perp --json -e <EX> trade cancel <SYM> # cancel all orders for
|
|
|
136
146
|
## Agent Tools
|
|
137
147
|
|
|
138
148
|
```bash
|
|
139
|
-
perp --json agent schema # discover all commands (don't guess)
|
|
140
149
|
perp --json -e <EX> trade check <SYM> <SIDE> <SIZE> # pre-validate trade
|
|
141
150
|
perp --json portfolio # balances + positions + risk level
|
|
142
151
|
perp --json --fields totalEquity,positions portfolio # filtered output
|
|
@@ -172,7 +181,7 @@ Response format: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "
|
|
|
172
181
|
| `SYMBOL_NOT_FOUND` | `market list` to verify symbol |
|
|
173
182
|
| `SIZE_TOO_SMALL` | `market info <SYM>` for min size |
|
|
174
183
|
| `DUPLICATE_ORDER` | already submitted — check positions |
|
|
175
|
-
| Lighter `invalid signature` | `
|
|
184
|
+
| Lighter `invalid signature` | `perp wallet agent approve lighter` to regenerate |
|
|
176
185
|
|
|
177
186
|
## Examples
|
|
178
187
|
|
|
@@ -81,12 +81,14 @@ perp --json arb scan --min 5
|
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
### Lighter API Key Setup
|
|
84
|
-
Lighter uses a separate API key for trading
|
|
85
|
-
When `LIGHTER_PRIVATE_KEY` is set (env var or `wallet set`), the CLI auto-generates and saves the API key on first use.
|
|
84
|
+
Lighter uses a separate API key for trading. There are two paths:
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
1. **Auto-setup (default)**: When `LIGHTER_PRIVATE_KEY` is set (env var or `wallet set`), the CLI auto-generates and saves the API key (slot 4) on first use.
|
|
87
|
+
2. **Managed agent (recommended for long-running agents)**: `perp wallet agent approve lighter --master <wallet> --api-key-index <slot>` registers a per-agent slot (4-254) with explicit expiry tracking + 3-tier signer routing.
|
|
88
|
+
|
|
89
|
+
If auto-setup fails (e.g. transient network error), retry manually:
|
|
88
90
|
```bash
|
|
89
|
-
perp --json
|
|
91
|
+
perp --json wallet agent approve lighter --master <wallet>
|
|
90
92
|
```
|
|
91
93
|
|
|
92
94
|
### Using the Same EVM Key for Multiple Exchanges
|