ERC-7786
ERC-7786¶
Overview¶
ERC-7786 (Cross-Chain Messaging Gateway) is Ethereum's cross-chain messaging gateway standard, released in October 2024. This standard provides a unified interface for contracts to send and receive cross-chain messages containing arbitrary data, aiming to solve the current fragmentation problem in cross-chain communication protocols.
ERC-7786 is part of the Ethereum Foundation's Interoperability Working Group initiative, dedicated to creating a truly interoperable blockchain ecosystem.
Background Problem¶
Cross-Chain Protocol Fragmentation The current cross-chain communication ecosystem faces severe fragmentation: - LayerZero, Axelar, Wormhole, Hyperlane, and other protocols each operate independently - Each protocol has its own interface and workflow - Applications need to write adapter code for each protocol - Cannot easily switch or use multiple protocols simultaneously
Developer Dilemma
// Using LayerZero
function sendToLayerZero(...) {
lzEndpoint.send(...);
}
// Using Axelar
function sendToAxelar(...) {
axelarGateway.callContract(...);
}
// Using Wormhole
function sendToWormhole(...) {
wormholeRelayer.sendPayloadToEvm(...);
}
// Each protocol requires different code!
Vendor Lock-in - Once a protocol is chosen, migration is difficult - Bound to a single protocol's limitations and risks - Cannot leverage the advantages of multiple protocols
ERC-7786 Solution¶
Unified Interface A standardized gateway interface that all cross-chain protocols can implement:
interface IGateway {
function sendMessage(CrossChainMessage calldata message) external payable returns (bytes32 messageId);
function receiveMessage(CrossChainMessage calldata message) external;
}
Adapter Pattern Existing protocols can implement ERC-7786 through adapters:
Protocol Agnostic Applications can easily switch protocols or use multiple protocols simultaneously.
Core Concepts¶
CrossChainMessage Standard structure for cross-chain messages:
struct CrossChainMessage {
address sender; // Sender address
uint256 sourceChainId; // Source chain ID
address recipient; // Recipient address
uint256 destinationChainId; // Destination chain ID
bytes payload; // Message payload
uint256 value; // Native token amount
Attribute[] attributes; // Extensible attributes
}
Attribute An extensible key-value pair system:
Attribute examples: - Timeout duration - Retry strategy - Fee payment method - Security level - Execution conditions
Core Interface¶
Send Message
function sendMessage(
CrossChainMessage calldata message
) external payable returns (bytes32 messageId);
Receive Message
Query Functions
// Check gateway capabilities
function supportsAttribute(bytes32 attributeKey) external view returns (bool);
// Estimate fees
function estimateFee(CrossChainMessage calldata message) external view returns (uint256);
// Query message status
function getMessageStatus(bytes32 messageId) external view returns (MessageStatus);
Events
event MessageSent(
bytes32 indexed messageId,
address indexed sender,
uint256 indexed destinationChainId,
address recipient,
bytes payload
);
event MessageReceived(
bytes32 indexed messageId,
address indexed recipient,
uint256 indexed sourceChainId,
address sender,
bytes payload
);
Implementation Examples¶
Application Contract
contract CrossChainApp {
IGateway public gateway;
constructor(address _gateway) {
gateway = IGateway(_gateway);
}
// Send cross-chain message
function sendCrossChain(
uint256 destinationChainId,
address recipient,
bytes calldata data
) external payable {
CrossChainMessage memory message = CrossChainMessage({
sender: address(this),
sourceChainId: block.chainid,
recipient: recipient,
destinationChainId: destinationChainId,
payload: data,
value: 0,
attributes: new Attribute[](0)
});
bytes32 messageId = gateway.sendMessage{value: msg.value}(message);
emit CrossChainSent(messageId, destinationChainId, recipient);
}
// Receive cross-chain message
function receiveMessage(
CrossChainMessage calldata message
) external {
require(msg.sender == address(gateway), "Only gateway");
// Process message
_handleMessage(message.payload);
emit CrossChainReceived(message.sourceChainId, message.sender);
}
function _handleMessage(bytes calldata payload) internal {
// Decode and process message
// ...
}
}
Gateway Adapter (LayerZero Example)
contract LayerZeroGateway is IGateway {
ILayerZeroEndpoint public lzEndpoint;
function sendMessage(
CrossChainMessage calldata message
) external payable returns (bytes32) {
// Convert ERC-7786 message to LayerZero format
uint16 dstChainId = _chainIdToLZ(message.destinationChainId);
bytes memory payload = abi.encode(message);
// Send via LayerZero
lzEndpoint.send{value: msg.value}(
dstChainId,
abi.encodePacked(message.recipient),
payload,
payable(msg.sender),
address(0),
bytes("")
);
return keccak256(abi.encode(message));
}
// LayerZero callback
function lzReceive(
uint16 _srcChainId,
bytes calldata _srcAddress,
uint64 _nonce,
bytes calldata _payload
) external override {
require(msg.sender == address(lzEndpoint), "Only endpoint");
// Decode message
CrossChainMessage memory message = abi.decode(_payload, (CrossChainMessage));
// Forward to recipient
ICrossChainReceiver(message.recipient).receiveMessage(message);
emit MessageReceived(...);
}
}
Attribute System¶
Standard Attributes ERC-7786 defines some recommended standard attributes:
Timeout Attribute
bytes32 constant TIMEOUT_KEY = keccak256("erc7786.timeout");
Attribute memory timeoutAttr = Attribute({
key: TIMEOUT_KEY,
value: abi.encode(block.timestamp + 1 hours)
});
Fee Payment Method
bytes32 constant FEE_PAYMENT_KEY = keccak256("erc7786.feePayment");
enum FeePayment {
Source, // Pay on source chain
Destination, // Pay on destination chain
ThirdParty // Third-party payment
}
Attribute memory feeAttr = Attribute({
key: FEE_PAYMENT_KEY,
value: abi.encode(FeePayment.Source)
});
Execution **Gas Limit**
bytes32 constant GAS_LIMIT_KEY = keccak256("erc7786.gasLimit");
Attribute memory gasAttr = Attribute({
key: GAS_LIMIT_KEY,
value: abi.encode(500000)
});
Custom Attributes Protocols can define their own attributes:
bytes32 constant CUSTOM_SECURITY_KEY = keccak256("myprotocol.security.level");
Attribute memory securityAttr = Attribute({
key: CUSTOM_SECURITY_KEY,
value: abi.encode("high")
});
Key Advantages¶
Interoperability - Applications write code once - Compatible with all protocols implementing ERC-7786 - Easily switch or combine multiple protocols
Reduced Vendor Lock-in
// Can easily switch protocols
gateway = new LayerZeroGateway();
// or
gateway = new AxelarGateway();
// or
gateway = new HyperlaneGateway();
// Application code needs no modification!
Simplified Development - Unified interface - Reduced learning costs - Less integration code - Standardized best practices
Protocol Competition - Protocols are interchangeable - Promotes innovation and optimization - Users choose the best solution
Extensibility - Attribute system supports new features - Backward compatible - Flexibly adapts to changing requirements
Use Cases¶
Cross-Chain *DeFi*
// Borrow on Ethereum, use on Arbitrum
function crossChainLend(
uint256 amount,
uint256 targetChainId,
address borrowerOnTarget
) external {
// Lock collateral on source chain
_lockCollateral(amount);
// Send cross-chain message to target chain
CrossChainMessage memory message = CrossChainMessage({
sender: address(this),
sourceChainId: block.chainid,
recipient: targetProtocol,
destinationChainId: targetChainId,
payload: abi.encodeWithSignature(
"releaseLoan(address,uint256)",
borrowerOnTarget,
amount
),
value: 0,
attributes: new Attribute[](0)
});
gateway.sendMessage(message);
}
Cross-Chain *NFTs*
// Move NFTs between chains
function bridgeNFT(uint256 tokenId, uint256 targetChainId) external {
require(ownerOf(tokenId) == msg.sender, "Not owner");
// Burn on source chain
_burn(tokenId);
// Notify target chain to mint
CrossChainMessage memory message = CrossChainMessage({
sender: address(this),
sourceChainId: block.chainid,
recipient: mirrorContractOnTarget,
destinationChainId: targetChainId,
payload: abi.encode(msg.sender, tokenId, _metadata[tokenId]),
value: 0,
attributes: new Attribute[](0)
});
gateway.sendMessage(message);
}
Cross-Chain Governance
// Initiate proposal on main chain, execute on multiple chains
function executeMultiChainProposal(
uint256[] calldata targetChains,
bytes[] calldata payloads
) external onlyGovernance {
for (uint i = 0; i < targetChains.length; i++) {
CrossChainMessage memory message = CrossChainMessage({
sender: address(this),
sourceChainId: block.chainid,
recipient: governanceExecutorOnTarget,
destinationChainId: targetChains[i],
payload: payloads[i],
value: 0,
attributes: new Attribute[](0)
});
gateway.sendMessage(message);
}
}
Cross-Chain Identity Users verify identity on one chain and use it on other chains: - Single Sign-On (SSO) - Cross-chain credit scores - Unified membership systems
Relationship with Other Standards¶
Difference from ERC-7683 - ERC-7683: Cross-chain intents, focused on value transfers and swaps - ERC-7786: General message passing, supporting arbitrary data and operations
The two can complement each other: - ERC-7683 for cross-chain trades - ERC-7786 for cross-chain messages and state synchronization
Relationship with EIP-5164 EIP-5164 is also a cross-chain messaging standard, but: - EIP-5164 is older with fewer features - ERC-7786 is more complete, supporting the attribute system - ERC-7786 has broader protocol support
Implementation Challenges¶
Security - Message verification: Ensure message authenticity - Replay protection: Prevent duplicate message execution - Permission checks: Verify sender identity
Fee Management - Cross-chain fee calculation is complex - Multiple fee payment methods - Price volatility impact
Protocol Differences - Different protocols have different security models - Confirmation time differences - Inconsistent feature support
Error Handling - Rolling back failed messages - Timeout handling - User notifications
Adoption Status¶
Protocol Support The following protocols are implementing or considering ERC-7786: - Hyperlane - LayerZero - Wormhole - Axelar - Chainlink CCIP
Application Integration - Cross-chain DEXs - Multi-chain DAOs - Cross-chain games - Unified liquidity pools
Development Tools¶
*OpenZeppelin* Contracts**
import "@openzeppelin/contracts/crosschain/ERC7786Gateway.sol";
contract MyGateway is ERC7786Gateway {
// Implement cross-chain logic
}
Testing Tools
import { testERC7786Compliance } from '@erc7786/test-suite';
describe('MyGateway', () => {
it('should comply with ERC-7786', async () => {
await testERC7786Compliance(gateway);
});
});
Best Practices¶
Message Design - Keep payloads compact - Use efficient encoding - Include necessary context
Error Handling
function receiveMessage(CrossChainMessage calldata message) external {
try this._handleMessage(message) {
emit MessageSuccess(message.messageId);
} catch Error(string memory reason) {
emit MessageFailed(message.messageId, reason);
// Optional: Store failed messages for retry
}
}
Retry Mechanism
mapping(bytes32 => CrossChainMessage) public failedMessages;
function retryMessage(bytes32 messageId) external {
CrossChainMessage memory message = failedMessages[messageId];
gateway.sendMessage{value: msg.value}(message);
}
Future Outlook¶
Ecosystem Development - More protocols adopting ERC-7786 - Standard libraries and tools maturing - Wallet and block explorer support
Standard Evolution - New standard attributes - More complex message types - Integration with other standards
True Chain Abstraction ERC-7786 is a key component for achieving chain abstraction: - Users do not need to worry about the underlying chain - Applications run seamlessly across chains - Unified liquidity
Related Links¶
- ERC-7786 Official Specification
- ERC-7786 Official Website
- Ethereum Interoperability Working Group
- OpenZeppelin Cross-Chain Documentation