@zerodev/sdk
Version:
A utility library for working with ERC-4337
265 lines • 10.6 kB
JavaScript
import { concatHex, createNonceManager, encodeDeployData, encodeFunctionData, getTypesForEIP712Domain, hashTypedData, parseAbi, validateTypedData } from "viem";
import { entryPoint06Abi, entryPoint06Address, toSmartAccount } from "viem/account-abstraction";
import { toAccount } from "viem/accounts";
import { getChainId } from "viem/actions";
import { getAction } from "viem/utils";
import { getAccountNonce, getSenderAddress } from "../../../actions/public/index.js";
import { KernelVersionToAddressesMap } from "../../../constants.js";
import { MULTISEND_ADDRESS, encodeMultiSend, multiSendAbi } from "../../utils/multisend.js";
import { isKernelPluginManager, toKernelPluginManager } from "../../utils/toKernelPluginManager.js";
import { KernelAccountV2Abi } from "./abi/KernelAccountV2Abi.js";
import { KernelFactoryV2Abi } from "./abi/KernelFactoryV2Abi.js";
// Safe's library for create and create2: https://github.com/safe-global/safe-contracts/blob/0acdd35a203299585438f53885df630f9d486a86/contracts/libraries/CreateCall.sol
// Address was found here: https://github.com/safe-global/safe-deployments/blob/926ec6bbe2ebcac3aa2c2c6c0aff74aa590cbc6a/src/assets/v1.4.1/create_call.json
const createCallAddress = "0x9b35Af71d77eaf8d7e40252370304687390A1A52";
const createCallAbi = parseAbi([
"function performCreate(uint256 value, bytes memory deploymentData) public returns (address newContract)",
"function performCreate2(uint256 value, bytes memory deploymentData, bytes32 salt) public returns (address newContract)"
]);
/**
* Default addresses for kernel smart account
*/
export const KERNEL_ADDRESSES = {
FACTORY_ADDRESS: "0xaee9762ce625e0a8f7b184670fb57c37bfe1d0f1",
ENTRYPOINT_V0_6: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"
};
/**
* Get the account initialization code for a kernel smart account
* @param index
* @param factoryAddress
* @param ecdsaValidatorAddress
*/
const getAccountInitCode = async ({ index, factoryAddress, validatorAddress, enableData }) => {
// Build the account init code
return concatHex([
factoryAddress,
encodeFunctionData({
abi: KernelFactoryV2Abi,
functionName: "createAccount",
args: [validatorAddress, enableData, index]
})
]);
};
/**
* Build a kernel smart account from a private key, that use the ECDSA signer behind the scene
* @param client
* @param privateKey
* @param entryPoint
* @param index
* @param factoryAddress
* @param ecdsaValidatorAddress
* @param deployedAccountAddress
*/
export async function createKernelAccountV0_2(client, { plugins, entryPoint, index = 0n, factoryAddress = KERNEL_ADDRESSES.FACTORY_ADDRESS, address }) {
const kernelPluginManager = isKernelPluginManager(plugins)
? plugins
: await toKernelPluginManager(client, {
sudo: plugins.sudo,
regular: plugins.regular,
action: plugins.action,
pluginEnableSignature: plugins.pluginEnableSignature,
kernelVersion: "0.0.2",
entryPoint
});
// Helper to generate the init code for the smart account
const generateInitCode = async () => {
const validatorInitData = await kernelPluginManager.getValidatorInitData();
return getAccountInitCode({
index,
factoryAddress,
validatorAddress: validatorInitData.validatorAddress,
enableData: validatorInitData.enableData
});
};
const getFactoryArgs = async () => {
return {
factory: factoryAddress,
factoryData: await generateInitCode()
};
};
// Fetch account address
let accountAddress = address ??
(await (async () => {
const { factory, factoryData } = await getFactoryArgs();
// Get the sender address based on the init code
return await getSenderAddress(client, {
factory,
factoryData,
entryPointAddress: entryPoint.address
});
})());
// Build the EOA Signer
const account = toAccount({
address: accountAddress,
async signMessage({ message }) {
return kernelPluginManager.signMessage({
message
});
},
async signTransaction(_, __) {
throw new Error("Smart account signer doesn't need to sign transactions");
},
async signTypedData(typedData) {
const _typedData = typedData;
const types = {
EIP712Domain: getTypesForEIP712Domain({
domain: _typedData.domain
}),
..._typedData.types
};
// Need to do a runtime validation check on addresses, byte ranges, integer ranges, etc
// as we can't statically check this with TypeScript.
validateTypedData({
domain: _typedData.domain,
message: _typedData.message,
primaryType: _typedData.primaryType,
types: types
});
const typedHash = hashTypedData(typedData);
return kernelPluginManager.signMessage({
message: {
raw: typedHash
}
});
}
});
const _entryPoint = {
address: entryPoint?.address ?? entryPoint06Address,
abi: entryPoint06Abi,
version: entryPoint?.version ?? "0.6"
};
let chainId;
const getMemoizedChainId = async () => {
if (chainId)
return chainId;
chainId = client.chain
? client.chain.id
: await getAction(client, getChainId, "getChainId")({});
return chainId;
};
return toSmartAccount({
kernelVersion: "0.0.2",
client,
entryPoint: _entryPoint,
kernelPluginManager,
factoryAddress,
accountImplementationAddress: KernelVersionToAddressesMap["0.0.2"].accountImplementationAddress,
generateInitCode,
encodeModuleInstallCallData: async () => {
return await kernelPluginManager.encodeModuleInstallCallData(accountAddress);
},
nonceKeyManager: createNonceManager({
source: { get: () => 0, set: () => { } }
}),
async sign({ hash }) {
return account.signMessage({ message: hash });
},
async signMessage(message) {
return account.signMessage(message);
},
async signTypedData(typedData) {
return account.signTypedData(typedData);
},
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;
},
// Get the nonce of the smart account
async getNonce(_args) {
const key = await kernelPluginManager.getNonceKey(accountAddress, _args?.key);
return getAccountNonce(client, {
address: accountAddress,
entryPointAddress: entryPoint.address,
key
});
},
// Sign a user operation
async signUserOperation(parameters) {
const { chainId = await getMemoizedChainId(), ...userOperation } = parameters;
return kernelPluginManager.signUserOperation({
...userOperation,
sender: userOperation.sender ?? (await this.getAddress()),
chainId
});
},
// Encode the deploy call data
async encodeDeployCallData(_tx) {
return encodeFunctionData({
abi: KernelAccountV2Abi,
functionName: "execute",
args: [
createCallAddress,
0n,
encodeFunctionData({
abi: createCallAbi,
functionName: "performCreate",
args: [
0n,
encodeDeployData({
abi: _tx.abi,
bytecode: _tx.bytecode,
args: _tx.args
})
]
}),
1 // Delegate call
]
});
},
// Encode a call
async encodeCalls(calls, callType) {
if (calls.length > 1) {
const multiSendCallData = encodeFunctionData({
abi: multiSendAbi,
functionName: "multiSend",
args: [encodeMultiSend(calls)]
});
return encodeFunctionData({
abi: KernelAccountV2Abi,
functionName: "execute",
args: [MULTISEND_ADDRESS, 0n, multiSendCallData, 1]
});
}
const call = calls.length === 0 ? undefined : calls[0];
if (!call) {
throw new Error("No calls to encode");
}
// Default to `call`
if (!callType || callType === "call") {
if (call.to.toLowerCase() === accountAddress.toLowerCase()) {
return call.data || "0x";
}
return encodeFunctionData({
abi: KernelAccountV2Abi,
functionName: "execute",
args: [call.to, call.value || 0n, call.data || "0x", 0]
});
}
if (callType === "delegatecall") {
return encodeFunctionData({
abi: KernelAccountV2Abi,
functionName: "execute",
args: [call.to, 0n, call.data || "0x", 1]
});
}
throw new Error("Invalid call type");
},
// Get simple dummy signature
async getStubSignature(userOperation) {
if (!userOperation) {
throw new Error("No user operation provided");
}
return kernelPluginManager.getStubSignature(userOperation);
}
});
}
//# sourceMappingURL=createKernelAccountV0_2.js.map