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

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

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