Abstract
By standardizing cross-chain bridge transaction event, it establishes a basis for regulatory compliance and user verification. This EIP contributes to improved security monitoring, and forensic analysis of cross-chain fund flows.
Motivation
The cross-chain ecosystem is currently experiencing rapid expansion, driven by a continuous increase in the number of blockchains and cross-chain bridge assets. However, as each bridge implementation typically uses its own proprietary event formats, the transaction tracking mechanism has become increasingly fragmented. This creates opportunities for illicit funds to be laundered through these cross-chain services which poses significant challenges to the cross-chain ecosystem in terms of security and compliance, and incident response capabilities, directly impacting users, project teams, and regulatory bodies.
The specific problems include:
- Lack of Standardized Event Logging: The bridge contract on the destination chain may not record transaction events at all, or different bridges use incompatible message structures.
- Absence of a Universal Identifier: The lack of a unified cross-chain transaction identifier, coupled with differences in event field names and formats, results in the separation of transaction records between the source and destination chains, making them impossible to correlate.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Standard Event Formats
event CrossChainTransactionInitiated(
bytes32 indexed messageId, // Cross-chain message unique identifier
address indexed destChainBridge, // Bridge contract address emitting CrossChainTransferCompleted event on destination chain
uint256 indexed destChainId, // Destination chain ID
bytes messageBody,
uint256 nonce
);
- The
CrossChainTransactionInitiatedeventMUST be emitted when a cross-chain transaction is initiated on the source chain, generating a cross-chain transaction event. - This event is monitored by existing off-chain indexers, as long as the event is supported. This event field can be added on top of existing bridges as an extended event in the source chain or as a standard event field of a bridge event.
- The
messageBodyfield is protocol-specific and allowed to be customized while it SHOULD contain necessary cross-chain transaction data to maintain traceability. - The
indexedkeyward is used to declare parameters in an event and indicate that the value of that parameter SHOULD be indexed.
event CrossChainMessageCompleted(
bytes32 indexed messageId,
bytes32 indexed messageBodyHash // Used to verify if the messageBody is consistent.
)
- The
CrossChainTransferCompletedevent MUST be emitted when a cross-chain transaction is successfully completed on the target chain. - The definiation of
messageBodyHashismessageBodyHash = keccak256(messageBody), which is used to verify the integrity of cross-chain message.
Cross-Chain Message ID Generation
The messageId in cross-chain events is encoded as follow:
messageId = keccak256(sourceChainId || sourceChainBridge || destChainId || destChainBridge || nonce)
It SHALL be generated using this structure to ensure global uniqueness, preventing replay attack, where:
- All fields are deterministically encoded
- The
nonceis a strictly increasing value per sender or channel on the source chain.
function generateCrossChainMessageId(
uint256 sourceChainId, // Source chain ID
address sourceChainBridge, // Source chain bridge contract address
uint256 destChainId, // Destination chain ID
address destChainBridge, // Destination chain bridge contract address
uint256 nonce // Message sequence number
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
sourceChainId,
sourceChainBridge,
destChainId,
destChainBridge,
nonce
));
}
Implementation Requirements
- Bridge contracts MAY implement these events as an extension to existing backwards compatibility while still achieving cross-chain transaction tracking.
- The content format of the
messageBodyfield is protocol-specific but SHOULD contain necessary cross-chain transaction data. - The event MUST be triggered at the appropriate stage of the transaction lifecycle, and all fields in the event MUST be valid. The
CrossChainTransactionInitiatedevent SHOULD be sent when the message is sent on the original chain, and theCrossChainTransferCompletedevent SHOULD be sent when funds are withdrawn from the target chain.
Rationale
Minimal Design Approac:The standard adopts a minimal design philosophy which include only necessary events to avoid over-standardization while providing sufficient tracking capabilities.
Implementation Flexibility:The messageBody field allows individual bridge protocols to include protocol-specific execution data while maintaining event structure consistency.
messageBody = (messageBodyDomain || sourceTokenSender || destTokenReceiver || sourceTokenAddress || destTokenAddress || amount)
e.g:messageBodyDomain = “TOKEN_BRIDGE”,sourceTokenSender = “Source chain token sender address”, destTokenReceiver = “Destination chain token receiver address”,sourceTokenAddress = “Source chain token address”,destTokenAddress = “Destination chain token address” ,amount = “Transfer amount”.
Cross-chain message ID SHOULD be generated using the following method to ensure uniqueness:
function generateCrossChainMessageHash(
address messageBodyDomain, // Type identifier for message body decoding
address sourceTokenSender, // Source chain token sender address
address destTokenReceiver, // Destination chain token receiver address
address sourceTokenAddress, // Source chain token address
address destTokenAddress // Destination chain token address
uint256 amount // Transfer amount
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
messageBodyDomain,
sourceTokenSender,
destTokenReceiver,
sourceTokenAddress,
destTokenAddress,
amount
));
}
Guarantee Uniqueness:The messageId generation algorithm combines multiple chain-specific and transaction-specific parameters to ensure sufficient entropy, preventing overlapping with different bridge implementations.
Event Indexing:Indexing of messageId, destChainId, and destChainBridge supports efficient filtering and querying for common use cases, such as tracking specific destination chains or bridge contracts for transaction correlation.
Backwards Compatibility
This EIP is fully backward compatible as it defines new event interfaces without modifying existing contract functionality. Bridge implementations MAY choose to implement these events alongside existing event structures. No breaking changes are introduced to existing smart contracts or protocols. Bridge protocols MAY adopt this standard gradually according to specific requirements.
Test cases
Example for tracking bridge event with this EIP standard:
- Simulate the processing of cross-chain transactions
- Monitor the source chain event (
CrossChainTransactionInitiated) of cross-chain transactions to getmessageIdandmessageBodyHash - Query the target chain event (
CrossChainMessageCompleted) of the bridge contract to associate it with the cross-chain transaction, and verify it by cross-checkingmessageIdandmessageBodyHash. Once the verification is successful, it can be associated with the target chain monitoring object through thedestChainId+destBridgeAddressfields in the source event
Figure 1: Simulation of source chain and target chain event generation
Figure 2:Cross-chain message verification
Reference Implementation
pragma solidity ^0.8.0;
/**
* @title CrossChainTrackingStandard
* @dev Implementation of EIP-XXXX Cross-Chain Transaction Tracking Standard
* Provides standardized event tracking for cross-chain transaction lifecycle
* This contract serves as a base implementation for cross-chain bridge protocols
* to ensure consistent transaction tracking across different blockchain networks
*/
contract CrossChainTrackingStandard {
/// @dev Transaction sequence counter to ensure unique nonce for each transaction
/// @notice This counter increments with each initiated transaction to prevent replay attacks
uint256 public nonce;
// Mapping to track processed messages to prevent replay attacks
mapping(bytes32 => bool) private _processedMessages;
/**
* @dev Emitted when a cross-chain transaction is initiated on the source chain
* @param messageId Cross-chain message unique identifier
* @param destChainBridge Bridge contract address on destination chain
* @param destChainId Destination chain ID
* @param messageBody Cross-chain message body containing transaction data
* @param nonce Cross-chain message sequence number
*/
event CrossChainTransactionInitiated(
bytes32 indexed messageId,
address indexed destChainBridge,
uint256 indexed destChainId,
bytes messageBody,
uint256 nonce
);
/**
* @dev Emitted when a cross-chain transaction is successfully completed on the target chain
* @param messageId Cross-chain message unique identifier
* @param messageBodyHash Hash of the message body for verification
*/
event CrossChainMessageCompleted(
bytes32 indexed messageId,
bytes32 indexed messageBodyHash
);
/**
* @dev Generates a unique cross-chain message ID
* @param sourceChainId Source chain ID
* @param sourceChainBridge Bridge contract address on source chain
* @param destChainId Destination chain ID
* @param destChainBridge Destination chain bridge contract address
* @param _nonce Transaction sequence number
* @return messageId Unique cross-chain message identifier
*/
function generateCrossChainTxId(
uint256 sourceChainId,
address sourceChainBridge,
uint256 destChainId,
address destChainBridge,
uint256 _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
sourceChainId,
sourceChainBridge,
destChainId,
destChainBridge,
_nonce
));
}
/**
* @dev Generates hash for cross-chain message body
* @param messageBodyDomain Type identifier for message body decoding
* @param sourceTokenSender Source chain token sender address
* @param destTokenReceiver Destination chain token receiver address
* @param sourceTokenAddress Source chain token address
* @param destTokenAddress Destination chain token address
* @param amount Transfer amount
* @return messageBodyHash Hash of the message body
*/
function generateCrossChainMessageHash(
bytes32 messageBodyDomain,
address sourceTokenSender,
address destTokenReceiver,
address sourceTokenAddress,
address destTokenAddress,
uint256 amount
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(
messageBodyDomain,
sourceTokenSender,
destTokenReceiver,
sourceTokenAddress,
destTokenAddress,
amount
));
}
/**
* @dev Initiates a cross-chain transaction and emits the standard event
* @notice messageBody MUST include messageBodyDomain as the first field for type identification
* @param destChainBridge Bridge contract address on destination chain
* @param destChainId Destination chain ID
* @param messageBody Encoded message body with messageBodyDomain as type identifier
* @return messageId Generated message ID for this transaction
*/
function initiateCrossChainTransfer(
address destChainBridge,
uint256 destChainId,
bytes memory messageBody
) external returns (bytes32) {
uint256 _currentNonce = ++nonce;
bytes32 messageId = generateCrossChainTxId(
block.chainid, // Current chain ID as source chain ID (EIP-155)
address(this), // Source chain bridge contract address (current contract)
destChainId,
destChainBridge,
_currentNonce
);
emit CrossChainTransactionInitiated(
messageId,
destChainBridge,
destChainId,
messageBody,
_currentNonce
);
return messageId;
}
/**
* @dev Completes a cross-chain transaction and emits the completion event
* @notice Implementations SHOULD decode messageBody based on messageBodyDomain type
* @param messageId Cross-chain message unique identifier
* @param messageBody Cross-chain message body
* @return success Whether the completion was successful
*/
function completeCrossChainMessage(
bytes32 messageId,
bytes memory messageBody
) external returns (bool) {
// NOTE: Require additional signature verification
require(!_processedMessages[messageId], "Message already processed");
_processedMessages[messageId] = true;
bytes32 messageBodyHash = keccak256(messageBody);
emit CrossChainMessageCompleted(messageId, messageBodyHash);
return true;
}
}
Security Considerations
- Transaction ID Collision Resistance:Implementations MUST ensure transaction ID generation has sufficient entropy to prevent collision attacks. The recommended keccak256 algorithm provides adequate collision resistance for typical cross-chain bridge usage, reducing the likelihood of enumeration and guessing attacks.
- Prevention of Attack Forgery: The source chain event includes an additional
destChainBridgefield to correlate the response status of cross-chain transactions with the target chain address. Since transactions that fail cross-chain validation will revert directly, and the target chain only records events from successfully validated transactions, even if an attacker attempts to confuse monitoring by forging a large number of identical cross-chain transactions simultaneously, it will not ultimately affect the correlation between source and target chain transactions. - Bridge-Specific Security:This standard only defines event interfaces. Individual bridge implementations remain responsible for their protocol-specific security considerations, including but not limited to multi-signature validator sets, transaction data validation, economic security, cryptographic proofs, and ensuring bridge protocols undergo security audits.
- Event Data Verification:Bridge protocols SHOULD implement appropriate verification mechanisms to ensure the authenticity and integrity of event data. Event emission does not guarantee transaction success; protocols MUST verify on-chain state changes.
Copyright
Copyright and related rights waived via CC0.

