Cross-chain vault operations enable Flux Protocol managers to execute strategies that span multiple blockchain networks from a single vault position. By leveraging cross-chain messaging protocols, managers can deploy capital across ecosystems—borrowing on Ethereum, deploying to Arbitrum, and farming yield on Base—all while maintaining unified health accounting and liquidation protection.
This capability transforms Flux from a single-chain lending protocol into a true omnichain capital coordination layer, where liquidity providers on one chain can fund strategies executing across the entire blockchain ecosystem.
Why Cross-Chain Operations?
The Multi-Chain Reality
DeFi liquidity is fragmented across dozens of networks. The best yield opportunities often exist on chains different from where capital resides:
Challenge
Single-Chain Limitation
Cross-Chain Solution
Yield Fragmentation
Miss opportunities on other chains
Access yield anywhere
Capital Inefficiency
Idle capital on low-yield chains
Deploy to highest-yield venues
Arbitrage Gaps
Can't exploit cross-chain mispricings
Unified position for arb
Risk Concentration
All exposure on one chain
Diversify across ecosystems
Key Benefits
Unified Leverage — Borrow once, deploy everywhere
Cross-Chain Arbitrage — Exploit price differences across DEXs on different chains
Yield Optimization — Automatically route capital to highest-yield venues
Risk Diversification — Spread positions across multiple networks
Capital Efficiency — Single bond secures multi-chain positions
Architecture Overview
Core Components
1. Cross-Chain Router
The router is the source-chain gateway for initiating cross-chain operations:
2. Cross-Chain Receiver
The receiver handles incoming messages and executes operations on the destination chain:
3. Remote Position Tracker
Tracks manager positions on remote chains and provides value attestations:
Cross-Chain Asset Wrappers
Cross-chain wrappers extend the standard IAsset interface to manage positions on remote chains:
Cross-Chain Health Accounting
Unified Health Calculation
The vault aggregates both local and remote positions for health calculations:
Attestation-Based Valuation
Since remote chain state cannot be read synchronously, Flux uses an attestation model:
Staleness Protection
Messaging Protocol Integration
Flux supports multiple cross-chain messaging protocols through an adapter pattern:
LayerZero Integration
Chainlink CCIP Integration
Adapter Selection
Cross-Chain Callback Execution
Managers can execute complex cross-chain strategies using the callback pattern:
Example: Cross-Chain Yield Farming
Remote Execution Handler
Cross-Chain Liquidation
Challenge: Multi-Chain Position Liquidation
When a manager's health drops below threshold, liquidators must handle positions across multiple chains:
Coordinated Liquidation Contract
Cross-Chain Oracle Synchronization
Oracle Price Attestations
Remote chains attest their oracle prices to ensure consistent valuation:
Security Considerations
1. Message Verification
Always verify cross-chain message authenticity:
2. Replay Protection
Prevent message replay attacks:
3. Finality Considerations
Different chains have different finality guarantees:
Chain
Finality Type
Safe Confirmations
Notes
Ethereum
Probabilistic → Finalized
2 epochs (~13 min)
Wait for finalization
Arbitrum
Optimistic
7 days (challenge period)
Use fast finality for small amounts
Base
Optimistic
7 days
Consider fraud proof window
Polygon
PoS
~128 blocks (~4 min)
Checkpoint to Ethereum
4. Bridge Risk Mitigation
5. Attestation Liveness
Ensure remote positions are regularly attested:
Best Practices
For Vault Curators
Set Conservative Attestation Windows — Require attestations every 30-60 minutes for volatile assets
Configure Bridge Limits — Cap per-transaction and daily bridge volumes
Whitelist Chains Carefully — Only enable chains with proven bridge security
Monitor Attestation Liveness — Alert if attestations stop arriving
For Managers
Understand Finality Delays — Cross-chain operations are not instant
Maintain Buffer Above Liquidation — Account for attestation lag in health calculations
Diversify Across Bridges — Don't concentrate all value through one bridge
Monitor Gas Costs — Cross-chain operations incur fees on multiple networks
For Developers
Implement Idempotent Handlers — Remote operations may be retried
Use Optimistic Updates Carefully — Always validate with attestations
Handle Partial Failures — Design for cases where some chains succeed, others fail
Test with Realistic Delays — Simulate 10-30 minute message delivery in tests
Related Concepts
Vaults — Core vault architecture and position model
Asset Wrappers — How wrappers abstract asset interactions
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface ICrossChainRouter {
/// @notice Send capital to a remote chain
/// @param destinationChainId Target chain identifier
/// @param asset Asset to bridge
/// @param amount Amount to send
/// @param payload Encoded operation to execute on destination
/// @return messageId Unique identifier for tracking
function sendCrossChain(
uint256 destinationChainId,
address asset,
uint256 amount,
bytes calldata payload
) external returns (bytes32 messageId);
/// @notice Execute a remote operation without bridging assets
/// @param destinationChainId Target chain identifier
/// @param payload Encoded operation (swap, stake, claim, etc.)
function executeRemote(
uint256 destinationChainId,
bytes calldata payload
) external returns (bytes32 messageId);
/// @notice Recall capital from a remote chain
/// @param sourceChainId Chain where assets currently reside
/// @param asset Asset to retrieve
/// @param amount Amount to recall
function recallCrossChain(
uint256 sourceChainId,
address asset,
uint256 amount
) external returns (bytes32 messageId);
}
interface ICrossChainReceiver {
/// @notice Handle incoming cross-chain message
/// @param sourceChainId Origin chain
/// @param sender Original sender address
/// @param payload Encoded operation
function receiveMessage(
uint256 sourceChainId,
address sender,
bytes calldata payload
) external;
/// @notice Attest current position value back to source chain
/// @param manager Manager address to attest
/// @param positionId Position identifier
function attestPosition(
address manager,
bytes32 positionId
) external returns (bytes32 messageId);
}
interface IRemotePositionTracker {
/// @notice Get the total value of a manager's positions on this chain
/// @param vault Source vault address
/// @param manager Manager address
/// @return totalValueUSD Aggregate USD value (18 decimals)
function getRemotePositionValue(
address vault,
address manager
) external view returns (uint256 totalValueUSD);
/// @notice Register a new remote position
function registerPosition(
address vault,
address manager,
bytes32 positionId,
address wrapper,
uint256 initialValue
) external;
/// @notice Update position after operation
function updatePosition(
address vault,
address manager,
bytes32 positionId,
uint256 newValue
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {IAsset} from "./interfaces/IAsset.sol";
import {ICrossChainRouter} from "./interfaces/ICrossChainRouter.sol";
contract CrossChainERC4626Wrapper is IAsset {
/// @notice Target chain for this wrapper
uint256 public immutable destinationChainId;
/// @notice Remote vault address (e.g., Aave aUSDC on Arbitrum)
address public immutable remoteVault;
/// @notice Cross-chain messaging router
ICrossChainRouter public immutable router;
/// @notice Cached position values from attestations
/// vault => manager => positionId => CachedValue
mapping(address => mapping(address => mapping(bytes32 => CachedValue)))
public cachedValues;
struct CachedValue {
uint256 value; // USD value (18 decimals)
uint256 attestedAt; // Timestamp of last attestation
bytes32 attestationId; // Message ID for verification
}
/// @notice Maximum age for cached values before requiring refresh
uint256 public constant MAX_CACHE_AGE = 1 hours;
/// @notice Deposit assets to remote chain vault
function deposit(
address account,
address subaccount,
bytes32 positionId,
uint256 amount
) external override {
// Pull assets from caller
IERC20(underlyingAsset).transferFrom(msg.sender, address(this), amount);
// Encode remote deposit operation
bytes memory payload = abi.encode(
RemoteOperation.DEPOSIT,
account,
subaccount,
positionId,
remoteVault,
amount
);
// Send assets + instructions to destination chain
router.sendCrossChain(
destinationChainId,
underlyingAsset,
amount,
payload
);
// Optimistically update cached value
// Will be corrected by attestation
cachedValues[account][subaccount][positionId].value += amount;
}
/// @notice Withdraw assets from remote chain
function withdraw(
address account,
address subaccount,
bytes32 positionId,
address recipient,
uint256 amount
) external override {
bytes memory payload = abi.encode(
RemoteOperation.WITHDRAW,
account,
subaccount,
positionId,
remoteVault,
amount,
recipient // Final recipient on source chain
);
router.executeRemote(destinationChainId, payload);
// Value update happens when assets arrive via callback
}
/// @notice Get position value from cache or request fresh attestation
function getValue(
address account,
address subaccount,
bytes32 positionId
) external view override returns (uint256) {
CachedValue memory cached = cachedValues[account][subaccount][positionId];
// Check cache freshness
if (block.timestamp - cached.attestedAt > MAX_CACHE_AGE) {
// In production: trigger async attestation request
// For view function: return cached with staleness flag
revert StaleCrossChainValue(cached.attestedAt, cached.value);
}
return cached.value;
}
/// @notice Receive attestation from remote chain
function receiveAttestation(
address account,
address subaccount,
bytes32 positionId,
uint256 attestedValue,
bytes32 attestationId
) external onlyCrossChainReceiver {
cachedValues[account][subaccount][positionId] = CachedValue({
value: attestedValue,
attestedAt: block.timestamp,
attestationId: attestationId
});
emit PositionAttested(account, subaccount, positionId, attestedValue);
}
}
Total Position Value = Local Positions + Σ(Remote Chain Positions)
= Bond + Working Capital + Local Wrappers
+ Arbitrum Positions (attested)
+ Base Positions (attested)
+ Optimism Positions (attested)
┌─────────────────────────────────────────────────────────────────┐
│ ATTESTATION FLOW │
│ │
│ 1. Manager deposits to remote chain │
│ ────────────────────────────▶ │
│ │
│ 2. Remote tracker registers position │
│ ◀──────────────────────────── │
│ │
│ 3. Periodic attestation (every N blocks or on-demand) │
│ Remote chain queries oracle for position value │
│ ────────────────────────────▶ │
│ │
│ 4. Attestation message sent to source chain │
│ Contains: positionId, value, timestamp, proof │
│ ◀──────────────────────────── │
│ │
│ 5. Source chain wrapper updates cached value │
│ Health ratio recalculated with fresh data │
└─────────────────────────────────────────────────────────────────┘
/// @notice Validate position values are fresh enough for health check
function validatePositionFreshness(
address vault,
address manager
) internal view {
CrossChainWrapper[] memory remoteWrappers = getRemoteWrappers(vault);
for (uint256 i = 0; i < remoteWrappers.length; i++) {
CachedValue memory cached = remoteWrappers[i].getCachedValue(
vault,
manager,
positionIds[i]
);
// Require attestation within acceptable window
if (block.timestamp - cached.attestedAt > MAX_ATTESTATION_AGE) {
revert StaleRemotePosition(
remoteWrappers[i].destinationChainId(),
cached.attestedAt
);
}
}
}