Skip to content

Commit fc7a02b

Browse files
Hiksangclaude
andcommitted
fix: lighter nonce retry with fresh query + exclude tests from tsc build
- Lighter ChangePubKey: retry up to 3 times with fresh /nextNonce query between attempts (was: single nonce+1 retry with stale nonce) - tsconfig.json: exclude src/__tests__ from build compilation to prevent vitest type errors in strict environments - Fix flaky test: match "Unknown" instead of "Unknown symbol" for getAssetIndex error message Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9defaa5 commit fc7a02b

3 files changed

Lines changed: 45 additions & 26 deletions

File tree

src/__tests__/integration/agent-features.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ describe("Agent Features Integration (Hyperliquid Mainnet)", () => {
542542
}, 30_000);
543543

544544
it("adapter getAssetIndex throws for unknown symbol", () => {
545-
expect(() => adapter.getAssetIndex("XYZNOTREAL999FAKE")).toThrow("Unknown symbol");
545+
expect(() => adapter.getAssetIndex("XYZNOTREAL999FAKE")).toThrow(/Unknown/i);
546546
});
547547

548548
it("adapter getMarkets returns well-formed data from real API", async () => {

src/exchanges/lighter.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -989,32 +989,50 @@ export class LighterAdapter implements ExchangeAdapter {
989989

990990
const result = await sendRes.json() as { code: number; message?: string; tx_hash?: string };
991991
if (result.code !== 200) {
992-
// Retry once with nonce+1 if invalid nonce (pending tx or stale nonce)
992+
// Retry with fresh nonce if invalid nonce (pending tx or stale nonce)
993993
if (result.message && /invalid nonce/i.test(result.message)) {
994-
const retryNonce = nonce + 1;
995-
const retrySigned = await client.signChangePubKey({
996-
pubkey: publicKey, nonce: retryNonce, apiKeyIndex, accountIndex: this._accountIndex,
997-
});
998-
if (retrySigned.error || !retrySigned.txInfo || !retrySigned.messageToSign) {
999-
throw new Error(`ChangePubKey retry failed: ${retrySigned.error ?? "incomplete response"}`);
1000-
}
1001-
const retryTxInfo = JSON.parse(retrySigned.txInfo);
1002-
retryTxInfo.L1Sig = await wallet.signMessage(retrySigned.messageToSign);
1003-
const retryRes = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
1004-
method: "POST",
1005-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
1006-
body: new URLSearchParams({
1007-
tx_type: String(retrySigned.txType ?? 0),
1008-
tx_info: JSON.stringify(retryTxInfo),
1009-
}),
1010-
});
1011-
if (!retryRes.ok) {
1012-
throw new Error(`ChangePubKey retry sendTx failed (${retryRes.status}): ${await retryRes.text()}`);
1013-
}
1014-
const retryResult = await retryRes.json() as { code: number; message?: string };
1015-
if (retryResult.code !== 200) {
1016-
throw new Error(`ChangePubKey failed after nonce retry: ${retryResult.message ?? JSON.stringify(retryResult)}`);
994+
const MAX_NONCE_RETRIES = 3;
995+
let lastError = result.message;
996+
997+
for (let attempt = 1; attempt <= MAX_NONCE_RETRIES; attempt++) {
998+
await new Promise(r => setTimeout(r, 500 * attempt));
999+
1000+
// Re-fetch nonce from API (may have updated since first call)
1001+
const freshNonceRes = await this.restGet("/nextNonce", {
1002+
account_index: String(this._accountIndex),
1003+
api_key_index: String(apiKeyIndex),
1004+
}) as { nonce?: number; next_nonce?: number };
1005+
const freshNonce = (freshNonceRes.nonce ?? freshNonceRes.next_nonce ?? nonce) + attempt;
1006+
1007+
const retrySigned = await client.signChangePubKey({
1008+
pubkey: publicKey, nonce: freshNonce, apiKeyIndex, accountIndex: this._accountIndex,
1009+
});
1010+
if (retrySigned.error || !retrySigned.txInfo || !retrySigned.messageToSign) {
1011+
lastError = retrySigned.error ?? "incomplete response";
1012+
continue;
1013+
}
1014+
const retryTxInfo = JSON.parse(retrySigned.txInfo);
1015+
retryTxInfo.L1Sig = await wallet.signMessage(retrySigned.messageToSign);
1016+
const retryRes = await fetch(`${this._baseUrl}/api/v1/sendTx`, {
1017+
method: "POST",
1018+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
1019+
body: new URLSearchParams({
1020+
tx_type: String(retrySigned.txType ?? 0),
1021+
tx_info: JSON.stringify(retryTxInfo),
1022+
}),
1023+
});
1024+
if (!retryRes.ok) {
1025+
lastError = `sendTx ${retryRes.status}: ${await retryRes.text()}`;
1026+
continue;
1027+
}
1028+
const retryResult = await retryRes.json() as { code: number; message?: string };
1029+
if (retryResult.code === 200) {
1030+
return { privateKey, publicKey }; // Success on retry
1031+
}
1032+
lastError = retryResult.message ?? JSON.stringify(retryResult);
1033+
if (!/invalid nonce/i.test(lastError)) break; // Different error, stop retrying
10171034
}
1035+
throw new Error(`ChangePubKey failed after ${MAX_NONCE_RETRIES} nonce retries: ${lastError}`);
10181036
} else {
10191037
throw new Error(`ChangePubKey failed: ${result.message ?? JSON.stringify(result)}`);
10201038
}

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"skipLibCheck": true,
1212
"resolveJsonModule": true
1313
},
14-
"include": ["src"]
14+
"include": ["src"],
15+
"exclude": ["src/__tests__"]
1516
}

0 commit comments

Comments
 (0)