From 49df78c07587f4e0d84b796be1b2c34758197457 Mon Sep 17 00:00:00 2001 From: discountry Date: Sun, 7 Dec 2025 22:48:23 +0800 Subject: [PATCH] fix nonce --- src/exchanges/lighter/gateway.ts | 49 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/exchanges/lighter/gateway.ts b/src/exchanges/lighter/gateway.ts index 03ef8d9..e960b0d 100644 --- a/src/exchanges/lighter/gateway.ts +++ b/src/exchanges/lighter/gateway.ts @@ -200,6 +200,8 @@ export class LighterGateway { private loggedCreateOrderPayload = false; private readonly logTxInfo: boolean; private readonly primaryApiKeyIndex: number; + private lastNonceRefreshAt = 0; + private orderChain: Promise = Promise.resolve(); private lastWsPositionUpdateAt = 0; private readonly lastWsPositionByMarket = new Map(); private httpPositionsEmptyLogged = false; @@ -357,13 +359,15 @@ export class LighterGateway { } async createOrder(params: CreateOrderParams): Promise { - await this.ensureInitialized(); - const conversion = this.mapCreateOrderParams(params); - const { baseAmountScaledString, priceScaledString, triggerPriceScaledString, ...signParams } = conversion; + const run = async (): Promise => { + await this.ensureInitialized(); + const conversion = this.mapCreateOrderParams(params); + const { baseAmountScaledString, priceScaledString, triggerPriceScaledString, ...signParams } = conversion; - const maxAttempts = 3; - for (let attempt = 0; attempt < maxAttempts; attempt++) { - const { apiKeyIndex, nonce } = + const apiKeyIndex = + this.primaryApiKeyIndex != null ? this.primaryApiKeyIndex : this.apiKeyIndices[0] ?? 0; + await this.refreshNonceForOrder(apiKeyIndex); + const { nonce } = this.primaryApiKeyIndex != null ? this.nonceManager.nextFor(this.primaryApiKeyIndex) : this.nonceManager.next(); try { const signed = await this.signer.signCreateOrder({ @@ -404,14 +408,20 @@ export class LighterGateway { } catch (error) { this.nonceManager.acknowledgeFailure(apiKeyIndex); if (isInvalidNonce(error)) { - await this.nonceManager.refresh(apiKeyIndex).catch((err) => this.logger("nonce.refresh", err)); - continue; // retry with freshly synced nonce + await this.refreshNonceThrottle(apiKeyIndex); } this.logger("createOrder", error); throw error; } - } - throw new Error("Failed to create order after refreshing nonce"); + }; + + // serialize order creation to avoid concurrent nonce consumption + const chain = this.orderChain.then(run, run); + this.orderChain = chain.then( + () => undefined, + () => undefined + ); + return chain; } async cancelOrder(params: { marketIndex?: number; orderId: number | string; apiKeyIndex?: number }): Promise { @@ -772,6 +782,25 @@ export class LighterGateway { return token; } + private async refreshNonceForOrder(apiKeyIndex: number): Promise { + try { + await this.nonceManager.refresh(apiKeyIndex); + this.lastNonceRefreshAt = Date.now(); + } catch (error) { + // Swallow refresh errors to avoid blocking order flow; real send will surface issues + this.logger("nonce.refresh.order", error); + } + } + + private async refreshNonceThrottle(apiKeyIndex: number): Promise { + const now = Date.now(); + if (now - this.lastNonceRefreshAt < 1500) { + return; // avoid spamming nextNonce; let next cycle try again + } + this.lastNonceRefreshAt = now; + await this.nonceManager.refresh(apiKeyIndex); + } + private async dispatchTransaction( txType: number, txInfo: string,