Creating a UserOperation from scratch and then sending it to bundler for execution
Opened this issue · 4 comments
I wanted to create a UserOperation from scratch and then send it to bundler . I am using stackup's bundler URL .
Below is the test I wrote to send a UserOperation to the bundler for inclusion but I am getting an error related to signature .
The smart account ( 0x1e87a1Eca600313aE1388D04e71ea473AD468CAE ) was created by calling SimpleAccountFactory contract deployed at 0x9406Cc6185a346906296840746125a0E44976454 .
Error Logged :
{
error: {
code: -32507,
data: null,
message: 'Invalid UserOp signature or paymaster signature'
},
id: 1,
jsonrpc: '2.0'
}
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);
});
});
Since you're not using a paymaster, this error means your signature
value is incorrect.
From a quick scan of your code, this part seems off. Any reason why you are passing a JSON.stringify
version of the userOpHash
?
const serializeObj = JSON.stringify(arraifiedHash); // **error suspect 1**
const signature = signer.signMessage(serializeObj);
Hi @hazim-j , glad that you replied .
Reference : https://github.com/stackup-wallet/userop.js/blob/main/src/preset/middleware/signature.ts
Line of Code :
ctx.op.signature = await signer.signMessage(ethers.utils.arrayify(ctx.getUserOpHash()));
Here you have used signer.signMessage method of ethers and inside it is the arrayified version of UserOpHash .
But when I try to do the same , I get an error .
I'll rewrite the code that produces the error along with the snapshot of the error message I get .
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();
const provider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.infura.io/v3/key");
const wallet = new ethers.Wallet('myprivatekey', provider);
const signer = wallet.provider.getSigner(wallet.address);
const sig = await signer.signMessage(ethers.utils.arrayify(userOpHash));
console.log("sig :",sig);
To get away with this error , I used web3js library , first I stringified the array version of userophash and then signed it using web3.eth.accounts.sign() . I was able to get the signature and when I passed it to send the UserOperation to bundler I get the error mentioned below :
{
code: -32507,
data: null,
message: 'Invalid UserOp signature or paymaster signature'
},
id: 1,
jsonrpc: '2.0'
}
I knew it would be wrong to first stringify the array and then sign it but found no solution , so did it anyway . It would be really great if you can help me with this . My guess is the problem is maybe with the provider , I have used infura's . Is there any special provider url for ERC 4337 ?
that is also an issue for me using infura. Infura also claims to have support for ERC 4337? should it be all a common protocol?
apparently this works using alchemy.com. So apparently infura is not fully supporting it