mirror of
https://github.com/getwax/wax.git
synced 2026-01-09 15:18:02 -05:00
215 lines
5.3 KiB
TypeScript
215 lines
5.3 KiB
TypeScript
import {
|
|
AbiCoder,
|
|
BigNumberish,
|
|
BytesLike,
|
|
concat,
|
|
hexlify,
|
|
isHexString,
|
|
keccak256,
|
|
} from "ethers";
|
|
import { PackedUserOperationStruct } from "../../../typechain-types/lib/account-abstraction/contracts/core/EntryPoint";
|
|
|
|
/**
|
|
* @notice these utils have been largely copied from ERC4337Utils.ts in eth-infinitism's
|
|
* bundler repo, which form part of @account-abstraction/utils. This is because
|
|
* @account-abstraction/utils v0.7.0 has not been published on npm yet. These utility
|
|
* function can be swapped out once v0.7.0 has been published.
|
|
*
|
|
* The only changes were to update ethers functionality from v5 to v6
|
|
*/
|
|
|
|
export type PackedUserOperation = PackedUserOperationStruct;
|
|
|
|
export type UserOperation = {
|
|
sender: string;
|
|
nonce: BigNumberish;
|
|
factory?: string;
|
|
factoryData?: BytesLike;
|
|
callData: BytesLike;
|
|
callGasLimit: BigNumberish;
|
|
verificationGasLimit: BigNumberish;
|
|
preVerificationGas: BigNumberish;
|
|
maxFeePerGas: BigNumberish;
|
|
maxPriorityFeePerGas: BigNumberish;
|
|
paymaster?: string;
|
|
paymasterVerificationGasLimit?: BigNumberish;
|
|
paymasterPostOpGasLimit?: BigNumberish;
|
|
paymasterData?: BytesLike;
|
|
signature: BytesLike;
|
|
};
|
|
|
|
export type FactoryParams = {
|
|
factory: string;
|
|
factoryData?: BytesLike;
|
|
};
|
|
|
|
/**
|
|
* calculate the userOpHash of a given userOperation.
|
|
* The userOpHash is a hash of all UserOperation fields, except the "signature" field.
|
|
* The entryPoint uses this value in the emitted UserOperationEvent.
|
|
* A wallet may use this value as the hash to sign (the SampleWallet uses this method)
|
|
* @param op
|
|
* @param entryPoint
|
|
* @param chainId
|
|
*/
|
|
export function getUserOpHash(
|
|
op: UserOperation,
|
|
entryPoint: string,
|
|
chainId: number
|
|
): string {
|
|
const userOpHash = keccak256(encodeUserOp(op, true));
|
|
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
|
const enc = defaultAbiCoder.encode(
|
|
["bytes32", "address", "uint256"],
|
|
[userOpHash, entryPoint, chainId]
|
|
);
|
|
return keccak256(enc);
|
|
}
|
|
|
|
/**
|
|
* abi-encode the userOperation
|
|
* @param op a PackedUserOp
|
|
* @param forSignature "true" if the hash is needed to calculate the getUserOpHash()
|
|
* "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
|
|
*/
|
|
export function encodeUserOp(
|
|
op1: PackedUserOperation | UserOperation,
|
|
forSignature = true
|
|
): string {
|
|
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
|
|
|
|
let op: PackedUserOperation;
|
|
if ("callGasLimit" in op1) {
|
|
op = packUserOp(op1);
|
|
} else {
|
|
op = op1;
|
|
}
|
|
|
|
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
|
if (forSignature) {
|
|
return defaultAbiCoder.encode(
|
|
[
|
|
"address",
|
|
"uint256",
|
|
"bytes32",
|
|
"bytes32",
|
|
"bytes32",
|
|
"uint256",
|
|
"bytes32",
|
|
"bytes32",
|
|
],
|
|
[
|
|
op.sender,
|
|
op.nonce,
|
|
keccak256(op.initCode),
|
|
keccak256(op.callData),
|
|
op.accountGasLimits,
|
|
op.preVerificationGas,
|
|
op.gasFees,
|
|
keccak256(op.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",
|
|
],
|
|
[
|
|
op.sender,
|
|
op.nonce,
|
|
op.initCode,
|
|
op.callData,
|
|
op.accountGasLimits,
|
|
op.preVerificationGas,
|
|
op.gasFees,
|
|
op.paymasterAndData,
|
|
op.signature,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
export function packUserOp(op: UserOperation): PackedUserOperation {
|
|
let paymasterAndData: BytesLike;
|
|
if (op.paymaster == null) {
|
|
paymasterAndData = "0x";
|
|
} else {
|
|
if (
|
|
op.paymasterVerificationGasLimit == null ||
|
|
op.paymasterPostOpGasLimit == null
|
|
) {
|
|
throw new Error("paymaster with no gas limits");
|
|
}
|
|
paymasterAndData = packPaymasterData(
|
|
op.paymaster,
|
|
op.paymasterVerificationGasLimit,
|
|
op.paymasterPostOpGasLimit,
|
|
op.paymasterData
|
|
);
|
|
}
|
|
|
|
return {
|
|
sender: op.sender,
|
|
nonce: "0x" + BigInt(op.nonce).toString(16),
|
|
initCode:
|
|
op.factory == null ? "0x" : concat([op.factory, op.factoryData ?? ""]),
|
|
callData: op.callData,
|
|
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
|
|
preVerificationGas: "0x" + BigInt(op.preVerificationGas).toString(16),
|
|
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
|
|
paymasterAndData,
|
|
signature: op.signature,
|
|
};
|
|
}
|
|
|
|
export function packPaymasterData(
|
|
paymaster: string,
|
|
paymasterVerificationGasLimit: BigNumberish,
|
|
postOpGasLimit: BigNumberish,
|
|
paymasterData?: BytesLike
|
|
): BytesLike {
|
|
return concat([
|
|
paymaster,
|
|
packUint(paymasterVerificationGasLimit, postOpGasLimit),
|
|
paymasterData ?? "0x",
|
|
]);
|
|
}
|
|
|
|
export function packUint(high128: BigNumberish, low128: BigNumberish): string {
|
|
high128 = BigInt(high128);
|
|
low128 = BigInt(low128);
|
|
const packed = "0x" + ((high128 << 128n) + low128).toString(16);
|
|
return hexZeroPad(packed, 32);
|
|
}
|
|
|
|
/**
|
|
* @notice Copied from ethers v5.7 as the ethers v6 equivalent behaves
|
|
* in a way the bundler doesn't expect and an error is thrown
|
|
*/
|
|
export function hexZeroPad(value: BytesLike, length: number): string {
|
|
if (typeof value !== "string") {
|
|
value = hexlify(value);
|
|
} else if (!isHexString(value)) {
|
|
console.log("invalid hex string", "value", value);
|
|
}
|
|
|
|
if (value.length > 2 * length + 2) {
|
|
console.log("value out of range", "value", arguments[1]);
|
|
}
|
|
|
|
while (value.length < 2 * length + 2) {
|
|
value = "0x0" + value.substring(2);
|
|
}
|
|
|
|
return value;
|
|
}
|