I read #435, but following the instructions don't guarantee that the transactions sent to an end-user wallet, such as MetaMask, will prompt the user to sign them in the same order.
Here's what I do:
const promises = [];
promises.push(createTokenApprovalTx());
promises.push(createMyTx());
await Promise.all(promises);
The 2nd transaction depends on the first one, that is, the token approval must be sufficient for the contract to be able to move tokens around. In the transaction creation functions, I call getTransactionCount to get the latest nonce and increment it for the 2nd transaction.
But the order is not enforced in the MetaMask popups. I couldn't identify a pattern for when it works and when it doesn't - it seems to be a stochastic process. I tried swapping the order of adding the transactions in the promises array, but that didn't move the needle either.
I find this a basic example and arguably the safer alternative to approve(MAX_UINT256). What's the best way to do this with ethers?


As you can see in the screenshots, the token approval comes 2nd, even if it should come 1st.
Nevermind, I had a JavaScript bug. I was asynchronously calling the contracts inside the createTokenApprovalTx and createMyTx functions, but I was not awaiting them (see promises.push above). Hence the order of the MetaMask prompts depended on the stochasticity of the JSON-RPC requests.
I rewrote my code like this:
const txs = await Promise.all(promises);
await Promise.all(
txs.map(tx => {
return tx.wait(1);
}),
);
So I am not doing any asynchronous call before the first Promise.all.
Reopening this because the latest solution only made it more likely to prompt the token approval transaction first, but it's still not a given.
I do not believe there is a way to enforce the order this way. You can bug metamask about this too, but the only way I think you can enforce this is by sending the first transaction, and waiting for it to be mined before sending the next.
Keep in mind that transactions can be cancelled. In MetaMask, a transaction sent could be cancelled within the MetaMask UI (which in actuality just sends a 0 ether transaction from and to the same account, with a much higher gas price, so the network prefers it, although cancelling is not guaranteed it usually works). So, it may be a better experience to wait for the transaction to be mined anyways. Again, depends on your use case, but the UX around multiple transactions is complicated and has many edge cases which are non-trivial to deal with...
Gotcha. I reached a similar conclusion after asking the same question on StackExchange and Twitter.
Now, there is a hack, which is to wait for the user to sign the 1st transaction. Works well with most wallets, although not in MetaMask Mobile:
https://github.com/MetaMask/metamask-mobile/issues/1249
Therefore, I agree, waiting for the 1st to be mined is the safest solution. To get the best of both worlds, one may want to use @PhABC's CREATE2 approach.
Oh yes. If you can use a contract wallet, in general, these problems can be solved since you can execute multiple transactions from the contract wallet in the same EOA transaction.
If you need a consistent address, you may be able to adapt Wisps as well: https://blog.ricmoo.com/wisps-the-magical-world-of-create2-5c2177027604
Now that is awesome!
Hi @PaulRBerg , I just read this thread and have a random question:
const txs = await Promise.all(promises);
await Promise.all(
txs.map(tx => {
return tx.wait(1);
}),
);
what exactly is the "1" in tx.wait(1) doing?
I couldnt find any reference for the wait method in the docs.
The 1 means wait for 1 block confirmation. The default is one, so it is not necessary, but if you pass in 0, then wait will return null if the transaction is not mined, and for Example, 2 would indicate to wait until there were two confirmations.
I think this is resolved, so I'm going to close it now. If not, please re-open.
Thanks! :)