permissionless
Version:
A utility library for working with ERC-4337
216 lines • 8.23 kB
JavaScript
import { encodeAbiParameters, encodeFunctionData, encodePacked, toHex, zeroAddress } from "viem";
import { entryPoint07Abi, entryPoint07Address, getUserOperationHash, toSmartAccount } from "viem/account-abstraction";
import { getChainId } from "viem/actions";
import { getAction } from "viem/utils";
import { getAccountNonce } from "../../actions/public/getAccountNonce.js";
import { getSenderAddress } from "../../actions/public/getSenderAddress.js";
import { decode7579Calls } from "../../utils/decode7579Calls.js";
import { encode7579Calls } from "../../utils/encode7579Calls.js";
import { toOwner } from "../../utils/index.js";
import { DEFAULT_CONTRACT_ADDRESS, DUMMY_ECDSA_SIGNATURE } from "./constants.js";
import { getInitMSAData } from "./utils/getInitMSAData.js";
import { getNonceKeyWithEncoding } from "./utils/getNonceKey.js";
/**
* The account creation ABI for a modular etherspot smart account
*/
const createAccountAbi = [
{
inputs: [
{
internalType: "bytes32",
name: "salt",
type: "bytes32"
},
{
internalType: "bytes",
name: "initCode",
type: "bytes"
}
],
name: "createAccount",
outputs: [
{
internalType: "address",
name: "",
type: "address"
}
],
stateMutability: "payable",
type: "function"
}
];
/**
* Get default addresses for Etherspot Smart Account based on chainId
* @param chainId
* @param validatorAddress
* @param accountLogicAddress
* @param factoryAddress
* @param metaFactoryAddress
*/
const getDefaultAddresses = ({ validatorAddress: _validatorAddress, metaFactoryAddress: _metaFactoryAddress, bootstrapAddress: _bootstrapAddress }) => {
const addresses = DEFAULT_CONTRACT_ADDRESS;
const validatorAddress = _validatorAddress ?? addresses.validatorAddress;
const metaFactoryAddress = _metaFactoryAddress ?? addresses?.metaFactoryAddress ?? zeroAddress;
const bootstrapAddress = _bootstrapAddress ?? addresses.bootstrapAddress ?? zeroAddress;
return {
validatorAddress,
metaFactoryAddress,
bootstrapAddress
};
};
/**
* Get the initialization data for a etherspot smart account
* @param entryPoint
* @param owner
* @param validatorAddress
*/
const getInitialisationData = ({ owner, validatorAddress, bootstrapAddress }) => {
const initMSAData = getInitMSAData(validatorAddress);
const initCode = encodeAbiParameters([{ type: "address" }, { type: "address" }, { type: "bytes" }], [owner, bootstrapAddress, initMSAData]);
return initCode;
};
/**
* Get the account initialization code for a etherspot smart account
* @param entryPoint
* @param owner
* @param index
* @param validatorAddress
* @param bootstrapAddress
*/
const getAccountInitCode = async ({ owner, index, validatorAddress, bootstrapAddress }) => {
if (!owner)
throw new Error("Owner account not found");
// Build the account initialization data
const initialisationData = getInitialisationData({
validatorAddress,
owner,
bootstrapAddress
});
return encodeFunctionData({
abi: createAccountAbi,
functionName: "createAccount",
args: [toHex(index, { size: 32 }), initialisationData]
});
};
export async function toEtherspotSmartAccount(parameters) {
const { client, owners, address, index = BigInt(0), metaFactoryAddress: _metaFactoryAddress, validatorAddress: _validatorAddress, bootstrapAddress: _bootstrapAddress } = parameters;
const localOwner = await toOwner({ owner: owners[0] });
const entryPoint = {
address: parameters.entryPoint?.address ?? entryPoint07Address,
abi: entryPoint07Abi,
version: parameters.entryPoint?.version ?? "0.7"
};
const { validatorAddress, metaFactoryAddress, bootstrapAddress } = getDefaultAddresses({
validatorAddress: _validatorAddress,
metaFactoryAddress: _metaFactoryAddress,
bootstrapAddress: _bootstrapAddress
});
// Helper to generate the init code for the smart account
const generateInitCode = () => getAccountInitCode({
owner: localOwner.address,
index,
validatorAddress,
bootstrapAddress
});
let accountAddress = address;
let chainId;
const getMemoizedChainId = async () => {
if (chainId)
return chainId;
chainId = client.chain
? client.chain.id
: await getAction(client, getChainId, "getChainId")({});
return chainId;
};
const getFactoryArgs = async () => {
return {
factory: metaFactoryAddress,
factoryData: await generateInitCode()
};
};
return toSmartAccount({
client,
entryPoint,
getFactoryArgs,
async getAddress() {
if (accountAddress)
return accountAddress;
const { factory, factoryData } = await getFactoryArgs();
// Get the sender address based on the init code
accountAddress = await getSenderAddress(client, {
factory,
factoryData,
entryPointAddress: entryPoint.address
});
return accountAddress;
},
async encodeCalls(calls) {
return encode7579Calls({
mode: {
type: calls.length > 1 ? "batchcall" : "call",
revertOnError: false,
selector: "0x",
context: "0x"
},
callData: calls
});
},
async decodeCalls(callData) {
return decode7579Calls(callData).callData;
},
async getNonce(_args) {
return getAccountNonce(client, {
address: await this.getAddress(),
entryPointAddress: entryPoint.address,
key: getNonceKeyWithEncoding(validatorAddress,
/*args?.key ?? */ parameters.nonceKey ?? 0n)
});
},
async getStubSignature() {
return DUMMY_ECDSA_SIGNATURE;
},
async sign({ hash }) {
return this.signMessage({ message: hash });
},
async signMessage({ message }) {
let signature = await localOwner.signMessage({
message
});
const potentiallyIncorrectV = Number.parseInt(signature.slice(-2), 16);
if (![27, 28].includes(potentiallyIncorrectV)) {
const correctV = potentiallyIncorrectV + 27;
signature = (signature.slice(0, -2) +
correctV.toString(16));
}
return encodePacked(["address", "bytes"], [validatorAddress, signature]);
},
async signTypedData(typedData) {
let signature = await localOwner.signTypedData(typedData);
const potentiallyIncorrectV = Number.parseInt(signature.slice(-2), 16);
if (![27, 28].includes(potentiallyIncorrectV)) {
const correctV = potentiallyIncorrectV + 27;
signature = (signature.slice(0, -2) +
correctV.toString(16));
}
return encodePacked(["address", "bytes"], [validatorAddress, signature]);
},
async signUserOperation(parameters) {
const { chainId = await getMemoizedChainId(), ...userOperation } = parameters;
const hash = getUserOperationHash({
userOperation: {
...userOperation,
sender: userOperation.sender ?? (await this.getAddress()),
signature: "0x"
},
entryPointAddress: entryPoint.address,
entryPointVersion: entryPoint.version,
chainId: chainId
});
const signature = await localOwner.signMessage({
message: { raw: hash }
});
return signature;
}
});
}
//# sourceMappingURL=toEtherspotSmartAccount.js.map