@zerodev/sdk
Version:
A utility library for working with ERC-4337
219 lines (209 loc) • 6.64 kB
text/typescript
import {
type Call,
concat,
concatHex,
decodeFunctionData,
encodeAbiParameters,
encodeFunctionData,
getAbiItem,
pad,
parseAbiParameters,
toFunctionSelector,
zeroAddress
} from "viem"
import type { EntryPointVersion } from "viem/account-abstraction"
import { readContract } from "viem/actions"
import { getAction } from "viem/utils"
import { CALL_TYPE, VALIDATOR_TYPE } from "../../constants.js"
import type { GetKernelVersion, KernelValidator } from "../../types/kernel.js"
import { MigrationHelperAbi } from "./abi/MigrationHelperAbi.js"
import {
KernelV3AccountAbi,
KernelV3ExecuteAbi
} from "./abi/kernel_v_3_0_0/KernelAccountAbi.js"
import {
type CreateKernelAccountParameters,
type CreateKernelAccountReturnType,
type KernelSmartAccountImplementation,
createKernelAccount
} from "./createKernelAccount.js"
export type CreateKernelMigrationAccountParameters<
entryPointVersion extends EntryPointVersion,
KernelVerion extends GetKernelVersion<entryPointVersion>
> = Omit<
CreateKernelAccountParameters<entryPointVersion, KernelVerion>,
"plugins"
> & {
plugins: {
sudo: {
migrate: {
from: KernelValidator
to: KernelValidator
}
}
}
}
export const MIGRATION_HELPER_ADDRESS =
"0x03EB97959433D55748839D27C93330Cb85F31A93"
export async function createKernelMigrationAccount<
entryPointVersion extends EntryPointVersion,
KernelVersion extends GetKernelVersion<entryPointVersion>
>(
client: KernelSmartAccountImplementation["client"],
params: CreateKernelMigrationAccountParameters<
entryPointVersion,
KernelVersion
>
): Promise<
CreateKernelAccountReturnType<entryPointVersion> & {
getRootValidatorMigrationStatus?: () => Promise<boolean>
}
> {
if (params.entryPoint.version !== "0.7") {
throw new Error("Only EntryPoint 0.7 is supported")
}
const {
plugins: {
sudo: {
migrate: { from: fromValidator, to: toValidator }
}
},
...rest
} = params
const account = await createKernelAccount(client, {
plugins: {
sudo: fromValidator
},
...rest
})
let isRootValidatorMigrated = false
let isAccountDeployed = false
const getRootValidatorMigrationStatus = async () => {
try {
if (isRootValidatorMigrated) return isRootValidatorMigrated
const rootValidator = await getAction(
client,
readContract,
"readContract"
)({
address: account.address,
abi: KernelV3AccountAbi,
functionName: "rootValidator",
args: []
})
const toRootValidatorId = concatHex([
VALIDATOR_TYPE[toValidator.validatorType],
pad(toValidator.getIdentifier(), {
size: 20,
dir: "right"
})
])
isRootValidatorMigrated =
rootValidator.toLowerCase() === toRootValidatorId.toLowerCase()
return isRootValidatorMigrated
} catch (error) {
return false
}
}
;[isAccountDeployed, isRootValidatorMigrated] = await Promise.all([
account.isDeployed(),
getRootValidatorMigrationStatus()
])
if (isAccountDeployed && isRootValidatorMigrated) {
return createKernelAccount(client, {
plugins: {
sudo: toValidator
},
...rest,
address: account.address
})
}
const installFallbackCallData = encodeFunctionData({
abi: KernelV3AccountAbi,
functionName: "installModule",
args: [
3n,
MIGRATION_HELPER_ADDRESS,
concat([
toFunctionSelector(
getAbiItem({
abi: MigrationHelperAbi,
name: "migrateWithCall"
})
),
zeroAddress,
encodeAbiParameters(
parseAbiParameters(
"bytes selectorInitData, bytes hookInitData"
),
[CALL_TYPE.DELEGATE_CALL, "0x0000"]
)
])
]
})
const installFallbackCall = {
to: account.address,
data: installFallbackCallData,
value: 0n
}
const uninstallFallbackCallData = encodeFunctionData({
abi: KernelV3AccountAbi,
functionName: "uninstallModule",
args: [
3n,
MIGRATION_HELPER_ADDRESS,
concat([
toFunctionSelector(
getAbiItem({
abi: MigrationHelperAbi,
name: "migrateWithCall"
})
),
"0x"
])
]
})
const uninstallFallbackCall = {
to: account.address,
data: uninstallFallbackCallData,
value: 0n
}
return {
...account,
getRootValidatorMigrationStatus,
async encodeCalls(calls, callType) {
const isRootValidatorMigrated =
await getRootValidatorMigrationStatus()
const executeCallData = await account.encodeCalls(calls, callType)
if (isRootValidatorMigrated) {
return executeCallData
}
const {
args: [execMode, executionCalldata]
} = decodeFunctionData({
abi: [getAbiItem({ abi: KernelV3ExecuteAbi, name: "execute" })],
data: executeCallData
})
const migrationCallData = encodeFunctionData({
abi: MigrationHelperAbi,
functionName: "migrateWithCall",
args: [[], [], execMode, executionCalldata]
})
const migrationCall = {
to: MIGRATION_HELPER_ADDRESS,
data: migrationCallData,
value: 0n
} as Call
if (params.kernelVersion !== "0.3.0") {
return account.encodeCalls([migrationCall], "delegatecall")
}
migrationCall.to = account.address
const finalCalls = [
installFallbackCall,
migrationCall,
uninstallFallbackCall
]
return account.encodeCalls(finalCalls, "call")
}
}
}