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
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { PacificaClient } from "../pacifica/index.js";
|
|
2
2
|
import { LocalSolanaSigner } from "../signer/index.js";
|
|
3
|
+
import { isExpired } from "../agent-wallet/expiry.js";
|
|
4
|
+
import { PerpError } from "../errors.js";
|
|
3
5
|
export class PacificaAdapter {
|
|
4
6
|
name = "pacifica";
|
|
5
7
|
chain = "solana";
|
|
6
8
|
aliases = ["pac"];
|
|
7
9
|
client;
|
|
10
|
+
/** Tier 3: PK direct (constructor-supplied keypair). */
|
|
8
11
|
_solanaSigner;
|
|
12
|
+
/** Tier 2: OWS master signer (set via setSigner). */
|
|
13
|
+
_masterSigner;
|
|
14
|
+
/** Tier 1: agent OWS signer (Phase 2c). */
|
|
15
|
+
_agentSigner;
|
|
16
|
+
/** Tier 1 metadata. */
|
|
17
|
+
_agentMeta;
|
|
18
|
+
/** --no-agent bypass flag. */
|
|
19
|
+
_useNoAgent = false;
|
|
20
|
+
/** True when adapter was constructed without a real PK (read-only). */
|
|
9
21
|
_hasRealKey;
|
|
22
|
+
/** Public Solana base58 of the active signer (`_resolveSigner().address`). */
|
|
10
23
|
account;
|
|
24
|
+
/** Bound `signMessage` for the active signer (Phase 2c routes through `_resolveSigner`). */
|
|
11
25
|
signMessage;
|
|
12
26
|
_marketsCache = null;
|
|
13
27
|
_marketsCacheTime = 0;
|
|
@@ -17,18 +31,92 @@ export class PacificaAdapter {
|
|
|
17
31
|
this._hasRealKey = hasRealKey;
|
|
18
32
|
this.account = this._solanaSigner.getPublicKeyBase58();
|
|
19
33
|
this.client = new PacificaClient({ network, builderCode });
|
|
20
|
-
|
|
34
|
+
// Hot-path methods always go through `_resolveSigner()` so the bound
|
|
35
|
+
// `signMessage` reflects the highest-priority active tier.
|
|
36
|
+
this.signMessage = (msg) => this._resolveSigner().signer.signMessage(msg);
|
|
21
37
|
}
|
|
22
38
|
ensureSigner() {
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
-
}
|
|
39
|
+
// Resolves across all tiers; throws PerpError if none available.
|
|
40
|
+
this._resolveSigner();
|
|
26
41
|
}
|
|
27
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Inject an external Solana signer (Tier 2 — OWS master).
|
|
44
|
+
* Backward-compatible: existing callers (e.g. `perp -e pacifica --ows ...`
|
|
45
|
+
* in `_initWithOws`) continue to work; the master signer is what
|
|
46
|
+
* `_resolveSigner()` falls back to when no agent is registered.
|
|
47
|
+
*/
|
|
28
48
|
setSigner(signer) {
|
|
49
|
+
this._masterSigner = signer;
|
|
29
50
|
this._solanaSigner = signer;
|
|
30
51
|
this.account = signer.getPublicKeyBase58();
|
|
31
|
-
this.
|
|
52
|
+
this._hasRealKey = true;
|
|
53
|
+
this.signMessage = (msg) => this._resolveSigner().signer.signMessage(msg);
|
|
54
|
+
}
|
|
55
|
+
/** Tier 1: inject agent OWS Solana signer (Phase 2c). */
|
|
56
|
+
setAgentSigner(meta, signer) {
|
|
57
|
+
this._agentMeta = meta;
|
|
58
|
+
this._agentSigner = signer;
|
|
59
|
+
// The `account` field is the master Solana address (used in REST envelopes
|
|
60
|
+
// as the `account` key) — agent path puts the master in `account` and the
|
|
61
|
+
// agent in `agent_wallet`. Capture the master Solana address from the
|
|
62
|
+
// AgentMeta so REST flows can reference it.
|
|
63
|
+
if (meta.userSolanaAddress) {
|
|
64
|
+
this.account = meta.userSolanaAddress;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Bypass Tier 1 (agent) — used for `--no-agent` flag. */
|
|
68
|
+
setNoAgent(noAgent) {
|
|
69
|
+
this._useNoAgent = noAgent;
|
|
70
|
+
}
|
|
71
|
+
/** True only when ALL THREE tiers are unconfigured. */
|
|
72
|
+
get isReadOnly() {
|
|
73
|
+
return !this._agentSigner && !this._masterSigner && !this._hasRealKey;
|
|
74
|
+
}
|
|
75
|
+
/** Which signer tier is currently active (for debug/diagnostics). */
|
|
76
|
+
get activeSignerTier() {
|
|
77
|
+
try {
|
|
78
|
+
return this._resolveSigner().tier;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Resolve the active signer across three tiers. Mirrors AsterAdapter._resolveSigner. */
|
|
85
|
+
_resolveSigner() {
|
|
86
|
+
// Tier 1: agent (when registered, not expired, --no-agent NOT set)
|
|
87
|
+
if (!this._useNoAgent && this._agentSigner && this._agentMeta) {
|
|
88
|
+
if (isExpired(this._agentMeta)) {
|
|
89
|
+
// Skip Tier 1 only if Tier 2/3 available; otherwise throw AGENT_EXPIRED
|
|
90
|
+
if (!this._masterSigner && !this._hasRealKey) {
|
|
91
|
+
throw new PerpError("AGENT_EXPIRED", "Agent wallet has expired", {
|
|
92
|
+
remediation: "perp wallet agent approve pacifica --rotate",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// fall through to Tier 2/3
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return { tier: "agent", signer: this._agentSigner, address: this._agentSigner.getPublicKeyBase58() };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Tier 2: OWS master
|
|
102
|
+
if (this._masterSigner) {
|
|
103
|
+
return { tier: "master", signer: this._masterSigner, address: this._masterSigner.getPublicKeyBase58() };
|
|
104
|
+
}
|
|
105
|
+
// Tier 3: PK direct
|
|
106
|
+
if (this._hasRealKey) {
|
|
107
|
+
// Guard: legacy test fixtures (e.g. bugfix-v042.test.ts) construct adapters
|
|
108
|
+
// via `Object.create(PacificaAdapter.prototype)` and only set `_hasRealKey = true`
|
|
109
|
+
// — `_solanaSigner` is undefined in that case. Fall back to the cached
|
|
110
|
+
// `account` field when available.
|
|
111
|
+
const address = this._solanaSigner
|
|
112
|
+
? this._solanaSigner.getPublicKeyBase58()
|
|
113
|
+
: this.account;
|
|
114
|
+
return { tier: "pk", signer: this._solanaSigner, address };
|
|
115
|
+
}
|
|
116
|
+
// No signer at any tier
|
|
117
|
+
throw new PerpError("NO_SIGNER_AVAILABLE", "No signing path configured for Pacifica", {
|
|
118
|
+
remediation: "Run one of: (a) perp wallet agent approve pacifica --master <wallet>; (b) perp wallet generate && perp -e pacifica --ows <wallet>; (c) export PACIFICA_PRIVATE_KEY=...",
|
|
119
|
+
});
|
|
32
120
|
}
|
|
33
121
|
/** Access the underlying Keypair (only available with LocalSolanaSigner). */
|
|
34
122
|
get keypair() {
|
package/dist/funding/rates.js
CHANGED
|
@@ -56,6 +56,9 @@ async function fetchHyperliquidRates() {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
async function fetchLighterRates() {
|
|
59
|
+
// Outer try/catch returning [] is intentional graceful degradation; see
|
|
60
|
+
// fetchAsterRates for the same pattern. Inside, SSOT rule #2 applies —
|
|
61
|
+
// skip rows whose mark price cannot be resolved instead of 0-coercing.
|
|
59
62
|
try {
|
|
60
63
|
const [details, funding] = await Promise.all([
|
|
61
64
|
fetchLighterOrderBookDetails(),
|
|
@@ -63,25 +66,47 @@ async function fetchLighterRates() {
|
|
|
63
66
|
]);
|
|
64
67
|
const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
|
|
65
68
|
const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
|
|
66
|
-
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const fr of funding) {
|
|
67
71
|
const symbol = fr.symbol || symMap.get(fr.marketId) || "";
|
|
72
|
+
if (!symbol)
|
|
73
|
+
continue;
|
|
68
74
|
const rate = fr.rate;
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
// Prefer fr.markPrice; fall back to orderBook last-trade as a documented
|
|
76
|
+
// price-source preference (NOT an error fallback).
|
|
77
|
+
const directMark = fr.markPrice;
|
|
78
|
+
const fallbackMark = priceMap.get(fr.marketId);
|
|
79
|
+
let markPrice;
|
|
80
|
+
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
81
|
+
markPrice = directMark;
|
|
82
|
+
}
|
|
83
|
+
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
84
|
+
markPrice = fallbackMark;
|
|
85
|
+
}
|
|
86
|
+
if (!Number.isFinite(rate) || markPrice === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
out.push({
|
|
71
89
|
exchange: "lighter",
|
|
72
90
|
symbol,
|
|
73
91
|
fundingRate: rate,
|
|
74
|
-
hourlyRate:
|
|
92
|
+
hourlyRate: toHourlyRate(rate, "lighter"),
|
|
75
93
|
annualizedPct: annualizeRate(rate, "lighter"),
|
|
76
|
-
markPrice
|
|
77
|
-
};
|
|
78
|
-
}
|
|
94
|
+
markPrice,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
79
98
|
}
|
|
80
99
|
catch {
|
|
81
100
|
return [];
|
|
82
101
|
}
|
|
83
102
|
}
|
|
84
103
|
async function fetchAsterRates() {
|
|
104
|
+
// Outer try/catch returning [] is intentional graceful degradation: when
|
|
105
|
+
// the entire endpoint is unreachable, the cross-DEX comparison should
|
|
106
|
+
// continue with the remaining venues.
|
|
107
|
+
// Inside the try block, SSOT rule #2 applies — refuse to silently 0-coerce
|
|
108
|
+
// missing markPrice / lastFundingRate, since those values feed downstream
|
|
109
|
+
// sizing and any 0 corrupts the comparison.
|
|
85
110
|
try {
|
|
86
111
|
const [premiums, tickers] = await Promise.all([
|
|
87
112
|
fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex").then(r => r.json()),
|
|
@@ -89,25 +114,38 @@ async function fetchAsterRates() {
|
|
|
89
114
|
]);
|
|
90
115
|
const priceMap = new Map();
|
|
91
116
|
for (const t of tickers ?? []) {
|
|
92
|
-
|
|
117
|
+
const last = Number(t.lastPrice);
|
|
118
|
+
if (Number.isFinite(last) && last > 0)
|
|
119
|
+
priceMap.set(String(t.symbol), last);
|
|
93
120
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.map(p => {
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const p of premiums ?? []) {
|
|
97
123
|
const rawSymbol = String(p.symbol ?? "");
|
|
124
|
+
if (!rawSymbol.endsWith("USDT"))
|
|
125
|
+
continue;
|
|
126
|
+
const fundingRate = Number(p.lastFundingRate);
|
|
127
|
+
// Prefer premiumIndex.markPrice; fall back to ticker.lastPrice as a
|
|
128
|
+
// documented price-source preference (NOT an error fallback).
|
|
129
|
+
const markFromPremium = Number(p.markPrice);
|
|
130
|
+
const markPrice = Number.isFinite(markFromPremium) && markFromPremium > 0
|
|
131
|
+
? markFromPremium
|
|
132
|
+
: priceMap.get(rawSymbol);
|
|
133
|
+
if (!Number.isFinite(fundingRate) || markPrice === undefined || !Number.isFinite(markPrice) || markPrice <= 0) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
98
136
|
const symbol = rawSymbol.replace(/USDT$/, "");
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
return {
|
|
137
|
+
const nextFundingTimeRaw = Number(p.nextFundingTime);
|
|
138
|
+
out.push({
|
|
102
139
|
exchange: "aster",
|
|
103
140
|
symbol,
|
|
104
|
-
fundingRate
|
|
105
|
-
hourlyRate:
|
|
106
|
-
annualizedPct: annualizeRate(
|
|
107
|
-
markPrice
|
|
108
|
-
nextFundingTime: Number(
|
|
109
|
-
};
|
|
110
|
-
}
|
|
141
|
+
fundingRate,
|
|
142
|
+
hourlyRate: toHourlyRate(fundingRate, "aster"),
|
|
143
|
+
annualizedPct: annualizeRate(fundingRate, "aster"),
|
|
144
|
+
markPrice,
|
|
145
|
+
nextFundingTime: Number.isFinite(nextFundingTimeRaw) ? nextFundingTimeRaw : 0,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
111
149
|
}
|
|
112
150
|
catch {
|
|
113
151
|
return [];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEX contract whitelist for perp-guardrail policy executable.
|
|
3
|
+
* Keyed by CAIP-2 chain ID → array of allowed contract/program addresses.
|
|
4
|
+
*/
|
|
5
|
+
export declare const USDC_ADDRESSES: Record<string, string>;
|
|
6
|
+
export declare const ALLOWED_CONTRACTS: Record<string, string[]>;
|
|
7
|
+
export declare const ALLOWED_CHAINS: string[];
|
|
8
|
+
export declare const DEFAULT_LIMITS: {
|
|
9
|
+
max_tx_usd: number;
|
|
10
|
+
max_daily_usd: number;
|
|
11
|
+
max_withdraw_usd: number;
|
|
12
|
+
max_daily_withdraw_usd: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const DANGEROUS_SELECTORS: string[];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEX contract whitelist for perp-guardrail policy executable.
|
|
3
|
+
* Keyed by CAIP-2 chain ID → array of allowed contract/program addresses.
|
|
4
|
+
*/
|
|
5
|
+
// ── Pacifica (Solana) ──
|
|
6
|
+
const PACIFICA_MAINNET = [
|
|
7
|
+
"PCFA5iYgmqK6MqPhWNKg7Yv7auX7VZ4Cx7T1eJyrAMH", // Pacifica program
|
|
8
|
+
"9Gdmhq4Gv1LnNMp7aiS1HSVd7pNnXNMsbuXALCQRmGjY", // Central state
|
|
9
|
+
"72R843XwZxqWhsJceARQQTTbYtWy6Zw9et2YV4FpRHTa", // Vault
|
|
10
|
+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint
|
|
11
|
+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // SPL Token
|
|
12
|
+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", // Associated Token
|
|
13
|
+
"11111111111111111111111111111111", // System Program
|
|
14
|
+
"CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe", // CCTP V2 Token Messenger
|
|
15
|
+
"CCTPV2Sm4AdWt5296sk4P66VBZ7bEhcARwFaaS9YPbeC", // CCTP V2 Message Transmitter
|
|
16
|
+
];
|
|
17
|
+
const PACIFICA_TESTNET = [
|
|
18
|
+
"peRPsYCcB1J9jvrs29jiGdjkytxs8uHLmSPLKKP9ptm", // Testnet program
|
|
19
|
+
"2zPRq1Qvdq5A4Ld6WsH7usgCge4ApZRYfhhf5VAjfXxv", // Testnet central state
|
|
20
|
+
"5SDFdHZGTZbyRYu54CgmRkCGnPHC5pYaN27p7XGLqnBs", // Testnet vault
|
|
21
|
+
"USDPqRbLidFGufty2s3oizmDEKdqx7ePTqzDMbf5ZKM", // Testnet USDC
|
|
22
|
+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
23
|
+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
|
|
24
|
+
"11111111111111111111111111111111",
|
|
25
|
+
];
|
|
26
|
+
// ── Hyperliquid (Arbitrum + HyperCore) ──
|
|
27
|
+
const HYPERLIQUID_CONTRACTS = [
|
|
28
|
+
"0xb21D281DEdb17AE5B501F6AA8256fe38C4e45757", // CctpForwarder (HyperCore)
|
|
29
|
+
];
|
|
30
|
+
// ── CCTP V2 (shared across EVM chains) ──
|
|
31
|
+
const CCTP_V2_EVM = [
|
|
32
|
+
"0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d", // TokenMessengerV2
|
|
33
|
+
"0x81D40F21F12A8F0E3252Bccb954D722d4c464B64", // MessageTransmitterV2
|
|
34
|
+
];
|
|
35
|
+
// ── USDC addresses ──
|
|
36
|
+
export const USDC_ADDRESSES = {
|
|
37
|
+
"eip155:1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // Ethereum
|
|
38
|
+
"eip155:42161": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // Arbitrum
|
|
39
|
+
"eip155:8453": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // Base
|
|
40
|
+
};
|
|
41
|
+
// ── Complete whitelist by CAIP-2 chain ──
|
|
42
|
+
export const ALLOWED_CONTRACTS = {
|
|
43
|
+
// Solana mainnet
|
|
44
|
+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": PACIFICA_MAINNET,
|
|
45
|
+
// Solana devnet
|
|
46
|
+
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": PACIFICA_TESTNET,
|
|
47
|
+
// Arbitrum (Hyperliquid deposits + CCTP)
|
|
48
|
+
"eip155:42161": [...HYPERLIQUID_CONTRACTS, ...CCTP_V2_EVM, USDC_ADDRESSES["eip155:42161"]],
|
|
49
|
+
// Hyperliquid signing domain (phantom chain for typed data)
|
|
50
|
+
"eip155:1337": [], // no contract calls, only typed data signing
|
|
51
|
+
// Base (CCTP bridge + USDC)
|
|
52
|
+
"eip155:8453": [...CCTP_V2_EVM, USDC_ADDRESSES["eip155:8453"]],
|
|
53
|
+
// Lighter (ZK rollup)
|
|
54
|
+
"eip155:304": [], // signing-based, no direct contract interaction
|
|
55
|
+
"eip155:300": [], // Lighter testnet
|
|
56
|
+
// Ethereum mainnet (CCTP + USDC)
|
|
57
|
+
"eip155:1": [...CCTP_V2_EVM, USDC_ADDRESSES["eip155:1"]],
|
|
58
|
+
};
|
|
59
|
+
// ── Allowed chain IDs (all chains our DEXes operate on) ──
|
|
60
|
+
export const ALLOWED_CHAINS = Object.keys(ALLOWED_CONTRACTS);
|
|
61
|
+
// ── Default guardrail limits ──
|
|
62
|
+
export const DEFAULT_LIMITS = {
|
|
63
|
+
max_tx_usd: 1000,
|
|
64
|
+
max_daily_usd: 5000,
|
|
65
|
+
max_withdraw_usd: 500,
|
|
66
|
+
max_daily_withdraw_usd: 2000,
|
|
67
|
+
};
|
|
68
|
+
// ── Common blocked selectors ──
|
|
69
|
+
export const DANGEROUS_SELECTORS = [
|
|
70
|
+
"0x095ea7b3", // approve(address,uint256) — unlimited token approvals
|
|
71
|
+
"0x23b872dd", // transferFrom(address,address,uint256)
|
|
72
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* perp-guardrail — OWS Policy Engine custom executable.
|
|
4
|
+
*
|
|
5
|
+
* Protocol:
|
|
6
|
+
* stdin → PolicyContext JSON (from OWS engine)
|
|
7
|
+
* stdout → PolicyResult JSON {"allow": bool, "reason"?: string}
|
|
8
|
+
*
|
|
9
|
+
* Validates:
|
|
10
|
+
* 1. Contract address whitelist (per chain)
|
|
11
|
+
* 2. Per-transaction USD amount limit
|
|
12
|
+
* 3. Daily cumulative spending limit
|
|
13
|
+
* 4. Withdrawal limits (per-tx and daily)
|
|
14
|
+
* 5. Blocked function selectors (e.g. approve)
|
|
15
|
+
* 6. Allowed trading symbols
|
|
16
|
+
*
|
|
17
|
+
* Configuration is read from PolicyContext.policy_config:
|
|
18
|
+
* { max_tx_usd, max_daily_usd, max_withdraw_usd, max_daily_withdraw_usd,
|
|
19
|
+
* allowed_symbols?, block_selectors?, allowed_contracts?, allowed_chains? }
|
|
20
|
+
*/
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* perp-guardrail — OWS Policy Engine custom executable.
|
|
4
|
+
*
|
|
5
|
+
* Protocol:
|
|
6
|
+
* stdin → PolicyContext JSON (from OWS engine)
|
|
7
|
+
* stdout → PolicyResult JSON {"allow": bool, "reason"?: string}
|
|
8
|
+
*
|
|
9
|
+
* Validates:
|
|
10
|
+
* 1. Contract address whitelist (per chain)
|
|
11
|
+
* 2. Per-transaction USD amount limit
|
|
12
|
+
* 3. Daily cumulative spending limit
|
|
13
|
+
* 4. Withdrawal limits (per-tx and daily)
|
|
14
|
+
* 5. Blocked function selectors (e.g. approve)
|
|
15
|
+
* 6. Allowed trading symbols
|
|
16
|
+
*
|
|
17
|
+
* Configuration is read from PolicyContext.policy_config:
|
|
18
|
+
* { max_tx_usd, max_daily_usd, max_withdraw_usd, max_daily_withdraw_usd,
|
|
19
|
+
* allowed_symbols?, block_selectors?, allowed_contracts?, allowed_chains? }
|
|
20
|
+
*/
|
|
21
|
+
import { ALLOWED_CONTRACTS, ALLOWED_CHAINS, DEFAULT_LIMITS } from "./contracts.js";
|
|
22
|
+
function deny(reason) {
|
|
23
|
+
return { allow: false, reason };
|
|
24
|
+
}
|
|
25
|
+
function allow() {
|
|
26
|
+
return { allow: true };
|
|
27
|
+
}
|
|
28
|
+
function weiToUsd(weiStr, ethPriceUsd) {
|
|
29
|
+
const wei = BigInt(weiStr || "0");
|
|
30
|
+
const ethValue = Number(wei) / 1e18;
|
|
31
|
+
return ethValue * ethPriceUsd;
|
|
32
|
+
}
|
|
33
|
+
// Well-known EVM function selectors
|
|
34
|
+
const KNOWN_SELECTORS = {
|
|
35
|
+
"0x095ea7b3": "approve",
|
|
36
|
+
"0x23b872dd": "transferFrom",
|
|
37
|
+
"0xa9059cbb": "transfer",
|
|
38
|
+
"0x3ccfd60b": "withdraw",
|
|
39
|
+
"0x2e1a7d4d": "withdraw(uint256)",
|
|
40
|
+
"0xd0e30db0": "deposit",
|
|
41
|
+
};
|
|
42
|
+
function isWithdrawTx(data) {
|
|
43
|
+
if (!data || data === "0x")
|
|
44
|
+
return false;
|
|
45
|
+
const selector = data.slice(0, 10).toLowerCase();
|
|
46
|
+
return selector === "0x3ccfd60b" || selector === "0x2e1a7d4d";
|
|
47
|
+
}
|
|
48
|
+
function evaluate(ctx) {
|
|
49
|
+
// SSOT rule #2: policy_config must be explicitly supplied. A missing
|
|
50
|
+
// policy_config previously fell through to silent defaults — that is a
|
|
51
|
+
// safety-critical fallback (the user might assume their custom limits are
|
|
52
|
+
// active when in fact only baked-in defaults are). Fail closed instead.
|
|
53
|
+
if (!ctx.policy_config) {
|
|
54
|
+
return deny("policy_config missing from PolicyContext; refusing to fall through to baked-in defaults. " +
|
|
55
|
+
"Caller must supply policy_config explicitly (max_tx_usd, max_daily_usd, etc.).");
|
|
56
|
+
}
|
|
57
|
+
const config = ctx.policy_config;
|
|
58
|
+
const maxTxUsd = config.max_tx_usd ?? DEFAULT_LIMITS.max_tx_usd;
|
|
59
|
+
const maxDailyUsd = config.max_daily_usd ?? DEFAULT_LIMITS.max_daily_usd;
|
|
60
|
+
const maxWithdrawUsd = config.max_withdraw_usd ?? maxTxUsd;
|
|
61
|
+
const maxDailyWithdrawUsd = config.max_daily_withdraw_usd ?? maxDailyUsd;
|
|
62
|
+
const contracts = config.allowed_contracts ?? ALLOWED_CONTRACTS;
|
|
63
|
+
const chains = config.allowed_chains ?? ALLOWED_CHAINS;
|
|
64
|
+
const ethPrice = config.eth_price_usd ?? 3500;
|
|
65
|
+
const blockSelectors = config.block_selectors ?? [];
|
|
66
|
+
// 1. Chain check
|
|
67
|
+
if (!chains.includes(ctx.chain_id)) {
|
|
68
|
+
return deny(`chain ${ctx.chain_id} not in allowed list`);
|
|
69
|
+
}
|
|
70
|
+
// 2. Contract whitelist (skip for signing-only chains like eip155:1337, eip155:304)
|
|
71
|
+
const chainContracts = contracts[ctx.chain_id];
|
|
72
|
+
if (chainContracts && chainContracts.length > 0 && ctx.transaction.to) {
|
|
73
|
+
const target = ctx.transaction.to.toLowerCase();
|
|
74
|
+
const allowed = chainContracts.map(a => a.toLowerCase());
|
|
75
|
+
if (!allowed.includes(target)) {
|
|
76
|
+
return deny(`contract ${ctx.transaction.to} not whitelisted for ${ctx.chain_id}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// 3. Blocked function selectors (e.g. approve, transferFrom)
|
|
80
|
+
if (blockSelectors.length > 0 && ctx.transaction.data && ctx.transaction.data !== "0x") {
|
|
81
|
+
const selector = ctx.transaction.data.slice(0, 10).toLowerCase();
|
|
82
|
+
if (blockSelectors.map(s => s.toLowerCase()).includes(selector)) {
|
|
83
|
+
const name = KNOWN_SELECTORS[selector] || selector;
|
|
84
|
+
return deny(`function ${name} is blocked by policy`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 4. Per-transaction amount
|
|
88
|
+
if (ctx.transaction.value && ctx.transaction.value !== "0") {
|
|
89
|
+
const txUsd = weiToUsd(ctx.transaction.value, ethPrice);
|
|
90
|
+
if (txUsd > maxTxUsd) {
|
|
91
|
+
return deny(`tx value $${txUsd.toFixed(2)} exceeds limit $${maxTxUsd}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 5. Withdrawal-specific limits
|
|
95
|
+
if (isWithdrawTx(ctx.transaction.data)) {
|
|
96
|
+
if (ctx.transaction.value && ctx.transaction.value !== "0") {
|
|
97
|
+
const withdrawUsd = weiToUsd(ctx.transaction.value, ethPrice);
|
|
98
|
+
if (withdrawUsd > maxWithdrawUsd) {
|
|
99
|
+
return deny(`withdrawal $${withdrawUsd.toFixed(2)} exceeds per-tx limit $${maxWithdrawUsd}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Daily withdrawal check uses same daily_total (OWS tracks cumulative)
|
|
103
|
+
if (ctx.spending.daily_total && ctx.spending.daily_total !== "0") {
|
|
104
|
+
const dailyUsd = weiToUsd(ctx.spending.daily_total, ethPrice);
|
|
105
|
+
if (dailyUsd > maxDailyWithdrawUsd) {
|
|
106
|
+
return deny(`daily withdrawals $${dailyUsd.toFixed(2)} exceeds limit $${maxDailyWithdrawUsd}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// 6. Daily spending limit (all operations)
|
|
111
|
+
if (ctx.spending.daily_total && ctx.spending.daily_total !== "0") {
|
|
112
|
+
const dailyUsd = weiToUsd(ctx.spending.daily_total, ethPrice);
|
|
113
|
+
if (dailyUsd > maxDailyUsd) {
|
|
114
|
+
return deny(`daily spending $${dailyUsd.toFixed(2)} exceeds limit $${maxDailyUsd}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return allow();
|
|
118
|
+
}
|
|
119
|
+
// ── Main: read stdin, evaluate, write stdout ──
|
|
120
|
+
async function main() {
|
|
121
|
+
const chunks = [];
|
|
122
|
+
for await (const chunk of process.stdin) {
|
|
123
|
+
chunks.push(chunk);
|
|
124
|
+
}
|
|
125
|
+
const input = Buffer.concat(chunks).toString("utf-8").trim();
|
|
126
|
+
if (!input) {
|
|
127
|
+
process.stdout.write(JSON.stringify(deny("empty stdin")));
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
let ctx;
|
|
131
|
+
try {
|
|
132
|
+
ctx = JSON.parse(input);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
process.stdout.write(JSON.stringify(deny("invalid JSON on stdin")));
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
const result = evaluate(ctx);
|
|
139
|
+
process.stdout.write(JSON.stringify(result));
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
main().catch((e) => {
|
|
143
|
+
process.stdout.write(JSON.stringify(deny(`internal error: ${e instanceof Error ? e.message : String(e)}`)));
|
|
144
|
+
process.exit(0);
|
|
145
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import "./_stderr-filter.js";
|