perp-cli 0.6.4 → 0.7.2

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 (181) hide show
  1. package/README.md +7 -2
  2. package/dist/api/public/hyperliquid.d.ts +2 -2
  3. package/dist/api/public/hyperliquid.js +0 -10
  4. package/dist/api/public/lighter.d.ts +2 -1
  5. package/dist/api/public/pacifica.d.ts +2 -1
  6. package/dist/arb/index.d.ts +7 -4
  7. package/dist/arb/index.js +5 -4
  8. package/dist/arb/sizing.d.ts +1 -1
  9. package/dist/arb/state.d.ts +1 -1
  10. package/dist/arb/state.js +4 -3
  11. package/dist/arb/utils.d.ts +0 -10
  12. package/dist/arb/utils.js +0 -42
  13. package/dist/bot/apex/guard.d.ts +26 -0
  14. package/dist/bot/apex/guard.js +86 -0
  15. package/dist/bot/apex/index.d.ts +6 -0
  16. package/dist/bot/apex/index.js +4 -0
  17. package/dist/bot/apex/pulse.d.ts +22 -0
  18. package/dist/bot/apex/pulse.js +107 -0
  19. package/dist/bot/apex/radar.d.ts +17 -0
  20. package/dist/bot/apex/radar.js +150 -0
  21. package/dist/bot/conditions.d.ts +1 -1
  22. package/dist/bot/config.d.ts +6 -1
  23. package/dist/bot/config.js +2 -2
  24. package/dist/bot/engine.d.ts +23 -2
  25. package/dist/bot/engine.js +331 -351
  26. package/dist/bot/index.d.ts +16 -0
  27. package/dist/bot/index.js +10 -0
  28. package/dist/bot/indicators.d.ts +29 -0
  29. package/dist/bot/indicators.js +144 -0
  30. package/dist/bot/reflect.d.ts +34 -0
  31. package/dist/bot/reflect.js +155 -0
  32. package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
  33. package/dist/bot/strategies/aggressive-taker.js +125 -0
  34. package/dist/bot/strategies/apex-strategy.d.ts +36 -0
  35. package/dist/bot/strategies/apex-strategy.js +185 -0
  36. package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
  37. package/dist/bot/strategies/avellaneda-mm.js +111 -0
  38. package/dist/bot/strategies/basis-arb.d.ts +27 -0
  39. package/dist/bot/strategies/basis-arb.js +97 -0
  40. package/dist/bot/strategies/claude-agent.d.ts +31 -0
  41. package/dist/bot/strategies/claude-agent.js +155 -0
  42. package/dist/bot/strategies/dca-strategy.d.ts +25 -0
  43. package/dist/bot/strategies/dca-strategy.js +59 -0
  44. package/dist/bot/strategies/engine-mm.d.ts +29 -0
  45. package/dist/bot/strategies/engine-mm.js +141 -0
  46. package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
  47. package/dist/bot/strategies/funding-arb-strategy.js +168 -0
  48. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  49. package/dist/bot/strategies/funding-arb-v2.js +222 -0
  50. package/dist/bot/strategies/funding-auto.d.ts +1 -0
  51. package/dist/bot/strategies/funding-auto.js +572 -0
  52. package/dist/bot/strategies/grid-mm.d.ts +25 -0
  53. package/dist/bot/strategies/grid-mm.js +99 -0
  54. package/dist/bot/strategies/grid-strategy.d.ts +39 -0
  55. package/dist/bot/strategies/grid-strategy.js +176 -0
  56. package/dist/bot/strategies/hedge-agent.d.ts +25 -0
  57. package/dist/bot/strategies/hedge-agent.js +79 -0
  58. package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
  59. package/dist/bot/strategies/liquidation-mm.js +108 -0
  60. package/dist/bot/strategies/mean-reversion.d.ts +35 -0
  61. package/dist/bot/strategies/mean-reversion.js +118 -0
  62. package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
  63. package/dist/bot/strategies/momentum-breakout.js +159 -0
  64. package/dist/bot/strategies/regime-mm.d.ts +25 -0
  65. package/dist/bot/strategies/regime-mm.js +111 -0
  66. package/dist/bot/strategies/rfq-agent.d.ts +25 -0
  67. package/dist/bot/strategies/rfq-agent.js +71 -0
  68. package/dist/bot/strategies/simple-mm.d.ts +25 -0
  69. package/dist/bot/strategies/simple-mm.js +76 -0
  70. package/dist/bot/strategy-registry.d.ts +9 -0
  71. package/dist/bot/strategy-registry.js +14 -0
  72. package/dist/bot/strategy-types.d.ts +64 -0
  73. package/dist/bot/strategy-types.js +5 -0
  74. package/dist/bot/trade-journal.d.ts +27 -0
  75. package/dist/bot/trade-journal.js +53 -0
  76. package/dist/bot/tui/BotDashboard.d.ts +56 -0
  77. package/dist/bot/tui/BotDashboard.js +94 -0
  78. package/dist/bot/tui/index.d.ts +6 -0
  79. package/dist/bot/tui/index.js +8 -0
  80. package/dist/bridge-engine.d.ts +0 -1
  81. package/dist/bridge-engine.js +0 -1
  82. package/dist/cache.d.ts +0 -1
  83. package/dist/cache.js +0 -1
  84. package/dist/commands/account.d.ts +1 -1
  85. package/dist/commands/account.js +94 -91
  86. package/dist/commands/agent.d.ts +1 -1
  87. package/dist/commands/arb/index.d.ts +1 -1
  88. package/dist/commands/arb/index.js +1 -1
  89. package/dist/commands/arb-auto.d.ts +1 -1
  90. package/dist/commands/arb-auto.js +5 -3
  91. package/dist/commands/arb.d.ts +2 -1
  92. package/dist/commands/arb.js +2 -0
  93. package/dist/commands/bot.d.ts +1 -1
  94. package/dist/commands/bot.js +236 -15
  95. package/dist/commands/bridge.js +1 -1
  96. package/dist/commands/dashboard.d.ts +1 -1
  97. package/dist/commands/dashboard.js +1 -1
  98. package/dist/commands/funds.d.ts +1 -1
  99. package/dist/commands/funds.js +34 -21
  100. package/dist/commands/history.d.ts +1 -1
  101. package/dist/commands/history.js +10 -5
  102. package/dist/commands/manage.d.ts +2 -3
  103. package/dist/commands/manage.js +11 -18
  104. package/dist/commands/market.d.ts +1 -1
  105. package/dist/commands/market.js +26 -17
  106. package/dist/commands/plan.d.ts +1 -1
  107. package/dist/commands/rebalance.d.ts +1 -1
  108. package/dist/commands/rebalance.js +8 -9
  109. package/dist/commands/risk.d.ts +1 -1
  110. package/dist/commands/settings.d.ts +1 -1
  111. package/dist/commands/settings.js +3 -1
  112. package/dist/commands/trade.d.ts +1 -1
  113. package/dist/commands/trade.js +42 -55
  114. package/dist/commands/wallet.d.ts +0 -2
  115. package/dist/commands/wallet.js +0 -5
  116. package/dist/constants.d.ts +0 -3
  117. package/dist/constants.js +0 -4
  118. package/dist/cross-chain-margin.d.ts +1 -1
  119. package/dist/dashboard/index.d.ts +4 -0
  120. package/dist/dashboard/index.js +3 -0
  121. package/dist/dashboard/server.d.ts +1 -1
  122. package/dist/dashboard/server.js +1 -1
  123. package/dist/dashboard/ws-feeds.d.ts +1 -1
  124. package/dist/event-stream.d.ts +1 -1
  125. package/dist/exchanges/aster.d.ts +62 -0
  126. package/dist/exchanges/aster.js +420 -0
  127. package/dist/exchanges/capabilities.d.ts +74 -0
  128. package/dist/exchanges/capabilities.js +44 -0
  129. package/dist/exchanges/edgex.d.ts +62 -0
  130. package/dist/exchanges/edgex.js +552 -0
  131. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  132. package/dist/exchanges/hyperliquid-spot.js +20 -12
  133. package/dist/exchanges/hyperliquid.d.ts +8 -2
  134. package/dist/exchanges/hyperliquid.js +21 -6
  135. package/dist/exchanges/index.d.ts +11 -0
  136. package/dist/exchanges/index.js +10 -0
  137. package/dist/exchanges/interface.d.ts +3 -0
  138. package/dist/exchanges/lighter-spot.d.ts +13 -0
  139. package/dist/exchanges/lighter-spot.js +98 -26
  140. package/dist/exchanges/lighter.d.ts +9 -3
  141. package/dist/exchanges/lighter.js +18 -8
  142. package/dist/exchanges/nado.d.ts +52 -0
  143. package/dist/exchanges/nado.js +223 -0
  144. package/dist/exchanges/pacifica.d.ts +7 -1
  145. package/dist/exchanges/pacifica.js +17 -2
  146. package/dist/exchanges/registry.d.ts +13 -0
  147. package/dist/exchanges/registry.js +37 -0
  148. package/dist/exchanges/spot-interface.d.ts +9 -0
  149. package/dist/funding/rates.js +1 -1
  150. package/dist/index.js +2 -6
  151. package/dist/jobs.js +1 -1
  152. package/dist/liquidity.d.ts +1 -2
  153. package/dist/liquidity.js +12 -5
  154. package/dist/mcp-server.js +4 -2
  155. package/dist/pacifica/constants.d.ts +0 -3
  156. package/dist/pacifica/constants.js +0 -4
  157. package/dist/pacifica/types/account.d.ts +0 -8
  158. package/dist/pacifica/types/lake.d.ts +0 -6
  159. package/dist/pacifica/types/position.d.ts +0 -12
  160. package/dist/pacifica/types/ws.d.ts +0 -24
  161. package/dist/pacifica/types/ws.js +1 -41
  162. package/dist/plan-executor.d.ts +1 -1
  163. package/dist/rebalance.d.ts +1 -7
  164. package/dist/rebalance.js +0 -6
  165. package/dist/risk.d.ts +1 -1
  166. package/dist/risk.js +3 -1
  167. package/dist/smart-order.d.ts +1 -1
  168. package/dist/strategies/dca.d.ts +1 -1
  169. package/dist/strategies/funding-arb.d.ts +1 -1
  170. package/dist/strategies/funding-arb.js +193 -181
  171. package/dist/strategies/grid.d.ts +1 -1
  172. package/dist/strategies/grid.js +1 -1
  173. package/dist/strategies/index.d.ts +12 -0
  174. package/dist/strategies/index.js +7 -0
  175. package/dist/strategies/split-order.d.ts +1 -1
  176. package/dist/strategies/trailing-stop.d.ts +1 -1
  177. package/dist/strategies/twap.d.ts +1 -1
  178. package/dist/trade-validator.d.ts +1 -1
  179. package/package.json +7 -1
  180. package/skills/perp-cli/SKILL.md +20 -0
  181. package/skills/perp-cli/references/commands.md +22 -8
@@ -1,8 +1,10 @@
1
1
  import { Hyperliquid } from "hyperliquid";
2
2
  import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
3
- import type { EvmSigner } from "../signer/interface.js";
3
+ import type { EvmSigner } from "../signer/index.js";
4
4
  export declare class HyperliquidAdapter implements ExchangeAdapter {
5
5
  readonly name = "hyperliquid";
6
+ readonly chain = "evm";
7
+ readonly aliases: readonly ["hl"];
6
8
  private sdk;
7
9
  private _address;
8
10
  private _privateKey;
@@ -13,6 +15,7 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
13
15
  private _szDecimalsMap;
14
16
  /** HIP-3 deployed perp dex name. Empty string = native (validator) perps. */
15
17
  private _dex;
18
+ private static _lastNonce;
16
19
  constructor(privateKey?: string, testnet?: boolean);
17
20
  /** Set the HIP-3 deployed perp dex to query/trade on. */
18
21
  setDex(dex: string): void;
@@ -134,7 +137,10 @@ export declare class HyperliquidAdapter implements ExchangeAdapter {
134
137
  * Withdraw from Hyperliquid L1 bridge.
135
138
  * Python SDK: withdraw_from_bridge(amount, destination) → action type "withdraw3"
136
139
  */
137
- withdraw(amount: string, destination: string): Promise<any>;
140
+ withdraw(amount: string, destination: string, _opts?: {
141
+ assetId?: number;
142
+ routeType?: number;
143
+ }): Promise<any>;
138
144
  /**
139
145
  * Transfer USD between accounts on Hyperliquid L1.
140
146
  * Python SDK: usd_transfer(amount, destination)
@@ -1,7 +1,9 @@
1
1
  import { Hyperliquid } from "hyperliquid";
2
- import { LocalEvmSigner } from "../signer/evm-local.js";
2
+ import { LocalEvmSigner } from "../signer/index.js";
3
3
  export class HyperliquidAdapter {
4
4
  name = "hyperliquid";
5
+ chain = "evm";
6
+ aliases = ["hl"];
5
7
  sdk;
6
8
  _address;
7
9
  _privateKey;
@@ -12,6 +14,7 @@ export class HyperliquidAdapter {
12
14
  _szDecimalsMap = new Map(); // symbol → szDecimals
13
15
  /** HIP-3 deployed perp dex name. Empty string = native (validator) perps. */
14
16
  _dex = "";
17
+ static _lastNonce = 0;
15
18
  // In-memory cache removed — using file-based cache (src/cache.ts) for cross-process dedup
16
19
  constructor(privateKey, testnet = false) {
17
20
  // Disable WebSocket in SDK — we use REST for all info calls and raw
@@ -149,7 +152,14 @@ export class HyperliquidAdapter {
149
152
  /** Get szDecimals for a symbol (cached from init/getMeta) */
150
153
  getSzDecimals(symbol) {
151
154
  const resolved = this.resolveSymbol(symbol.toUpperCase());
152
- return this._szDecimalsMap.get(resolved) ?? 2; // 2 is safe middle ground
155
+ const dec = this._szDecimalsMap.get(resolved);
156
+ if (dec !== undefined)
157
+ return dec;
158
+ // Retry asset map load if empty
159
+ if (this._szDecimalsMap.size === 0) {
160
+ console.error(`[hyperliquid] szDecimals not loaded for ${symbol}, using fallback 2`);
161
+ }
162
+ return 2;
153
163
  }
154
164
  /** HL official price rounding: 5 significant figures */
155
165
  _hlRoundPrice(px, szDecimals, maxDecimals = 6) {
@@ -327,7 +337,9 @@ export class HyperliquidAdapter {
327
337
  side: szi > 0 ? "long" : "short",
328
338
  size: String(Math.abs(szi)),
329
339
  entryPrice: String(pos.entryPx ?? "0"),
330
- markPrice: String(pos.positionValue ? Number(pos.positionValue) / Math.abs(szi) : "0"),
340
+ markPrice: String(szi !== 0 && pos.positionValue && Number(pos.positionValue) !== 0
341
+ ? Number(pos.positionValue) / Math.abs(szi)
342
+ : pos.markPx ?? "0"),
331
343
  liquidationPrice: String(pos.liquidationPx ?? "N/A"),
332
344
  unrealizedPnl: String(pos.unrealizedPnl ?? "0"),
333
345
  leverage: Number(pos.leverage?.value ?? 1),
@@ -485,7 +497,10 @@ export class HyperliquidAdapter {
485
497
  // recovered signer address is wrong → "User or API Wallet does not exist".
486
498
  const normalizedAction = HyperliquidAdapter._normalizeAction(action);
487
499
  // Sign L1 action (replicates SDK's signL1Action)
488
- const nonce = Date.now();
500
+ // Monotonic nonce: ensures uniqueness even for rapid-fire requests in same millisecond
501
+ const now = Date.now();
502
+ HyperliquidAdapter._lastNonce = now > HyperliquidAdapter._lastNonce ? now : HyperliquidAdapter._lastNonce + 1;
503
+ const nonce = HyperliquidAdapter._lastNonce;
489
504
  const msgPackBytes = encode(normalizedAction);
490
505
  const data = new Uint8Array(msgPackBytes.length + 9); // +8 nonce +1 vault flag
491
506
  data.set(msgPackBytes);
@@ -531,7 +546,7 @@ export class HyperliquidAdapter {
531
546
  throw new Error(`HL exchange API error (${res.status}): ${text.slice(0, 200)}`);
532
547
  }
533
548
  if (result?.status === "err") {
534
- throw new Error(result.response ?? JSON.stringify(result));
549
+ throw new Error(typeof result.response === "string" ? result.response : JSON.stringify(result));
535
550
  }
536
551
  return result;
537
552
  }
@@ -821,7 +836,7 @@ export class HyperliquidAdapter {
821
836
  * Withdraw from Hyperliquid L1 bridge.
822
837
  * Python SDK: withdraw_from_bridge(amount, destination) → action type "withdraw3"
823
838
  */
824
- async withdraw(amount, destination) {
839
+ async withdraw(amount, destination, _opts) {
825
840
  this.ensureSigner();
826
841
  try {
827
842
  return await this.sdk.exchange.initiateWithdrawal(destination, parseFloat(amount));
@@ -0,0 +1,11 @@
1
+ export type { ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline, ExchangeAdapter, } from "./interface.js";
2
+ export type { SpotMarketInfo, SpotBalance, SpotAdapter } from "./spot-interface.js";
3
+ export { SPOT_PERP_TOKEN_MAP, PERP_TO_SPOT_MAP, MAX_PRICE_DEVIATION_PCT } from "./spot-interface.js";
4
+ export * from "./capabilities.js";
5
+ export { registerAdapter, getAdapterRegistration, listExchanges, resolveExchangeName } from "./registry.js";
6
+ export type { AdapterRegistration } from "./registry.js";
7
+ export { PacificaAdapter } from "./pacifica.js";
8
+ export { HyperliquidAdapter } from "./hyperliquid.js";
9
+ export { LighterAdapter } from "./lighter.js";
10
+ export { HyperliquidSpotAdapter } from "./hyperliquid-spot.js";
11
+ export { LighterSpotAdapter } from "./lighter-spot.js";
@@ -0,0 +1,10 @@
1
+ export { SPOT_PERP_TOKEN_MAP, PERP_TO_SPOT_MAP, MAX_PRICE_DEVIATION_PCT } from "./spot-interface.js";
2
+ // Capability interfaces + type guards
3
+ export * from "./capabilities.js";
4
+ // Adapter registry
5
+ export { registerAdapter, getAdapterRegistration, listExchanges, resolveExchangeName } from "./registry.js";
6
+ export { PacificaAdapter } from "./pacifica.js";
7
+ export { HyperliquidAdapter } from "./hyperliquid.js";
8
+ export { LighterAdapter } from "./lighter.js";
9
+ export { HyperliquidSpotAdapter } from "./hyperliquid-spot.js";
10
+ export { LighterSpotAdapter } from "./lighter-spot.js";
@@ -61,6 +61,9 @@ export interface ExchangeKline {
61
61
  }
62
62
  export interface ExchangeAdapter {
63
63
  readonly name: string;
64
+ readonly chain?: string;
65
+ readonly aliases?: readonly string[];
66
+ init?(): Promise<void>;
64
67
  getMarkets(): Promise<ExchangeMarketInfo[]>;
65
68
  getOrderbook(symbol: string): Promise<{
66
69
  bids: [string, string][];
@@ -22,6 +22,8 @@ export declare class LighterSpotAdapter implements SpotAdapter {
22
22
  init(): Promise<void>;
23
23
  private _loadSpotMarkets;
24
24
  /** Resolve a symbol (e.g., "ETH" or "ETH/USDC") to the full spot symbol */
25
+ /** Get decimals for a symbol — always from API data, never hardcoded fallback */
26
+ private _getDecimals;
25
27
  private resolveSymbol;
26
28
  getSpotMarkets(): Promise<SpotMarketInfo[]>;
27
29
  getSpotOrderbook(symbol: string): Promise<{
@@ -51,6 +53,17 @@ export declare class LighterSpotAdapter implements SpotAdapter {
51
53
  getSpotMarketId(symbol: string): number | undefined;
52
54
  /** Check if a base token has a spot market */
53
55
  hasSpotMarket(base: string): boolean;
56
+ /** Query open orders across all spot markets (market_id ≥ 2048). */
57
+ getSpotOpenOrders(): Promise<Array<{
58
+ orderId: string;
59
+ symbol: string;
60
+ side: "buy" | "sell";
61
+ price: string;
62
+ size: string;
63
+ clientOrderIndex?: number;
64
+ }>>;
65
+ /** Cancel a spot order by clientOrderIndex (safe integer, avoids order_id overflow). */
66
+ spotCancelByClientIndex(symbol: string, clientOrderIndex: number): Promise<unknown>;
54
67
  private _placeOrder;
55
68
  /** Generate a unique client order index (uint48). Uses timestamp + counter. */
56
69
  private _nextClientOrderIndex;
@@ -74,6 +74,19 @@ export class LighterSpotAdapter {
74
74
  }
75
75
  }
76
76
  /** Resolve a symbol (e.g., "ETH" or "ETH/USDC") to the full spot symbol */
77
+ /** Get decimals for a symbol — always from API data, never hardcoded fallback */
78
+ _getDecimals(symbol) {
79
+ const resolved = symbol.includes("/") ? symbol : symbol.toUpperCase();
80
+ const dec = this._spotDecimals.get(resolved);
81
+ if (dec)
82
+ return dec;
83
+ // Try base token lookup (e.g., "ETH" → "ETH/USDC")
84
+ for (const [key, val] of this._spotDecimals) {
85
+ if (key.split("/")[0] === symbol.toUpperCase())
86
+ return val;
87
+ }
88
+ throw new Error(`No decimal info for spot market ${symbol}. Ensure init() completed successfully.`);
89
+ }
77
90
  resolveSymbol(symbol) {
78
91
  const upper = symbol.toUpperCase();
79
92
  if (this._spotMarketMap.has(upper))
@@ -106,7 +119,7 @@ export class LighterSpotAdapter {
106
119
  const parts = symbol.split("/");
107
120
  const base = parts[0] ?? "";
108
121
  const quote = parts[1] ?? "USDC";
109
- const dec = this._spotDecimals.get(symbol) ?? { size: 4, price: 2 };
122
+ const dec = this._getDecimals(symbol);
110
123
  return {
111
124
  symbol, baseToken: base, quoteToken: quote,
112
125
  markPrice: this._spotPrices.get(symbol) ?? "0", volume24h: "0",
@@ -207,10 +220,10 @@ export class LighterSpotAdapter {
207
220
  throw new Error(`Order value $${(sizeNum * refPrice).toFixed(2)} below min_quote_amount $${minSize.quote} for ${resolved}`);
208
221
  }
209
222
  const slippagePrice = side === "buy" ? refPrice * 1.05 : refPrice * 0.95;
210
- const dec = this._spotDecimals.get(resolved) ?? { size: 2, price: 4 };
223
+ const dec = this._getDecimals(resolved);
211
224
  const baseAmount = Math.round(sizeNum * Math.pow(10, dec.size));
212
225
  const priceTicks = Math.round(slippagePrice * Math.pow(10, dec.price));
213
- return this._placeOrder({
226
+ const result = await this._placeOrder({
214
227
  marketIndex: marketId,
215
228
  baseAmount,
216
229
  price: Math.max(priceTicks, 1),
@@ -218,6 +231,7 @@ export class LighterSpotAdapter {
218
231
  orderType: 1, // MARKET
219
232
  timeInForce: 0, // IOC
220
233
  });
234
+ return result;
221
235
  }
222
236
  async spotLimitOrder(symbol, side, price, size, opts) {
223
237
  await this.init();
@@ -226,23 +240,28 @@ export class LighterSpotAdapter {
226
240
  if (marketId === undefined) {
227
241
  throw new Error(`Unknown Lighter spot market: ${symbol}`);
228
242
  }
229
- // Validate min order size
230
- const sizeNum = parseFloat(size);
231
- const priceNum = parseFloat(price);
243
+ const dec = this._getDecimals(resolved);
244
+ // Auto-round to exchange precision — caller doesn't need to know decimals
245
+ let sizeNum = Number(Number(size).toFixed(dec.size));
246
+ const priceNum = Number(Number(price).toFixed(dec.price));
247
+ // Auto-ceil size to min_base_amount if just below (within 10%)
232
248
  const minSize = this._spotMinSize.get(resolved);
249
+ if (minSize && sizeNum > 0 && sizeNum < minSize.base && sizeNum >= minSize.base * 0.9) {
250
+ sizeNum = minSize.base;
251
+ }
252
+ // Validate min order size
233
253
  if (minSize) {
234
254
  if (sizeNum < minSize.base) {
235
- throw new Error(`Size ${size} below min_base_amount ${minSize.base} for ${resolved}`);
255
+ throw new Error(`Size ${sizeNum} below min_base_amount ${minSize.base} for ${resolved}`);
236
256
  }
237
257
  if (sizeNum * priceNum < minSize.quote) {
238
258
  throw new Error(`Order value $${(sizeNum * priceNum).toFixed(2)} below min_quote_amount $${minSize.quote} for ${resolved}`);
239
259
  }
240
260
  }
241
- const dec = this._spotDecimals.get(resolved) ?? { size: 2, price: 4 };
242
261
  const baseAmount = Math.round(sizeNum * Math.pow(10, dec.size));
243
262
  const priceTicks = Math.round(priceNum * Math.pow(10, dec.price));
244
263
  const isIoc = opts?.tif?.toUpperCase() === "IOC";
245
- return this._placeOrder({
264
+ const result = await this._placeOrder({
246
265
  marketIndex: marketId,
247
266
  baseAmount,
248
267
  price: priceTicks,
@@ -250,6 +269,7 @@ export class LighterSpotAdapter {
250
269
  orderType: 0, // LIMIT
251
270
  timeInForce: isIoc ? 0 : 1,
252
271
  });
272
+ return result;
253
273
  }
254
274
  async spotCancelOrder(symbol, orderId) {
255
275
  await this.init();
@@ -258,24 +278,29 @@ export class LighterSpotAdapter {
258
278
  if (marketId === undefined) {
259
279
  throw new Error(`Unknown Lighter spot market: ${symbol}`);
260
280
  }
261
- // Lighter order IDs can exceed Number.MAX_SAFE_INTEGER (2^53-1).
262
- // The WASM signer only accepts JS number, so large IDs lose precision
263
- // and the cancel silently targets the wrong order.
264
- // Fall back to cancelAll for the market when the ID is unsafe.
265
281
  const idNum = Number(orderId);
266
- if (!Number.isSafeInteger(idNum)) {
267
- return this.spotCancelAllOrders(symbol);
282
+ // If order ID is safe integer, cancel directly
283
+ if (Number.isSafeInteger(idNum)) {
284
+ const signer = this._lt.signer;
285
+ const nonce = await this._getNextNonce();
286
+ const signed = await signer.signCancelOrder({
287
+ marketIndex: marketId,
288
+ orderIndex: idNum,
289
+ nonce,
290
+ apiKeyIndex: this._lt._apiKeyIndex,
291
+ accountIndex: this._lt.accountIndex,
292
+ });
293
+ return this._sendTx(signed);
268
294
  }
269
- const signer = this._lt.signer;
270
- const nonce = await this._getNextNonce();
271
- const signed = await signer.signCancelOrder({
272
- marketIndex: marketId,
273
- orderIndex: idNum,
274
- nonce,
275
- apiKeyIndex: this._lt._apiKeyIndex,
276
- accountIndex: this._lt.accountIndex,
277
- });
278
- return this._sendTx(signed);
295
+ // Order ID exceeds MAX_SAFE_INTEGER → try lookup from open orders
296
+ // Note: getSpotOpenOrders() can return empty due to block finality delay.
297
+ // Prefer spotCancelByClientIndex() with the clientOrderIndex from spotLimitOrder().
298
+ const openOrders = await this.getSpotOpenOrders();
299
+ const symbolOrders = openOrders.filter(o => o.symbol.split("/")[0].toUpperCase() === resolved.toUpperCase() && o.clientOrderIndex);
300
+ if (symbolOrders.length >= 1) {
301
+ return this.spotCancelByClientIndex(resolved, symbolOrders[0].clientOrderIndex);
302
+ }
303
+ throw new Error(`Cannot cancel Lighter spot order ${orderId}: exceeds MAX_SAFE_INTEGER and no clientOrderIndex found`);
279
304
  }
280
305
  /** Cancel all orders across all markets (spot + perp) */
281
306
  async spotCancelAllOrders(_symbol) {
@@ -363,6 +388,52 @@ export class LighterSpotAdapter {
363
388
  return this._spotBaseMap.has(base.toUpperCase()) ||
364
389
  this._spotMarketMap.has(`${base.toUpperCase()}/USDC`);
365
390
  }
391
+ /** Query open orders across all spot markets (market_id ≥ 2048). */
392
+ async getSpotOpenOrders() {
393
+ await this.init();
394
+ if (this._lt.isReadOnly)
395
+ return [];
396
+ const allOrders = [];
397
+ for (const [sym, marketId] of this._spotMarketMap) {
398
+ try {
399
+ const data = await this._restGetAuth("/accountActiveOrders", {
400
+ account_index: String(this._lt.accountIndex),
401
+ market_id: String(marketId),
402
+ });
403
+ for (const o of data.orders ?? []) {
404
+ allOrders.push({
405
+ orderId: String(o.order_id ?? o.order_index ?? ""),
406
+ symbol: sym,
407
+ side: o.is_ask ? "sell" : "buy",
408
+ price: String(o.price ?? "0"),
409
+ size: String(o.remaining_base_amount ?? o.initial_base_amount ?? "0"),
410
+ clientOrderIndex: o.client_order_index != null ? Number(o.client_order_index) : undefined,
411
+ });
412
+ }
413
+ }
414
+ catch { /* skip market on error */ }
415
+ }
416
+ return allOrders;
417
+ }
418
+ /** Cancel a spot order by clientOrderIndex (safe integer, avoids order_id overflow). */
419
+ async spotCancelByClientIndex(symbol, clientOrderIndex) {
420
+ await this.init();
421
+ const resolved = this.resolveSymbol(symbol);
422
+ const marketId = this._spotMarketMap.get(resolved);
423
+ if (marketId === undefined)
424
+ throw new Error(`Unknown spot market: ${symbol}`);
425
+ const signer = this._lt.signer;
426
+ const nonce = await this._getNextNonce();
427
+ const apiKeyIndex = this._lt._apiKeyIndex;
428
+ const signed = await signer.signCancelOrder({
429
+ marketIndex: marketId,
430
+ orderIndex: clientOrderIndex,
431
+ nonce,
432
+ apiKeyIndex,
433
+ accountIndex: this._lt.accountIndex,
434
+ });
435
+ return this._sendTx(signed);
436
+ }
366
437
  // ── Private helpers ──
367
438
  async _placeOrder(opts) {
368
439
  if (this._lt.isReadOnly) {
@@ -393,7 +464,8 @@ export class LighterSpotAdapter {
393
464
  if (signed.error) {
394
465
  throw new Error(`Signer: ${signed.error}`);
395
466
  }
396
- return this._sendTx(signed);
467
+ const txResult = await this._sendTx(signed);
468
+ return { ...txResult, clientOrderIndex };
397
469
  }
398
470
  /** Generate a unique client order index (uint48). Uses timestamp + counter. */
399
471
  _nextClientOrderIndex() {
@@ -1,8 +1,10 @@
1
1
  import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
2
- import type { EvmSigner } from "../signer/interface.js";
2
+ import type { EvmSigner } from "../signer/index.js";
3
3
  import type { WasmSignerClient as WasmSignerClientType } from "lighter-ts-sdk";
4
4
  export declare class LighterAdapter implements ExchangeAdapter {
5
5
  readonly name = "lighter";
6
+ readonly chain = "ethereum";
7
+ readonly aliases: readonly ["lt"];
6
8
  private _signer;
7
9
  private _accountIndex;
8
10
  private _apiKeyIndex;
@@ -34,7 +36,7 @@ export declare class LighterAdapter implements ExchangeAdapter {
34
36
  } & WasmSignerClientType;
35
37
  get accountIndex(): number;
36
38
  get address(): string;
37
- get evmKey(): string;
39
+ /** @internal — evmKey is intentionally not exposed publicly for security */
38
40
  get isReadOnly(): boolean;
39
41
  /** Inject an external EVM signer. Call before init() to skip LocalEvmSigner creation. */
40
42
  setSigner(signer: EvmSigner): void;
@@ -70,7 +72,11 @@ export declare class LighterAdapter implements ExchangeAdapter {
70
72
  reduceOnly?: boolean;
71
73
  }): Promise<unknown>;
72
74
  updateLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
73
- withdraw(amount: number, assetId?: number, routeType?: number): Promise<unknown>;
75
+ withdraw(amount: string, destination: string, opts?: {
76
+ assetId?: number;
77
+ routeType?: number;
78
+ }): Promise<unknown>;
79
+ private _withdrawRaw;
74
80
  editOrder(symbol: string, orderId: string, price: string, size: string): Promise<unknown>;
75
81
  setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
76
82
  getRecentTrades(symbol: string, limit?: number): Promise<ExchangeTrade[]>;
@@ -1,6 +1,8 @@
1
- import { LocalEvmSigner } from "../signer/evm-local.js";
1
+ import { LocalEvmSigner } from "../signer/index.js";
2
2
  export class LighterAdapter {
3
3
  name = "lighter";
4
+ chain = "ethereum";
5
+ aliases = ["lt"];
4
6
  _signer;
5
7
  _accountIndex = -1;
6
8
  _apiKeyIndex;
@@ -53,9 +55,7 @@ export class LighterAdapter {
53
55
  get address() {
54
56
  return this._address;
55
57
  }
56
- get evmKey() {
57
- return this._evmKey;
58
- }
58
+ /** @internal — evmKey is intentionally not exposed publicly for security */
59
59
  get isReadOnly() {
60
60
  return this._readOnly;
61
61
  }
@@ -347,9 +347,13 @@ export class LighterAdapter {
347
347
  side: (Number(p.sign) > 0 ? "long" : "short"),
348
348
  size: String(Math.abs(posSize)),
349
349
  entryPrice: String(p.avg_entry_price || "0"),
350
- markPrice: String(posSize !== 0
351
- ? (Number(p.position_value || 0) / Math.abs(posSize)).toFixed(this._marketDecimals.get(String(p.symbol || "").toUpperCase())?.price ?? 4)
352
- : "0"),
350
+ markPrice: (() => {
351
+ if (posSize === 0)
352
+ return "0";
353
+ const rawMark = Number(p.position_value || 0) / Math.abs(posSize);
354
+ const priceDec = this._marketDecimals.get(String(p.symbol || "").toUpperCase())?.price;
355
+ return String(priceDec !== undefined ? rawMark.toFixed(priceDec) : rawMark);
356
+ })(),
353
357
  liquidationPrice: String(p.liquidation_price || "N/A"),
354
358
  unrealizedPnl: String(p.unrealized_pnl || "0"),
355
359
  // Compute actual leverage = notional / account equity (not max leverage from IMF)
@@ -587,7 +591,10 @@ export class LighterAdapter {
587
591
  }
588
592
  return this.sendTx(signed);
589
593
  }
590
- async withdraw(amount, assetId = 3, routeType = 0) {
594
+ async withdraw(amount, destination, opts) {
595
+ return this._withdrawRaw(parseFloat(amount), opts?.assetId ?? 3, opts?.routeType ?? 0);
596
+ }
597
+ async _withdrawRaw(amount, assetId = 3, routeType = 0) {
591
598
  this.ensureSigner();
592
599
  const nonce = await this.getNextNonce();
593
600
  const signed = await this._signer.signWithdraw({
@@ -1022,6 +1029,9 @@ export class LighterAdapter {
1022
1029
  if (!this._evmSigner) {
1023
1030
  throw new Error("No EVM private key configured. Run: perp init");
1024
1031
  }
1032
+ if (this._accountIndex < 0) {
1033
+ throw new Error("Account index not available. Call init() first or set LIGHTER_ACCOUNT_INDEX.");
1034
+ }
1025
1035
  // 1. Generate key pair
1026
1036
  const { privateKey, publicKey } = await LighterAdapter.generateApiKey();
1027
1037
  // 2. Get nonce from API
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Nado DEX adapter — Orderbook DEX on Ink L2.
3
+ * API: https://gateway.nado.xyz/v1 (mainnet) / https://gateway.test.nado.xyz/v1 (testnet)
4
+ * Docs: https://docs.nado.xyz/developer-resources/api/gateway
5
+ * Note: API documentation is incomplete — many endpoints are stubbed pending full docs.
6
+ */
7
+ import type { ExchangeAdapter, ExchangeMarketInfo, ExchangePosition, ExchangeOrder, ExchangeBalance, ExchangeTrade, ExchangeFundingPayment, ExchangeKline } from "./interface.js";
8
+ export declare class NadoAdapter implements ExchangeAdapter {
9
+ readonly name = "nado";
10
+ readonly chain = "ink";
11
+ readonly aliases: readonly ["nad"];
12
+ private _baseUrl;
13
+ private _apiKey;
14
+ constructor(apiKey?: string, testnet?: boolean);
15
+ init(): Promise<void>;
16
+ getMarkets(): Promise<ExchangeMarketInfo[]>;
17
+ getOrderbook(symbol: string): Promise<{
18
+ bids: [string, string][];
19
+ asks: [string, string][];
20
+ }>;
21
+ getRecentTrades(symbol: string, limit?: number): Promise<ExchangeTrade[]>;
22
+ getFundingHistory(symbol: string, _limit?: number): Promise<{
23
+ time: number;
24
+ rate: string;
25
+ price: string | null;
26
+ }[]>;
27
+ getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
28
+ getBalance(): Promise<ExchangeBalance>;
29
+ getPositions(): Promise<ExchangePosition[]>;
30
+ getOpenOrders(): Promise<ExchangeOrder[]>;
31
+ getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
32
+ getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
33
+ getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
34
+ marketOrder(symbol: string, side: "buy" | "sell", size: string): Promise<unknown>;
35
+ limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
36
+ reduceOnly?: boolean;
37
+ tif?: string;
38
+ }): Promise<unknown>;
39
+ editOrder(symbol: string, orderId: string, price: string, size: string): Promise<unknown>;
40
+ cancelOrder(symbol: string, orderId: string): Promise<unknown>;
41
+ cancelAllOrders(symbol?: string): Promise<unknown>;
42
+ setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
43
+ stopOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, opts?: {
44
+ limitPrice?: string;
45
+ reduceOnly?: boolean;
46
+ }): Promise<unknown>;
47
+ withdraw(amount: string, destination: string): Promise<unknown>;
48
+ private _get;
49
+ private _authGet;
50
+ private _authPost;
51
+ private _authDelete;
52
+ }