For each sending and receiving chain pair supported by Vea, there is a separate set of Vea contract deployments. For each chain, there is exactly one deployed contract.
Sending Chain: VeaInbox - Manages state of all messages sent through Vea.
Receiving Chain: VeaOutbox - Manages optimistic game over inbox state
To integrate Vea, sender and receiver gateway contracts need to be deployed to interface with the Vea inbox and outbox.
1. Sender Gateway
For each sending, receiving chain pair supported by Vea, there is a 'Vea Inbox' contract deployed on the sending chain. Contracts send messages through Vea by calling the 'sendMessage(...)' function in the Vea Inbox.
where the IVeaInbox interface includes the function stub to send messages through Vea.
interface IVeaInbox {/// @dev Sends an arbitrary message to receiving chain./// Note: Calls authenticated by receiving gateway checking the sender argument./// @param _to The cross-domain contract address which receives the calldata./// @param _fnSelection The function selector of the receiving contract./// @param _data The message calldata, abi.encode(...)/// @return msgId The index of the message in the inbox, as a message Id, needed to relay the message.functionsendMessage(address_to,bytes4_fnSelection,bytesmemory_data ) externalreturns (uint64 msgId);}
To send a message through Vea, the sending gateway should implement a function to call sendMessage(...) in the Vea Inbox. Here is an example mock implementation.
Notice that the sendMessage(...) call in the Vea Inbox returns a uint64 message id. This id is used to relay the message on the receiving chain. Your dapp will probably want to index these messages with an event like below to later relay the message.
emitveaMessageSent(msgId);
In this example, the sender gateway is sending some uint256 _data. The Vea Inbox contract expects a bytes array encoding the calldata to be passed with the cross-chain call.
bytesmemory data = abi.encode(_data);
The data which you include in the cross-chain message depends on your specific application.
2. Receiver Gateway
For each sending, receiving chain pair supported by Vea, there is a 'Vea Outbox' contract deployed on the receiving chain. Receiver gateways receive messages from Vea by receiving calls from the 'sendMessage(...)' function in the Vea Outbox.
The Vea SDK provides utility functions to calculate proofs and fetch message data to relay.
IReceiverGateway Function Specification
In order to implement a cross-chain call, you need to specify the function selector in the receiver gateway to call. You should define the function stub to call in an interface for the receiver gateway. Here's an example where we define the function stub receiveMessage.
interfaceIReceiverGatewayMockisIReceiverGateway {/// Receive the message from the sender gateway.functionreceiveMessage(address msgSender,uint256_data) external;}interface IReceiverGateway {functionveaOutbox() externalviewreturns (address);functionsenderGateway() externalviewreturns (address);}
Note that Vea passes the msg.sender who called the sendMessage(...) function on the sending chain as the first argument of any cross-chain call. This means that the IReceiverGateway interface should always contain the msgSender as the first argument.
interfaceIReceiverGatewayMockisIReceiverGateway {/// Receive the message from the sender gateway.functionreceiveMessage(address msgSender,/*your types here*/) external;}
Here's a complete example of a ReceiverGateway.
pragmasolidity 0.8.18;import"./IReceiverGatewayMock.sol";/// Receiver Gateway Mock/// Counterpart of `SenderGatewayMock`contractReceiverGatewayMockisIReceiverGatewayMock {addresspublicimmutableoverride veaOutbox;addresspublicimmutableoverride senderGateway;uint256public data;constructor(address_veaOutbox,address_senderGateway) { veaOutbox = _veaOutbox; senderGateway = _senderGateway; }modifieronlyFromAuthenticatedVeaSender(address messageSender) {require(veaOutbox == msg.sender,"Vea Bridge only.");require(messageSender == senderGateway,"Only the sender gateway is allowed."); _; }/// Receive the message from the sender gateway.functionreceiveMessage(address messageSender,uint256_data ) externalonlyFromAuthenticatedVeaSender(messageSender) { data = _data; }}
Message Sender Authentication
The message sender of a cross-chain call is always the first argument of calldata passed to any receiver gateways.
modifieronlyFromAuthenticatedVeaSender(address messageSender) {require(veaOutbox == msg.sender,"Vea Bridge only.");require(messageSender == senderGateway,"Only the sender gateway is allowed."); _;}functionreceiveMessage(address messageSender,uint256_data) externalonlyFromAuthenticatedVeaSender(messageSender) {...}
Cross-chain call authentication requires checking that the msg.sender on the sending chain is the Vea Outbox contract, and checking the first argument of the call is equal to the sender gateway like shown in the modifier onlyFromAuthenticatedVeaSender.
Customizing the data types sent between gateways
To support transfer of data types other than uint256 as shown in this example, simply define those custom arguments in the function stub of the IReceiverGateway and encode those data types into a bytes array to pass in the SenderGateway when calling sendMessage(...) in the Vea Inbox.
For example, to support a call passing data of a string, uint64, and a bytes array, the ISenderGateway would instead implement
and the IReceiverGateway interface would appropriately include a function selector expecting the data types string, uint64, and bytes, with an additional argument in the first position which will always include the message sender (Vea ensures the message sender will be passed in this argument).
interfaceIReceiverGatewayMockisIReceiverGateway {/// Receive the message from the sender gateway.functionreceiveMessage(address msgSender,stringmemory_data1,uint64_data2,bytesmemory_data3 ) external;}
3. Relaying your message
Once messages are bridged by Vea, which can take hours to days depending on the sending and receiving chain pairs, the messages can be relayed by providing a merkle proof of message inclusion in the Vea Outbox contract.
The Vea SDK provides utility functions to calculate merkle inclusion proofs given the msgId. Here is an example of relaying a message given the msgId.
import { Wallet } from"@ethersproject/wallet";import VeaSdk from"../src/index";// Create the Vea clientconstvea=VeaSdk.ClientFactory.arbitrumGoerliToChiadoDevnet("https://rpc.goerli.eth.gateway.fm","https://rpc.chiado.gnosis.gateway.fm");// Get the message infoconstmessageId=42;constmessageInfo=awaitvea.getMessageInfo(messageId);// Relay the messageconstprivateKey=process.env["PRIVATE_KEY"] ??"";constwallet=newWallet(privateKey,vea.outboxProvider);awaitvea.relay(messageInfo, wallet);