@@ -22,6 +22,7 @@ import { TransactionDB } from "../../db/transactions/db";
2222import {
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