Skip to content

Send Cross-Chain Messages with Wormhole

Wormhole's Core Contract lets any smart contract on the XRPL EVM publish arbitrary messages that can be verified and consumed on 40+ connected chains. This guide shows the raw messaging flow in Solidity: publishing a message on the source chain and verifying its VAA (Verifiable Action Approval) on the destination chain.

Delivery on Mainnet uses the Executor

On XRPL EVM Mainnet the legacy IWormholeRelayer is not part of the official deployment and no delivery provider services it, so tutorials built on sendPayloadToEvm only work on Testnet. For production delivery, publish through the Core Contract and request delivery through the Executor framework. The Wormhole TypeScript SDK manages Executor quoting and execution for you.

Prerequisites

forge install wormhole-foundation/wormhole-solidity-sdk@v1.0.0

Use tagged releases of the SDK; the main branch is a nightly build. In SDK v1.0.0 the Core Contract interface is ICoreBridge (defined in interfaces/ICoreBridge.sol, together with the file-level CoreBridgeVM struct). Older Wormhole tutorials that import IWormhole.sol will not compile against this release.

Publish a Message (Source Chain)

Call publishMessage on the Core Contract (0xaBf89de706B583424328B54dD05a8fC986750Da8 on both XRPL EVM Mainnet and Testnet), paying the current message fee:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "wormhole-solidity-sdk/interfaces/ICoreBridge.sol";

contract HelloWormhole {
    ICoreBridge public immutable wormhole;

    constructor(address wormholeCore) {
        wormhole = ICoreBridge(wormholeCore);
    }

    function sendMessage(
        string memory message
    ) external payable returns (uint64 sequence) {
        uint256 wormholeFee = wormhole.messageFee();
        require(msg.value >= wormholeFee, "insufficient message fee");

        sequence = wormhole.publishMessage{value: wormholeFee}(
            0,                  // nonce: free integer field
            abi.encode(message),
            1                   // consistencyLevel: finality required before attestation
        );
    }
}

Once the transaction finalizes, the Guardian network observes the emitted message and produces a signed VAA. You can fetch it from the WormholeScan API at https://api.wormholescan.io/v1/signed_vaa/57/<emitterAddress>/<sequence> (use api.testnet.wormholescan.io for Testnet; emitterAddress is your contract address left-padded to 32 bytes), or track it at https://wormholescan.io/#/tx/<txHash> (append ?network=Testnet for Testnet).

Verify and Consume the VAA (Destination Chain)

On the destination chain, pass the encoded VAA to parseAndVerifyVM on that chain's Core Contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "wormhole-solidity-sdk/interfaces/ICoreBridge.sol";

contract HelloWormholeReceiver {
    ICoreBridge public immutable wormhole;
    // Trusted emitter (source contract) per Wormhole chain ID
    mapping(uint16 => bytes32) public registeredEmitters;
    // Replay protection
    mapping(bytes32 => bool) public consumedMessages;

    constructor(address wormholeCore) {
        wormhole = ICoreBridge(wormholeCore);
    }

    function receiveMessage(bytes memory encodedVaa) public {
        (
            CoreBridgeVM memory vm,
            bool valid,
            string memory reason
        ) = wormhole.parseAndVerifyVM(encodedVaa);

        require(valid, reason);
        require(
            registeredEmitters[vm.emitterChainId] == vm.emitterAddress,
            "unknown emitter"
        );
        require(!consumedMessages[vm.hash], "message already consumed");
        consumedMessages[vm.hash] = true;

        string memory message = abi.decode(vm.payload, (string));
        // Your application logic here
    }
}

Safety Checks

Wormhole verifies Guardian signatures, but your contract is responsible for application-level checks:

  1. Emitter validation: Only accept VAAs whose emitterChainId and emitterAddress match contracts you trust. XRPL EVM's Wormhole chain ID is 57.
  2. Replay protection: Store the VAA digest (vm.hash) and reject duplicates.
  3. Finality: Choose a consistencyLevel appropriate for your use case when publishing.

Message Delivery

Publishing a message does not deliver it: someone must submit the VAA to the destination chain. Your options on XRPL EVM:

  • Executor (recommended): Request permissionless execution with an off-chain quote; independent relay providers deliver the VAA. The Wormhole SDK handles quotes and execution requests. The Executor contract on XRPL EVM is listed in Deployed Contracts.
  • Self-relay: Fetch the signed VAA from the WormholeScan API and submit it to your destination contract yourself. This is useful for testing and for flows where your backend already submits transactions.
  • Legacy Wormhole Relayer (Testnet only): IWormholeRelayer.sendPayloadToEvm works on XRPL EVM Testnet (0x362fca37E45fe1096b42021b543f462D49a5C8df) and is the model used by the official cross-chain contracts tutorial. Do not ship Mainnet integrations against it.

Next Steps