perp-cli 0.11.0 → 0.12.1
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/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 +18 -14
- 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 +756 -28
- 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 +84 -34
- package/dist/exchanges/lighter-spot.js +14 -3
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +199 -27
- 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/perp-guardrail.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +268 -360
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +85 -83
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +67 -3
- package/dist/settings.js +1 -2
- package/dist/signer/ows-evm.d.ts +14 -0
- package/dist/signer/ows-evm.js +28 -9
- 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 +2 -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/ows.d.ts +0 -2
- package/dist/commands/ows.js +0 -887
- 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,4 +1,30 @@
|
|
|
1
1
|
import { LocalEvmSigner } from "../signer/index.js";
|
|
2
|
+
import { isExpired } from "../agent-wallet/expiry.js";
|
|
3
|
+
import { PerpError } from "../errors.js";
|
|
4
|
+
/** Lighter trigger orderType enum (matches WASM signer). */
|
|
5
|
+
export const LIGHTER_ORDER_TYPE = {
|
|
6
|
+
STOP_LOSS: 2,
|
|
7
|
+
STOP_LOSS_LIMIT: 3,
|
|
8
|
+
TAKE_PROFIT: 4,
|
|
9
|
+
TAKE_PROFIT_LIMIT: 5,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Pick Lighter trigger orderType from side, trigger price, and current mark.
|
|
13
|
+
*
|
|
14
|
+
* STOP_LOSS protects against adverse moves; TAKE_PROFIT locks in favorable moves.
|
|
15
|
+
* buy + trigger >= mark → STOP_LOSS (close short on rise / breakout entry)
|
|
16
|
+
* buy + trigger < mark → TAKE_PROFIT (close short on fall)
|
|
17
|
+
* sell + trigger <= mark → STOP_LOSS (close long on fall)
|
|
18
|
+
* sell + trigger > mark → TAKE_PROFIT (close long on rise)
|
|
19
|
+
*
|
|
20
|
+
* Tied trigger==mark is classified as STOP_LOSS — the protective default.
|
|
21
|
+
*/
|
|
22
|
+
export function classifyTriggerOrderType(side, triggerPrice, markPrice, isMarket) {
|
|
23
|
+
const isStopLoss = side === "buy" ? triggerPrice >= markPrice : triggerPrice <= markPrice;
|
|
24
|
+
if (isStopLoss)
|
|
25
|
+
return isMarket ? LIGHTER_ORDER_TYPE.STOP_LOSS : LIGHTER_ORDER_TYPE.STOP_LOSS_LIMIT;
|
|
26
|
+
return isMarket ? LIGHTER_ORDER_TYPE.TAKE_PROFIT : LIGHTER_ORDER_TYPE.TAKE_PROFIT_LIMIT;
|
|
27
|
+
}
|
|
2
28
|
export class LighterAdapter {
|
|
3
29
|
name = "lighter";
|
|
4
30
|
chain = "ethereum";
|
|
@@ -21,6 +47,16 @@ export class LighterAdapter {
|
|
|
21
47
|
_marketsCache = null;
|
|
22
48
|
_marketsCacheTime = 0;
|
|
23
49
|
static CACHE_TTL = 30_000;
|
|
50
|
+
// ── 3-tier signer state (Phase 2d) ──
|
|
51
|
+
/** Tier 1 metadata (Phase 2d). When set + not expired + !_useNoAgent, agent
|
|
52
|
+
* apiKeyIndex/publicKey/accountIndex take priority over master/PK env. */
|
|
53
|
+
_agentMeta;
|
|
54
|
+
/** Tier 1 secp256k1 hex private key (40 bytes) for the agent slot. Lighter
|
|
55
|
+
* has no OWS curve binding for raw L2 keys; this is held in-memory only when
|
|
56
|
+
* passed by the caller (e.g. immediately after `setupApiKey()` or via env). */
|
|
57
|
+
_agentApiKey;
|
|
58
|
+
/** --no-agent bypass flag. */
|
|
59
|
+
_useNoAgent = false;
|
|
24
60
|
/**
|
|
25
61
|
* @param evmKey EVM private key (0x-prefixed, 32 bytes) — for deposits & key registration
|
|
26
62
|
* @param testnet Use testnet (chain ID 300) instead of mainnet (chain ID 304)
|
|
@@ -28,9 +64,12 @@ export class LighterAdapter {
|
|
|
28
64
|
*/
|
|
29
65
|
constructor(evmKey, testnet = false, opts) {
|
|
30
66
|
this._evmKey = evmKey;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
67
|
+
// SSOT Rule #3: L2 API key is no longer read from env. Caller must inject
|
|
68
|
+
// via opts.apiKey (Tier 1 agent path uses setAgentSigner; auto-setup at
|
|
69
|
+
// init() runs ChangePubKey when apiKey is empty AND we have an EVM key).
|
|
70
|
+
this._apiKey = opts?.apiKey ?? "";
|
|
71
|
+
this._accountIndexInit = opts?.accountIndex ?? -1;
|
|
72
|
+
this._apiKeyIndex = opts?.apiKeyIndex ?? 4;
|
|
34
73
|
this._address = "";
|
|
35
74
|
this._testnet = testnet;
|
|
36
75
|
this._readOnly = !this._apiKey;
|
|
@@ -65,8 +104,105 @@ export class LighterAdapter {
|
|
|
65
104
|
setSigner(signer) {
|
|
66
105
|
this._evmSigner = signer;
|
|
67
106
|
this._address = signer.getAddress();
|
|
107
|
+
// Reset any caller-supplied accountIndex hint: when the agent flow injects
|
|
108
|
+
// an OWS signer that differs from the constructor-time master, force a
|
|
109
|
+
// fresh /api/v1/account lookup keyed on the injected signer. Without this
|
|
110
|
+
// reset, a stale account index would route ChangePubKey to the wrong
|
|
111
|
+
// account (Lighter rejects the L1 sig with code=21504 "fail to l1 signature").
|
|
112
|
+
this._accountIndexInit = -1;
|
|
113
|
+
this._accountIndex = -1;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Tier 1: inject a Lighter agent's slot+L2 key (Phase 2d).
|
|
117
|
+
*
|
|
118
|
+
* Caller supplies the AgentMeta (settings.agents.lighter[name]) plus the
|
|
119
|
+
* 40-byte L2 private key (hex). Adapter swaps `_apiKey`/`_apiKeyIndex` to
|
|
120
|
+
* the agent's slot/key so all subsequent `signCreateOrder`/`signCancelOrder`
|
|
121
|
+
* etc. flow through the agent identity.
|
|
122
|
+
*
|
|
123
|
+
* NOTE: when the agent path is active, `_address` (master EVM) is left
|
|
124
|
+
* unchanged because Lighter REST queries (account, balance, positions) use
|
|
125
|
+
* `account_index` not address. The L1 EVM identity is irrelevant for the
|
|
126
|
+
* hot-path; only the L2 secp256k1 key is consulted.
|
|
127
|
+
*/
|
|
128
|
+
setAgentSigner(meta, agentApiKey) {
|
|
129
|
+
this._agentMeta = meta;
|
|
130
|
+
this._agentApiKey = agentApiKey;
|
|
131
|
+
// Reset any prior accountIndex hint: the agent may belong to a different
|
|
132
|
+
// Lighter account than the master signer, so any stale value must not
|
|
133
|
+
// override the AgentMeta-derived accountIndex applied in init().
|
|
134
|
+
this._accountIndexInit = -1;
|
|
135
|
+
this._accountIndex = -1;
|
|
136
|
+
// Active slot+key swap happens lazily in `_resolveSigner()`/init flows.
|
|
137
|
+
}
|
|
138
|
+
/** Bypass Tier 1 (agent) — used for `--no-agent` flag. */
|
|
139
|
+
setNoAgent(noAgent) {
|
|
140
|
+
this._useNoAgent = noAgent;
|
|
141
|
+
}
|
|
142
|
+
/** Which signer tier is currently active (for debug/diagnostics). */
|
|
143
|
+
get activeSignerTier() {
|
|
144
|
+
try {
|
|
145
|
+
return this._resolveSigner().tier;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Resolve the active signer tier across three tiers (Phase 2d).
|
|
153
|
+
*
|
|
154
|
+
* Tier 1 (agent): active when AgentMeta + agent L2 key set, not expired,
|
|
155
|
+
* and `--no-agent` NOT set. The actual swap of `_apiKeyIndex`/`_apiKey`
|
|
156
|
+
* happens in `init()` (or via `_activateAgentTier()` post-init).
|
|
157
|
+
* Tier 2 (master): active when an OWS master EVM signer was injected via
|
|
158
|
+
* `setSigner()`. Used for the L1 ChangePubKey path (auto-setup at slot 4).
|
|
159
|
+
* Tier 3 (pk): active when constructor was supplied an evmKey. Same role
|
|
160
|
+
* as Tier 2 — signs L1 ChangePubKey for auto-setup.
|
|
161
|
+
*
|
|
162
|
+
* Lighter is unique among the 4 DEXs in that the actual order-signing is
|
|
163
|
+
* always done by the L2 secp256k1 key (`_apiKey`), regardless of tier. The
|
|
164
|
+
* tiers differ in WHICH L2 key is loaded: Tier 1 uses the agent's slot/key
|
|
165
|
+
* from AgentMeta; Tier 2/3 use whatever the env/auto-setup produced.
|
|
166
|
+
*/
|
|
167
|
+
_resolveSigner() {
|
|
168
|
+
if (!this._useNoAgent && this._agentMeta && this._agentApiKey) {
|
|
169
|
+
if (isExpired(this._agentMeta)) {
|
|
170
|
+
// Skip Tier 1 only if Tier 2/3 available; otherwise throw AGENT_EXPIRED
|
|
171
|
+
if (!this._evmSigner && !this._evmKey) {
|
|
172
|
+
throw new PerpError("AGENT_EXPIRED", "Agent wallet has expired", {
|
|
173
|
+
remediation: "perp wallet agent approve lighter --rotate",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// fall through
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return { tier: "agent" };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (this._evmSigner)
|
|
183
|
+
return { tier: "master" };
|
|
184
|
+
if (this._evmKey)
|
|
185
|
+
return { tier: "pk" };
|
|
186
|
+
throw new PerpError("NO_SIGNER_AVAILABLE", "No signing path configured for Lighter", {
|
|
187
|
+
remediation: "Run one of: (a) perp wallet agent approve lighter --master <wallet>; (b) perp wallet generate && perp -e lighter --ows <wallet>; (c) export LIGHTER_PRIVATE_KEY=0x...",
|
|
188
|
+
});
|
|
68
189
|
}
|
|
69
190
|
async init() {
|
|
191
|
+
// Phase 2d: when an agent is registered (Tier 1), swap _apiKey/_apiKeyIndex
|
|
192
|
+
// to the agent's slot/key BEFORE any of the existing init logic runs. This
|
|
193
|
+
// ensures the WASM client below is created with the agent's credentials.
|
|
194
|
+
// The agent L2 key is loaded from the encrypted keystore by the caller and
|
|
195
|
+
// injected via setAgentSigner() — never read from env (SSOT Rule #3).
|
|
196
|
+
if (!this._useNoAgent && this._agentMeta && this._agentApiKey && !isExpired(this._agentMeta)) {
|
|
197
|
+
this._apiKey = this._agentApiKey;
|
|
198
|
+
if (typeof this._agentMeta.apiKeyIndex === "number") {
|
|
199
|
+
this._apiKeyIndex = this._agentMeta.apiKeyIndex;
|
|
200
|
+
}
|
|
201
|
+
if (typeof this._agentMeta.accountIndex === "number") {
|
|
202
|
+
this._accountIndex = this._agentMeta.accountIndex;
|
|
203
|
+
this._accountIndexInit = this._agentMeta.accountIndex;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
70
206
|
// Initialize EVM signer if not externally injected (skip if no key — read-only mode)
|
|
71
207
|
if (!this._evmSigner && this._evmKey) {
|
|
72
208
|
this._evmSigner = await LocalEvmSigner.create(this._evmKey);
|
|
@@ -80,7 +216,7 @@ export class LighterAdapter {
|
|
|
80
216
|
await this._refreshMarketMap();
|
|
81
217
|
return;
|
|
82
218
|
}
|
|
83
|
-
// If accountIndex is already known (from env/opts), skip API lookup entirely
|
|
219
|
+
// If accountIndex is already known (from env/opts/agent meta), skip API lookup entirely
|
|
84
220
|
if (this._accountIndexInit >= 0) {
|
|
85
221
|
this._accountIndex = this._accountIndexInit;
|
|
86
222
|
}
|
|
@@ -118,19 +254,18 @@ export class LighterAdapter {
|
|
|
118
254
|
// setupApiKey already configured the static WASM client — reuse it
|
|
119
255
|
this._signer = LighterAdapter._wasmClient;
|
|
120
256
|
signerReady = true;
|
|
121
|
-
//
|
|
257
|
+
// SSOT Rule #3: persist L2 key to encrypted keystore so subsequent
|
|
258
|
+
// process invocations can reload it without re-running ChangePubKey.
|
|
122
259
|
try {
|
|
123
|
-
const {
|
|
124
|
-
|
|
125
|
-
setEnvVar("LIGHTER_ACCOUNT_INDEX", String(this._accountIndex));
|
|
126
|
-
setEnvVar("LIGHTER_API_KEY_INDEX", String(autoKeyIndex));
|
|
260
|
+
const { saveLighterKey } = await import("../agent-wallet/lighter-keystore.js");
|
|
261
|
+
saveLighterKey(this._accountIndex, autoKeyIndex, apiKey, "");
|
|
127
262
|
}
|
|
128
|
-
catch { /* non-critical —
|
|
263
|
+
catch { /* non-critical — keystore save may fail in some contexts */ }
|
|
129
264
|
}
|
|
130
265
|
catch (e) {
|
|
131
266
|
// Auto-setup failed — log the error and continue in read-only mode
|
|
132
267
|
const msg = e instanceof Error ? e.message : String(e);
|
|
133
|
-
console.error(`[lighter] API key auto-setup failed: ${msg}. Trading will be read-only. Run 'perp
|
|
268
|
+
console.error(`[lighter] API key auto-setup failed: ${msg}. Trading will be read-only. Run 'perp wallet agent approve lighter' to register an agent.`);
|
|
134
269
|
}
|
|
135
270
|
}
|
|
136
271
|
// Initialize signer for trading if we have an API key (reuse singleton WASM client)
|
|
@@ -140,7 +275,7 @@ export class LighterAdapter {
|
|
|
140
275
|
if (cleanKey.startsWith("0x"))
|
|
141
276
|
cleanKey = cleanKey.slice(2);
|
|
142
277
|
if (cleanKey.length > 0 && !/^[0-9a-fA-F]+$/.test(cleanKey)) {
|
|
143
|
-
console.error(`[lighter] API key contains invalid chars (expected hex).
|
|
278
|
+
console.error(`[lighter] API key contains invalid chars (expected hex). Run 'perp wallet agent approve lighter' to register an agent.`);
|
|
144
279
|
this._apiKey = "";
|
|
145
280
|
}
|
|
146
281
|
else if (cleanKey.length === 0) {
|
|
@@ -148,7 +283,7 @@ export class LighterAdapter {
|
|
|
148
283
|
}
|
|
149
284
|
else if (this._accountIndex < 0) {
|
|
150
285
|
// Account lookup failed or returned no accounts — cannot initialize WASM signer
|
|
151
|
-
console.error(`[lighter] Account index not available (got ${this._accountIndex}). Trading will be read-only. Ensure the wallet has a Lighter account or
|
|
286
|
+
console.error(`[lighter] Account index not available (got ${this._accountIndex}). Trading will be read-only. Ensure the wallet has a Lighter account, or run 'perp wallet agent approve lighter' to register an agent.`);
|
|
152
287
|
this._apiKey = "";
|
|
153
288
|
}
|
|
154
289
|
else {
|
|
@@ -284,9 +419,10 @@ export class LighterAdapter {
|
|
|
284
419
|
const maxLev = imf > 0 ? Math.floor(10000 / imf) : 50;
|
|
285
420
|
const fr = fundingRates.get(d.symbol);
|
|
286
421
|
const dec = this._marketDecimals.get(d.symbol.toUpperCase());
|
|
422
|
+
const fundingMark = fr && Number(fr.markPrice) > 0 ? fr.markPrice : null;
|
|
287
423
|
markets.push({
|
|
288
424
|
symbol: d.symbol,
|
|
289
|
-
markPrice:
|
|
425
|
+
markPrice: fundingMark ?? String(d.last_trade_price ?? 0),
|
|
290
426
|
indexPrice: String(d.last_trade_price ?? 0),
|
|
291
427
|
fundingRate: fr?.rate ?? null,
|
|
292
428
|
volume24h: String(d.daily_quote_token_volume ?? 0),
|
|
@@ -620,28 +756,29 @@ export class LighterAdapter {
|
|
|
620
756
|
const marketIndex = this.getMarketIndex(symbol);
|
|
621
757
|
const { baseAmount } = this.toTicks(symbol, parseFloat(size), 0);
|
|
622
758
|
const { priceTicks: triggerTicks } = this.toTicks(symbol, 0, parseFloat(triggerPrice));
|
|
623
|
-
|
|
759
|
+
const triggerNum = parseFloat(triggerPrice);
|
|
760
|
+
const markPrice = await this.getMarkPrice(symbol);
|
|
624
761
|
const isMarket = !opts?.limitPrice;
|
|
625
762
|
let priceTicks;
|
|
626
763
|
if (isMarket) {
|
|
627
|
-
const markPrice = await this.getMarkPrice(symbol);
|
|
628
764
|
const slippagePrice = side === "buy" ? markPrice * 2 : markPrice * 0.5;
|
|
629
765
|
priceTicks = this.toTicks(symbol, 0, slippagePrice).priceTicks;
|
|
630
766
|
}
|
|
631
767
|
else {
|
|
632
768
|
priceTicks = this.toTicks(symbol, 0, parseFloat(opts.limitPrice)).priceTicks;
|
|
633
769
|
}
|
|
770
|
+
const orderType = classifyTriggerOrderType(side, triggerNum, markPrice, isMarket);
|
|
634
771
|
const signed = await this.signOrder({
|
|
635
772
|
marketIndex,
|
|
636
773
|
clientOrderIndex: this.nextClientOrderIndex(),
|
|
637
774
|
baseAmount,
|
|
638
775
|
price: Math.max(priceTicks, 1),
|
|
639
776
|
isAsk: side === "sell" ? 1 : 0,
|
|
640
|
-
orderType
|
|
777
|
+
orderType,
|
|
641
778
|
timeInForce: isMarket ? 0 : 1, // IOC for market, GTT for limit
|
|
642
779
|
reduceOnly: opts?.reduceOnly ? 1 : 0,
|
|
643
780
|
triggerPrice: triggerTicks,
|
|
644
|
-
orderExpiry:
|
|
781
|
+
orderExpiry: -1, // matches Lighter SDK DEFAULT_28_DAY_ORDER_EXPIRY for trigger orders
|
|
645
782
|
nonce,
|
|
646
783
|
});
|
|
647
784
|
return this.sendTx(signed);
|
|
@@ -996,7 +1133,15 @@ export class LighterAdapter {
|
|
|
996
1133
|
account_index: String(this._accountIndex),
|
|
997
1134
|
api_key_index: String(this._apiKeyIndex),
|
|
998
1135
|
});
|
|
999
|
-
|
|
1136
|
+
const nonce = res.nonce ?? res.next_nonce;
|
|
1137
|
+
// SSOT rule #2: refuse to silently default to nonce 0. A stale or
|
|
1138
|
+
// missing nonce will cause Lighter to reject the tx with cryptic errors
|
|
1139
|
+
// and the user has no way to diagnose the upstream failure.
|
|
1140
|
+
if (nonce === undefined || !Number.isFinite(nonce) || nonce < 0) {
|
|
1141
|
+
throw new Error(`Lighter /nextNonce returned no valid nonce for account_index=${this._accountIndex} ` +
|
|
1142
|
+
`api_key_index=${this._apiKeyIndex}; raw response=${JSON.stringify(res)}`);
|
|
1143
|
+
}
|
|
1144
|
+
return nonce;
|
|
1000
1145
|
}
|
|
1001
1146
|
async signOrder(params) {
|
|
1002
1147
|
try {
|
|
@@ -1024,11 +1169,18 @@ export class LighterAdapter {
|
|
|
1024
1169
|
throw new Error(`Signer error: ${signed.error}`);
|
|
1025
1170
|
if (!signed.txInfo)
|
|
1026
1171
|
throw new Error("Signer returned empty txInfo");
|
|
1172
|
+
// SSOT rule #2: refuse to silently default txType to 0 — different tx
|
|
1173
|
+
// types (createOrder=14, cancelOrder=15, updateLeverage=20, etc.) need
|
|
1174
|
+
// different routing on Lighter's side, and a 0 either rejects with a
|
|
1175
|
+
// generic error or silently misroutes.
|
|
1176
|
+
if (signed.txType === undefined || !Number.isFinite(signed.txType)) {
|
|
1177
|
+
throw new Error(`Lighter signer returned no txType (received ${signed.txType}); refusing to send with default 0.`);
|
|
1178
|
+
}
|
|
1027
1179
|
const res = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
|
|
1028
1180
|
method: "POST",
|
|
1029
1181
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1030
1182
|
body: new URLSearchParams({
|
|
1031
|
-
tx_type: String(signed.txType
|
|
1183
|
+
tx_type: String(signed.txType),
|
|
1032
1184
|
tx_info: signed.txInfo,
|
|
1033
1185
|
}),
|
|
1034
1186
|
});
|
|
@@ -1056,8 +1208,7 @@ export class LighterAdapter {
|
|
|
1056
1208
|
}
|
|
1057
1209
|
ensureSigner() {
|
|
1058
1210
|
if (this._readOnly || this._accountIndex < 0 || !this._signer) {
|
|
1059
|
-
throw new Error("This command requires a Lighter API key. Run `perp
|
|
1060
|
-
"then set LIGHTER_API_KEY in your .env");
|
|
1211
|
+
throw new Error("This command requires a Lighter API key. Run `perp wallet agent approve lighter` to register an agent.");
|
|
1061
1212
|
}
|
|
1062
1213
|
}
|
|
1063
1214
|
static _wasmClient = null;
|
|
@@ -1108,16 +1259,22 @@ export class LighterAdapter {
|
|
|
1108
1259
|
throw new Error("No EVM private key configured. Run: perp setup");
|
|
1109
1260
|
}
|
|
1110
1261
|
if (this._accountIndex < 0) {
|
|
1111
|
-
throw new Error("Account index not available. Call init() first
|
|
1262
|
+
throw new Error("Account index not available. Call init() first.");
|
|
1112
1263
|
}
|
|
1113
1264
|
// 1. Generate key pair
|
|
1114
1265
|
const { privateKey, publicKey } = await LighterAdapter.generateApiKey();
|
|
1115
1266
|
// 2. Get nonce from API
|
|
1267
|
+
// SSOT rule #2: refuse to silently default to nonce 0 — see getNextNonce.
|
|
1116
1268
|
const nonceRes = await this.restGet("/nextNonce", {
|
|
1117
1269
|
account_index: String(this._accountIndex),
|
|
1118
1270
|
api_key_index: String(apiKeyIndex),
|
|
1119
1271
|
});
|
|
1120
|
-
const nonce = nonceRes.nonce ?? nonceRes.next_nonce
|
|
1272
|
+
const nonce = nonceRes.nonce ?? nonceRes.next_nonce;
|
|
1273
|
+
if (nonce === undefined || !Number.isFinite(nonce) || nonce < 0) {
|
|
1274
|
+
throw new Error(`Lighter /nextNonce returned no valid nonce for ChangePubKey ` +
|
|
1275
|
+
`(account_index=${this._accountIndex} api_key_index=${apiKeyIndex}); ` +
|
|
1276
|
+
`raw response=${JSON.stringify(nonceRes)}`);
|
|
1277
|
+
}
|
|
1121
1278
|
// 3. Create signer client with new key and sign ChangePubKey
|
|
1122
1279
|
const client = await LighterAdapter.getWasmClient();
|
|
1123
1280
|
await client.createClient({
|
|
@@ -1145,11 +1302,15 @@ export class LighterAdapter {
|
|
|
1145
1302
|
const txInfo = JSON.parse(signed.txInfo);
|
|
1146
1303
|
txInfo.L1Sig = l1Sig;
|
|
1147
1304
|
// 6. Submit to Lighter API
|
|
1305
|
+
// SSOT rule #2: refuse to silently default txType to 0 — see sendTx.
|
|
1306
|
+
if (signed.txType === undefined || !Number.isFinite(signed.txType)) {
|
|
1307
|
+
throw new Error(`Lighter signChangePubKey returned no txType (received ${signed.txType}); refusing to send with default 0.`);
|
|
1308
|
+
}
|
|
1148
1309
|
const sendRes = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
|
|
1149
1310
|
method: "POST",
|
|
1150
1311
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1151
1312
|
body: new URLSearchParams({
|
|
1152
|
-
tx_type: String(signed.txType
|
|
1313
|
+
tx_type: String(signed.txType),
|
|
1153
1314
|
tx_info: JSON.stringify(txInfo),
|
|
1154
1315
|
}),
|
|
1155
1316
|
});
|
|
@@ -1170,7 +1331,14 @@ export class LighterAdapter {
|
|
|
1170
1331
|
account_index: String(this._accountIndex),
|
|
1171
1332
|
api_key_index: String(apiKeyIndex),
|
|
1172
1333
|
});
|
|
1173
|
-
const
|
|
1334
|
+
const freshNonceCandidate = freshNonceRes.nonce ?? freshNonceRes.next_nonce;
|
|
1335
|
+
if (freshNonceCandidate === undefined || !Number.isFinite(freshNonceCandidate) || freshNonceCandidate < 0) {
|
|
1336
|
+
// SSOT rule #2: refuse to silently fall back to the original nonce.
|
|
1337
|
+
// Skip the retry attempt and surface the upstream nonce-fetch failure.
|
|
1338
|
+
lastError = `fresh /nextNonce returned no valid nonce (raw=${JSON.stringify(freshNonceRes)})`;
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const freshNonce = freshNonceCandidate + attempt;
|
|
1174
1342
|
const retrySigned = await client.signChangePubKey({
|
|
1175
1343
|
pubkey: publicKey, nonce: freshNonce, apiKeyIndex, accountIndex: this._accountIndex,
|
|
1176
1344
|
});
|
|
@@ -1178,13 +1346,17 @@ export class LighterAdapter {
|
|
|
1178
1346
|
lastError = retrySigned.error ?? "incomplete response";
|
|
1179
1347
|
continue;
|
|
1180
1348
|
}
|
|
1349
|
+
if (retrySigned.txType === undefined || !Number.isFinite(retrySigned.txType)) {
|
|
1350
|
+
lastError = `signChangePubKey retry returned no txType (received ${retrySigned.txType})`;
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1181
1353
|
const retryTxInfo = JSON.parse(retrySigned.txInfo);
|
|
1182
1354
|
retryTxInfo.L1Sig = await this._evmSigner.signMessage(retrySigned.messageToSign);
|
|
1183
1355
|
const retryRes = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
|
|
1184
1356
|
method: "POST",
|
|
1185
1357
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1186
1358
|
body: new URLSearchParams({
|
|
1187
|
-
tx_type: String(retrySigned.txType
|
|
1359
|
+
tx_type: String(retrySigned.txType),
|
|
1188
1360
|
tx_info: JSON.stringify(retryTxInfo),
|
|
1189
1361
|
}),
|
|
1190
1362
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacifica agent-wallet signing builders (Phase 2c).
|
|
3
|
+
*
|
|
4
|
+
* Pacifica auth model — off-chain signed messages over Solana Ed25519.
|
|
5
|
+
* Reference: https://pacifica.gitbook.io/docs/api-documentation/api/signing.md
|
|
6
|
+
*
|
|
7
|
+
* Canonical JSON rules (from Pacifica signing spec + production reference
|
|
8
|
+
* `src/pacifica/signing.ts:prepareMessage()`):
|
|
9
|
+
* 1. Recursively sort all object keys alphabetically.
|
|
10
|
+
* 2. Compact JSON (`JSON.stringify(value)` — no whitespace).
|
|
11
|
+
* 3. Outer wrapper has the shape `{ <header fields>, "data": <payload> }`
|
|
12
|
+
* where header is `{ type, timestamp, expiry_window }`. The `data` key
|
|
13
|
+
* is sorted lexicographically alongside the header fields.
|
|
14
|
+
*
|
|
15
|
+
* The agent bind/unbind action types are documented in HypurrQuant_FE
|
|
16
|
+
* `PacificaPerpAdapter.ts` (production reference for HypurrQuant_FE; see
|
|
17
|
+
* project memory `hypurrquant_fe_reference.md`). We mirror their `bind_agent_wallet`
|
|
18
|
+
* and `unbind_agent_wallet` operation types here.
|
|
19
|
+
*
|
|
20
|
+
* Pure functions — no side effects, no fs/fetch/env reads.
|
|
21
|
+
*/
|
|
22
|
+
/** Default expiry window for signed Pacifica REST requests (ms). */
|
|
23
|
+
export declare const DEFAULT_EXPIRY_WINDOW = 5000;
|
|
24
|
+
export interface BindAgentMessageParams {
|
|
25
|
+
/** Master Solana base58 public key. */
|
|
26
|
+
account: string;
|
|
27
|
+
/** Agent Solana base58 public key. */
|
|
28
|
+
agentWallet: string;
|
|
29
|
+
/** Optional ms-epoch timestamp; defaults to `Date.now()`. */
|
|
30
|
+
timestamp?: number;
|
|
31
|
+
/** Optional expiry window (ms); defaults to DEFAULT_EXPIRY_WINDOW. */
|
|
32
|
+
expiryWindow?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface UnbindAgentMessageParams {
|
|
35
|
+
/** Master Solana base58 public key. */
|
|
36
|
+
account: string;
|
|
37
|
+
/**
|
|
38
|
+
* Agent Solana base58 public key to unbind. Pass an empty string to
|
|
39
|
+
* unbind all agent wallets per Pacifica convention.
|
|
40
|
+
*/
|
|
41
|
+
agentWallet: string;
|
|
42
|
+
timestamp?: number;
|
|
43
|
+
expiryWindow?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface BuiltMessage {
|
|
46
|
+
/** Canonical JSON string ready for Ed25519 signing. */
|
|
47
|
+
canonicalJson: string;
|
|
48
|
+
/** Header captured for use in the REST envelope. */
|
|
49
|
+
header: {
|
|
50
|
+
type: string;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
expiry_window: number;
|
|
53
|
+
};
|
|
54
|
+
/** Original payload object (caller supplies in REST envelope alongside signature). */
|
|
55
|
+
payload: Record<string, unknown>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build the canonical JSON for `bind_agent_wallet`.
|
|
59
|
+
*
|
|
60
|
+
* Wire shape (after sorting):
|
|
61
|
+
* {"data":{"agent_wallet":"<base58>"},"expiry_window":5000,"timestamp":...,"type":"bind_agent_wallet"}
|
|
62
|
+
*
|
|
63
|
+
* The `account` field is supplied separately in the REST envelope; only the
|
|
64
|
+
* `agent_wallet` payload field is included inside the signed `data` block.
|
|
65
|
+
* This mirrors `buildSignedRequest()` in `src/pacifica/signing.ts`, which is
|
|
66
|
+
* the production reference for non-agent signed Pacifica requests.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildBindAgentMessage(params: BindAgentMessageParams): BuiltMessage;
|
|
69
|
+
/**
|
|
70
|
+
* Build the canonical JSON for `unbind_agent_wallet`.
|
|
71
|
+
*
|
|
72
|
+
* Pacifica convention: empty `agent_wallet` string unbinds all agents; a
|
|
73
|
+
* specific base58 address unbinds just that agent.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildUnbindAgentMessage(params: UnbindAgentMessageParams): BuiltMessage;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pacifica agent-wallet signing builders (Phase 2c).
|
|
3
|
+
*
|
|
4
|
+
* Pacifica auth model — off-chain signed messages over Solana Ed25519.
|
|
5
|
+
* Reference: https://pacifica.gitbook.io/docs/api-documentation/api/signing.md
|
|
6
|
+
*
|
|
7
|
+
* Canonical JSON rules (from Pacifica signing spec + production reference
|
|
8
|
+
* `src/pacifica/signing.ts:prepareMessage()`):
|
|
9
|
+
* 1. Recursively sort all object keys alphabetically.
|
|
10
|
+
* 2. Compact JSON (`JSON.stringify(value)` — no whitespace).
|
|
11
|
+
* 3. Outer wrapper has the shape `{ <header fields>, "data": <payload> }`
|
|
12
|
+
* where header is `{ type, timestamp, expiry_window }`. The `data` key
|
|
13
|
+
* is sorted lexicographically alongside the header fields.
|
|
14
|
+
*
|
|
15
|
+
* The agent bind/unbind action types are documented in HypurrQuant_FE
|
|
16
|
+
* `PacificaPerpAdapter.ts` (production reference for HypurrQuant_FE; see
|
|
17
|
+
* project memory `hypurrquant_fe_reference.md`). We mirror their `bind_agent_wallet`
|
|
18
|
+
* and `unbind_agent_wallet` operation types here.
|
|
19
|
+
*
|
|
20
|
+
* Pure functions — no side effects, no fs/fetch/env reads.
|
|
21
|
+
*/
|
|
22
|
+
import { sortJsonKeys } from "../pacifica/signing.js";
|
|
23
|
+
/** Default expiry window for signed Pacifica REST requests (ms). */
|
|
24
|
+
export const DEFAULT_EXPIRY_WINDOW = 5_000;
|
|
25
|
+
// ── Builders ─────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Build the canonical JSON for `bind_agent_wallet`.
|
|
28
|
+
*
|
|
29
|
+
* Wire shape (after sorting):
|
|
30
|
+
* {"data":{"agent_wallet":"<base58>"},"expiry_window":5000,"timestamp":...,"type":"bind_agent_wallet"}
|
|
31
|
+
*
|
|
32
|
+
* The `account` field is supplied separately in the REST envelope; only the
|
|
33
|
+
* `agent_wallet` payload field is included inside the signed `data` block.
|
|
34
|
+
* This mirrors `buildSignedRequest()` in `src/pacifica/signing.ts`, which is
|
|
35
|
+
* the production reference for non-agent signed Pacifica requests.
|
|
36
|
+
*/
|
|
37
|
+
export function buildBindAgentMessage(params) {
|
|
38
|
+
const timestamp = params.timestamp ?? Date.now();
|
|
39
|
+
const expiryWindow = params.expiryWindow ?? DEFAULT_EXPIRY_WINDOW;
|
|
40
|
+
const type = "bind_agent_wallet";
|
|
41
|
+
const payload = {
|
|
42
|
+
agent_wallet: params.agentWallet,
|
|
43
|
+
};
|
|
44
|
+
const header = { type, timestamp, expiry_window: expiryWindow };
|
|
45
|
+
const wrapped = { ...header, data: payload };
|
|
46
|
+
const sorted = sortJsonKeys(wrapped);
|
|
47
|
+
return {
|
|
48
|
+
canonicalJson: JSON.stringify(sorted),
|
|
49
|
+
header,
|
|
50
|
+
payload,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the canonical JSON for `unbind_agent_wallet`.
|
|
55
|
+
*
|
|
56
|
+
* Pacifica convention: empty `agent_wallet` string unbinds all agents; a
|
|
57
|
+
* specific base58 address unbinds just that agent.
|
|
58
|
+
*/
|
|
59
|
+
export function buildUnbindAgentMessage(params) {
|
|
60
|
+
const timestamp = params.timestamp ?? Date.now();
|
|
61
|
+
const expiryWindow = params.expiryWindow ?? DEFAULT_EXPIRY_WINDOW;
|
|
62
|
+
const type = "unbind_agent_wallet";
|
|
63
|
+
const payload = {
|
|
64
|
+
agent_wallet: params.agentWallet,
|
|
65
|
+
};
|
|
66
|
+
const header = { type, timestamp, expiry_window: expiryWindow };
|
|
67
|
+
const wrapped = { ...header, data: payload };
|
|
68
|
+
const sorted = sortJsonKeys(wrapped);
|
|
69
|
+
return {
|
|
70
|
+
canonicalJson: JSON.stringify(sorted),
|
|
71
|
+
header,
|
|
72
|
+
payload,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -2,22 +2,50 @@ import { Keypair } from "@solana/web3.js";
|
|
|
2
2
|
import { PacificaClient, type Network } from "../pacifica/index.js";
|
|
3
3
|
import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
|
|
4
4
|
import type { SolanaSigner } from "../signer/index.js";
|
|
5
|
+
import type { AgentMeta } from "../settings.js";
|
|
5
6
|
export declare class PacificaAdapter implements ExchangeAdapter {
|
|
6
7
|
readonly name = "pacifica";
|
|
7
8
|
readonly chain = "solana";
|
|
8
9
|
readonly aliases: readonly ["pac"];
|
|
9
10
|
private client;
|
|
11
|
+
/** Tier 3: PK direct (constructor-supplied keypair). */
|
|
10
12
|
private _solanaSigner;
|
|
13
|
+
/** Tier 2: OWS master signer (set via setSigner). */
|
|
14
|
+
private _masterSigner?;
|
|
15
|
+
/** Tier 1: agent OWS signer (Phase 2c). */
|
|
16
|
+
private _agentSigner?;
|
|
17
|
+
/** Tier 1 metadata. */
|
|
18
|
+
private _agentMeta?;
|
|
19
|
+
/** --no-agent bypass flag. */
|
|
20
|
+
private _useNoAgent;
|
|
21
|
+
/** True when adapter was constructed without a real PK (read-only). */
|
|
11
22
|
private _hasRealKey;
|
|
23
|
+
/** Public Solana base58 of the active signer (`_resolveSigner().address`). */
|
|
12
24
|
private account;
|
|
25
|
+
/** Bound `signMessage` for the active signer (Phase 2c routes through `_resolveSigner`). */
|
|
13
26
|
private signMessage;
|
|
14
27
|
private _marketsCache;
|
|
15
28
|
private _marketsCacheTime;
|
|
16
29
|
private static readonly CACHE_TTL;
|
|
17
30
|
constructor(keypair: Keypair, network?: Network, builderCode?: string, hasRealKey?: boolean);
|
|
18
31
|
private ensureSigner;
|
|
19
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Inject an external Solana signer (Tier 2 — OWS master).
|
|
34
|
+
* Backward-compatible: existing callers (e.g. `perp -e pacifica --ows ...`
|
|
35
|
+
* in `_initWithOws`) continue to work; the master signer is what
|
|
36
|
+
* `_resolveSigner()` falls back to when no agent is registered.
|
|
37
|
+
*/
|
|
20
38
|
setSigner(signer: SolanaSigner): void;
|
|
39
|
+
/** Tier 1: inject agent OWS Solana signer (Phase 2c). */
|
|
40
|
+
setAgentSigner(meta: AgentMeta, signer: SolanaSigner): void;
|
|
41
|
+
/** Bypass Tier 1 (agent) — used for `--no-agent` flag. */
|
|
42
|
+
setNoAgent(noAgent: boolean): void;
|
|
43
|
+
/** True only when ALL THREE tiers are unconfigured. */
|
|
44
|
+
get isReadOnly(): boolean;
|
|
45
|
+
/** Which signer tier is currently active (for debug/diagnostics). */
|
|
46
|
+
get activeSignerTier(): "agent" | "master" | "pk" | null;
|
|
47
|
+
/** Resolve the active signer across three tiers. Mirrors AsterAdapter._resolveSigner. */
|
|
48
|
+
private _resolveSigner;
|
|
21
49
|
/** Access the underlying Keypair (only available with LocalSolanaSigner). */
|
|
22
50
|
get keypair(): Keypair;
|
|
23
51
|
private _getPrices;
|