Skip to main content
Privy lets users create wallets through email or social login. This guide shows you how to connect Privy wallets to Biconomy using the AbstractJS SDK for gasless, cross-chain transactions.

What We’re Building

By the end of this tutorial, your users will be able to:
  1. Log in with email (no seed phrase)
  2. Execute transactions without holding ETH for gas
  3. Perform cross-chain operations with one signature
How it works: When a user logs in with Privy, they get an embedded wallet. We upgrade this wallet to a smart account using EIP-7702, then route transactions through Biconomy’s MEE to handle gas payments.

Step 1: Install Dependencies

npm install @privy-io/react-auth @biconomy/abstractjs viem
What these packages do:
  • @privy-io/react-auth — Privy’s React SDK for wallet creation and auth
  • @biconomy/abstractjs — Biconomy’s SDK for smart accounts and MEE
  • viem — Ethereum library for building transactions

Step 2: Configure Privy Provider

Wrap your app with Privy’s provider. The key settings are:
import { PrivyProvider } from "@privy-io/react-auth";

function App({ children }) {
  return (
    <PrivyProvider
      appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
      config={{
        // Allow email login
        loginMethods: ["email"],
        
        embeddedWallets: {
          // Auto-create wallet on first login
          createOnLogin: true,
          // Don't prompt for every signature (better UX)
          noPromptOnSignature: true,
        },
      }}
    >
      {children}
    </PrivyProvider>
  );
}

Step 3: Access the Embedded Wallet

After a user logs in, get their wallet from Privy:
import { useWallets, useSignAuthorization } from "@privy-io/react-auth";
import { createWalletClient, custom } from "viem";
import { optimism } from "viem/chains";

function usePrivyWallet() {
  const { wallets } = useWallets();
  const { signAuthorization } = useSignAuthorization();
  
  // The embedded wallet is typically the first one
  const embeddedWallet = wallets?.[0];
  
  return { embeddedWallet, signAuthorization };
}

Step 4: Create a Wallet Client

The wallet client is how you’ll sign transactions:
async function getWalletClient(embeddedWallet) {
  // Switch to your target chain
  await embeddedWallet.switchChain(optimism.id);
  
  // Get the Ethereum provider from Privy
  const provider = await embeddedWallet.getEthereumProvider();
  
  // Create a viem wallet client
  const walletClient = createWalletClient({
    account: embeddedWallet.address,
    chain: optimism,
    transport: custom(provider),
  });
  
  return walletClient;
}

Step 5: Sign EIP-7702 Authorization

This step upgrades the EOA to a smart account:
// The Nexus smart account implementation address
const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03";

// Sign the authorization to upgrade the wallet
const authorization = await signAuthorization({
  contractAddress: NEXUS_IMPLEMENTATION,
  chainId: 0, // 0 = works on any chain
});
What just happened? The user signed a message that says “I authorize my wallet to behave like this smart contract.” Their address stays the same, but now it has smart account capabilities.

Step 6: Create the Smart Account

Now create a Nexus account that can work across multiple chains:
import { 
  toMultichainNexusAccount, 
  getMEEVersion, 
  MEEVersion 
} from "@biconomy/abstractjs";
import { optimism, base } from "viem/chains";
import { http } from "viem";

const nexusAccount = await toMultichainNexusAccount({
  chainConfigurations: [
    {
      chain: optimism,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0),
      // Use the original wallet address (EIP-7702 mode)
      accountAddress: embeddedWallet.address,
    },
    {
      chain: base,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0),
      accountAddress: embeddedWallet.address,
    },
  ],
  signer: walletClient.account,
});

Step 7: Create the MEE Client

The MEE client handles transaction execution:
import { createMeeClient } from "@biconomy/abstractjs";

const meeClient = await createMeeClient({ 
  account: nexusAccount 
});

Step 8: Execute a Gasless Transaction

Now you can execute transactions where gas is paid in any token:
import { erc20Abi } from "viem";

const USDC_ADDRESS = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; // USDC on Optimism

const { hash } = await meeClient.execute({
  // Required for EIP-7702 flow
  authorization,
  delegate: true,
  
  // Pay gas fees with USDC instead of ETH
  feeToken: {
    address: USDC_ADDRESS,
    chainId: optimism.id,
  },
  
  // Your transaction instructions
  instructions: [
    {
      chainId: optimism.id,
      calls: [
        {
          to: "0xRecipientAddress",
          value: 0n,
          data: "0x", // or encoded function data
        },
      ],
    },
  ],
});

// Wait for confirmation
const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
console.log("Transaction complete:", receipt.hash);

Complete Example

Here’s a full React component putting it all together:
import { useState } from "react";
import { useWallets, useSignAuthorization } from "@privy-io/react-auth";
import { createWalletClient, custom, http, erc20Abi } from "viem";
import { optimism, base } from "viem/chains";
import {
  createMeeClient,
  toMultichainNexusAccount,
  getMEEVersion,
  MEEVersion,
} from "@biconomy/abstractjs";

const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03";
const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85";

export function GaslessTransfer() {
  const { wallets } = useWallets();
  const { signAuthorization } = useSignAuthorization();
  const [status, setStatus] = useState("");

  async function sendGaslessTransaction() {
    try {
      setStatus("Setting up...");
      
      const embeddedWallet = wallets?.[0];
      if (!embeddedWallet) throw new Error("Please log in first");

      // Create wallet client
      await embeddedWallet.switchChain(optimism.id);
      const provider = await embeddedWallet.getEthereumProvider();
      const walletClient = createWalletClient({
        account: embeddedWallet.address,
        chain: optimism,
        transport: custom(provider),
      });

      setStatus("Signing authorization...");
      
      // Sign EIP-7702 authorization
      const authorization = await signAuthorization({
        contractAddress: NEXUS_IMPLEMENTATION,
        chainId: 0,
      });

      setStatus("Creating smart account...");
      
      // Create multichain account
      const nexusAccount = await toMultichainNexusAccount({
        chainConfigurations: [
          {
            chain: optimism,
            transport: http(),
            version: getMEEVersion(MEEVersion.V2_1_0),
            accountAddress: embeddedWallet.address,
          },
        ],
        signer: walletClient.account,
      });

      // Create MEE client
      const meeClient = await createMeeClient({ account: nexusAccount });

      setStatus("Executing transaction...");
      
      // Execute gasless transaction
      const { hash } = await meeClient.execute({
        authorization,
        delegate: true,
        feeToken: {
          address: USDC_OPTIMISM,
          chainId: optimism.id,
        },
        instructions: [
          {
            chainId: optimism.id,
            calls: [
              {
                to: "0x0000000000000000000000000000000000000000",
                value: 0n,
              },
            ],
          },
        ],
      });

      const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
      setStatus(`Success! Hash: ${receipt.hash}`);
      
    } catch (error) {
      setStatus(`Error: ${error.message}`);
    }
  }

  return (
    <div>
      <button onClick={sendGaslessTransaction}>
        Send Gasless Transaction
      </button>
      <p>{status}</p>
    </div>
  );
}

Key Takeaways

  1. Privy creates the wallet — Users log in with email, Privy creates an embedded wallet
  2. EIP-7702 adds smart features — The wallet gets upgraded without changing address
  3. MEE handles gas — Transactions are executed with any ERC-20 token for gas
  4. Cross-chain ready — Add more chains to chainConfigurations for multi-chain ops

Next Steps