@ensuro/account-abstraction
Version:
ERC-4337 related contracts by the Ensuro Team
142 lines (132 loc) • 4.46 kB
JavaScript
import * as ethers from "ethers";
const { ZeroAddress } = ethers;
const defaultAbiCoder = ethers.AbiCoder.defaultAbiCoder();
export function packAccountGasLimits(verificationGasLimit, callGasLimit) {
return ethers.toBeHex(verificationGasLimit, 16) + ethers.toBeHex(callGasLimit, 16).slice(2);
}
export function packUserOp(userOp) {
const accountGasLimits = packAccountGasLimits(userOp.verificationGasLimit, userOp.callGasLimit);
const gasFees = packAccountGasLimits(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas);
let paymasterAndData = "0x";
if (userOp.paymaster?.length >= 20 && userOp.paymaster !== ZeroAddress) {
paymasterAndData = packPaymasterData(
userOp.paymaster,
userOp.paymasterVerificationGasLimit,
userOp.paymasterPostOpGasLimit,
userOp.paymasterData
);
}
return {
sender: userOp.sender,
nonce: userOp.nonce,
callData: userOp.callData,
accountGasLimits,
initCode: userOp.initCode,
preVerificationGas: userOp.preVerificationGas,
gasFees,
paymasterAndData,
signature: userOp.signature,
};
}
export function packedUserOpAsArray(packedUserOp, includeSignature = true) {
if (includeSignature) {
return [
packedUserOp.sender,
packedUserOp.nonce,
packedUserOp.initCode,
packedUserOp.callData,
packedUserOp.accountGasLimits,
packedUserOp.preVerificationGas,
packedUserOp.gasFees,
packedUserOp.paymasterAndData,
packedUserOp.signature,
];
} else {
return [
packedUserOp.sender,
packedUserOp.nonce,
packedUserOp.initCode,
packedUserOp.callData,
packedUserOp.accountGasLimits,
packedUserOp.preVerificationGas,
packedUserOp.gasFees,
packedUserOp.paymasterAndData,
];
}
}
export function encodeUserOp(userOp, forSignature = true) {
const packedUserOp = packUserOp(userOp);
if (forSignature) {
return defaultAbiCoder.encode(
["address", "uint256", "bytes32", "bytes32", "bytes32", "uint256", "bytes32", "bytes32"],
[
packedUserOp.sender,
packedUserOp.nonce,
ethers.keccak256(packedUserOp.initCode),
ethers.keccak256(packedUserOp.callData),
packedUserOp.accountGasLimits,
packedUserOp.preVerificationGas,
packedUserOp.gasFees,
ethers.keccak256(packedUserOp.paymasterAndData),
]
);
} else {
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
return defaultAbiCoder.encode(
["address", "uint256", "bytes", "bytes", "bytes32", "uint256", "bytes32", "bytes", "bytes"],
[
packedUserOp.sender,
packedUserOp.nonce,
packedUserOp.initCode,
packedUserOp.callData,
packedUserOp.accountGasLimits,
packedUserOp.preVerificationGas,
packedUserOp.gasFees,
packedUserOp.paymasterAndData,
packedUserOp.signature,
]
);
}
}
export function getUserOpHash(op, entryPoint, chainId) {
const userOpHash = ethers.keccak256(encodeUserOp(op, true));
const enc = defaultAbiCoder.encode(["bytes32", "address", "uint256"], [userOpHash, entryPoint, chainId]);
return ethers.keccak256(enc);
}
export const DefaultsForUserOp = {
sender: ZeroAddress,
nonce: 0,
initCode: "0x",
callData: "0x",
callGasLimit: 0n,
verificationGasLimit: 150000n, // default verification gas. will add create2 cost (3200+200*length) if initCode exists
preVerificationGas: 21000n, // should also cover calldata cost.
maxFeePerGas: 0n,
maxPriorityFeePerGas: 1e9,
paymaster: ZeroAddress,
paymasterData: "0x",
paymasterVerificationGasLimit: 300000n,
paymasterPostOpGasLimit: 0,
signature: "0x",
};
export async function signUserOp(op, signer, entryPoint, chainId) {
const userOpHash = getUserOpHash(op, entryPoint, chainId);
const signature = await signer.signMessage(ethers.getBytes(userOpHash));
return {
...op,
signature,
};
}
export function fillUserOpDefaults(op, defaults = DefaultsForUserOp) {
const partial = { ...op };
// we want "item:undefined" to be used from defaults, and not override defaults, so we must explicitly
// remove those so "merge" will succeed.
for (const key in partial) {
if (partial[key] == null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete partial[key];
}
}
const filled = { ...defaults, ...partial };
return filled;
}