Skip to main content
Para provides session-based authentication that’s perfect for server-side transaction execution. This guide shows you how to combine Para with Biconomy’s MEE using the AbstractJS SDK for gasless, cross-chain transactions.

What We’re Building

By the end of this tutorial, you’ll have a backend service that can:
  1. Use Para sessions for secure authentication
  2. Execute batched transactions without holding ETH
  3. Handle operations across multiple chains
How it works: Users authenticate with Para on the frontend and receive a session token. Your backend uses this token to sign transactions on the user’s behalf, routing them through Biconomy’s MEE for gas abstraction.

Step 1: Install Dependencies

npm install @biconomy/abstractjs @getpara/server-sdk @getpara/viem-v2-integration viem express
What these packages do:
  • @getpara/server-sdk — Para’s server SDK for session management
  • @getpara/viem-v2-integration — Adapter to use Para with viem
  • @biconomy/abstractjs — Biconomy’s SDK for smart accounts and MEE
  • viem — Ethereum library for building transactions

Step 2: Set Up Environment Variables

Create a .env file:
# Para credentials
PARA_API_KEY=your_para_api_key
PARA_ENVIRONMENT=BETA

# RPC endpoint
RPC_URL=https://sepolia.arbitrum.io/rpc
Keep your API keys secure. Never commit them to version control.

Step 3: Create the Request Handler

Set up an Express handler that receives the user’s session:
import type { Request, Response } from "express";
import { Para as ParaServer, Environment } from "@getpara/server-sdk";

export async function handleTransaction(req: Request, res: Response) {
  // Get session from request body
  const session = req.body.session as string;
  
  if (!session) {
    return res.status(400).json({ 
      error: "Missing session token" 
    });
  }

  // Initialize Para with the session
  const para = new ParaServer(
    process.env.PARA_ENVIRONMENT as Environment,
    process.env.PARA_API_KEY
  );
  
  await para.importSession(session);
  
  // Continue with transaction...
}

Step 4: Create Para Account

Create a viem-compatible account from the Para session:
import { createParaAccount } from "@getpara/viem-v2-integration";
import type { LocalAccount } from "viem";

// Create the Para account adapter
const paraAccount: LocalAccount = createParaAccount(para);
What’s happening here? Para already has the user’s key material from their session. The adapter lets viem use Para for signing operations.

Step 5: Set Up Custom Signing

Para requires custom signing methods for EIP-7702:
import { customSignMessage, customSignAuthorization } from "./signature-utils";

// Override the default signing methods
paraAccount.signMessage = async ({ message }) => 
  customSignMessage(para, message);

paraAccount.signAuthorization = async (authorization) => 
  customSignAuthorization(para, authorization);
Here’s a basic implementation of the signing utilities:
// signature-utils.ts
import { Para as ParaServer } from "@getpara/server-sdk";

export async function customSignMessage(
  para: ParaServer, 
  message: string | Uint8Array
): Promise<`0x${string}`> {
  const messageHex = typeof message === "string" 
    ? message 
    : Buffer.from(message).toString("hex");
    
  return await para.signMessage(messageHex);
}

export async function customSignAuthorization(
  para: ParaServer,
  authorization: any
): Promise<any> {
  return await para.signAuthorization(authorization);
}

Step 6: Create Wallet Client

Create a wallet client for signing transactions:
import { createWalletClient, http } from "viem";
import { arbitrumSepolia } from "viem/chains";

const walletClient = createWalletClient({
  account: paraAccount,
  chain: arbitrumSepolia,
  transport: http(process.env.RPC_URL),
});

Step 7: Sign EIP-7702 Authorization

Sign the authorization that upgrades the EOA to a smart account:
// Biconomy Nexus smart account implementation
const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03";

const authorization = await walletClient.signAuthorization({
  contractAddress: NEXUS_IMPLEMENTATION,
  chainId: 0, // 0 = works on any chain
});
What is chainId: 0? Setting chainId to 0 means the authorization works on any chain. This is useful for cross-chain applications where you want one authorization to enable transactions everywhere.

Step 8: Create the Smart Account

Create a Nexus account using the EOA address for EIP-7702 mode:
import { 
  toMultichainNexusAccount, 
  getMEEVersion, 
  MEEVersion 
} from "@biconomy/abstractjs";

const nexusAccount = await toMultichainNexusAccount({
  chainConfigurations: [
    {
      chain: arbitrumSepolia,
      transport: http(process.env.RPC_URL),
      version: getMEEVersion(MEEVersion.V2_1_0),
      // Use the EOA address for EIP-7702 mode
      accountAddress: paraAccount.address,
    },
  ],
  signer: walletClient,
});
Important: Setting accountAddress to the EOA address is required for EIP-7702 mode. Without this, the SDK will try to compute a different smart account address.

Step 9: Create MEE Client and Execute

Create the MEE client and execute your transaction:
import { createMeeClient } from "@biconomy/abstractjs";
import { encodeFunctionData } from "viem";

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

// Build your transaction calls
const calls = [
  {
    to: "0xYourContractAddress" as `0x${string}`,
    value: 0n,
    data: encodeFunctionData({
      abi: yourContractAbi,
      functionName: "yourFunction",
      args: [arg1, arg2],
    }),
  },
];

// Execute with EIP-7702 delegation
const { hash } = await meeClient.execute({
  authorization,
  delegate: true,
  
  // Optional: pay gas with ERC-20
  // feeToken: {
  //   address: "0xUSDC...",
  //   chainId: arbitrumSepolia.id,
  // },
  
  instructions: [
    {
      chainId: arbitrumSepolia.id,
      calls,
    },
  ],
});

Step 10: Wait for Confirmation

const receipt = await meeClient.waitForSupertransactionReceipt({
  hash,
  timeout: 30000, // 30 seconds
});

res.status(200).json({
  success: true,
  transactionHash: hash,
  receipt: {
    hash: receipt.hash,
    status: receipt.status,
  },
});

Complete Example

Here’s a full Express.js handler:
import type { Request, Response } from "express";
import { 
  createMeeClient, 
  toMultichainNexusAccount,
  getMEEVersion,
  MEEVersion
} from "@biconomy/abstractjs";
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createParaAccount } from "@getpara/viem-v2-integration";
import { createWalletClient, http, encodeFunctionData } from "viem";
import { arbitrumSepolia } from "viem/chains";

const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03";

export async function executeTransaction(
  req: Request, 
  res: Response
): Promise<void> {
  try {
    const { session, calls } = req.body;

    if (!session) {
      res.status(400).json({ error: "Missing session" });
      return;
    }

    // 1. Initialize Para
    const para = new ParaServer(
      Environment.BETA,
      process.env.PARA_API_KEY!
    );
    await para.importSession(session);

    // 2. Create Para account
    const paraAccount = createParaAccount(para);

    // 3. Create wallet client
    const walletClient = createWalletClient({
      account: paraAccount,
      chain: arbitrumSepolia,
      transport: http(process.env.RPC_URL),
    });

    // 4. Sign authorization
    const authorization = await walletClient.signAuthorization({
      contractAddress: NEXUS_IMPLEMENTATION,
      chainId: 0,
    });

    // 5. Create Nexus account
    const nexusAccount = await toMultichainNexusAccount({
      chainConfigurations: [
        {
          chain: arbitrumSepolia,
          transport: http(process.env.RPC_URL),
          version: getMEEVersion(MEEVersion.V2_1_0),
          accountAddress: paraAccount.address,
        },
      ],
      signer: walletClient,
    });

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

    // 7. Execute transaction
    const { hash } = await meeClient.execute({
      authorization,
      delegate: true,
      instructions: [
        {
          chainId: arbitrumSepolia.id,
          calls,
        },
      ],
    });

    // 8. Wait for confirmation
    const receipt = await meeClient.waitForSupertransactionReceipt({
      hash,
      timeout: 30000,
    });

    res.status(200).json({
      success: true,
      address: paraAccount.address,
      transactionHash: hash,
      receipt: {
        hash: receipt.hash,
        status: receipt.status,
      },
    });

  } catch (error) {
    res.status(500).json({
      error: error instanceof Error ? error.message : "Unknown error",
    });
  }
}

Gas Abstraction Options

Pay with ERC-20 Token

const { hash } = await meeClient.execute({
  authorization,
  delegate: true,
  feeToken: {
    address: "0xUSDC_ADDRESS",
    chainId: arbitrumSepolia.id,
  },
  instructions: [/* ... */],
});
Configure sponsorship in your Biconomy dashboard to cover all gas costs for users.

Cross-Chain Execution

Add multiple chains and execute across them:
import { optimism, base } from "viem/chains";

const nexusAccount = await toMultichainNexusAccount({
  chainConfigurations: [
    {
      chain: arbitrumSepolia,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0),
      accountAddress: paraAccount.address,
    },
    {
      chain: optimism,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0),
      accountAddress: paraAccount.address,
    },
  ],
  signer: walletClient,
});

const { hash } = await meeClient.execute({
  authorization,
  delegate: true,
  instructions: [
    { chainId: arbitrumSepolia.id, calls: [/* Arbitrum calls */] },
    { chainId: optimism.id, calls: [/* Optimism calls */] },
  ],
});

Key Takeaways

  1. Para provides sessions — Users authenticate once, then your backend can sign on their behalf
  2. EIP-7702 adds smart features — The user’s address gains smart account capabilities
  3. MEE handles execution — Gas abstraction and cross-chain routing are automatic
  4. Server-side control — Your backend manages transaction flow securely

Next Steps