Documentation Index
Fetch the complete documentation index at: https://docs.biconomy.io/llms.txt
Use this file to discover all available pages before exploring further.
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:
- Log in with email (no seed phrase)
- Execute transactions without holding ETH for gas
- 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
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
- Privy creates the wallet — Users log in with email, Privy creates an embedded wallet
- EIP-7702 adds smart features — The wallet gets upgraded without changing address
- MEE handles gas — Transactions are executed with any ERC-20 token for gas
- Cross-chain ready — Add more chains to
chainConfigurations for multi-chain ops
Next Steps