Skip to content

Instantly share code, notes, and snippets.

@arcticfloyd1984
Created April 12, 2022 08:41
Show Gist options
  • Save arcticfloyd1984/d1b63b3afc51d8ecb6a7b8873afc3de5 to your computer and use it in GitHub Desktop.
Save arcticfloyd1984/d1b63b3afc51d8ecb6a7b8873afc3de5 to your computer and use it in GitHub Desktop.
Batched Transaction Script
import { Biconomy } from "@biconomy/mexa";
let userAddress1 = "0x040a9cbC4453B0eeaE12f3210117B422B890C1ED";
let userAddress2 = "0x4C07E2fa10f9871142883139B32Cb03F2A180494";
let userAddress3 = "0x6561aD57cde7863bCE871977c951Af21bEC2E74C";
let userAddress4 = "0xaa7f52b2bF183CE85c1d291D838030DF41018a44";
let userAddress5 = "0x02649F6d43556e76CF7a515a9f589BB23287378d";
let userAddresses = [userAddress1, userAddress2, userAddress3, userAddress4, userAddress5];
let contracts;
let contractInterface;
let biconomy;
let jsonRpcProvider;
helperAttributes.domainType = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "verifyingContract", type: "address" },
{ name: "salt", type: "bytes32" },
];
helperAttributes.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'}
];
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://eth-kovan.alchemyapi.io/v2/9u6nUXr9vhhlS91veNoU2-Khngvv5Ae9");
biconomy = new Biconomy(jsonRpcProvider, {
apiKey: "yARl7lxj5.715a8264-060f-41c0-b588-146b42190e0c",
debug: true
});
biconomy.onEvent(biconomy.READY, async () => {
for(let userAddressIndex = 0; userAddressIndex < userAddresses.length; userAddressIndex++) {
contract = new ethers.Contract(
"0x880176EDA9f1608A2Bf182385379bDcC1a65Dfcf",
abi,
biconomy.getSignerByAddress(userAddresses[userAddressIndex])
);
contracts.push(contract);
}
contractInterface = new ethers.utils.Interface(config.contract.abi);
}).onEvent(biconomy.ERROR, (error, message) => {
// Handle error while initializing mexa
console.log(message);
console.log(error);
});
}
const executeBatchedTransactionsEIP712 = async () => {
try {
let { data } = await contract.populateTransaction.setQuote('newQuote');
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 = config.contract.address;
const domainSeparator = await getDomainSeperator(42);
const sigs = [];
const reqs = [];
for(let userAddressIndex = 0; userAddressIndex < userAddresses.length; userAddressIndex++) {
let gasLimit = await ethersProvider.estimateGas({
to: config.contract.address,
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 = await forwarderContract.getBatch(userAddresses[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);
jsonRpcProvider
.send("eth_signTypedData_v3", [userAddress, dataToSign])
.then(function(sig) {
sigs.push(sig);
})
.catch(function (error) {
console.log(error);
});
}
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;
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);
showInfoMessage(`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 = "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 = "0xc1B06d4eFd335658dd0DCFA494CEEd8B985E4B0E";
let domainData = helperAttributes.biconomyForwarderDomainData;
domainData.salt = ethers.utils.hexZeroPad((ethers.BigNumber.from(42)).toHexString(), 32);
domainData.verifyingContract = forwarderAddress;
const dataToSign = JSON.stringify({
types: {
EIP712Domain: helperAttributes.domainType,
ERC20ForwardRequest: helperAttributes.forwardRequestType
},
domain: domainData,
primaryType: "ERC20ForwardRequest",
message: request
});
return dataToSign;
}
await init()
// executeBatchedTransactionsEIP712();
// executeBatchedTransactionsPersonalSign();
// excuteTransactionEIP712();
// executeTransactionPersonalSign();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment