Migration Guide
Below are the steps to migrate your project from older versions of the aa-sdk
to the latest version.
Migrating to version 3.x.x
This version update brings a lot of breaking changes. As we began to support Modular Accounts and their interfaces, we realized that the previous version of the SDK was not as flexible as it could have been to handle the modularity of the new account interfaces. To address this, version 3.x.x of the SDK switches to an approach more idiomatic to viem
.
Viem Version
We have updated our dependency to viem v2.x.x. This means you will need to update your project to use >= v2.5.0 of viem.
Client: SmartAccountProvider
→ SmartAccountClient
The biggest change is that the SmartAccountProvider
class has been removed and replaced with a SmartAccountClient
type that extends viem
's Client
. To get started with the new clients, you can do the following:
import { SmartAccountProvider } from "@alchemy/aa-core";
import { getDefaultEntryPointAddress } from "@alchemy/aa-core";
import { http } from "viem";
import { sepolia } from "@alchemy/aa-core";
const provider = new SmartAccountProvider({
const client = createSmartAccountClient({
rpcProvider: "RPC_URL",
transport: http("RPC_URL"),
chain: sepolia,
entryPointAddress: getDefaultEntryPointAddress(sepolia),
});
Client: Removal of with*
middleware override functions
The SmartAccountProvider
in previous versions had a number of with*
functions that mapped to the corresponding middleware functions on the provider. The concept of the middlewares is still present in this version, but their configuration has been moved to the SmartAccountClient
creator. For example,
import { SmartAccountProvider } from "@alchemy/aa-core";
import { getDefaultEntryPointAddress } from "@alchemy/aa-core";
import { http } from "viem";
import { sepolia } from "@alchemy/aa-core";
const provider = new SmartAccountProvider({
const client = createSmartAccountClient({
rpcProvider: "RPC_URL",
transport: http("RPC_URL"),
chain: sepolia,
entryPointAddress: getDefaultEntryPointAddress(sepolia),
}).withGasEstimator(async () => ({
gasEstimator: async (struct) => ({
...struct,
callGasLimit: 0n,
preVerificationGas: 0n,
verificationGasLimit: 0n,
}),
});
Client: signature changes on methods
To support the various ways of connecting to a smart account, the signatures of the methods on SmartAccountClient
have changed. Almost all methods now have an optional param for account
and have been converted into single argument functions that take an object with the their properties instead.
Account: BaseSmartContractAccount
→ SmartContractAccount
The next big change is the removal of the class-based BaseSmartContractAccount
that all accounts extended from. This has been replaced with a SmartContractAccount
type that extends viem
's Account
, and instantiation of an account is now an async
action. To get started with the new accounts (using LightAccount
as an example), you will have to make the following changes:
import {
LightSmartContractAccount,
createLightAccount,
getDefaultLightAccountFactoryAddress,
} from "@alchemy/aa-accounts";
import {
LocalAccountSigner,
type Hex,
} from "@alchemy/aa-core";
import { sepolia } from "@alchemy/aa-core";
const chain = sepolia;
const account = new LightSmartContractAccount({
const account = await createLightAccount({
rpcClient: client,
transport: http("RPC_URL"),
signer,
chain,
factoryAddress: getDefaultLightAccountFactoryAddress(chain),
});
Account: Connecting to a Smart Account
In earlier versions, a provider could not be used with a smart account until it was connected to one using .connect
. In version 3.x.x, you have the option of keeping the two disconnected and passing the account to the client methods directly. You also have the option to hoist the account so that you don't have to pass the account to every method.
Option 1: Passing the Account to the Client Methods
import { createLightAccount } from "@alchemy/aa-accounts";
import {
createBundlerClient,
createSmartAccountClientFromExisting
LocalAccountSigner,
type Hex,
} from "@alchemy/aa-core";
import { sepolia } from "@alchemy/aa-core";
import { custom, http } from "viem";
const chain = sepolia;
const client = createBundlerClient({
chain,
transport: http("JSON_RPC_URL"),
});
// createSmartAccountClientFromExisting is a helper method that allows you
// to reuse a JSON RPC client to create a Smart Account client.
const smartAccountClient = createSmartAccountClientFromExisting({
client,
});
const account = await createLightAccount({
signer,
chain,
transport: custom(client),
});
const { hash } = await smartAccountClient.sendUserOperation({
uo: {
target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
data: "0x",
value: 10n,
},
account,
});
Option 2: Hoisting the Account
Hoisting the account is similar to using .connect
in previous versions. You simply create your client with an account passed in to it.
import { createLightAccount } from "@alchemy/aa-accounts";
import {
createBundlerClient,
createSmartAccountClientFromExisting
LocalAccountSigner,
type Hex,
} from "@alchemy/aa-core";
import { sepolia } from "@alchemy/aa-core";
import { http, custom } from "viem";
const chain = sepolia;
const client = createBundlerClient({
chain,
transport: http("JSON_RPC_URL"),
});
const account = await createLightAccount({
signer,
transport: custom(client),
chain,
});
const smartAccountClient = createSmartAccountClientFromExisting({
account,
client,
});
const { hash } = await smartAccountClient.sendUserOperation({
uo: {
target: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
data: "0x",
value: 10n,
},
account,
});
Account: Custom Accounts
In prior versions, using your own smart contract account implementations required that you extend BaseSmartContractAccount
. In version 3.x.x, you can use the toSmartContractAccount
method which will allow you to use any account with SmartAccountClient
. toSmartContractAccount
has the form of:
type toSmartContractAccount = <
Name extends string = string,
TTransport extends Transport = Transport
>({
transport,
chain,
source,
entryPointAddress,
accountAddress,
getAccountInitCode,
signMessage,
signTypedData,
encodeBatchExecute,
encodeExecute,
getDummySignature,
signUserOperationHash,
encodeUpgradeToAndCall,
}: ToSmartContractAccountParams<Name, TTransport>) => Promise<
SmartContractAccount<Name>
>;
Account: SimpleAccount and NaniAccount initialization params
chain
and transport
have been added to the constructor and rpcClient
has been removed.
Account: SimpleAccount and LightAccount intialization params
index
is now called salt
Signer: signTypedData
signature change
The signTypedData
method found on SmartAccountSigner
has been updated to match the signature found on SmartContractAccount
and viem's Account
.
signTypedData: (params: SignTypedDataParams) => Promise<Hex>;
signTypedData: <
const TTypedData extends TypedData | { [key: string]: unknown },
TPrimaryType extends string = string
>(
params: TypedDataDefinition<TTypedData, TPrimaryType>
) => Promise<Hex>;
Ethers: Removed methods
The with*
methods have been removed from the Provider and Signer classes. The connect
methods has been removed in favor of immutable properties on the Provider and Signer classes. See updated AccountSigner constructor below.
Ethers: getPublicErc4337Client
→ getBundlerClient
The getPublicErc4337Client
method has been renamed to getBundlerClient
to match the naming found in aa-core
.
Ethers: Updated Signer Adapter constructor
The AccountSigner
now takes in a SmartContractAccount
as a param in its constructor.
Core: Transition from Percent
to Multiplier
api and types
Percent
The Percent
type and PercentSchema
have been removed in favor of the Multiplier
type and MultiplierSchema
.
Going forward when using the feeOptions, you can specify the Multiplier
type instead of a Percent
. The Multiplier
type is a number that represents direct multipliaction of the estimation. For example, 0.1
is 10% of the estimated value and 1
is 100% of the estimated value.
createModularAccountAlchemyClient({
...
opts: {
...
// The maxFeePerGas and maxPriorityFeePerGas estimated values will now be multipled by 1.5
feeOptions: {
// This was previously { percent: 50n }
maxFeePerGas: { multiplier: 1.5 },
// This was previously { percent: 25n }
maxPriorityFeePerGas: { multiplier: 1.25 },
},
},
});