3rd Party Smart Contracts
You are not limited to the accounts defined in @account-kit/smart-contracts.
If you’d like to bring your own smart account, you have a couple options:
- Use a 3rd party library that exports an
aa-sdkcompatibleSmartContractAccountinterface. - Implement your own
SmartContractAccountinterface usingtoSmartContractAccount.
Third Party SDKs
If you’ve built an SDK or Guide that leverages any of the methods above to use as a Smart Contract within Smart Wallets, we’re happy to include you in this list!
Please open a PR to add a link to your content in this section.
Using toSmartContractAccount
The SmartAccountClient can be used with any smart account because it only relies on the SmartContractAccount interface. This means you can use your own smart account implementation with Smart Wallets.
1 import { getEntryPoint, toSmartContractAccount } from "@aa-sdk/core"; 2 import { http, type SignableMessage, type Hash } from "viem"; 3 import { sepolia } from "viem/chains"; 4 5 const myAccount = await toSmartContractAccount({ 6 /// REQUIRED PARAMS /// 7 source: "MyAccount", 8 transport: http("RPC_URL"), 9 chain: sepolia, 10 // The EntryPointDef that your account is compatible with 11 entryPoint: getEntryPoint(sepolia), 12 // This should return a concatenation of your `factoryAddress` and the `callData` for your factory's create account method 13 getAccountInitCode: async (): Promise<Hash> => "0x{factoryAddress}{callData}", 14 // an invalid signature that doesn't cause your account to revert during validation 15 getDummySignature: async (): Promise<Hash> => "0x1234...", 16 // given a UO in the form of {target, data, value} should output the calldata for calling your contract's execution method 17 encodeExecute: async (uo): Promise<Hash> => "0x....", 18 signMessage: async ({ message }): Promise<Hash> => "0x...", 19 signTypedData: async (typedData): Promise<Hash> => "0x000", 20 21 /// OPTIONAL PARAMS /// 22 // if you already know your account's address, pass that in here to avoid generating a new counterfactual 23 accountAddress: "0x...", 24 // if your account supports batching, this should take an array of UOs and return the calldata for calling your contract's batchExecute method 25 encodeBatchExecute: async (uos): Promise<Hash> => "0x...", 26 // if your contract expects a different signing scheme than the default signMessage scheme, you can override that here 27 signUserOperationHash: async (hash): Promise<Hash> => "0x...", 28 // allows you to define the calldata for upgrading your account 29 encodeUpgradeToAndCall: async (params): Promise<Hash> => "0x...", 30 });
To use your account, you will need to pass it into a SmartAccountClient.
1 import { createAlchemySmartAccountClient, alchemy } from "@account-kit/infra"; 2 import { sepolia } from "viem/chains"; 3 import { myAccount } from "./my-account"; 4 5 const client = createAlchemySmartAccountClient({ 6 // created above 7 account: myAccount, 8 chain: sepolia, 9 transport: alchemy({ 10 apiKey: "YOUR_API_KEY", 11 }), 12 });
LightSmartContractAccount as an Example
We have built an extension of the eth-infinitism SimpleAccount called LightAccount.sol. You can learn more about Light Account in the Light Account documentation.
We provide an implementation of SmartContractAccount that works with LightAccount.sol, which can be used as an example of how to implement your own Smart Contract Account:
LightSmartContractAccount
1 import { 2 createBundlerClient, 3 getEntryPoint, 4 type Address, 5 type EntryPointDef, 6 type SmartAccountSigner, 7 } from "@aa-sdk/core"; 8 import { 9 concatHex, 10 encodeFunctionData, 11 type Chain, 12 type Hex, 13 type Transport, 14 } from "viem"; 15 import { LightAccountAbi_v1 } from "../abis/LightAccountAbi_v1.js"; 16 import { LightAccountAbi_v2 } from "../abis/LightAccountAbi_v2.js"; 17 import { LightAccountFactoryAbi_v1 } from "../abis/LightAccountFactoryAbi_v1.js"; 18 import { LightAccountFactoryAbi_v2 } from "../abis/LightAccountFactoryAbi_v2.js"; 19 import type { 20 LightAccountEntryPointVersion, 21 LightAccountVersion, 22 } from "../types.js"; 23 import { 24 AccountVersionRegistry, 25 LightAccountUnsupported1271Factories, 26 defaultLightAccountVersion, 27 getDefaultLightAccountFactoryAddress, 28 } from "../utils.js"; 29 import { 30 createLightAccountBase, 31 type CreateLightAccountBaseParams, 32 type LightAccountBase, 33 } from "./base.js"; 34 import { predictLightAccountAddress } from "./predictAddress.js"; 35 36 export type LightAccount< 37 TSigner extends SmartAccountSigner = SmartAccountSigner, 38 TLightAccountVersion extends 39 LightAccountVersion<"LightAccount"> = LightAccountVersion<"LightAccount">, 40 > = LightAccountBase<TSigner, "LightAccount", TLightAccountVersion> & { 41 encodeTransferOwnership: (newOwner: Address) => Hex; 42 getOwnerAddress: () => Promise<Address>; 43 }; 44 45 export type CreateLightAccountParams< 46 TTransport extends Transport = Transport, 47 TSigner extends SmartAccountSigner = SmartAccountSigner, 48 TLightAccountVersion extends 49 LightAccountVersion<"LightAccount"> = LightAccountVersion<"LightAccount">, 50 > = Omit< 51 CreateLightAccountBaseParams< 52 "LightAccount", 53 TLightAccountVersion, 54 TTransport, 55 TSigner 56 >, 57 | "getAccountInitCode" 58 | "entryPoint" 59 | "version" 60 | "abi" 61 | "accountAddress" 62 | "type" 63 > & { 64 salt?: bigint; 65 initCode?: Hex; 66 accountAddress?: Address; 67 factoryAddress?: Address; 68 version?: TLightAccountVersion; 69 entryPoint?: EntryPointDef< 70 LightAccountEntryPointVersion<"LightAccount", TLightAccountVersion>, 71 Chain 72 >; 73 }; 74 75 export async function createLightAccount< 76 TTransport extends Transport = Transport, 77 TSigner extends SmartAccountSigner = SmartAccountSigner, 78 TLightAccountVersion extends LightAccountVersion<"LightAccount"> = "v2.0.0", 79 >( 80 config: CreateLightAccountParams<TTransport, TSigner, TLightAccountVersion>, 81 ): Promise<LightAccount<TSigner, TLightAccountVersion>>; 82 83 /** 84 * Creates a light account based on the provided parameters such as transport, chain, signer, init code, and more. Ensures that an account is configured and returned with various capabilities, such as transferring ownership and retrieving the owner's address. 85 * 86 * @example 87 * ```ts 88 * import { createLightAccount } from "@account-kit/smart-contracts"; 89 * import { LocalAccountSigner } from "@aa-sdk/core"; 90 * import { sepolia } from "viem/chains"; 91 * import { http, generatePrivateKey } from "viem" 92 * 93 * const account = await createLightAccount({ 94 * chain: sepolia, 95 * transport: http("RPC_URL"), 96 * signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()) 97 * }); 98 * ``` 99 * 100 * @param {CreateLightAccountParams} config The parameters for creating a light account 101 * @returns {Promise<LightAccount>} A promise that resolves to a `LightAccount` object containing the created account information and methods 102 */ 103 export async function createLightAccount({ 104 transport, 105 chain, 106 signer, 107 initCode, 108 version = defaultLightAccountVersion(), 109 entryPoint = getEntryPoint(chain, { 110 version: AccountVersionRegistry["LightAccount"][version] 111 .entryPointVersion as any, 112 }), 113 accountAddress, 114 factoryAddress = getDefaultLightAccountFactoryAddress(chain, version), 115 salt: salt_ = 0n, 116 }: CreateLightAccountParams): Promise<LightAccount> { 117 const client = createBundlerClient({ 118 transport, 119 chain, 120 }); 121 122 const accountAbi = 123 version === "v2.0.0" ? LightAccountAbi_v2 : LightAccountAbi_v1; 124 const factoryAbi = 125 version === "v2.0.0" 126 ? LightAccountFactoryAbi_v1 127 : LightAccountFactoryAbi_v2; 128 129 const signerAddress = await signer.getAddress(); 130 131 const salt = LightAccountUnsupported1271Factories.has( 132 factoryAddress.toLowerCase() as Address, 133 ) 134 ? 0n 135 : salt_; 136 137 const getAccountInitCode = async () => { 138 if (initCode) return initCode; 139 140 return concatHex([ 141 factoryAddress, 142 encodeFunctionData({ 143 abi: factoryAbi, 144 functionName: "createAccount", 145 args: [signerAddress, salt], 146 }), 147 ]); 148 }; 149 150 const address = 151 accountAddress ?? 152 predictLightAccountAddress({ 153 factoryAddress, 154 salt, 155 ownerAddress: signerAddress, 156 version, 157 }); 158 159 const account = await createLightAccountBase< 160 "LightAccount", 161 LightAccountVersion<"LightAccount">, 162 Transport, 163 SmartAccountSigner 164 >({ 165 transport, 166 chain, 167 signer, 168 abi: accountAbi, 169 type: "LightAccount", 170 version, 171 entryPoint, 172 accountAddress: address, 173 getAccountInitCode, 174 }); 175 176 return { 177 ...account, 178 179 encodeTransferOwnership: (newOwner: Address) => { 180 return encodeFunctionData({ 181 abi: accountAbi, 182 functionName: "transferOwnership", 183 args: [newOwner], 184 }); 185 }, 186 async getOwnerAddress(): Promise<Address> { 187 const callResult = await client.readContract({ 188 address, 189 abi: accountAbi, 190 functionName: "owner", 191 }); 192 193 if (callResult == null) { 194 throw new Error("could not get on-chain owner"); 195 } 196 197 return callResult; 198 }, 199 }; 200 }
The toSmartContractAccount Method
For your reference, this is the definition of the toSmartContractAccount interface as pulled from the source code:
SmartContractAccount
1 import { 2 getContract, 3 hexToBytes, 4 type Address, 5 type Chain, 6 type CustomSource, 7 type Hex, 8 type LocalAccount, 9 type PublicClient, 10 type SignableMessage, 11 type Transport, 12 type TypedData, 13 type TypedDataDefinition, 14 } from "viem"; 15 import { toAccount } from "viem/accounts"; 16 import { createBundlerClient } from "../client/bundlerClient.js"; 17 import type { 18 EntryPointDef, 19 EntryPointRegistryBase, 20 EntryPointVersion, 21 } from "../entrypoint/types.js"; 22 import { 23 BatchExecutionNotSupportedError, 24 FailedToGetStorageSlotError, 25 GetCounterFactualAddressError, 26 SignTransactionNotSupportedError, 27 UpgradesNotSupportedError, 28 } from "../errors/account.js"; 29 import { InvalidRpcUrlError } from "../errors/client.js"; 30 import { InvalidEntryPointError } from "../errors/entrypoint.js"; 31 import { Logger } from "../logger.js"; 32 import type { SmartAccountSigner } from "../signer/types.js"; 33 import { wrapSignatureWith6492 } from "../signer/utils.js"; 34 import type { NullAddress } from "../types.js"; 35 import type { IsUndefined, Never } from "../utils/types.js"; 36 37 export type AccountOp = { 38 target: Address; 39 value?: bigint; 40 data: Hex | "0x"; 41 }; 42 43 export enum DeploymentState { 44 UNDEFINED = "0x0", 45 NOT_DEPLOYED = "0x1", 46 DEPLOYED = "0x2", 47 } 48 49 export type SignatureRequest = 50 | { 51 type: "personal_sign"; 52 data: SignableMessage; 53 } 54 | { 55 type: "eth_signTypedData_v4"; 56 data: TypedDataDefinition; 57 }; 58 59 export type SigningMethods = { 60 prepareSign: (request: SignatureRequest) => Promise<SignatureRequest>; 61 formatSign: (signature: Hex) => Promise<Hex>; 62 }; 63 64 export type GetEntryPointFromAccount< 65 TAccount extends SmartContractAccount | undefined, 66 TAccountOverride extends SmartContractAccount = SmartContractAccount, 67 > = 68 GetAccountParameter<TAccount, TAccountOverride> extends SmartContractAccount< 69 string, 70 infer TEntryPointVersion 71 > 72 ? TEntryPointVersion 73 : EntryPointVersion; 74 75 export type GetAccountParameter< 76 TAccount extends SmartContractAccount | undefined = 77 | SmartContractAccount 78 | undefined, 79 TAccountOverride extends SmartContractAccount = SmartContractAccount, 80 > = 81 IsUndefined<TAccount> extends true 82 ? { account: TAccountOverride } 83 : { account?: TAccountOverride }; 84 85 export type UpgradeToAndCallParams = { 86 upgradeToAddress: Address; 87 upgradeToInitData: Hex; 88 }; 89 90 export type SmartContractAccountWithSigner< 91 Name extends string = string, 92 TSigner extends SmartAccountSigner = SmartAccountSigner, 93 TEntryPointVersion extends EntryPointVersion = EntryPointVersion, 94 > = SmartContractAccount<Name, TEntryPointVersion> & { 95 getSigner: () => TSigner; 96 }; 97 98 /** 99 * Determines if the given SmartContractAccount has a signer associated with it. 100 * 101 * @example 102 * ```ts 103 * import { toSmartContractAccount } from "@aa-sdk/core"; 104 * 105 * const account = await toSmartContractAccount(...); 106 * 107 * console.log(isSmartAccountWithSigner(account)); // false: the base account does not have a publicly accessible signer 108 * ``` 109 * 110 * @param {SmartContractAccount} account The account to check. 111 * @returns {boolean} true if the account has a signer, otherwise false. 112 */ 113 export const isSmartAccountWithSigner = ( 114 account: SmartContractAccount, 115 ): account is SmartContractAccountWithSigner => { 116 return "getSigner" in account; 117 }; 118 119 export type SmartContractAccount< 120 Name extends string = string, 121 TEntryPointVersion extends EntryPointVersion = EntryPointVersion, 122 > = LocalAccount<Name> & { 123 source: Name; 124 getDummySignature: () => Hex | Promise<Hex>; 125 encodeExecute: (tx: AccountOp) => Promise<Hex>; 126 encodeBatchExecute: (txs: AccountOp[]) => Promise<Hex>; 127 signUserOperationHash: (uoHash: Hex) => Promise<Hex>; 128 signMessageWith6492: (params: { message: SignableMessage }) => Promise<Hex>; 129 signTypedDataWith6492: < 130 const typedData extends TypedData | Record<string, unknown>, 131 primaryType extends keyof typedData | "EIP712Domain" = keyof typedData, 132 >( 133 typedDataDefinition: TypedDataDefinition<typedData, primaryType>, 134 ) => Promise<Hex>; 135 encodeUpgradeToAndCall: (params: UpgradeToAndCallParams) => Promise<Hex>; 136 getAccountNonce(nonceKey?: bigint): Promise<bigint>; 137 getInitCode: () => Promise<Hex>; 138 isAccountDeployed: () => Promise<boolean>; 139 getFactoryAddress: () => Promise<Address>; 140 getFactoryData: () => Promise<Hex>; 141 getEntryPoint: () => EntryPointDef<TEntryPointVersion>; 142 getImplementationAddress: () => Promise<NullAddress | Address>; 143 } & SigningMethods; 144 145 export interface AccountEntryPointRegistry<Name extends string = string> 146 extends EntryPointRegistryBase< 147 SmartContractAccount<Name, EntryPointVersion> 148 > { 149 "0.6.0": SmartContractAccount<Name, "0.6.0">; 150 "0.7.0": SmartContractAccount<Name, "0.7.0">; 151 } 152 153 export type ToSmartContractAccountParams< 154 Name extends string = string, 155 TTransport extends Transport = Transport, 156 TChain extends Chain = Chain, 157 TEntryPointVersion extends EntryPointVersion = EntryPointVersion, 158 > = { 159 source: Name; 160 transport: TTransport; 161 chain: TChain; 162 entryPoint: EntryPointDef<TEntryPointVersion, TChain>; 163 accountAddress?: Address; 164 getAccountInitCode: () => Promise<Hex>; 165 getDummySignature: () => Hex | Promise<Hex>; 166 encodeExecute: (tx: AccountOp) => Promise<Hex>; 167 encodeBatchExecute?: (txs: AccountOp[]) => Promise<Hex>; 168 getNonce?: (nonceKey?: bigint) => Promise<bigint>; 169 // if not provided, will default to just using signMessage over the Hex 170 signUserOperationHash?: (uoHash: Hex) => Promise<Hex>; 171 encodeUpgradeToAndCall?: (params: UpgradeToAndCallParams) => Promise<Hex>; 172 getImplementationAddress?: () => Promise<NullAddress | Address>; 173 } & Omit<CustomSource, "signTransaction" | "address"> & 174 (SigningMethods | Never<SigningMethods>); 175 176 /** 177 * Parses the factory address and factory calldata from the provided account initialization code (initCode). 178 * 179 * @example 180 * ```ts 181 * import { parseFactoryAddressFromAccountInitCode } from "@aa-sdk/core"; 182 * 183 * const [address, calldata] = parseFactoryAddressFromAccountInitCode("0xAddressCalldata"); 184 * ``` 185 * 186 * @param {Hex} initCode The initialization code from which to parse the factory address and calldata 187 * @returns {[Address, Hex]} A tuple containing the parsed factory address and factory calldata 188 */ 189 export const parseFactoryAddressFromAccountInitCode = ( 190 initCode: Hex, 191 ): [Address, Hex] => { 192 const factoryAddress: Address = `0x${initCode.substring(2, 42)}`; 193 const factoryCalldata: Hex = `0x${initCode.substring(42)}`; 194 return [factoryAddress, factoryCalldata]; 195 }; 196 197 export type GetAccountAddressParams = { 198 client: PublicClient; 199 entryPoint: EntryPointDef; 200 accountAddress?: Address; 201 getAccountInitCode: () => Promise<Hex>; 202 }; 203 204 /** 205 * Retrieves the account address. Uses a provided `accountAddress` if available; otherwise, it computes the address using the entry point contract and the initial code. 206 * 207 * @example 208 * ```ts 209 * import { getEntryPoint, getAccountAddress } from "@aa-sdk/core"; 210 * 211 * const accountAddress = await getAccountAddress({ 212 * client, 213 * entryPoint: getEntryPoint(chain), 214 * getAccountInitCode: async () => "0x{factoryAddress}{factoryCallData}", 215 * }); 216 * ``` 217 * 218 * @param {GetAccountAddressParams} params The configuration object 219 * @param {PublicClient} params.client A public client instance to interact with the blockchain 220 * @param {EntryPointDef} params.entryPoint The entry point definition which includes the address and ABI 221 * @param {Address} params.accountAddress Optional existing account address 222 * @param {() => Promise<Hex>} params.getAccountInitCode A function that returns a Promise resolving to a Hex string representing the initial code of the account 223 * @returns {Promise<Address>} A promise that resolves to the account address 224 */ 225 export const getAccountAddress = async ({ 226 client, 227 entryPoint, 228 accountAddress, 229 getAccountInitCode, 230 }: GetAccountAddressParams) => { 231 if (accountAddress) return accountAddress; 232 233 const entryPointContract = getContract({ 234 address: entryPoint.address, 235 abi: entryPoint.abi, 236 client, 237 }); 238 239 const initCode = await getAccountInitCode(); 240 Logger.verbose("[BaseSmartContractAccount](getAddress) initCode: ", initCode); 241 242 try { 243 await entryPointContract.simulate.getSenderAddress([initCode]); 244 } catch (err: any) { 245 Logger.verbose( 246 "[BaseSmartContractAccount](getAddress) getSenderAddress err: ", 247 err, 248 ); 249 if (err.cause?.data?.errorName === "SenderAddressResult") { 250 Logger.verbose( 251 "[BaseSmartContractAccount](getAddress) entryPoint.getSenderAddress result:", 252 err.cause.data.args[0], 253 ); 254 255 return err.cause.data.args[0] as Address; 256 } 257 258 if (err.details === "Invalid URL") { 259 throw new InvalidRpcUrlError(); 260 } 261 } 262 263 throw new GetCounterFactualAddressError(); 264 }; 265 266 export async function toSmartContractAccount< 267 Name extends string = string, 268 TTransport extends Transport = Transport, 269 TChain extends Chain = Chain, 270 TEntryPointVersion extends EntryPointVersion = EntryPointVersion, 271 >({ 272 transport, 273 chain, 274 entryPoint, 275 source, 276 accountAddress, 277 getAccountInitCode, 278 getNonce, 279 signMessage, 280 signTypedData, 281 encodeBatchExecute, 282 encodeExecute, 283 getDummySignature, 284 signUserOperationHash, 285 encodeUpgradeToAndCall, 286 }: ToSmartContractAccountParams< 287 Name, 288 TTransport, 289 TChain, 290 TEntryPointVersion 291 >): Promise<SmartContractAccount<Name, TEntryPointVersion>>; 292 293 /** 294 * Converts an account to a smart contract account and sets up various account-related methods using the provided parameters like transport, chain, entry point, and other utilities. 295 * 296 * @example 297 * ```ts 298 * import { http, type SignableMessage } from "viem"; 299 * import { sepolia } from "viem/chains"; 300 * 301 * const myAccount = await toSmartContractAccount({ 302 * /// REQUIRED PARAMS /// 303 * source: "MyAccount", 304 * transport: http("RPC_URL"), 305 * chain: sepolia, 306 * // The EntryPointDef that your account is com"patible with 307 * entryPoint: getEntryPoint(sepolia, { version: "0.6.0" }), 308 * // This should return a concatenation of your `factoryAddress` and the `callData` for your factory's create account method 309 * getAccountInitCode: async () => "0x{factoryAddress}{callData}", 310 * // an invalid signature that doesn't cause your account to revert during validation 311 * getDummySignature: () => "0x1234...", 312 * // given a UO in the form of {target, data, value} should output the calldata for calling your contract's execution method 313 * encodeExecute: async (uo) => "0xcalldata", 314 * signMessage: async ({ message }: { message: SignableMessage }) => "0x...", 315 * signTypedData: async (typedData) => "0x000", 316 * 317 * /// OPTIONAL PARAMS /// 318 * // if you already know your account's address, pass that in here to avoid generating a new counterfactual 319 * accountAddress: "0xaddressoverride", 320 * // if your account supports batching, this should take an array of UOs and return the calldata for calling your contract's batchExecute method 321 * encodeBatchExecute: async (uos) => "0x...", 322 * // if your contract expects a different signing scheme than the default signMessage scheme, you can override that here 323 * signUserOperationHash: async (hash) => "0x...", 324 * // allows you to define the calldata for upgrading your account 325 * encodeUpgradeToAndCall: async (params) => "0x...", 326 * }); 327 * ``` 328 * 329 * @param {ToSmartContractAccountParams} params the parameters required for converting to a smart contract account 330 * @param {Transport} params.transport the transport mechanism used for communication 331 * @param {Chain} params.chain the blockchain chain used in the account 332 * @param {EntryPoint} params.entryPoint the entry point of the smart contract 333 * @param {string} params.source the source identifier for the account 334 * @param {Address} [params.accountAddress] the address of the account 335 * @param {() => Promise<Hex>} params.getAccountInitCode a function to get the initial state code of the account 336 * @param {(message: { message: SignableMessage }) => Promise<Hex>} params.signMessage a function to sign a message 337 * @param {(typedDataDefinition: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>} params.signTypedData a function to sign typed data 338 * @param {(transactions: Transaction[]) => Hex} [params.encodeBatchExecute] a function to encode batch transactions 339 * @param {(tx: Transaction) => Hex} params.encodeExecute a function to encode a single transaction 340 * @param {() => Promise<Hex>} params.getDummySignature a function to get a dummy signature 341 * @param {(uoHash: Hex) => Promise<Hex>} [params.signUserOperationHash] a function to sign user operations 342 * @param {(implementationAddress: Address, implementationCallData: Hex) => Hex} [params.encodeUpgradeToAndCall] a function to encode upgrade call 343 * @returns {Promise<SmartContractAccount>} a promise that resolves to a SmartContractAccount object with methods and properties for interacting with the smart contract account 344 */ 345 export async function toSmartContractAccount( 346 params: ToSmartContractAccountParams, 347 ): Promise<SmartContractAccount> { 348 const { 349 transport, 350 chain, 351 entryPoint, 352 source, 353 accountAddress, 354 getAccountInitCode, 355 signMessage, 356 signTypedData, 357 encodeExecute, 358 encodeBatchExecute, 359 getNonce, 360 getDummySignature, 361 signUserOperationHash, 362 encodeUpgradeToAndCall, 363 getImplementationAddress, 364 prepareSign: prepareSign_, 365 formatSign: formatSign_, 366 } = params; 367 368 const client = createBundlerClient({ 369 // we set the retry count to 0 so that viem doesn't retry during 370 // getting the address. That call always reverts and without this 371 // viem will retry 3 times, making this call very slow 372 transport: (opts) => transport({ ...opts, chain, retryCount: 0 }), 373 chain, 374 }); 375 376 const entryPointContract = getContract({ 377 address: entryPoint.address, 378 abi: entryPoint.abi, 379 client, 380 }); 381 382 const accountAddress_ = await getAccountAddress({ 383 client, 384 entryPoint: entryPoint, 385 accountAddress, 386 getAccountInitCode, 387 }); 388 389 let deploymentState = DeploymentState.UNDEFINED; 390 391 const getInitCode = async () => { 392 if (deploymentState === DeploymentState.DEPLOYED) { 393 return "0x"; 394 } 395 const contractCode = await client.getCode({ 396 address: accountAddress_, 397 }); 398 399 if ((contractCode?.length ?? 0) > 2) { 400 deploymentState = DeploymentState.DEPLOYED; 401 return "0x"; 402 } else { 403 deploymentState = DeploymentState.NOT_DEPLOYED; 404 } 405 406 return getAccountInitCode(); 407 }; 408 409 const signUserOperationHash_ = 410 signUserOperationHash ?? 411 (async (uoHash: Hex) => { 412 return signMessage({ message: { raw: hexToBytes(uoHash) } }); 413 }); 414 415 const getFactoryAddress = async (): Promise<Address> => 416 parseFactoryAddressFromAccountInitCode(await getAccountInitCode())[0]; 417 418 const getFactoryData = async (): Promise<Hex> => 419 parseFactoryAddressFromAccountInitCode(await getAccountInitCode())[1]; 420 421 const encodeUpgradeToAndCall_ = 422 encodeUpgradeToAndCall ?? 423 (() => { 424 throw new UpgradesNotSupportedError(source); 425 }); 426 427 const isAccountDeployed = async () => { 428 const initCode = await getInitCode(); 429 return initCode === "0x"; 430 }; 431 432 const getNonce_ = 433 getNonce ?? 434 (async (nonceKey = 0n): Promise<bigint> => { 435 return entryPointContract.read.getNonce([ 436 accountAddress_, 437 nonceKey, 438 ]) as Promise<bigint>; 439 }); 440 441 const account = toAccount({ 442 address: accountAddress_, 443 signMessage, 444 signTypedData, 445 signTransaction: () => { 446 throw new SignTransactionNotSupportedError(); 447 }, 448 }); 449 450 const create6492Signature = async (isDeployed: boolean, signature: Hex) => { 451 if (isDeployed) { 452 return signature; 453 } 454 455 const [factoryAddress, factoryCalldata] = 456 parseFactoryAddressFromAccountInitCode(await getAccountInitCode()); 457 458 return wrapSignatureWith6492({ 459 factoryAddress, 460 factoryCalldata, 461 signature, 462 }); 463 }; 464 465 const signMessageWith6492 = async (message: { message: SignableMessage }) => { 466 const [isDeployed, signature] = await Promise.all([ 467 isAccountDeployed(), 468 account.signMessage(message), 469 ]); 470 471 return create6492Signature(isDeployed, signature); 472 }; 473 474 const signTypedDataWith6492 = async < 475 const typedData extends TypedData | Record<string, unknown>, 476 primaryType extends keyof typedData | "EIP712Domain" = keyof typedData, 477 >( 478 typedDataDefinition: TypedDataDefinition<typedData, primaryType>, 479 ): Promise<Hex> => { 480 const [isDeployed, signature] = await Promise.all([ 481 isAccountDeployed(), 482 account.signTypedData(typedDataDefinition), 483 ]); 484 485 return create6492Signature(isDeployed, signature); 486 }; 487 488 const getImplementationAddress_ = 489 getImplementationAddress ?? 490 (async () => { 491 const storage = await client.getStorageAt({ 492 address: account.address, 493 // This is the default slot for the implementation address for Proxies 494 slot: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", 495 }); 496 497 if (storage == null) { 498 throw new FailedToGetStorageSlotError( 499 "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", 500 "Proxy Implementation Address", 501 ); 502 } 503 504 // The storage slot contains a full bytes32, but we want only the last 20 bytes. 505 // So, slice off the leading `0x` and the first 12 bytes (24 characters), leaving the last 20 bytes, then prefix with `0x`. 506 return `0x${storage.slice(26)}`; 507 }); 508 509 if (entryPoint.version !== "0.6.0" && entryPoint.version !== "0.7.0") { 510 throw new InvalidEntryPointError(chain, entryPoint.version); 511 } 512 513 if ((prepareSign_ && !formatSign_) || (!prepareSign_ && formatSign_)) { 514 throw new Error( 515 "Must implement both prepareSign and formatSign or neither", 516 ); 517 } 518 519 const prepareSign = 520 prepareSign_ ?? 521 (() => { 522 throw new Error("prepareSign not implemented"); 523 }); 524 525 const formatSign = 526 formatSign_ ?? 527 (() => { 528 throw new Error("formatSign not implemented"); 529 }); 530 531 return { 532 ...account, 533 source, 534 // TODO: I think this should probably be signUserOperation instead 535 // and allow for generating the UO hash based on the EP version 536 signUserOperationHash: signUserOperationHash_, 537 getFactoryAddress, 538 getFactoryData, 539 encodeBatchExecute: 540 encodeBatchExecute ?? 541 (() => { 542 throw new BatchExecutionNotSupportedError(source); 543 }), 544 encodeExecute, 545 getDummySignature, 546 getInitCode, 547 encodeUpgradeToAndCall: encodeUpgradeToAndCall_, 548 getEntryPoint: () => entryPoint, 549 isAccountDeployed, 550 getAccountNonce: getNonce_, 551 signMessageWith6492, 552 signTypedDataWith6492, 553 getImplementationAddress: getImplementationAddress_, 554 prepareSign, 555 formatSign, 556 }; 557 }