Too often, examples given in documentations are very crude and minimal. Such is the case with the Web3.js documentation, which gives this example for batch requests:
var contract = new web3.eth.Contract(abi, address);
var batch = new web3.BatchRequest();
batch.add(web3.eth.getBalance.request('0x0000000000000000000000000000000000000000', 'latest', callback));
batch.add(contract.methods.balance(address).call.request({from: '0x0000000000000000000000000000000000000000'}, callback2));
batch.execute();
The problem with this example is that it doesn't tell you:
- How to have callbacks that return something else that a transaction hash;
- How to return a value from the last execution;
- How to deal with errors.
Here is an example incorporating all those missing elements to make up for it: let's say we want to stake WETH and, to do so, first wrap our ETH; then we want to return true if the stake was successful, else return false. We'll have to code the following steps:
- Add the wrapping transaction to the batch
- Add the staking transaction to the batch
- Execute the batch
- Return stake success boolean
In the code below, you can replace some of my console.log lines with actual code if you need, or discard them if you don't need them.
Credit: the following code was inspired to me by this tutorial by Aniruddha Deshmukh. Shout outs to him!
Note: if the second transaction depends on the first one having been executed to succeed, and would fail by itself, then I didn't find any way to estimate the gas fees for that second transaction in advance. If someone has the answer, please share!
// This method returns true if both transactions are successful, false otherwise
const wrapAndStakeEth = (walletAddrress, _tokenAmount) => {
// Initialize wrapping contract
let ethToWethContract = new web3.eth.Contract(ethToWethAbi, ethToWethContractAddress);
// Initialize staking contract
let stakeContract = new web3.eth.Contract(ethToWethAbi, ethToWethContractAddress);
// Create batch
let batch = new web3.BatchRequest();
return new Promise((resolve, reject) => {
// Wrap ETH to WETH
batch.add(
ethToWethContract.methods.deposit().send.request(
{ from: walletAddress, value: _tokenAmount },
(error, txnHash) => {
if (error) reject(error);
console.log('~ Wrap txnHash', txnHash)
waitForReceipt(txnHash, (receipt) => {
console.log('~ Wrap receipt', receipt)
if (receipt.status) {
console.log("Wrap transaction successful");
} else {
console.log("Wrap transaction failed");
resolve(false);
}
});
}
)
);
// Stake WETH
batch.add(
stakeContract.methods.stake(_tokenAmount).send.request(
{ from: walletAddress },
(error, txnHash) => {
if (error) reject(error);
console.log('~ Stake txnHash', txnHash)
waitForReceipt(txnHash, (receipt) => {
console.log('~ Stake receipt', receipt)
if (receipt.status) {
console.log("Stake transaction successful");
} else {
console.log("Stake transaction failed");
}
resolve(receipt.status);
});
}
)
);
// Execute batch
batch.execute();
});
}
// With getTransactionReceipt, access the transaction's details including status (fail/success)
export const waitForReceipt = (tx, cb) =>{
let receipt = web3.eth.getTransactionReceipt(tx, (err, receipt) => {
if(receipt){
cb(receipt);
} else {
window.setTimeout(function () {
waitForReceipt(tx, cb);
}, 2000);
}
});
}
And voilà! If you execute this, your wallet (like Metamask) will prompt you to validate two transactions in a row. Transaction 1 will be pending while transaction 2 is waiting to be signed.