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:
- Use Para sessions for secure authentication
- Execute batched transactions without holding ETH
- 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
- Para provides sessions — Users authenticate once, then your backend can sign on their behalf
- EIP-7702 adds smart features — The user’s address gains smart account capabilities
- MEE handles execution — Gas abstraction and cross-chain routing are automatic
- Server-side control — Your backend manages transaction flow securely
Next Steps