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.
Files changed (254) 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/bot/engine.js +23 -4
  21. package/dist/bot/presets.js +3 -3
  22. package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
  23. package/dist/bot/strategies/funding-arb-strategy.js +41 -176
  24. package/dist/bot/strategies/funding-auto.js +2 -2
  25. package/dist/bridge-engine.js +102 -49
  26. package/dist/cli-spec.js +1 -2
  27. package/dist/commands/account.js +2 -500
  28. package/dist/commands/agent.d.ts +8 -2
  29. package/dist/commands/agent.js +1903 -366
  30. package/dist/commands/alerts.js +3 -1
  31. package/dist/commands/arb/index.js +37 -7
  32. package/dist/commands/arb-auto.js +44 -11
  33. package/dist/commands/arb.js +83 -34
  34. package/dist/commands/bot.d.ts +1 -1
  35. package/dist/commands/bot.js +20 -16
  36. package/dist/commands/bridge.d.ts +1 -1
  37. package/dist/commands/bridge.js +4 -4
  38. package/dist/commands/funds.d.ts +1 -1
  39. package/dist/commands/funds.js +18 -68
  40. package/dist/commands/jobs.d.ts +1 -1
  41. package/dist/commands/jobs.js +2 -2
  42. package/dist/commands/manage.d.ts +1 -1
  43. package/dist/commands/manage.js +12 -84
  44. package/dist/commands/plan.d.ts +1 -1
  45. package/dist/commands/plan.js +2 -2
  46. package/dist/commands/portfolio.d.ts +19 -3
  47. package/dist/commands/portfolio.js +747 -163
  48. package/dist/commands/rebalance.d.ts +1 -1
  49. package/dist/commands/rebalance.js +18 -12
  50. package/dist/commands/risk.js +2 -6
  51. package/dist/commands/settings.js +30 -53
  52. package/dist/commands/trade.js +16 -15
  53. package/dist/commands/wallet.d.ts +2 -1
  54. package/dist/commands/wallet.js +1122 -176
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +55 -9
  57. package/dist/errors.d.ts +64 -0
  58. package/dist/errors.js +82 -2
  59. package/dist/event-stream.js +12 -1
  60. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  61. package/dist/exchanges/aster-typed-data.js +189 -0
  62. package/dist/exchanges/aster.d.ts +60 -11
  63. package/dist/exchanges/aster.js +278 -136
  64. package/dist/exchanges/hyperliquid.d.ts +14 -0
  65. package/dist/exchanges/hyperliquid.js +88 -34
  66. package/dist/exchanges/lighter-spot.js +19 -5
  67. package/dist/exchanges/lighter.d.ts +66 -0
  68. package/dist/exchanges/lighter.js +213 -31
  69. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  70. package/dist/exchanges/pacifica-typed-data.js +74 -0
  71. package/dist/exchanges/pacifica.d.ts +29 -1
  72. package/dist/exchanges/pacifica.js +94 -6
  73. package/dist/funding/rates.js +59 -21
  74. package/dist/guardrail/contracts.d.ts +14 -0
  75. package/dist/guardrail/contracts.js +72 -0
  76. package/dist/guardrail/perp-guardrail.d.ts +21 -0
  77. package/dist/guardrail/perp-guardrail.js +145 -0
  78. package/dist/index.d.ts +1 -1
  79. package/dist/index.js +307 -362
  80. package/dist/jobs.js +3 -1
  81. package/dist/mcp-server.js +92 -88
  82. package/dist/position-history.js +47 -4
  83. package/dist/settings.d.ts +69 -3
  84. package/dist/settings.js +3 -2
  85. package/dist/signer/index.d.ts +2 -0
  86. package/dist/signer/index.js +2 -0
  87. package/dist/signer/ows-evm.d.ts +32 -0
  88. package/dist/signer/ows-evm.js +86 -0
  89. package/dist/signer/ows-loader.d.ts +2 -0
  90. package/dist/signer/ows-loader.js +6 -0
  91. package/dist/signer/ows-solana.d.ts +25 -0
  92. package/dist/signer/ows-solana.js +56 -0
  93. package/dist/smart-order.d.ts +8 -2
  94. package/dist/smart-order.js +46 -11
  95. package/dist/strategies/funding-arb.js +29 -8
  96. package/dist/trade-validator.js +30 -5
  97. package/dist/utils.d.ts +10 -0
  98. package/dist/utils.js +19 -0
  99. package/package.json +4 -2
  100. package/skills/perp-cli/SKILL.md +23 -14
  101. package/skills/perp-cli/references/agent-operations.md +6 -4
  102. package/skills/perp-cli/references/commands.md +55 -34
  103. package/skills/perp-cli/scripts/preflight.sh +4 -4
  104. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  105. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  106. package/dist/__tests__/alert-logic.test.js +0 -107
  107. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  108. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  109. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  110. package/dist/__tests__/arb-history-stats.test.js +0 -176
  111. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  112. package/dist/__tests__/arb-logic.test.js +0 -84
  113. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  114. package/dist/__tests__/arb-manage.test.js +0 -253
  115. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  116. package/dist/__tests__/arb-new-features.test.js +0 -457
  117. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  118. package/dist/__tests__/arb-sizing.test.js +0 -48
  119. package/dist/__tests__/arb-state.test.d.ts +0 -1
  120. package/dist/__tests__/arb-state.test.js +0 -284
  121. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  122. package/dist/__tests__/arb-userflow.test.js +0 -945
  123. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  124. package/dist/__tests__/arb-utils.test.js +0 -264
  125. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  126. package/dist/__tests__/bot-conditions.test.js +0 -341
  127. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  128. package/dist/__tests__/client-id-tracker.test.js +0 -137
  129. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  130. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  131. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  132. package/dist/__tests__/commands/order-intent.test.js +0 -600
  133. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  134. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  135. package/dist/__tests__/config.test.d.ts +0 -1
  136. package/dist/__tests__/config.test.js +0 -86
  137. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  138. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  139. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  140. package/dist/__tests__/dex-asset-map.test.js +0 -191
  141. package/dist/__tests__/errors.test.d.ts +0 -1
  142. package/dist/__tests__/errors.test.js +0 -110
  143. package/dist/__tests__/event-stream.test.d.ts +0 -1
  144. package/dist/__tests__/event-stream.test.js +0 -276
  145. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  146. package/dist/__tests__/exchanges/interface.test.js +0 -132
  147. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  148. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  149. package/dist/__tests__/execution-log.test.d.ts +0 -1
  150. package/dist/__tests__/execution-log.test.js +0 -106
  151. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  152. package/dist/__tests__/funding-calc.test.js +0 -71
  153. package/dist/__tests__/funding-history.test.d.ts +0 -1
  154. package/dist/__tests__/funding-history.test.js +0 -343
  155. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  156. package/dist/__tests__/funding-rates.test.js +0 -342
  157. package/dist/__tests__/funding.test.d.ts +0 -1
  158. package/dist/__tests__/funding.test.js +0 -173
  159. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  160. package/dist/__tests__/gap-logic.test.js +0 -43
  161. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  162. package/dist/__tests__/hip3-dex.test.js +0 -234
  163. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  165. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  166. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  167. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  169. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  170. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  171. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  173. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  175. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  177. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  178. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  179. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  180. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  181. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  182. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  183. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  184. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  185. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  186. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  187. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  188. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  189. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  190. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  191. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  192. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  193. package/dist/__tests__/liquidity.test.d.ts +0 -1
  194. package/dist/__tests__/liquidity.test.js +0 -225
  195. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  196. package/dist/__tests__/plan-executor.test.js +0 -314
  197. package/dist/__tests__/position-history.test.d.ts +0 -1
  198. package/dist/__tests__/position-history.test.js +0 -367
  199. package/dist/__tests__/retry.test.d.ts +0 -1
  200. package/dist/__tests__/retry.test.js +0 -310
  201. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  202. package/dist/__tests__/risk-assessment.test.js +0 -288
  203. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  204. package/dist/__tests__/security-adversarial.test.js +0 -574
  205. package/dist/__tests__/strategies.test.d.ts +0 -1
  206. package/dist/__tests__/strategies.test.js +0 -539
  207. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  208. package/dist/__tests__/trade-execution.test.js +0 -129
  209. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  210. package/dist/__tests__/trade-validator.test.js +0 -655
  211. package/dist/__tests__/utils.test.d.ts +0 -1
  212. package/dist/__tests__/utils.test.js +0 -76
  213. package/dist/arb/reconcile.d.ts +0 -28
  214. package/dist/arb/reconcile.js +0 -162
  215. package/dist/commands/alert.d.ts +0 -2
  216. package/dist/commands/alert.js +0 -421
  217. package/dist/commands/analytics.d.ts +0 -3
  218. package/dist/commands/analytics.js +0 -311
  219. package/dist/commands/arb-manage.d.ts +0 -5
  220. package/dist/commands/arb-manage.js +0 -5
  221. package/dist/commands/dashboard.d.ts +0 -3
  222. package/dist/commands/dashboard.js +0 -176
  223. package/dist/commands/deposit.d.ts +0 -4
  224. package/dist/commands/deposit.js +0 -573
  225. package/dist/commands/dex.d.ts +0 -3
  226. package/dist/commands/dex.js +0 -120
  227. package/dist/commands/env.d.ts +0 -2
  228. package/dist/commands/env.js +0 -136
  229. package/dist/commands/funding.d.ts +0 -3
  230. package/dist/commands/funding.js +0 -554
  231. package/dist/commands/gap.d.ts +0 -2
  232. package/dist/commands/gap.js +0 -305
  233. package/dist/commands/health.d.ts +0 -2
  234. package/dist/commands/health.js +0 -67
  235. package/dist/commands/multileg.d.ts +0 -16
  236. package/dist/commands/multileg.js +0 -174
  237. package/dist/commands/pnl.d.ts +0 -12
  238. package/dist/commands/pnl.js +0 -243
  239. package/dist/commands/run.d.ts +0 -3
  240. package/dist/commands/run.js +0 -202
  241. package/dist/commands/stream.d.ts +0 -5
  242. package/dist/commands/stream.js +0 -200
  243. package/dist/commands/withdraw.d.ts +0 -3
  244. package/dist/commands/withdraw.js +0 -187
  245. package/dist/exchanges/edgex.d.ts +0 -62
  246. package/dist/exchanges/edgex.js +0 -552
  247. package/dist/exchanges/nado.d.ts +0 -52
  248. package/dist/exchanges/nado.js +0 -223
  249. package/dist/funding/index.d.ts +0 -3
  250. package/dist/funding/index.js +0 -3
  251. package/dist/retry.d.ts +0 -74
  252. package/dist/retry.js +0 -129
  253. package/dist/ws/market-feed.d.ts +0 -74
  254. package/dist/ws/market-feed.js +0 -227
@@ -1,6 +1,7 @@
1
1
  import { Hyperliquid } from "hyperliquid";
2
2
  import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
3
3
  import type { EvmSigner } from "../signer/index.js";
4
+ import type { AgentMeta } from "../settings.js";
4
5
  export declare class HyperliquidAdapter implements ExchangeAdapter {
5
6
  readonly name = "hyperliquid";
6
7
  readonly chain = "evm";
@@ -11,6 +12,9 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
11
12
  private _privateKey;
12
13
  private _testnet;
13
14
  private _evmSigner?;
15
+ private _agentSigner?;
16
+ private _agentMeta?;
17
+ private _useNoAgent;
14
18
  private _assetMap;
15
19
  private _assetMapReverse;
16
20
  private _szDecimalsMap;
@@ -29,6 +33,16 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
29
33
  get isTestnet(): boolean;
30
34
  /** Inject an external EVM signer. Call before init() to skip LocalEvmSigner creation. */
31
35
  setSigner(signer: EvmSigner): void;
36
+ /** Tier 1: inject agent OWS wallet signer. */
37
+ setAgentSigner(meta: AgentMeta, signer: EvmSigner): void;
38
+ /** Bypass Tier 1 (agent) — use for --no-agent flag. */
39
+ setNoAgent(noAgent: boolean): void;
40
+ /** True only when ALL THREE tiers are unconfigured. */
41
+ get isReadOnly(): boolean;
42
+ /** Which signer tier is currently active (for debug/diagnostics). */
43
+ get activeSignerTier(): "agent" | "master" | "pk" | null;
44
+ /** Resolve the active signer across three tiers. */
45
+ private _resolveSigner;
32
46
  init(): Promise<void>;
33
47
  /** Load asset index map — supports native and HIP-3 dex. */
34
48
  private _loadAssetMap;
@@ -1,5 +1,6 @@
1
1
  import { Hyperliquid } from "hyperliquid";
2
2
  import { LocalEvmSigner } from "../signer/index.js";
3
+ import { PerpError } from "../errors.js";
3
4
  export class HyperliquidAdapter {
4
5
  name = "hyperliquid";
5
6
  chain = "evm";
@@ -9,7 +10,10 @@ export class HyperliquidAdapter {
9
10
  _address;
10
11
  _privateKey;
11
12
  _testnet;
12
- _evmSigner;
13
+ _evmSigner; // Tier 2: OWS master
14
+ _agentSigner; // Tier 1: agent OWS wallet
15
+ _agentMeta; // Tier 1 metadata
16
+ _useNoAgent = false; // --no-agent bypass flag
13
17
  _assetMap = new Map();
14
18
  _assetMapReverse = new Map();
15
19
  _szDecimalsMap = new Map(); // symbol → szDecimals
@@ -55,6 +59,65 @@ export class HyperliquidAdapter {
55
59
  /** Inject an external EVM signer. Call before init() to skip LocalEvmSigner creation. */
56
60
  setSigner(signer) {
57
61
  this._evmSigner = signer;
62
+ this._address = signer.getAddress();
63
+ }
64
+ /** Tier 1: inject agent OWS wallet signer. */
65
+ setAgentSigner(meta, signer) {
66
+ this._agentMeta = meta;
67
+ this._agentSigner = signer;
68
+ }
69
+ /** Bypass Tier 1 (agent) — use for --no-agent flag. */
70
+ setNoAgent(noAgent) {
71
+ this._useNoAgent = noAgent;
72
+ }
73
+ /** True only when ALL THREE tiers are unconfigured. */
74
+ get isReadOnly() {
75
+ return !this._agentSigner && !this._evmSigner && !this._privateKey;
76
+ }
77
+ /** Which signer tier is currently active (for debug/diagnostics). */
78
+ get activeSignerTier() {
79
+ try {
80
+ return this._resolveSigner().tier;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ /** Resolve the active signer across three tiers. */
87
+ _resolveSigner() {
88
+ // Tier 1: agent (when registered, not expired, --no-agent NOT set)
89
+ if (!this._useNoAgent && this._agentSigner && this._agentMeta) {
90
+ const expiresAt = new Date(this._agentMeta.expiresAt).getTime();
91
+ const expired = expiresAt < Date.now();
92
+ if (expired) {
93
+ // Skip Tier 1 only if Tier 2/3 available; otherwise throw AGENT_EXPIRED
94
+ if (!this._evmSigner && !this._privateKey) {
95
+ throw new PerpError("AGENT_EXPIRED", "Agent wallet has expired", {
96
+ remediation: "perp wallet agent approve hyperliquid --rotate",
97
+ });
98
+ }
99
+ // fall through to Tier 2/3
100
+ }
101
+ else {
102
+ return { tier: "agent", signer: this._agentSigner };
103
+ }
104
+ }
105
+ // Tier 2: OWS master (_evmSigner set via setSigner)
106
+ if (this._evmSigner) {
107
+ return { tier: "master", signer: this._evmSigner };
108
+ }
109
+ // Tier 3: PK direct (_privateKey → _evmSigner created in init())
110
+ // After init(), PK is reflected in _evmSigner. But if init() hasn't run
111
+ // yet, _privateKey is non-empty. Either way this path is reachable only
112
+ // if _evmSigner is set, so Tier 2 catches it. This guard is for the edge
113
+ // case where init() failed but _privateKey is set — throw a clear error.
114
+ if (this._privateKey) {
115
+ throw new PerpError("NO_SIGNER_AVAILABLE", "Hyperliquid adapter has a private key but init() has not been called", { remediation: "Call adapter.init() before using mutating methods" });
116
+ }
117
+ // No signer at any tier
118
+ throw new PerpError("NO_SIGNER_AVAILABLE", "No signing path configured for Hyperliquid", {
119
+ remediation: "Run one of: (a) perp wallet agent approve hyperliquid --master <wallet>; (b) perp wallet generate && perp -e hyperliquid --ows <wallet>; (c) export PRIVATE_KEY=0x...",
120
+ });
58
121
  }
59
122
  async init() {
60
123
  // Suppress "WebSocket connected" noise from hyperliquid SDK
@@ -71,6 +134,9 @@ export class HyperliquidAdapter {
71
134
  this._evmSigner = await LocalEvmSigner.create(this._privateKey);
72
135
  this._address = this._evmSigner.getAddress();
73
136
  }
137
+ else if (this._evmSigner && !this._address) {
138
+ this._address = this._evmSigner.getAddress();
139
+ }
74
140
  // Build asset index map
75
141
  await this._loadAssetMap();
76
142
  }
@@ -479,9 +545,8 @@ export class HyperliquidAdapter {
479
545
  * Bypasses the SDK entirely — works for order, batchModify, twapOrder, etc.
480
546
  */
481
547
  ensureSigner() {
482
- if (!this._evmSigner) {
483
- throw new Error("No private key configured. Run: perp setup");
484
- }
548
+ // Resolves across all tiers; throws PerpError if none available
549
+ this._resolveSigner();
485
550
  }
486
551
  /**
487
552
  * Normalize trailing zeros from price ('p') and size ('s') string fields
@@ -518,7 +583,7 @@ export class HyperliquidAdapter {
518
583
  return result;
519
584
  }
520
585
  async _signAndSendAction(action) {
521
- this.ensureSigner();
586
+ const { signer } = this._resolveSigner();
522
587
  const { encode } = await import("@msgpack/msgpack");
523
588
  const { ethers, keccak256 } = await import("ethers");
524
589
  const isMainnet = !this._testnet;
@@ -557,7 +622,7 @@ export class HyperliquidAdapter {
557
622
  source: isMainnet ? "a" : "b",
558
623
  connectionId: hash,
559
624
  };
560
- const sig = await this._evmSigner.signTypedData(phantomDomain, agentTypes, phantomAgent);
625
+ const sig = await signer.signTypedData(phantomDomain, agentTypes, phantomAgent);
561
626
  const parsed = ethers.Signature.from(sig);
562
627
  const payload = {
563
628
  action,
@@ -635,21 +700,15 @@ export class HyperliquidAdapter {
635
700
  }
636
701
  async cancelOrder(symbol, orderId) {
637
702
  this.ensureSigner();
638
- if (this._dex) {
639
- // HIP-3 dex: use raw cancel action with global asset index
640
- const assetIndex = await this.getAssetIndex(symbol.toUpperCase());
641
- const result = await this._signAndSendAction({
642
- type: "cancel",
643
- cancels: [{ a: assetIndex, o: parseInt(orderId) }],
644
- });
645
- await this._invalidateAccountCache();
646
- return result;
647
- }
648
- // HL SDK expects coin in "ETH-PERP" format (internal symbol convention)
649
- const resolved = this.resolveSymbol(symbol);
650
- const result = await this.sdk.exchange.cancelOrder({
651
- coin: resolved,
652
- o: parseInt(orderId),
703
+ // HL SDK declares exchange.cancelOrder in types but the runtime ExchangeAPI
704
+ // instance (when constructed with our wallet/agent injection path) does not
705
+ // expose it as a callable method. Use raw cancel action via _signAndSendAction
706
+ // instead — same approach as the HIP-3 dex branch — which works uniformly for
707
+ // standard perps and HIP-3 dexes.
708
+ const assetIndex = await this.getAssetIndex(symbol.toUpperCase());
709
+ const result = await this._signAndSendAction({
710
+ type: "cancel",
711
+ cancels: [{ a: assetIndex, o: parseInt(orderId) }],
653
712
  });
654
713
  await this._invalidateAccountCache();
655
714
  return result;
@@ -663,21 +722,16 @@ export class HyperliquidAdapter {
663
722
  return norm(o.symbol) === norm(symbol);
664
723
  })
665
724
  : orders;
725
+ // Use _signAndSendAction uniformly for both standard perps and HIP-3 dexes.
726
+ // SDK exchange.cancelOrder has the same runtime unavailability as single
727
+ // cancelOrder (see comment there); raw cancel action works for both paths.
666
728
  const results = [];
667
729
  for (const o of toCancel) {
668
- if (this._dex) {
669
- const assetIndex = await this.getAssetIndex(o.symbol);
670
- results.push(await this._signAndSendAction({
671
- type: "cancel",
672
- cancels: [{ a: assetIndex, o: parseInt(o.orderId) }],
673
- }));
674
- }
675
- else {
676
- results.push(await this.sdk.exchange.cancelOrder({
677
- coin: o.symbol,
678
- o: parseInt(o.orderId),
679
- }));
680
- }
730
+ const assetIndex = await this.getAssetIndex(o.symbol);
731
+ results.push(await this._signAndSendAction({
732
+ type: "cancel",
733
+ cancels: [{ a: assetIndex, o: parseInt(o.orderId) }],
734
+ }));
681
735
  }
682
736
  return results;
683
737
  }
@@ -458,7 +458,7 @@ export class LighterSpotAdapter {
458
458
  // ── Private helpers ──
459
459
  async _placeOrder(opts) {
460
460
  if (this._lt.isReadOnly) {
461
- throw new Error("Spot trading requires a Lighter API key. Run `perp -e lighter manage setup-api-key` first.");
461
+ throw new Error("Spot trading requires a Lighter API key. Run `perp wallet agent approve lighter` to register an agent.");
462
462
  }
463
463
  const nonce = await this._getNextNonce();
464
464
  const signer = this._lt.signer;
@@ -501,19 +501,30 @@ export class LighterSpotAdapter {
501
501
  account_index: String(this._lt.accountIndex),
502
502
  api_key_index: String(apiKeyIndex),
503
503
  });
504
- return res.nonce ?? res.next_nonce ?? 0;
504
+ const nonce = res.nonce ?? res.next_nonce;
505
+ // SSOT rule #2: refuse to silently default to nonce 0. A stale or missing
506
+ // nonce will cause Lighter to reject the tx with cryptic errors and the
507
+ // user has no way to diagnose the upstream failure.
508
+ if (nonce === undefined || !Number.isFinite(nonce) || nonce < 0) {
509
+ throw new Error(`Lighter /nextNonce returned no valid nonce for account_index=${this._lt.accountIndex} ` +
510
+ `api_key_index=${apiKeyIndex}; raw response=${JSON.stringify(res)}`);
511
+ }
512
+ return nonce;
505
513
  }
506
514
  async _sendTx(signed) {
507
515
  if (signed.error)
508
516
  throw new Error(`Signer error: ${signed.error}`);
509
517
  if (!signed.txInfo)
510
518
  throw new Error("Signer returned empty txInfo");
519
+ if (signed.txType === undefined || !Number.isFinite(signed.txType)) {
520
+ throw new Error(`Lighter signer returned no txType (received ${signed.txType}); refusing to send with default 0.`);
521
+ }
511
522
  const baseUrl = this._lt._baseUrl;
512
523
  const res = await fetch(`${baseUrl}/api/v1/sendTx`, {
513
524
  method: "POST",
514
525
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
515
526
  body: new URLSearchParams({
516
- tx_type: String(signed.txType ?? 0),
527
+ tx_type: String(signed.txType),
517
528
  tx_info: signed.txInfo,
518
529
  }),
519
530
  });
@@ -528,8 +539,11 @@ export class LighterSpotAdapter {
528
539
  for (const [k, v] of Object.entries(params))
529
540
  url.searchParams.set(k, v);
530
541
  const res = await fetch(url.toString());
531
- if (!res.ok)
532
- throw new Error(`GET ${path} failed (${res.status}): ${await res.text()}`);
542
+ if (!res.ok) {
543
+ const text = await res.text();
544
+ const clean = text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().slice(0, 120);
545
+ throw new Error(`GET ${path} failed (${res.status}): ${clean}`);
546
+ }
533
547
  return res.json();
534
548
  }
535
549
  async _restGetAuth(path, params) {
@@ -1,6 +1,27 @@
1
1
  import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
2
2
  import type { EvmSigner } from "../signer/index.js";
3
+ import type { AgentMeta } from "../settings.js";
3
4
  import type { WasmSignerClient as WasmSignerClientType } from "lighter-ts-sdk";
5
+ /** Lighter trigger orderType enum (matches WASM signer). */
6
+ export declare const LIGHTER_ORDER_TYPE: {
7
+ readonly STOP_LOSS: 2;
8
+ readonly STOP_LOSS_LIMIT: 3;
9
+ readonly TAKE_PROFIT: 4;
10
+ readonly TAKE_PROFIT_LIMIT: 5;
11
+ };
12
+ export type LighterTriggerOrderType = typeof LIGHTER_ORDER_TYPE.STOP_LOSS | typeof LIGHTER_ORDER_TYPE.STOP_LOSS_LIMIT | typeof LIGHTER_ORDER_TYPE.TAKE_PROFIT | typeof LIGHTER_ORDER_TYPE.TAKE_PROFIT_LIMIT;
13
+ /**
14
+ * Pick Lighter trigger orderType from side, trigger price, and current mark.
15
+ *
16
+ * STOP_LOSS protects against adverse moves; TAKE_PROFIT locks in favorable moves.
17
+ * buy + trigger >= mark → STOP_LOSS (close short on rise / breakout entry)
18
+ * buy + trigger < mark → TAKE_PROFIT (close short on fall)
19
+ * sell + trigger <= mark → STOP_LOSS (close long on fall)
20
+ * sell + trigger > mark → TAKE_PROFIT (close long on rise)
21
+ *
22
+ * Tied trigger==mark is classified as STOP_LOSS — the protective default.
23
+ */
24
+ export declare function classifyTriggerOrderType(side: "buy" | "sell", triggerPrice: number, markPrice: number, isMarket: boolean): LighterTriggerOrderType;
4
25
  export declare class LighterAdapter implements ExchangeAdapter {
5
26
  readonly name = "lighter";
6
27
  readonly chain = "ethereum";
@@ -23,6 +44,15 @@ export declare class LighterAdapter implements ExchangeAdapter {
23
44
  private _marketsCache;
24
45
  private _marketsCacheTime;
25
46
  private static readonly CACHE_TTL;
47
+ /** Tier 1 metadata (Phase 2d). When set + not expired + !_useNoAgent, agent
48
+ * apiKeyIndex/publicKey/accountIndex take priority over master/PK env. */
49
+ private _agentMeta?;
50
+ /** Tier 1 secp256k1 hex private key (40 bytes) for the agent slot. Lighter
51
+ * has no OWS curve binding for raw L2 keys; this is held in-memory only when
52
+ * passed by the caller (e.g. immediately after `setupApiKey()` or via env). */
53
+ private _agentApiKey?;
54
+ /** --no-agent bypass flag. */
55
+ private _useNoAgent;
26
56
  /**
27
57
  * @param evmKey EVM private key (0x-prefixed, 32 bytes) — for deposits & key registration
28
58
  * @param testnet Use testnet (chain ID 300) instead of mainnet (chain ID 304)
@@ -31,6 +61,7 @@ export declare class LighterAdapter implements ExchangeAdapter {
31
61
  constructor(evmKey: string, testnet?: boolean, opts?: {
32
62
  apiKey?: string;
33
63
  accountIndex?: number;
64
+ apiKeyIndex?: number;
34
65
  });
35
66
  get signer(): {
36
67
  createAuthToken(deadline: number): Promise<{
@@ -43,6 +74,41 @@ export declare class LighterAdapter implements ExchangeAdapter {
43
74
  get isReadOnly(): boolean;
44
75
  /** Inject an external EVM signer. Call before init() to skip LocalEvmSigner creation. */
45
76
  setSigner(signer: EvmSigner): void;
77
+ /**
78
+ * Tier 1: inject a Lighter agent's slot+L2 key (Phase 2d).
79
+ *
80
+ * Caller supplies the AgentMeta (settings.agents.lighter[name]) plus the
81
+ * 40-byte L2 private key (hex). Adapter swaps `_apiKey`/`_apiKeyIndex` to
82
+ * the agent's slot/key so all subsequent `signCreateOrder`/`signCancelOrder`
83
+ * etc. flow through the agent identity.
84
+ *
85
+ * NOTE: when the agent path is active, `_address` (master EVM) is left
86
+ * unchanged because Lighter REST queries (account, balance, positions) use
87
+ * `account_index` not address. The L1 EVM identity is irrelevant for the
88
+ * hot-path; only the L2 secp256k1 key is consulted.
89
+ */
90
+ setAgentSigner(meta: AgentMeta, agentApiKey: string): void;
91
+ /** Bypass Tier 1 (agent) — used for `--no-agent` flag. */
92
+ setNoAgent(noAgent: boolean): void;
93
+ /** Which signer tier is currently active (for debug/diagnostics). */
94
+ get activeSignerTier(): "agent" | "master" | "pk" | null;
95
+ /**
96
+ * Resolve the active signer tier across three tiers (Phase 2d).
97
+ *
98
+ * Tier 1 (agent): active when AgentMeta + agent L2 key set, not expired,
99
+ * and `--no-agent` NOT set. The actual swap of `_apiKeyIndex`/`_apiKey`
100
+ * happens in `init()` (or via `_activateAgentTier()` post-init).
101
+ * Tier 2 (master): active when an OWS master EVM signer was injected via
102
+ * `setSigner()`. Used for the L1 ChangePubKey path (auto-setup at slot 4).
103
+ * Tier 3 (pk): active when constructor was supplied an evmKey. Same role
104
+ * as Tier 2 — signs L1 ChangePubKey for auto-setup.
105
+ *
106
+ * Lighter is unique among the 4 DEXs in that the actual order-signing is
107
+ * always done by the L2 secp256k1 key (`_apiKey`), regardless of tier. The
108
+ * tiers differ in WHICH L2 key is loaded: Tier 1 uses the agent's slot/key
109
+ * from AgentMeta; Tier 2/3 use whatever the env/auto-setup produced.
110
+ */
111
+ private _resolveSigner;
46
112
  init(): Promise<void>;
47
113
  /** Populate marketMap + decimals from /orderBooks API (safe to call multiple times) */
48
114
  private _refreshMarketMap;