@owlprotocol/contracts-account-abstraction
Version:
ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.
230 lines (229 loc) • 7.48 kB
JavaScript
import {
getOrDeployDeterministicContract,
DETERMINISTIC_DEPLOYER_ADDRESS,
getDeployDeterministicAddress,
getOrPrepareDeterministicContract
} from "@owlprotocol/viem-utils";
import {
encodeDeployData,
formatEther,
zeroHash
} from "viem";
import { entryPoint07Address } from "viem/account-abstraction";
import {
getCode,
readContract,
sendTransaction,
simulateContract,
waitForTransactionReceipt,
writeContract
} from "viem/actions";
import { getAction } from "viem/utils";
import { ENTRYPOINT_SALT_V07 } from "./constants.js";
import { EntryPoint } from "./artifacts/EntryPoint.js";
import { SimpleAccountFactory } from "./artifacts/SimpleAccountFactory.js";
import { VerifyingPaymaster } from "./artifacts/VerifyingPaymaster.js";
import { EntryPointSimulations } from "./artifacts/EntryPointSimulations.js";
import { PimlicoEntryPointSimulations } from "./artifacts/PimlicoEntryPointSimulations.js";
function getERC4337Contracts() {
const deterministicDeployer = DETERMINISTIC_DEPLOYER_ADDRESS;
const entrypoint = getDeployDeterministicAddress({ salt: ENTRYPOINT_SALT_V07, bytecode: EntryPoint.bytecode });
const simpleAccountFactory = getDeployDeterministicAddress({
salt: zeroHash,
bytecode: encodeDeployData({
abi: SimpleAccountFactory.abi,
bytecode: SimpleAccountFactory.bytecode,
args: [entrypoint]
})
});
const entrypointSimulations = getDeployDeterministicAddress({
salt: zeroHash,
bytecode: encodeDeployData({
abi: EntryPointSimulations.abi,
bytecode: EntryPointSimulations.bytecode,
args: []
})
});
const pimlicoEntrypointSimulations = getDeployDeterministicAddress({
salt: zeroHash,
bytecode: encodeDeployData({
abi: PimlicoEntryPointSimulations.abi,
bytecode: PimlicoEntryPointSimulations.bytecode,
args: [entrypointSimulations]
})
});
return {
deterministicDeployer,
entrypoint,
simpleAccountFactory,
entrypointSimulations,
pimlicoEntrypointSimulations
};
}
const erc4337Contracts = getERC4337Contracts();
async function prepareERC4337Contracts(client) {
const requests = [];
const [entrypoint, simpleAccountFactory, entrypointSimulations, pimlicoEntrypointSimulations] = await Promise.all([
getOrPrepareDeterministicContract(
client,
//Extracted salt (first 32 bytes) from original tx
//https://etherscan.io/tx/0x5c81ea86f6c54481d3e21c78675b4f1d985c1fa62b678dcdfdf7934ddd6e127e
{
salt: ENTRYPOINT_SALT_V07,
bytecode: EntryPoint.bytecode
}
),
getOrPrepareDeterministicContract(client, {
salt: zeroHash,
bytecode: encodeDeployData({
abi: SimpleAccountFactory.abi,
bytecode: SimpleAccountFactory.bytecode,
args: [erc4337Contracts.entrypoint]
})
}),
getOrPrepareDeterministicContract(client, {
salt: zeroHash,
bytecode: encodeDeployData({
abi: EntryPointSimulations.abi,
bytecode: EntryPointSimulations.bytecode,
args: []
})
}),
getOrPrepareDeterministicContract(client, {
salt: zeroHash,
bytecode: encodeDeployData({
abi: PimlicoEntryPointSimulations.abi,
bytecode: PimlicoEntryPointSimulations.bytecode,
args: [erc4337Contracts.entrypointSimulations]
})
})
]);
if (entrypoint.address != entryPoint07Address) {
throw new Error(
`Entrypoint v0.7 deployed address ${entryPoint07Address} (expected) != ${entrypoint.address} (actual)`
);
}
if (entrypoint.request)
requests.push(entrypoint.request);
if (simpleAccountFactory.request)
requests.push(simpleAccountFactory.request);
if (entrypointSimulations.request)
requests.push(entrypointSimulations.request);
if (pimlicoEntrypointSimulations.request)
requests.push(pimlicoEntrypointSimulations.request);
return {
requests,
entrypoint,
simpleAccountFactory,
entrypointSimulations,
pimlicoEntrypointSimulations
};
}
async function setupERC4337Contracts(client) {
if (!client.account.nonceManager) {
throw new Error("client.account.nonceManager undefined");
}
const erc4337Contracts2 = await prepareERC4337Contracts(client);
const transactions = await Promise.all(
erc4337Contracts2.requests.map(
(request) => getAction(client, sendTransaction, "sendTransaction")(request)
)
);
const receipts = await Promise.all(
transactions.map((hash) => getAction(client, waitForTransactionReceipt, "waitForTransactionReceipt")({ hash }))
);
return {
...erc4337Contracts2,
transactions,
receipts
};
}
function getVerifyingPaymaster(params) {
const { verifyingSignerAddress } = params;
return getDeployDeterministicAddress({
salt: zeroHash,
bytecode: encodeDeployData({
abi: VerifyingPaymaster.abi,
bytecode: VerifyingPaymaster.bytecode,
args: [entryPoint07Address, verifyingSignerAddress]
})
});
}
async function setupVerifyingPaymaster(client, parameters) {
const { verifyingSignerAddress } = parameters;
const entrypointBytecode = await getAction(client, getCode, "getCode")({ address: entryPoint07Address });
if (!entrypointBytecode) {
throw new Error(
`EntryPoint v0.7 ${entryPoint07Address} not deployed. Consider deploying with setupERC4337Contracts()`
);
}
const verifyingPaymaster = await getOrDeployDeterministicContract(client, {
salt: zeroHash,
bytecode: encodeDeployData({
abi: VerifyingPaymaster.abi,
bytecode: VerifyingPaymaster.bytecode,
args: [entryPoint07Address, verifyingSignerAddress]
})
});
if (verifyingPaymaster.hash) {
await getAction(
client,
waitForTransactionReceipt,
"waitForTransactionReceipt"
)({ hash: verifyingPaymaster.hash });
}
return verifyingPaymaster;
}
async function topupPaymaster(client, parameters) {
const { paymaster, minBalance } = parameters;
if (minBalance == 0n && parameters.targetBalance === void 0) {
throw new Error(`topupAddressL2: minBalance 0, targetBalance MUST be defined`);
}
const targetBalance = parameters.targetBalance ?? minBalance;
if (minBalance > targetBalance) {
throw new Error(
`topupPaymaster: minBalance (${formatEther(minBalance)}) > targetBalance (${formatEther(targetBalance)})`
);
}
if (0n >= targetBalance) {
throw new Error(`topupPaymaster: 0 >= targetBalance (${formatEther(targetBalance)})`);
}
const balance = await getAction(
client,
readContract,
"readContract"
)({
address: entryPoint07Address,
abi: EntryPoint.abi,
functionName: "balanceOf",
args: [paymaster]
});
const targetDeficit = targetBalance - balance;
if (targetDeficit > 0n && (balance < minBalance || minBalance == 0n)) {
const paymasterDeposit = await getAction(
client,
simulateContract,
"simulateContract"
)({
account: client.account,
chain: client.chain,
address: paymaster,
abi: VerifyingPaymaster.abi,
functionName: "deposit",
value: targetDeficit,
args: []
});
const paymasterDepositHash = await getAction(client, writeContract, "writeContract")(paymasterDeposit.request);
return { balance, hash: paymasterDepositHash };
}
return { balance };
}
export {
erc4337Contracts,
getERC4337Contracts,
getVerifyingPaymaster,
prepareERC4337Contracts,
setupERC4337Contracts,
setupVerifyingPaymaster,
topupPaymaster
};