fix: aa-utils

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