Skip to content
Start on Wormhole

Writing the Cross-Chain USDC Contract

First, head over to this GitHub template and create a new repository using it.

Clone the repository and set up the starter code in your workspace:

git clone git@github.com:<your username>/xUSDC.git
cd xSwap/
npm i

We’ve already set up deploy scripts and tests for you. 🤓 So let’s jump straight into implementing the Spoke model with a smart contract!

Step 1: First things first, let’s get started by specifying the licenses and our usual imports:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "wormhole-solidity-sdk/WormholeRelayerSDK.sol";
import "wormhole-solidity-sdk/interfaces/IERC20.sol";

Just like in our Cross Chain Mailbox tutorial, we’re importing the wormhole-solidity-sdkfor those nifty cross-chain operations.

Step 2: Construct the Contract

Next up, we’ll build out the structure of the HelloUSDC contract:

contract HelloUSDC is CCTPSender, CCTPReceiver {

Inheriting from CCTPSender and CCTPReceiver gives us superpowers for sending and receiving USDC across chains. 💥

Step 3: Initialize the Contract Now we can set up constants and initialize things in the constructor:

    uint256 constant GAS_LIMIT = 250_000;

    constructor(
        address _wormholeRelayer,
        address _wormhole,
        address _circleMessageTransmitter,
        address _circleTokenMessenger,
        address _USDC
    )
        CCTPBase(
            _wormholeRelayer,
            _wormhole,
            _circleMessageTransmitter,
            _circleTokenMessenger,
            _USDC
        )
    {
        setCCTPDomain(2, 0);
        setCCTPDomain(6, 1);
        setCCTPDomain(24, 2);
        setCCTPDomain(23, 3);
        setCCTPDomain(30, 6);
    }

This lays the groundwork for our contract’s cross-chain abilities.

Step 4: Estimate Deposit Costs

Let’s add a function to quote deposit costs:

    function quoteCrossChainDeposit(
        uint16 targetChain
    ) public view returns (uint256 cost) {
        // Cost of delivering token and payload to targetChain
        (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(
            targetChain,
            0,
            GAS_LIMIT
        );
    }

This will allow users to check pricing before sending USDC.

Step 5: Initiate Deposits

Next up, a function to handle the actual deposit:

    function sendCrossChainDeposit(
        uint16 targetChain,
        address targetHelloUSDC,
        address recipient,
        uint256 amount
    ) public payable {
        uint256 cost = quoteCrossChainDeposit(targetChain);
        require(
            msg.value == cost,
            "msg.value must be quoteCrossChainDeposit(targetChain)"
        );

        IERC20(USDC).transferFrom(msg.sender, address(this), amount);

        bytes memory payload = abi.encode(recipient);
        sendUSDCWithPayloadToEvm(
            targetChain,
            targetHelloUSDC, // address (on targetChain) to send token and payload to
            payload,
            0, // receiver value
            GAS_LIMIT,
            amount
        );
    }

This will burn USDC on the source chain and kick off the cross-chain transfer. 🌉

Step 6: Receive the Assets

Finally, logic to receive the USDC and payload:

    function receivePayloadAndUSDC(
        bytes memory payload,
        uint256 amountUSDCReceived,
        bytes32, // sourceAddress
        uint16, // sourceChain
        bytes32 // deliveryHash
    ) internal override onlyWormholeRelayer {
        address recipient = abi.decode(payload, (address));

        IERC20(USDC).transfer(recipient, amountUSDCReceived);
    }

Step 7: Finally, don’t forget to close the contract with a }.

And there we have it - a contract to send USDC across blockchains! 🎉