ethers.js icon indicating copy to clipboard operation
ethers.js copied to clipboard

Await on Transaction Submission Waits for Confirmation

Open IttsKK opened this issue 1 year ago • 16 comments

Ethers Version

6.11.1

Search Terms

transaction submission, await, transaction confirmation, tx.wait, transaction hash

Describe the Problem

Expected Behavior: Using await on a transaction method (contract.function) should resolve as soon as the transaction is sent to the network and a transaction hash is received, allowing subsequent code to run immediately after the transaction submission.

Actual Behavior: Instead, await on contract.function only resolves after the transaction has been confirmed. This behavior is identical to what happens when using tx.wait(), which should only occur at the transaction confirmation stage. Removing tx.wait() from the workflow has no effect, as the initial await on the transaction already waits for confirmation, causing significant delays in UI updates that depend on the transaction submission.

Code Snippet

const tx = await contract.function();
console.log(`Transaction hash: ${tx.hash}`);  // This line is supposed to execute right after transaction submission
await tx.wait();  // This line should wait for the confirmation
console.log("Transaction confirmed.");

Contract ABI

No response

Errors

No response

Environment

node.js (v12 or newer), Browser (Chrome, Safari, etc), Hardhat

Environment (Other)

No response

IttsKK avatar May 03 '24 01:05 IttsKK

That is how tx = await contract.func() works; returning once the transaction is in the mempool. Calling await tx.wait() will then wait for it to be mined, resolving to the receipt. But the initial call certainly doesn't wait for the tx to be mined.

Can you provide an example where you do not believe this is happening?

ricmoo avatar May 03 '24 01:05 ricmoo

I understand that await contract.func() is expected to resolve as soon as the transaction reaches the mempool, with await tx.wait() then waiting for the transaction's confirmation. However, I am experiencing behavior that suggests the initial await on the transaction function is waiting until the transaction is confirmed before resolving.

  • Environment: I am running this within a React application, interacting with the network via MetaMask and ethers.
  • Code Snippet:
    const amountToApprove = ethers.parseEther(tokenAmount);
    console.log("Approving Tokens");
    const approve = await paymentTokenContract.approve(tokenSaleAddress, amountToApprove);
    console.log("Tokens Approved, Awaiting Confirmation");
    await approve.wait();
    console.log("Approval Confirmed, Initiating Purchase");
    const tx = await tokenSaleContract.buyTokens(paymentTokenAddress, amountToApprove);
    console.log("Purchase Transaction Submitted, Awaiting Confirmation");
    await tx.wait();
    console.log("Purchase Confirmed");
    

Observed Behavior:

  • The logs "Tokens Approved, Awaiting Confirmation" and "Purchase Transaction Submitted, Awaiting Confirmation" are expected to print immediately after the respective transactions (approve and buyTokens) are sent to the network.
  • However, these logs only appear after each transaction has been confirmed on the blockchain. This indicates that the await on both approve and buyTokens does not resolve upon submission to the mempool but rather waits until the transaction is confirmed.

IttsKK avatar May 03 '24 02:05 IttsKK

What network are you on? Since ethers needs to lookup the tx from the mempool, this can happen on networks (or backends) which do not return transactions in the mempool.

ricmoo avatar May 03 '24 02:05 ricmoo

I've tested this on BNB Smart Chain and locally with Hardhat. Everything was working fine up until about a week ago, and I haven't made any changes to the relevant parts of my codebase since then. I suspect this may be an issue with Metamask, but I thought it would be good to ask here as well.

IttsKK avatar May 03 '24 03:05 IttsKK

I've tested this on BNB Smart Chain and locally with Hardhat. Everything was working fine up until about a week ago, and I haven't made any changes to the relevant parts of my codebase since then. I suspect this may be an issue with Metamask, but I thought it would be good to ask here as well.

Did you figure this out? I've now just noticed the same same thing on BNB & Arbitrum.

cosullivan avatar May 06 '24 12:05 cosullivan

Started to get the same issue after MetaMask browser extension was updated on chrome. Using ETH Sepolia network. MetaMask Version 11.14.5

Using Firefox with older MetaMask version works correctly (same as before promise is resolved when tx was submitted and not on tx confirmation). MetaMask Version 11.12.4

ArnasAlex avatar May 06 '24 12:05 ArnasAlex

Can confirm that I have the same issues with Metamask since one of their last updates. Calling signer.sendTransaction or contract.connect(signer).<any method> gets stuck until the transaction is executed.

One workaround is to use sendUncheckedTransaction. I think it doesn't officially exist in v6 anymore but we copied it from v5:

export class UncheckedJsonRpcSigner extends JsonRpcSigner {
  async sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
    return this.sendUncheckedTransaction(transaction).then((hash) => {
      return <TransactionResponse>(<unknown>{
        hash,
        nonce: null,
        gasLimit: null,
        gasPrice: null,
        data: null,
        value: null,
        chainId: null,
        confirmations: 0,
        from: null,
        wait: (confirmations?: number) => {
          return this.provider.waitForTransaction(hash, confirmations)
        },
      })
    })
  }
}

// How to use it:
const uncheckedJsonRpcSigner = new UncheckedJsonRpcSigner(signer.provider, await signer.getAddress())
const result = await uncheckedJsonRpcSigner.sendTransaction(...)
// will resolve immediately

usame-algan avatar May 08 '24 14:05 usame-algan

Just chiming in to say this is happening to me as well. Using ethers 6.10.0 and MetaMask on Sepolia or my local node

Code looks like

const tx = await contract.function(<function args>)

const txHash = tx.hash

...

My app is sorta breaking cause it's expecting to get the txHash back immediately and redirect to an intermediate page while the transaction is waiting to be confirmed (it contains the await tx.wait()). Now it skips the intermediate page.

ip10x avatar May 10 '24 20:05 ip10x

Same problem

kaptn3 avatar May 13 '24 20:05 kaptn3

I've contacted MetaMask to find out more about any recent changes. :)

ricmoo avatar May 16 '24 14:05 ricmoo

Hello guys. If anyone have same issue with contract.METHOD() and tx.wait

  1. @gmxer nice solution!
  2. https://github.com/gmx-io/gmx-interface/blob/8e0eb420aa71e63720ba78f17f0117c1ce555810/src/lib/rpc/UncheckedJsonRpcSigner.ts

sp1r1don avatar May 31 '24 21:05 sp1r1don

@gmxer i noticed your solution doesn't work anymore either? also don't see the commit in gmx-io/gmx-interface

timongll avatar Oct 17 '24 07:10 timongll

Is there any way to fix this?

This is still a very big issue.

truthixify avatar Feb 22 '25 05:02 truthixify

What error are you getting with the Unchecked solution?

ricmoo avatar Feb 22 '25 05:02 ricmoo

I can’t reproduce the error now, because I’ve deleted the code and trying other solutions.

Nothing is working and this is the last part of the project I’m building 😔

truthixify avatar Feb 22 '25 16:02 truthixify

Issue exists for me on ETH network with Metamask 12.15.1. I'm using wagmi so I also think that issue is on Metamask side.

await writeContract returns hash only after transaction executed instead of after submitted.

Dalcor avatar Apr 07 '25 09:04 Dalcor