ERC-7786: Cross-Chain Messaging Gateway
ERC-7786 standardises the contract-side interface for sending and receiving messages across blockchains. It abstracts over the messaging provider (LayerZero, Hyperlane, Wormhole, Axelar, CCIP) so application contracts can target the gateway interface and let operators choose the underlying transport. The motivation: Solidity dApps that wanted cross-chain capabilities had to write per-provider integrations and could not switch providers without rewriting the call sites. ERC-7786 picks one shape and standardises it.
Required Interface
interface IERC7786GatewaySource {
event MessagePosted(
bytes32 indexed outboxId,
string sender, // CAIP-10 source identifier
string receiver, // CAIP-10 destination identifier
bytes payload,
bytes[] attributes
);
function supportsAttribute(bytes4 selector) external view returns (bool);
function sendMessage(
string calldata destinationChain, // CAIP-2 chain id
string calldata receiver, // address on destination
bytes calldata payload,
bytes[] calldata attributes
) external payable returns (bytes32 outboxId);
}
interface IERC7786Receiver {
function executeMessage(
bytes32 messageId,
string calldata sourceChain, // CAIP-2 source chain id
string calldata sender, // CAIP-10 source address
bytes calldata payload,
bytes[] calldata attributes
) external returns (bytes4); // returns IERC7786Receiver.executeMessage.selector
}CAIP-2 / CAIP-10 are the chain-agnostic identifier standards (eip155:1 = Ethereum mainnet, eip155:42161 = Arbitrum, etc.). The standard is transport-agnostic: a Hyperlane-backed gateway and a LayerZero-backed gateway expose the same interface.
Neo Equivalent: Oracle + Bridge Attestation Pattern
Neo has two relevant primitives but no first-class cross-chain message bus:
- Native Oracle service — fetch arbitrary HTTPS data via consensus. Useful as the "fetch the source-chain proof" step inside a bridge.
- Bridge contracts — community-built bridges (Poly Network, Bywire, etc.) that maintain header relays + lock-mint contracts. Each one exposes its own interface today.
The cross-chain story for Neo is currently transport-specific, much as Ethereum was pre-7786. The mirror page documents the gap and proposes a Neo-side gateway interface that mirrors ERC-7786 so future bridge implementations can converge on a single dApp-facing shape.
| ERC-7786 (Ethereum) | Proposed Neo Equivalent | Status |
|---|---|---|
IERC7786GatewaySource.sendMessage(...) | IGateway.SendMessage(destChain, receiver, payload, attributes) on a per-bridge gateway contract | Per-bridge today; would be a Neo-side "ERC-7786 NEP" if formalised |
IERC7786Receiver.executeMessage(...) | OnInboundMessage(messageId, sourceChain, sender, payload, attributes) callback | Per-bridge today |
| CAIP-2 / CAIP-10 ids | Same identifiers (chain-agnostic by design) | Direct reuse |
| Selector handshake (0x675b049b) | Verify-method handshake on the receiver | Same affordance |
attributes array (gas limit, finality, etc.) | attributes array — same shape | |
| Provider choice (LayerZero / Hyperlane / Axelar) | Provider choice (Poly Network / Bywire / future Neo-native bridge) |
Where Neo's Native Primitives Fit
// Inside a Neo gateway contract — the SendMessage entry point posts an
// outbound message and emits an event the bridge relayer indexes.
[DisplayName("MessagePosted")]
public static event Action<ByteString, string, string, ByteString, object[]> OnMessagePosted;
public static ByteString SendMessage(
string destinationChain, // "eip155:1"
string receiver, // "eip155:1:0x…"
ByteString payload,
object[] attributes)
{
if (!Runtime.CheckWitness(GetAuthorisedSender())) throw new Exception("not authorised");
var outboxId = ComputeOutboxId(destinationChain, receiver, payload);
// Bridge-specific: deposit fee, escrow tokens, etc.
// ...
OnMessagePosted(outboxId, GetSelfCAIP10(), receiver, payload, attributes);
return outboxId;
}
// Inbound side: the bridge relayer calls this on the destination Neo contract
// after verifying the source-chain proof off-chain (or via Oracle).
public static ByteString ExecuteMessage(
ByteString messageId,
string sourceChain,
string sender,
ByteString payload,
object[] attributes)
{
if (Runtime.CallingScriptHash != BridgeRelayer()) throw new Exception("not relayer");
// Process payload — application-specific.
return (ByteString)EXECUTE_MESSAGE_SELECTOR; // matches ERC-7786 handshake selector
}Why This is "Pattern" Not "Direct"
Neo doesn't yet have a canonical native bridge that wraps multiple underlying transports the way ERC-7786 does for Ethereum. Each Neo bridge ships its own send/receive shape. The mirror page makes the case for a NEP-7786 convention so future bridge work converges; until that NEP exists, dApp authors writing cross-chain code on Neo should:
- Pick a specific bridge (Poly Network is the most mature today).
- Wrap that bridge's API in a thin adapter contract whose interface matches the proposed gateway shape above.
- Keep the adapter swappable so a future NEP-7786 gateway can replace it without changing call sites.
Recommended Roadmap
- Short term — adopt the proposed
IGatewayshape in any new cross-chain dApp for forward-compatibility. - Medium term — if Neo gets a native cross-chain primitive (rumoured for the next major NeoFS / Oracle upgrade), wrap it in the gateway interface so existing dApps need no source changes.
- Long term — propose a NEP-7786 once two or more bridges support the shape, mirroring ERC-7786's "transport-agnostic, per-provider implementation" model.
