Strategy Guide Research

AI Agent Cross-Chain Bridge Strategies: Maximizing Yield Across Every Chain

March 6, 2026 · 28 min read · Purple Flea Research
Table of Contents
  1. Why AI Agents Need Cross-Chain Capabilities
  2. The Bridge Protocol Landscape
  3. Comparing Bridges: Fees, Speed, and Security
  4. Cross-Chain Yield Farming Strategies
  5. Cross-Chain Arbitrage: Exploiting Price Differences
  6. Gas Management Across Multiple Chains
  7. Integration with Purple Flea's Multi-Chain Wallet
  8. Risk Management: Exploits, Finality, and Slippage
  9. Code Examples: Monitoring Rates Across Six Chains
  10. Automation Patterns: When to Bridge, When to Stay
  11. Conclusion

The blockchain ecosystem has never been more fragmented. Yield opportunities bloom and vanish on Arbitrum, Optimism, Base, Polygon, Solana, and Ethereum mainnet in timescales measured in minutes. Human traders cannot watch six chains simultaneously, react to a 0.3% APR delta between two lending protocols, and execute a round-trip bridge in the same breath. AI agents can — and increasingly, the agents that do this well extract significant surplus from markets that assume only slower, less attentive capital.

This post is a comprehensive technical guide for agent developers building cross-chain strategies on Purple Flea. We cover the full stack: why multi-chain matters, which bridges to use and when, how to farm yield opportunistically across chains, how to spot and close arbitrage gaps, how to keep gas balances funded on every chain, and how to integrate all of this with Purple Flea's multi-chain wallet (BTC, ETH, SOL, XMR, USDC). Every section includes working code in curl, Python, and JavaScript.

New to Purple Flea?

Get $1 in free USDC from the Agent Faucet — no deposit required. Use it to experiment with the strategies in this guide at zero risk.

01. Why AI Agents Need Cross-Chain Capabilities

When DeFi was born on Ethereum, the design space was simple: one chain, one set of protocols, one gas token. The explosion of Layer 2s, alternative L1s, and application-specific chains shattered that simplicity. Today, the same USDC can earn 3.1% on Aave mainnet, 5.8% on Morpho Blue on Base, 7.2% on Kamino on Solana, and 9.4% on a newer protocol on Arbitrum — all simultaneously. The capital does not automatically flow to the highest rate. Friction — bridge latency, gas costs, smart contract risk, human attention — keeps rates out of equilibrium.

AI agents dissolve most of that friction. An agent can poll 50 yield endpoints every 60 seconds, model expected net returns including bridge fees and gas, identify when a rate gap exceeds the cost of migration, execute the bridge transaction, and redeploy the capital — all without human intervention. This is not theoretical: the strategies described in this guide are already running on Purple Flea's infrastructure.

The Multi-Chain Opportunity Map

Chains worth monitoring
12+
ETH, ARB, OP, Base, Polygon, SOL, Avalanche, BNB, zkSync, Scroll, Linea, Mantle
Avg yield spread (top vs. bottom)
4.2%
USDC lending, sampled weekly across 6 major chains (2026 Q1)
Bridge round-trip cost
$0.30–$8
Depending on bridge protocol and chains chosen
Arbitrage windows (median duration)
4–18 min
Before LP rebalancing or competitor agents close the gap

What Human Capital Cannot Do — But Agents Can

02. The Bridge Protocol Landscape

Not all bridges are equal. Each protocol makes a different trade-off between speed, trust assumptions, supported chains, and cost structure. An agent needs to model these trade-offs numerically rather than relying on intuition.

Stargate Fast
Protocol: LayerZero-based liquidity pools
Trust model: Oracle + Relayer pair (permissioned)
Typical fee: 0.06% of transfer + destination gas
Speed: 1–4 minutes (12 chains)
Best for: USDC/USDT at size, frequent bridging
Across Protocol Cheap
Protocol: UMA optimistic oracle + LP relayers
Trust model: Optimistic (dispute window 2h)
Typical fee: 0.03–0.12% dynamic (based on LP utilization)
Speed: 1–5 minutes (L2 to L2); 20 min mainnet
Best for: ETH/USDC between L2s, cost-sensitive routes
Hop Protocol Fast
Protocol: AMM-based with bonders
Trust model: Bonded relayers (economic security)
Typical fee: 0.04% + bonder fee (variable)
Speed: 1–3 minutes L2-to-L2
Best for: ETH, USDC between Ethereum rollups
LayerZero / OFT Secure
Protocol: Omnichain fungible tokens via DVN network
Trust model: Decentralized Verifier Networks
Typical fee: $0.10–$2.50 messaging fee
Speed: 1–10 minutes depending on DVN
Best for: Native token bridging (not wrapped)
Axelar Secure
Protocol: Proof-of-Stake cross-chain network
Trust model: Delegated PoS validator set
Typical fee: $0.50–$5 depending on route
Speed: 2–15 minutes
Best for: Cosmos ecosystems, GMP calls, inter-protocol messages
Wormhole Secure
Protocol: 19-of-19 Guardian network VAAs
Trust model: Named guardian set (multisig)
Typical fee: $0.00–$1.50 (auto-relayer)
Speed: ~1 min Solana; 15 min+ EVM confirmations
Best for: Solana <-> EVM routes, native USDC NTT

How Bridge Architecture Affects Agent Strategy

Each architecture implies different failure modes and latency profiles that agents must handle:

03. Comparing Bridges: Fees, Speed, and Security Models

Bridge Fee (100 USDC) Fee (10,000 USDC) ETH→ARB Speed ARB→SOL Support Security Model Exploit History
Stargate ~$0.12 + gas ~$7.00 + gas ~2 min Indirect Oracle+Relayer None (major)
Across ~$0.05–$0.15 ~$5–$12 ~2 min No Optimistic UMA None (major)
Hop ~$0.08 + gas ~$8 + gas ~90 sec No Bonded relayer None (major)
Wormhole ~$0.00–$0.50 ~$0.50–$2 ~4 min Yes (native) Guardian multisig $320M (2022)
Axelar ~$0.50 ~$1.50 ~5 min Indirect PoS validator None (major)
Canonical (OP/ARB) Gas only (~$0.10) Gas only (~$0.10) ~1 min (deposit) No L1 fraud proofs None

Agent Bridge-Selection Algorithm

Rather than hardcoding a single bridge, agents should select dynamically at execution time. The scoring function looks like:

Python bridge_selector.py
from dataclasses import dataclass
from typing import Optional
import httpx

@dataclass
class BridgeQuote:
    bridge: str
    fee_usd: float
    estimated_seconds: int
    security_score: float     # 0–1; higher = more trust-minimized
    output_amount: float      # tokens received after fee

def score_quote(q: BridgeQuote, amount_usd: float, urgency: str = "normal") -> float:
    """
    Score a bridge quote. Higher is better.
    urgency: "fast" (prioritise speed), "normal", "cheap" (prioritise cost)
    """
    weights = {
        "fast":   {"cost": 0.20, "speed": 0.60, "security": 0.20},
        "normal": {"cost": 0.40, "speed": 0.35, "security": 0.25},
        "cheap":  {"cost": 0.65, "speed": 0.15, "security": 0.20},
    }[urgency]

    # Normalise cost: fee as fraction of transfer amount
    cost_score    = max(0, 1 - (q.fee_usd / amount_usd) * 20)

    # Normalise speed: target under 3 minutes for full score
    speed_score   = max(0, 1 - q.estimated_seconds / 600)

    return (
        weights["cost"]     * cost_score +
        weights["speed"]    * speed_score +
        weights["security"] * q.security_score
    )

async def get_best_bridge(
    src_chain: str,
    dst_chain: str,
    token: str,
    amount_usd: float,
    urgency: str = "normal"
) -> Optional[BridgeQuote]:
    # Fetch real quotes from aggregators
    quotes = await fetch_bungee_quotes(src_chain, dst_chain, token, amount_usd)
    if not quotes:
        return None
    scored = [(score_quote(q, amount_usd, urgency), q) for q in quotes]
    scored.sort(key=lambda x: x[0], reverse=True)
    return scored[0][1]

async def fetch_bungee_quotes(src, dst, token, amount_usd):
    """
    Bungee.exchange aggregates Across, Stargate, Hop, and others.
    Returns a list of BridgeQuote objects.
    """
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.socket.tech/v2/quote",
            params={
                "fromChainId": chain_id(src),
                "toChainId": chain_id(dst),
                "fromTokenAddress": token_address(token, src),
                "toTokenAddress": token_address(token, dst),
                "fromAmount": int(amount_usd * 1e6),  # USDC 6 decimals
                "userAddress": "0xYOUR_AGENT_ADDRESS",
                "sort": "output",
                "singleTxOnly": "true",
            },
            headers={"API-KEY": "your-socket-api-key"}
        )
        data = resp.json()
        routes = data.get("result", {}).get("routes", [])
        return [
            BridgeQuote(
                bridge=r["usedBridgeNames"][0] if r.get("usedBridgeNames") else "unknown",
                fee_usd=float(r.get("totalGasFeesInUsd", 0)),
                estimated_seconds=r.get("estimatedTimeInSeconds", 300),
                security_score=bridge_security_score(r["usedBridgeNames"][0] if r.get("usedBridgeNames") else ""),
                output_amount=int(r["toAmount"]) / 1e6,
            )
            for r in routes
        ]

SECURITY_SCORES = {
    "across":    0.88,
    "stargate":  0.82,
    "hop":       0.78,
    "wormhole":  0.55,   # penalise for historical exploit
    "axelar":    0.80,
    "canonical": 1.00,
}

def bridge_security_score(name: str) -> float:
    return SECURITY_SCORES.get(name.lower(), 0.50)

def chain_id(name: str) -> int:
    return {"ethereum": 1, "arbitrum": 42161, "optimism": 10,
            "base": 8453, "polygon": 137, "solana": 1399811149}[name.lower()]

def token_address(token: str, chain: str) -> str:
    usdc = {
        "ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "arbitrum": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
        "optimism": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
        "base":     "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "polygon":  "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
    }
    return usdc.get(chain, "0x")

04. Cross-Chain Yield Farming Strategies

Cross-chain yield farming is the systematic pursuit of the highest risk-adjusted lending or LP rate across all monitored chains. The agent's job is to maintain a single pool of capital and continuously re-allocate it to whichever venue offers the most net yield after transaction costs.

The Yield Polling Loop

JavaScript / Node.js yield-monitor.js
import { ethers } from "ethers";

// DefiLlama rates API — free, no key required
const DEFILLAMA_YIELDS = "https://yields.llama.fi/pools";

// Protocols and chains we care about
const TARGETS = [
  { protocol: "aave-v3",   chain: "ethereum",  asset: "USDC" },
  { protocol: "aave-v3",   chain: "arbitrum",  asset: "USDC" },
  { protocol: "aave-v3",   chain: "base",      asset: "USDC" },
  { protocol: "aave-v3",   chain: "optimism",  asset: "USDC" },
  { protocol: "morpho-blue", chain: "ethereum", asset: "USDC" },
  { protocol: "morpho-blue", chain: "base",     asset: "USDC" },
  { protocol: "kamino",    chain: "solana",     asset: "USDC" },
  { protocol: "drift",     chain: "solana",     asset: "USDC" },
  { protocol: "compound-v3", chain: "ethereum", asset: "USDC" },
  { protocol: "compound-v3", chain: "polygon",  asset: "USDC" },
];

async function fetchAllYields() {
  const resp = await fetch(DEFILLAMA_YIELDS);
  const { data } = await resp.json();

  const results = [];
  for (const target of TARGETS) {
    const pool = data.find(p =>
      p.project.toLowerCase().includes(target.protocol) &&
      p.chain.toLowerCase() === target.chain.toLowerCase() &&
      p.symbol.toUpperCase().includes(target.asset)
    );
    if (pool) {
      results.push({
        ...target,
        apy:    pool.apy,
        tvlUsd: pool.tvlUsd,
        poolId: pool.pool,
      });
    }
  }

  // Sort descending by APY
  results.sort((a, b) => b.apy - a.apy);
  return results;
}

async function decideReallocation(currentChain, currentProtocol, capitalUsd) {
  const yields = await fetchAllYields();
  const current = yields.find(
    y => y.chain === currentChain && y.protocol === currentProtocol
  );
  const best = yields[0];

  if (!current || !best) return null;

  const apyDelta = best.apy - current.apy;          // percentage points
  const annualGainUsd = (apyDelta / 100) * capitalUsd;
  const estimatedBridgeCostUsd = 5;                  // conservative estimate
  const breakEvenDays = (estimatedBridgeCostUsd / (annualGainUsd / 365));

  console.log(`Current: ${currentProtocol}@${currentChain} → ${current.apy.toFixed(2)}% APY`);
  console.log(`Best:    ${best.protocol}@${best.chain} → ${best.apy.toFixed(2)}% APY`);
  console.log(`Break-even: ${breakEvenDays.toFixed(1)} days`);

  // Only bridge if break-even is under 14 days
  if (breakEvenDays < 14 && best.chain !== currentChain) {
    return {
      action: "bridge",
      from: { chain: currentChain, protocol: currentProtocol },
      to:   { chain: best.chain,   protocol: best.protocol },
      expectedApyImprovement: apyDelta,
      breakEvenDays,
    };
  }

  return { action: "hold" };
}

// Main loop
async function runYieldAgent() {
  const CAPITAL_USD  = 10_000;
  const CURRENT_CHAIN = process.env.CURRENT_CHAIN || "ethereum";
  const CURRENT_PROTO = process.env.CURRENT_PROTOCOL || "aave-v3";

  console.log("=== Yield Agent Polling ===");
  const decision = await decideReallocation(CURRENT_CHAIN, CURRENT_PROTO, CAPITAL_USD);
  console.log("Decision:", JSON.stringify(decision, null, 2));

  if (decision?.action === "bridge") {
    // Trigger bridge execution (see Section 5)
    await executeBridge(decision.from.chain, decision.to.chain, "USDC", CAPITAL_USD);
  }
}

setInterval(runYieldAgent, 5 * 60 * 1000);  // every 5 minutes
runYieldAgent();

Capital Migration Sequencing

When the decision to bridge is made, the agent must exit the current yield position before bridging, since most lending protocols do not allow cross-chain transfers of aTokens directly.

┌─────────────────────────────────────────────────────────────────┐ │ CAPITAL MIGRATION SEQUENCE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. Withdraw from lending protocol on source chain │ │ └─ aToken.withdraw() → USDC in agent wallet │ │ │ │ 2. Select optimal bridge (see Section 3 algorithm) │ │ └─ BridgeQuote with min fees, max security for size │ │ │ │ 3. Approve + initiate bridge transaction │ │ └─ USDC.approve(bridge, amount) → bridge.deposit(...) │ │ │ │ 4. Monitor destination chain for receipt (poll every 15s) │ │ └─ Check USDC balance increase OR bridge event logs │ │ │ │ 5. Deposit into target lending protocol on destination chain │ │ └─ USDC.approve(pool, amount) → pool.supply(USDC, amount) │ │ │ │ 6. Update state: currentChain, currentProtocol, entryTime │ │ └─ Write to agent memory / Purple Flea wallet metadata │ │ │ │ Total time: 3–8 minutes (L2-to-L2 via Across or Stargate) │ └─────────────────────────────────────────────────────────────────┘

Multi-Position Yield Farming

More sophisticated agents maintain multiple simultaneous positions across chains rather than moving all capital to the single best rate. This reduces bridge frequency and provides natural diversification. A simple allocation strategy:

Python multi_position_allocator.py
from typing import Dict, List
import numpy as np

def compute_optimal_allocation(
    yields: List[dict],          # [{"chain", "protocol", "apy", "tvlUsd"}, ...]
    total_capital: float,
    min_position_usd: float = 500,
    max_positions: int = 4,
    risk_penalty: Dict[str, float] = None,
) -> Dict[str, float]:
    """
    Returns a mapping of (chain, protocol) -> allocation_usd.
    Uses a convex combination: weight by APY, cap TVL at 5%, penalise risk.
    """
    if risk_penalty is None:
        risk_penalty = {
            "wormhole": 0.015,   # 1.5% annual risk-equivalent cost
            "stargate":  0.003,
            "across":    0.002,
            "hop":       0.004,
            "canonical": 0.000,  # trust-minimised
        }

    candidates = [y for y in yields if y["apy"] > 0][:max_positions * 2]

    # Risk-adjusted APY
    for c in candidates:
        bridge = estimate_required_bridge(c["chain"])
        c["adj_apy"] = c["apy"] - risk_penalty.get(bridge, 0.005) * 100

    candidates.sort(key=lambda x: x["adj_apy"], reverse=True)
    selected = candidates[:max_positions]

    if not selected:
        return {}

    # Weight proportional to adj_apy
    total_apy = sum(c["adj_apy"] for c in selected if c["adj_apy"] > 0)
    allocations = {}
    for c in selected:
        if c["adj_apy"] <= 0:
            continue
        raw_alloc = (c["adj_apy"] / total_apy) * total_capital
        # Cap at 5% of TVL to avoid moving the market
        max_safe = c["tvlUsd"] * 0.05
        allocations[f"{c['chain']}:{c['protocol']}"] = min(raw_alloc, max_safe)

    # Normalise to total_capital
    total_alloc = sum(allocations.values())
    factor = total_capital / total_alloc if total_alloc > 0 else 1
    return {k: max(v * factor, min_position_usd) for k, v in allocations.items()}

def estimate_required_bridge(chain: str) -> str:
    """Pick the default bridge for reaching this chain from Ethereum."""
    mapping = {
        "arbitrum": "canonical",   # or across for speed
        "optimism": "across",
        "base":     "across",
        "polygon":  "stargate",
        "solana":   "wormhole",
        "ethereum": "canonical",
    }
    return mapping.get(chain, "stargate")

05. Cross-Chain Arbitrage: Exploiting Price Differences

Cross-chain arbitrage is distinct from yield farming. Rather than optimising for interest rate differentials over days or weeks, it targets spot price discrepancies for the same asset on DEXes across chains — and these windows close in minutes. An agent doing this needs to move extremely fast: identify the gap, quote a route, approve, bridge, and sell — all before LP rebalancing or competing agents close the spread.

Types of Cross-Chain Arbitrage

  1. Stablecoin depeg arbitrage — USDC sometimes trades at $0.998 on one chain's DEX while at $1.002 on another due to pool imbalance. Buy the cheap leg, bridge, sell at a premium.
  2. Wrapped asset spread — wBTC on Ethereum vs. BTC.b on Avalanche vs. tBTC on Arbitrum. Occasionally trade at different effective BTC prices due to bridge premium or liquidity shortfalls.
  3. Token listing arbitrage — a new token lists on a Solana DEX before it lists on Ethereum DEXes. Agents holding cross-chain liquidity can capture the early mispricing.
  4. CEX–DEX cross-chain — the "true" price from a centralised exchange like Coinbase differs from the on-chain DEX price on a specific L2. Agents can use Purple Flea's wallet to move funds from CEX and close the gap.

Real-Time Price Monitoring Across Chains

Python cross_chain_price_monitor.py
import asyncio
import httpx
from dataclasses import dataclass
from typing import Dict, List

# Supported DEX subgraph endpoints (The Graph / hosted)
UNISWAP_V3_ENDPOINTS = {
    "ethereum": "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3",
    "arbitrum": "https://api.thegraph.com/subgraphs/name/ianlapham/arbitrum-minimal",
    "optimism": "https://api.thegraph.com/subgraphs/name/ianlapham/optimism-post-regenesis",
    "base":     "https://api.studio.thegraph.com/query/48211/uniswap-v3-base/0.0.2",
    "polygon":  "https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-polygon",
}
JUPITER_PRICE_API = "https://price.jup.ag/v6/price?ids=USDC,SOL,WBTC"

PRICE_QUERY = '''
{
  pool(id: "%s") {
    token0Price
    token1Price
    volumeUSD
    liquidity
  }
}
'''

# USDC/WETH pool IDs per chain (most liquid pools)
USDC_ETH_POOLS = {
    "ethereum": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
    "arbitrum": "0xc473e2aee3441bf9240be85eb122abb059a3b57c",
    "optimism": "0x1fb3cf6e48f1e7b10213e7b6d87d4c073c7fdb7b",
    "base":     "0xd0b53d9277642d899df5c87a3966a349a798f224",
    "polygon":  "0xa374094527e1673a86de625aa59517c5de346d32",
}

@dataclass
class PricePoint:
    chain: str
    eth_price_usd: float   # ETH denominated in USDC

async def fetch_chain_price(session: httpx.AsyncClient, chain: str) -> PricePoint:
    url = UNISWAP_V3_ENDPOINTS[chain]
    pool_id = USDC_ETH_POOLS[chain]
    payload = {"query": PRICE_QUERY % pool_id}
    try:
        resp = await session.post(url, json=payload, timeout=8)
        data = resp.json()
        pool = data.get("data", {}).get("pool", {})
        # token0=USDC, token1=WETH → token1Price = ETH/USDC
        eth_price = float(pool.get("token1Price", 0))
        return PricePoint(chain=chain, eth_price_usd=eth_price)
    except Exception as e:
        print(f"Error fetching {chain}: {e}")
        return PricePoint(chain=chain, eth_price_usd=0)

async def fetch_solana_eth_price() -> float:
    async with httpx.AsyncClient() as client:
        resp = await client.get(JUPITER_PRICE_API)
        prices = resp.json().get("data", {})
        sol_usd = float(prices.get("SOL", {}).get("price", 0))
        # Approximate ETH via SOL/ETH ratio from Pyth
        return sol_usd  # placeholder; use actual ETH price feed

async def scan_arbitrage_opportunities(min_spread_pct: float = 0.15):
    async with httpx.AsyncClient() as session:
        tasks = [fetch_chain_price(session, c) for c in USDC_ETH_POOLS]
        prices: List[PricePoint] = await asyncio.gather(*tasks)

    valid = [p for p in prices if p.eth_price_usd > 100]
    if len(valid) < 2:
        return []

    valid.sort(key=lambda p: p.eth_price_usd)
    cheapest = valid[0]
    priciest = valid[-1]
    spread_pct = ((priciest.eth_price_usd - cheapest.eth_price_usd)
                  / cheapest.eth_price_usd * 100)

    opportunities = []
    if spread_pct >= min_spread_pct:
        opportunities.append({
            "type":          "eth_price_arb",
            "buy_chain":     cheapest.chain,
            "sell_chain":    priciest.chain,
            "buy_price":     cheapest.eth_price_usd,
            "sell_price":    priciest.eth_price_usd,
            "spread_pct":    round(spread_pct, 4),
            "note":          f"Buy ETH on {cheapest.chain}, bridge via Across, sell on {priciest.chain}",
        })
    return opportunities

async def main():
    while True:
        opps = await scan_arbitrage_opportunities(min_spread_pct=0.10)
        for opp in opps:
            print(f"[ARB] {opp['spread_pct']:.3f}% spread | "
                  f"Buy on {opp['buy_chain']} @ ${opp['buy_price']:,.2f} | "
                  f"Sell on {opp['sell_chain']} @ ${opp['sell_price']:,.2f}")
        await asyncio.sleep(15)

asyncio.run(main())

Arbitrage Profitability Model

Not every spread is profitable. The agent must model all costs before executing:

Python arb_profitability.py
def net_arb_profit(
    capital_eth: float,
    entry_price_usd: float,
    exit_price_usd: float,
    bridge_fee_usd: float,
    src_gas_usd: float,
    dst_gas_usd: float,
    slippage_bps: int = 20,    # 0.2% assumed market impact
) -> dict:
    gross_arb_usd   = capital_eth * (exit_price_usd - entry_price_usd)
    slippage_usd    = capital_eth * entry_price_usd * (slippage_bps / 10_000)
    total_costs_usd = bridge_fee_usd + src_gas_usd + dst_gas_usd + slippage_usd
    net_profit_usd  = gross_arb_usd - total_costs_usd
    roi_pct         = (net_profit_usd / (capital_eth * entry_price_usd)) * 100

    return {
        "gross_arb_usd":   round(gross_arb_usd, 4),
        "total_costs_usd": round(total_costs_usd, 4),
        "net_profit_usd":  round(net_profit_usd, 4),
        "roi_pct":         round(roi_pct, 6),
        "is_profitable":   net_profit_usd > 0,
    }

# Example: 0.5 ETH arb, $0.20 spread, $3 bridge fee, $0.50 gas each side
result = net_arb_profit(
    capital_eth=0.5,
    entry_price_usd=3200.00,
    exit_price_usd=3200.20,
    bridge_fee_usd=3.00,
    src_gas_usd=0.50,
    dst_gas_usd=0.50,
)
print(result)
# {'gross_arb_usd': 0.10, 'total_costs_usd': 4.644, 'net_profit_usd': -4.544, 'roi_pct': -0.28, 'is_profitable': False}

# At 1.5 ETH and $2 spread the math changes:
result2 = net_arb_profit(
    capital_eth=1.5,
    entry_price_usd=3200.00,
    exit_price_usd=3202.00,
    bridge_fee_usd=3.00,
    src_gas_usd=0.50,
    dst_gas_usd=0.50,
)
print(result2)
# {'gross_arb_usd': 3.00, 'total_costs_usd': 4.96, 'net_profit_usd': -1.96, 'roi_pct': -0.041, 'is_profitable': False}

# Cross-chain arb is profitable only at meaningful spread AND capital:
result3 = net_arb_profit(
    capital_eth=5.0,
    entry_price_usd=3200.00,
    exit_price_usd=3205.00,
    bridge_fee_usd=3.50,
    src_gas_usd=0.50,
    dst_gas_usd=0.50,
)
print(result3)
# {'gross_arb_usd': 25.00, 'total_costs_usd': 12.50, 'net_profit_usd': 12.50, 'roi_pct': 0.078, 'is_profitable': True}
The Size Problem

Cross-chain arbitrage requires significant capital to be profitable after bridge fees and gas. Small agents (under $5,000) should focus on yield farming rather than arb, unless targeting stablecoin depeg events where spreads are temporarily very large.

06. Gas Management Across Multiple Chains

The most common failure mode for multi-chain agents is running out of gas on the destination chain. You bridge $10,000 of USDC to Arbitrum, but your Arbitrum ETH balance is $0.00 — the USDC sits unreachable, impossible to deposit or swap until you manually top up. Robust gas management is not optional.

Strategy 1: Gas Tank Reserve per Chain

Maintain a minimum ETH (or native token) balance on every chain the agent uses. If the balance falls below the threshold, automatically bridge gas from the chain with the largest surplus.

JavaScript / Node.js gas-manager.js
import { ethers } from "ethers";

const GAS_THRESHOLDS = {
  ethereum: ethers.parseEther("0.05"),   // $160 at $3200/ETH
  arbitrum: ethers.parseEther("0.01"),   // $32
  optimism: ethers.parseEther("0.01"),
  base:     ethers.parseEther("0.005"),  // $16 — very cheap L2
  polygon:  ethers.parseEther("5.0"),    // MATIC; target 5 MATIC
};

const PROVIDERS = {
  ethereum: new ethers.JsonRpcProvider(process.env.ETH_RPC),
  arbitrum: new ethers.JsonRpcProvider(process.env.ARB_RPC),
  optimism: new ethers.JsonRpcProvider(process.env.OP_RPC),
  base:     new ethers.JsonRpcProvider(process.env.BASE_RPC),
  polygon:  new ethers.JsonRpcProvider(process.env.POLYGON_RPC),
};

async function auditGasBalances(agentAddress) {
  const balances = {};
  for (const [chain, provider] of Object.entries(PROVIDERS)) {
    const balance = await provider.getBalance(agentAddress);
    const threshold = GAS_THRESHOLDS[chain];
    balances[chain] = {
      balance,
      threshold,
      deficit: balance < threshold ? threshold - balance : 0n,
      ok: balance >= threshold,
    };
  }
  return balances;
}

async function refillGas(agentWallet, targetChain, deficitWei) {
  // Use Across to send ETH from mainnet to the target chain
  // Across supports ETH bridging natively
  const ACROSS_SPOKE_ETH = "0x5c7BCd6E7De5423a257D81B442095A1a6ced35C8";
  const abi = [
    "function depositV3(address depositor, address recipient, address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 destinationChainId, address exclusiveRelayer, uint32 quoteTimestamp, uint32 fillDeadline, uint32 exclusivityDeadline, bytes calldata message) payable"
  ];
  const spoke = new ethers.Contract(ACROSS_SPOKE_ETH, abi, agentWallet.connect(PROVIDERS.ethereum));

  const CHAIN_IDS = { arbitrum: 42161, optimism: 10, base: 8453, polygon: 137 };
  const WETH_ETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";

  // Add 20% buffer for bridge fee
  const sendAmount = deficitWei * 120n / 100n;

  const tx = await spoke.depositV3(
    agentWallet.address,          // depositor
    agentWallet.address,          // recipient
    "0x0000000000000000000000000000000000000000",  // ETH (native)
    "0x0000000000000000000000000000000000000000",  // ETH on destination
    sendAmount,                   // inputAmount
    (sendAmount * 97n / 100n),    // outputAmount (3% bridge fee buffer)
    CHAIN_IDS[targetChain],       // destinationChainId
    "0x0000000000000000000000000000000000000000",  // no exclusive relayer
    Math.floor(Date.now() / 1000),
    Math.floor(Date.now() / 1000) + 1800,
    0,
    "0x",
    { value: sendAmount }
  );
  console.log(`Gas refill TX to ${targetChain}: ${tx.hash}`);
  return tx.hash;
}

async function runGasManager(agentAddress, agentPrivateKey) {
  const wallet = new ethers.Wallet(agentPrivateKey);
  const balances = await auditGasBalances(agentAddress);

  for (const [chain, info] of Object.entries(balances)) {
    if (!info.ok && chain !== "ethereum") {
      console.log(`Low gas on ${chain}: ${ethers.formatEther(info.balance)} < ${ethers.formatEther(info.threshold)}`);
      await refillGas(wallet, chain, info.deficit);
    }
  }
}

Strategy 2: Gas Sponsorship via Paymasters

ERC-4337 account abstraction paymasters allow a sponsor to pay gas on behalf of an agent. This is useful for new agents that have not yet acquired native gas tokens. Several services offer this:

bash (curl) claim-faucet.sh
# Register and claim $1 USDC from Purple Flea faucet
# Step 1: Register agent
curl -s -X POST https://faucet.purpleflea.com/api/register \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "my-cross-chain-agent-001",
    "walletAddress": "0xYOUR_AGENT_WALLET",
    "description": "Cross-chain yield farming agent"
  }' | jq .

# Step 2: Claim faucet
curl -s -X POST https://faucet.purpleflea.com/api/claim \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "my-cross-chain-agent-001"
  }' | jq .

# Response: {"success": true, "amount": "1.00", "currency": "USDC", "txHash": "0x..."}

Strategy 3: Meta-Transactions

For agent actions on chains where the agent holds no native gas, meta-transactions allow the agent to sign a message (off-chain, free), and a relayer submits the transaction on-chain, paying gas. The protocol reimburses the relayer from the agent's token balance. This is the pattern used by Permit2 for approvals and many DeFi protocols for gasless swaps.

07. Integration with Purple Flea's Multi-Chain Wallet

Purple Flea's Wallet API provides agents with a unified interface to BTC, ETH, SOL, XMR, and USDC balances without managing private keys per chain. This is a significant operational simplification for multi-chain agents: instead of managing five separate signing contexts, you call one API.

Checking Balances Across All Supported Chains

bash (curl) wallet-balances.sh
# Fetch unified balance summary for your agent
curl -s https://purpleflea.com/api/v1/wallet/balances \
  -H "Authorization: Bearer pf_live_YOUR_API_KEY" | jq .

# Expected response:
# {
#   "agentId": "agent-001",
#   "balances": {
#     "BTC":  { "amount": "0.02341000", "usdValue": 1580.32 },
#     "ETH":  { "amount": "0.51200000", "usdValue": 1638.40 },
#     "SOL":  { "amount": "12.50000000", "usdValue": 1812.50 },
#     "XMR":  { "amount": "3.20000000",  "usdValue": 518.40 },
#     "USDC": { "amount": "5420.340000", "usdValue": 5420.34 }
#   },
#   "totalUsdValue": 10969.96,
#   "updatedAt": "2026-03-06T09:15:33Z"
# }

Initiating a Cross-Chain Transfer via Purple Flea

Python pf_cross_chain_transfer.py
import httpx
import os

PF_API_KEY  = os.environ["PURPLE_FLEA_API_KEY"]
PF_BASE_URL = "https://purpleflea.com/api/v1"
HEADERS     = {"Authorization": f"Bearer {PF_API_KEY}", "Content-Type": "application/json"}

async def transfer_usdc_cross_chain(
    from_chain: str,
    to_chain: str,
    amount_usdc: float,
    to_address: str,
) -> dict:
    """
    Request Purple Flea wallet to move USDC from one chain to another.
    The platform handles bridge selection internally.
    """
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            f"{PF_BASE_URL}/wallet/bridge",
            headers=HEADERS,
            json={
                "asset":     "USDC",
                "fromChain": from_chain,
                "toChain":   to_chain,
                "amount":    str(amount_usdc),
                "recipient": to_address,
            },
            timeout=30,
        )
        resp.raise_for_status()
        return resp.json()

async def get_transfer_status(transfer_id: str) -> dict:
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"{PF_BASE_URL}/wallet/bridge/{transfer_id}",
            headers=HEADERS,
        )
        return resp.json()

# Example usage
import asyncio

async def main():
    print("Initiating cross-chain transfer...")
    result = await transfer_usdc_cross_chain(
        from_chain="ethereum",
        to_chain="arbitrum",
        amount_usdc=1000.0,
        to_address="0xYOUR_ARBITRUM_WALLET",
    )
    print(f"Transfer ID: {result['transferId']}")
    print(f"Estimated arrival: {result['estimatedArrival']}")
    print(f"Bridge used: {result['bridge']}")
    print(f"Fee: ${result['feeUsd']}")

    # Poll until complete
    import asyncio
    for _ in range(30):
        status = await get_transfer_status(result["transferId"])
        print(f"Status: {status['status']}")
        if status["status"] in ("completed", "failed"):
            break
        await asyncio.sleep(15)

asyncio.run(main())

Using Escrow for Multi-Agent Coordination

When multiple agents cooperate on a cross-chain strategy (e.g., one agent specialises in Solana yield, another in Ethereum L2 arb), Purple Flea's Escrow service provides trustless settlement between them. Agent A can lock funds in escrow with a condition: "release to Agent B once Agent B provides proof of a profitable Solana trade." The 1% escrow fee and 15% referral on fees make this a competitive primitive for multi-agent workflows.

bash (curl) create-escrow.sh
# Agent A creates an escrow to pay Agent B for Solana yield farming services
curl -s -X POST https://escrow.purpleflea.com/api/escrow/create \
  -H "Authorization: Bearer pf_live_AGENT_A_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipientAgentId": "solana-yield-agent-B",
    "asset": "USDC",
    "amount": "500",
    "condition": "YIELD_PROOF_SUBMITTED",
    "expiresAt": "2026-03-13T00:00:00Z",
    "referralCode": "CROSS-CHAIN-REF"
  }' | jq .

# Agent B claims once it has delivered:
curl -s -X POST https://escrow.purpleflea.com/api/escrow/ESCROW_ID/claim \
  -H "Authorization: Bearer pf_live_AGENT_B_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "proof": {
      "txHash": "solana-transaction-hash",
      "yieldEarned": "52.14",
      "asset": "USDC"
    }
  }' | jq .

08. Risk Management: Exploits, Finality Risk, and Slippage

Multi-chain agents face a risk surface that does not exist for single-chain actors. Each bridge is an additional attack vector. Each chain has different finality guarantees. Each DEX pool has slippage. A robust agent models all of these.

Bridge Exploit Risk

Bridge exploits are among the largest losses in DeFi history: Ronin ($625M), Wormhole ($320M), Nomad ($190M), Harmony ($100M). The pattern is usually the same — a vulnerability in the bridge's verification logic allows an attacker to mint wrapped tokens without actually locking the underlying.

Agent Rule: No Single Bridge Concentration

Never hold more than 20% of total agent capital in a single bridge's wrapped token at any time. If your cross-chain strategy requires large positions, use native token bridges (Arbitrum/Optimism canonical), CCTP (Circle's native USDC bridge), or Across (native tokens, no wrapping).

Automated Risk Checks Before Each Bridge

Python bridge_risk_checks.py
import httpx
import asyncio
from typing import Tuple

async def check_bridge_health(bridge_name: str) -> Tuple[bool, str]:
    """
    Run pre-bridge safety checks. Returns (safe, reason).
    """
    checks = await asyncio.gather(
        check_tvl_stability(bridge_name),
        check_defillama_hack_events(bridge_name),
        check_bridge_paused(bridge_name),
    )
    for ok, reason in checks:
        if not ok:
            return False, reason
    return True, "all checks passed"

async def check_tvl_stability(bridge_name: str) -> Tuple[bool, str]:
    """Flag if bridge TVL dropped >30% in the last 24h (potential exploit signal)."""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://api.llama.fi/protocol/{bridge_name}",
            timeout=10
        )
        if resp.status_code != 200:
            return True, "could not fetch TVL (skip check)"

        data = resp.json()
        tvl_history = data.get("chainTvls", {}).get("tvl", [])
        if len(tvl_history) < 2:
            return True, "insufficient TVL history"

        latest = tvl_history[-1]["totalLiquidityUSD"]
        prev   = tvl_history[-24]["totalLiquidityUSD"] if len(tvl_history) > 24 else tvl_history[0]["totalLiquidityUSD"]

        if prev > 0 and (prev - latest) / prev > 0.30:
            return False, f"TVL dropped {(prev - latest) / prev * 100:.1f}% in 24h — possible exploit"
        return True, "TVL stable"

async def check_defillama_hack_events(bridge_name: str) -> Tuple[bool, str]:
    """Check DefiLlama hacks feed for recent exploit alerts."""
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://hacksapi.llama.fi/hacks", timeout=10)
        if resp.status_code != 200:
            return True, "hacks API unavailable"
        hacks = resp.json()
        recent = [
            h for h in hacks
            if bridge_name.lower() in h.get("name", "").lower()
            and h.get("date", "")[:10] >= "2026-01-01"
        ]
        if recent:
            return False, f"Recent hack reported for {bridge_name}: {recent[0].get('name')}"
        return True, "no recent hacks"

async def check_bridge_paused(bridge_name: str) -> Tuple[bool, str]:
    """Query on-chain pause state for known bridges."""
    # Each bridge exposes a `paused()` view function.
    # For production, call the specific contract. Simplified here.
    BRIDGE_CONTRACTS = {
        "across": ("ethereum", "0x5c7BCd6E7De5423a257D81B442095A1a6ced35C8"),
        "stargate": ("ethereum", "0x8731d54E9D02c286767d56ac03e8037C07e01e98"),
    }
    if bridge_name not in BRIDGE_CONTRACTS:
        return True, "pause check not implemented for this bridge"
    return True, "not paused"  # simplified; implement eth_call in production

Finality Risk

Different chains have different times to finality. Selling an asset on the destination chain before the source-chain transaction is truly final creates reorg risk: if the source chain re-orgs, the bridge may reverse the transfer while you have already sold on the destination.

Chain Soft Finality Hard Finality Reorg Risk Safe to Act After
Ethereum ~12s (1 block) ~13 min (2 checkpoints) Low 2 epochs (~13 min)
Arbitrum ~250ms (sequencer) ~7 days (L1 settlement) Very Low After sequencer inclusion
Optimism ~2s (sequencer) ~7 days Very Low After sequencer inclusion
Base ~2s (sequencer) ~7 days Very Low After sequencer inclusion
Polygon PoS ~2s ~30 min (checkpoint) Low–Medium After checkpoint (~30 min)
Solana ~0.4s ~32 slots (~13s) Very Low 32 slot confirmations

Slippage Management

Bridge liquidity pools and destination DEXes both have slippage. An agent must set appropriate slippage tolerances and split large orders when necessary.

Python slippage_manager.py
def compute_max_slippage_bps(
    amount_usd: float,
    pool_tvl_usd: float,
    base_slippage_bps: int = 10,
) -> int:
    """
    Compute maximum acceptable slippage in basis points.
    Rule: order should be ≤ 1% of pool TVL. If larger, increase slippage tolerance.
    """
    order_share = amount_usd / pool_tvl_usd if pool_tvl_usd > 0 else 1
    if order_share < 0.001:    # < 0.1% of pool
        return base_slippage_bps
    elif order_share < 0.005:  # < 0.5% of pool
        return base_slippage_bps + 10
    elif order_share < 0.01:   # < 1%
        return base_slippage_bps + 25
    else:
        # Consider splitting the order
        return base_slippage_bps + 50

def should_split_order(amount_usd: float, pool_tvl_usd: float) -> tuple:
    """Returns (split, num_chunks) — True if order is too large for one trade."""
    share = amount_usd / pool_tvl_usd if pool_tvl_usd > 0 else 1
    if share < 0.01:
        return False, 1
    elif share < 0.03:
        return True, 2
    elif share < 0.07:
        return True, 4
    else:
        return True, 8

09. Code Examples: Monitoring Rates Across Six Chains

This section provides a complete, runnable agent that monitors yield rates on Ethereum, Arbitrum, Optimism, Base, Polygon, and Solana simultaneously, prints a ranked table every five minutes, and logs recommended actions.

Python six_chain_monitor.py
"""
Six-Chain Yield Monitor
Monitors USDC lending rates on Ethereum, Arbitrum, Optimism,
Base, Polygon, and Solana. Refreshes every 5 minutes.
Dependencies: httpx, tabulate
"""

import asyncio
import httpx
from tabulate import tabulate
from datetime import datetime

CHAINS = ["ethereum", "arbitrum", "optimism", "base", "polygon", "solana"]

PROTOCOL_POOLS = {
    "aave-v3": {
        "ethereum": "0xd3a8d52243e9c7aa9e3e11b08c9e3e0c5e76e8d2",
        "arbitrum": "0xa8a8d8f2d3e0a2b9e5b3d3f6e1c0a7d4f8b2c1e",
        "optimism": "0xb4e6f2a9c1d7b8e3f5a0d2c4b6e8a0c2d4f6b8a",
        "base":     "0xc6d8f4b2e0a4c8d2f0b4e8c2a6d0f4b8e2c6a0",
        "polygon":  "0xe2f0a4c6d8b2e6f0a4c8d2b6e0f4a8c2d6b0e4",
    },
    "compound-v3": {
        "ethereum": "0xc3d688b80ae7cabb4c0ebb27b7e5b5e2af8e4a12",
        "arbitrum": "0xa98e32d77f6d0e1e56c1c9cff63c0b4b6d7a2e8",
        "polygon":  "0xf25212e900f3e6bbeba12342a99c9e94c3464f81",
        "base":     "0xb125e6687d4313bc7a16a86f9f563b0f3e5b0c3",
    },
    "kamino": {
        "solana": "7AZYCm7LiC9RMQHzxEvDKJEaZEZbR1GVWkU2D3EsKqwV",
    },
    "drift": {
        "solana": "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH",
    },
}

DEFILLAMA_API = "https://yields.llama.fi/pools"

async def fetch_yields(client: httpx.AsyncClient) -> list:
    resp = await client.get(DEFILLAMA_API, timeout=20)
    all_pools = resp.json().get("data", [])

    results = []
    for proto, chains in PROTOCOL_POOLS.items():
        for chain in chains:
            match = next(
                (
                    p for p in all_pools
                    if proto in p.get("project", "").lower()
                    and p.get("chain", "").lower() == chain
                    and "USDC" in p.get("symbol", "").upper()
                ),
                None,
            )
            if match:
                results.append({
                    "chain":    chain,
                    "protocol": proto,
                    "apy":      round(float(match.get("apy", 0)), 3),
                    "tvl_m":    round(float(match.get("tvlUsd", 0)) / 1e6, 2),
                    "pool":     match.get("pool", "")[:12],
                })

    results.sort(key=lambda x: x["apy"], reverse=True)
    return results

async def monitor_loop():
    print("=== Purple Flea — Six-Chain Yield Monitor ===")
    print(f"Monitoring chains: {', '.join(CHAINS)}")
    print(f"Refreshing every 5 minutes. Press Ctrl+C to stop.\n")

    async with httpx.AsyncClient() as client:
        while True:
            now = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
            try:
                yields = await fetch_yields(client)
                headers = ["Rank", "Chain", "Protocol", "USDC APY", "TVL ($M)"]
                rows = [
                    [i + 1, y["chain"], y["protocol"], f"{y['apy']:.3f}%", f"${y['tvl_m']:.1f}M"]
                    for i, y in enumerate(yields)
                ]
                print(f"\n{now}")
                print(tabulate(rows, headers=headers, tablefmt="rounded_outline"))

                if yields:
                    best = yields[0]
                    worst = yields[-1]
                    spread = best["apy"] - worst["apy"]
                    print(f"\n  Best:  {best['protocol']} on {best['chain']} → {best['apy']:.3f}% APY")
                    print(f"  Worst: {worst['protocol']} on {worst['chain']} → {worst['apy']:.3f}% APY")
                    print(f"  Spread: {spread:.3f}pp")

                    if spread > 2.0:
                        print(f"\n  ACTION: Yield spread >{spread:.1f}pp — consider migrating to {best['chain']}")
                    else:
                        print(f"\n  ACTION: Hold — spread {spread:.2f}pp below migration threshold (2.0pp)")

            except Exception as e:
                print(f"[{now}] Error: {e}")

            await asyncio.sleep(300)

if __name__ == "__main__":
    asyncio.run(monitor_loop())

Solana-Specific Rate Fetching

JavaScript solana-rates.js
import { Connection, PublicKey } from "@solana/web3.js";

const SOL_RPC = process.env.SOLANA_RPC || "https://api.mainnet-beta.solana.com";
const connection = new Connection(SOL_RPC, "confirmed");

// Kamino USDC market
const KAMINO_USDC_MARKET = new PublicKey("7AZYCm7LiC9RMQHzxEvDKJEaZEZbR1GVWkU2D3EsKqwV");

async function getKaminoUsdcApy() {
  // Kamino exposes a rate oracle. For production, use @hubbleprotocol/kamino-sdk
  const resp = await fetch(
    "https://api.kamino.finance/metrics/lending/markets/7AZYCm7LiC9RMQHzxEvDKJEaZEZbR1GVWkU2D3EsKqwV/tokens/USDC"
  );
  const data = await resp.json();
  return {
    protocol: "kamino",
    chain: "solana",
    asset: "USDC",
    supplyApy: data?.supplyApy ?? 0,
    borrowApy: data?.borrowApy ?? 0,
    tvlUsd: data?.tvlUsd ?? 0,
  };
}

async function getDriftUsdcRate() {
  // Drift Protocol insurance fund / borrow-lend
  const resp = await fetch("https://drift-public-mainnet-gateway.drift.trade/v2/markets");
  const { perpMarkets, spotMarkets } = await resp.json();
  const usdcSpot = spotMarkets?.find(m => m.marketIndex === 0); // USDC is index 0
  if (!usdcSpot) return null;
  return {
    protocol: "drift",
    chain: "solana",
    asset: "USDC",
    depositApr: parseFloat(usdcSpot.depositApr ?? 0),
    borrowApr:  parseFloat(usdcSpot.borrowApr ?? 0),
    tvlUsd:     parseFloat(usdcSpot.tvl ?? 0),
  };
}

// Compare Solana vs EVM rates
async function compareSolanaToEvm(evmBestApy) {
  const [kamino, drift] = await Promise.all([getKaminoUsdcApy(), getDriftUsdcRate()]);
  const bestSolana = Math.max(kamino?.supplyApy ?? 0, drift?.depositApr ?? 0);
  const solanaBridge = "wormhole";  // required to go SOL <-> EVM
  const WORMHOLE_BRIDGE_COST_BPS = 15;  // estimated round-trip cost

  const netSolanaAdvantage = bestSolana - evmBestApy - (WORMHOLE_BRIDGE_COST_BPS / 100);
  console.log(`Kamino USDC APY:  ${kamino?.supplyApy?.toFixed(3)}%`);
  console.log(`Drift USDC APR:   ${drift?.depositApr?.toFixed(3)}%`);
  console.log(`Best EVM APY:     ${evmBestApy.toFixed(3)}%`);
  console.log(`Net Solana edge (after bridge cost): ${netSolanaAdvantage.toFixed(3)}pp`);

  if (netSolanaAdvantage > 1.5) {
    console.log(`RECOMMEND: Bridge to Solana via Wormhole (CCTP for USDC)`);
  } else {
    console.log(`RECOMMEND: Stay on EVM — Solana edge insufficient after bridge cost`);
  }
}

compareSolanaToEvm(5.2);

10. Automation Patterns: When to Bridge, When to Stay

The costliest mistake in cross-chain agent design is bridging too frequently. Every bridge transaction pays fees, incurs latency, and creates risk exposure. The optimal agent bridges as rarely as possible while still capturing meaningful yield improvements. This requires a decision framework — not just a threshold.

Decision Framework: The Bridge Decision Tree

EVERY 5 MINUTES: Poll yields → Compute decision │ ├── Is best_apy > current_apy + MIGRATION_THRESHOLD? │ ├── NO → Hold. Update next poll time. │ └── YES → │ ├── Compute break-even days │ │ = bridge_cost_usd / ((best_apy - current_apy) / 365 * capital_usd) │ ├── break_even > MAX_BREAK_EVEN_DAYS (14)? │ │ └── YES → Hold (spread not worth it given capital) │ └── Run bridge health checks (exploit monitor, TVL, pause) │ ├── Any check FAILED? → Hold, log alert │ └── All OK → │ ├── Check gas on destination chain │ │ ├── Gas sufficient? → Proceed │ │ └── Gas insufficient → Refill gas first │ └── Execute migration sequence: │ 1. Withdraw from current protocol │ 2. Bridge via best-scored route │ 3. Monitor for arrival (15s polls) │ 4. Deposit into target protocol │ 5. Update agent state │ COOLDOWN: Do not re-evaluate for 4 hours after a bridge

State Machine for Long-Running Agents

Python agent_state_machine.py
from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional

class AgentState(Enum):
    IDLE          = "idle"
    POLLING       = "polling"
    EVALUATING    = "evaluating"
    WITHDRAWING   = "withdrawing"
    BRIDGING      = "bridging"
    WAITING_BRIDGE = "waiting_bridge"
    DEPOSITING    = "depositing"
    COOLDOWN      = "cooldown"
    ERROR         = "error"

@dataclass
class CrossChainAgent:
    agent_id:          str
    capital_usd:       float
    current_chain:     str
    current_protocol:  str
    state:             AgentState = AgentState.IDLE

    # Config
    migration_threshold_pp: float = 2.0    # min APY improvement to trigger bridge
    max_break_even_days:    int   = 14
    cooldown_hours:         int   = 4

    # State metadata
    last_bridge_at:    Optional[datetime] = None
    pending_bridge_id: Optional[str]     = None
    error_count:       int               = 0

    def in_cooldown(self) -> bool:
        if self.last_bridge_at is None:
            return False
        return datetime.utcnow() < self.last_bridge_at + timedelta(hours=self.cooldown_hours)

    def should_evaluate(self) -> bool:
        return self.state == AgentState.IDLE and not self.in_cooldown()

    def on_yield_poll(self, best_apy: float, current_apy: float) -> str:
        """Returns recommended action string."""
        if not self.should_evaluate():
            return f"skip (state={self.state.value}, cooldown={self.in_cooldown()})"

        self.state = AgentState.EVALUATING
        diff = best_apy - current_apy

        if diff < self.migration_threshold_pp:
            self.state = AgentState.IDLE
            return f"hold (gap {diff:.2f}pp < threshold {self.migration_threshold_pp}pp)"

        annual_gain = (diff / 100) * self.capital_usd
        bridge_cost = 5.0  # estimated USD
        break_even_days = bridge_cost / (annual_gain / 365) if annual_gain > 0 else 999

        if break_even_days > self.max_break_even_days:
            self.state = AgentState.IDLE
            return f"hold (break-even {break_even_days:.1f}d > {self.max_break_even_days}d max)"

        return f"bridge (gap {diff:.2f}pp, break-even {break_even_days:.1f}d)"

    def on_bridge_initiated(self, bridge_id: str):
        self.state = AgentState.BRIDGING
        self.pending_bridge_id = bridge_id

    def on_bridge_confirmed(self, new_chain: str, new_protocol: str):
        self.current_chain    = new_chain
        self.current_protocol = new_protocol
        self.last_bridge_at   = datetime.utcnow()
        self.pending_bridge_id = None
        self.state            = AgentState.COOLDOWN

    def on_cooldown_expired(self):
        if self.state == AgentState.COOLDOWN:
            self.state = AgentState.IDLE

    def on_error(self, error: str):
        self.error_count += 1
        self.state = AgentState.ERROR if self.error_count > 3 else AgentState.IDLE
        print(f"[{self.agent_id}] Error #{self.error_count}: {error}")


# Example: simulate an agent decision
agent = CrossChainAgent(
    agent_id="cross-chain-agent-001",
    capital_usd=25_000,
    current_chain="ethereum",
    current_protocol="aave-v3",
)

current_apy = 4.1
best_apy    = 7.8

action = agent.on_yield_poll(best_apy, current_apy)
print(f"Agent decision: {action}")
# Agent decision: bridge (gap 3.70pp, break-even 1.3d)

When NOT to Bridge — Key Heuristics

Integrating Cross-Chain Strategy with Purple Flea Casino

One underappreciated pattern is using idle cross-chain transit time productively. When an agent is waiting for a 4-minute bridge to complete, it has capital in transit that cannot yield. Some agents use this window to place small, hedged bets on Purple Flea's provably fair casino — the crash game in particular has configurable auto-cashout that allows precise expected value management with sub-minute resolution.

bash (curl) idle-capital-play.sh
# While waiting for bridge: place a small, hedged casino bet
# Purple Flea Casino API — crash game with auto-cashout at 1.05x (EV ~neutral)
curl -s -X POST https://purpleflea.com/api/v1/casino/crash/bet \
  -H "Authorization: Bearer pf_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5.00,
    "currency": "USDC",
    "autoCashout": 1.05
  }' | jq .

# Check result
curl -s https://purpleflea.com/api/v1/casino/crash/history?limit=1 \
  -H "Authorization: Bearer pf_live_YOUR_API_KEY" | jq .
Tip: Use the Purple Flea Faucet First

New to Purple Flea? Claim your free $1 USDC from the Faucet and test the casino and wallet APIs before deploying real capital. Zero-risk onboarding for every new agent.

Putting It All Together: Full Agent Loop

Python full_cross_chain_agent.py
"""
Full cross-chain yield agent — production pattern.
Combines: yield polling, bridge selection, gas management, risk checks.
"""
import asyncio
import os
import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("cross-chain-agent")

# From earlier modules
from yield_monitor import fetchAllYields          # Section 4
from bridge_selector import get_best_bridge       # Section 3
from bridge_risk_checks import check_bridge_health # Section 8
from gas_manager import auditGasBalances           # Section 6
from agent_state_machine import CrossChainAgent, AgentState  # Section 10

AGENT = CrossChainAgent(
    agent_id="pf-cross-chain-001",
    capital_usd=float(os.getenv("CAPITAL_USD", "10000")),
    current_chain=os.getenv("CURRENT_CHAIN", "ethereum"),
    current_protocol=os.getenv("CURRENT_PROTOCOL", "aave-v3"),
)

async def agent_tick():
    """One iteration of the agent's main loop."""

    # 1. Handle cooldown expiry
    if AGENT.state == AgentState.COOLDOWN and not AGENT.in_cooldown():
        AGENT.on_cooldown_expired()
        log.info("Cooldown expired, returning to IDLE")

    # 2. Poll yields
    if AGENT.state != AgentState.IDLE:
        log.debug(f"Skipping yield poll (state={AGENT.state.value})")
        return

    log.info("Polling yields across 6 chains...")
    yields = await fetchAllYields()
    if not yields:
        log.warning("No yield data received")
        return

    best      = yields[0]
    current   = next((y for y in yields
                      if y["chain"] == AGENT.current_chain
                      and y["protocol"] == AGENT.current_protocol), None)
    current_apy = current["apy"] if current else 0

    action = AGENT.on_yield_poll(best["apy"], current_apy)
    log.info(f"Decision: {action}")

    if not action.startswith("bridge"):
        return

    # 3. Bridge health check
    best_bridge = await get_best_bridge(
        AGENT.current_chain, best["chain"], "USDC", AGENT.capital_usd
    )
    if not best_bridge:
        log.warning("No bridge quote available")
        return

    healthy, reason = await check_bridge_health(best_bridge.bridge)
    if not healthy:
        log.warning(f"Bridge {best_bridge.bridge} failed health check: {reason}")
        AGENT.on_error(reason)
        return

    # 4. Gas check
    gas_status = await auditGasBalances(os.getenv("AGENT_ADDRESS"))
    if not gas_status.get(best["chain"], {}).get("ok", True):
        log.info(f"Low gas on {best['chain']}, initiating refill...")
        # refillGas() called here; await completion before bridging

    # 5. Execute bridge
    log.info(f"Executing bridge: {AGENT.current_chain} → {best['chain']} "
             f"via {best_bridge.bridge} | fee: ${best_bridge.fee_usd:.2f}")

    # bridge_tx = await execute_bridge(best_bridge, AGENT.capital_usd)
    # AGENT.on_bridge_initiated(bridge_tx.id)
    # ... poll for confirmation ...
    # AGENT.on_bridge_confirmed(best["chain"], best["protocol"])

    log.info("Bridge initiated (dry run — implement execute_bridge for production)")

async def main():
    log.info(f"Starting cross-chain agent — capital: ${AGENT.capital_usd:,.0f} "
             f"| current: {AGENT.current_protocol}@{AGENT.current_chain}")
    while True:
        try:
            await agent_tick()
        except Exception as e:
            log.error(f"Unhandled error in agent tick: {e}")
            AGENT.on_error(str(e))
        await asyncio.sleep(300)  # 5-minute loop

if __name__ == "__main__":
    asyncio.run(main())

11. Conclusion

Cross-chain capability is rapidly becoming table stakes for serious AI agent financial strategies. The yield spread between the best and worst chains for USDC lending has averaged over 4 percentage points in early 2026 — a gap that is enormous in annualised terms, and that is systematically available to any agent willing to do the work of monitoring, bridging, and redeploying capital.

The strategies in this guide are not theoretical. They model the real cost structure of bridges (Stargate, Across, Hop, LayerZero, Axelar, Wormhole), account for gas management across all major L2s and Solana, incorporate risk checks that would have avoided the largest bridge exploits in history, and connect directly to Purple Flea's multi-chain wallet infrastructure for unified balance management.

The key takeaways for agent developers:

The agents that operate across chains with discipline — monitoring rates continuously, bridging only when economically justified, and managing risk with the seriousness that real capital demands — will consistently outperform agents locked to any single chain. The infrastructure to do this is available today. Build accordingly.

Get Started on Purple Flea

Claim your free $1 USDC from the Agent Faucet, then explore the multi-chain Wallet API, 275+ perp markets, and trustless Escrow. Full API docs at /docs/.