perp-cli 0.11.0 → 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.
Files changed (240) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bridge-engine.js +102 -49
  21. package/dist/cli-spec.js +1 -2
  22. package/dist/commands/account.js +2 -500
  23. package/dist/commands/agent.d.ts +8 -2
  24. package/dist/commands/agent.js +1903 -366
  25. package/dist/commands/alerts.js +3 -1
  26. package/dist/commands/arb/index.js +37 -7
  27. package/dist/commands/arb-auto.js +44 -11
  28. package/dist/commands/arb.js +83 -34
  29. package/dist/commands/bot.d.ts +1 -1
  30. package/dist/commands/bot.js +18 -14
  31. package/dist/commands/bridge.d.ts +1 -1
  32. package/dist/commands/bridge.js +4 -4
  33. package/dist/commands/funds.d.ts +1 -1
  34. package/dist/commands/funds.js +18 -68
  35. package/dist/commands/jobs.d.ts +1 -1
  36. package/dist/commands/jobs.js +2 -2
  37. package/dist/commands/manage.d.ts +1 -1
  38. package/dist/commands/manage.js +12 -84
  39. package/dist/commands/plan.d.ts +1 -1
  40. package/dist/commands/plan.js +2 -2
  41. package/dist/commands/portfolio.d.ts +19 -3
  42. package/dist/commands/portfolio.js +747 -163
  43. package/dist/commands/rebalance.d.ts +1 -1
  44. package/dist/commands/rebalance.js +18 -12
  45. package/dist/commands/risk.js +2 -6
  46. package/dist/commands/settings.js +30 -53
  47. package/dist/commands/trade.js +16 -15
  48. package/dist/commands/wallet.d.ts +2 -1
  49. package/dist/commands/wallet.js +756 -28
  50. package/dist/errors.d.ts +64 -0
  51. package/dist/errors.js +82 -2
  52. package/dist/event-stream.js +12 -1
  53. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  54. package/dist/exchanges/aster-typed-data.js +189 -0
  55. package/dist/exchanges/aster.d.ts +60 -11
  56. package/dist/exchanges/aster.js +278 -136
  57. package/dist/exchanges/hyperliquid.d.ts +14 -0
  58. package/dist/exchanges/hyperliquid.js +84 -34
  59. package/dist/exchanges/lighter-spot.js +14 -3
  60. package/dist/exchanges/lighter.d.ts +66 -0
  61. package/dist/exchanges/lighter.js +199 -27
  62. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  63. package/dist/exchanges/pacifica-typed-data.js +74 -0
  64. package/dist/exchanges/pacifica.d.ts +29 -1
  65. package/dist/exchanges/pacifica.js +94 -6
  66. package/dist/funding/rates.js +59 -21
  67. package/dist/guardrail/perp-guardrail.js +9 -1
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +255 -360
  70. package/dist/jobs.js +3 -1
  71. package/dist/mcp-server.js +85 -83
  72. package/dist/position-history.js +47 -4
  73. package/dist/settings.d.ts +67 -3
  74. package/dist/settings.js +1 -2
  75. package/dist/signer/ows-evm.d.ts +14 -0
  76. package/dist/signer/ows-evm.js +28 -9
  77. package/dist/smart-order.d.ts +8 -2
  78. package/dist/smart-order.js +46 -11
  79. package/dist/strategies/funding-arb.js +29 -8
  80. package/dist/trade-validator.js +30 -5
  81. package/dist/utils.d.ts +10 -0
  82. package/dist/utils.js +19 -0
  83. package/package.json +2 -2
  84. package/skills/perp-cli/SKILL.md +23 -14
  85. package/skills/perp-cli/references/agent-operations.md +6 -4
  86. package/skills/perp-cli/references/commands.md +55 -34
  87. package/skills/perp-cli/scripts/preflight.sh +4 -4
  88. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  89. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  90. package/dist/__tests__/alert-logic.test.js +0 -107
  91. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  92. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  93. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  94. package/dist/__tests__/arb-history-stats.test.js +0 -176
  95. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  96. package/dist/__tests__/arb-logic.test.js +0 -84
  97. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  98. package/dist/__tests__/arb-manage.test.js +0 -253
  99. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  100. package/dist/__tests__/arb-new-features.test.js +0 -457
  101. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  102. package/dist/__tests__/arb-sizing.test.js +0 -48
  103. package/dist/__tests__/arb-state.test.d.ts +0 -1
  104. package/dist/__tests__/arb-state.test.js +0 -284
  105. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  106. package/dist/__tests__/arb-userflow.test.js +0 -945
  107. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  108. package/dist/__tests__/arb-utils.test.js +0 -264
  109. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  110. package/dist/__tests__/bot-conditions.test.js +0 -341
  111. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  112. package/dist/__tests__/client-id-tracker.test.js +0 -137
  113. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  114. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  115. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  116. package/dist/__tests__/commands/order-intent.test.js +0 -600
  117. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  118. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  119. package/dist/__tests__/config.test.d.ts +0 -1
  120. package/dist/__tests__/config.test.js +0 -86
  121. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  122. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  123. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  124. package/dist/__tests__/dex-asset-map.test.js +0 -191
  125. package/dist/__tests__/errors.test.d.ts +0 -1
  126. package/dist/__tests__/errors.test.js +0 -110
  127. package/dist/__tests__/event-stream.test.d.ts +0 -1
  128. package/dist/__tests__/event-stream.test.js +0 -276
  129. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  130. package/dist/__tests__/exchanges/interface.test.js +0 -132
  131. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  132. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  133. package/dist/__tests__/execution-log.test.d.ts +0 -1
  134. package/dist/__tests__/execution-log.test.js +0 -106
  135. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  136. package/dist/__tests__/funding-calc.test.js +0 -71
  137. package/dist/__tests__/funding-history.test.d.ts +0 -1
  138. package/dist/__tests__/funding-history.test.js +0 -343
  139. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  140. package/dist/__tests__/funding-rates.test.js +0 -342
  141. package/dist/__tests__/funding.test.d.ts +0 -1
  142. package/dist/__tests__/funding.test.js +0 -173
  143. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  144. package/dist/__tests__/gap-logic.test.js +0 -43
  145. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  146. package/dist/__tests__/hip3-dex.test.js +0 -234
  147. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  148. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  149. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  150. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  151. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  152. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  153. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  154. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  155. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  156. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  157. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  158. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  159. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  160. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  161. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  162. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  163. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  165. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  166. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  167. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  169. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  170. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  171. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  173. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  175. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  177. package/dist/__tests__/liquidity.test.d.ts +0 -1
  178. package/dist/__tests__/liquidity.test.js +0 -225
  179. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  180. package/dist/__tests__/plan-executor.test.js +0 -314
  181. package/dist/__tests__/position-history.test.d.ts +0 -1
  182. package/dist/__tests__/position-history.test.js +0 -367
  183. package/dist/__tests__/retry.test.d.ts +0 -1
  184. package/dist/__tests__/retry.test.js +0 -310
  185. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  186. package/dist/__tests__/risk-assessment.test.js +0 -288
  187. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  188. package/dist/__tests__/security-adversarial.test.js +0 -574
  189. package/dist/__tests__/strategies.test.d.ts +0 -1
  190. package/dist/__tests__/strategies.test.js +0 -539
  191. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  192. package/dist/__tests__/trade-execution.test.js +0 -129
  193. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  194. package/dist/__tests__/trade-validator.test.js +0 -655
  195. package/dist/__tests__/utils.test.d.ts +0 -1
  196. package/dist/__tests__/utils.test.js +0 -76
  197. package/dist/arb/reconcile.d.ts +0 -28
  198. package/dist/arb/reconcile.js +0 -162
  199. package/dist/commands/alert.d.ts +0 -2
  200. package/dist/commands/alert.js +0 -421
  201. package/dist/commands/analytics.d.ts +0 -3
  202. package/dist/commands/analytics.js +0 -311
  203. package/dist/commands/arb-manage.d.ts +0 -5
  204. package/dist/commands/arb-manage.js +0 -5
  205. package/dist/commands/dashboard.d.ts +0 -3
  206. package/dist/commands/dashboard.js +0 -176
  207. package/dist/commands/deposit.d.ts +0 -4
  208. package/dist/commands/deposit.js +0 -573
  209. package/dist/commands/dex.d.ts +0 -3
  210. package/dist/commands/dex.js +0 -120
  211. package/dist/commands/env.d.ts +0 -2
  212. package/dist/commands/env.js +0 -136
  213. package/dist/commands/funding.d.ts +0 -3
  214. package/dist/commands/funding.js +0 -554
  215. package/dist/commands/gap.d.ts +0 -2
  216. package/dist/commands/gap.js +0 -305
  217. package/dist/commands/health.d.ts +0 -2
  218. package/dist/commands/health.js +0 -67
  219. package/dist/commands/multileg.d.ts +0 -16
  220. package/dist/commands/multileg.js +0 -174
  221. package/dist/commands/ows.d.ts +0 -2
  222. package/dist/commands/ows.js +0 -887
  223. package/dist/commands/pnl.d.ts +0 -12
  224. package/dist/commands/pnl.js +0 -243
  225. package/dist/commands/run.d.ts +0 -3
  226. package/dist/commands/run.js +0 -202
  227. package/dist/commands/stream.d.ts +0 -5
  228. package/dist/commands/stream.js +0 -200
  229. package/dist/commands/withdraw.d.ts +0 -3
  230. package/dist/commands/withdraw.js +0 -187
  231. package/dist/exchanges/edgex.d.ts +0 -62
  232. package/dist/exchanges/edgex.js +0 -552
  233. package/dist/exchanges/nado.d.ts +0 -52
  234. package/dist/exchanges/nado.js +0 -223
  235. package/dist/funding/index.d.ts +0 -3
  236. package/dist/funding/index.js +0 -3
  237. package/dist/retry.d.ts +0 -74
  238. package/dist/retry.js +0 -129
  239. package/dist/ws/market-feed.d.ts +0 -74
  240. 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
- this._apiKey = opts?.apiKey || process.env.LIGHTER_API_KEY || "";
32
- this._accountIndexInit = opts?.accountIndex ?? parseInt(process.env.LIGHTER_ACCOUNT_INDEX || "-1");
33
- this._apiKeyIndex = parseInt(process.env.LIGHTER_API_KEY_INDEX || "4");
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
- // Save to .env for future use
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 { setEnvVar } = await import("../commands/init.js");
124
- setEnvVar("LIGHTER_API_KEY", apiKey);
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 — env save may fail in some contexts */ }
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 -e lighter manage setup-api-key' to retry.`);
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). Check LIGHTER_API_KEY in ~/.perp/.env or run 'perp -e lighter manage setup-api-key' to regenerate.`);
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 set LIGHTER_ACCOUNT_INDEX.`);
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: fr?.markPrice ?? String(d.last_trade_price ?? 0),
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
- // If limitPrice given → stop-limit (type 0, GTT), else stop-market (type 1, IOC)
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: isMarket ? 1 : 0,
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: isMarket ? 0 : -1,
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
- return res.nonce ?? res.next_nonce ?? 0;
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 ?? 0),
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 -e lighter manage setup-api-key` first, " +
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 or set LIGHTER_ACCOUNT_INDEX.");
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 ?? 0;
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 ?? 0),
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 freshNonce = (freshNonceRes.nonce ?? freshNonceRes.next_nonce ?? nonce) + attempt;
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 ?? 0),
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
- /** Inject an external Solana signer. */
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;