@owlprotocol/contracts-account-abstraction
Version:
ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.
244 lines (217 loc) • 8.77 kB
text/typescript
import { describe, test, beforeAll, beforeEach, expect } from "vitest";
import {
Address,
Chain,
Transport,
createPublicClient,
createWalletClient,
http,
parseEther,
PrivateKeyAccount,
padHex,
custom,
Hex,
EIP1193RequestFn,
numberToHex,
nonceManager,
Account,
HttpTransport,
} from "viem";
import { localhost } from "viem/chains";
import { sepolia } from "@owlprotocol/chains";
import {
entryPoint07Address,
createPaymasterClient,
SmartAccount,
waitForUserOperationReceipt,
PaymasterClient,
createBundlerClient,
BundlerClient,
} from "viem/account-abstraction";
import {
getLocalAccount,
getDeployDeterministicAddress,
getDeployDeterministicFunctionData,
getUtilityAccount,
getPaymasterSignerAccount,
} from "@owlprotocol/viem-utils";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { SimpleSmartAccountImplementation, toSimpleSmartAccount } from "permissionless/accounts";
import { createSmartAccountClient, SmartAccountClient } from "permissionless/clients";
import { getUserOperationGasPrice } from "permissionless/actions/pimlico";
import { createHttpEIP1193, createPublicEIP1193 } from "@owlprotocol/backend-public/eip1193";
import { NODE_ENV } from "@owlprotocol/envvars";
import { createBackendBundlerEIP1193 } from "./createBundlerEIP1193.js";
import { createBackendPaymasterEIP1193 } from "./createPaymasterEIP1193.js";
import { createBackendBundler } from "../clients/createBackendBundler.js";
import { createBackendPaymaster } from "../clients/createBackendPaymaster.js";
import { port } from "../test/constants.js";
import { getSimpleAccountAddress } from "../SimpleAccount.js";
import { ERC1967Proxy } from "../artifacts/ERC1967Proxy.js";
import { MyContract } from "../artifacts/MyContract.js";
import { erc4337Contracts, setupVerifyingPaymaster, topupPaymaster } from "../setupERC4337Contracts.js";
describe("eip1993/createPaymasterEIP1193.test.ts", function () {
let chain: Chain;
let account: Account;
let transport: HttpTransport;
if (NODE_ENV === "test") {
chain = {
...localhost,
rpcUrls: {
default: {
http: [`http://127.0.0.1:${port}`],
},
},
};
account = getLocalAccount(0, { nonceManager });
transport = http(chain.rpcUrls.default.http[0]);
} else {
chain = sepolia as unknown as Chain;
account = getUtilityAccount({ nonceManager });
transport = http(sepolia.rpcUrls.drpc!.http[0]);
}
const publicClient = createPublicClient({
chain,
transport,
});
const walletClient = createWalletClient({
account,
chain,
transport,
});
// Contracts
const entryPointAddress = erc4337Contracts.entrypoint;
const entryPointSimulationsAddress = erc4337Contracts.pimlicoEntrypointSimulations;
const factoryAddress = erc4337Contracts.simpleAccountFactory;
let paymasterAddress: Address;
// AA clients
let bundlerTransport: Transport;
let bundlerClient: BundlerClient;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let paymasterRequest: EIP1193RequestFn;
let paymasterTransport: Transport;
let paymasterClient: PaymasterClient;
beforeAll(async () => {
// Paymaster
paymasterAddress = (
await setupVerifyingPaymaster(walletClient, {
verifyingSignerAddress: getPaymasterSignerAccount({ nonceManager }).address,
})
).address;
if (NODE_ENV === "test") {
const { hash: topupHash } = await topupPaymaster(walletClient, {
paymaster: paymasterAddress,
minBalance: parseEther("1"),
});
if (topupHash) {
await publicClient.waitForTransactionReceipt({ hash: topupHash });
}
}
// AA Clients
const requestOverride = createPublicEIP1193(createHttpEIP1193(`http://127.0.0.1:${port}`));
const bundlerRequest = createBackendBundlerEIP1193(
createBackendBundler({
account: walletClient.account,
chain,
transport,
entryPointSimulationsAddress,
}),
//viem middleware bypass
{ requestOverride },
);
bundlerTransport = custom({
request: bundlerRequest,
config: { retryCount: 0 },
});
bundlerClient = createBundlerClient({ transport: bundlerTransport });
paymasterRequest = createBackendPaymasterEIP1193(
createBackendPaymaster({
account: getPaymasterSignerAccount({ nonceManager }),
chain,
transport,
paymaster: paymasterAddress,
}),
);
paymasterTransport = custom({
request: paymasterRequest,
config: { retryCount: 0 },
});
paymasterClient = createPaymasterClient({ transport: paymasterTransport });
});
describe("paymasterClient", () => {
test("pm_getPaymasterStubData", async () => {
const result = await paymasterClient.request({
method: "pm_getPaymasterStubData",
params: [{} as any, entryPoint07Address, numberToHex(chain.id), {}],
});
expect(result).toBeDefined();
});
});
describe("smartAccountClient", () => {
// Generated account on each test
let owner: PrivateKeyAccount;
let smartAccount: SmartAccount<SimpleSmartAccountImplementation<"0.7">>;
let smartAccountClient: SmartAccountClient<Transport, Chain, SmartAccount>;
let contractAddressExpected: Address;
let contractDeployTransaction: { to: Address; data: Hex };
beforeEach(async () => {
owner = privateKeyToAccount(generatePrivateKey());
const smartAccountAddress = getSimpleAccountAddress(
{
owner: owner.address,
salt: 0n,
},
{
factoryAddress,
proxyBytecode: ERC1967Proxy.bytecode,
},
);
smartAccount = await toSimpleSmartAccount({
address: smartAccountAddress,
client: publicClient,
owner: owner,
factoryAddress,
entryPoint: {
address: entryPointAddress,
version: "0.7",
},
});
smartAccountClient = createSmartAccountClient({
account: smartAccount,
chain: publicClient.chain,
paymaster: paymasterClient,
bundlerTransport,
userOperation: {
estimateFeesPerGas: async () => (await getUserOperationGasPrice(paymasterClient)).fast,
},
});
//Deploy hello world contract
const deployParams = {
// "random" salt
salt: padHex(owner.address, { size: 32 }),
bytecode: MyContract.bytecode,
};
contractDeployTransaction = getDeployDeterministicFunctionData(deployParams);
contractAddressExpected = getDeployDeterministicAddress(deployParams);
});
test("smartAccountClient.sendTransaction", async () => {
const hash = await smartAccountClient.sendTransaction(contractDeployTransaction);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
expect(receipt).toBeDefined();
const contractBytecode = await publicClient.getCode({ address: contractAddressExpected });
expect(contractBytecode).toBeDefined();
});
test("smartAccountClient.prepareUserOperation", async () => {
const userOperation = await smartAccountClient.prepareUserOperation({
calls: [contractDeployTransaction],
});
userOperation.signature = await smartAccount.signUserOperation(userOperation as any);
const userOpHash = await bundlerClient.sendUserOperation(userOperation as any);
const userOpReceipt = await waitForUserOperationReceipt(bundlerClient, { hash: userOpHash });
expect(userOpReceipt).toBeDefined();
console.log({ contractAddressExpected });
const contractBytecode = await publicClient.getCode({ address: contractAddressExpected });
expect(contractBytecode).toBeDefined();
});
});
});