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