The Synapse Interchain Network (SIN) is designed to be as easy as possible for developers to integrate. To this end, we've developed an abstract contract called MessageRecipient that provides much of the already minimal scaffolding required to get started sending cross-chain messages. If you don't want to use this, you can also implement the IMessageRecipient interface
You can also consider using BaseClient to avoid implementing any receiver checks yourself.
The contract also does some basic checks for us when receiving a message. Let's take a look at the receiveBaseMessage related functions which are used to receive a message from a destination:
/** * @notice Message recipient needs to implement this function in order to * receive cross-chain messages. * @dev Message recipient needs to ensure that merkle proof for the message * is at least as old as the optimistic period that the recipient is using. * Note: as this point it is checked that the "message optimistic period" has passed, * however the period value itself could be anything, and thus could differ from the one * that the recipient would like to enforce. * @param origin Domain where message originated * @param nonce Message nonce on the origin domain * @param sender Sender address on origin chain * @param proofMaturity Message's merkle proof age in seconds * @param version Message version specified by sender * @param content Raw bytes content of message */functionreceiveBaseMessage(uint32 origin_,uint32 nonce,bytes32 sender,uint256 proofMaturity,uint32 version,bytesmemory content) externalpayable {if (msg.sender != destination) revertCallerNotDestination();if (nonce ==0) revertIncorrectNonce();if (sender ==0) revertIncorrectSender();if (proofMaturity ==0) revertZeroProofMaturity();_receiveBaseMessageUnsafe(origin_, nonce, sender, proofMaturity, version, content);}/** * @dev Child contracts should implement the logic for receiving a Base Message in an "unsafe way". * Following checks HAVE been performed: * - receiveBaseMessage() was called by Destination (i.e. this is a legit base message). * - Nonce is not zero. * - Message sender on origin chain is not a zero address. * - Proof maturity is not zero. * Following checks HAVE NOT been performed (thus "unsafe"): * - Message sender on origin chain could be anything non-zero at this point. * - Proof maturity could be anything non-zero at this point. */function_receiveBaseMessageUnsafe(uint32 origin_,uint32 nonce,bytes32 sender,uint256 proofMaturity,uint32 version,bytesmemory content) internalvirtual;
This means in our test client we're going to have to implement some checks. The first thing you'll notice is both the origin and destination are pre-defined so you'll have to define those in the constructor like so using the appropriate origin and destination chain addresses for your chain.
Next up, you're going to want to define the sender checks and make sure the optimistic seconds period is set correctly. Let's define _receiveBaseMessageUnsafe
uint256privateconstant REQUIRED_OPTIMISTIC_SECONDS =20errorProofMaturityTooLow();errorInvalidSender();/// @inheritdoc MessageRecipientfunction_receiveBaseMessageUnsafe(uint32 origin_,uint32 nonce,bytes32 sender,uint256 proofMaturity,uint32 version,bytesmemory content) internaloverride {// let's make sure everything is kosher before we accept the message// first we want to make sure the sender is who we think it is.if Typecast.addressToBytes32(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045) != sender {revertInvalidSender(); }// and we want to make sure the optimistic seconds period has exceeded// what we want on our side.if (proofMaturity != REQUIRED_OPTIMISTIC_SECONDS) {revertProofMaturityTooLow(); }emitMessageReceived(origin_, nonce, sender, proofMaturity, version, content);}
Now we're ready to receive a message, but we still have to send one. Let's define a sendMessage function:
functionsendMessage(uint32 destination_,address recipientAddress,uint32 optimisticSeconds,uint64 gasLimit,uint32 version,bytesmemory content) externalpayable {// receipient is the address of this contract, but on the other chainbytes32 recipient = TypeCasts.addressToBytes32(recipientAddress);// if you want the user to get dropped gas on the destination chain, can define that here MessageRequest memory request =MessageRequest({gasDrop:0, gasLimit: gasLimit, version: version}); (uint32 nonce,) =_sendBaseMessage(destination_, recipient, optimisticSeconds, request, content);// you've sent he messageemitMessageSent(destination_, nonce, TypeCasts.addressToBytes32(address(this)), recipient, content);}
Feel free to reach out in Discord for more help integrating these contracts. As the ecosystem matures, more dev docs will be released as well as more integrations.
In the meantime, you can see examples here and here.