perp-cli 0.7.50 → 0.9.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.
@@ -478,7 +478,51 @@ export class LighterAdapter {
478
478
  orderExpiry: 0, // DEFAULT_IOC_EXPIRY
479
479
  nonce,
480
480
  });
481
- return this.sendTx(signed);
481
+ const result = await this.sendTx(signed);
482
+ // Verify fill via tx endpoint
483
+ const txResult = result;
484
+ if (txResult.tx_hash) {
485
+ // Poll tx status (may take a moment to process)
486
+ for (let attempt = 0; attempt < 5; attempt++) {
487
+ await new Promise(r => setTimeout(r, 500));
488
+ try {
489
+ const tx = await this.restGet("/tx", { by: "hash", value: txResult.tx_hash });
490
+ const status = Number(tx.status ?? 0);
491
+ if (status === 3) {
492
+ // status 3 = executed. Parse event_info for fill details
493
+ try {
494
+ const eventInfo = JSON.parse(String(tx.event_info ?? "{}"));
495
+ const takerOrder = eventInfo.to;
496
+ if (takerOrder && Number(takerOrder.rs ?? -1) === 0) {
497
+ return result; // fully filled
498
+ }
499
+ // Partially filled or unfilled
500
+ if (takerOrder && Number(takerOrder.rs) > 0 && Number(takerOrder.is) > 0) {
501
+ const filled = Number(takerOrder.is) - Number(takerOrder.rs);
502
+ if (filled > 0)
503
+ return result; // partially filled is OK for market orders
504
+ }
505
+ }
506
+ catch { /* parse error, fall through to status=3 return */ }
507
+ // status=3 means tx was executed (even if zero-fill IOC — strategy verifies position)
508
+ return result;
509
+ }
510
+ if (status > 3) {
511
+ // status > 3 likely means rejected/cancelled
512
+ throw new Error(`Market ${side} ${symbol}: tx rejected (status=${status}, hash=${txResult.tx_hash})`);
513
+ }
514
+ // status < 3: still processing, retry
515
+ }
516
+ catch (e) {
517
+ if (e instanceof Error && e.message.startsWith("Market "))
518
+ throw e;
519
+ // tx query failed, retry
520
+ }
521
+ }
522
+ // After 5 attempts, tx still not processed
523
+ throw new Error(`Market ${side} ${symbol}: tx not confirmed after 2.5s (hash=${txResult.tx_hash})`);
524
+ }
525
+ return result;
482
526
  }
483
527
  async limitOrder(symbol, side, price, size, opts) {
484
528
  this.ensureSigner();
@@ -182,7 +182,48 @@ export class PacificaAdapter {
182
182
  }
183
183
  async marketOrder(symbol, side, size, opts) {
184
184
  this.ensureSigner();
185
- return this.client.createMarketOrder({ symbol, amount: size, side: side === "buy" ? "bid" : "ask", reduce_only: opts?.reduceOnly ?? false, slippage_percent: "1" }, this.account, this.signMessage);
185
+ const result = await this.client.createMarketOrder({ symbol, amount: size, side: side === "buy" ? "bid" : "ask", reduce_only: opts?.reduceOnly ?? false, slippage_percent: "1" }, this.account, this.signMessage);
186
+ // Validate response
187
+ const r = result;
188
+ const data = (r.data ?? r);
189
+ if (r.success === false || r.error) {
190
+ throw new Error(`Market ${side} ${symbol}: ${r.error ?? 'order rejected'}`);
191
+ }
192
+ // Get order_id from response
193
+ const orderId = data.order_id ? String(data.order_id) : null;
194
+ if (orderId && !(opts?.reduceOnly ?? false)) {
195
+ // Poll order status via history_by_id
196
+ let lastPollError = null;
197
+ for (let attempt = 0; attempt < 5; attempt++) {
198
+ await new Promise(r => setTimeout(r, 500));
199
+ try {
200
+ const history = await this.client.getOrderHistoryById(Number(orderId));
201
+ const histData = (history.data ?? history);
202
+ if (Array.isArray(histData) && histData.length > 0) {
203
+ const latest = histData[histData.length - 1];
204
+ const status = String(latest.order_status ?? "");
205
+ const filled = parseFloat(String(latest.filled_amount ?? "0"));
206
+ if (status === "filled" || filled > 0)
207
+ return result; // confirmed fill
208
+ if (status === "cancelled" || status === "rejected") {
209
+ throw new Error(`Market ${side} ${symbol}: order ${orderId} ${status}`);
210
+ }
211
+ // Still processing, retry
212
+ }
213
+ }
214
+ catch (e) {
215
+ if (e instanceof Error && e.message.startsWith("Market "))
216
+ throw e;
217
+ lastPollError = e;
218
+ }
219
+ }
220
+ const errDetail = lastPollError instanceof Error ? ` (poll error: ${lastPollError.message})` : "";
221
+ throw new Error(`Market ${side} ${symbol}: order ${orderId} not confirmed after 2.5s${errDetail}`);
222
+ }
223
+ if (!(opts?.reduceOnly ?? false)) {
224
+ console.error(`[pacifica] Warning: market ${side} ${symbol} accepted but no order_id in response — skipping fill verification`);
225
+ }
226
+ return result;
186
227
  }
187
228
  async limitOrder(symbol, side, price, size, opts) {
188
229
  this.ensureSigner();
@@ -15,6 +15,10 @@ export const SPOT_PERP_TOKEN_MAP = {
15
15
  UETH: "ETH",
16
16
  USOL: "SOL",
17
17
  UFART: "FARTCOIN",
18
+ UPUMP: "PUMP",
19
+ LINK0: "LINK",
20
+ AVAX0: "AVAX",
21
+ AAVE0: "AAVE",
18
22
  };
19
23
  /** Reverse: perp symbol → U-token spot name on HL */
20
24
  export const PERP_TO_SPOT_MAP = Object.fromEntries(Object.entries(SPOT_PERP_TOKEN_MAP).map(([k, v]) => [v, k]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.7.50",
3
+ "version": "0.9.2",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",