feat: allowList paymaster

feat: allowList paymaster

diff --git a/.gitignore b/.gitignore
index a908138..d1f69cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,15 @@ wallet.json
 wallet-2.json
 .yalc
 yalc.lock
-.vscode
\ No newline at end of file
+.vscode
+node_modules
+.env
+coverage
+coverage.json
+typechain
+typechain-types
+
+# Hardhat files
+cache
+artifacts
+
diff --git a/aa-utils.ts b/aa-utils.ts
new file mode 100644
index 0000000..9c4b9be
--- /dev/null
+++ b/aa-utils.ts
@@ -0,0 +1,133 @@
+// @ts-nocheck
+import {
+    ecsign,
+    toRpcSig,
+    keccak256 as keccak256_buffer,
+} from "ethereumjs-util";
+import {
+    arrayify,
+    defaultAbiCoder,
+    hexDataSlice,
+    keccak256,
+} from "ethers/lib/utils";
+import { UserOperationStruct } from "@account-abstraction/contracts";
+import { Wallet } 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,
+            ]
+        );
+    }
+}
+
+export function getUserOpHash(
+    op: NotPromise<UserOperationStruct>,
+    entryPoint: string,
+    chainId: number,
+    senderNonce: number,
+    validUntil: number,
+    validAfter: number
+): string {
+    const userOpHash = keccak256(packUserOp(op, true));
+    const enc = defaultAbiCoder.encode(
+        ["bytes32", "address", "uint256", "uint256", "uint48", "uint48"],
+        [userOpHash, entryPoint, chainId, senderNonce, validUntil, validAfter]
+    );
+    return keccak256(enc);
+}
+
+export function signUserOp(
+    op: NotPromise<UserOperationStruct>,
+    signer: Wallet,
+    entryPoint: string,
+    chainId: number,
+    senderNonce: number,
+    validUntil: number,
+    validAfter: number
+): UserOperationStruct {
+    const message = getUserOpHash(
+        op,
+        entryPoint,
+        chainId,
+        senderNonce,
+        validUntil,
+        validAfter
+    );
+    const msg1 = Buffer.concat([
+        Buffer.from("\x19Ethereum Signed Message:\n32", "ascii"),
+        Buffer.from(arrayify(message)),
+    ]);
+
+    const sig = ecsign(
+        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);
+    return {
+        ...op,
+        signature: signedMessage1,
+    };
+}
diff --git a/index.ts b/index.ts
index 5a992de..80f767b 100644
--- a/index.ts
+++ b/index.ts
@@ -1,28 +1,64 @@
+// @ts-nocheck
 import { config } from "dotenv";
 import { ThirdwebSDK } from "@thirdweb-dev/sdk";
-import { SmartWallet, SmartWalletConfig } from "@thirdweb-dev/wallets";
-import { LocalWalletNode } from "@thirdweb-dev/wallets/evm/wallets/local-wallet-node";
-import {
-  BaseGoerli,
-  CeloAlfajoresTestnet,
-  Goerli,
-  Mumbai,
-  OptimismGoerli,
-  ScrollAlphaTestnet,
-  Sepolia,
-} from "@thirdweb-dev/chains";
+import { defaultAbiCoder } from "ethers/lib/utils";
 import {
-  batchTransaction,
-  claimCeloToken,
-  claimERC721Token,
-  claimMumbaiNFT,
-  claimSepoliaNFT,
-  claimToken,
-  playCatAttack,
-} from "./sdk-calls";
+    SmartWallet,
+    SmartWalletConfig,
+    PrivateKeyWallet,
+} from "@thirdweb-dev/wallets";
+import { PaymasterAPI } from "@account-abstraction/sdk";
+import { UserOperationStruct } from "@account-abstraction/contracts";
+import { LocalWalletNode } from "@thirdweb-dev/wallets/evm/wallets/local-wallet-node";
+import { CeloAlfajoresTestnet } from "@thirdweb-dev/chains";
+import { claimCeloToken } from "./sdk-calls";
+import { signUserOp } from "./aa-utils";
 
 config();
 
+const ENTRYPOINT = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
+
+const ALLOWLIST_PAYMASTER_ADDRESS =
+    "0x34A00151460C7Bec401D3b24fE86E9C152EE8284";
+
+// Only the following UserOperation sender will be allowed
+const allowList = ["0xdA82D492a49d08cF732A47Acf34efb51BE351dd6"];
+
+class AllowlistPaymaster extends PaymasterAPI {
+    private allowlist: string[];
+
+    constructor(allowlist: string[]) {
+        super();
+        this.allowlist = allowlist;
+    }
+
+    async getPaymasterAndData(
+        userOp: Partial<UserOperationStruct>
+    ): Promise<string> {
+        // Ask the paymaster to sign the transaction and return a valid paymasterAndData value.
+        if (allowList.indexOf(userOp.sender as string) != -1) {
+            let signer = new PrivateKeyWallet(process.env.SIGNER_KEY as string);
+            let validUntil = Date.now() / 1000 + 10 * 60;
+            let signature = signUserOp(
+                userOp,
+                signer,
+                ENTRYPOINT,
+                44787,
+                0,
+                validUntil,
+                0
+            );
+
+            let paymasterAndData = defaultAbiCoder.encode(
+                ["address", "uint48", "uint48", "bytes"],
+                [ALLOWLIST_PAYMASTER_ADDRESS, validUntil, 0, signature]
+            );
+            return paymasterAndData;
+        }
+
+        return "";
+    }
+}
 // Put your chain here
 const chain = CeloAlfajoresTestnet;
 // Put your thirdweb API key here (or in .env)
@@ -30,77 +66,60 @@ const thirdwebApiKey = process.env.THIRDWEB_API_KEY as string;
 
 // Factory addresses for each chain
 const factories = {
-  [Goerli.chainId]: "0xd559b9e1d3214179b8D5d177beCBd4eEB827Db6f",
-  [BaseGoerli.chainId]: "0x88d9A32D459BBc7B77fc912d9048926dEd78986B",
-  [OptimismGoerli.chainId]: "0x54ec360704b2e9E4e6499a732b78094D6d78e37B",
-  [ScrollAlphaTestnet.chainId]: "0x2eaDAa60dBB74Ead3E20b23E4C5A0Dd789932846",
-  [Mumbai.chainId]: "0x272A90FF4403473d766127A3CCB7ff1d9E7d45A2",
-  [Sepolia.chainId]: "0x9D4409c65AC036860F5CAAF34D5b69ae324A7075",
-  [CeloAlfajoresTestnet.chainId]: "0xE646849d679602F2588CA8eEDf0b261B1aB085AF",
+    [CeloAlfajoresTestnet.chainId]:
+        "0xE646849d679602F2588CA8eEDf0b261B1aB085AF",
 };
 
 const main = async () => {
-  try {
-    const factoryAddress = factories[chain.chainId];
-    console.log("Running on", chain.slug, "with factory", factoryAddress);
-    // Local signer
-    let localWallet = new LocalWalletNode({
-      chain,
-    });
-    await localWallet.loadOrCreate({
-      strategy: "mnemonic",
-      encryption: false,
-    });

[... diff too long, it was truncated ...]

GitHub
sha: b116192f8372850b7ce201d8d9d35e98bc3259c6