Skip to content

Commit

Permalink
fix: Reset nonce if wallet is out of funds
Browse files Browse the repository at this point in the history
  • Loading branch information
arcoraven committed Dec 6, 2024
1 parent af85f61 commit 78eb918
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 29 deletions.
6 changes: 2 additions & 4 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ const _parseMessage = (error: unknown): string | null => {

export const isNonceAlreadyUsedError = (error: unknown) => {
const message = _parseMessage(error);
const errorPhrases = ["nonce too low", "already known"];

if (message) {
return errorPhrases.some((phrase) =>
message.toLowerCase().includes(phrase),
return (
message.includes("nonce too low") || message.includes("already known")
);
}

Expand Down
52 changes: 27 additions & 25 deletions src/worker/tasks/sendTransactionWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { TransactionDB } from "../../db/transactions/db";
import {
acquireNonce,
addSentNonce,
deleteNoncesForBackendWallets,
recycleNonce,
syncLatestNonceFromOnchainIfHigher,
} from "../../db/wallets/walletNonce";
Expand Down Expand Up @@ -279,10 +280,7 @@ const _sendTransaction = async (

const { queueId, chainId, from, to, overrides } = queuedTransaction;
const chain = await getChain(chainId);
const account = await getAccount({
chainId: chainId,
from: from,
});
const account = await getAccount({ chainId, from });

// Populate the transaction to resolve gas values.
// This call throws if the execution would be reverted.
Expand Down Expand Up @@ -352,37 +350,41 @@ const _sendTransaction = async (
await account.sendTransaction(populatedTransaction);
transactionHash = sendTransactionResult.transactionHash;
} catch (error: unknown) {
// If the nonce is already seen onchain (nonce too low) or in mempool (replacement underpriced),
// correct the DB nonce.
if (isNonceAlreadyUsedError(error) || isReplacementGasFeeTooLow(error)) {
const result = await syncLatestNonceFromOnchainIfHigher(chainId, from);
job.log(`Re-synced nonce: ${result}`);
} else {
// Otherwise this nonce is not used yet. Recycle it to be used by a future transaction.
job.log(`Recycling nonce: ${nonce}`);
await recycleNonce(chainId, from, nonce);
}

// Do not retry errors that are expected to be rejected by RPC again.
if (isInsufficientFundsError(error)) {
const { name, nativeCurrency } = await getChainMetadata(chain);
// Insufficient funds. Do not retry
const { gas, value = 0n } = populatedTransaction;
const { name, nativeCurrency } = await getChainMetadata(chain);

// This and other pending transactions will fail.
// Reset the nonce state for this wallet. The first transaction after the wallet is funded will resync the nonce.
if (value === 0n) {
await deleteNoncesForBackendWallets([{ chainId, walletAddress: from }]);
}

const gasPrice =
populatedTransaction.gasPrice ?? populatedTransaction.maxFeePerGas;

const minGasTokens = gasPrice
? toTokens(gas * gasPrice + value, 18)
: null;
const errorMessage = minGasTokens
? `Insufficient funds in ${account.address} on ${name}. Transaction requires > ${minGasTokens} ${nativeCurrency.symbol}.`
: `Insufficient funds in ${account.address} on ${name}. Transaction requires more ${nativeCurrency.symbol}.`;
populatedTransaction.gasPrice ??
populatedTransaction.maxFeePerGas ??
0n;
const minGasTokens = toTokens(gas * gasPrice + value, 18);
const errorMessage = `Insufficient funds in ${account.address} on ${name}. Transaction requires > ${minGasTokens} ${nativeCurrency.symbol}.`;

return {
...queuedTransaction,
status: "errored",
errorMessage,
} satisfies ErroredTransaction;
}

if (isNonceAlreadyUsedError(error) || isReplacementGasFeeTooLow(error)) {
// Nonce is already used (onchain or in mempool). Resync to correct the DB nonce.
const result = await syncLatestNonceFromOnchainIfHigher(chainId, from);
job.log(`Re-synced nonce: ${result}`);
} else {
// Other error: assume the nonce is not used. Recycle it to be used by a future transaction.
job.log(`Recycling nonce: ${nonce}`);
await recycleNonce(chainId, from, nonce);
}

throw wrapError(error, "RPC");
}

Expand Down

0 comments on commit 78eb918

Please sign in to comment.