User-operation
@shakesco/userop
This repository makes it easy for developers to create userop and estimate gas that can be sent to bundlers for execution!
Install
To install:
npm i @shakesco/userop
After installing:
const shakesco = require("@shakesco/userop");
What is a User operation
With traditional wallets, you had to specify the address that will receive the funds and the amount only. However smart wallets are different. To send funds using a smart wallet, you actually have to send a request to a bundler for execution. That request is call User Operation or simply Userop/intent.
To send a user operation or intents you need:
- Sender - This is the smart account
- Nonce - This is the smart accounts number of intent sent from deployment
- initCode - This is the code used to deploy the sender if not yet on-chain.
- callData - Data that you want the smart account to execute
- callGasLimit - Gas limit for the execution phase
- verificationGasLimit - Gas limit for the verification phase
- preVerificationGas - Gas to compensate the bundler for the overhead to submit the User Operation.
- maxFeePerGas - Similar to eip 1559
- maxPriorityFeePerGas - Similar to eip 1559
- paymasterAndData - The paymaster plus data used to sponsor transaction and verify legibility
- signature - The signature from the owner of the smart wallet.
So let’s start with nonce because sender is pretty straightforward.
Nonce
To get the nonce call the function below from your smart account:
const ACCOUNT = new ethers.Contract(sender, smartWalletABI, provider);
const nonce = await ACCOUNT.getNonce();
Initcode
To get the initcode you need the factory contract. You can create the initcode by simply doing:
const factory = new ethers.Interface(accountFactoryABI);
const initCode = ethers.concat([
accountFactoryAddress,
factory.encodeFunctionData("deployWallet", [
owner, //owner of the smart wallet
salt, //random salt value
]),
]);
Calldata
The calldata is the function you want to execute. So you encode the function as below:
const accountABI = new ethers.Interface(smartWalletABI);
const calldata = accountABI.encodeFunctionData("execute",
[
address,
ethers.parseEther(value),
"0x",
]);
Gas
Now to the gas value:
Use our package to get the eip 1559 gas value as below:
const provider = new ethers.JsonRpcProvider(
process.env.RPC_URL
);
const { maxFeePerGas, maxPriorityFeePerGas } = await shakesco.getEIP1559(
provider
);
console.log(maxFeePerGas); //0x1500000016
console.log(maxPriorityFeePerGas); //0x1500000000
To get the other userop gas value:
const { callGasLimit, preVerificationGas, verificationGasLimit } =
await shakesco.useropGasValues(
smartWallerAddress,
provider,
calldata,
initCode
);
console.log(callGasLimit); //0x83074
console.log(preVerificationGas); //0x01228e
console.log(verificationGasLimit); //0x0186a0
initCode
as empty string. If you also don’t want to call any data from the smart wallet pass calldata
an empty string.Prepare for signing
Finally we need to sign the userop so as to send the userop. We can do this by:
const arraifiedHash = shakesco.useropHash(
smartWallerAddress,
nonce,
initCode,
calldata,
callGasLimit,
verificationGasLimit,
preVerificationGas,
maxFeePerGas,
maxPriorityFeePerGas,
paymasterAndDataNOSIG,
chainId
);
console.log(arraifiedHash);
Sign
After getting the hash we can sign this by doing:
const signer = new ethers.Wallet(process.env.PRIV_KEY, provider);
const signature = await signer.signMessage(arraifiedHash);
You now have all the value to send the userop. Pass them as required by your bundler provider.