Skip to content

Instantly share code, notes, and snippets.

@arcticfloyd1984
Created April 12, 2022 13:07
Show Gist options
  • Save arcticfloyd1984/a3c75f34226574a6774b573379151f14 to your computer and use it in GitHub Desktop.
Save arcticfloyd1984/a3c75f34226574a6774b573379151f14 to your computer and use it in GitHub Desktop.
Script for batched transactions
const { Biconomy } = require("@biconomy/mexa");
const { ethers } = require("ethers");
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
const privateKey = "0xd952fa86f1e2fed30fb3a6da6e5c24d7deb65b6bb46da3ece5f56fd39e64bbd0";
let userAddress1 = ethers.utils.getAddress("0x040a9cbC4453B0eeaE12f3210117B422B890C1ED");
let userAddress2 = ethers.utils.getAddress("0x4C07E2fa10f9871142883139B32Cb03F2A180494");
let userAddress3 = ethers.utils.getAddress("0x6561aD57cde7863bCE871977c951Af21bEC2E74C");
let userAddress4 = ethers.utils.getAddress("0xaa7f52b2bF183CE85c1d291D838030DF41018a44");
let userAddress5 = ethers.utils.getAddress("0x02649F6d43556e76CF7a515a9f589BB23287378d");
let userAddresses = [userAddress1, userAddress2, userAddress3, userAddress4, userAddress5];
let contracts = [];
let biconomy;
let jsonRpcProvider;
let signer;
let helperAttributes = {
domainType: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "verifyingContract", type: "address" },
{ name: "salt", type: "bytes32" },
],
forwardRequestType: [
{name:'from',type:'address'},
{name:'to',type:'address'},
{name:'token',type:'address'},
{name:'txGas',type:'uint256'},
{name:'tokenGasPrice',type:'uint256'},
{name:'batchId',type:'uint256'},
{name:'batchNonce',type:'uint256'},
{name:'deadline',type:'uint256'},
{name:'data',type:'bytes'}
],
biconomyForwarderDomainData: {
name : "Biconomy Forwarder",
version : "1",
}
};
let ethersProvider;
const abi = [{ "inputs": [{ "internalType": "address", "name": "forwarder", "type": "address" }], "name": "isTrustedForwarder", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "owner", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "quote", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "trustedForwarder", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [{ "internalType": "string", "name": "newQuote", "type": "string" }], "name": "setQuote", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "getQuote", "outputs": [{ "internalType": "string", "name": "currentQuote", "type": "string" }, { "internalType": "address", "name": "currentOwner", "type": "address" }], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "versionRecipient", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function", "constant": true }]
const init = async () => {
jsonRpcProvider = new ethers.providers.JsonRpcProvider("https://kovan.infura.io/v3/30125eccc47e475b9a9421f0eaf1e19c");
biconomy = new Biconomy(jsonRpcProvider, {
apiKey: "UoqE4xJwj.6146f43c-07a6-45a7-84f9-edbf9ba19480",
// apiKey: "yARl7lxj5.715a8264-060f-41c0-b588-146b42190e0c",
debug: true
});
ethersProvider = new ethers.providers.Web3Provider(biconomy);
signer = new ethers.Wallet(privateKey, jsonRpcProvider);
biconomy.onEvent(biconomy.READY, async () => {
for(let userAddressIndex = 0; userAddressIndex < userAddresses.length; userAddressIndex++) {
let contract = new ethers.Contract(
ethers.utils.getAddress("0x880176EDA9f1608A2Bf182385379bDcC1a65Dfcf"),
abi,
biconomy.getSignerByAddress(userAddresses[userAddressIndex])
);
contracts.push(contract);
}
contractInterface = new ethers.utils.Interface(abi);
await executeBatchedTransactionsEIP712();
}).onEvent(biconomy.ERROR, (error, message) => {
// Handle error while initializing mexa
console.log(message);
console.log(error);
});
}
const executeBatchedTransactionsEIP712 = async () => {
try {
let gasPrice = await ethersProvider.getGasPrice();
let forwarder = {address: "0xc1B06d4eFd335658dd0DCFA494CEEd8B985E4B0E", abi: [ { "inputs": [ { "internalType": "address", "name": "_owner", "type": "address" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "bytes32", "name": "domainSeparator", "type": "bytes32" }, { "indexed": false, "internalType": "bytes", "name": "domainValue", "type": "bytes" } ], "name": "DomainRegistered", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "txGas", "type": "uint256" }, { "internalType": "uint256", "name": "tokenGasPrice", "type": "uint256" }, { "internalType": "uint256", "name": "batchId", "type": "uint256" }, { "internalType": "uint256", "name": "batchNonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "internalType": "struct ERC20ForwardRequestTypes.ERC20ForwardRequest[]", "name": "reqs", "type": "tuple[]" }, { "internalType": "bytes32", "name": "domainSeparator", "type": "bytes32" }, { "internalType": "bytes[]", "name": "sigs", "type": "bytes[]" } ], "name": "executeBatchEIP712", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "txGas", "type": "uint256" }, { "internalType": "uint256", "name": "tokenGasPrice", "type": "uint256" }, { "internalType": "uint256", "name": "batchId", "type": "uint256" }, { "internalType": "uint256", "name": "batchNonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "internalType": "struct ERC20ForwardRequestTypes.ERC20ForwardRequest[]", "name": "reqs", "type": "tuple[]" }, { "internalType": "bytes[]", "name": "sigs", "type": "bytes[]" } ], "name": "executeBatchPersonalSign", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "txGas", "type": "uint256" }, { "internalType": "uint256", "name": "tokenGasPrice", "type": "uint256" }, { "internalType": "uint256", "name": "batchId", "type": "uint256" }, { "internalType": "uint256", "name": "batchNonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "internalType": "struct ERC20ForwardRequestTypes.ERC20ForwardRequest", "name": "req", "type": "tuple" }, { "internalType": "bytes32", "name": "domainSeparator", "type": "bytes32" }, { "internalType": "bytes", "name": "sig", "type": "bytes" } ], "name": "executeEIP712", "outputs": [ { "internalType": "bool", "name": "success", "type": "bool" }, { "internalType": "bytes", "name": "ret", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "OwnershipTransferred", "type": "event" }, { "inputs": [ { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "string", "name": "version", "type": "string" } ], "name": "registerDomainSeparator", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "name": "domains", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "EIP712_DOMAIN_TYPE", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "uint256", "name": "batchId", "type": "uint256" } ], "name": "getNonce", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "isOwner", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "REQUEST_TYPEHASH", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "txGas", "type": "uint256" }, { "internalType": "uint256", "name": "tokenGasPrice", "type": "uint256" }, { "internalType": "uint256", "name": "batchId", "type": "uint256" }, { "internalType": "uint256", "name": "batchNonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "internalType": "struct ERC20ForwardRequestTypes.ERC20ForwardRequest", "name": "req", "type": "tuple" }, { "internalType": "bytes32", "name": "domainSeparator", "type": "bytes32" }, { "internalType": "bytes", "name": "sig", "type": "bytes" } ], "name": "verifyEIP712", "outputs": [], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "from", "type": "address" }, { "internalType": "address", "name": "to", "type": "address" }, { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "txGas", "type": "uint256" }, { "internalType": "uint256", "name": "tokenGasPrice", "type": "uint256" }, { "internalType": "uint256", "name": "batchId", "type": "uint256" }, { "internalType": "uint256", "name": "batchNonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" } ], "internalType": "struct ERC20ForwardRequestTypes.ERC20ForwardRequest", "name": "req", "type": "tuple" }, { "internalType": "bytes", "name": "sig", "type": "bytes" } ], "name": "verifyPersonalSign", "outputs": [], "stateMutability": "view", "type": "function" } ]};
const forwarderContracts = [];
const to = ethers.utils.getAddress("0x880176EDA9f1608A2Bf182385379bDcC1a65Dfcf");
const domainSeparator = await getDomainSeperator(42);
const sigs = [];
const reqs = [];
for(let userAddressIndex = 0; userAddressIndex < userAddresses.length; userAddressIndex++) {
let { data } = await contracts[userAddressIndex].populateTransaction.setQuote(`newQuote_${userAddresses[userAddressIndex]}`);
let gasLimit = await ethersProvider.estimateGas({
to: ethers.utils.getAddress("0x880176EDA9f1608A2Bf182385379bDcC1a65Dfcf"),
from: userAddresses[userAddressIndex],
data: data,
});
let forwarderContract = new ethers.Contract(
forwarder.address,
forwarder.abi,
biconomy.getSignerByAddress(userAddresses[userAddressIndex])
);
forwarderContracts.push(forwarderContract);
const batchNonce = await forwarderContract.getNonce(userAddresses[userAddressIndex], userAddressIndex);
const batchId = userAddressIndex;
const gasLimitNum = Number(gasLimit.toNumber().toString());
const req = await buildForwardTxRequest({
account: (userAddresses[userAddressIndex]),
to,
gasLimitNum,
batchId,
batchNonce,
data,
});
reqs.push(req);
const dataToSign = await getDataToSignForEIP712(req);
console.log("signer", signer);
const sig = await signer._signTypedData(dataToSign.domain, dataToSign.types, dataToSign.message);
console.log("sig", sig);
sigs.push(sig);
console.log("sigs", sigs);
}
sendBatchedTransaction({
sigs,
domainSeparator,
reqs
}, "EIP712_SIGN");
} catch(error) {
console.log(error);
}
}
// const executeBatchedTransactionsPersonalSign = async () => {
// try {
// let { data } = await contract.populateTransaction.setQuote(newQuote);
// let gasPrice = await ethersProvider.getGasPrice();
// let gasLimit = await ethersProvider.estimateGas({
// to: config.contract.address,
// from: userAddress,
// data: data,
// });
// console.log(gasLimit.toString());
// console.log(gasPrice.toString());
// let forwarder = await getBiconomyForwarderConfig(42);
// let forwarderContract = new ethers.Contract(
// forwarder.address,
// forwarder.abi,
// biconomy.getSignerByAddress(userAddress)
// );
// const batchNonce = await forwarderContract.getNonce(userAddress, 0);
// //const batchId = await forwarderContract.getBatch(userAddress);
// console.log(batchNonce);
// const to = config.contract.address;
// const gasLimitNum = Number(gasLimit.toNumber().toString());
// console.log(gasLimitNum);
// const batchId = 0;
// const req = await buildForwardTxRequest({
// account: userAddress,
// to,
// gasLimitNum,
// batchId,
// batchNonce,
// data,
// });
// console.log(req);
// const hashToSign = getDataToSignForPersonalSign(req);
// walletSigner.signMessage(hashToSign)
// .then(function(sig){
// console.log('signature ' + sig);
// sendTransaction({userAddress, request:req, sig, signatureType:"PERSONAL_SIGN"});
// })
// .catch(function(error) {
// console.log(error)
// });
// } catch (error) {
// console.log(error);
// }
// }
const sendBatchedTransaction = async (batchedTransactionRequests, signatureType) => {
try {
/**
* batchedTransactionRequest = [{userAddress, request, sig, domainSeparator, signatureType}]
*/
let sigs = batchedTransactionRequests.sigs;
let requests = batchedTransactionRequests.reqs;
let domainSeparator = batchedTransactionRequests.domainSeparator;
console.log('Sending request to backend');
fetch('http://localhost:4000/api/v2/meta-tx/native-batch-transactions', {
method: "POST",
headers: {
"x-api-key": "yARl7lxj5.715a8264-060f-41c0-b588-146b42190e0c",
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({
to: "0x880176EDA9f1608A2Bf182385379bDcC1a65Dfcf",
apiId: "f0567e06-5f36-4826-bff3-1b8809642d56",
domainSeparator: domainSeparator,
requests: requests,
sigs: sigs,
userAddresses: userAddresses,
signatureType: signatureType
}),
})
.then((response) => response.json())
.then(function(result) {
console.log(result);
console.log(`Transaction sent by relayer with hash ${result.txHash}`);
return result.txHash;
// todo - fetch mined transaction receipt, show tx confirmed and update quotes
}).then(function(hash){
//event emitter methods
ethersProvider.once(hash, (transaction) => {
// Emitted when the transaction has been mined
console.log(transaction);
})
})
.catch(function (error) {
console.log(error);
});
} catch (error) {
console.log(error);
}
};
const buildForwardTxRequest = async ({account, to, gasLimitNum, batchId, batchNonce, data, deadline}) => {
const req = {
from: account,
to: to,
token: helperAttributes.ZERO_ADDRESS,
txGas: gasLimitNum,
tokenGasPrice: "0",
batchId: parseInt(batchId),
batchNonce: parseInt(batchNonce),
deadline: deadline || Math.floor(Date.now() / 1000 + 3600),
data: data
};
return req;
};
const getDomainSeperator = async (networkId) => {
const forwarderAddress = ethers.utils.getAddress("0xc1B06d4eFd335658dd0DCFA494CEEd8B985E4B0E");
let domainData = helperAttributes.biconomyForwarderDomainData;
domainData.salt = ethers.utils.hexZeroPad((ethers.BigNumber.from(networkId)).toHexString(), 32);
domainData.verifyingContract = forwarderAddress;
const domainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode([
"bytes32",
"bytes32",
"bytes32",
"address",
"bytes32"
], [
ethers.utils.id("EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)"),
ethers.utils.id(domainData.name),
ethers.utils.id(domainData.version),
domainData.verifyingContract,
domainData.salt,
]));
return domainSeparator;
};
const getDataToSignForEIP712 = async (request) => {
const forwarderAddress = ethers.utils.getAddress("0xc1B06d4eFd335658dd0DCFA494CEEd8B985E4B0E");
let domainData = helperAttributes.biconomyForwarderDomainData;
domainData.salt = ethers.utils.hexZeroPad((ethers.BigNumber.from(42)).toHexString(), 32);
domainData.verifyingContract = forwarderAddress;
const dataToSign = {
types: {
ERC20ForwardRequest: [
{name:'from',type:'address'},
{name:'to',type:'address'},
{name:'token',type:'address'},
{name:'txGas',type:'uint256'},
{name:'tokenGasPrice',type:'uint256'},
{name:'batchId',type:'uint256'},
{name:'batchNonce',type:'uint256'},
{name:'deadline',type:'uint256'},
{name:'data',type:'bytes'}
]
},
domain: {
name : "Biconomy Forwarder",
version : "1",
salt: ethers.utils.hexZeroPad((ethers.BigNumber.from(42)).toHexString(), 32),
verifyingContract: ethers.utils.getAddress("0xc1B06d4eFd335658dd0DCFA494CEEd8B985E4B0E")
},
message: request
};
return dataToSign;
}
init()
// executeBatchedTransactionsEIP712();
// executeBatchedTransactionsPersonalSign();
// excuteTransactionEIP712();
// executeTransactionPersonalSign();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment