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

Cross-Chain Messaging with LayerZero V2

BridgeIntermediate30 min

LayerZero V2 is an omnichain messaging protocol with per-application DVN committees and a pay-as-you-go executor model. The Tenzro MCP server exposes 20 LayerZero tools — quote/send/track for raw messages, OFT for omnichain ERC-20s, Stargate V2 for native ETH/USDC/USDT, and the unified Value Transfer API covering 130+ chains including Solana. This tutorial walks the full lifecycle.

What You'll Build

  • Quote and send an arbitrary cross-chain message
  • Bridge an OFT (omnichain fungible token)
  • Bridge native USDC via Stargate V2
  • Use the unified Value Transfer API across 130+ chains

Architecture

LayerZero V2 on Tenzro  component map

  src chain                    verification                  dst chain
  ---------                    ------------                  ---------
  App.send() ──► EndpointV2 ──► DVN_1 ──┐
                                DVN_2 ──┼──► ULN ──► EndpointV2 ──► App.lzReceive()
                                DVN_N ──┘            
                                                     
                                              Executor pays gas

  DVNs   = Decentralized Verifier Networks (per-app configurable committees)
  ULN    = Ultra-Light Node verification on dst
  GUID   = globally unique message ID for tracking

Step 1: Encode Executor Options

V2 options are a TLV-encoded bytes blob that tells the executor how much gas and value to forward to lzReceive on the destination:

// EXECUTOR_LZ_RECEIVE option — pays the executor to call lzReceive on the dst chain
lz_encode_options({
  "option_type":   3,
  "executor_gas":  200000,
  "executor_value": "0"
})
  -> { options: "0x00030100110100000000000000000000000000030d40" }

Step 2: Quote the Fee

This is a real EndpointV2.quote() eth_call — no estimates, no caching. The returned native_fee is what your wallet must attach as msg.value:

lz_quote_fee({
  "src_chain":  "ethereum",
  "dst_chain":  "base",
  "receiver":   "0xReceiverOnBase...",
  "payload":    "0xdeadbeef",
  "options":    "0x00030100110100000000000000000000000000030d40"
})
  -> {
    native_fee: "0.0005",   // ETH
    lz_token_fee: "0",
    endpoint: "0x1a44..."
  }

Step 3: Send an Arbitrary Message

lz_send_message({
  "src_chain":  "ethereum",
  "dst_chain":  "base",
  "receiver":   "0xReceiverOnBase...",
  "payload":    "0xdeadbeef",
  "options":    "0x00030100110100000000000000000000000000030d40",
  "refund_address": "0xSender..."
})
  -> {
    tx_calldata: "0x...",   // EndpointV2.send() encoded
    to: "0x1a44...",         // EndpointV2 address
    value: "500000000000000", // native_fee in wei
    guid_hint: "0x..."
  }

Step 4: Track Delivery

lz_track_message({ "guid": "0x..." })
  -> {
    status: "Delivered",     // Inflight | Delivered | Failed
    src_tx:  "0x...",
    dst_tx:  "0x...",
    dvn_count: 2
  }

Step 5: Bridge an OFT

amount is uint64 amountSD(shared decimals) — the OFT contract handles the local-decimal conversion. The helper auto-quotes the fee and returns ready-to-sign calldata:

// 1. Quote an OFT send
lz_oft_quote({
  "oft_address": "0xOftOnEthereum...",
  "dst_chain":   "base",
  "receiver":    "0xReceiverOnBase...",
  "amount":      "1000000000"  // uint64 amountSD (shared decimals)
})
  -> { native_fee: "0.0004", lz_token_fee: "0" }

// 2. Build the OFT send calldata (with auto fee quoting)
lz_oft_send({
  "oft_address": "0xOftOnEthereum...",
  "dst_chain":   "base",
  "receiver":    "0xReceiverOnBase...",
  "amount":      "1000000000"
})
  -> {
    tx_calldata: "0x...",   // OFT.send() encoded
    to:          "0xOftOnEthereum...",
    value:       "400000000000000"
  }

Step 6: Native Bridging via Stargate V2

For native ETH/USDC/USDT where you'd otherwise need a wrapper, Stargate V2 routes through liquidity pools — returning the full sequence including ERC-20 approval:

// Stargate V2 native token bridge (ETH, USDC, USDT) — uses StargatePoolNative
lz_stargate_quote({
  "token":     "USDC",
  "src_chain": "ethereum",
  "dst_chain": "base",
  "amount":    "100.0",
  "receiver":  "0xReceiverOnBase..."
})
  -> {
    amount_received: "99.98",
    native_fee:      "0.0012",
    min_amount_out:  "99.88"
  }

lz_stargate_send({
  "token":     "USDC",
  "src_chain": "ethereum",
  "dst_chain": "base",
  "amount":    "100.0",
  "receiver":  "0xReceiverOnBase..."
})
  -> {
    steps: [
      { kind: "erc20_approve", token: "0xA0b86...", spender: "0xStargate...", amount: "100000000" },
      { kind: "call", to: "0xStargate...", data: "0x...", value: "1200000000000000" }
    ]
  }

Step 7: Unified Value Transfer API

The highest-level path — abstract over LayerZero's V2 endpoints, OFT deployments, and Stargate pools with a single quote → build → status flow:

// Unified Value Transfer API — covers 130+ chains including Solana
lz_transfer_quote({
  "src_chain": "ethereum",
  "dst_chain": "solana",
  "token":     "USDC",
  "amount":    "25.0",
  "receiver":  "SolanaPublicKey..."
})
  -> {
    quote_id: "vq_01HR...",
    amount_out: "24.97",
    native_fee: "0.0008"
  }

lz_transfer_build({ "quote_id": "vq_01HR..." })
  -> { steps: [ /* signable tx steps for the wallet */ ] }

lz_transfer_status({ "quote_id": "vq_01HR..." })
  -> { status: "delivered", src_tx: "0x...", dst_tx: "5Kq..." }

Step 8: CLI

# List all LayerZero supported chains (130+)
tenzro layerzero list-chains

# Quote a message send
tenzro layerzero quote-fee \
  --src ethereum --dst base \
  --receiver 0x... --payload 0xdeadbeef

# Send arbitrary cross-chain message
tenzro layerzero send \
  --src ethereum --dst base \
  --receiver 0x... --payload 0xdeadbeef

# Bridge TNZO via the omnichain OFT
tenzro bridge transfer \
  --adapter layerzero \
  --from ethereum --to base \
  --token TNZO --amount 100.0 \
  --recipient 0x...

# Track delivery
tenzro layerzero track --guid 0x...

Step 9: TypeScript SDK

import { TenzroClient } from "@tenzro/sdk";

const client = new TenzroClient();

// 1. Quote the fee on-chain via EndpointV2.quote()
const q = await client.layerzero.quoteFee({
  src_chain: "ethereum",
  dst_chain: "base",
  receiver:  "0xReceiverOnBase...",
  payload:   "0xdeadbeef",
  options:   "0x00030100110100000000000000000000000000030d40",
});
console.log("native fee (ETH):", q.native_fee);

// 2. Build send calldata
const send = await client.layerzero.send({
  src_chain: "ethereum",
  dst_chain: "base",
  receiver:  "0xReceiverOnBase...",
  payload:   "0xdeadbeef",
  options:   "0x00030100110100000000000000000000000000030d40",
});

// 3. Submit via your wallet
const hash = await wallet.sendTransaction({
  to:    send.to,
  data:  send.tx_calldata,
  value: BigInt(send.value),
});

// 4. Track by GUID
const status = await client.layerzero.trackMessage({ guid: send.guid_hint! });

LayerZero vs CCT vs Wormhole. LayerZero V2 is per-app configurable: each application chooses its own DVN committee and security threshold. CCIP uses a global DON + RMN. Wormhole uses a fixed 19-Guardian set. For independent trust domains on high-value flows, run two adapters in parallel and require both to attest before releasing value.

What You Learned

Next Steps