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.
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: 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 { 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(): HexgeneratePrivateKey } 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: 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" }),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
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()),
});
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(): HexgeneratePrivateKey } 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(): HexgeneratePrivateKey()),
});
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.