Modular Account • Getting started

It is easy to get started with Modular Account! We will show you two different ways using Alchemy Infra or 3rd party infra.

Install packages

Prerequisites

  • minimum Typescript version of 5

Installation

First, install the @account-kit/smart-contracts package.

$yarn add @account-kit/smart-contracts
>yarn add @account-kit/infra

With Alchemy Infra

Then you can do the following:

import { 
function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>
createModularAccountAlchemyClient
} from "@account-kit/smart-contracts";
import {
const sepolia: Chain
sepolia
,
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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 {
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 generatePrivateKey(): Hex
generatePrivateKey
} from "viem/accounts";
const
const alchemyAccountClient: { account: MultiOwnerModularAccount<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>; ... 100 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
alchemyAccountClient
= await
createModularAccountAlchemyClient<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>
createModularAccountAlchemyClient
({
transport: AlchemyTransport

The RPC transport

transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
apiKey
: "your-api-key" }),
chain: { blockExplorers?: { [key: string]: ChainBlockExplorer; default: ChainBlockExplorer; } | undefined; ... 7 more ...; testnet?: boolean | undefined; } & ChainConfig<...>

Chain for the client.

chain
:
const sepolia: Chain
sepolia
,
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(): Hex
generatePrivateKey
()),
});
Address calculation

For Modular Account, the address of the smart account will be calculated as a combination of the owners and the salt. You will get the same smart account address each time you supply the same owners, the signer(s) used to create the account for the first time. You can also optionally supply salt if you want a different address for the same owners param (the default salt is 0n).

If you want to use a signer to connect to an account whose address does not map to the contract-generated address, you can supply the accountAddress to connect with the account of interest. In that case, the signer address is not used for address calculation, but only for signing the operation.

With 3rd-party infra

If you’re using a 3rd-party for infra, we also expose a client that you can use to interact with Modular Account using other RPC providers.

import { 
function createMultiOwnerModularAccountClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner> & { transport: AlchemyTransport; }): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>> (+1 overload)
createMultiOwnerModularAccountClient
} 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 {
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 {
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(): Hex
generatePrivateKey
} from "viem/accounts";
const
const accountClient: { account: MultiOwnerModularAccount<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>; ... 99 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
accountClient
= await
createMultiOwnerModularAccountClient<HttpTransport<[{ Method: "web3_clientVersion"; Parameters?: undefined; ReturnType: string; }, { Method: "web3_sha3"; Parameters: [data: Hash]; ReturnType: string; }, { Method: "net_listening"; Parameters?: undefined; ReturnType: boolean; }, ... 43 more ..., { ...; }], false>, Chain | undefined, LocalAccountSigner<...>>(args: CreateMultiOwnerModularAccountClientParams<...>): Promise<...> (+1 overload)
createMultiOwnerModularAccountClient
({
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
,
transport: HttpTransport<[{ Method: "web3_clientVersion"; Parameters?: undefined; ReturnType: string; }, { Method: "web3_sha3"; Parameters: [data: Hash]; ReturnType: string; }, { Method: "net_listening"; Parameters?: undefined; ReturnType: boolean; }, ... 43 more ..., { ...; }], false>
transport
:
http<[{ Method: "web3_clientVersion"; Parameters?: undefined; ReturnType: string; }, { Method: "web3_sha3"; Parameters: [data: Hash]; ReturnType: string; }, { Method: "net_listening"; Parameters?: undefined; ReturnType: boolean; }, ... 43 more ..., { ...; }], false>(url?: string | undefined, config?: HttpTransportConfig<...> | undefined): HttpTransport<...>
http
("RPC_URL"),
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(): Hex
generatePrivateKey
()),
});

Next, if you want to use a different signer with a smart account signer, check out choosing a signer. Otherwise, if you are ready to get on-chain, go to send user operations.