UNPKG

@alchemy/aa-core

Version:

viem based SDK that enables interactions with ERC-4337 Smart Accounts. ABIs are based off the definitions generated in @account-abstraction/contracts

167 lines 6.63 kB
import { getContract, hexToBytes, trim, } from "viem"; import { toAccount } from "viem/accounts"; import { createBundlerClient } from "../client/bundlerClient.js"; import { BatchExecutionNotSupportedError, FailedToGetStorageSlotError, GetCounterFactualAddressError, SignTransactionNotSupportedError, UpgradesNotSupportedError, } from "../errors/account.js"; import { InvalidRpcUrlError } from "../errors/client.js"; import { InvalidEntryPointError } from "../errors/entrypoint.js"; import { Logger } from "../logger.js"; import { wrapSignatureWith6492 } from "../signer/utils.js"; import { DeploymentState } from "./base.js"; export const isSmartAccountWithSigner = (account) => { return "getSigner" in account; }; export const parseFactoryAddressFromAccountInitCode = (initCode) => { const factoryAddress = `0x${initCode.substring(2, 42)}`; const factoryCalldata = `0x${initCode.substring(42)}`; return [factoryAddress, factoryCalldata]; }; export const getAccountAddress = async ({ client, entryPoint, accountAddress, getAccountInitCode, }) => { if (accountAddress) return accountAddress; const entryPointContract = getContract({ address: entryPoint.address, abi: entryPoint.abi, client, }); const initCode = await getAccountInitCode(); Logger.verbose("[BaseSmartContractAccount](getAddress) initCode: ", initCode); try { await entryPointContract.simulate.getSenderAddress([initCode]); } catch (err) { Logger.verbose("[BaseSmartContractAccount](getAddress) getSenderAddress err: ", err); if (err.cause?.data?.errorName === "SenderAddressResult") { Logger.verbose("[BaseSmartContractAccount](getAddress) entryPoint.getSenderAddress result:", err.cause.data.args[0]); return err.cause.data.args[0]; } if (err.details === "Invalid URL") { throw new InvalidRpcUrlError(); } } throw new GetCounterFactualAddressError(); }; export async function toSmartContractAccount({ transport, chain, entryPoint, source, accountAddress, getAccountInitCode, signMessage, signTypedData, encodeBatchExecute, encodeExecute, getDummySignature, signUserOperationHash, encodeUpgradeToAndCall, }) { const client = createBundlerClient({ transport: (opts) => transport({ ...opts, chain, retryCount: 0 }), chain, }); const entryPointContract = getContract({ address: entryPoint.address, abi: entryPoint.abi, client, }); const accountAddress_ = await getAccountAddress({ client, entryPoint: entryPoint, accountAddress, getAccountInitCode, }); let deploymentState = DeploymentState.UNDEFINED; const getInitCode = async () => { if (deploymentState === DeploymentState.DEPLOYED) { return "0x"; } const contractCode = await client.getBytecode({ address: accountAddress_, }); if ((contractCode?.length ?? 0) > 2) { deploymentState = DeploymentState.DEPLOYED; return "0x"; } else { deploymentState = DeploymentState.NOT_DEPLOYED; } return getAccountInitCode(); }; const signUserOperationHash_ = signUserOperationHash ?? (async (uoHash) => { return signMessage({ message: { raw: hexToBytes(uoHash) } }); }); const getFactoryAddress = async () => parseFactoryAddressFromAccountInitCode(await getAccountInitCode())[0]; const getFactoryData = async () => parseFactoryAddressFromAccountInitCode(await getAccountInitCode())[1]; const encodeUpgradeToAndCall_ = encodeUpgradeToAndCall ?? (() => { throw new UpgradesNotSupportedError(source); }); const isAccountDeployed = async () => { const initCode = await getInitCode(); return initCode === "0x"; }; const getNonce = async (nonceKey = 0n) => { if (!(await isAccountDeployed())) { return 0n; } return entryPointContract.read.getNonce([ accountAddress_, nonceKey, ]); }; const account = toAccount({ address: accountAddress_, signMessage, signTypedData, signTransaction: () => { throw new SignTransactionNotSupportedError(); }, }); const create6492Signature = async (isDeployed, signature) => { if (isDeployed) { return signature; } const [factoryAddress, factoryCalldata] = parseFactoryAddressFromAccountInitCode(await getAccountInitCode()); return wrapSignatureWith6492({ factoryAddress, factoryCalldata, signature, }); }; const signMessageWith6492 = async (message) => { const [isDeployed, signature] = await Promise.all([ isAccountDeployed(), account.signMessage(message), ]); return create6492Signature(isDeployed, signature); }; const signTypedDataWith6492 = async (typedDataDefinition) => { const [isDeployed, signature] = await Promise.all([ isAccountDeployed(), account.signTypedData(typedDataDefinition), ]); return create6492Signature(isDeployed, signature); }; const getImplementationAddress = async () => { const storage = await client.getStorageAt({ address: account.address, slot: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", }); if (storage == null) { throw new FailedToGetStorageSlotError("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", "Proxy Implementation Address"); } return trim(storage); }; if (entryPoint.version !== "0.6.0" && entryPoint.version !== "0.7.0") { throw new InvalidEntryPointError(chain, entryPoint.version); } return { ...account, source, signUserOperationHash: signUserOperationHash_, getFactoryAddress, getFactoryData, encodeBatchExecute: encodeBatchExecute ?? (() => { throw new BatchExecutionNotSupportedError(source); }), encodeExecute, getDummySignature, getInitCode, encodeUpgradeToAndCall: encodeUpgradeToAndCall_, getEntryPoint: () => entryPoint, isAccountDeployed, getNonce, signMessageWith6492, signTypedDataWith6492, getImplementationAddress, }; } //# sourceMappingURL=smartContractAccount.js.map