eth-infinitism / account-abstraction

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Creating a UserOperation from scratch and then sending it to bundler for execution .

iamgauravpant opened this issue · 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);
          });
});

it is very hard to tell where it failed - filling fields, calculating hash or signature.
I suggest first try to create a UserOp using an API (e.g. accountabstraction/sdk and createSignedUserOp())
Then, once you have a successful creation of a UserOperation, you can replace with your own code portions of the flow, until you find out where it breaks and fix it.