Skip to content

Solidity SDK

Introduction

The Wormhole Solidity SDK simplifies cross-chain messaging on EVM-compatible chains by providing essential Solidity interfaces, utility libraries, and testing tools. It allows developers to build secure and efficient cross-chain decentralized applications (dApps) without manually interacting with Wormhole’s core contracts across multiple chains.

By abstracting away complex interactions, the SDK drastically reduces the overhead associated with cross-chain development. It provides:

  • Unified interfaces - developers can use a standardized set of Solidity interfaces to handle cross-chain messaging, token transfers, and verifiable action approvals (VAAs) without needing to manage the underlying infrastructure
  • Automated message delivery - the SDK leverages Wormhole’s relayer infrastructure, automatically delivering messages across chains, reducing the need for manual intervention, and simplifying gas management on the target chain
  • Seamless integration with Wormhole services - the SDK integrates with Wormhole’s TokenBridge and Circle’s CCTP, providing built-in mechanisms for cross-chain asset transfers, making token bridges and cross-chain messaging easy to implement
  • Testing and development tools - it comes with comprehensive tools for local testing and simulation, allowing developers to validate their cross-chain logic before deployment, minimizing the risk of errors in production environments

These features significantly streamline the development workflow by reducing complexity and offering tools compatible with various EVM versions. This helps developers avoid issues that arise from differences in EVM equivalence across chains.

This guide covers installation, key concepts, and usage examples to help you build secure cross-chain applications using the SDK, from token transfers to advanced message passing.

Installation

To install the SDK, use Foundry and Forge. This pulls the necessary libraries into your project:

forge install wormhole-foundation/wormhole-solidity-sdk@v0.1.0

When developing cross-chain applications, ensure that the chains you target support the EVM version you’re using. For instance, the PUSH0 opcode (introduced in Solidity 0.8.20) may not be available on all chains. To avoid compatibility issues, you can set the EVM version in your foundry.toml file:

evm_version = "paris"

This ensures compatibility across all targeted chains, even if some do not yet support the latest EVM upgrades.

Key Considerations

Before deploying applications using the Wormhole Solidity SDK, keep these considerations in mind:

  • Version compatibility - the SDK is evolving, and using tagged releases for production is crucial, as the main branch may introduce breaking changes
  • IERC-20 remapping - the SDK provides a remapping mechanism to handle potential conflicts between different implementations of IERC20, ensuring seamless integration with other libraries
  • Testing - given the cross-chain dependencies, testing all integrations is critical to avoid issues in production environments

Concepts and Components

The Wormhole Solidity SDK consists of key components that streamline cross-chain communication, allowing developers to securely and efficiently interact with Wormhole’s infrastructure. Below are the critical concepts and contracts you'll encounter when working with the SDK.

Cross-Chain Messaging with the Wormhole Relayer SDK

The WormholeRelayerSDK.sol contract simplifies cross-chain messaging and asset transfers by integrating several necessary modules, including the Wormhole relayer. By automating message delivery between chains, the Wormhole relayer removes the need for developers to manage relayer infrastructure or handle gas on the target chain. Delivery providers handle the message payload, ensuring secure and efficient communication.

You can refer to the Wormhole relayer documentation for more details.

Key modules in the SDK include:

  • Base.sol - the core module for cross-chain messaging. It provides utility functions like onlyWormholeRelayer() and setRegisteredSender(), ensuring that only messages from trusted relayers are processed

  • TokenBase.sol - this module extends the base messaging functionality to support cross-chain token transfers. It includes utilities for securely sending and receiving tokens between EVM-compatible chains

  • CCTPBase.sol - designed for Circle’s Cross-Chain Transfer Protocol, this module manages asset transfers such as USDC between chains. It includes functionalities for both sending and receiving CCTP-based assets

  • CCTPAndTokenBase.sol - a combined module that supports token and CCTP-based asset transfers in a single implementation. This module simplifies development for applications needing to handle both types of transfers

The Wormhole Solidity SDK offers a unified framework for cross-chain communication. Developers can select specific modules based on their application’s requirements, whether for messaging, token transfers, or CCTP. Each module includes built-in security measures, ensuring that only authorized senders or relayers are accepted, thereby protecting the application from unauthorized interactions.

Please refer to the complete WormholeRelayerSDK.sol file below for further details.

WormholeRelayerSDK.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;

import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/constants/Chains.sol";
import "wormhole-sdk/Utils.sol";

import {Base} from "wormhole-sdk/WormholeRelayer/Base.sol";
import {TokenBase, TokenReceiver, TokenSender} from "wormhole-sdk/WormholeRelayer/TokenBase.sol";
import {CCTPBase, CCTPReceiver, CCTPSender} from "wormhole-sdk/WormholeRelayer/CCTPBase.sol";
import {CCTPAndTokenBase, CCTPAndTokenReceiver, CCTPAndTokenSender} from "wormhole-sdk/WormholeRelayer/CCTPAndTokenBase.sol";

Base Contract Overview

The Base.sol contract is a core part of the Wormhole Solidity SDK, providing essential helper functions and modifiers for managing cross-chain messages securely via the Wormhole Relayer. It handles sender registration and message validation, ensuring only authorized senders from specific chains can send messages.

  • onlyWormholeRelayer() - a modifier that ensures only authorized messages from the Wormhole relayer contract are processed, restricting access to certain functions

        modifier onlyWormholeRelayer() {
            require(
                msg.sender == address(wormholeRelayer),
                "Msg.sender is not Wormhole Relayer"
            );
            _;
        }
    
  • setRegisteredSender() - restricts message acceptance to a registered sender from a specific chain, ensuring messages are only processed from trusted sources

        function setRegisteredSender(
            uint16 sourceChain,
            bytes32 sourceAddress
        ) public {
            require(
                msg.sender == registrationOwner,
                "Not allowed to set registered sender"
            );
            registeredSenders[sourceChain] = sourceAddress;
        }
    

These security measures ensure messages come from the correct source and are processed securely. Please refer to the complete Base.sol contract below for further details.

Base.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;

import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/interfaces/IWormhole.sol";
import "wormhole-sdk/Utils.sol";

abstract contract Base {
    IWormholeRelayer public immutable wormholeRelayer;
    IWormhole public immutable wormhole;

    address registrationOwner;
    mapping(uint16 => bytes32) registeredSenders;

    constructor(address _wormholeRelayer, address _wormhole) {
        wormholeRelayer = IWormholeRelayer(_wormholeRelayer);
        wormhole = IWormhole(_wormhole);
        registrationOwner = msg.sender;
    }

    modifier onlyWormholeRelayer() {
        require(
            msg.sender == address(wormholeRelayer),
            "Msg.sender is not Wormhole Relayer"
        );
        _;
    }

    modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) {
        require(
            registeredSenders[sourceChain] == sourceAddress,
            "Not registered sender"
        );
        _;
    }

    /**
     * Sets the registered address for 'sourceChain' to 'sourceAddress'
     * So that for messages from 'sourceChain', only ones from 'sourceAddress' are valid
     *
     * Assumes only one sender per chain is valid
     * Sender is the address that called 'send' on the Wormhole Relayer contract on the source chain)
     */
    function setRegisteredSender(
        uint16 sourceChain,
        bytes32 sourceAddress
    ) public {
        require(
            msg.sender == registrationOwner,
            "Not allowed to set registered sender"
        );
        registeredSenders[sourceChain] = sourceAddress;
    }
}

Interface for Cross-Chain Messages

The Wormhole Solidity SDK interacts with the Wormhole relayer for sending and receiving messages across EVM-compatible chains. The IWormholeRelayer and IWormholeReceiver interfaces are central to cross-chain communication, enabling secure and efficient message delivery.

For detailed information on how to implement these interfaces, refer to the Wormhole Relayer Interfaces documentation. This section covers:

  • IWormholeRelayer – methods for sending cross-chain messages, VAAs, and token transfers
  • IWormholeReceiver – the required implementation for receiving cross-chain messages
  • quoteEVMDeliveryPrice() – how to estimate gas and fees for cross-chain transactions

These interfaces reduce the complexity of cross-chain dApp development by abstracting away the details of relayer infrastructure, ensuring that message delivery is handled efficiently.

Advanced Concepts

For developers interested in exploring additional advanced topics, the following sections provide insights into key aspects of the SDK’s functionality.

Error Handling and Reverts

The SDK defines several custom errors to help developers handle common issues like incorrect gas fees, invalid senders, and more. For example, InvalidMsgValue is thrown when the message value for a relayed message is erroneous.

error InvalidMsgValue(uint256 msgValue, uint256 totalFee);

Usage

This section covers cross-chain messaging and token transfers and shows how to use the Wormhole Solidity SDK in real-world scenarios.

Send a Cross-Chain Message

To send a cross-chain message, inherit from the base contract provided by the SDK and use its helper methods to define your message and sender address. Here’s a basic example:

pragma solidity ^0.8.19;

import "@wormhole-foundation/wormhole-solidity-sdk/src/WormholeRelayer/Base.sol";

contract CrossChainSender is Base {
    constructor(
        address _wormholeRelayer,
        address _wormhole
    ) Base(_wormholeRelayer, _wormhole) {}

    function sendMessage(
        bytes memory message,
        uint16 targetChain,
        bytes32 targetAddress
    ) external payable {
        // Register sender and send message through WormholeRelayer
        setRegisteredSender(targetChain, msg.sender);
        onlyWormholeRelayer().sendPayloadToEvm(
            targetChain,
            address(targetAddress),
            message,
            0,
            500_000
        );
    }
}

This contract extends Base.sol and allows sending cross-chain messages securely using the WormholeRelayer.

Send Tokens Across Chains

The SDK enables seamless token transfers between EVM-compatible chains in addition to sending messages. To facilitate cross-chain token transfers, you can extend the SDK's TokenSender and TokenReceiver base contracts.

pragma solidity ^0.8.19;

import "@wormhole-foundation/wormhole-solidity-sdk/src/WormholeRelayer/TokenBase.sol";

contract CrossChainTokenSender is TokenSender {
    constructor(
        address _wormholeRelayer,
        address _wormhole
    ) TokenSender(_wormholeRelayer, _wormhole) {}

    function sendToken(
        address token,
        uint256 amount,
        uint16 targetChain,
        bytes32 targetAddress
    ) external payable {
        // Send tokens across chains
        transferTokenToTarget(token, amount, targetChain, targetAddress);
    }
}

In this example, TokenSender initiates a token transfer to another chain. The SDK’s built-in utilities securely handle token transfers, ensuring proper VAAs are generated and processed.

Receive Tokens Across Chains

To receive tokens on the target chain, implement a contract that inherits from TokenReceiver and overrides the receiveWormholeMessages function.

pragma solidity ^0.8.19;

import "@wormhole-foundation/wormhole-solidity-sdk/src/WormholeRelayer/TokenBase.sol";

contract CrossChainTokenReceiver is TokenReceiver {
    constructor(
        address _wormholeRelayer,
        address _wormhole
    ) TokenReceiver(_wormholeRelayer, _wormhole) {}

    // Function to handle received tokens from another chain
    function receiveWormholeMessages(
        bytes memory payload,
        bytes[] memory additionalMessages,
        bytes32 sourceAddress,
        uint16 sourceChain,
        bytes32 deliveryHash
    ) external payable override {
        // Process the received tokens here
        receiveTokens(payload);
    }
}

In this example, TokenReceiver allows the contract to handle tokens sent from the source chain. Once the cross-chain message is received, the receiveWormholeMessages function processes the incoming tokens. Always validate the message's authenticity and source.

Note

Always verify the source of incoming messages and tokens to prevent unauthorized access to your contract. Please refer to the Emitter Verification section for more details.

Testing Environment

The SDK includes built-in support for Forge-based testing, which allows you to test your cross-chain applications locally before deploying them to production. Testing with the same Solidity compiler version and configuration you plan to use in production is highly recommended to catch any potential issues early.

For a detailed example, check out the below repositories: