Skip to main content
This guide explains how to migrate your BiconomySmartAccountV2 smart accounts to the newer Nexus smart accounts. The migration process preserves your account’s address, balance, and history while upgrading to Nexus’s enhanced architecture.

Why Migrate?

Benefits of Migration

Migrating from V2 to Nexus smart accounts provides several benefits:
1

Enhanced Security

Improved security model with modular validators
2

Better Performance

More gas-efficient transaction processing
3

Expanded Features

Access to the newest account abstraction capabilities
4

Future Compatibility

Ensure your smart account remains compatible with the latest AbstractJS SDK

Migration Process Overview

Migration Steps

The migration follows these steps:
1

Connect to V2 Account

Connect to your existing V2 smart account
2

Deploy V2 Account

If not already deployed. Ensure the account is deployed on-chain
3

Migrate to Nexus

Update implementation and initialize the Nexus account
4

Verify Migration

Test the migrated account with a transaction
5

Update Your Application

Use the latest SDK to interact with the migrated account

Prerequisites

Before starting the migration, ensure you have the following:

Required Setup

1

Update SDK

Update to the latest version of the AbstractJS SDK:
npm install @biconomy/abstractjs
2

Prepare Credentials

Make sure you have your EOA’s private key and the V2 account address

Step 1: Connect to Your V2 Account

V2 Account Connection

First, set up the necessary connections to your V2 smart account:
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";
import { 
  createSmartAccountClient as createV2Client,
  BiconomySmartAccountV2,
  PaymasterMode
} from "@biconomy/account";
import { getMEEVersion, MEEVersion } from "@biconomy/abstractjs";
import dotenv from "dotenv";

dotenv.config();

// Use the version that suits your needs from the list
// See MEE Versioning
const version = MEEVersion.V2_1_0;
const versionConfig = getMEEVersion(version)

// Define configuration variables
const config = {
  // Chain and network information
  chain: baseSepolia,
  
  // EOA credentials
  eoaPrivateKey: process.env.EOA_PRIVATE_KEY, // Replace with your private key
  eoaAddress: process.env.EOA_ADDRESS, // Replace with your EOA address
  
  // Biconomy infrastructure URLs
  v2BundlerUrl: process.env.V2_BUNDLER_URL, // Replace with your V2 bundler URL
  nexusBundlerUrl: process.env.NEXUS_BUNDLER_URL, // Replace with your Nexus bundler URL
  
  // API keys
  paymasterApiKey: process.env.PAYMASTER_API_KEY, // Replace with your Paymaster API key
  
  // Nexus contract addresses
  nexusImplementationAddress: versionConfig.implementationAddress,
  nexusBootstrapAddress: versionConfig.bootStrapAddress,
};

// Connect to your EOA
const eoaAccount = privateKeyToAccount(config.eoaPrivateKey as `0x${string}`);
const client = createWalletClient({
  account: eoaAccount,
  chain: config.chain,
  transport: http(),
});

// Connect to your V2 smart account
const V2Account = await createV2Client({
  signer: client,
  biconomyPaymasterApiKey: config.paymasterApiKey,
  bundlerUrl: config.v2BundlerUrl!,
});

// Get V2 account address
const V2AccountAddress = await V2Account.getAccountAddress();
console.log("V2 Account Address:", V2AccountAddress);

Step 2: Check Deployment Status

Account Deployment Check

Check if your V2 account is already deployed, and deploy it if necessary:
// Check if account is deployed
const isDeployed = await V2Account.isAccountDeployed();

if (!isDeployed) {
  console.log("Account not deployed, deploying now...");
  
  // Deploy the V2 account
  const deploymentResponse = await V2Account.sendTransaction([
    {
      to: V2AccountAddress,
      value: 0n,
      data: "0x",
    },
  ]);

  const { transactionHash } = await deploymentResponse.waitForTxHash();
  console.log("V2 account deployment transaction hash:", transactionHash);
} else {
  console.log("Account already deployed, proceeding with migration");
}

Step 3: Migrate to Nexus

Nexus Migration

Now perform the migration by updating the implementation to Nexus and initializing the Nexus account:
import { 
  encodeFunctionData,
  encodeAbiParameters
} from "viem";

async function migrateToNexus(V2Account: BiconomySmartAccountV2) {
  const V2AccountAddress = await V2Account.getAccountAddress();
  
  // Step 1: Update implementation to Nexus
  console.log("Preparing update implementation to Nexus...");
  const updateImplementationCalldata = encodeFunctionData({
    abi: [
      {
        name: "updateImplementation",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [{ type: "address", name: "newImplementation" }],
        outputs: []
      }
    ],
    functionName: "updateImplementation",
    args: [config.nexusImplementationAddress],
  });
  
  const updateImplementationTransaction = {
    to: V2AccountAddress,
    data: updateImplementationCalldata,
  };
  
  // Step 2: Initialize Nexus Account
  console.log("Preparing initialize Nexus account...");
  const ownerAddress = config.eoaAddress;
  
  // Prepare initialization data for the validator
    const initData = encodeFunctionData({
      abi: [
        { 
          name: "initNexusWithDefaultValidator", type: "function",
           stateMutability: "nonpayable", 
           inputs: [
            { type: "bytes", name: "data" }
          ], 
          outputs: [] 
        }
      ],
      functionName: "initNexusWithDefaultValidator",
      args: [ownerAddress as `0x${string}`]
    });
  
  // Encode bootstrap data
  const initDataWithBootstrap = encodeAbiParameters(
    [
      { name: "bootstrap", type: "address" },
      { name: "initData", type: "bytes" },
    ],
    [config.nexusBootstrapAddress, initData]
  );
  
  // Create initializeAccount calldata
  const initializeNexusCalldata = encodeFunctionData({
    abi: [
      {
        name: "initializeAccount",
        type: "function",
        stateMutability: "nonpayable",
        inputs: [{ type: "bytes", name: "data" }],
        outputs: []
      }
    ],
    functionName: "initializeAccount",
    args: [initDataWithBootstrap],
  });
  
  const initializeNexusTransaction = {
    to: V2AccountAddress,
    data: initializeNexusCalldata,
  };
  
  // Send both transactions in a batch
  console.log("Sending migration transaction...");
  const migrateToNexusResponse = await V2Account.sendTransaction(
    [updateImplementationTransaction, initializeNexusTransaction],
    {
      paymasterServiceData: { mode: PaymasterMode.SPONSORED },
    }
  );
  
  const { transactionHash } = await migrateToNexusResponse.waitForTxHash();
  console.log("Migration transaction hash:", transactionHash);
  console.log("Migration completed successfully");
  
  return V2AccountAddress; // Return the address for the next step
}

Step 4: Test Your Migrated Account

Migration Verification

After migration, verify that your account works correctly by creating a test transaction:
import { createBicoBundlerClient, toNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs";
import { parseEther } from "viem";

async function testMigratedAccount(accountAddress: string) : Promise<boolean> {
  // Connect to the migrated account using Nexus SDK
  const eoaAccount = privateKeyToAccount(config.eoaPrivateKey as `0x${string}`);
  
  const nexusAccount = createBicoBundlerClient({
    account: await toNexusAccount({
      signer: eoaAccount,
      chainConfiguration: {
        chain: config.chain,
        transport: http(),
        version: getMEEVersion(version)
      },
      // IMPORTANT: Use the same address as your V2 account
      accountAddress: accountAddress as `0x${string}`,
    }),
    transport: http(config.nexusBundlerUrl),
  });
  
  console.log("Testing migrated account...");
  
  // Send a test transaction
  const testHash = await nexusAccount.sendUserOperation({
    calls: [{
      to: config.eoaAddress as `0x${string}`,
      value: parseEther("0.00000001"),
    }],
  });
  
  console.log("Test transaction hash:", testHash);
  
  // Wait for the receipt (optional)
  const receipt = await nexusAccount.waitForUserOperationReceipt({ hash: testHash });
  console.log("Test transaction successful:", receipt.success);
  return receipt.success;
}

Step 5: Update Your Application

Application Update

Update your application to use the Nexus SDK for all future interactions:
// IMPORTANT: Always use the same address as your V2 account
const migratedAccountAddress = "YOUR_V2_ACCOUNT_ADDRESS";

// Use this pattern for all future SDK interactions
// Use the same version you used to get implementation address to upgrade to
const nexusAccount = await toNexusAccount({
  signer: eoaAccount,
  chainConfiguration: {
    chain: base,
    transport: http(),
    version: getMEEVersion(version) 
  },
  accountAddress: migratedAccountAddress
});

const bundlerClient = createBicoBundlerClient({
  account: nexusAccount,
  transport: http(bundlerUrl)
});

Troubleshooting

Common Issues and Solutions

If you encounter issues during migration:
  • Migration Transaction Fails
  • Cannot Access Account After Migration
1

Check Funds

Ensure your EOA has sufficient funds for gas
2

Verify Deployment

Verify the V2 account is properly deployed
3

Check Configuration

Check that all environment variables are set correctly

Next Steps

After successfully migrating your V2 account to Nexus:

Post-Migration Actions

1

Store Account Address

STORE YOUR ACCOUNT ADDRESS in your application’s persistent storage
2

Update Application Code

Update your application code to use the accountAddress parameter in all future interactions
3

Test Thoroughly

Test thoroughly with real transactions to ensure everything works as expected
By following this migration guide and properly storing your account address, you’ve successfully upgraded your V2 account to a Nexus account while preserving its address, balance, and history.
I