# Omnichain Vaults

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

1. **Unified Leverage** — Borrow once, deploy everywhere
2. **Cross-Chain Arbitrage** — Exploit price differences across DEXs on different chains
3. **Yield Optimization** — Automatically route capital to highest-yield venues
4. **Risk Diversification** — Spread positions across multiple networks
5. **Capital Efficiency** — Single bond secures multi-chain positions

***

### Architecture Overview

<figure><img src="/files/AgvIiv8MPdZTgCGFmOqH" alt=""><figcaption></figcaption></figure>

***

### Core Components

#### 1. Cross-Chain Router

The router is the source-chain gateway for initiating cross-chain operations:

```solidity
// 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);
}
```

#### 2. Cross-Chain Receiver

The receiver handles incoming messages and executes operations on the destination chain:

```solidity
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);
}
```

#### 3. Remote Position Tracker

Tracks manager positions on remote chains and provides value attestations:

```solidity
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;
}
```

***

### Cross-Chain Asset Wrappers

Cross-chain wrappers extend the standard IAsset interface to manage positions on remote chains:

```solidity
// 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);
    }
}
```

***

### Cross-Chain Health Accounting

#### Unified Health Calculation

The vault aggregates both local and remote positions for health calculations:

```
Total Position Value = Local Positions + Σ(Remote Chain Positions)

                     = Bond + Working Capital + Local Wrappers
                       + Arbitrum Positions (attested)
                       + Base Positions (attested)
                       + Optimism Positions (attested)
```

#### Attestation-Based Valuation

Since remote chain state cannot be read synchronously, Flux uses an **attestation model**:

```
┌─────────────────────────────────────────────────────────────────┐
│                    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                    │
└─────────────────────────────────────────────────────────────────┘
```

#### Staleness Protection

```solidity
/// @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
            );
        }
    }
}
```

***

### Messaging Protocol Integration

Flux supports multiple cross-chain messaging protocols through an adapter pattern:

#### LayerZero Integration

```solidity
contract LayerZeroAdapter is ICrossChainAdapter {
    ILayerZeroEndpoint public immutable lzEndpoint;
    
    mapping(uint256 => uint16) public chainIdToLzId;  // Flux chain ID → LZ chain ID
    mapping(uint256 => bytes) public trustedRemotes;   // Remote contract addresses

    function sendMessage(
        uint256 destinationChainId,
        bytes calldata payload,
        address refundAddress
    ) external payable override returns (bytes32 messageId) {
        uint16 lzChainId = chainIdToLzId[destinationChainId];
        bytes memory trustedRemote = trustedRemotes[destinationChainId];
        
        // Estimate and validate fees
        (uint256 nativeFee, ) = lzEndpoint.estimateFees(
            lzChainId,
            address(this),
            payload,
            false,
            bytes("")
        );
        require(msg.value >= nativeFee, "Insufficient fee");
        
        // Send via LayerZero
        lzEndpoint.send{value: nativeFee}(
            lzChainId,
            trustedRemote,
            payload,
            payable(refundAddress),
            address(0),
            bytes("")
        );
        
        return keccak256(abi.encode(block.number, destinationChainId, payload));
    }

    function lzReceive(
        uint16 _srcChainId,
        bytes calldata _srcAddress,
        uint64 _nonce,
        bytes calldata _payload
    ) external override {
        require(msg.sender == address(lzEndpoint), "Invalid sender");
        
        // Verify source is trusted
        uint256 srcChainId = lzIdToChainId[_srcChainId];
        require(
            keccak256(_srcAddress) == keccak256(trustedRemotes[srcChainId]),
            "Untrusted source"
        );
        
        // Route to cross-chain receiver
        crossChainReceiver.receiveMessage(srcChainId, _srcAddress, _payload);
    }
}
```

#### Chainlink CCIP Integration

```solidity
contract CCIPAdapter is ICrossChainAdapter, CCIPReceiver {
    IRouterClient public immutable ccipRouter;
    
    mapping(uint256 => uint64) public chainIdToCcipSelector;
    mapping(uint256 => address) public trustedSenders;

    function sendMessage(
        uint256 destinationChainId,
        bytes calldata payload,
        address refundAddress
    ) external payable override returns (bytes32 messageId) {
        uint64 destinationSelector = chainIdToCcipSelector[destinationChainId];
        
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(trustedSenders[destinationChainId]),
            data: payload,
            tokenAmounts: new Client.EVMTokenAmount[](0),
            extraArgs: "",
            feeToken: address(0)  // Pay in native token
        });
        
        uint256 fee = ccipRouter.getFee(destinationSelector, message);
        require(msg.value >= fee, "Insufficient fee");
        
        messageId = ccipRouter.ccipSend{value: fee}(
            destinationSelector,
            message
        );
    }

    function _ccipReceive(
        Client.Any2EVMMessage memory message
    ) internal override {
        uint256 srcChainId = ccipSelectorToChainId[message.sourceChainSelector];
        address sender = abi.decode(message.sender, (address));
        
        require(sender == trustedSenders[srcChainId], "Untrusted sender");
        
        crossChainReceiver.receiveMessage(srcChainId, sender, message.data);
    }
}
```

#### Adapter Selection

```solidity
contract CrossChainRouter {
    mapping(uint256 => ICrossChainAdapter) public preferredAdapters;
    mapping(uint256 => ICrossChainAdapter[]) public fallbackAdapters;

    function sendCrossChain(
        uint256 destinationChainId,
        address asset,
        uint256 amount,
        bytes calldata payload
    ) external returns (bytes32 messageId) {
        ICrossChainAdapter adapter = preferredAdapters[destinationChainId];
        
        // Bridge assets first (if amount > 0)
        if (amount > 0) {
            _bridgeAssets(destinationChainId, asset, amount);
        }
        
        // Send execution message
        try adapter.sendMessage{value: msg.value}(
            destinationChainId,
            payload,
            msg.sender
        ) returns (bytes32 id) {
            return id;
        } catch {
            // Try fallback adapters
            return _tryFallbacks(destinationChainId, payload);
        }
    }
}
```

***

### Cross-Chain Callback Execution

Managers can execute complex cross-chain strategies using the callback pattern:

#### Example: Cross-Chain Yield Farming

```solidity
contract CrossChainYieldStrategy is IFluxUnlockCallback {
    IFluxVault public immutable vault;
    ICrossChainRouter public immutable router;
    
    function executeStrategy(
        uint256 borrowAmount,
        uint256 bondAmount,
        uint256[] calldata chainAllocations  // Amount per chain
    ) external {
        bytes memory callbackData = abi.encode(
            borrowAmount,
            bondAmount,
            chainAllocations
        );
        
        vault.unlock(callbackData);
    }

    function fluxUnlockCallback(bytes calldata data) external override {
        require(msg.sender == address(vault), "Only vault");
        
        (
            uint256 borrowAmount,
            uint256 bondAmount,
            uint256[] memory allocations
        ) = abi.decode(data, (uint256, uint256, uint256[]));
        
        // Step 1: Deposit bond
        vault.locked_depositBond(bondAmount);
        
        // Step 2: Borrow capital
        vault.locked_borrow(borrowAmount);
        
        // Step 3: Distribute across chains
        uint256[] memory chainIds = getSupportedChains();
        
        for (uint256 i = 0; i < chainIds.length; i++) {
            if (allocations[i] > 0) {
                bytes memory remotePayload = abi.encode(
                    RemoteOperation.DEPOSIT_TO_AAVE,
                    address(vault),
                    msg.sender,  // manager
                    allocations[i]
                );
                
                router.sendCrossChain(
                    chainIds[i],
                    USDC,
                    allocations[i],
                    remotePayload
                );
            }
        }
        
        // Health check happens automatically at end of unlock
        // Remote positions use optimistic values initially
        // Attestations will update values asynchronously
    }
}
```

#### Remote Execution Handler

```solidity
contract RemoteExecutionHandler is ICrossChainReceiver {
    IRemotePositionTracker public immutable tracker;
    
    function receiveMessage(
        uint256 sourceChainId,
        address sender,
        bytes calldata payload
    ) external override onlyTrustedBridge {
        (
            RemoteOperation op,
            address vault,
            address manager,
            uint256 amount
        ) = abi.decode(payload, (RemoteOperation, address, address, uint256));
        
        if (op == RemoteOperation.DEPOSIT_TO_AAVE) {
            _executeAaveDeposit(vault, manager, amount);
        } else if (op == RemoteOperation.WITHDRAW_FROM_AAVE) {
            _executeAaveWithdraw(vault, manager, amount, sourceChainId);
        } else if (op == RemoteOperation.SWAP) {
            _executeSwap(vault, manager, payload);
        }
        // ... other operations
    }

    function _executeAaveDeposit(
        address vault,
        address manager,
        uint256 amount
    ) internal {
        // Approve and deposit to Aave
        IERC20(USDC).approve(AAVE_POOL, amount);
        IAavePool(AAVE_POOL).supply(USDC, amount, address(this), 0);
        
        // Register position with tracker
        bytes32 positionId = keccak256(abi.encode("AAVE_USDC", manager));
        tracker.registerPosition(
            vault,
            manager,
            positionId,
            address(aaveWrapper),
            amount
        );
        
        // Send attestation back to source chain
        _sendAttestation(vault, manager, positionId, amount);
    }
}
```

***

### Cross-Chain Liquidation

#### Challenge: Multi-Chain Position Liquidation

When a manager's health drops below threshold, liquidators must handle positions across multiple chains:

```
┌─────────────────────────────────────────────────────────────────┐
│                 CROSS-CHAIN LIQUIDATION FLOW                     │
│                                                                  │
│  Manager Position (Unhealthy):                                   │
│  ├── Ethereum: Bond $200K, Debt $800K                           │
│  ├── Arbitrum: Aave position $300K                              │
│  ├── Base: GMX position $250K                                   │
│  └── Total Value: $750K, Health: 93.75% (< 110%)                │
│                                                                  │
│  Liquidation Steps:                                              │
│  1. Liquidator calls vault.liquidate(manager) on Ethereum       │
│  2. Vault sends recall messages to all remote chains            │
│  3. Remote handlers withdraw positions and bridge back          │
│  4. Assets arrive on Ethereum, debt repaid                      │
│  5. Remaining equity returned to manager                         │
│  6. Liquidator receives profit margin                            │
└─────────────────────────────────────────────────────────────────┘
```

#### Coordinated Liquidation Contract

```solidity
contract CrossChainLiquidator {
    struct LiquidationState {
        address manager;
        uint256 totalDebt;
        uint256 recalledValue;
        uint256 pendingRecalls;
        mapping(uint256 => bool) chainRecalled;
    }
    
    mapping(bytes32 => LiquidationState) public liquidations;

    /// @notice Initiate cross-chain liquidation
    function initiateLiquidation(
        IFluxVault vault,
        address manager
    ) external returns (bytes32 liquidationId) {
        // Verify manager is liquidatable
        require(vault.isLiquidatable(manager), "Not liquidatable");
        
        liquidationId = keccak256(abi.encode(
            address(vault), 
            manager, 
            block.number
        ));
        
        LiquidationState storage state = liquidations[liquidationId];
        state.manager = manager;
        state.totalDebt = vault.getTrueDebt(manager);
        
        // Get all chains with remote positions
        uint256[] memory remoteChains = vault.getRemoteChains(manager);
        state.pendingRecalls = remoteChains.length;
        
        // Send recall messages to each chain
        for (uint256 i = 0; i < remoteChains.length; i++) {
            bytes memory recallPayload = abi.encode(
                RemoteOperation.LIQUIDATION_RECALL,
                liquidationId,
                address(vault),
                manager
            );
            
            router.executeRemote(remoteChains[i], recallPayload);
            state.chainRecalled[remoteChains[i]] = true;
        }
        
        emit LiquidationInitiated(liquidationId, manager, remoteChains.length);
    }

    /// @notice Receive recalled assets from remote chain
    function receiveRecalledAssets(
        bytes32 liquidationId,
        uint256 sourceChainId,
        uint256 assetValue
    ) external onlyCrossChainReceiver {
        LiquidationState storage state = liquidations[liquidationId];
        
        require(state.chainRecalled[sourceChainId], "Not recalled");
        
        state.recalledValue += assetValue;
        state.pendingRecalls--;
        
        // If all assets recalled, finalize liquidation
        if (state.pendingRecalls == 0) {
            _finalizeLiquidation(liquidationId);
        }
    }

    function _finalizeLiquidation(bytes32 liquidationId) internal {
        LiquidationState storage state = liquidations[liquidationId];
        
        // Repay debt to vault
        uint256 repayAmount = Math.min(state.recalledValue, state.totalDebt);
        vault.repayOnBehalf(state.manager, repayAmount);
        
        // Calculate liquidator profit
        uint256 profit = (repayAmount * LIQUIDATION_PROFIT_BPS) / 10000;
        
        // Return remaining equity to manager
        uint256 remaining = state.recalledValue - repayAmount - profit;
        if (remaining > 0) {
            IERC20(USDC).transfer(state.manager, remaining);
        }
        
        emit LiquidationFinalized(liquidationId, repayAmount, profit);
    }
}
```

***

### Cross-Chain Oracle Synchronization

#### Oracle Price Attestations

Remote chains attest their oracle prices to ensure consistent valuation:

```solidity
contract CrossChainOracleSync {
    struct PriceAttestation {
        address asset;
        uint256 price;
        uint256 timestamp;
        uint256 sourceChainId;
        bytes32 oracleSource;  // e.g., "CHAINLINK", "PYTH"
    }
    
    /// @notice Aggregate prices from multiple chains
    function getConsensusPriceUSD(
        address asset
    ) external view returns (uint256 price) {
        PriceAttestation[] memory attestations = recentAttestations[asset];
        
        require(attestations.length >= MIN_ATTESTATIONS, "Insufficient attestations");
        
        // Use median price for manipulation resistance
        uint256[] memory prices = new uint256[](attestations.length);
        for (uint256 i = 0; i < attestations.length; i++) {
            // Only use fresh attestations
            require(
                block.timestamp - attestations[i].timestamp < MAX_PRICE_AGE,
                "Stale attestation"
            );
            prices[i] = attestations[i].price;
        }
        
        return _median(prices);
    }

    /// @notice Receive price attestation from remote chain
    function receivePriceAttestation(
        uint256 sourceChainId,
        address asset,
        uint256 price,
        bytes32 oracleSource
    ) external onlyCrossChainReceiver {
        recentAttestations[asset].push(PriceAttestation({
            asset: asset,
            price: price,
            timestamp: block.timestamp,
            sourceChainId: sourceChainId,
            oracleSource: oracleSource
        }));
        
        emit PriceAttested(asset, price, sourceChainId, oracleSource);
    }
}
```

***

### Security Considerations

#### 1. Message Verification

Always verify cross-chain message authenticity:

```solidity
modifier onlyTrustedBridge(uint256 sourceChainId, address sender) {
    require(
        trustedBridges[sourceChainId] == msg.sender,
        "Untrusted bridge"
    );
    require(
        trustedSenders[sourceChainId] == sender,
        "Untrusted sender"
    );
    _;
}
```

#### 2. Replay Protection

Prevent message replay attacks:

```solidity
mapping(bytes32 => bool) public processedMessages;

function receiveMessage(bytes32 messageId, bytes calldata payload) external {
    require(!processedMessages[messageId], "Already processed");
    processedMessages[messageId] = true;
    
    // Process message...
}
```

#### 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              |

```solidity
/// @notice Configure finality requirements per chain
mapping(uint256 => uint256) public requiredConfirmations;

function setFinalityRequirement(
    uint256 chainId,
    uint256 confirmations
) external onlyGovernance {
    requiredConfirmations[chainId] = confirmations;
}
```

#### 4. Bridge Risk Mitigation

```solidity
/// @notice Maximum value that can be bridged per transaction
mapping(uint256 => uint256) public bridgeLimits;

/// @notice Cooldown between large bridge operations
uint256 public constant LARGE_BRIDGE_COOLDOWN = 1 hours;

function validateBridgeOperation(
    uint256 destinationChainId,
    uint256 amount
) internal {
    require(amount <= bridgeLimits[destinationChainId], "Exceeds bridge limit");
    
    if (amount > LARGE_BRIDGE_THRESHOLD) {
        require(
            block.timestamp - lastLargeBridge[destinationChainId] >= LARGE_BRIDGE_COOLDOWN,
            "Cooldown active"
        );
        lastLargeBridge[destinationChainId] = block.timestamp;
    }
}
```

#### 5. Attestation Liveness

Ensure remote positions are regularly attested:

```solidity
/// @notice Force attestation refresh if stale
function refreshAttestation(
    address vault,
    address manager,
    bytes32 positionId
) external {
    CachedValue memory cached = cachedValues[vault][manager][positionId];
    
    require(
        block.timestamp - cached.attestedAt > STALE_THRESHOLD,
        "Not stale"
    );
    
    // Request fresh attestation from remote chain
    bytes memory payload = abi.encode(
        RemoteOperation.REQUEST_ATTESTATION,
        vault,
        manager,
        positionId
    );
    
    router.executeRemote(destinationChainId, payload);
    
    emit AttestationRequested(vault, manager, positionId);
}
```

***

### Best Practices

#### For Vault Curators

1. **Set Conservative Attestation Windows** — Require attestations every 30-60 minutes for volatile assets
2. **Configure Bridge Limits** — Cap per-transaction and daily bridge volumes
3. **Whitelist Chains Carefully** — Only enable chains with proven bridge security
4. **Monitor Attestation Liveness** — Alert if attestations stop arriving

#### For Managers

1. **Understand Finality Delays** — Cross-chain operations are not instant
2. **Maintain Buffer Above Liquidation** — Account for attestation lag in health calculations
3. **Diversify Across Bridges** — Don't concentrate all value through one bridge
4. **Monitor Gas Costs** — Cross-chain operations incur fees on multiple networks

#### For Developers

1. **Implement Idempotent Handlers** — Remote operations may be retried
2. **Use Optimistic Updates Carefully** — Always validate with attestations
3. **Handle Partial Failures** — Design for cases where some chains succeed, others fail
4. **Test with Realistic Delays** — Simulate 10-30 minute message delivery in tests

***

### Related Concepts

* [Vaults ](/learn/concepts/vaults.md)— Core vault architecture and position model
* [Asset Wrappers](/learn/concepts/asset-wrappers.md) — How wrappers abstract asset interactions
* [Oracle Registry](/learn/concepts/oracle-registry.md) — Price oracle integration
* [Strategies](/learn/concepts/strategies.md) — Strategy contracts and risk parameters
* [Liquidation](/learn/concepts/liquidation.md) — Health calculations and liquidation mechanics


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.useflux.xyz/learn/concepts/omnichain-vaults.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
