perp-cli 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/api/public/hyperliquid.d.ts +2 -2
- package/dist/api/public/hyperliquid.js +0 -10
- package/dist/api/public/lighter.d.ts +2 -1
- package/dist/api/public/pacifica.d.ts +2 -1
- package/dist/arb/index.d.ts +7 -4
- package/dist/arb/index.js +5 -4
- package/dist/arb/sizing.d.ts +1 -1
- package/dist/arb/state.d.ts +1 -1
- package/dist/arb/state.js +4 -3
- package/dist/arb/utils.d.ts +0 -10
- package/dist/arb/utils.js +0 -42
- package/dist/bot/apex/guard.d.ts +26 -0
- package/dist/bot/apex/guard.js +86 -0
- package/dist/bot/apex/index.d.ts +6 -0
- package/dist/bot/apex/index.js +4 -0
- package/dist/bot/apex/pulse.d.ts +22 -0
- package/dist/bot/apex/pulse.js +107 -0
- package/dist/bot/apex/radar.d.ts +17 -0
- package/dist/bot/apex/radar.js +150 -0
- package/dist/bot/conditions.d.ts +1 -1
- package/dist/bot/config.d.ts +5 -1
- package/dist/bot/config.js +1 -1
- package/dist/bot/engine.d.ts +23 -2
- package/dist/bot/engine.js +296 -351
- package/dist/bot/index.d.ts +16 -0
- package/dist/bot/index.js +10 -0
- package/dist/bot/indicators.d.ts +29 -0
- package/dist/bot/indicators.js +144 -0
- package/dist/bot/reflect.d.ts +34 -0
- package/dist/bot/reflect.js +155 -0
- package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
- package/dist/bot/strategies/aggressive-taker.js +125 -0
- package/dist/bot/strategies/apex-strategy.d.ts +36 -0
- package/dist/bot/strategies/apex-strategy.js +185 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
- package/dist/bot/strategies/avellaneda-mm.js +111 -0
- package/dist/bot/strategies/basis-arb.d.ts +27 -0
- package/dist/bot/strategies/basis-arb.js +97 -0
- package/dist/bot/strategies/claude-agent.d.ts +31 -0
- package/dist/bot/strategies/claude-agent.js +155 -0
- package/dist/bot/strategies/dca-strategy.d.ts +25 -0
- package/dist/bot/strategies/dca-strategy.js +59 -0
- package/dist/bot/strategies/engine-mm.d.ts +29 -0
- package/dist/bot/strategies/engine-mm.js +141 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-strategy.js +168 -0
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +222 -0
- package/dist/bot/strategies/funding-auto.d.ts +1 -0
- package/dist/bot/strategies/funding-auto.js +572 -0
- package/dist/bot/strategies/grid-mm.d.ts +25 -0
- package/dist/bot/strategies/grid-mm.js +99 -0
- package/dist/bot/strategies/grid-strategy.d.ts +39 -0
- package/dist/bot/strategies/grid-strategy.js +176 -0
- package/dist/bot/strategies/hedge-agent.d.ts +25 -0
- package/dist/bot/strategies/hedge-agent.js +79 -0
- package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
- package/dist/bot/strategies/liquidation-mm.js +108 -0
- package/dist/bot/strategies/mean-reversion.d.ts +35 -0
- package/dist/bot/strategies/mean-reversion.js +118 -0
- package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
- package/dist/bot/strategies/momentum-breakout.js +159 -0
- package/dist/bot/strategies/regime-mm.d.ts +25 -0
- package/dist/bot/strategies/regime-mm.js +111 -0
- package/dist/bot/strategies/rfq-agent.d.ts +25 -0
- package/dist/bot/strategies/rfq-agent.js +71 -0
- package/dist/bot/strategies/simple-mm.d.ts +25 -0
- package/dist/bot/strategies/simple-mm.js +76 -0
- package/dist/bot/strategy-registry.d.ts +9 -0
- package/dist/bot/strategy-registry.js +14 -0
- package/dist/bot/strategy-types.d.ts +64 -0
- package/dist/bot/strategy-types.js +5 -0
- package/dist/bot/trade-journal.d.ts +27 -0
- package/dist/bot/trade-journal.js +53 -0
- package/dist/bot/tui/BotDashboard.d.ts +49 -0
- package/dist/bot/tui/BotDashboard.js +93 -0
- package/dist/bot/tui/index.d.ts +6 -0
- package/dist/bot/tui/index.js +8 -0
- package/dist/bridge-engine.d.ts +0 -1
- package/dist/bridge-engine.js +0 -1
- package/dist/cache.d.ts +0 -1
- package/dist/cache.js +0 -1
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +94 -91
- package/dist/commands/agent.d.ts +1 -1
- package/dist/commands/arb/index.d.ts +1 -1
- package/dist/commands/arb/index.js +1 -1
- package/dist/commands/arb-auto.d.ts +1 -1
- package/dist/commands/arb-auto.js +1 -1
- package/dist/commands/arb.d.ts +1 -1
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +198 -14
- package/dist/commands/dashboard.d.ts +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +33 -20
- package/dist/commands/history.d.ts +1 -1
- package/dist/commands/history.js +5 -4
- package/dist/commands/manage.d.ts +2 -3
- package/dist/commands/manage.js +11 -18
- package/dist/commands/market.d.ts +1 -1
- package/dist/commands/market.js +7 -7
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +8 -9
- package/dist/commands/risk.d.ts +1 -1
- package/dist/commands/settings.d.ts +1 -1
- package/dist/commands/settings.js +3 -1
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +39 -52
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -5
- package/dist/constants.d.ts +0 -3
- package/dist/constants.js +0 -4
- package/dist/cross-chain-margin.d.ts +1 -1
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/dashboard/server.d.ts +1 -1
- package/dist/dashboard/server.js +1 -1
- package/dist/dashboard/ws-feeds.d.ts +1 -1
- package/dist/event-stream.d.ts +1 -1
- package/dist/exchanges/aster.d.ts +58 -0
- package/dist/exchanges/aster.js +409 -0
- package/dist/exchanges/capabilities.d.ts +74 -0
- package/dist/exchanges/capabilities.js +44 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +8 -2
- package/dist/exchanges/hyperliquid.js +21 -6
- package/dist/exchanges/index.d.ts +11 -0
- package/dist/exchanges/index.js +10 -0
- package/dist/exchanges/interface.d.ts +3 -0
- package/dist/exchanges/lighter-spot.d.ts +13 -0
- package/dist/exchanges/lighter-spot.js +98 -26
- package/dist/exchanges/lighter.d.ts +9 -3
- package/dist/exchanges/lighter.js +18 -8
- package/dist/exchanges/nado.d.ts +52 -0
- package/dist/exchanges/nado.js +223 -0
- package/dist/exchanges/pacifica.d.ts +7 -1
- package/dist/exchanges/pacifica.js +17 -2
- package/dist/exchanges/registry.d.ts +13 -0
- package/dist/exchanges/registry.js +37 -0
- package/dist/exchanges/spot-interface.d.ts +9 -0
- package/dist/funding/rates.js +1 -1
- package/dist/index.js +2 -6
- package/dist/jobs.js +1 -1
- package/dist/liquidity.d.ts +1 -2
- package/dist/liquidity.js +12 -5
- package/dist/mcp-server.js +4 -2
- package/dist/pacifica/constants.d.ts +0 -3
- package/dist/pacifica/constants.js +0 -4
- package/dist/pacifica/types/account.d.ts +0 -8
- package/dist/pacifica/types/lake.d.ts +0 -6
- package/dist/pacifica/types/position.d.ts +0 -12
- package/dist/pacifica/types/ws.d.ts +0 -24
- package/dist/pacifica/types/ws.js +1 -41
- package/dist/plan-executor.d.ts +1 -1
- package/dist/rebalance.d.ts +1 -7
- package/dist/rebalance.js +0 -6
- package/dist/risk.d.ts +1 -1
- package/dist/risk.js +3 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/strategies/dca.d.ts +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +193 -181
- package/dist/strategies/grid.d.ts +1 -1
- package/dist/strategies/grid.js +1 -1
- package/dist/strategies/index.d.ts +12 -0
- package/dist/strategies/index.js +7 -0
- package/dist/strategies/split-order.d.ts +1 -1
- package/dist/strategies/trailing-stop.d.ts +1 -1
- package/dist/strategies/twap.d.ts +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/package.json +7 -1
- package/skills/perp-cli/SKILL.md +20 -0
- package/skills/perp-cli/references/commands.md +22 -8
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Hyperliquid } from "hyperliquid";
|
|
2
|
-
import { LocalEvmSigner } from "../signer/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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:
|
|
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/
|
|
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
|
-
|
|
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:
|
|
351
|
-
|
|
352
|
-
|
|
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,
|
|
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
|
+
}
|