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