UNPKG

@owlprotocol/contracts-account-abstraction

Version:

ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.

280 lines (262 loc) 9.36 kB
import { Address, Hash, Hex, bytesToHex, concatHex, encodeAbiParameters, getAddress, numberToHex, pad, slice, zeroAddress, } from "viem"; import { UserOperation } from "viem/account-abstraction"; /** * PackedUserOp suitable for call to EntryPoint contract * //TODO: Move to `PackedUserOperation.ts` */ export interface PackedUserOperation { sender: Address; nonce: bigint; initCode: Hex; callData: Hex; accountGasLimits: Hash; preVerificationGas: bigint; gasFees: Hash; paymasterAndData: Hex; signature: Hex; } /** * Convert `UserOperationWithBigIntAsHex` to `PackedUserOperation` * @param userOp * @returns packedUserOperation */ export function toPackedUserOperation(userOp: UserOperation<"0.7", Hex>): PackedUserOperation { const accountGasLimits = packAccountGasLimits(userOp.verificationGasLimit, userOp.callGasLimit); const gasFees = packAccountGasLimits(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas); let paymasterAndData: Hex = "0x"; if ( userOp.paymaster && userOp.paymaster !== zeroAddress && userOp.paymasterVerificationGasLimit && userOp.paymasterPostOpGasLimit && userOp.paymasterData ) { paymasterAndData = packPaymasterData( userOp.paymaster, userOp.paymasterVerificationGasLimit, userOp.paymasterPostOpGasLimit, userOp.paymasterData, ); } const initCode = userOp.factory && userOp.factory != zeroAddress && userOp.factoryData ? concatHex([userOp.factory, userOp.factoryData]) : "0x"; return { sender: userOp.sender, nonce: BigInt(userOp.nonce), callData: userOp.callData, accountGasLimits, initCode, preVerificationGas: BigInt(userOp.preVerificationGas), gasFees, paymasterAndData, signature: userOp.signature, }; } /** * Convert `PackedUserOperation` to `UserOperationWithBigIntAsHex` * @param userOp * @returns packedUserOperation */ export function toUserOperationEncoded(packedUserOperation: PackedUserOperation): UserOperation<"0.7", Hex> { const { factory, factoryData } = unpackInitCode(packedUserOperation.initCode); const { callGasLimit, verificationGasLimit } = unpackAccountGasLimits(packedUserOperation.accountGasLimits); const { maxFeePerGas, maxPriorityFeePerGas } = unpackGasLimits(packedUserOperation.gasFees); const { paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData } = unpackPaymasterAndData( packedUserOperation.paymasterAndData, ); return { sender: packedUserOperation.sender, nonce: numberToHex(packedUserOperation.nonce), factory: factory ?? zeroAddress, factoryData: factoryData ?? "0x", callData: packedUserOperation.callData, callGasLimit: numberToHex(callGasLimit), verificationGasLimit: numberToHex(verificationGasLimit), preVerificationGas: numberToHex(packedUserOperation.preVerificationGas), maxFeePerGas: numberToHex(maxFeePerGas), maxPriorityFeePerGas: numberToHex(maxPriorityFeePerGas), paymaster: paymaster ?? zeroAddress, paymasterVerificationGasLimit: numberToHex(paymasterVerificationGasLimit ?? 0n), paymasterPostOpGasLimit: numberToHex(paymasterPostOpGasLimit ?? 0n), paymasterData: paymasterData ?? "0x", signature: packedUserOperation.signature, }; } /** * Return `PackedUserOperation` with random data for better gas estimation * @param packedUserOperation * @returns */ export function packedUserOperationToRandomDataUserOp(packedUserOperation: PackedUserOperation): PackedUserOperation { return { sender: packedUserOperation.sender, nonce: BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), initCode: packedUserOperation.initCode, callData: packedUserOperation.callData, accountGasLimits: bytesToHex(new Uint8Array(32).fill(255)), preVerificationGas: BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), gasFees: bytesToHex(new Uint8Array(32).fill(255)), paymasterAndData: bytesToHex(new Uint8Array(packedUserOperation.paymasterAndData.length).fill(255)), signature: bytesToHex(new Uint8Array(packedUserOperation.signature.length).fill(255)), }; } //TODO: Rename packUserOp to `toPackedUserOperation()` /** * //TODO: Bit weird to have this random data logic inside hear as obfuscates logic * Pack `PackedUserOperation` as bytes and add random user data for gas estimation * @param packedUserOperation * @returns */ export function packUserOperation(packedUserOperation: PackedUserOperation): Hex { const randomDataUserOp: PackedUserOperation = packedUserOperationToRandomDataUserOp(packedUserOperation); return encodeAbiParameters( [ { internalType: "address", name: "sender", type: "address", }, { internalType: "uint256", name: "nonce", type: "uint256", }, { internalType: "bytes", name: "initCode", type: "bytes", }, { internalType: "bytes", name: "callData", type: "bytes", }, { internalType: "uint256", name: "accountGasLimits", type: "bytes32", }, { internalType: "uint256", name: "preVerificationGas", type: "uint256", }, { internalType: "uint256", name: "gasFees", type: "bytes32", }, { internalType: "bytes", name: "paymasterAndData", type: "bytes", }, { internalType: "bytes", name: "signature", type: "bytes", }, ], [ randomDataUserOp.sender, randomDataUserOp.nonce, // need non zero bytes to get better estimations for preVerificationGas packedUserOperation.initCode, packedUserOperation.callData, randomDataUserOp.accountGasLimits, // need non zero bytes to get better estimations for preVerificationGas randomDataUserOp.preVerificationGas, // need non zero bytes to get better estimations for preVerificationGas randomDataUserOp.gasFees, // need non zero bytes to get better estimations for preVerificationGas randomDataUserOp.paymasterAndData, randomDataUserOp.signature, ], ); } export function packAccountGasLimits(verificationGasLimit: Hex, callGasLimit: Hex): Hex { return concatHex([ pad(verificationGasLimit, { dir: "left", size: 16 }), pad(callGasLimit, { dir: "left", size: 16 }), ]); } export function unpackInitCode(initCode: Hex) { if (initCode === "0x") { return { factory: null, factoryData: null, }; } return { factory: getAddress(slice(initCode, 0, 20)), factoryData: slice(initCode, 20), }; } export function unpackGasLimits(gasLimits: Hex) { return { maxPriorityFeePerGas: BigInt(slice(gasLimits, 0, 16)), maxFeePerGas: BigInt(slice(gasLimits, 16)), }; } export function unpackPaymasterAndData(paymasterAndData: Hex) { if (paymasterAndData === "0x") { return { paymaster: null, paymasterVerificationGasLimit: null, paymasterPostOpGasLimit: null, paymasterData: null, }; } return { paymaster: getAddress(slice(paymasterAndData, 0, 20)), paymasterVerificationGasLimit: BigInt(slice(paymasterAndData, 20, 36)), paymasterPostOpGasLimit: BigInt(slice(paymasterAndData, 36, 52)), paymasterData: slice(paymasterAndData, 52), }; } /** * * paymasterAndData[:20] : address(this) * paymasterAndData[20:36] : paymasterVerificationGasLimit * paymasterAndData[36:52] : postOpGasLimit * paymasterAndData[52:] : paymasterData (custom data) * paymasterAndData[52:116] : abi.encode(validUntil, validAfter) * paymasterAndData[116:] : signature (64-65 bytex) * @param paymaster * @param paymasterVerificationGasLimit * @param postOpGasLimit * @param paymasterData * @returns */ export function packPaymasterData( paymaster: Address, paymasterVerificationGasLimit: Hex, postOpGasLimit: Hex, paymasterData: Hex, ): Hex { return concatHex([ pad(paymaster, { dir: "left", size: 20 }), pad(paymasterVerificationGasLimit, { dir: "left", size: 16 }), pad(postOpGasLimit, { dir: "left", size: 16 }), paymasterData, ]); } export function unpackAccountGasLimits(accountGasLimits: string): { verificationGasLimit: number; callGasLimit: number; } { return { verificationGasLimit: parseInt(accountGasLimits.slice(2, 34), 16), callGasLimit: parseInt(accountGasLimits.slice(34), 16), }; }