permissionless
Version:
A utility library for working with ERC-4337
647 lines • 25.5 kB
JavaScript
import { concatHex, decodeFunctionData, encodeAbiParameters, encodeFunctionData, keccak256, toHex, zeroAddress } from "viem";
import { entryPoint06Abi, entryPoint07Abi, entryPoint07Address, getUserOperationHash, toSmartAccount } from "viem/account-abstraction";
import { getChainId, readContract } from "viem/actions";
import { getAction } from "viem/utils";
import { getAccountNonce } from "../../actions/public/getAccountNonce.js";
import { getSenderAddress } from "../../actions/public/getSenderAddress.js";
import { encode7579Calls } from "../../utils/encode7579Calls.js";
import { encodeInstallModule } from "../../utils/encodeInstallModule.js";
import { encodeUninstallModule } from "../../utils/encodeUninstallModule.js";
import { getOxExports } from "../../utils/ox.js";
import { toOwner } from "../../utils/toOwner.js";
import { KernelInitAbi } from "./abi/KernelAccountAbi.js";
import { KernelV3InitAbi, KernelV3_1AccountAbi } from "./abi/KernelV3AccountAbi.js";
import { KernelV3FactoryAbi } from "./abi/KernelV3FactoryAbi.js";
import { KernelV3MetaFactoryDeployWithFactoryAbi } from "./abi/KernelV3MetaFactoryAbi.js";
import { DUMMY_ECDSA_SIGNATURE, ROOT_MODE_KERNEL_V2, VALIDATOR_TYPE } from "./constants.js";
import { decodeCallData } from "./utils/decodeCallData.js";
import { encodeCallData } from "./utils/encodeCallData.js";
import { getNonceKeyWithEncoding } from "./utils/getNonceKey.js";
import { isKernelV2 } from "./utils/isKernelV2.js";
import { isWebAuthnAccount } from "./utils/isWebAuthnAccount.js";
import { signMessage } from "./utils/signMessage.js";
import { signTypedData } from "./utils/signTypedData.js";
const migrationHelperAbi = [
{
type: "function",
name: "migrateWithCall",
inputs: [
{
name: "validators",
type: "address[]",
internalType: "contract IValidator[]"
},
{
name: "permissionIds",
type: "bytes4[]",
internalType: "bytes4[]"
},
{ name: "execMode", type: "bytes32", internalType: "ExecMode" },
{ name: "execData", type: "bytes", internalType: "bytes" }
],
outputs: [],
stateMutability: "nonpayable"
}
];
/**
* The account creation ABI for a kernel smart account (from the KernelFactory)
*/
const createAccountAbi = [
{
inputs: [
{
internalType: "address",
name: "_implementation",
type: "address"
},
{
internalType: "bytes",
name: "_data",
type: "bytes"
},
{
internalType: "uint256",
name: "_index",
type: "uint256"
}
],
name: "createAccount",
outputs: [
{
internalType: "address",
name: "proxy",
type: "address"
}
],
stateMutability: "payable",
type: "function"
}
];
export const MIGRATION_HELPER_ADDRESS = "0x03EB97959433D55748839D27C93330Cb85F31A93";
/**
* Default addresses map for different kernel smart account versions
*/
export const KERNEL_VERSION_TO_ADDRESSES_MAP = {
"0.2.1": {
ECDSA_VALIDATOR: "0xd9AB5096a832b9ce79914329DAEE236f8Eea0390",
ACCOUNT_LOGIC: "0xf048AD83CB2dfd6037A43902a2A5Be04e53cd2Eb",
FACTORY_ADDRESS: "0x5de4839a76cf55d0c90e2061ef4386d962E15ae3"
},
"0.2.2": {
ECDSA_VALIDATOR: "0xd9AB5096a832b9ce79914329DAEE236f8Eea0390",
ACCOUNT_LOGIC: "0x0DA6a956B9488eD4dd761E59f52FDc6c8068E6B5",
FACTORY_ADDRESS: "0x5de4839a76cf55d0c90e2061ef4386d962E15ae3"
},
"0.2.3": {
ECDSA_VALIDATOR: "0xd9AB5096a832b9ce79914329DAEE236f8Eea0390",
ACCOUNT_LOGIC: "0xD3F582F6B4814E989Ee8E96bc3175320B5A540ab",
FACTORY_ADDRESS: "0x5de4839a76cf55d0c90e2061ef4386d962E15ae3"
},
"0.2.4": {
ECDSA_VALIDATOR: "0xd9AB5096a832b9ce79914329DAEE236f8Eea0390",
ACCOUNT_LOGIC: "0xd3082872F8B06073A021b4602e022d5A070d7cfC",
FACTORY_ADDRESS: "0x5de4839a76cf55d0c90e2061ef4386d962E15ae3"
},
"0.3.0-beta": {
ECDSA_VALIDATOR: "0x8104e3Ad430EA6d354d013A6789fDFc71E671c43",
ACCOUNT_LOGIC: "0x94F097E1ebEB4ecA3AAE54cabb08905B239A7D27",
FACTORY_ADDRESS: "0x6723b44Abeec4E71eBE3232BD5B455805baDD22f",
META_FACTORY_ADDRESS: "0xd703aaE79538628d27099B8c4f621bE4CCd142d5",
WEB_AUTHN_VALIDATOR: "0x7ab16Ff354AcB328452F1D445b3Ddee9a91e9e69"
},
"0.3.1": {
ECDSA_VALIDATOR: "0x845ADb2C711129d4f3966735eD98a9F09fC4cE57",
ACCOUNT_LOGIC: "0xBAC849bB641841b44E965fB01A4Bf5F074f84b4D",
FACTORY_ADDRESS: "0xaac5D4240AF87249B3f71BC8E4A2cae074A3E419",
META_FACTORY_ADDRESS: "0xd703aaE79538628d27099B8c4f621bE4CCd142d5",
WEB_AUTHN_VALIDATOR: "0x7ab16Ff354AcB328452F1D445b3Ddee9a91e9e69"
},
"0.3.2": {
ECDSA_VALIDATOR: "0x845ADb2C711129d4f3966735eD98a9F09fC4cE57",
ACCOUNT_LOGIC: "0xD830D15D3dc0C269F3dBAa0F3e8626d33CFdaBe1",
FACTORY_ADDRESS: "0x7a1dBAB750f12a90EB1B60D2Ae3aD17D4D81EfFe",
META_FACTORY_ADDRESS: "0xd703aaE79538628d27099B8c4f621bE4CCd142d5"
},
"0.3.3": {
ECDSA_VALIDATOR: "0x845ADb2C711129d4f3966735eD98a9F09fC4cE57",
ACCOUNT_LOGIC: "0xd6CEDDe84be40893d153Be9d467CD6aD37875b28",
FACTORY_ADDRESS: "0x2577507b78c2008Ff367261CB6285d44ba5eF2E9",
META_FACTORY_ADDRESS: "0xd703aaE79538628d27099B8c4f621bE4CCd142d5"
}
};
/**
* Get supported Kernel Smart Account version based on entryPoint
* @param entryPoint
*/
const getDefaultKernelVersion = (entryPointVersion, version, eip7702) => {
if (eip7702) {
return "0.3.3";
}
if (version) {
return version;
}
return (entryPointVersion === "0.6" ? "0.2.2" : "0.3.0-beta");
};
/**
* Get default addresses for Kernel Smart Account based on entryPoint or user input
* @param entryPointAddress
* @param ecdsaValidatorAddress
* @param accountLogicAddress
* @param factoryAddress
* @param metaFactoryAddress
*/
const getDefaultAddresses = ({ validatorAddress: _validatorAddress, accountLogicAddress: _accountLogicAddress, factoryAddress: _factoryAddress, metaFactoryAddress: _metaFactoryAddress, kernelVersion, isWebAuthn }) => {
const addresses = KERNEL_VERSION_TO_ADDRESSES_MAP[kernelVersion];
const validatorAddress = _validatorAddress ??
(isWebAuthn ? addresses.WEB_AUTHN_VALIDATOR : addresses.ECDSA_VALIDATOR);
const accountLogicAddress = _accountLogicAddress ?? addresses.ACCOUNT_LOGIC;
const factoryAddress = _factoryAddress ?? addresses.FACTORY_ADDRESS;
const metaFactoryAddress = _metaFactoryAddress ?? addresses?.META_FACTORY_ADDRESS ?? zeroAddress; // Meta Factory doesn't exists for Kernel v2.2
return {
validatorAddress,
accountLogicAddress,
factoryAddress,
metaFactoryAddress
};
};
export const getEcdsaRootIdentifierForKernelV3 = (validatorAddress, eip7702 = false) => {
return concatHex([
eip7702 ? VALIDATOR_TYPE.EIP7702 : VALIDATOR_TYPE.VALIDATOR,
eip7702 ? "0x" : validatorAddress
]);
};
/**
* Get the initialization data for a kernel smart account
* @param entryPoint
* @param owner
* @param ecdsaValidatorAddress
*/
const getInitializationData = ({ entryPoint: { version: entryPointVersion }, kernelVersion, validatorData, validatorAddress }) => {
if (entryPointVersion === "0.6") {
return encodeFunctionData({
abi: KernelInitAbi,
functionName: "initialize",
args: [validatorAddress, validatorData]
});
}
if (kernelVersion === "0.3.0-beta") {
return encodeFunctionData({
abi: KernelV3InitAbi,
functionName: "initialize",
args: [
getEcdsaRootIdentifierForKernelV3(validatorAddress),
zeroAddress,
validatorData,
"0x"
]
});
}
return encodeFunctionData({
abi: KernelV3_1AccountAbi,
functionName: "initialize",
args: [
getEcdsaRootIdentifierForKernelV3(validatorAddress),
zeroAddress,
validatorData,
"0x",
[]
]
});
};
const getValidatorData = async (owner) => {
if (owner.type === "local") {
return owner.address;
}
if (isWebAuthnAccount(owner)) {
const { PublicKey, Hex, Base64 } = await getOxExports();
const parsedPublicKey = PublicKey.fromHex(owner.publicKey);
const authenticatorIdHash = keccak256(Hex.fromBytes(Base64.toBytes(owner.id)));
return encodeAbiParameters([
{
components: [
{ name: "x", type: "uint256" },
{ name: "y", type: "uint256" }
],
name: "webAuthnData",
type: "tuple"
},
{
name: "authenticatorIdHash",
type: "bytes32"
}
], [
{
x: parsedPublicKey.x,
y: parsedPublicKey.y
},
authenticatorIdHash
]);
}
throw new Error("Invalid owner type");
};
/**
* Get the account initialization code for a kernel smart account
* @param entryPoint
* @param owner
* @param index
* @param factoryAddress
* @param accountLogicAddress
* @param ecdsaValidatorAddress
*/
const getAccountInitCode = async ({ entryPointVersion, kernelVersion, validatorData, index, factoryAddress, accountLogicAddress, validatorAddress, useMetaFactory }) => {
// Build the account initialization data
const initializationData = getInitializationData({
entryPoint: { version: entryPointVersion },
kernelVersion,
validatorAddress,
validatorData
});
// Build the account init code
if (entryPointVersion === "0.6") {
return encodeFunctionData({
abi: createAccountAbi,
functionName: "createAccount",
args: [accountLogicAddress, initializationData, index]
});
}
if (!useMetaFactory) {
return encodeFunctionData({
abi: KernelV3FactoryAbi,
functionName: "createAccount",
args: [initializationData, toHex(index, { size: 32 })]
});
}
return encodeFunctionData({
abi: KernelV3MetaFactoryDeployWithFactoryAbi,
functionName: "deployWithFactory",
args: [factoryAddress, initializationData, toHex(index, { size: 32 })]
});
};
/**
* Build a kernel smart account from a private key, that use the ECDSA or passkeys signer behind the scene
* @param client
* @param privateKey
* @param entryPoint
* @param index
* @param factoryAddress
* @param accountLogicAddress
* @param validatorAddress
*/
export async function toKernelSmartAccount(parameters) {
const { client, address, index = 0n, version, validatorAddress: _validatorAddress, factoryAddress: _factoryAddress, metaFactoryAddress: _metaFactoryAddress, accountLogicAddress: _accountLogicAddress, useMetaFactory = true, eip7702 = false } = parameters;
const owners = (() => {
if (eip7702 && "owner" in parameters) {
return [parameters.owner];
}
if ("owners" in parameters) {
return parameters.owners;
}
throw new Error("Invalid parameters");
})();
const isWebAuthn = owners[0].type === "webAuthn";
const owner = await (() => {
if (isWebAuthn) {
return owners[0];
}
return toOwner({
owner: owners[0]
});
})();
const entryPoint = (() => {
const address = parameters.entryPoint?.address ?? entryPoint07Address;
const version = parameters.entryPoint?.version ?? "0.7";
let abi = entryPoint07Abi;
if (version === "0.6") {
abi = entryPoint06Abi;
}
return {
address,
abi,
version
};
})();
const kernelVersion = getDefaultKernelVersion(entryPoint.version, version, eip7702);
const { accountLogicAddress, validatorAddress, factoryAddress, metaFactoryAddress } = getDefaultAddresses({
validatorAddress: _validatorAddress,
accountLogicAddress: _accountLogicAddress,
factoryAddress: _factoryAddress,
metaFactoryAddress: _metaFactoryAddress,
kernelVersion,
isWebAuthn
});
if (!validatorAddress) {
throw new Error("Validator address is required");
}
// Helper to generate the init code for the smart account
const generateInitCode = async (_useMetaFactory) => getAccountInitCode({
entryPointVersion: entryPoint.version,
kernelVersion,
validatorData: await getValidatorData(owner),
index,
factoryAddress,
accountLogicAddress,
validatorAddress,
useMetaFactory: _useMetaFactory
});
let chainId;
const getMemoizedChainId = async () => {
if (chainId)
return chainId;
chainId = client.chain
? client.chain.id
: await getAction(client, getChainId, "getChainId")({});
return chainId;
};
const getFactoryArgsFunc = (_useMetaFactory) => async () => {
return {
factory: entryPoint.version === "0.6" || _useMetaFactory === false
? factoryAddress
: metaFactoryAddress,
factoryData: (await generateInitCode(_useMetaFactory))
};
};
const { accountAddress, getFactoryArgs } = await (async () => {
if (eip7702) {
return {
accountAddress: owner.address,
getFactoryArgs: async () => {
return {
factory: undefined,
factoryData: undefined
};
}
};
}
let getFactoryArgs = getFactoryArgsFunc(useMetaFactory === "optional" ? true : useMetaFactory);
if (address && useMetaFactory !== "optional") {
return { accountAddress: address, getFactoryArgs };
}
const { factory, factoryData } = await getFactoryArgs();
let accountAddress = await getSenderAddress(client, {
factory,
factoryData,
entryPointAddress: entryPoint.address
});
if (address === accountAddress) {
return { accountAddress, getFactoryArgs };
}
if (useMetaFactory === "optional" && accountAddress === zeroAddress) {
getFactoryArgs = getFactoryArgsFunc(false);
const { factory, factoryData } = await getFactoryArgs();
accountAddress = await getSenderAddress(client, {
factory,
factoryData,
entryPointAddress: entryPoint.address
});
}
return { accountAddress, getFactoryArgs };
})();
const getKernelAccountPatchedStatus = async () => {
const rootValidator = await getAction(client, readContract, "readContract")({
address: accountAddress,
abi: [
{
type: "function",
name: "rootValidator",
inputs: [],
outputs: [
{
name: "",
type: "bytes21",
internalType: "ValidationId"
}
],
stateMutability: "view"
}
],
functionName: "rootValidator",
args: []
});
const patchedRootValidator = "0x017ab16ff354acb328452f1d445b3ddee9a91e9e69";
return rootValidator.toLowerCase() === patchedRootValidator;
};
let isKernelAccountPatched = validatorAddress !== "0xbA45a2BFb8De3D24cA9D7F1B551E14dFF5d690Fd";
return toSmartAccount({
client,
entryPoint,
getFactoryArgs,
extend: eip7702
? {
implementation: accountLogicAddress
}
: undefined,
authorization: eip7702
? {
address: accountLogicAddress,
account: owner
}
: undefined,
async getAddress() {
return accountAddress;
},
async encodeCalls(calls) {
if (!isKernelAccountPatched) {
const isDeployed = "isDeployed" in this && (await this.isDeployed());
isKernelAccountPatched =
isDeployed && (await getKernelAccountPatchedStatus());
}
if (!isKernelAccountPatched) {
const [installFallbackCall] = encodeInstallModule({
account: this,
modules: [
{
type: "fallback",
address: MIGRATION_HELPER_ADDRESS,
context: "0x36f541c10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000"
}
]
});
const [uninstallFallbackCall] = encodeUninstallModule({
account: this,
modules: [
{
type: "fallback",
address: MIGRATION_HELPER_ADDRESS,
context: "0x36f541c1"
}
]
});
const executeCallData = encodeCallData({
calls,
kernelVersion
});
const { args } = decodeFunctionData({
abi: [
{
type: "function",
name: "execute",
inputs: [
{
name: "execMode",
type: "bytes32",
internalType: "ExecMode"
},
{
name: "executionCalldata",
type: "bytes",
internalType: "bytes"
}
],
outputs: [],
stateMutability: "payable"
}
],
data: executeCallData
});
const migrationCallData = encodeFunctionData({
abi: migrationHelperAbi,
functionName: "migrateWithCall",
args: [[], [], ...args]
});
if (kernelVersion !== "0.3.0-beta") {
return encode7579Calls({
mode: {
type: "delegatecall",
revertOnError: false,
selector: "0x",
context: "0x"
},
callData: [
{
to: MIGRATION_HELPER_ADDRESS,
data: migrationCallData,
value: 0n
}
]
});
}
return encodeCallData({
calls: [
installFallbackCall,
{
to: accountAddress,
data: migrationCallData,
value: 0n
},
uninstallFallbackCall
],
kernelVersion
});
}
return encodeCallData({ calls, kernelVersion });
},
async decodeCalls(callData) {
return decodeCallData({ callData, kernelVersion });
},
async getNonce(_args) {
return getAccountNonce(client, {
address: await this.getAddress(),
entryPointAddress: entryPoint.address,
key: getNonceKeyWithEncoding(kernelVersion, validatorAddress,
/*args?.key ?? */ parameters.nonceKey ?? 0n)
});
},
async getStubSignature() {
if (isKernelV2(kernelVersion)) {
return concatHex([ROOT_MODE_KERNEL_V2, DUMMY_ECDSA_SIGNATURE]);
}
if (isWebAuthnAccount(owner)) {
return encodeAbiParameters([
{ name: "authenticatorData", type: "bytes" },
{ name: "clientDataJSON", type: "string" },
{ name: "responseTypeLocation", type: "uint256" },
{ name: "r", type: "uint256" },
{ name: "s", type: "uint256" },
{ name: "usePrecompiled", type: "bool" }
], [
"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000",
'{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false, "other_keys_can_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex"}',
1n,
44941127272049826721201904734628716258498742255959991581049806490182030242267n,
9910254599581058084911561569808925251374718953855182016200087235935345969636n,
false
]);
}
return DUMMY_ECDSA_SIGNATURE;
},
async sign({ hash }) {
return this.signMessage({ message: hash });
},
async signMessage({ message }) {
if ("isDeployed" in this &&
!(await this.isDeployed()) &&
eip7702) {
throw new Error("Kernel with EIP-7702 isn't 1271 compliant before delegation.");
}
const signature = await signMessage({
owner,
message,
accountAddress: await this.getAddress(),
kernelVersion: kernelVersion,
chainId: await getMemoizedChainId(),
eip7702: eip7702
});
if (isKernelV2(kernelVersion)) {
return signature;
}
return concatHex([
getEcdsaRootIdentifierForKernelV3(validatorAddress, eip7702),
signature
]);
},
async signTypedData(typedData) {
if ("isDeployed" in this &&
!(await this.isDeployed()) &&
eip7702) {
throw new Error("Kernel with EIP-7702 isn't 1271 compliant before delegation.");
}
const signature = await signTypedData({
owner: owner,
chainId: await getMemoizedChainId(),
...typedData,
accountAddress: await this.getAddress(),
kernelVersion: kernelVersion,
eip7702
});
if (isKernelV2(kernelVersion)) {
return signature;
}
return concatHex([
getEcdsaRootIdentifierForKernelV3(validatorAddress, eip7702),
signature
]);
},
// Sign a user operation
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 = isWebAuthnAccount(owner)
? await signMessage({
owner,
message: { raw: hash },
chainId,
accountAddress: await this.getAddress(),
kernelVersion,
eip7702: false
})
: await owner.signMessage({
message: { raw: hash }
});
// Always use the sudo mode, since we will use external paymaster
if (isKernelV2(kernelVersion)) {
return concatHex(["0x00000000", signature]);
}
return signature;
}
});
}
//# sourceMappingURL=toKernelSmartAccount.js.map