Skip to content

Building a DeFi Agent

A comprehensive guide to building an AI-powered DeFi agent using BNB-Chain-MCP.


Overview

This guide walks you through creating a fully functional DeFi agent that can:

  • Monitor market conditions
  • Analyze opportunities
  • Execute trades safely
  • Manage portfolios
  • Assess risks

Architecture

┌─────────────────────────────────────────────────────┐
│                    DeFi Agent                        │
├─────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │   Market    │  │   Risk      │  │  Execution  │ │
│  │   Analyzer  │  │   Manager   │  │   Engine    │ │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘ │
│         │                │                │        │
│         └────────────────┼────────────────┘        │
│                          │                          │
│              ┌───────────┴───────────┐              │
│              │   Universal Crypto    │              │
│              │      MCP Server       │              │
│              └───────────┬───────────┘              │
└──────────────────────────┼──────────────────────────┘
         ┌─────────────────┼─────────────────┐
         │                 │                 │
    ┌────┴────┐      ┌────┴────┐      ┌────┴────┐
    │Ethereum │      │ Arbitrum│      │   BSC   │
    └─────────┘      └─────────┘      └─────────┘

Step 1: Project Setup

Initialize Project

mkdir defi-agent
cd defi-agent
npm init -y
npm install @modelcontextprotocol/sdk openai dotenv

Environment Configuration

Create .env:

OPENAI_API_KEY=your-openai-key
PRIVATE_KEY=0x...  # Optional: for transactions

Project Structure

defi-agent/
├── src/
│   ├── agent.ts
│   ├── mcp-client.ts
│   ├── strategies/
│   │   ├── market-analyzer.ts
│   │   ├── yield-optimizer.ts
│   │   └── risk-manager.ts
│   └── utils/
│       ├── formatters.ts
│       └── validators.ts
├── config/
│   └── settings.json
├── .env
└── package.json

Step 2: MCP Client Setup

Basic MCP Client

// src/mcp-client.ts
import { spawn, ChildProcess } from 'child_process';
import * as readline from 'readline';

interface MCPRequest {
  jsonrpc: '2.0';
  id: number;
  method: string;
  params: Record<string, any>;
}

export class MCPClient {
  private process: ChildProcess | null = null;
  private requestId = 0;
  private pending = new Map<number, {
    resolve: (value: any) => void;
    reject: (error: Error) => void;
  }>();
  private initialized = false;

  async connect(): Promise<void> {
    return new Promise((resolve, reject) => {
      this.process = spawn('npx', ['-y', '@nirholas/bnb-chain-mcp@latest'], {
        env: {
          ...process.env,
          PRIVATE_KEY: process.env.PRIVATE_KEY
        },
        stdio: ['pipe', 'pipe', 'pipe']
      });

      const rl = readline.createInterface({
        input: this.process.stdout!,
        crlfDelay: Infinity
      });

      rl.on('line', (line) => {
        try {
          const response = JSON.parse(line);
          const pending = this.pending.get(response.id);
          if (pending) {
            this.pending.delete(response.id);
            if (response.error) {
              pending.reject(new Error(response.error.message));
            } else {
              pending.resolve(response.result);
            }
          }
        } catch (e) {
          // Handle parse errors
        }
      });

      this.process.stderr?.on('data', (data) => {
        console.error('MCP Error:', data.toString());
      });

      // Initialize connection
      this.sendRequest('initialize', {
        protocolVersion: '2024-11-05',
        capabilities: {},
        clientInfo: { name: 'defi-agent', version: '1.0.0' }
      }).then(() => {
        this.sendNotification('notifications/initialized', {});
        this.initialized = true;
        resolve();
      }).catch(reject);
    });
  }

  private sendRequest(method: string, params: Record<string, any>): Promise<any> {
    return new Promise((resolve, reject) => {
      const id = ++this.requestId;
      const request: MCPRequest = {
        jsonrpc: '2.0',
        id,
        method,
        params
      };
      this.pending.set(id, { resolve, reject });
      this.process?.stdin?.write(JSON.stringify(request) + '\n');
    });
  }

  private sendNotification(method: string, params: Record<string, any>): void {
    this.process?.stdin?.write(JSON.stringify({
      jsonrpc: '2.0',
      method,
      params
    }) + '\n');
  }

  async callTool(name: string, args: Record<string, any> = {}): Promise<any> {
    if (!this.initialized) {
      throw new Error('MCP client not initialized');
    }

    const result = await this.sendRequest('tools/call', {
      name,
      arguments: args
    });

    const content = result?.content || [];
    if (content[0]?.type === 'text') {
      try {
        return JSON.parse(content[0].text);
      } catch {
        return content[0].text;
      }
    }
    return result;
  }

  async listTools(): Promise<any[]> {
    const result = await this.sendRequest('tools/list', {});
    return result?.tools || [];
  }

  disconnect(): void {
    if (this.process) {
      this.process.kill();
      this.process = null;
    }
  }
}

Step 3: Market Analyzer

// src/strategies/market-analyzer.ts
import { MCPClient } from '../mcp-client';

export interface MarketCondition {
  sentiment: 'bullish' | 'bearish' | 'neutral';
  fearGreedIndex: number;
  btcDominance: number;
  totalMarketCap: number;
  defiTvl: number;
  gasConditions: 'low' | 'medium' | 'high';
  recommendations: string[];
}

export class MarketAnalyzer {
  constructor(private mcp: MCPClient) {}

  async getMarketCondition(): Promise<MarketCondition> {
    // Gather market data in parallel
    const [
      fearGreed,
      btcData,
      globalData,
      defiTvl,
      gasPrice
    ] = await Promise.all([
      this.mcp.callTool('market_get_fear_greed_index'),
      this.mcp.callTool('market_get_price', { coinId: 'bitcoin' }),
      this.mcp.callTool('market_get_global_data'),
      this.mcp.callTool('defi_get_total_tvl'),
      this.mcp.callTool('gas_get_gas_price', { network: 'ethereum' })
    ]);

    // Analyze sentiment
    const sentiment = this.analyzeSentiment(fearGreed.value, btcData.priceChange24h);

    // Analyze gas conditions
    const gasConditions = this.analyzeGas(gasPrice.gasPrice);

    // Generate recommendations
    const recommendations = this.generateRecommendations({
      sentiment,
      fearGreed: fearGreed.value,
      gasConditions
    });

    return {
      sentiment,
      fearGreedIndex: fearGreed.value,
      btcDominance: globalData.btcDominance,
      totalMarketCap: globalData.totalMarketCap,
      defiTvl: defiTvl.tvl,
      gasConditions,
      recommendations
    };
  }

  private analyzeSentiment(
    fearGreed: number,
    btcChange: number
  ): 'bullish' | 'bearish' | 'neutral' {
    if (fearGreed > 60 && btcChange > 0) return 'bullish';
    if (fearGreed < 40 && btcChange < 0) return 'bearish';
    return 'neutral';
  }

  private analyzeGas(gwei: number): 'low' | 'medium' | 'high' {
    if (gwei < 30) return 'low';
    if (gwei < 80) return 'medium';
    return 'high';
  }

  private generateRecommendations(data: {
    sentiment: string;
    fearGreed: number;
    gasConditions: string;
  }): string[] {
    const recommendations: string[] = [];

    if (data.fearGreed < 25) {
      recommendations.push('Extreme fear - potential buying opportunity');
    } else if (data.fearGreed > 75) {
      recommendations.push('Extreme greed - consider taking profits');
    }

    if (data.gasConditions === 'high') {
      recommendations.push('Gas prices elevated - delay non-urgent transactions');
    } else if (data.gasConditions === 'low') {
      recommendations.push('Low gas - good time for on-chain operations');
    }

    return recommendations;
  }

  async findOpportunities(
    tokens: string[],
    minPriceChange: number = -10
  ): Promise<any[]> {
    const opportunities: any[] = [];

    for (const token of tokens) {
      try {
        const [price, security] = await Promise.all([
          this.mcp.callTool('market_get_price', { coinId: token }),
          this.mcp.callTool('security_check_token', { 
            tokenAddress: token,
            network: 'ethereum' 
          })
        ]);

        // Look for dips that might be opportunities
        if (price.priceChange24h <= minPriceChange && security.isSecure) {
          opportunities.push({
            token,
            price: price.price,
            change24h: price.priceChange24h,
            securityScore: security.score,
            signal: 'potential_dip_buy'
          });
        }
      } catch (e) {
        // Skip tokens that fail
      }
    }

    return opportunities.sort((a, b) => a.change24h - b.change24h);
  }
}

Step 4: Risk Manager

// src/strategies/risk-manager.ts
import { MCPClient } from '../mcp-client';

export interface RiskAssessment {
  overallRisk: 'low' | 'medium' | 'high' | 'critical';
  score: number;
  factors: RiskFactor[];
  approved: boolean;
  warnings: string[];
}

interface RiskFactor {
  name: string;
  risk: 'low' | 'medium' | 'high';
  weight: number;
  details: string;
}

export class RiskManager {
  private riskLimits = {
    maxPositionSize: 0.2,      // 20% of portfolio
    maxSlippage: 0.03,         // 3%
    minSecurityScore: 60,      // GoPlus score
    minHealthFactor: 1.5,      // For lending
    maxGasGwei: 100
  };

  constructor(private mcp: MCPClient) {}

  async assessTokenRisk(
    tokenAddress: string,
    network: string = 'ethereum'
  ): Promise<RiskAssessment> {
    const factors: RiskFactor[] = [];
    const warnings: string[] = [];

    // Security analysis
    const security = await this.mcp.callTool('security_check_token', {
      tokenAddress,
      network
    });

    factors.push({
      name: 'Smart Contract Security',
      risk: security.score >= 80 ? 'low' : security.score >= 60 ? 'medium' : 'high',
      weight: 0.4,
      details: `GoPlus score: ${security.score}/100`
    });

    if (security.isHoneypot) {
      warnings.push('⚠️ HONEYPOT DETECTED - DO NOT TRADE');
      factors.push({
        name: 'Honeypot',
        risk: 'high',
        weight: 1.0,
        details: 'Token cannot be sold'
      });
    }

    if (security.hasOwnershipRisk) {
      warnings.push('Owner can modify token - centralization risk');
      factors.push({
        name: 'Ownership Risk',
        risk: 'medium',
        weight: 0.2,
        details: 'Owner has elevated privileges'
      });
    }

    // Liquidity analysis
    try {
      const liquidity = await this.mcp.callTool('dex_get_liquidity', {
        tokenAddress,
        network
      });

      factors.push({
        name: 'Liquidity',
        risk: liquidity.totalLiquidity > 1000000 ? 'low' : 
              liquidity.totalLiquidity > 100000 ? 'medium' : 'high',
        weight: 0.2,
        details: `Total liquidity: $${liquidity.totalLiquidity.toLocaleString()}`
      });

      if (liquidity.totalLiquidity < 50000) {
        warnings.push('Very low liquidity - high slippage risk');
      }
    } catch (e) {
      factors.push({
        name: 'Liquidity',
        risk: 'high',
        weight: 0.2,
        details: 'Unable to determine liquidity'
      });
    }

    return this.calculateOverallRisk(factors, warnings);
  }

  async assessTradeRisk(
    tokenIn: string,
    tokenOut: string,
    amount: string,
    network: string = 'ethereum'
  ): Promise<RiskAssessment> {
    const factors: RiskFactor[] = [];
    const warnings: string[] = [];

    // Get quote to check slippage
    const quote = await this.mcp.callTool('swap_get_quote', {
      tokenIn,
      tokenOut,
      amountIn: amount,
      network
    });

    const priceImpact = parseFloat(quote.priceImpact || '0');

    factors.push({
      name: 'Price Impact',
      risk: priceImpact < 1 ? 'low' : priceImpact < 3 ? 'medium' : 'high',
      weight: 0.3,
      details: `Expected impact: ${priceImpact.toFixed(2)}%`
    });

    if (priceImpact > this.riskLimits.maxSlippage * 100) {
      warnings.push(`High price impact (${priceImpact.toFixed(2)}%) - consider smaller trade`);
    }

    // Check gas conditions
    const gas = await this.mcp.callTool('gas_get_gas_price', { network });

    factors.push({
      name: 'Gas Cost',
      risk: gas.gasPrice < 50 ? 'low' : gas.gasPrice < 100 ? 'medium' : 'high',
      weight: 0.2,
      details: `Current gas: ${gas.gasPrice} Gwei`
    });

    if (gas.gasPrice > this.riskLimits.maxGasGwei) {
      warnings.push('Gas prices are elevated - consider waiting');
    }

    // Check token security
    const tokenRisk = await this.assessTokenRisk(tokenOut, network);
    factors.push(...tokenRisk.factors.map(f => ({ ...f, weight: f.weight * 0.5 })));
    warnings.push(...tokenRisk.warnings);

    return this.calculateOverallRisk(factors, warnings);
  }

  private calculateOverallRisk(
    factors: RiskFactor[],
    warnings: string[]
  ): RiskAssessment {
    // Calculate weighted score
    let totalWeight = 0;
    let weightedScore = 0;

    for (const factor of factors) {
      const riskScore = factor.risk === 'low' ? 0 : factor.risk === 'medium' ? 50 : 100;
      weightedScore += riskScore * factor.weight;
      totalWeight += factor.weight;
    }

    const score = totalWeight > 0 ? weightedScore / totalWeight : 50;

    // Determine overall risk level
    let overallRisk: 'low' | 'medium' | 'high' | 'critical';
    if (score < 25) overallRisk = 'low';
    else if (score < 50) overallRisk = 'medium';
    else if (score < 75) overallRisk = 'high';
    else overallRisk = 'critical';

    // Critical warnings override
    if (warnings.some(w => w.includes('HONEYPOT'))) {
      overallRisk = 'critical';
    }

    return {
      overallRisk,
      score: 100 - score, // Convert to "safety" score
      factors,
      approved: overallRisk === 'low' || overallRisk === 'medium',
      warnings
    };
  }
}

Step 5: Execution Engine

// src/strategies/execution-engine.ts
import { MCPClient } from '../mcp-client';
import { RiskManager, RiskAssessment } from './risk-manager';

export interface TradeResult {
  success: boolean;
  transactionHash?: string;
  error?: string;
  details: any;
}

export class ExecutionEngine {
  constructor(
    private mcp: MCPClient,
    private riskManager: RiskManager
  ) {}

  async executeSwap(
    tokenIn: string,
    tokenOut: string,
    amount: string,
    network: string = 'ethereum',
    options: {
      maxSlippage?: number;
      skipRiskCheck?: boolean;
    } = {}
  ): Promise<TradeResult> {
    const { maxSlippage = 1, skipRiskCheck = false } = options;

    // Risk assessment
    if (!skipRiskCheck) {
      const risk = await this.riskManager.assessTradeRisk(
        tokenIn,
        tokenOut,
        amount,
        network
      );

      if (!risk.approved) {
        return {
          success: false,
          error: `Trade rejected: ${risk.overallRisk} risk`,
          details: { riskAssessment: risk }
        };
      }

      console.log(`Risk assessment: ${risk.overallRisk} (score: ${risk.score})`);
      risk.warnings.forEach(w => console.log(`⚠️ ${w}`));
    }

    // Get quote first
    const quote = await this.mcp.callTool('swap_get_quote', {
      tokenIn,
      tokenOut,
      amountIn: amount,
      network,
      slippage: maxSlippage
    });

    console.log(`Quote received:`);
    console.log(`  Input: ${amount} ${tokenIn}`);
    console.log(`  Output: ${quote.amountOut} ${tokenOut}`);
    console.log(`  Price impact: ${quote.priceImpact}%`);
    console.log(`  Gas estimate: ${quote.gasEstimate}`);

    // Execute swap
    try {
      const result = await this.mcp.callTool('swap_execute_swap', {
        tokenIn,
        tokenOut,
        amountIn: amount,
        minAmountOut: quote.minAmountOut,
        network,
        slippage: maxSlippage
      });

      return {
        success: true,
        transactionHash: result.transactionHash,
        details: {
          amountIn: amount,
          amountOut: result.amountOut,
          priceImpact: quote.priceImpact,
          gasUsed: result.gasUsed
        }
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message,
        details: { quote }
      };
    }
  }

  async executeYieldStrategy(
    protocol: string,
    pool: string,
    amount: string,
    network: string = 'ethereum'
  ): Promise<TradeResult> {
    // Check pool safety
    const poolInfo = await this.mcp.callTool('staking_get_pool_info', {
      protocol,
      pool,
      network
    });

    console.log(`Pool info:`);
    console.log(`  APY: ${poolInfo.apy}%`);
    console.log(`  TVL: $${poolInfo.tvl.toLocaleString()}`);

    // Stake
    try {
      const result = await this.mcp.callTool('staking_stake', {
        protocol,
        pool,
        amount,
        network
      });

      return {
        success: true,
        transactionHash: result.transactionHash,
        details: {
          staked: amount,
          expectedApy: poolInfo.apy,
          received: result.received
        }
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message,
        details: { poolInfo }
      };
    }
  }
}

Step 6: Main Agent

// src/agent.ts
import { MCPClient } from './mcp-client';
import { MarketAnalyzer } from './strategies/market-analyzer';
import { RiskManager } from './strategies/risk-manager';
import { ExecutionEngine } from './strategies/execution-engine';
import 'dotenv/config';

class DeFiAgent {
  private mcp: MCPClient;
  private marketAnalyzer: MarketAnalyzer;
  private riskManager: RiskManager;
  private executionEngine: ExecutionEngine;

  constructor() {
    this.mcp = new MCPClient();
  }

  async initialize(): Promise<void> {
    console.log('Initializing DeFi Agent...');
    await this.mcp.connect();

    this.marketAnalyzer = new MarketAnalyzer(this.mcp);
    this.riskManager = new RiskManager(this.mcp);
    this.executionEngine = new ExecutionEngine(this.mcp, this.riskManager);

    console.log('DeFi Agent ready!');
  }

  async analyzeMarket(): Promise<void> {
    console.log('\n📊 Analyzing market conditions...\n');

    const condition = await this.marketAnalyzer.getMarketCondition();

    console.log(`Market Sentiment: ${condition.sentiment.toUpperCase()}`);
    console.log(`Fear & Greed Index: ${condition.fearGreedIndex}`);
    console.log(`BTC Dominance: ${condition.btcDominance.toFixed(1)}%`);
    console.log(`Total Market Cap: $${(condition.totalMarketCap / 1e12).toFixed(2)}T`);
    console.log(`DeFi TVL: $${(condition.defiTvl / 1e9).toFixed(2)}B`);
    console.log(`Gas Conditions: ${condition.gasConditions}`);

    console.log('\nRecommendations:');
    condition.recommendations.forEach(r => console.log(`  • ${r}`));
  }

  async findYieldOpportunities(
    token: string,
    network: string = 'arbitrum'
  ): Promise<void> {
    console.log(`\n🔍 Finding yield opportunities for ${token} on ${network}...\n`);

    const options = await this.mcp.callTool('staking_get_staking_options', {
      network,
      token
    });

    console.log('Available opportunities:');
    for (const opt of options.options.slice(0, 5)) {
      console.log(`\n  ${opt.protocol} - ${opt.pool}`);
      console.log(`    APY: ${opt.apy}%`);
      console.log(`    TVL: $${(opt.tvl / 1e6).toFixed(2)}M`);
      console.log(`    Type: ${opt.type}`);
    }
  }

  async checkPortfolio(address: string): Promise<void> {
    console.log(`\n💼 Checking portfolio for ${address.slice(0, 8)}...\n`);

    const networks = ['ethereum', 'arbitrum', 'polygon', 'bsc'];
    let totalValue = 0;

    for (const network of networks) {
      const balance = await this.mcp.callTool('wallet_get_balance', {
        address,
        network
      });

      const tokens = await this.mcp.callTool('wallet_get_token_balances', {
        address,
        network
      });

      if (tokens.totalValueUsd > 0) {
        console.log(`${network.toUpperCase()}:`);
        console.log(`  Native: ${balance.balance} ${balance.symbol}`);
        console.log(`  Tokens: $${tokens.totalValueUsd.toLocaleString()}`);
        totalValue += tokens.totalValueUsd;
      }
    }

    console.log(`\nTotal Portfolio Value: $${totalValue.toLocaleString()}`);
  }

  async executeTrade(
    tokenIn: string,
    tokenOut: string,
    amount: string,
    network: string = 'arbitrum'
  ): Promise<void> {
    console.log(`\n🔄 Executing swap: ${amount} ${tokenIn} -> ${tokenOut}...\n`);

    const result = await this.executionEngine.executeSwap(
      tokenIn,
      tokenOut,
      amount,
      network
    );

    if (result.success) {
      console.log('✅ Swap successful!');
      console.log(`  TX: ${result.transactionHash}`);
      console.log(`  Received: ${result.details.amountOut}`);
    } else {
      console.log('❌ Swap failed!');
      console.log(`  Error: ${result.error}`);
    }
  }

  async shutdown(): Promise<void> {
    console.log('\nShutting down DeFi Agent...');
    this.mcp.disconnect();
  }
}

// Main execution
async function main() {
  const agent = new DeFiAgent();

  try {
    await agent.initialize();

    // Example workflow
    await agent.analyzeMarket();
    await agent.findYieldOpportunities('USDC', 'arbitrum');

    // Check portfolio (replace with your address)
    // await agent.checkPortfolio('0x...');

    // Execute trade (uncomment when ready)
    // await agent.executeTrade('USDC', 'WETH', '100', 'arbitrum');

  } finally {
    await agent.shutdown();
  }
}

main().catch(console.error);

Step 7: Running the Agent

Build & Run

npx ts-node src/agent.ts

Example Output

Initializing DeFi Agent...
DeFi Agent ready!

📊 Analyzing market conditions...

Market Sentiment: NEUTRAL
Fear & Greed Index: 52
BTC Dominance: 51.2%
Total Market Cap: $2.45T
DeFi TVL: $89.50B
Gas Conditions: low

Recommendations:
  • Low gas - good time for on-chain operations

🔍 Finding yield opportunities for USDC on arbitrum...

Available opportunities:

  Aave - USDC Supply
    APY: 3.85%
    TVL: $425.00M
    Type: lending

  GMX - GLP
    APY: 12.50%
    TVL: $285.00M
    Type: yield-farming

Shutting down DeFi Agent...

Advanced Features

Automated Monitoring Loop

async function monitorLoop(agent: DeFiAgent, intervalMs: number = 60000) {
  while (true) {
    try {
      await agent.analyzeMarket();
      // Add custom logic here
    } catch (error) {
      console.error('Monitor error:', error);
    }
    await new Promise(resolve => setTimeout(resolve, intervalMs));
  }
}

Alert System

class AlertManager {
  async checkPriceAlerts(
    mcp: MCPClient,
    alerts: { token: string; above?: number; below?: number }[]
  ): Promise<void> {
    for (const alert of alerts) {
      const price = await mcp.callTool('market_get_price', {
        coinId: alert.token
      });

      if (alert.above && price.price > alert.above) {
        console.log(`🔔 ${alert.token} above $${alert.above}: $${price.price}`);
      }
      if (alert.below && price.price < alert.below) {
        console.log(`🔔 ${alert.token} below $${alert.below}: $${price.price}`);
      }
    }
  }
}

Best Practices

  1. Always assess risk before executing trades
  2. Use testnets for development and testing
  3. Implement rate limiting for API calls
  4. Log all operations for debugging and auditing
  5. Monitor health factors for lending positions
  6. Set stop-losses for automated trading
  7. Keep private keys secure - use hardware wallets for production

Next Steps