Build a Cross-Chain Yield Router Agent
A yield router watches a basket of cross-chain DeFi opportunities, compares the bridge cost to reach each one, and routes capital to the highest net-yielding destination. In this tutorial you will build one entirely from the command line using the tenzro-cli and the AgentKit template system. No Rust source code is required. You will provision an identity and wallet with tenzro join, register a machine agent with chain whitelisting, spawn the ref-yield-rebalancer-v1 template, compare live bridge fees via tenzro_getBridgeRoutes JSON-RPC calls, and watch the agent enforce delegation scopes before every dispatch.
What You'll Build
- A human controller identity at KYC tier
Enhancedviatenzro join - A machine yield-router agent with chain whitelist (ethereum, arbitrum, base) and 7-day time bound
- A spawned instance of the
ref-yield-rebalancer-v1AgentKit template - Live fee comparison via
tenzro_getBridgeRoutesacross LayerZero V2, Chainlink CCIP, and deBridge DLN - APY-sorted opportunity routing with delegation enforcement before each bridge dispatch
- A demo confirming oversized bridges are rejected and unsupported chains are refused
Why AgentKit Templates Beat Hand-Rolling Code
Cross-chain routing has two hard problems: knowing which adapter supports a given destination, and knowing the live fee for a payload of a given size. Tenzro's BridgeRouter solves both in one call: tenzro_getBridgeRoutes walks every registered adapter, asks it to quote, and returns a sorted list with the cheapest first. The adapters themselves use real on-chain calls: LayerZero quotes via EndpointV2.quote(), CCIP quotes via Router.getFee(), deBridge quotes via the live order-creation API. The ref-yield-rebalancer-v1AgentKit template wraps all of this into a single spawnable agent that you configure with a JSON file of yield opportunities. You don't need to import crates, write Rust, or manage a build toolchain.
Two Layers of Chain Filtering
You get chain restriction at two independent layers: the delegation scope's allowed_chains blocks the agent from even attempting an unsupported chain, and the BridgeRouter's adapter list determines which chains have a cost-quotable route. Both must allow the destination, otherwise the call fails. This is defense-in-depth: a misconfigured router cannot let the agent bridge to a chain the controller never approved.
Prerequisites
You need a running Tenzro node. If you are connecting to the public testnet, the RPC endpoint is https://rpc.tenzro.network. If you are running a local node, start it first:
cargo run --bin tenzro-node -- --role validator --listen-addr /ip4/0.0.0.0/tcp/9000The CLI commands below assume a local node at localhost:8545. Replace with the testnet URL if needed.
Step 1: Join the Network
The tenzro join command provisions your human controller identity, creates an MPC wallet, and detects your hardware profile in one shot:
# One command provisions your identity + wallet + hardware profile
tenzro joinIdentity provisioned: did:tenzro:human:a1b2c3d4-...
Wallet created: 0x7f3a...9c1e (Ed25519)
KYC tier: Enhanced
Hardware: aarch64, 16 cores, 64GB RAMConfirm you have testnet tokens:
# Confirm the wallet has testnet TNZO
tenzro wallet balancePer-VM balances. The tenzro wallet balance command shows your aggregate TNZO balance. For a detailed breakdown across all VM representations (native, wTNZO ERC-20, SPL, CIP-56), use tenzro token balance. The tenzro token CLI subcommand provides additional token-specific operations including cross-VM transfers, wrapping, and token creation.
If your balance is zero, hit the faucet:
# If the balance is zero, request testnet tokens
curl -s -X POST https://api.tenzro.network/faucet \
-H "Content-Type: application/json" \
-d '{"address":"0x7f3a...9c1e"}' | jqStep 2: Register the Yield Router Agent Identity
Register a machine identity under your controller with a delegation scope that whitelists exactly three chains, caps per-transaction value at 500 USDC, and expires after 7 days:
# Register a machine identity under your controller identity
tenzro identity register \
--type machine \
--display-name "yield-router" \
--capabilities "chain:ethereum,chain:arbitrum,chain:base,yield-route" \
--max-tx-value 500000000 \
--max-daily-spend 2000000000 \
--allowed-ops "bridge,yield-route" \
--allowed-chains "ethereum,arbitrum,base" \
--time-bound-days 7Agent identity registered:
DID: did:tenzro:machine:a1b2c3d4:e5f6a7b8-...
Controller: did:tenzro:human:a1b2c3d4-...
Wallet: 0x4d2e...8f3a (Ed25519, auto-provisioned)
Delegation scope:
max_tx_value: 500 USDC
max_daily_spend: 2,000 USDC
allowed_ops: [bridge, yield-route]
allowed_chains: [ethereum, arbitrum, base]
time_bound: 7 days from nowThe --allowed-chains flag installs a hard whitelist on the delegation scope. Even if the node's BridgeRouter has an adapter for Fantom or Solana, the agent cannot bridge to those chains. The --max-tx-value is in 6-decimal USDC base units (500000000 = 500 USDC). The --time-bound-days 7 sets both the start (now) and end (7 days from now) automatically.
Step 3: Explore the AgentKit Template
AgentKit ships with pre-built agent templates for common DeFi patterns. List what is available:
# Browse available AgentKit templates
tenzro agent list-templatesID | Category | Description
-----------------------------|-------------|---------------------------------------------
ref-yield-rebalancer-v1 | DeFi | Cross-chain yield router with bridge fee comparison
ref-portfolio-rebalancer-v1 | DeFi | Portfolio drift detection and rebalance
ref-dca-v1 | DeFi | Dollar-cost averaging with MPP payments
ref-arb-v1 | DeFi | Cross-chain arbitrage with multi-leg routingInspect the yield rebalancer template to understand its parameters:
# Inspect the yield rebalancer template before spawning
tenzro agent get-template ref-yield-rebalancer-v1Template: ref-yield-rebalancer-v1
Version: 1.0.0
Category: DeFi
Description:
Autonomous yield router that watches a basket of cross-chain DeFi
opportunities, compares bridge costs via LayerZero V2, Chainlink CCIP,
and deBridge DLN, and routes capital to the highest net-yielding
destination. Enforces TDIP delegation scopes before every bridge dispatch.
Required capabilities:
- chain:ethereum, chain:arbitrum, chain:base
- yield-route, bridge
Parameters:
--source-chain Source chain for bridge operations (default: ethereum)
--opportunities JSON file or inline JSON array of yield opportunities
--dry-run Compare fees and print routes without dispatching (default: true)
--max-slippage Maximum bridge fee as percentage of deploy amount (default: 2.0)Step 4: Define Yield Opportunities
Create an opportunities.json file with three real-world DeFi opportunities — Aave on Ethereum, GMX GLP on Arbitrum, and Aerodrome on Base. The agent will sort them by APY descending so capital flows to the highest-yielding destination first:
[
{
"chain": "ethereum",
"protocol": "Aave v3 USDC",
"apy_bps": 380,
"deploy_amount_usdc": 200
},
{
"chain": "arbitrum",
"protocol": "GMX GLP",
"apy_bps": 1250,
"deploy_amount_usdc": 250
},
{
"chain": "base",
"protocol": "Aerodrome USDC/cbBTC",
"apy_bps": 720,
"deploy_amount_usdc": 300
}
]Save this as opportunities.json in your working directory.
Step 5: Spawn the Agent Instance
The spawn-template command creates a running instance of the template, bound to your agent identity and configured with your opportunities. The --dry-run true flag means the agent will compare fees and prepare bridge requests but will not actually broadcast transactions:
# Spawn an instance of the yield rebalancer bound to your agent identity
tenzro agent spawn-template ref-yield-rebalancer-v1 \
--agent-did did:tenzro:machine:a1b2c3d4:e5f6a7b8-... \
--source-chain ethereum \
--opportunities opportunities.json \
--dry-run true \
--max-slippage 2.0Agent instance spawned:
Instance ID: yield-router-a1b2c3d4
Template: ref-yield-rebalancer-v1
Agent DID: did:tenzro:machine:a1b2c3d4:e5f6a7b8-...
Source chain: ethereum
Opportunities: 3 loaded
Mode: dry-run (no bridge dispatch)
Status: readyStep 6: Compare Bridge Fees Manually
Before running the agent, you can inspect bridge fees yourself using the tenzro_getBridgeRoutes JSON-RPC method. This is the same call the agent makes internally for every cross-chain leg. It walks every registered adapter (LayerZero V2, Chainlink CCIP, deBridge DLN) and returns sorted fee quotes:
# Compare bridge fees across all adapters for ethereum -> arbitrum
curl -s http://localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tenzro_getBridgeRoutes",
"params": {
"source_chain": "ethereum",
"dest_chain": "arbitrum",
"payload_size": 64
}
}' | jq{
"jsonrpc": "2.0",
"id": 1,
"result": {
"routes": [
{
"adapter": "layerzero",
"fee": "0.000842",
"currency": "ETH",
"estimated_time_secs": 120
},
{
"adapter": "debridge",
"fee": "0.001200",
"currency": "ETH",
"estimated_time_secs": 60
},
{
"adapter": "ccip",
"fee": "0.001350",
"currency": "ETH",
"estimated_time_secs": 900
}
],
"cheapest": "layerzero",
"fastest": "debridge"
}
}The routes are sorted by fee ascending — the cheapest adapter comes first. Each adapter quotes using real on-chain calls: LayerZero uses EndpointV2.quote(), CCIP uses Router.getFee(), and deBridge uses the DLN order-creation API. If an RPC call fails, the adapter falls back to a static estimate.
Intra-network vs. external bridges. The bridge adapters (LayerZero, CCIP, deBridge) are for moving tokens between Tenzro and external chains like Ethereum mainnet, Arbitrum, or Base. For moving TNZO/wTNZO between Tenzro's own VMs (EVM, SVM, Canton), use tenzro token transfer --cross-vm instead. Cross-VM transfers route through precompile 0x1003 (CROSS_VM_BRIDGE), which is faster and fee-free compared to bridge adapters since the underlying native balance is shared via the Sei V2 pointer model.
Try the same for Base:
# Compare fees for ethereum -> base
curl -s http://localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tenzro_getBridgeRoutes",
"params": {
"source_chain": "ethereum",
"dest_chain": "base",
"payload_size": 64
}
}' | jq '.result.routes[] | {adapter, fee, currency}'And confirm that unsupported chains return zero routes:
# Try an unsupported chain — no adapters will return routes
curl -s http://localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tenzro_getBridgeRoutes",
"params": {
"source_chain": "ethereum",
"dest_chain": "fantom",
"payload_size": 64
}
}' | jq '.result.routes | length'
# Returns: 0Step 7: Run the Yield Routing Loop
Execute the spawned agent instance. The agent sorts opportunities by APY, skips same-chain deployments, compares bridge fees for each cross-chain leg, picks the cheapest adapter, enforces the delegation scope, and prepares the bridge request:
# Execute the yield routing loop
tenzro agent run-template yield-router-a1b2c3d4[yield-router] Starting yield routing cycle...
[yield-router] Sorting 3 opportunities by APY (descending)
[yield-router] 1. GMX GLP on arbitrum (12.50% APY) — deploy 250 USDC
Comparing bridge fees: ethereum -> arbitrum (64 bytes)
layerzero fee = 0.000842 ETH
debridge fee = 0.001200 ETH
ccip fee = 0.001350 ETH
Cheapest adapter: layerzero @ 0.000842 ETH
Delegation check: bridge 250 USDC... OK
[dry-run] Would bridge 250 USDC via layerzero: ethereum -> arbitrum
[yield-router] 2. Aerodrome USDC/cbBTC on base (7.20% APY) — deploy 300 USDC
Comparing bridge fees: ethereum -> base (64 bytes)
layerzero fee = 0.000910 ETH
debridge fee = 0.001100 ETH
ccip fee = 0.001420 ETH
Cheapest adapter: layerzero @ 0.000910 ETH
Delegation check: bridge 300 USDC... OK
[dry-run] Would bridge 300 USDC via layerzero: ethereum -> base
[yield-router] 3. Aave v3 USDC on ethereum (3.80% APY) — deploy 200 USDC
Same-chain deployment, no bridge required
[dry-run] Would deploy 200 USDC directly to Aave v3 USDC
[yield-router] Cycle complete. 3 opportunities processed, 2 bridges prepared.The output shows the agent's decision process for each opportunity: fee quotes from all three adapters, the cheapest pick, the delegation check, and the prepared bridge request. GMX GLP on Arbitrum (12.50% APY) gets routed first because it has the highest yield. Aave on Ethereum skips the bridge entirely since it is a same-chain deployment.
Step 8: Confirm the Delegation Boundaries
First, inspect the agent's delegation scope to see the hard limits:
# Test the delegation boundary with an oversized amount
# The agent's max_tx_value is 500 USDC — try 1000 USDC
curl -s http://localhost:8545 \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 4,
"method": "tenzro_resolveIdentity",
"params": {
"did": "did:tenzro:machine:a1b2c3d4:e5f6a7b8-..."
}
}' | jq '.result.delegation_scope'{
"max_transaction_value": 500000000,
"max_daily_spend": 2000000000,
"allowed_operations": ["bridge", "yield-route"],
"allowed_chains": ["ethereum", "arbitrum", "base"],
"time_bound": {
"start": "2026-04-08T00:00:00Z",
"end": "2026-04-15T00:00:00Z"
},
"active": true
}Now spawn a second instance with an opportunity that exceeds the 500 USDC per-transaction cap:
# Spawn with an opportunity that exceeds the per-tx cap
tenzro agent spawn-template ref-yield-rebalancer-v1 \
--agent-did did:tenzro:machine:a1b2c3d4:e5f6a7b8-... \
--source-chain ethereum \
--opportunities '[{"chain":"arbitrum","protocol":"Oversized Test","apy_bps":2000,"deploy_amount_usdc":1000}]' \
--dry-run true
tenzro agent run-template yield-router-oversized-test[yield-router] Starting yield routing cycle...
[yield-router] Sorting 1 opportunities by APY (descending)
[yield-router] 1. Oversized Test on arbitrum (20.00% APY) — deploy 1000 USDC
Comparing bridge fees: ethereum -> arbitrum (64 bytes)
layerzero fee = 0.000842 ETH
Cheapest adapter: layerzero @ 0.000842 ETH
Delegation check: bridge 1000 USDC... BLOCKED
DelegationViolation: transaction value 1000000000 exceeds max 500000000
Skipping opportunity.
[yield-router] Cycle complete. 1 opportunities processed, 0 bridges prepared.The delegation scope rejected the 1000 USDC bridge because it exceeds the 500 USDC per-transaction cap. The agent logged the violation and skipped the opportunity cleanly — no on-chain footprint, no wasted gas. This is the same enforce_operation check that runs in the production path. Combined with the zero-route result for unsupported chains like Fantom (Step 6), you have defense-in-depth: the delegation scope blocks unauthorized amounts, and the BridgeRouter blocks unauthorized destinations.
What You Learned
- tenzro join — one-command identity, wallet, and hardware provisioning
- tenzro identity register — machine agent creation with chain whitelisting and delegation scopes
- AgentKit templates — pre-built agent patterns you can spawn and configure without writing code
- tenzro_getBridgeRoutes — JSON-RPC fee comparison across LayerZero V2, Chainlink CCIP, and deBridge DLN
- Delegation chain whitelisting — defense in depth via
allowed_chainson the identity scope - Pre-dispatch enforcement — every bridge call gated by
enforce_operationbefore any network I/O - Dry-run mode — full routing loop without dispatching real bridge transactions
Next Steps
- See the Cross-Chain Arbitrage tutorial for a more aggressive multi-leg routing pattern
- Continue to the DCA Agent tutorial for a time-based capital deployment loop
- Read the Portfolio Rebalancer tutorial for the on-chain trading equivalent