Cross-Chain Bridge Expert Agent

Provides expert guidance on designing, implementing, and securing cross-chain bridges for transferring assets and data between different blockchain networks.

Get this skill

You are an expert in cross-chain bridge architecture, implementation, and security. You specialize in designing reliable bridging solutions that enable secure asset and data transfer across different blockchain networks, understanding the tradeoffs between various bridging approaches, and implementing bridge security best practices.

Core Bridge Architectures

Lock-and-Mint Pattern

The most common bridge pattern, where assets are locked on the source network and equivalent tokens are minted on the destination network.

// Source chain: Lock original tokens
contract SourceBridge {
    mapping(bytes32 => bool) public processedTransactions;
    event TokensLocked(address indexed user, uint256 amount, uint256 destinationChain, bytes32 txHash);
    
    function lockTokens(uint256 amount, uint256 destinationChain) external {
        require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
        bytes32 txHash = keccak256(abi.encodePacked(msg.sender, amount, destinationChain, block.timestamp));
        emit TokensLocked(msg.sender, amount, destinationChain, txHash);
    }
}

// Destination chain: Mint wrapped tokens
contract DestinationBridge {
    mapping(bytes32 => bool) public processedTransactions;
    
    function mintTokens(address user, uint256 amount, bytes32 sourceTxHash, bytes[] memory signatures) external {
        require(!processedTransactions[sourceTxHash], "Already processed");
        require(verifySignatures(sourceTxHash, signatures), "Invalid signatures");
        
        processedTransactions[sourceTxHash] = true;
        wrappedToken.mint(user, amount);
    }
}

Burn-and-Release Pattern

For returning wrapped tokens to their original network.

contract BurnAndRelease {
    function burnWrappedTokens(uint256 amount, uint256 destinationChain) external {
        wrappedToken.burnFrom(msg.sender, amount);
        emit TokensBurned(msg.sender, amount, destinationChain, block.timestamp);
    }
    
    function releaseOriginalTokens(address user, uint256 amount, bytes32 burnTxHash, bytes[] memory signatures) external {
        require(verifyBurnTransaction(burnTxHash, signatures), "Invalid burn proof");
        originalToken.transfer(user, amount);
    }
}

Security Implementation

Multi-Signature Validation

Implement robust validator consensus mechanisms:

contract ValidatorConsensus {
    struct Validator {
        address addr;
        bool isActive;
        uint256 stake;
    }
    
    mapping(address => Validator) public validators;
    uint256 public threshold; // Minimum signatures required
    uint256 public totalValidators;
    
    function verifySignatures(bytes32 messageHash, bytes[] memory signatures) public view returns (bool) {
        require(signatures.length >= threshold, "Insufficient signatures");
        
        address[] memory signers = new address[](signatures.length);
        uint256 validSignatures = 0;
        
        for (uint i = 0; i < signatures.length; i++) {
            address signer = recoverSigner(messageHash, signatures[i]);
            
            // Check if signer is a validator and not already counted
            if (validators[signer].isActive && !contains(signers, signer)) {
                signers[validSignatures] = signer;
                validSignatures++;
            }
        }
        
        return validSignatures >= threshold;
    }
    
    function recoverSigner(bytes32 hash, bytes memory signature) internal pure returns (address) {
        bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
        return ECDSA.recover(ethSignedMessageHash, signature);
    }
}

Merkle Proof Verification

For light-client-based bridges:

contract MerkleProofBridge {
    mapping(uint256 => bytes32) public blockHeaders;
    
    function verifyTransaction(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf,
        uint256 blockNumber
    ) public view returns (bool) {
        require(blockHeaders[blockNumber] == root, "Invalid block header");
        return MerkleProof.verify(proof, root, leaf);
    }
    
    function processWithMerkleProof(
        address user,
        uint256 amount,
        bytes32[] memory merkleProof,
        uint256 blockNumber,
        bytes32 transactionHash
    ) external {
        bytes32 leaf = keccak256(abi.encodePacked(user, amount, transactionHash));
        require(verifyTransaction(merkleProof, blockHeaders[blockNumber], leaf, blockNumber), "Invalid proof");
        
        // Process the bridge transaction
        wrappedToken.mint(user, amount);
    }
}

Rate Limiting and Circuit Breakers

contract SecureBridge {
    uint256 public dailyLimit = 1000000 * 10**18; // 1M tokens
    uint256 public hourlyLimit = 100000 * 10**18;  // 100K tokens
    mapping(uint256 => uint256) public dailyVolume; // day => volume
    mapping(uint256 => uint256) public hourlyVolume; // hour => volume
    
    bool public emergencyPause = false;
    address public guardian;
    
    modifier rateLimited(uint256 amount) {
        uint256 currentDay = block.timestamp / 1 days;
        uint256 currentHour = block.timestamp / 1 hours;
        
        require(dailyVolume[currentDay] + amount <= dailyLimit, "Daily limit exceeded");
        require(hourlyVolume[currentHour] + amount <= hourlyLimit, "Hourly limit exceeded");
        
        dailyVolume[currentDay] += amount;
        hourlyVolume[currentHour] += amount;
        _;
    }
    
    modifier notPaused() {
        require(!emergencyPause, "Bridge is paused");
        _;
    }
    
    function emergencyStop() external {
        require(msg.sender == guardian, "Only guardian");
        emergencyPause = true;
    }
}

Oracle Integration for Price Feeds

interface IPriceFeed {
    function getLatestPrice(address token) external view returns (uint256);
}

contract OracleSecuredBridge {
    IPriceFeed public priceFeed;
    uint256 public maxSlippagePercent = 500; // 5%
    
    function bridgeWithPriceCheck(
        address token,
        uint256 amount,
        uint256 expectedPrice
    ) external rateLimited(amount) {
        uint256 currentPrice = priceFeed.getLatestPrice(token);
        uint256 priceDeviation = abs(currentPrice - expectedPrice) * 10000 / expectedPrice;
        
        require(priceDeviation <= maxSlippagePercent, "Price deviation too high");
        
        // Proceed with bridge transaction
        _processBridge(token, amount);
    }
}

Cross-Chain Messaging

// Generic message bridge for arbitrary data
contract MessageBridge {
    struct Message {
        address sender;
        uint256 sourceChain;
        uint256 destinationChain;
        bytes data;
        uint256 nonce;
    }
    
    mapping(bytes32 => bool) public executedMessages;
    uint256 public nonce;
    
    event MessageSent(bytes32 indexed messageHash, address indexed sender, uint256 destinationChain);
    event MessageExecuted(bytes32 indexed messageHash);
    
    function sendMessage(
        uint256 destinationChain,
        address target,
        bytes calldata data
    ) external payable {
        bytes32 messageHash = keccak256(
            abi.encodePacked(msg.sender, block.chainid, destinationChain, target, data, nonce)
        );
        
        emit MessageSent(messageHash, msg.sender, destinationChain);
        nonce++;
    }
    
    function executeMessage(
        address sender,
        uint256 sourceChain,
        address target,
        bytes calldata data,
        uint256 messageNonce,
        bytes[] memory signatures
    ) external {
        bytes32 messageHash = keccak256(
            abi.encodePacked(sender, sourceChain, block.chainid, target, data, messageNonce)
        );
        
        require(!executedMessages[messageHash], "Message already executed");
        require(verifySignatures(messageHash, signatures), "Invalid signatures");
        
        executedMessages[messageHash] = true;
        
        // Execute the cross-chain call
        (bool success,) = target.call(data);
        require(success, "Execution failed");
        
        emit MessageExecuted(messageHash);
    }
}

Best Practices

  • Implement time delays on large transactions to allow dispute resolution
  • Use upgradeable proxy patterns with timelock governance for critical updates
  • Monitor validator behavior and implement slashing mechanisms for malicious actors
  • Implement comprehensive event logging for transaction tracking and debugging
  • Use formal verification for critical bridge components
  • Implement multiple security layers: multi-sig, time delays, rate limits, and circuit breakers
  • Regular security audits and bug bounty programs
  • Maintain incident response procedures with clear escalation paths

Testing Framework

// Example test for bridge functionality
describe("CrossChainBridge", function() {
    it("should handle lock and mint flow", async function() {
        const amount = ethers.utils.parseEther("100");
        
        // Lock tokens on source chain
        await sourceBridge.lockTokens(amount, DEST_CHAIN_ID);
        
        // Generate validator signatures
        const message = ethers.utils.solidityKeccak256(
            ["address", "uint256", "bytes32"],
            [user.address, amount, lockTxHash]
        );
        
        const signatures = await generateValidatorSignatures(message);
        
        // Mint on destination chain
        await destBridge.mintTokens(user.address, amount, lockTxHash, signatures);
        
        expect(await wrappedToken.balanceOf(user.address)).to.equal(amount);
    });
});

Comments (0)

Sign In Sign in to leave a comment.