import "@ethersproject/shims" import { Buffer } from 'buffer'; global.Buffer = Buffer; import { ContractTransaction, ethers, Signer, BigNumberish } from 'ethers' import { wrapProvider, PaymasterAPI, ERC4337EthersSigner } from "./account-abstraction" import { hexValue, resolveProperties } from 'ethers/lib/utils' import { UserOperationStruct, SimpleWallet__factory } from '@account-abstraction/contracts' import * as api from './api' import * as constants from './constants' export interface SignerParams { projectId: string identity?: string token?: string privateKey?: string web3Provider?: any, } export async function getSigner(params: SignerParams): Promise { let provider, chainId, signer if (params.privateKey) { chainId = await api.getChainId(params.projectId) provider = new ethers.providers.JsonRpcProvider(`https://${constants.CHAIN_ID_TO_INFURA_NAMES[chainId]}.infura.io/v3/${constants.INFURA_API_KEY}`) signer = new ethers.Wallet(params.privateKey, provider) } else if (params.web3Provider) { chainId = await api.getChainId(params.projectId) provider = new ethers.providers.Web3Provider(params.web3Provider as ethers.providers.ExternalProvider) signer = provider.getSigner() } else if (params.identity && params.token) { const [_chainId, privateKey] = await Promise.all([ api.getChainId(params.projectId), api.getPrivateKeyByToken(params.projectId, params.identity, params.token), ]) chainId = _chainId provider = new ethers.providers.JsonRpcProvider(`https://${constants.CHAIN_ID_TO_INFURA_NAMES[chainId]}.infura.io/v3/${constants.INFURA_API_KEY}`) signer = new ethers.Wallet(privateKey, provider) } else { throw new Error("must provide one of identity token, private key, or Web3 provider") } const paymaster = new ethers.Contract(constants.PAYMASTER_ADDRESS[chainId], constants.PAYMASTER_ABI, signer) const aaConfig = { chainId: chainId, entryPointAddress: constants.ENTRYPOINT_ADDRESS[chainId], bundlerUrl: constants.BUNDLER_URL[chainId], paymasterAPI: new VerifyingPaymasterAPI(params.projectId, paymaster), walletFactoryAddress: constants.WALLET_FACTORY_ADDRESS[chainId], } const aaProvider = await wrapProvider(provider, aaConfig, signer) const aaSigner = aaProvider.getSigner() return aaSigner } export interface Call { to: string, data: string, value?: BigNumberish, } export async function execBatch(signer: Signer, batch: Call[]): Promise { if (!(signer instanceof ERC4337EthersSigner)) { throw new Error("execBatch only works with a ZeroDev signer") } if (!batch.length) { throw new Error("batch must have at least one call") } // TODO: here we assume that the signer is tied to a `SimpleWallet`, which is not always // going to be the case. const wallet = new ethers.Contract(await signer.getAddress(), [ 'function execBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func)', ], signer) const dest = [] const value = [] const func = [] for (let call of batch) { dest.push(call.to) value.push(call.value || 0) func.push(call.data) } return wallet.execBatch(dest, value, func, { gasLimit: 2000000, }) } class VerifyingPaymasterAPI extends PaymasterAPI { constructor(readonly projectId: string, readonly paymaster: ethers.Contract) { super() this.projectId = projectId this.paymaster = paymaster } async getPaymasterAndData(userOp: Partial): Promise { const resolvedUserOp = await resolveProperties(userOp) const hexifiedUserOp: any = Object.keys(resolvedUserOp) .map(key => { let val = (resolvedUserOp as any)[key] if (typeof val !== 'string' || !val.startsWith('0x')) { val = hexValue(val) } return [key, val] }) .reduce((set, [k, v]) => ({ ...set, [k]: v }), {}) const signature = await api.signUserOp(this.projectId, hexifiedUserOp) if (!signature) { throw new Error('transaction failed gas checks') } return ethers.utils.hexConcat([ this.paymaster.address, signature, ]) } }