fix: aa-utils
diff --git a/aa-utils.ts b/aa-utils.ts
index 9c4b9be..04a8be3 100644
--- a/aa-utils.ts
+++ b/aa-utils.ts
@@ -8,112 +8,119 @@ import {
arrayify,
defaultAbiCoder,
hexDataSlice,
+ hexlify,
keccak256,
} from "ethers/lib/utils";
import { UserOperationStruct } from "@account-abstraction/contracts";
-import { Wallet } from "ethers";
+import { Signer } from "ethers";
export type NotPromise<T> = {
[P in keyof T]: Exclude<T[P], Promise<any>>;
};
-export function packUserOp(
- op: NotPromise<UserOperationStruct>,
- forSignature = true
-): string {
- if (forSignature) {
- return defaultAbiCoder.encode(
- [
- "address",
- "uint256",
- "bytes32",
- "bytes32",
- "uint256",
- "uint256",
- "uint256",
- "uint256",
- "uint256",
- "bytes32",
- ],
- [
- op.sender,
- op.nonce,
- keccak256(op.initCode),
- keccak256(op.callData),
- op.callGasLimit,
- op.verificationGasLimit,
- op.preVerificationGas,
- op.maxFeePerGas,
- op.maxPriorityFeePerGas,
- 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",
- "uint256",
- "uint256",
- "uint256",
- "uint256",
- "uint256",
- "bytes",
- "bytes",
- ],
- [
- op.sender,
- op.nonce,
- op.initCode,
- op.callData,
- op.callGasLimit,
- op.verificationGasLimit,
- op.preVerificationGas,
- op.maxFeePerGas,
- op.maxPriorityFeePerGas,
- op.paymasterAndData,
- op.signature,
- ]
- );
- }
+// DUMMY value for paymasterAndData
+export const DUMMY_PND =
+ "0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
+
+// DUMMY value for signature field
+export const DUMMY_SIGNATURE =
+ "0x0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
+
+/**
+ * Pack the UserOperation
+ *
+ * During packing we add the dummy paymasterAndData and signature to the UserOperation to get the offset values
+ *
+ * After we know the offsets the dummy values are stripped off
+ */
+export function packUserOp(op: NotPromise<UserOperationStruct>): string {
+ let packedUserOpWithDummyPNDAndSignature = defaultAbiCoder.encode(
+ [
+ "address",
+ "uint256",
+ "bytes",
+ "bytes",
+ "uint256",
+ "uint256",
+ "uint256",
+ "uint256",
+ "uint256",
+ "bytes",
+ "bytes",
+ ],
+ [
+ op.sender,
+ op.nonce,
+ op.initCode,
+ op.callData,
+ op.callGasLimit,
+ op.verificationGasLimit,
+ op.preVerificationGas,
+ op.maxFeePerGas,
+ op.maxPriorityFeePerGas,
+ DUMMY_PND,
+ DUMMY_SIGNATURE,
+ ]
+ );
+
+ // Remove paymasterAndData and signature but keep their offset as is (even though they are wrong)
+ // https://github.com/pimlicolabs/account-abstraction/blob/939eea6ea43e8ec0427fe6eab41b2aff6831283b/contracts/samples/VerifyingPaymaster.sol#L78
+ let packedUserOp = hexDataSlice(
+ hexlify(packedUserOpWithDummyPNDAndSignature),
+ 0,
+ packedUserOpWithDummyPNDAndSignature.length / 2 - 321 // 321 - is the number of bytes paymasterAndData and signature combined
+ );
+
+ return packedUserOp;
}
+/**
+ * Generate the UserOperation Hash
+ */
export function getUserOpHash(
op: NotPromise<UserOperationStruct>,
- entryPoint: string,
chainId: number,
+ paymasterAddress: string,
senderNonce: number,
validUntil: number,
validAfter: number
): string {
- const userOpHash = keccak256(packUserOp(op, true));
+ const packedUserOp = packUserOp(op);
const enc = defaultAbiCoder.encode(
- ["bytes32", "address", "uint256", "uint256", "uint48", "uint48"],
- [userOpHash, entryPoint, chainId, senderNonce, validUntil, validAfter]
+ ["bytes", "uint256", "address", "uint256", "uint48", "uint48"],
+ [
+ packedUserOp,
+ chainId,
+ paymasterAddress,
+ senderNonce,
+ validUntil,
+ validAfter,
+ ]
);
return keccak256(enc);
}
+/**
+ * Sign the UserOperation Hash with the paymaster owner Signer
+ */
export function signUserOp(
op: NotPromise<UserOperationStruct>,
- signer: Wallet,
- entryPoint: string,
+ signer: Signer,
chainId: number,
+ paymasterAddress: string,
senderNonce: number,
validUntil: number,
validAfter: number
): UserOperationStruct {
const message = getUserOpHash(
op,
- entryPoint,
chainId,
+ paymasterAddress,
senderNonce,
validUntil,
validAfter
);
+
const msg1 = Buffer.concat([
Buffer.from("\x19Ethereum Signed Message:\n32", "ascii"),
Buffer.from(arrayify(message)),
@@ -123,6 +130,7 @@ export function signUserOp(
keccak256_buffer(msg1),
Buffer.from(arrayify(signer.privateKey))
);
+
// that's equivalent of: await signer.signMessage(message);
// (but without "async"
const signedMessage1 = toRpcSig(sig.v, sig.r, sig.s);
GitHub
sha: 0a870b706c76abf9c7d6aa30e748813b32d18d18