CrosschainLinkLib
Title: CrosschainLinkLib
Authors: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80), Adapted for EIP-2535 from OpenZeppelin CrosschainLinked + ERC7786Recipient v5.6.1 (https://github.com/OpenZeppelin/openzeppelin-contracts)
Logic + ERC-7201 storage for the CrosschainLink facet: an ERC-7786 send + receive endpoint
with a per-chain (gateway, counterpart) link registry and tag-routed inbound handlers.
Divergence from OZ: ERC-7786's receiveMessage is a single Diamond selector, so this one library
authenticates AND dispatches (OZ leaves _processMessage to a subclass). Dispatch is by the
payload's leading 4-byte tag → a registered handler. Unlike OZ's ERC7786Recipient (which trusts
the gateway for at-most-once delivery), this adds defensive receiveId replay protection because
a Diamond may authorize more than one gateway (uniqueness is only guaranteed per-gateway).
Functions
crosschainLinkStorage
Returns the ERC-7201 storage struct for CrosschainLink.
function crosschainLinkStorage() internal pure returns (CrosschainLinkStorage storage $);Returns
| Name | Type | Description |
|---|---|---|
| $ | CrosschainLinkStorage |
__CrosschainLink_init
Registers the ICrosschainLink ERC-165 interface.
Must be called between preInitializer / postInitializer.
function __CrosschainLink_init() internal;registerInterface
Writes true to the ERC-165 map slot for ICrosschainLink.
function registerInterface() internal;getLink
Returns the gateway and counterpart registered for a chain-only interoperable address.
function getLink(bytes memory chain) internal view returns (address gateway, bytes memory counterpart);Parameters
| Name | Type | Description |
|---|---|---|
| chain | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| gateway | address | |
| counterpart | bytes |
getHandler
Returns the handler registered for a message tag (zero if none).
function getHandler(bytes4 tag) internal view returns (address);Parameters
| Name | Type | Description |
|---|---|---|
| tag | bytes4 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | address |
isProcessed
Returns whether a receiveId from a given gateway has already been processed.
Keyed per-gateway (ERC-7786 only guarantees receiveId uniqueness for the calling gateway).
function isProcessed(address gateway, bytes32 receiveId) internal view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| gateway | address | |
| receiveId | bytes32 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
receiveMessage
Authenticates, de-duplicates, and dispatches an inbound ERC-7786 message.
Checks-effects-interactions: receiveId is marked used (effect) BEFORE the handler call
(interaction); a reverting handler rolls that back, keeping the message retryable.
function receiveMessage(bytes32 receiveId, bytes calldata sender, bytes calldata payload)
internal
returns (bytes4);Parameters
| Name | Type | Description |
|---|---|---|
| receiveId | bytes32 | |
| sender | bytes | |
| payload | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes4 | The ERC-7786 magic value IERC7786Recipient.receiveMessage.selector (0x2432ef26). |
setLink
Registers (or overrides) the gateway + counterpart for the counterpart's source chain.
Caller must hold DEFAULT_ADMIN_ROLE.
function setLink(address gateway, bytes calldata counterpart, bool allowOverride) internal;Parameters
| Name | Type | Description |
|---|---|---|
| gateway | address | |
| counterpart | bytes | |
| allowOverride | bool |
setHandler
Registers a handler for inbound messages whose payload starts with tag (zero to clear).
Caller must hold DEFAULT_ADMIN_ROLE.
function setHandler(bytes4 tag, address handler) internal;Parameters
| Name | Type | Description |
|---|---|---|
| tag | bytes4 | |
| handler | address |
sendMessage
Sends a message to the counterpart registered for chain via its ERC-7786 gateway.
Caller must hold DEFAULT_ADMIN_ROLE. Admin entrypoint for ad-hoc messages (e.g. governance signalling); handler/bridge logic uses the un-gated sendToCounterpart directly.
function sendMessage(bytes calldata chain, bytes calldata payload, bytes[] calldata attributes)
internal
returns (bytes32);Parameters
| Name | Type | Description |
|---|---|---|
| chain | bytes | |
| payload | bytes | |
| attributes | bytes[] |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes32 |
sendToCounterpart
Sends a message to the counterpart registered for chain via its ERC-7786 gateway.
NO access control — callable only from in-Diamond facet logic (it is internal). Bridge
handlers call this; their own token mechanics (burn/lock the caller's funds) are the gate.
External callers can only reach the gated sendMessage selector, never this directly.
function sendToCounterpart(bytes memory chain, bytes memory payload, bytes[] memory attributes)
internal
returns (bytes32);Parameters
| Name | Type | Description |
|---|---|---|
| chain | bytes | |
| payload | bytes | |
| attributes | bytes[] |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes32 |
_isAuthorizedGateway
Whether instance is the registered gateway AND sender is the registered counterpart for
the source chain encoded in sender.
function _isAuthorizedGateway(address instance, bytes calldata sender) private view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| instance | address | |
| sender | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
_extractChain
Reduces a full interoperable address to its "chain-only" form (drops the address part).
function _extractChain(bytes memory self) private pure returns (bytes memory);Parameters
| Name | Type | Description |
|---|---|---|
| self | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes |