Architecture Overview
Copy
Ask AI
+------------------------------------------------------------------+
| YOUR APPLICATION |
+------------------------------------------------------------------+
| Frontend (User Grants Permission) |
| - sessionClient.grantPermissionTypedDataSign() |
+------------------------------------------------------------------+
| Backend (Agent Executes) |
| - Store agent private key securely |
| - Store sessionDetails per user |
| - agentClient.usePermission() |
+------------------------------------------------------------------+
| BICONOMY MEE NETWORK |
| - Executes transactions, enforces policies onchain |
+------------------------------------------------------------------+
Step 1: Setup
Install Dependencies
Copy
Ask AI
npm install @biconomy/abstractjs viem
Initialize SDK
Copy
Ask AI
import {
createMeeClient,
toMultichainNexusAccount,
toSmartSessionsModule,
meeSessionActions,
getMEEVersion,
MEEVersion
} from "@biconomy/abstractjs";
import { privateKeyToAccount } from "viem/accounts";
import { base, optimism, arbitrum } from "viem/chains";
import { http } from "viem";
Step 2: Generate Agent Key
Your agent needs its own keypair. Store this securely in your backend.Copy
Ask AI
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
// Generate once, store securely (e.g., AWS Secrets Manager, Vault)
const agentPrivateKey = generatePrivateKey();
const agentSigner = privateKeyToAccount(agentPrivateKey);
console.log("Agent address:", agentSigner.address);
// This address is what users will grant permissions to
Never expose the agent private key. Treat it like a production database password.
Step 3: User Onboarding (Frontend)
When a user wants to enable your agent, they grant permissions.Create User’s Account & Session Client
Copy
Ask AI
// User's signer (from their wallet - Privy, MetaMask, etc.)
const userSigner = /* from wallet connection */;
// Create the user's multichain account
const userAccount = await toMultichainNexusAccount({
signer: userSigner,
chainConfigurations: [
{ chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) },
{ chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) },
{ chain: arbitrum, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }
]
});
// Create MEE client with session actions
const meeClient = await createMeeClient({ account: userAccount });
const sessionClient = meeClient.extend(meeSessionActions);
Prepare Account (One-Time)
Install the Smart Sessions module on the user’s account:Copy
Ask AI
const sessionsModule = toSmartSessionsModule({ signer: agentSigner });
const prepareResult = await sessionClient.prepareForPermissions({
smartSessionsValidator: sessionsModule,
feeToken: { address: USDC, chainId: base.id }
});
if (prepareResult) {
await meeClient.waitForSupertransactionReceipt({ hash: prepareResult.hash });
}
Grant Permissions
Copy
Ask AI
import { getSudoPolicy, getUniversalActionPolicy } from "@biconomy/abstractjs";
// Define what your agent can do
const sessionDetails = await sessionClient.grantPermissionTypedDataSign({
redeemer: agentSigner.address, // Your agent's address
feeToken: { address: USDC, chainId: base.id },
// Time limits
sessionValidAfter: Math.floor(Date.now() / 1000),
sessionValidUntil: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days
// Max gas spend
maxPaymentAmount: parseUnits("20", 6), // 20 USDC for gas
// Allowed actions
actions: [
{
chainId: base.id,
actionTarget: MORPHO_VAULT,
actionTargetSelector: toFunctionSelector("deposit(uint256,address)"),
actionPolicies: [getSudoPolicy()]
},
{
chainId: base.id,
actionTarget: MORPHO_VAULT,
actionTargetSelector: toFunctionSelector("withdraw(uint256,address,address)"),
actionPolicies: [getSudoPolicy()]
}
]
});
// IMPORTANT: Send sessionDetails to your backend
await sendToBackend(sessionDetails, userAccount.addressOn(base.id));
Step 4: Store Session Data (Backend)
Your backend needs to store session details for each user:Copy
Ask AI
// Example database schema
interface UserSession {
userId: string;
accountAddress: string; // User's smart account address
sessionDetails: string; // JSON stringified session details
createdAt: Date;
expiresAt: Date;
}
// Store session
async function storeSession(userId: string, accountAddress: string, sessionDetails: any) {
await db.userSessions.insert({
userId,
accountAddress,
sessionDetails: JSON.stringify(sessionDetails),
createdAt: new Date(),
expiresAt: new Date(sessionDetails.sessionValidUntil * 1000)
});
}
Step 5: Agent Execution (Backend)
Your agent can now act on behalf of users:Copy
Ask AI
async function executeAgentAction(userId: string, action: AgentAction) {
// 1. Load user's session
const session = await db.userSessions.findOne({ userId });
const sessionDetails = JSON.parse(session.sessionDetails);
// 2. Create agent's MEE client
const agentAccount = await toMultichainNexusAccount({
signer: agentSigner,
chainConfigurations: [
{ chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }
],
accountAddress: session.accountAddress // User's account address
});
const agentMeeClient = await createMeeClient({ account: agentAccount });
const agentSessionClient = agentMeeClient.extend(meeSessionActions);
// 3. Build the instruction
const instruction = await agentAccount.buildComposable({
type: "default",
data: {
chainId: base.id,
to: MORPHO_VAULT,
abi: MorphoAbi,
functionName: "deposit",
args: [action.amount, session.accountAddress]
}
});
// 4. Execute with permission
const result = await agentSessionClient.usePermission({
sessionDetails,
mode: "ENABLE_AND_USE",
feeToken: { address: USDC, chainId: base.id },
instructions: [instruction]
});
// 5. Wait for confirmation
const receipt = await agentMeeClient.waitForSupertransactionReceipt({
hash: result.hash
});
return receipt;
}
Step 6: Agent Logic
Implement your agent’s decision-making:Yield Optimization Agent
Copy
Ask AI
async function runYieldOptimizer() {
// Get all users with active sessions
const users = await db.userSessions.find({
expiresAt: { $gt: new Date() }
});
for (const user of users) {
// Check current positions
const positions = await getPositions(user.accountAddress);
// Find best yield opportunity
const bestOpportunity = await findBestYield();
// Rebalance if significantly better
if (bestOpportunity.apy > positions.currentApy * 1.1) {
// Withdraw from current
await executeAgentAction(user.userId, {
type: "withdraw",
protocol: positions.currentProtocol,
amount: positions.amount
});
// Deposit to better opportunity
await executeAgentAction(user.userId, {
type: "deposit",
protocol: bestOpportunity.protocol,
amount: positions.amount
});
}
}
}
// Run every hour
setInterval(runYieldOptimizer, 60 * 60 * 1000);
Trading Agent
Copy
Ask AI
async function runTradingAgent() {
const users = await getActiveUsers();
for (const user of users) {
const signal = await analyzeMarket(user.preferences);
if (signal.shouldTrade) {
await executeAgentAction(user.userId, {
type: "swap",
tokenIn: signal.fromToken,
tokenOut: signal.toToken,
amount: signal.amount
});
}
}
}
Sponsored Agent Execution
If you want to pay gas for your users:Copy
Ask AI
const result = await agentSessionClient.usePermission({
sessionDetails,
mode: "ENABLE_AND_USE",
sponsorship: true, // You pay gas
instructions: [instruction]
});
When using
sponsorship: true, don’t set feeToken when granting permissions.Error Handling
Copy
Ask AI
async function safeExecute(userId: string, action: AgentAction) {
try {
return await executeAgentAction(userId, action);
} catch (error) {
if (error.code === "PERMISSION_DENIED") {
// Session expired or revoked
await notifyUser(userId, "Please renew agent permissions");
await db.userSessions.delete({ userId });
} else if (error.code === "LIMIT_EXCEEDED") {
// Spending or usage limit hit
await notifyUser(userId, "Agent has reached its limits");
} else if (error.code === "SIMULATION_FAILED") {
// Transaction would revert
console.error("Action would fail:", error.message);
} else {
throw error;
}
}
}
Monitoring & Observability
Copy
Ask AI
// Log all agent actions
async function executeWithLogging(userId: string, action: AgentAction) {
const startTime = Date.now();
try {
const result = await executeAgentAction(userId, action);
await logAgentAction({
userId,
action,
status: "success",
txHash: result.hash,
duration: Date.now() - startTime
});
return result;
} catch (error) {
await logAgentAction({
userId,
action,
status: "failed",
error: error.message,
duration: Date.now() - startTime
});
throw error;
}
}
Security Checklist
Agent private key stored in secure secret management
Session details stored encrypted in database
Rate limiting on agent actions
Monitoring for unusual activity
Automatic session cleanup on expiry
User notification system for important events