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