Skip to content

Commit 78eb918

Browse files
committed
fix: Reset nonce if wallet is out of funds
1 parent af85f61 commit 78eb918

2 files changed

Lines changed: 29 additions & 29 deletions

File tree

src/utils/error.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ const _parseMessage = (error: unknown): string | null => {
1616

1717
export const isNonceAlreadyUsedError = (error: unknown) => {
1818
const message = _parseMessage(error);
19-
const errorPhrases = ["nonce too low", "already known"];
20-
2119
if (message) {
22-
return errorPhrases.some((phrase) =>
23-
message.toLowerCase().includes(phrase),
20+
return (
21+
message.includes("nonce too low") || message.includes("already known")
2422
);
2523
}
2624

src/worker/tasks/sendTransactionWorker.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { TransactionDB } from "../../db/transactions/db";
2222
import {
2323
acquireNonce,
2424
addSentNonce,
25+
deleteNoncesForBackendWallets,
2526
recycleNonce,
2627
syncLatestNonceFromOnchainIfHigher,
2728
} from "../../db/wallets/walletNonce";
@@ -279,10 +280,7 @@ const _sendTransaction = async (
279280

280281
const { queueId, chainId, from, to, overrides } = queuedTransaction;
281282
const chain = await getChain(chainId);
282-
const account = await getAccount({
283-
chainId: chainId,
284-
from: from,
285-
});
283+
const account = await getAccount({ chainId, from });
286284

287285
// Populate the transaction to resolve gas values.
288286
// This call throws if the execution would be reverted.
@@ -352,37 +350,41 @@ const _sendTransaction = async (
352350
await account.sendTransaction(populatedTransaction);
353351
transactionHash = sendTransactionResult.transactionHash;
354352
} catch (error: unknown) {
355-
// If the nonce is already seen onchain (nonce too low) or in mempool (replacement underpriced),
356-
// correct the DB nonce.
357-
if (isNonceAlreadyUsedError(error) || isReplacementGasFeeTooLow(error)) {
358-
const result = await syncLatestNonceFromOnchainIfHigher(chainId, from);
359-
job.log(`Re-synced nonce: ${result}`);
360-
} else {
361-
// Otherwise this nonce is not used yet. Recycle it to be used by a future transaction.
362-
job.log(`Recycling nonce: ${nonce}`);
363-
await recycleNonce(chainId, from, nonce);
364-
}
365-
366-
// Do not retry errors that are expected to be rejected by RPC again.
367353
if (isInsufficientFundsError(error)) {
368-
const { name, nativeCurrency } = await getChainMetadata(chain);
354+
// Insufficient funds. Do not retry
369355
const { gas, value = 0n } = populatedTransaction;
356+
const { name, nativeCurrency } = await getChainMetadata(chain);
357+
358+
// This and other pending transactions will fail.
359+
// Reset the nonce state for this wallet. The first transaction after the wallet is funded will resync the nonce.
360+
if (value === 0n) {
361+
await deleteNoncesForBackendWallets([{ chainId, walletAddress: from }]);
362+
}
363+
370364
const gasPrice =
371-
populatedTransaction.gasPrice ?? populatedTransaction.maxFeePerGas;
372-
373-
const minGasTokens = gasPrice
374-
? toTokens(gas * gasPrice + value, 18)
375-
: null;
376-
const errorMessage = minGasTokens
377-
? `Insufficient funds in ${account.address} on ${name}. Transaction requires > ${minGasTokens} ${nativeCurrency.symbol}.`
378-
: `Insufficient funds in ${account.address} on ${name}. Transaction requires more ${nativeCurrency.symbol}.`;
365+
populatedTransaction.gasPrice ??
366+
populatedTransaction.maxFeePerGas ??
367+
0n;
368+
const minGasTokens = toTokens(gas * gasPrice + value, 18);
369+
const errorMessage = `Insufficient funds in ${account.address} on ${name}. Transaction requires > ${minGasTokens} ${nativeCurrency.symbol}.`;
370+
379371
return {
380372
...queuedTransaction,
381373
status: "errored",
382374
errorMessage,
383375
} satisfies ErroredTransaction;
384376
}
385377

378+
if (isNonceAlreadyUsedError(error) || isReplacementGasFeeTooLow(error)) {
379+
// Nonce is already used (onchain or in mempool). Resync to correct the DB nonce.
380+
const result = await syncLatestNonceFromOnchainIfHigher(chainId, from);
381+
job.log(`Re-synced nonce: ${result}`);
382+
} else {
383+
// Other error: assume the nonce is not used. Recycle it to be used by a future transaction.
384+
job.log(`Recycling nonce: ${nonce}`);
385+
await recycleNonce(chainId, from, nonce);
386+
}
387+
386388
throw wrapError(error, "RPC");
387389
}
388390

0 commit comments

Comments
 (0)