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.
import { function getEntryPoint<TEntryPointVersion extends EntryPointVersion = "0.6.0", TChain extends Chain = Chain>(chain: TChain, options: GetEntryPointOptions<TEntryPointVersion>): EntryPointDefRegistry<TChain>[TEntryPointVersion] (+2 overloads)getEntryPoint, function toSmartContractAccount<Name extends string = string, TTransport extends Transport = Transport, TChain extends Chain = Chain, TEntryPointVersion extends EntryPointVersion = keyof EntryPointRegistryBase<unknown>>({ transport, chain, entryPoint, source, accountAddress, getAccountInitCode, getNonce, signMessage, signTypedData, encodeBatchExecute, encodeExecute, getDummySignature, signUserOperationHash, encodeUpgradeToAndCall, }: ToSmartContractAccountParams<Name, TTransport, TChain, TEntryPointVersion>): Promise<SmartContractAccount<Name, TEntryPointVersion>>toSmartContractAccount } from "@aa-sdk/core";
import { function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>http, type type SignableMessage = string | {
raw: Hex | ByteArray;
}SignableMessage, type type Hash = `0x${string}`Hash } from "viem";
import { const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia } from "viem/chains";
const const myAccount: SmartContractAccount<"MyAccount", "0.6.0">myAccount = await toSmartContractAccount<"MyAccount", HttpTransport<undefined, false>, {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}, "0.6.0">({ transport, chain, entryPoint, source, accountAddress, getAccountInitCode, getNonce, signMessage, signTypedData, encodeBatchExecute, encodeExecute, getDummySignature, signUserOperationHash, encodeUpgradeToAndCall, }: ToSmartContractAccountParams<...>): Promise<...>toSmartContractAccount({
/// REQUIRED PARAMS ///
source: "MyAccount"source: "MyAccount",
transport: HttpTransport<undefined, false>transport: http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>http("RPC_URL"),
chain: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}chain: const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia,
// The EntryPointDef that your account is compatible with
entryPoint: EntryPointDef<"0.6.0", {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}>entryPoint: getEntryPoint<"0.6.0", {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}>(chain: {
...;
}, options?: {
...;
} | undefined): EntryPointDef<...> (+2 overloads)getEntryPoint(const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia),
// This should return a concatenation of your `factoryAddress` and the `callData` for your factory's create account method
getAccountInitCode: () => Promise<Hex>getAccountInitCode: async (): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x{factoryAddress}{callData}",
// an invalid signature that doesn't cause your account to revert during validation
getDummySignature: () => Hex | Promise<Hex>getDummySignature: async (): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x1234...",
// given a UO in the form of {target, data, value} should output the calldata for calling your contract's execution method
encodeExecute: (tx: AccountOp) => Promise<Hex>encodeExecute: async (uo: AccountOpuo): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x....",
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>signMessage: async ({ message: SignableMessagemessage }): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>signTypedData: async (typedData: TypedDataDefinition<typedData, primaryType, typedData extends {
[x: string]: readonly TypedDataParameter[];
[x: `string[${string}]`]: undefined;
[x: `function[${string}]`]: undefined;
[x: `address[${string}]`]: undefined;
[x: `bool[${string}]`]: undefined;
[x: `bytes[${string}]`]: undefined;
[x: `bytes1[${string}]`]: undefined;
[x: `bytes2[${string}]`]: undefined;
[x: `bytes3[${string}]`]: undefined;
[x: `bytes4[${string}]`]: undefined;
[x: `bytes5[${string}]`]: undefined;
[x: `bytes6[${string}]`]: undefined;
[x: `bytes7[${string}]`]: undefined;
[x: `bytes8[${string}]`]: undefined;
[x: `bytes9[${string}]`]: undefined;
[x: `bytes10[${string}]`]: undefined;
[x: `bytes11[${string}]`]: undefined;
[x: `bytes12[${string}]`]: undefined;
[x: `bytes13[${string}]`]: undefined;
[x: `bytes14[${string}]`]: undefined;
[x: `bytes15[${string}]`]: undefined;
[x: `bytes16[${string}]`]: undefined;
[x: `bytes17[${string}]`]: undefined;
[x: `bytes18[${string}]`]: undefined;
[x: `bytes19[${string}]`]: undefined;
[x: `bytes20[${string}]`]: undefined;
[x: `bytes21[${string}]`]: undefined;
[x: `bytes22[${string}]`]: undefined;
[x: `bytes23[${string}]`]: undefined;
[x: `bytes24[${string}]`]: undefined;
[x: `bytes25[${string}]`]: undefined;
[x: `bytes26[${string}]`]: undefined;
[x: `bytes27[${string}]`]: undefined;
[x: `bytes28[${string}]`]: undefined;
[x: `bytes29[${string}]`]: undefined;
[x: `bytes30[${string}]`]: undefined;
[x: `bytes31[${string}]`]: undefined ...typedData): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x000",
/// OPTIONAL PARAMS ///
// if you already know your account's address, pass that in here to avoid generating a new counterfactual
accountAddress?: `0x${string}` | undefinedaccountAddress: "0x...",
// if your account supports batching, this should take an array of UOs and return the calldata for calling your contract's batchExecute method
encodeBatchExecute?: ((txs: AccountOp[]) => Promise<Hex>) | undefinedencodeBatchExecute: async (uos: AccountOp[]uos): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
// if your contract expects a different signing scheme than the default signMessage scheme, you can override that here
signUserOperationHash?: ((uoHash: Hex) => Promise<Hex>) | undefinedsignUserOperationHash: async (hash: `0x${string}`hash): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
// allows you to define the calldata for upgrading your account
encodeUpgradeToAndCall?: ((params: UpgradeToAndCallParams) => Promise<Hex>) | undefinedencodeUpgradeToAndCall: async (params: UpgradeToAndCallParamsparams): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
});
To use your account, you will need to pass it into a SmartAccountClient
.
import { function createAlchemySmartAccountClient<TChain extends Chain = Chain, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined, TContext extends UserOperationContext | undefined = UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<TChain, TAccount, TContext>): AlchemySmartAccountClient<TChain, TAccount, Record<string, never>, TContext>createAlchemySmartAccountClient, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia } from "viem/chains";
import { import myAccountmyAccount } from "./my-account";
const const client: {
[x: string]: never;
account: any;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}client = createAlchemySmartAccountClient<{
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}, any, UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<...>): {
...;
}createAlchemySmartAccountClient({
// created above
account?: anyaccount: import myAccountmyAccount,
chain?: Chain | {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
} | undefinedChain for the client.
chain: const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({
apiKey: stringapiKey: "YOUR_API_KEY",
}),
});
import { function getEntryPoint<TEntryPointVersion extends EntryPointVersion = "0.6.0", TChain extends Chain = Chain>(chain: TChain, options: GetEntryPointOptions<TEntryPointVersion>): EntryPointDefRegistry<TChain>[TEntryPointVersion] (+2 overloads)getEntryPoint, function toSmartContractAccount<Name extends string = string, TTransport extends Transport = Transport, TChain extends Chain = Chain, TEntryPointVersion extends EntryPointVersion = keyof EntryPointRegistryBase<unknown>>({ transport, chain, entryPoint, source, accountAddress, getAccountInitCode, getNonce, signMessage, signTypedData, encodeBatchExecute, encodeExecute, getDummySignature, signUserOperationHash, encodeUpgradeToAndCall, }: ToSmartContractAccountParams<Name, TTransport, TChain, TEntryPointVersion>): Promise<SmartContractAccount<Name, TEntryPointVersion>>toSmartContractAccount } from "@aa-sdk/core";
import { function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>http, type type SignableMessage = string | {
raw: Hex | ByteArray;
}SignableMessage, type type Hash = `0x${string}`Hash } from "viem";
import { const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia } from "viem/chains";
export const const myAccount: SmartContractAccount<"MyAccount", "0.6.0">myAccount = await toSmartContractAccount<"MyAccount", HttpTransport<undefined, false>, {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}, "0.6.0">({ transport, chain, entryPoint, source, accountAddress, getAccountInitCode, getNonce, signMessage, signTypedData, encodeBatchExecute, encodeExecute, getDummySignature, signUserOperationHash, encodeUpgradeToAndCall, }: ToSmartContractAccountParams<...>): Promise<...>toSmartContractAccount({
/// REQUIRED PARAMS ///
source: "MyAccount"source: "MyAccount",
transport: HttpTransport<undefined, false>transport: http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>http("RPC_URL"),
chain: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}chain: const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia,
// The EntryPointDef that your account is compatible with
entryPoint: EntryPointDef<"0.6.0", {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}>entryPoint: getEntryPoint<"0.6.0", {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}>(chain: {
...;
}, options?: {
...;
} | undefined): EntryPointDef<...> (+2 overloads)getEntryPoint(const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
... 11 more ...;
serializers?: ChainSerializers<...> | undefined;
}sepolia),
// This should return a concatenation of your `factoryAddress` and the `callData` for your factory's create account method
getAccountInitCode: () => Promise<Hex>getAccountInitCode: async (): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x{factoryAddress}{callData}",
// an invalid signature that doesn't cause your account to revert during validation
getDummySignature: () => Hex | Promise<Hex>getDummySignature: async (): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x1234...",
// given a UO in the form of {target, data, value} should output the calldata for calling your contract's execution method
encodeExecute: (tx: AccountOp) => Promise<Hex>encodeExecute: async (uo: AccountOpuo): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x....",
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>signMessage: async ({ message: SignableMessagemessage }): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>signTypedData: async (typedData: TypedDataDefinition<typedData, primaryType, typedData extends {
[x: string]: readonly TypedDataParameter[];
[x: `string[${string}]`]: undefined;
[x: `function[${string}]`]: undefined;
[x: `address[${string}]`]: undefined;
[x: `bool[${string}]`]: undefined;
[x: `bytes[${string}]`]: undefined;
[x: `bytes1[${string}]`]: undefined;
[x: `bytes2[${string}]`]: undefined;
[x: `bytes3[${string}]`]: undefined;
[x: `bytes4[${string}]`]: undefined;
[x: `bytes5[${string}]`]: undefined;
[x: `bytes6[${string}]`]: undefined;
[x: `bytes7[${string}]`]: undefined;
[x: `bytes8[${string}]`]: undefined;
[x: `bytes9[${string}]`]: undefined;
[x: `bytes10[${string}]`]: undefined;
[x: `bytes11[${string}]`]: undefined;
[x: `bytes12[${string}]`]: undefined;
[x: `bytes13[${string}]`]: undefined;
[x: `bytes14[${string}]`]: undefined;
[x: `bytes15[${string}]`]: undefined;
[x: `bytes16[${string}]`]: undefined;
[x: `bytes17[${string}]`]: undefined;
[x: `bytes18[${string}]`]: undefined;
[x: `bytes19[${string}]`]: undefined;
[x: `bytes20[${string}]`]: undefined;
[x: `bytes21[${string}]`]: undefined;
[x: `bytes22[${string}]`]: undefined;
[x: `bytes23[${string}]`]: undefined;
[x: `bytes24[${string}]`]: undefined;
[x: `bytes25[${string}]`]: undefined;
[x: `bytes26[${string}]`]: undefined;
[x: `bytes27[${string}]`]: undefined;
[x: `bytes28[${string}]`]: undefined;
[x: `bytes29[${string}]`]: undefined;
[x: `bytes30[${string}]`]: undefined;
[x: `bytes31[${string}]`]: undefined ...typedData): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x000",
/// OPTIONAL PARAMS ///
// if you already know your account's address, pass that in here to avoid generating a new counterfactual
accountAddress?: `0x${string}` | undefinedaccountAddress: "0x...",
// if your account supports batching, this should take an array of UOs and return the calldata for calling your contract's batchExecute method
encodeBatchExecute?: ((txs: AccountOp[]) => Promise<Hex>) | undefinedencodeBatchExecute: async (uos: AccountOp[]uos): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
// if your contract expects a different signing scheme than the default signMessage scheme, you can override that here
signUserOperationHash?: ((uoHash: Hex) => Promise<Hex>) | undefinedsignUserOperationHash: async (hash: `0x${string}`hash): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
// allows you to define the calldata for upgrading your account
encodeUpgradeToAndCall?: ((params: UpgradeToAndCallParams) => Promise<Hex>) | undefinedencodeUpgradeToAndCall: async (params: UpgradeToAndCallParamsparams): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Hash = `0x${string}`Hash> => "0x...",
});
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
The toSmartContractAccount
Method
For your reference, this is the definition of the toSmartContractAccount
interface as pulled from the source code: