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
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aster DEX adapter — Binance Futures API compatible REST wrapper.
|
|
3
|
+
* Docs: https://docs.asterdex.com/product/aster-perpetuals/api/api-documentation
|
|
4
|
+
* Base: https://fapi.asterdex.com
|
|
5
|
+
*/
|
|
6
|
+
import { createHmac } from "node:crypto";
|
|
7
|
+
export class AsterAdapter {
|
|
8
|
+
name = "aster";
|
|
9
|
+
chain = "bnb";
|
|
10
|
+
aliases = ["ast"];
|
|
11
|
+
_apiKey;
|
|
12
|
+
_apiSecret;
|
|
13
|
+
_baseUrl;
|
|
14
|
+
_testnet;
|
|
15
|
+
constructor(apiKey, apiSecret, testnet = false) {
|
|
16
|
+
this._apiKey = apiKey || process.env.ASTER_API_KEY || "";
|
|
17
|
+
this._apiSecret = apiSecret || process.env.ASTER_API_SECRET || "";
|
|
18
|
+
this._testnet = testnet;
|
|
19
|
+
this._baseUrl = testnet
|
|
20
|
+
? (process.env.ASTER_TESTNET_URL || "https://testnet.asterdex.com")
|
|
21
|
+
: "https://fapi.asterdex.com";
|
|
22
|
+
}
|
|
23
|
+
get isReadOnly() {
|
|
24
|
+
return !this._apiKey || !this._apiSecret;
|
|
25
|
+
}
|
|
26
|
+
async init() {
|
|
27
|
+
// Verify connectivity by fetching server time
|
|
28
|
+
await this._publicGet("/fapi/v1/time");
|
|
29
|
+
}
|
|
30
|
+
// ── Market Data ──
|
|
31
|
+
async getMarkets() {
|
|
32
|
+
const [info, tickers] = await Promise.all([
|
|
33
|
+
this._publicGet("/fapi/v1/exchangeInfo"),
|
|
34
|
+
this._publicGet("/fapi/v1/ticker/24hr"),
|
|
35
|
+
]);
|
|
36
|
+
const tickerMap = new Map();
|
|
37
|
+
for (const t of tickers ?? []) {
|
|
38
|
+
tickerMap.set(String(t.symbol), t);
|
|
39
|
+
}
|
|
40
|
+
// Also fetch premium index for mark/index/funding
|
|
41
|
+
let premiumMap = new Map();
|
|
42
|
+
try {
|
|
43
|
+
const premiums = await this._publicGet("/fapi/v1/premiumIndex");
|
|
44
|
+
premiumMap = new Map(premiums.map(p => [String(p.symbol), p]));
|
|
45
|
+
}
|
|
46
|
+
catch { /* non-critical */ }
|
|
47
|
+
const tradingSymbols = (info?.symbols ?? [])
|
|
48
|
+
.filter((s) => String(s.contractType) === "PERPETUAL" && String(s.status) === "TRADING");
|
|
49
|
+
// Fetch open interest per symbol in parallel (batched to avoid overwhelming the API).
|
|
50
|
+
// The /fapi/v1/ticker/24hr response does NOT include openInterest — need a separate endpoint.
|
|
51
|
+
const oiMap = new Map();
|
|
52
|
+
const OI_BATCH = 20;
|
|
53
|
+
for (let i = 0; i < tradingSymbols.length; i += OI_BATCH) {
|
|
54
|
+
const batch = tradingSymbols.slice(i, i + OI_BATCH);
|
|
55
|
+
const results = await Promise.allSettled(batch.map(async (s) => {
|
|
56
|
+
const sym = String(s.symbol);
|
|
57
|
+
const res = await this._publicGet("/fapi/v1/openInterest", { symbol: sym });
|
|
58
|
+
return { sym, oi: String(res?.openInterest ?? "0") };
|
|
59
|
+
}));
|
|
60
|
+
for (const r of results) {
|
|
61
|
+
if (r.status === "fulfilled")
|
|
62
|
+
oiMap.set(r.value.sym, r.value.oi);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return tradingSymbols
|
|
66
|
+
.map((s) => {
|
|
67
|
+
const sym = String(s.symbol);
|
|
68
|
+
const ticker = tickerMap.get(sym);
|
|
69
|
+
const premium = premiumMap.get(sym);
|
|
70
|
+
// Extract max leverage from leverage brackets if available
|
|
71
|
+
const maxLev = Number(s.maxLeverage ?? 50);
|
|
72
|
+
return {
|
|
73
|
+
symbol: sym,
|
|
74
|
+
markPrice: String(premium?.markPrice ?? ticker?.lastPrice ?? "0"),
|
|
75
|
+
indexPrice: String(premium?.indexPrice ?? "0"),
|
|
76
|
+
fundingRate: String(premium?.lastFundingRate ?? "0"),
|
|
77
|
+
volume24h: String(ticker?.quoteVolume ?? ticker?.volume ?? "0"),
|
|
78
|
+
openInterest: oiMap.get(sym) ?? "0",
|
|
79
|
+
maxLeverage: maxLev,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async getOrderbook(symbol) {
|
|
84
|
+
const res = await this._publicGet("/fapi/v1/depth", { symbol: symbol.toUpperCase(), limit: "50" });
|
|
85
|
+
return {
|
|
86
|
+
bids: res?.bids ?? [],
|
|
87
|
+
asks: res?.asks ?? [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async getRecentTrades(symbol, limit = 20) {
|
|
91
|
+
const trades = await this._publicGet("/fapi/v1/trades", {
|
|
92
|
+
symbol: symbol.toUpperCase(),
|
|
93
|
+
limit: String(limit),
|
|
94
|
+
});
|
|
95
|
+
return (trades ?? []).map((t) => ({
|
|
96
|
+
time: Number(t.time ?? 0),
|
|
97
|
+
symbol: symbol.toUpperCase(),
|
|
98
|
+
side: t.isBuyerMaker ? "sell" : "buy",
|
|
99
|
+
price: String(t.price ?? "0"),
|
|
100
|
+
size: String(t.qty ?? "0"),
|
|
101
|
+
fee: "0",
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
async getFundingHistory(symbol, limit = 10) {
|
|
105
|
+
const data = await this._publicGet("/fapi/v1/fundingRate", {
|
|
106
|
+
symbol: symbol.toUpperCase(),
|
|
107
|
+
limit: String(limit),
|
|
108
|
+
});
|
|
109
|
+
return (data ?? []).map((d) => ({
|
|
110
|
+
time: Number(d.fundingTime ?? 0),
|
|
111
|
+
rate: String(d.fundingRate ?? "0"),
|
|
112
|
+
price: d.markPrice ? String(d.markPrice) : null,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
async getKlines(symbol, interval, startTime, endTime) {
|
|
116
|
+
const data = await this._publicGet("/fapi/v1/klines", {
|
|
117
|
+
symbol: symbol.toUpperCase(),
|
|
118
|
+
interval,
|
|
119
|
+
startTime: String(startTime),
|
|
120
|
+
endTime: String(endTime),
|
|
121
|
+
limit: "500",
|
|
122
|
+
});
|
|
123
|
+
return (data ?? []).map((k) => ({
|
|
124
|
+
time: Number(k[0]),
|
|
125
|
+
open: String(k[1]),
|
|
126
|
+
high: String(k[2]),
|
|
127
|
+
low: String(k[3]),
|
|
128
|
+
close: String(k[4]),
|
|
129
|
+
volume: String(k[5]),
|
|
130
|
+
trades: Number(k[8] ?? 0),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
// ── Account ──
|
|
134
|
+
async getBalance() {
|
|
135
|
+
const account = await this._signedGet("/fapi/v2/account");
|
|
136
|
+
const totalWallet = Number(account.totalWalletBalance ?? 0);
|
|
137
|
+
const unrealizedPnl = Number(account.totalUnrealizedProfit ?? 0);
|
|
138
|
+
const available = Number(account.availableBalance ?? 0);
|
|
139
|
+
const marginUsed = Number(account.totalInitialMargin ?? 0);
|
|
140
|
+
return {
|
|
141
|
+
equity: String(totalWallet + unrealizedPnl),
|
|
142
|
+
available: String(available),
|
|
143
|
+
marginUsed: String(marginUsed),
|
|
144
|
+
unrealizedPnl: String(unrealizedPnl),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async getPositions() {
|
|
148
|
+
const data = await this._signedGet("/fapi/v2/positionRisk");
|
|
149
|
+
return (data ?? [])
|
|
150
|
+
.filter((p) => Number(p.positionAmt ?? 0) !== 0)
|
|
151
|
+
.map((p) => {
|
|
152
|
+
const amt = Number(p.positionAmt ?? 0);
|
|
153
|
+
return {
|
|
154
|
+
symbol: String(p.symbol ?? ""),
|
|
155
|
+
side: amt > 0 ? "long" : "short",
|
|
156
|
+
size: String(Math.abs(amt)),
|
|
157
|
+
entryPrice: String(p.entryPrice ?? "0"),
|
|
158
|
+
markPrice: String(p.markPrice ?? "0"),
|
|
159
|
+
liquidationPrice: String(p.liquidationPrice ?? "0"),
|
|
160
|
+
unrealizedPnl: String(p.unRealizedProfit ?? "0"),
|
|
161
|
+
leverage: Number(p.leverage ?? 1),
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async getOpenOrders() {
|
|
166
|
+
const orders = await this._signedGet("/fapi/v1/openOrders");
|
|
167
|
+
return (orders ?? []).map((o) => ({
|
|
168
|
+
orderId: String(o.orderId ?? ""),
|
|
169
|
+
symbol: String(o.symbol ?? ""),
|
|
170
|
+
side: String(o.side).toLowerCase(),
|
|
171
|
+
price: String(o.price ?? "0"),
|
|
172
|
+
size: String(o.origQty ?? "0"),
|
|
173
|
+
filled: String(o.executedQty ?? "0"),
|
|
174
|
+
status: String(o.status ?? ""),
|
|
175
|
+
type: String(o.type ?? ""),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
async getOrderHistory(limit = 30) {
|
|
179
|
+
// allOrders requires symbol — get from positions or recent trades
|
|
180
|
+
const positions = await this.getPositions();
|
|
181
|
+
const symbols = new Set(positions.map(p => p.symbol));
|
|
182
|
+
if (symbols.size === 0) {
|
|
183
|
+
// Fallback: query the most common perp symbols when no open positions exist
|
|
184
|
+
for (const s of ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"]) {
|
|
185
|
+
symbols.add(s);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const allOrders = [];
|
|
189
|
+
for (const sym of symbols) {
|
|
190
|
+
try {
|
|
191
|
+
const orders = await this._signedGet("/fapi/v1/allOrders", {
|
|
192
|
+
symbol: sym,
|
|
193
|
+
limit: String(limit),
|
|
194
|
+
});
|
|
195
|
+
for (const o of orders ?? []) {
|
|
196
|
+
allOrders.push({
|
|
197
|
+
orderId: String(o.orderId ?? ""),
|
|
198
|
+
symbol: String(o.symbol ?? ""),
|
|
199
|
+
side: String(o.side).toLowerCase(),
|
|
200
|
+
price: String(o.price ?? "0"),
|
|
201
|
+
size: String(o.origQty ?? "0"),
|
|
202
|
+
filled: String(o.executedQty ?? "0"),
|
|
203
|
+
status: String(o.status ?? ""),
|
|
204
|
+
type: String(o.type ?? ""),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch { /* skip */ }
|
|
209
|
+
}
|
|
210
|
+
return allOrders.slice(0, limit);
|
|
211
|
+
}
|
|
212
|
+
async getTradeHistory(limit = 30) {
|
|
213
|
+
const positions = await this.getPositions();
|
|
214
|
+
const symbols = new Set(positions.map(p => p.symbol));
|
|
215
|
+
if (symbols.size === 0)
|
|
216
|
+
symbols.add("BTCUSDT");
|
|
217
|
+
const allTrades = [];
|
|
218
|
+
for (const sym of symbols) {
|
|
219
|
+
try {
|
|
220
|
+
const trades = await this._signedGet("/fapi/v1/userTrades", {
|
|
221
|
+
symbol: sym,
|
|
222
|
+
limit: String(limit),
|
|
223
|
+
});
|
|
224
|
+
for (const t of trades ?? []) {
|
|
225
|
+
allTrades.push({
|
|
226
|
+
time: Number(t.time ?? 0),
|
|
227
|
+
symbol: String(t.symbol ?? ""),
|
|
228
|
+
side: t.buyer ? "buy" : "sell",
|
|
229
|
+
price: String(t.price ?? "0"),
|
|
230
|
+
size: String(t.qty ?? "0"),
|
|
231
|
+
fee: String(t.commission ?? "0"),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch { /* skip */ }
|
|
236
|
+
}
|
|
237
|
+
return allTrades.sort((a, b) => b.time - a.time).slice(0, limit);
|
|
238
|
+
}
|
|
239
|
+
async getFundingPayments(limit = 200) {
|
|
240
|
+
const data = await this._signedGet("/fapi/v1/income", {
|
|
241
|
+
incomeType: "FUNDING_FEE",
|
|
242
|
+
limit: String(limit),
|
|
243
|
+
});
|
|
244
|
+
return (data ?? []).map((f) => ({
|
|
245
|
+
time: Number(f.time ?? 0),
|
|
246
|
+
symbol: String(f.symbol ?? ""),
|
|
247
|
+
payment: String(f.income ?? "0"),
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
// ── Trading ──
|
|
251
|
+
async marketOrder(symbol, side, size) {
|
|
252
|
+
return this._signedPost("/fapi/v1/order", {
|
|
253
|
+
symbol: symbol.toUpperCase(),
|
|
254
|
+
side: side.toUpperCase(),
|
|
255
|
+
type: "MARKET",
|
|
256
|
+
quantity: size,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async limitOrder(symbol, side, price, size, opts) {
|
|
260
|
+
const params = {
|
|
261
|
+
symbol: symbol.toUpperCase(),
|
|
262
|
+
side: side.toUpperCase(),
|
|
263
|
+
type: "LIMIT",
|
|
264
|
+
price,
|
|
265
|
+
quantity: size,
|
|
266
|
+
timeInForce: opts?.tif?.toUpperCase() || "GTC",
|
|
267
|
+
};
|
|
268
|
+
if (opts?.reduceOnly)
|
|
269
|
+
params.reduceOnly = "true";
|
|
270
|
+
return this._signedPost("/fapi/v1/order", params);
|
|
271
|
+
}
|
|
272
|
+
async editOrder(symbol, orderId, price, size) {
|
|
273
|
+
// Aster has no atomic edit — cancel + replace
|
|
274
|
+
const openOrders = await this.getOpenOrders();
|
|
275
|
+
const existing = openOrders.find(o => o.orderId === orderId);
|
|
276
|
+
const side = existing?.side ?? "buy";
|
|
277
|
+
await this.cancelOrder(symbol, orderId);
|
|
278
|
+
return this.limitOrder(symbol, side, price, size);
|
|
279
|
+
}
|
|
280
|
+
async cancelOrder(symbol, orderId) {
|
|
281
|
+
return this._signedDelete("/fapi/v1/order", {
|
|
282
|
+
symbol: symbol.toUpperCase(),
|
|
283
|
+
orderId,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
async cancelAllOrders(symbol) {
|
|
287
|
+
if (!symbol) {
|
|
288
|
+
// Cancel all for all symbols with open orders
|
|
289
|
+
const orders = await this.getOpenOrders();
|
|
290
|
+
const symbols = new Set(orders.map(o => o.symbol));
|
|
291
|
+
const results = [];
|
|
292
|
+
for (const sym of symbols) {
|
|
293
|
+
results.push(await this._signedDelete("/fapi/v1/allOpenOrders", { symbol: sym }));
|
|
294
|
+
}
|
|
295
|
+
return results;
|
|
296
|
+
}
|
|
297
|
+
return this._signedDelete("/fapi/v1/allOpenOrders", { symbol: symbol.toUpperCase() });
|
|
298
|
+
}
|
|
299
|
+
// ── Risk ──
|
|
300
|
+
async setLeverage(symbol, leverage, marginMode) {
|
|
301
|
+
// Set margin type first if specified
|
|
302
|
+
if (marginMode) {
|
|
303
|
+
try {
|
|
304
|
+
await this._signedPost("/fapi/v1/marginType", {
|
|
305
|
+
symbol: symbol.toUpperCase(),
|
|
306
|
+
marginType: marginMode === "cross" ? "CROSSED" : "ISOLATED",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// May fail if already set — non-critical
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return this._signedPost("/fapi/v1/leverage", {
|
|
314
|
+
symbol: symbol.toUpperCase(),
|
|
315
|
+
leverage: String(leverage),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
async stopOrder(symbol, side, size, triggerPrice, opts) {
|
|
319
|
+
const params = {
|
|
320
|
+
symbol: symbol.toUpperCase(),
|
|
321
|
+
side: side.toUpperCase(),
|
|
322
|
+
quantity: size,
|
|
323
|
+
stopPrice: triggerPrice,
|
|
324
|
+
type: opts?.limitPrice ? "STOP" : "STOP_MARKET",
|
|
325
|
+
};
|
|
326
|
+
if (opts?.limitPrice) {
|
|
327
|
+
params.price = opts.limitPrice;
|
|
328
|
+
params.timeInForce = "GTC";
|
|
329
|
+
}
|
|
330
|
+
if (opts?.reduceOnly)
|
|
331
|
+
params.reduceOnly = "true";
|
|
332
|
+
return this._signedPost("/fapi/v1/order", params);
|
|
333
|
+
}
|
|
334
|
+
// ── Withdraw (unified interface) ──
|
|
335
|
+
async withdraw(amount, _destination, _opts) {
|
|
336
|
+
// Aster withdrawal is via separate spot/wallet API, not futures API
|
|
337
|
+
throw new Error("Aster withdrawal requires the spot API (not available in futures mode). Use the Aster web UI to withdraw.");
|
|
338
|
+
}
|
|
339
|
+
// ── Internal HTTP helpers ──
|
|
340
|
+
_sign(params) {
|
|
341
|
+
params.timestamp = String(Date.now());
|
|
342
|
+
params.recvWindow = "5000";
|
|
343
|
+
const queryString = Object.entries(params)
|
|
344
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
345
|
+
.join("&");
|
|
346
|
+
const signature = createHmac("sha256", this._apiSecret)
|
|
347
|
+
.update(queryString)
|
|
348
|
+
.digest("hex");
|
|
349
|
+
return `${queryString}&signature=${signature}`;
|
|
350
|
+
}
|
|
351
|
+
async _publicGet(path, params = {}) {
|
|
352
|
+
const qs = Object.entries(params)
|
|
353
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
|
354
|
+
.join("&");
|
|
355
|
+
const url = `${this._baseUrl}${path}${qs ? `?${qs}` : ""}`;
|
|
356
|
+
const res = await fetch(url);
|
|
357
|
+
if (!res.ok) {
|
|
358
|
+
const text = await res.text();
|
|
359
|
+
throw new Error(`GET ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
360
|
+
}
|
|
361
|
+
return res.json();
|
|
362
|
+
}
|
|
363
|
+
async _signedGet(path, params = {}) {
|
|
364
|
+
if (this.isReadOnly)
|
|
365
|
+
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
366
|
+
const signed = this._sign({ ...params });
|
|
367
|
+
const url = `${this._baseUrl}${path}?${signed}`;
|
|
368
|
+
const res = await fetch(url, {
|
|
369
|
+
headers: { "X-MBX-APIKEY": this._apiKey },
|
|
370
|
+
});
|
|
371
|
+
if (!res.ok) {
|
|
372
|
+
const text = await res.text();
|
|
373
|
+
throw new Error(`GET ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
374
|
+
}
|
|
375
|
+
return res.json();
|
|
376
|
+
}
|
|
377
|
+
async _signedPost(path, params = {}) {
|
|
378
|
+
if (this.isReadOnly)
|
|
379
|
+
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
380
|
+
const signed = this._sign({ ...params });
|
|
381
|
+
const res = await fetch(`${this._baseUrl}${path}`, {
|
|
382
|
+
method: "POST",
|
|
383
|
+
headers: {
|
|
384
|
+
"X-MBX-APIKEY": this._apiKey,
|
|
385
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
386
|
+
},
|
|
387
|
+
body: signed,
|
|
388
|
+
});
|
|
389
|
+
if (!res.ok) {
|
|
390
|
+
const text = await res.text();
|
|
391
|
+
throw new Error(`POST ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
392
|
+
}
|
|
393
|
+
return res.json();
|
|
394
|
+
}
|
|
395
|
+
async _signedDelete(path, params = {}) {
|
|
396
|
+
if (this.isReadOnly)
|
|
397
|
+
throw new Error("No API key configured for Aster. Set ASTER_API_KEY and ASTER_API_SECRET in ~/.perp/.env");
|
|
398
|
+
const signed = this._sign({ ...params });
|
|
399
|
+
const res = await fetch(`${this._baseUrl}${path}?${signed}`, {
|
|
400
|
+
method: "DELETE",
|
|
401
|
+
headers: { "X-MBX-APIKEY": this._apiKey },
|
|
402
|
+
});
|
|
403
|
+
if (!res.ok) {
|
|
404
|
+
const text = await res.text();
|
|
405
|
+
throw new Error(`DELETE ${path} failed (${res.status}): ${text.slice(0, 200)}`);
|
|
406
|
+
}
|
|
407
|
+
return res.json();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability sub-interfaces for exchange-specific features.
|
|
3
|
+
* Use type guard functions (isTwapCapable, hasPacificaSdk, etc.) instead of instanceof checks.
|
|
4
|
+
*/
|
|
5
|
+
import type { ExchangeAdapter } from "./interface.js";
|
|
6
|
+
export interface WithdrawCapable {
|
|
7
|
+
withdraw(amount: string, destination: string, opts?: {
|
|
8
|
+
assetId?: number;
|
|
9
|
+
routeType?: number;
|
|
10
|
+
}): Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface TwapCapable {
|
|
13
|
+
twapOrder(symbol: string, side: "buy" | "sell", size: string, duration: number, opts?: {
|
|
14
|
+
reduceOnly?: boolean;
|
|
15
|
+
}): Promise<unknown>;
|
|
16
|
+
twapCancel(symbol: string, twapId: number): Promise<unknown>;
|
|
17
|
+
}
|
|
18
|
+
export interface TpSlCapable {
|
|
19
|
+
setTpSl(symbol: string, side: "buy" | "sell", opts: {
|
|
20
|
+
tp?: string;
|
|
21
|
+
tpLimit?: string;
|
|
22
|
+
sl?: string;
|
|
23
|
+
size?: string;
|
|
24
|
+
}): Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
export interface TriggerOrderCapable {
|
|
27
|
+
triggerOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, type: string, opts?: Record<string, unknown>): Promise<unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface DexCapable {
|
|
30
|
+
readonly dex: string;
|
|
31
|
+
setDex(dex: string): void;
|
|
32
|
+
listDeployedDexes(): Promise<{
|
|
33
|
+
name: string;
|
|
34
|
+
deployer: string;
|
|
35
|
+
assets: string[];
|
|
36
|
+
}[]>;
|
|
37
|
+
}
|
|
38
|
+
export interface SubAccountCapable {
|
|
39
|
+
createSubAccount(name: string): Promise<unknown>;
|
|
40
|
+
subAccountTransfer(subAccountUser: string, isDeposit: boolean, amount: number): Promise<unknown>;
|
|
41
|
+
}
|
|
42
|
+
export interface PacificaSdkCapable {
|
|
43
|
+
readonly sdk: unknown;
|
|
44
|
+
readonly publicKey: string;
|
|
45
|
+
readonly signer: (msg: Uint8Array) => Promise<Uint8Array>;
|
|
46
|
+
}
|
|
47
|
+
export interface LighterAccountCapable {
|
|
48
|
+
readonly accountIndex: number;
|
|
49
|
+
readonly address: string;
|
|
50
|
+
setupApiKey(apiKeyIndex?: number): Promise<{
|
|
51
|
+
privateKey: string;
|
|
52
|
+
publicKey: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
export interface EvmAddressCapable {
|
|
56
|
+
readonly address: string;
|
|
57
|
+
}
|
|
58
|
+
export interface QueryOrderCapable {
|
|
59
|
+
queryOrder(orderId: number): Promise<unknown>;
|
|
60
|
+
}
|
|
61
|
+
export interface UsdTransferCapable {
|
|
62
|
+
usdTransfer(amount: number, destination: string): Promise<unknown>;
|
|
63
|
+
}
|
|
64
|
+
export declare function isWithdrawCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & WithdrawCapable;
|
|
65
|
+
export declare function isTwapCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & TwapCapable;
|
|
66
|
+
export declare function isTpSlCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & TpSlCapable;
|
|
67
|
+
export declare function isTriggerOrderCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & TriggerOrderCapable;
|
|
68
|
+
export declare function isDexCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & DexCapable;
|
|
69
|
+
export declare function isSubAccountCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & SubAccountCapable;
|
|
70
|
+
export declare function hasPacificaSdk(adapter: ExchangeAdapter): adapter is ExchangeAdapter & PacificaSdkCapable;
|
|
71
|
+
export declare function hasLighterAccount(adapter: ExchangeAdapter): adapter is ExchangeAdapter & LighterAccountCapable;
|
|
72
|
+
export declare function hasEvmAddress(adapter: ExchangeAdapter): adapter is ExchangeAdapter & EvmAddressCapable;
|
|
73
|
+
export declare function isQueryOrderCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & QueryOrderCapable;
|
|
74
|
+
export declare function isUsdTransferCapable(adapter: ExchangeAdapter): adapter is ExchangeAdapter & UsdTransferCapable;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability sub-interfaces for exchange-specific features.
|
|
3
|
+
* Use type guard functions (isTwapCapable, hasPacificaSdk, etc.) instead of instanceof checks.
|
|
4
|
+
*/
|
|
5
|
+
// ── Type Guards ──
|
|
6
|
+
export function isWithdrawCapable(adapter) {
|
|
7
|
+
return typeof adapter.withdraw === "function";
|
|
8
|
+
}
|
|
9
|
+
export function isTwapCapable(adapter) {
|
|
10
|
+
return typeof adapter.twapOrder === "function"
|
|
11
|
+
&& typeof adapter.twapCancel === "function";
|
|
12
|
+
}
|
|
13
|
+
export function isTpSlCapable(adapter) {
|
|
14
|
+
return typeof adapter.setTpSl === "function";
|
|
15
|
+
}
|
|
16
|
+
export function isTriggerOrderCapable(adapter) {
|
|
17
|
+
return typeof adapter.triggerOrder === "function";
|
|
18
|
+
}
|
|
19
|
+
export function isDexCapable(adapter) {
|
|
20
|
+
return typeof adapter.setDex === "function"
|
|
21
|
+
&& typeof adapter.listDeployedDexes === "function";
|
|
22
|
+
}
|
|
23
|
+
export function isSubAccountCapable(adapter) {
|
|
24
|
+
return typeof adapter.createSubAccount === "function";
|
|
25
|
+
}
|
|
26
|
+
export function hasPacificaSdk(adapter) {
|
|
27
|
+
return adapter.name === "pacifica"
|
|
28
|
+
&& adapter.sdk != null
|
|
29
|
+
&& typeof adapter.sdk === "object"
|
|
30
|
+
&& typeof adapter.publicKey === "string";
|
|
31
|
+
}
|
|
32
|
+
export function hasLighterAccount(adapter) {
|
|
33
|
+
return typeof adapter.accountIndex === "number"
|
|
34
|
+
&& typeof adapter.setupApiKey === "function";
|
|
35
|
+
}
|
|
36
|
+
export function hasEvmAddress(adapter) {
|
|
37
|
+
return typeof adapter.address === "string";
|
|
38
|
+
}
|
|
39
|
+
export function isQueryOrderCapable(adapter) {
|
|
40
|
+
return typeof adapter.queryOrder === "function";
|
|
41
|
+
}
|
|
42
|
+
export function isUsdTransferCapable(adapter) {
|
|
43
|
+
return typeof adapter.usdTransfer === "function";
|
|
44
|
+
}
|
|
@@ -41,7 +41,7 @@ export declare class HyperliquidSpotAdapter implements SpotAdapter {
|
|
|
41
41
|
transferUsdcToPerp(amount: number): Promise<unknown>;
|
|
42
42
|
/** Get the spot asset index (without the 10000 offset) */
|
|
43
43
|
getSpotIndex(base: string): number | undefined;
|
|
44
|
-
/** Get spot size decimals for a base token */
|
|
44
|
+
/** Get spot size decimals for a base token — from API data, not hardcoded */
|
|
45
45
|
getSpotDecimals(base: string): {
|
|
46
46
|
size: number;
|
|
47
47
|
price: number;
|
|
@@ -98,23 +98,28 @@ export class HyperliquidSpotAdapter {
|
|
|
98
98
|
if (t.szDecimals !== undefined)
|
|
99
99
|
tokenSzDec.set(t.index, t.szDecimals);
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
// Fetch accurate mid prices from allMids (markPx in metaCtx is unreliable for spot)
|
|
102
|
+
const allMids = await this._infoPost({ type: "allMids" });
|
|
103
|
+
const usdcTokenIndex = metaCtx[0].tokens?.find(t => t.name === "USDC")?.index ?? 0;
|
|
102
104
|
const markets = [];
|
|
103
|
-
for (
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
for (const u of metaCtx[0].universe) {
|
|
106
|
+
// Only USDC-quoted pairs
|
|
107
|
+
if (u.tokens[1] !== usdcTokenIndex)
|
|
108
|
+
continue;
|
|
106
109
|
const baseToken = tokenNames.get(u.tokens[0]) ?? "";
|
|
107
|
-
const quoteToken = tokenNames.get(u.tokens[1]) ?? "USDC";
|
|
108
110
|
if (!baseToken)
|
|
109
111
|
continue;
|
|
112
|
+
// Price from allMids using universe.name (e.g., "PURR/USDC" or "@107")
|
|
113
|
+
const midPrice = allMids[u.name] ?? "0";
|
|
114
|
+
const spotDec = this._spotDecimals.get(baseToken.toUpperCase());
|
|
110
115
|
markets.push({
|
|
111
116
|
symbol: `${baseToken}/USDC`,
|
|
112
117
|
baseToken,
|
|
113
|
-
quoteToken,
|
|
114
|
-
markPrice:
|
|
115
|
-
volume24h:
|
|
116
|
-
sizeDecimals: tokenSzDec.get(u.tokens[0]) ?? 2,
|
|
117
|
-
priceDecimals: 6,
|
|
118
|
+
quoteToken: "USDC",
|
|
119
|
+
markPrice: midPrice,
|
|
120
|
+
volume24h: "0",
|
|
121
|
+
sizeDecimals: spotDec?.size ?? tokenSzDec.get(u.tokens[0]) ?? 2,
|
|
122
|
+
priceDecimals: spotDec?.price ?? 6,
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
return markets;
|
|
@@ -230,9 +235,12 @@ export class HyperliquidSpotAdapter {
|
|
|
230
235
|
getSpotIndex(base) {
|
|
231
236
|
return this._spotAssetMap.get(base.toUpperCase());
|
|
232
237
|
}
|
|
233
|
-
/** Get spot size decimals for a base token */
|
|
238
|
+
/** Get spot size decimals for a base token — from API data, not hardcoded */
|
|
234
239
|
getSpotDecimals(base) {
|
|
235
|
-
|
|
240
|
+
const dec = this._spotDecimals.get(base.toUpperCase());
|
|
241
|
+
if (dec)
|
|
242
|
+
return dec;
|
|
243
|
+
throw new Error(`No decimal info for spot token ${base}. Ensure init() completed successfully.`);
|
|
236
244
|
}
|
|
237
245
|
/**
|
|
238
246
|
* Resolve a symbol to its actual spot base token and index.
|
|
@@ -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/
|
|
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
|
|
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)
|