I want to create a raw ERC-4337 UserOperation. I am using ethers library to write the test cases .
According to ERC-4337 official documentation , the UserOperation is a struct with many fields .
Reference : https://eips.ethereum.org/EIPS/eip-4337#definitions
How to calculate each fields value ( like initCode , calldata , signature etc ) and combine them to make a UserOperation struct which will then be sent to the alternative mempool ? [ Edit : I searched through repos and got something , dont know if it's correct ] Below is the test case I wrote to create a UserOperation and send it to bundler ( I have StackUp's bundler url )
it("creating a user operation from scratch and then send it to bundler", async function () {
const EntryPoint_Addr = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
const RECEIVER_ADDR = "0x53C242Cc21d129155aCC5FAc321abDfe83C35Af7"; // sending ether to this address
const amount = '1000000000000000'; // amount to send to RECEIVER_ADDR
const sender = "0x1e87a1Eca600313aE1388D04e71ea473AD468CAE"; // smart account address , created by calling createAccount method of AccountFactory ( deployed at 0x9406Cc6185a346906296840746125a0E44976454 ) passing RECEIVER_ADDRESS as address and 1 as salt value
const nonce = '1'; // used salt's value as 1 at the time of smart account creation . that's why I kept it same .
const callGasLimit = '500000';
const verificationGasLimit = '200000';
const preVerificationGas = '50000';
const maxFeePerGas = '1000000000'; // adjust the value according to your needs
const maxPriorityFeePerGas = '100000000'; // adjust the value according to your needs
const account = new ethers.utils.Interface(accountABI); // ethers code
const calldata = account.encodeFunctionData('execute',[RECEIVER_ADDR, amount, "0x"]);
// calldata : 0xb61d27f600000000000000000000000053c242cc21d129155acc5fac321abdfe83c35af700000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000
// getUserOpHash function is taken from StackUp's UserOp library . Reference : https://github.com/stackup-wallet/userop.js/blob/main/src/context.ts
const getUserOpHash = () => {
const packed = ethers.utils.defaultAbiCoder.encode(
[
"address",
"uint256",
"bytes32",
"bytes32",
"uint256",
"uint256",
"uint256",
"uint256",
"uint256",
"bytes32",
],
[
sender,
nonce,
ethers.utils.keccak256('0x'), ethers.utils.keccak256('0xb61d27f600000000000000000000000053c242cc21d129155acc5fac321abdfe83c35af700000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000'),
callGasLimit,
verificationGasLimit,
preVerificationGas,
maxFeePerGas,
maxPriorityFeePerGas,
ethers.utils.keccak256('0x'),
]
);
const enc = ethers.utils.defaultAbiCoder.encode(
["bytes32", "address", "uint256"],
[ethers.utils.keccak256(packed), EntryPoint_Addr, 80001]
);
return ethers.utils.keccak256(enc);
}
const userOpHash = getUserOpHash();
// userOpHash value : 0x1a23a91638f4a6c594c64b1b17bb930f9505c244b7a9cbc7065213bfccc71ba9
// Arraified the userOpHash . Reference : https://github.com/stackup-wallet/userop.js/blob/main/src/preset/middleware/signature.ts
const arraifiedHash = ethers.utils.arrayify(userOpHash);
console.log("arraified Hash :",arraifiedHash);
const provider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.infura.io/v3/infurakey");
const wallet = new ethers.Wallet('myprivatekey', provider);
const signer = wallet.provider.getSigner(wallet.address);
const serializeObj = JSON.stringify(arraifiedHash); // **error suspect 1**
const signature = signer.signMessage(serializeObj);
const options = {
method: "POST",
url: "https://api.stackup.sh/v1/node/stackupkey",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
jsonrpc: "2.0",
id: 1,
method: "eth_sendUserOperation",
params: [
{
sender: "0x1e87a1Eca600313aE1388D04e71ea473AD468CAE",
nonce: "0x0",
initCode: '0x',
callData: "0xb61d27f600000000000000000000000053c242cc21d129155acc5fac321abdfe83c35af700000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
callGasLimit: "500000",
verificationGasLimit: "200000",
preVerificationGas: "50000",
maxFeePerGas: "1000000000",
maxPriorityFeePerGas: "100000000",
paymasterAndData: "0x",
signature:signature
},
"0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
],
},
};
await axios
.request(options)
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.error(error);
});
});