Wisp Protocol

Wisp Integration Example

One of the major goals of the DEMO app is to serve as an example of what the integration with Wisp looks like on the smart contract level.

Sending Message

Sending of the message is as easy as calling the sendMessage function of the CRCOutbox contract for the source rollup.
function sendMessage(Types.CRCMessage calldata message)
returns (bytes32 messageHash);
This CRCMessage type is:
struct CRCMessage {
uint8 version; // Version of the protocol this message confirms to
uint256 destinationChainId; // The “chain id” of the network this message is intended for
uint64 nonce; // A nonce used as an anti-replay attack mechanism. Randomly generated by the user.
address user; // An arbitrary address that is the actual sender of the message. Can be used by smart contracts that automate the messaging to specify the address of the user or be the same as the msg.sender.
address target; // The address of a contract that the CRC Smart contract will send the Payload to when finalizing the CRC.
bytes payload; // Arbitrary bytes that will be sent as calldata to the Execution Target address in the destination contract when finalizing the CRC.
uint256 stateRelayFee; // Fee in wei that the sender will be locking as a reward for the first relayer that brings the state containing this information inside the destination network.
uint256 deliveryFee; // Fee in wei that the sender will be locking as a reward for the first relayer that triggers the execution of the CRC delivery. Could be 0 if the Sender is willing to finalize it itself.
bytes extra; // Arbitrary bytes that will be sent alongside the data for dapps to make sense of
Most of these can be fairly static. For example the Demo bridge uses the following parameters:
function sendMessage(uint64 nonce, uint256 value) internal {
bytes memory payload = abi.encode(msg.sender, value);
Types.CRCMessage memory message = Types.CRCMessage(
counterpartyChainId, // destination chain id
nonce, // random nonce from the user
msg.sender, // the sender
counterparty, // the address of the bridge in the destination rollup
payload, // the message payload
0, // state relay fee
0, // message relay fee
hex"" // extra data
bytes32 messageHash = outbox.sendMessage(message);

Receiving Messages

In order to receive a message the target contract must conform to the following interface:
/// @notice Interface that the contracts receiving messages should implement
/// @author Perseverance
interface IMessageReceiver {
/// @notice receives CRCMessageEnvelope
/// @param envelope the message envelope you are receiving
function receiveMessage(
Types.CRCMessageEnvelope calldata envelope,
uint256 sourceChainId
) external returns (bool success);
The target contract will be called by the relevant Inbox contract. In the demo this is the OptimismInbox contract for the destination rollup. This means that the target must ensure that their receiveMessage function is called by the inbox contract:
modifier onlyInbox() {
require(msg.sender == inbox, "not sent by the inbox contract");
The full example from the demo app is:
function receiveMessage(
Types.CRCMessageEnvelope calldata envelope,
uint256 sourceChainId
) external virtual onlyInbox returns (bool success) {
address receiver;
uint256 value;
(receiver, value) = abi.decode(
(address, uint256)
// Do something with the message and its payload
return true;

Contract addresses

The addresses for the currently supported rollups are:


OptimismInbox0x25ec3396b6d709d122999bc1de1327d0598a0fd1 → Proves Base Goerli Messages


OptimismInbox0x13a2764480db29103cb804476ffa1ed68d604805 → Proves Optimism Goerli Messages