Smart Account Client
The SmartAccountClient
is an extension of viem
’s Client which allows you to interact
with Smart Contract Accounts. You can use the Smart Account Client to send User Operations, sign messages with your account, sponsor gas,
and other account-related actions.
The client is also EIP-1193 compliant, meaning it can easily be swapped out in place of other web3 providers (eg. window.ethereum
).
Account hoisting
If you’re using the React or Core packages, the Smart Account Clients already handle hoisting and creation of account instances. The Smart Contracts package exports client definitions for hoisted instances of the Smart Contract Accounts.
When creating a Smart Account Client, you have two options:
- You can pass an account instance directly into the client constructor. Doing so will use that account for all of the actions you perform with the client, and you won’t have to pass in the account each time you call a method. This is known as “account hoisting”.
- You can create a client without passing in an account. As a result, you’ll have to pass in an account instance to each method you call on the client. This is really useful if you want to manage multiple instances of an account in your app, and want to use one client for all of them.
Let’s see an example:
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,
const sepolia: Chainsepolia,
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 { function createLightAccount<TTransport extends Transport = Transport, TSigner extends SmartAccountSigner = SmartAccountSigner<any>, TLightAccountVersion extends LightAccountVersion<"LightAccount"> = "v2.0.0">(config: CreateLightAccountParams<TTransport, TSigner, TLightAccountVersion>): Promise<LightAccount<TSigner, TLightAccountVersion>>createLightAccount } from "@account-kit/smart-contracts";
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } 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 } from "viem";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
// with account hoisting
const const transport: AlchemyTransporttransport = 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" });
const const hoistedClient: {
[x: string]: never;
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">;
... 85 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}hoistedClient = createAlchemySmartAccountClient<Chain, LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">, UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<...>): {
...;
}createAlchemySmartAccountClient({
transport: AlchemyTransportThe RPC transport
transport,
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
account?: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0"> | undefinedaccount: await createLightAccount<AlchemyTransport, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">(config: CreateLightAccountParams<...>): Promise<...>createLightAccount({
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport,
}),
});
const const signature: `0x${string}`signature = await const hoistedClient: {
[x: string]: never;
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">;
... 85 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}hoistedClient.signMessage: (args: SignMessageParameters<LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">>) => Promise<Hex>signMessage({ message: SignableMessagemessage: "Hello world! " });
// without account hoisting
const const nonHoistedClient: {
[x: string]: never;
account: SmartContractAccount | undefined;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}nonHoistedClient = createAlchemySmartAccountClient<Chain, SmartContractAccount | undefined, UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<Chain, SmartContractAccount | undefined, UserOperationContext | undefined>): {
...;
}createAlchemySmartAccountClient({
transport: AlchemyTransportThe RPC transport
transport,
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
});
const const lightAccount: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">lightAccount = await createLightAccount<AlchemyTransport, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">(config: CreateLightAccountParams<...>): Promise<...>createLightAccount({
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport,
});
const const signature2: `0x${string}`signature2 = await const nonHoistedClient: {
[x: string]: never;
account: SmartContractAccount | undefined;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}nonHoistedClient.signMessage: (args: SignMessageParameters<SmartContractAccount | undefined>) => Promise<Hex>signMessage({
message: SignableMessagemessage: "Hello world! ",
account: SmartContractAccount<string, keyof EntryPointRegistryBase<unknown>>account: const lightAccount: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">lightAccount,
});
Smart Account Client actions
Because the Smart Account Clients are extensions of viem’s clients, they support extensions via the .extend
method. The base client already includes a number of actions by default.
You can find additional details about these actions in the @aa-sdk/core
documentation.