Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials

Build a Cross-Chain DeFi Application

DeFiAdvanced40 min

Build a cross-chain DeFi application that bridges tokens across Ethereum, Arbitrum, Base, and Solana using four bridge adapters — LayerZero V2, Chainlink CCIP, deBridge DLN, and LI.FI. Compare routes and fees in real time, execute optimal cross-chain swaps, track bridge status, and aggregate yield opportunities across chains. Includes both Rust and TypeScript SDK examples.

What You'll Build

  • Multi-adapter bridge route comparison (LayerZero, CCIP, deBridge, LI.FI)
  • Optimal cross-chain token transfers with fee minimization
  • Real-time bridge status tracking with polling
  • Cross-chain DEX swap execution (bridge + swap in one flow)
  • Yield aggregation across chains with automatic allocation
  • Settlement of profits on the Tenzro Ledger

Bridge Adapters Used

  • LayerZero V2 — EndpointV2 omnichain messaging, OFT transfers, 130+ chains
  • Chainlink CCIP — Router.ccipSend() cross-chain messaging with token transfers
  • deBridge DLN — Intent-based cross-chain swaps, order creation API
  • LI.FI — Bridge aggregator covering 30+ bridges and 66+ chains

Prerequisites

[dependencies]
tenzro-sdk = "0.1"
tokio = { version = "1", features = ["full"] }
tracing-subscriber = "0.3"
uuid = { version = "1", features = ["v4"] }

Or for TypeScript:

npm install @tenzro/sdk

Step 1: Connect and Create Identity

use tenzro_sdk::{TenzroClient, SettlementRequest, config::SdkConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let config = SdkConfig::testnet();
    let client = TenzroClient::connect(config).await?;

    // Register identity and create wallet
    let identity = client.identity().register_human("Cross-Chain DeFi Builder").await?;
    let wallet = client.wallet().create_wallet().await?;
    println!("DID: {}  Wallet: {}", identity.did, wallet.address);
TypeScript version
import { TenzroClient } from "@tenzro/sdk";

const client = new TenzroClient({ network: "testnet" });

// Register identity and create wallet
const identity = await client.identity.registerHuman("Cross-Chain DeFi Builder");
const wallet = await client.wallet.createWallet();
console.log(`DID: ${identity.did}  Wallet: ${wallet.address}`);

Step 2: Compare Bridge Routes and Fees

Before bridging, query all available adapters for routes and fees. The get_routes method returns results sorted by fee ascending, so the first entry is always the cheapest option.

// Compare bridge routes from Ethereum to Arbitrum
let routes = client.bridge().get_routes(
    "ethereum",     // source chain
    "arbitrum",     // destination chain
    "USDC",         // token
    "500000000",    // 500 USDC (6 decimals)
).await?;

for route in &routes {
    println!(
        "  {} -- fee: {} USD, time: {}s",
        route.adapter, route.fee_usd_estimate, route.estimated_time_seconds,
    );
}
  layerzero -- fee: 0.42 USD, time: 120s
  chainlink_ccip -- fee: 0.68 USD, time: 180s
  debridge -- fee: 0.35 USD, time: 90s
TypeScript version
// Compare bridge routes (TypeScript SDK)
const routes = await client.bridge.getRoutes({
  sourceChain: "ethereum",
  destinationChain: "arbitrum",
  token: "USDC",
  amount: "500000000",
});

for (const route of routes) {
  console.log(
    `  ${route.adapter} -- fee: ${route.feeUsdEstimate} USD, time: ${route.estimatedTimeSeconds}s`
  );
}

Fee sources. LayerZero fees come from a real EndpointV2.quote() eth_call. CCIP fees come from Router.getFee(). deBridge fees come from the DLN order-creation API. LI.FI fees come from the /v1/quote REST endpoint. All fall back to static estimates if the RPC call fails.

Step 3: Execute the Bridge Transfer

Select the cheapest adapter and execute the transfer. The SDK handles calldata encoding, fee payment, and transaction submission.

// Execute the cheapest bridge transfer
let bridge_result = client.bridge().bridge_tokens(
    "ethereum",             // source
    "arbitrum",             // destination
    "USDC",                 // token
    "500000000",            // amount
    &wallet.address,        // recipient
    Some("debridge"),       // adapter (cheapest from comparison)
).await?;

println!("Bridge tx: {}", bridge_result.transaction_hash);
println!("Order ID:  {}", bridge_result.order_id);
Bridge tx: 0x8a3f...c4d1
Order ID:  ord-a1b2c3d4-e5f6-7890

Step 4: Track Bridge Status

Bridge transfers are not instant. Poll the status endpoint until the transfer is fulfilled on the destination chain. Each adapter uses its own tracking mechanism: LayerZero uses the Scan API, CCIP uses OffRamp.getExecutionState(), and deBridge uses the stats API.

// Track bridge transfer status
loop {
    let status = client.bridge().get_status(&bridge_result.order_id).await?;
    println!("Status: {:?}", status.state);

    match status.state.as_str() {
        "Fulfilled" | "Completed" => {
            println!("Bridge complete! Dest tx: {}", status.destination_tx.unwrap_or_default());
            break;
        }
        "Failed" | "Cancelled" => {
            eprintln!("Bridge failed: {}", status.error.unwrap_or_default());
            break;
        }
        _ => {
            tokio::time::sleep(std::time::Duration::from_secs(15)).await;
        }
    }
}

Step 5: Cross-Chain Swap (Bridge + DEX)

A cross-chain swap combines a bridge transfer with a DEX trade on the destination chain. Bridge ETH from Arbitrum to Base, then swap into USDC on a Base DEX:

// Execute a cross-chain swap: sell ETH on Arbitrum, receive USDC on Base
// Step 1: Get the best route across all adapters
let swap_routes = client.bridge().get_routes("arbitrum", "base", "ETH", "100000000000000000").await?;
let best = &swap_routes[0]; // sorted by fee ascending
println!("Best route: {} (fee: {} USD)", best.adapter, best.fee_usd_estimate);

// Step 2: Execute the bridge
let swap_result = client.bridge().bridge_tokens(
    "arbitrum", "base", "ETH", "100000000000000000",
    &wallet.address, Some(&best.adapter),
).await?;

// Step 3: Once bridged, swap ETH -> USDC on Base DEX
let agent = client.agent().register(
    "defi-swap-agent", "Cross-Chain Swap Agent", &["defi", "swap"],
).await?;

let dex_swap = client.agent().send_message(
    &agent.agent_id,
    &format!(
        "Use eth_encode_function tool: \
         function_signature=swapExactETHForTokens(uint256,address[],address,uint256), \
         args=[\"0\", \"[\\\"0xWETH\\\", \\\"0xUSDC\\\"]\", \"{}\", \"999999999\"]",
        wallet.address,
    ),
).await?;
println!("DEX swap calldata: {}", dex_swap.payload);

Step 6: Yield Aggregation Across Chains

Scan yield opportunities across multiple chains, rank by APY, and automatically bridge capital to the best opportunity. The agent handles route selection and bridge execution.

// Yield aggregation: compare APY across chains and allocate
struct YieldOpportunity {
    chain: String,
    protocol: String,
    apy: f64,
    tvl_usd: f64,
}

let opportunities = vec![
    YieldOpportunity { chain: "ethereum".into(), protocol: "Aave V3".into(), apy: 3.2, tvl_usd: 5_200_000_000.0 },
    YieldOpportunity { chain: "arbitrum".into(), protocol: "GMX".into(), apy: 8.7, tvl_usd: 420_000_000.0 },
    YieldOpportunity { chain: "base".into(), protocol: "Aerodrome".into(), apy: 12.4, tvl_usd: 180_000_000.0 },
];

// Sort by APY descending, filter by minimum TVL
let mut sorted: Vec<_> = opportunities.iter()
    .filter(|o| o.tvl_usd > 100_000_000.0)
    .collect();
sorted.sort_by(|a, b| b.apy.partial_cmp(&a.apy).unwrap());

for opp in &sorted {
    println!("{} on {} -- APY: {:.1}%, TVL: {:.0}M",
        opp.protocol, opp.chain, opp.apy, opp.tvl_usd / 1_000_000.0);
}

// Bridge to the best chain and deposit
let best_opp = &sorted[0];
println!("\nAllocating to {} on {}", best_opp.protocol, best_opp.chain);

// Check if we need to bridge
if best_opp.chain != "ethereum" {
    let bridge = client.bridge().bridge_tokens(
        "ethereum", &best_opp.chain, "USDC", "1000000000",
        &wallet.address, None,  // auto-select cheapest adapter
    ).await?;
    println!("Bridging to {}: tx {}", best_opp.chain, bridge.transaction_hash);
}
Aerodrome on base -- APY: 12.4%, TVL: $180M
GMX on arbitrum -- APY: 8.7%, TVL: $420M
Aave V3 on ethereum -- APY: 3.2%, TVL: $5200M

Allocating to Aerodrome on base
Bridging to base: tx 0xf4a9...2b7c

Step 7: Settle on Tenzro Ledger

All profits settle on the Tenzro Ledger with the 0.5% network fee automatically deducted:

// Settle profits back to Tenzro Ledger
let settlement = client.settlement().settle(SettlementRequest {
    request_id: format!("xchain-defi-{}", uuid::Uuid::new_v4()),
    provider: wallet.address.clone(),
    customer: "0x0000000000000000000000000000000000000000".to_string(),
    amount: 2500,
    asset: "TNZO".to_string(),
}).await?;
println!("Settlement: {} (status: {})", settlement.receipt_id, settlement.status);

Full Example

Run with:

cargo run --example cross_chain_defi
View full source
use tenzro_sdk::{TenzroClient, SettlementRequest, config::SdkConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();
    let config = SdkConfig::testnet();
    let client = TenzroClient::connect(config).await?;

    // 1. Identity & Wallet
    let identity = client.identity().register_human("Cross-Chain DeFi Builder").await?;
    let wallet = client.wallet().create_wallet().await?;

    // 2. Compare bridge routes
    let routes = client.bridge().get_routes("ethereum", "arbitrum", "USDC", "500000000").await?;
    let cheapest = &routes[0];
    println!("Cheapest: {} ({} USD)", cheapest.adapter, cheapest.fee_usd_estimate);

    // 3. Bridge tokens
    let bridge = client.bridge().bridge_tokens(
        "ethereum", "arbitrum", "USDC", "500000000",
        &wallet.address, Some(&cheapest.adapter),
    ).await?;

    // 4. Track until complete
    loop {
        let status = client.bridge().get_status(&bridge.order_id).await?;
        if matches!(status.state.as_str(), "Fulfilled" | "Completed") { break; }
        if matches!(status.state.as_str(), "Failed" | "Cancelled") {
            return Err(format!("Bridge failed: {:?}", status.error).into());
        }
        tokio::time::sleep(std::time::Duration::from_secs(15)).await;
    }

    // 5. Settle on Tenzro Ledger
    let settlement = client.settlement().settle(SettlementRequest {
        request_id: format!("xchain-defi-{}", uuid::Uuid::new_v4()),
        provider: wallet.address.clone(),
        customer: "0x0000000000000000000000000000000000000000".to_string(),
        amount: 2500,
        asset: "TNZO".to_string(),
    }).await?;

    println!("Done. Settlement: {}", settlement.receipt_id);
    Ok(())
}

What You Learned

Next Steps