Build a Cross-Chain Arbitrage Agent
Cross-chain arbitrage is the canonical multi-leg agent pattern: spot a price spread between two chains, bridge inventory from the cheap side to the rich side, sell into the spread, and pocket the difference net of bridge fees and DEX taker fees. This tutorial walks through the entire flow using the Tenzro CLI and the AgentKit template system — no Rust source code, no IDE, just terminal commands. The ref-bridge-arbitrage-scanner-v1 template handles identity provisioning, delegation enforcement, bridge fee comparison, spread filtering, and trade dispatch out of the box.
What You'll Do
- Join the Tenzro testnet and fund a wallet in two commands
- Inspect the
ref-bridge-arbitrage-scanner-v1AgentKit template to understand its delegation scope, chain whitelist, and spread thresholds - Spawn an arbitrage agent whose identity, wallet, and delegation are auto-provisioned
- Dry-run the agent to verify delegation enforcement rejects oversized trades
- Execute a live scan across Ethereum, Arbitrum, and Base with real bridge fee comparison
- Review the results: profitable trades dispatched, thin spreads filtered, per-trade cap binding
Why Multi-Subsystem Agents Need Hard Boundaries
A cross-chain arbitrage agent touches four moving parts on every iteration: an identity check, a router fee comparison, a bridge dispatch, and a profit calculation. Any one of those can go wrong — a stale price feed, a bridge that's temporarily offline, a calldata-encoding bug, an off-by-one in the fee math — and the agent ends up burning capital instead of capturing spread. The TDIP delegation scope is the hard backstop. It refuses to authorize a trade that exceeds the per-trade cap beforeany of the other subsystems run, so a buggy strategy can't blow up the wallet. The agent then layers a software-only profit filter on top: even if delegation says "yes", the agent itself refuses any spread that doesn't cover bridge fees and the 5 bps DEX taker on each leg.
Two layers of safety. Delegation enforces the maximum loss the controller is willing to take on a single trade — that's the financial boundary. The profit filter inside the agent enforces the strategy's own break-even logic — that's the operational boundary. Both must agree before a trade dispatches. If you only have the strategy filter, a bug in the spread math can drain the wallet. If you only have delegation, every trade burns the per-trade cap.
Step 1: Join the Testnet and Fund Your Wallet
If you haven't already joined the network, the tenzro join command provisions your human identity, wallet, and hardware profile in a single step. Then hit the faucet to get testnet TNZO for gas.
# One-click join: provisions identity + wallet + hardware profile
tenzro join
# Check your wallet address
tenzro wallet list
# Request testnet TNZO from the faucet (100 TNZO, 24h cooldown)
curl -X POST https://api.tenzro.network/faucet \
-H "Content-Type: application/json" \
-d '{"address": "YOUR_WALLET_ADDRESS"}'
# Confirm the balance
tenzro wallet balanceThe join command registers a human identity at KYC tier Basic(sufficient for the testnet), creates a 2-of-3 MPC threshold wallet, and reports your DID and wallet address. The faucet gives you 100 TNZO — more than enough for this tutorial's gas costs.
Step 2: Explore the Bridge Arbitrage Scanner Template
AgentKit templates are declarative agent blueprints. The ref-bridge-arbitrage-scanner-v1 template ships with a pre-configured delegation scope, bridge adapter set, and spread evaluation logic. Inspect it before spawning.
# List all available AgentKit templates
tenzro agent list-templates
# Inspect the arbitrage scanner template
tenzro agent get-template ref-bridge-arbitrage-scanner-v1The template output shows the full configuration. Here are the key fields:
{
"template_id": "ref-bridge-arbitrage-scanner-v1",
"name": "Bridge Arbitrage Scanner",
"version": "1.0.0",
"description": "Cross-chain arbitrage agent with delegation-gated bridge transfers",
"capabilities": ["arbitrage", "bridge", "trade"],
"delegation": {
"max_transaction_value": "1000000000",
"max_daily_spend": "5000000000",
"allowed_operations": ["arbitrage", "bridge", "trade"],
"allowed_chains": ["ethereum", "arbitrum", "base"],
"time_bound_days": 7
},
"bridge": {
"adapters": ["layerzero", "debridge"],
"fee_comparison": true,
"strategy": "cheapest"
},
"spread_filter": {
"min_profit_bps": 10,
"taker_fee_bps": 5,
"include_bridge_fees": true
}
}Every dial on the delegation scope is doing real work. The 1000 USDC per-trade cap (max_transaction_value in 6-decimal micros) caps a single bad trade at a known maximum. The 5000 USDC daily cap ensures even five maximum-cap trades a day can't exceed the controller's daily risk budget. The allowed_operations whitelist means anything outside arbitrage, bridge, and trade is rejected at the registry level. The allowed_chains set is enforced when picking opportunities: if a spread pops up between Solana and Avalanche, the agent will skip it because neither chain is in the whitelist. The 7-day time bound means even a compromised agent key has at most a week of attack window before the delegation expires.
Spread filter. The min_profit_bps of 10 means the agent requires at least a 10 basis-point net spread after subtracting bridge fees and the 5 bps taker fee on each leg. A 1 USDC spread on a 2500 USDC asset (4 bps) won't pass this filter even if delegation approves the trade size.
Step 3: Spawn the Agent
Spawning creates a new machine identity under your human controller DID, provisions its MPC wallet, and binds the delegation scope from the template. Everything is auto-provisioned — no manual key generation or scope construction.
# Spawn the agent from the template
tenzro agent spawn-template ref-bridge-arbitrage-scanner-v1
# Expected output:
# Agent spawned successfully
# Agent ID: agent-arb-a1b2c3d4
# Agent DID: did:tenzro:machine:<your-did>:a1b2c3d4
# Wallet: 0x7f3a...9e21
# Controller: did:tenzro:human:<your-uuid>
# Delegation:
# max_transaction_value: 1000000000 (1000 USDC)
# max_daily_spend: 5000000000 (5000 USDC)
# allowed_operations: [arbitrage, bridge, trade]
# allowed_chains: [ethereum, arbitrum, base]
# expires: 2026-04-15T00:00:00ZThe agent now has its own DID (did:tenzro:machine:...) registered under your human identity as controller. Its wallet is a fresh 2-of-3 threshold wallet. The delegation scope is bound to the agent's DID and enforced by the identity registry on every operation. You can verify this by resolving the agent's DID:
# Verify the agent's identity and delegation scope
tenzro identity resolve did:tenzro:machine:<your-did>:a1b2c3d4Step 4: Dry-Run to Verify Delegation Enforcement
Before running the agent against live spreads, verify that the delegation boundaries are active. The --dry-run flag runs the full scan logic without dispatching any bridge transfers or trades, and includes an oversized-trade test.
# Dry-run: scans opportunities, enforces delegation, but does not execute
tenzro agent run-template ref-bridge-arbitrage-scanner-v1 \
--agent-id agent-arb-a1b2c3d4 \
--dry-run
# Expected output:
# [dry-run] Scanning 3 opportunities...
#
# Opportunity 1: ETH arbitrum -> ethereum
# buy: 2500.000000 USDC sell: 2525.000000 USDC size: 500 USDC
# delegation check: OK (500 USDC <= 1000 USDC cap)
# bridge fee (layerzero): 0.42 USDC (cheapest of 2 adapters)
# taker fee (2x5bps): 0.50 USDC
# gross profit: 5.00 USDC
# net profit: 4.08 USDC
# [dry-run] would execute trade
#
# Opportunity 2: USDC base -> arbitrum
# buy: 0.999500 USDC sell: 1.002000 USDC size: 800 USDC
# delegation check: OK (800 USDC <= 1000 USDC cap)
# bridge fee (debridge): 0.35 USDC (cheapest of 2 adapters)
# taker fee (2x5bps): 0.80 USDC
# gross profit: 2.00 USDC
# net profit: 0.85 USDC
# [dry-run] would execute trade
#
# Opportunity 3: ETH ethereum -> base
# buy: 2500.000000 USDC sell: 2501.000000 USDC size: 200 USDC
# delegation check: OK (200 USDC <= 1000 USDC cap)
# bridge fee (layerzero): 0.42 USDC
# taker fee (2x5bps): 0.20 USDC
# gross profit: 0.08 USDC
# net profit: -0.54 USDC
# spread unprofitable after fees -- skipping
#
# [dry-run] Oversized trade test: 5000 USDC
# BLOCKED by delegation: DelegationViolation: transaction value
# 5000000000 exceeds max_transaction_value 1000000000
#
# Dry-run complete: 2 would execute, 1 filtered, 1 blockedNote the strict ordering the agent follows for each opportunity: delegation enforcement runs first, then the router fee comparison across both adapters, then the taker fee math, then the net-profit check. Each step has its own failure mode and the loop short-circuits at the first one that breaks. The oversized-trade test at the end requests authorization for a 5000 USDC trade — 5x the per-trade cap — and gets a typed DelegationViolation rejection. This is the smoke test that proves the safety boundary is active.
Fee comparison. The agent queries both LayerZero V2 and deBridge DLN for every bridge leg and picks the cheapest. Bridge fees are quoted in native chain units (wei for EVM chains), so the agent converts to USDC at the current ETH price for an apples-to-apples comparison with the spread profit. You can also query bridge routes directly via the CLI or JSON-RPC.
Step 5: Execute a Live Scan
Once the dry-run looks correct, drop the --dry-run flag to execute for real. The agent will bridge tokens, dispatch trades, and report realized profit.
# Live execution: bridges, trades, and settles
tenzro agent run-template ref-bridge-arbitrage-scanner-v1 \
--agent-id agent-arb-a1b2c3d4
# Expected output:
# Scanning 3 opportunities...
#
# Opportunity 1: ETH arbitrum -> ethereum
# delegation check: OK
# bridge: layerzero (fee: 0.42 USDC)
# bridging 500 USDC from arbitrum to ethereum... done (tx: 0x8a3f...)
# trade dispatched on ethereum DEX (tx: 0xc1d2...)
# confirmed sell price: 2525.000000 USDC (slot 0 readback)
# net profit: +4.08 USDC
#
# Opportunity 2: USDC base -> arbitrum
# delegation check: OK
# bridge: debridge (fee: 0.35 USDC)
# bridging 800 USDC from base to arbitrum... done (tx: 0x5b7e...)
# trade dispatched on arbitrum DEX (tx: 0xf4a9...)
# confirmed sell price: 1.002000 USDC (slot 0 readback)
# net profit: +0.85 USDC
#
# Opportunity 3: ETH ethereum -> base
# delegation check: OK
# spread unprofitable after fees -- skipping
#
# Summary:
# executed: 2/3
# filtered: 1 (thin spread)
# total realized profit: +4.93 USDC
# total bridge fees paid: 0.77 USDC
# total taker fees paid: 1.30 USDCStep 6: Review Results and Bridge Routes
After execution you can inspect bridge routes and fees independently using the CLI or direct JSON-RPC calls. This is useful for debugging why a particular route was chosen or for building your own monitoring.
# Query available bridge routes between two chains
curl -X POST https://rpc.tenzro.network \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_getBridgeRoutes",
"params": {
"source_chain": "arbitrum",
"destination_chain": "ethereum"
},
"id": 1
}'
# Response includes each adapter's fee and estimated time:
# {
# "routes": [
# {
# "adapter": "layerzero",
# "fee_native": "180000000000000",
# "fee_usd_estimate": "0.42",
# "estimated_time_seconds": 120
# },
# {
# "adapter": "debridge",
# "fee_native": "210000000000000",
# "fee_usd_estimate": "0.49",
# "estimated_time_seconds": 90
# }
# ]
# }You can also trigger a bridge transfer directly without the arbitrage template, which is useful for testing individual legs:
# Bridge tokens directly via JSON-RPC
curl -X POST https://rpc.tenzro.network \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tenzro_bridgeTokens",
"params": {
"source_chain": "arbitrum",
"destination_chain": "ethereum",
"token": "USDC",
"amount": "500000000",
"recipient": "0x7f3a...9e21",
"adapter": "layerzero"
},
"id": 2
}'Cross-VM vs. external bridges. The tenzro_bridgeTokens RPC and the bridge adapters (LayerZero, deBridge) are for moving tokens to and from external chains like Ethereum mainnet, Arbitrum, and Base. For TNZO/wTNZO moving between Tenzro's own VMs (EVM, SVM, Canton), use the cross-VM transfer via precompile 0x1003 (tenzro token transfer --cross-vm) instead — it is faster and fee-free since all VMs share the same underlying native balance via the Sei V2 pointer model.
Native-fee conversion. Bridge adapters quote fees in native chain units (wei for EVM chains). The agent converts that into USDC at the current ETH price to compare with the spread profit. For example, a LayerZero fee of 180000000000000 wei at 2500 USDC/ETH = 180000000000000 * 2500 / 1e18 = 0.45 USDC. The template handles this conversion automatically using the price feed from the opportunity data.
Step 7: Customizing the Template
The template accepts override parameters at spawn time and run time. You can tighten the delegation, change the chain whitelist, or adjust the spread threshold without writing any code.
# Spawn with a tighter per-trade cap and additional chain
tenzro agent spawn-template ref-bridge-arbitrage-scanner-v1 \
--param delegation.max_transaction_value=500000000 \
--param delegation.allowed_chains=ethereum,arbitrum,base,optimism \
--param spread_filter.min_profit_bps=20
# Run with a custom opportunity set (JSON file)
tenzro agent run-template ref-bridge-arbitrage-scanner-v1 \
--agent-id agent-arb-e5f6g7h8 \
--param opportunities=@my_opportunities.jsonThe --param flag uses dot notation to override nested fields in the template configuration. The @ prefix reads the value from a file, which is useful for passing a list of opportunities. The opportunity file is a JSON array of objects with the same shape the template expects:
[
{
"asset": "ETH",
"buy_chain": "arbitrum",
"sell_chain": "ethereum",
"buy_price": 2500000000,
"sell_price": 2525000000,
"size_usdc": 500000000
},
{
"asset": "USDC",
"buy_chain": "base",
"sell_chain": "arbitrum",
"buy_price": 999500,
"sell_price": 1002000,
"size_usdc": 800000000
}
]What You Learned
- CLI-first agent lifecycle — spawning, dry-running, and executing an agent entirely from the terminal with
tenzro agent spawn-templateandtenzro agent run-template - AgentKit templates — declarative agent blueprints that auto-provision identity, wallet, and delegation scope without writing code
- Multi-adapter bridge comparison — the agent queries LayerZero V2 and deBridge DLN for every bridge leg and picks the cheapest
- Native-fee to USDC normalization — converting wei-denominated bridge fees into USDC for an apples-to-apples spread comparison
- Two-layer safety — delegation as the financial backstop, in-agent profit filter as the operational backstop
- Dry-run validation — verifying delegation enforcement and spread filtering before committing real capital
- Direct bridge inspection — querying routes and triggering transfers via
tenzro_getBridgeRoutesandtenzro_bridgeTokensJSON-RPC
Going Deeper
The ref-bridge-arbitrage-scanner-v1 template source is available in sdk/tenzro-sdk/examples/agent_kit_bridge.rs if you want to see the Rust implementation that powers the template. The template's spread evaluation uses JMESPath expressions internally, and you can inspect them with tenzro agent get-template ref-bridge-arbitrage-scanner-v1 --show-expressions.
Next Steps
- Continue to the Canton Institutional Repo tutorial for the enterprise-grade DAML pattern with multi-leg settlement
- Read the Yield Router tutorial for a single-leg version of this pattern that focuses on the bridge router
- See the Portfolio Rebalancer tutorial for the on-chain trading subset without the bridge legs