Skip to content

Migration Guide

Below are the steps to migrate your project from older versions of the aa-sdk and account-kit to the latest version.

Migrating to version v4.x.x

Packages

All of the v3 packages have been renamed or removed. The new packages are as follows:

  • @alchemy/aa-core -> @aa-sdk/core
  • @alchemy/aa-ethers -> @aa-sdk/ethers
  • @alchmey/aa-alchemy -> split into @account-kit/signer, @account-kit/infra, @account-kit/core, @account-kit/react
  • @alchemy/aa-accounts -> @account-kit/smart-contracts
  • @alchemy/aa-signers -> removed

AA signers

The @alchemy/aa-signers package has been deprecated. The signers in that package are still compatible with v4, but they will not be receiving future support and the interfaces are not guaranteed to work with future versions of the SDK. If you want to integrate a 3rd party signer, it's still very easy if the signer exposes an EIP-1193 interface. See this guide.

Alchemy Transport

The new Transport type: AlchemyTransport has been added. This impacts how Alchemy clients, middleware, and configs are created.

For Smart Account Clients, the create*AlchemyClient methods have been updated to take a new transport property (of type AlchemyTrasnport) instead of rpcUrl or apiKey directly.

The alchemyFeeEstimator and alchemyUserOperationSimulator middleware methods now no longer take in a ClientWithAlchemyMethods (returned by createAlchemyPublicRpcClient) and instead take in an AlchemyTransport as well.

The AlchemyTransport is a type of viem transport that makes it easier to configure your communication with Alchemy RPC. It supports splitting traffic between Alchemy's AA infra and other Node providers (if needed). The AlchemyTransport has been added to @account-kit/infra and is exported as alchemy.

Creating a config with @account-kit/core or @account-kit/react has been updated to accept an AlchemyTransport and the parameters of createConfig have been simplified as a result. You will now need to replace your rpcUrl or apiKey params with transport: alchemy({...}).

For more detailed examples, see the relevant Quickstart guides, depending on which package you are using. If you don't know where to start, checkout the React Quickstart.

Hooks: useSendTransaction and useSendTransactions removed

These methods have been removed since useSendUserOperation can be used to send transactions and user operations. If you were using useSendTransaction or useSendTransactions, you can replace them with useSendUserOperation and pass in waitForTxn: true so that all sendUserOperation calls will wait for the transaction to be mined.

Utils: verifyEIP6492Signature removed

This method has been removed. Use verifyMessage from viem.

Utils: defineReadOnly removed

This method is no longer used internally and has been removed. The reference impl can be found within ethers.js.

Utils: getChain removed

This method was not super efficient and maintainable because it was importing all the chains from viem. That meant that if you used this method, it risked increasing your bundle size massively. Now all methods require a Chain instead of chainId in some places.

Ethers: Chain required param

Certain methods required a chainId which would be converted into a Chain using the above method. This has been removed for the reasons above.

Alchemy Chain defs

The Alchemy Chain definitions have been moved from @aa-sdk/core to @account-kit/infra.

Ethers: getPublicErc4337ClientgetBundlerClient

This method on the AccountSigner has been renamed.

Accounts: Default LightAccount version is now v2.0.0

If you were creating LightAccounts for users without explicitly specifying the version (ie. in createLightAccount or useAccount or useSmartAccountClient), you should manually specify the previous default of v1.1.0.

Accounts: create*Client methods now have the account parameters un-nested

Previously, the create*Client (ie. createLightAccountClient) methods used to have an account parameter which took in the params for the underlying account. This is resulted in different APIs for both the corresponding create*AlchemyClient (ie. createLightAccountAlchemyClient) which had these as top-level parameters. If you were using createLightAccountClient or similar methods, you should now pass the account parameters as top-level parameters.

Accounts: getNonce -> getAccountNonce

Due to a clash in the typing of viem's Account type, the getNonce method has been renamed to getAccountNonce.

Viem Version

The version of viem has been updated to 2.20.0 and been added as a peer dependency. If you have viem listed as a dependency, make sure it is updated to 2.20.0.

Dropped CJS support

Due to our dependency on wagmi in the core and react packages, those packages no longer support cjs builds. To keep things consistent across all of our packages, we've dropped CJS support in all the packages in this repo. See this guide on how to migrate from CJS to ESM.

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: SmartAccountProviderSmartAccountClient

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:

Client: removed sign*With6492 methods

The signMessageWith6492 and signTypedDataWith6492 methods have been removed from the SmartAccountClient. Now, all clients will always use 6492 when signing messages. However, if you need to sign messages without 6492, you can still use the client.account.signMessage methods.

Client: checkGasSponsorshipEligibility return type updated

The checkGasSponsorshipEligibility methods now returns an object that includes the eligibility status of a User Operation as well as the UserOperationStruct that the eligibility is for. This now ensures that checking for sponsorhip doesn't cause you to use up your gas sponsorship limits. If your User Operation is eligible, then you can sign the UO with client.signUserOperation and send it with client.sendRawUserOperation.

import { SmartAccountProvider } from "@aa-sdk/core"; 
import { getDefaultEntryPointAddress } from "@aa-sdk/core"; 
import { http } from "viem"; 
import { sepolia } from "@aa-sdk/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 "@aa-sdk/core"; 
import { getDefaultEntryPointAddress } from "@aa-sdk/core"; 
import { http } from "viem"; 
import { sepolia } from "@aa-sdk/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: BaseSmartContractAccountSmartContractAccount

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 "@account-kit/smart-contracts";
import {
  LocalAccountSigner,
  type Hex,
} from "@aa-sdk/core";
import { sepolia } from "@aa-sdk/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 "@account-kit/smart-contracts";
import {
  createBundlerClient,
  createSmartAccountClientFromExisting
  LocalAccountSigner,
  type Hex,
} from "@aa-sdk/core";
import { sepolia } from "@aa-sdk/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 "@account-kit/smart-contracts";
import {
  createBundlerClient,
  createSmartAccountClientFromExisting
  LocalAccountSigner,
  type Hex,
} from "@aa-sdk/core";
import { sepolia } from "@aa-sdk/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.

(params: SignTypedDataParams) => Promise<Hex>; 
 
<
  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: getPublicErc4337ClientgetBundlerClient

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

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 },
      },
    },
  });