Mint on Source Chain

We will now Mint an NFT on the source Chain (Ethereum Sepolia 11155111)

MINT

Go to Remix and select the CrossChainPriceNFT on the deployed contracts section in the lower lefthand side of the screen.

This is what the deployed contract looks like in your Remix IDE

You will want to make sure you use the Mint Function, and you want to provide your Wallet address as the parameter as you want to mint the NFTs to your Ethereum Sepolia wallet address.

Include your Wallet address as the Parameter
  • Confirm the transaction in your Metamask Wallet.


Once you Confirm your transaction, open testnet.opensea.io or use the link to your collection that we saved in the last section to see your freshly minted NFT in the Collection

Your New NFT Should have populated

CrossDestinationMinter.sol

On Remix open FILE EXPLORER

Example of the File Explorer

Create a new file and name it CrossDestinationMinter.sol

Paste this code there.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Deploy this contract on Sepolia

import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";

interface InftMinter {
    function mintFrom(address account, uint256 sourceId) external;
}

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */
contract CrossDestinationMinter is CCIPReceiver {
    InftMinter public nft;

    event MintCallSuccessfull();
    // https://docs.chain.link/ccip/supported-networks/testnet
    address routerSepolia = 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59;

    constructor(address nftAddress) CCIPReceiver(routerSepolia) {
        nft = InftMinter(nftAddress);
    }

    function _ccipReceive(
        Client.Any2EVMMessage memory message
    ) internal override {
        (bool success, ) = address(nft).call(message.data);
        require(success);
        emit MintCallSuccessfull();
    }

    function testMint() external {
        // Mint from Sepolia
        nft.mintFrom(msg.sender, 0);
    }

    function testMessage() external {
        // Mint from Sepolia
        bytes memory message;
        message = abi.encodeWithSignature("mintFrom(address,uint256)", msg.sender, 0);

        (bool success, ) = address(nft).call(message);
        require(success);
        emit MintCallSuccessfull();
    }

    function updateNFT(address nftAddress) external {
        nft = InftMinter(nftAddress);
    }
}

Deploy this code to Ethereum Sepolia. The parameter is the address of your deployed CrossChainPriceNFT.sol Contract , on Ethereum Sepolia.

Example of the Deployment tab with my CrossChainPriceNFT.sol Contract address in the NFTADDRESS parameter field

We will then test it by executing testMint and testMessage on newly deployed contract CrossDestinationMinter.sol:

CrossDestinationMinter Deployed

Click one of these functions, confirm the transaction, and when that's done click the other and confirm its transaction in Metamask.

You can check to confirm that they worked by refreshing your Opensea Collection

Example of Opensea collection after testMint & testMessage

CrossSourceMinter.sol

We Need to switch our network to Avalanche Fuji. You can do the following by going into your Metamask and selecting "Networks", then selecting the Avalanche Fuji Network. If you dont already have the Avalanche Fuji Network Imported in your Metamask extension you can find it by going to:

  • going into your metamask extension and selecting networks,

  • then selecting the Avalanche Fuji Network.

    • If you dont already have the Avalanche Fuji Network Imported in your Metamask extension you can find it by visiting chainlist.org

Avalanche Fuji Chainlist Quicklink
In the Top left of your Metamask select the Network
Select Avalanche Fuji

Once you have switched networks, return to Remix. In FILE EXPLORER create a new file.

Enter the Second Icon Titled - FILE EXPLORER and create a new file.

We will be naming this file CrossSourceMinter.sol

Example of File Explorer

And paste the following code in there:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Deploy this contract on Fuji

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {LinkTokenInterface} from "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol";

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */
contract CrossSourceMinter {

    // Custom errors to provide more descriptive revert messages.
    error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
    error NothingToWithdraw(); // Used when trying to withdraw but there's nothing to withdraw.

    IRouterClient public router;
    LinkTokenInterface public linkToken;
    uint64 public destinationChainSelector;
    address public owner;
    address public destinationMinter;

    event MessageSent(bytes32 messageId);

    constructor(address destMinterAddress) {
        owner = msg.sender;

        // https://docs.chain.link/ccip/supported-networks/testnet

        // from Fuji
        address routerAddressFuji = 0xF694E193200268f9a4868e4Aa017A0118C9a8177;
        router = IRouterClient(routerAddressFuji);
        linkToken = LinkTokenInterface(0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846);
        linkToken.approve(routerAddressFuji, type(uint256).max);

        // to Sepolia
        destinationChainSelector = 16015286601757825753;
        destinationMinter = destMinterAddress;
    }

    function mintOnSepolia() external {
        // Mint from Fuji network = chain[1]
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(destinationMinter),
            data: abi.encodeWithSignature("mintFrom(address,uint256)", msg.sender, 1),
            tokenAmounts: new Client.EVMTokenAmount[](0),
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 980_000})
            ),
            feeToken: address(linkToken)
        });        

        // Get the fee required to send the message
        uint256 fees = router.getFee(destinationChainSelector, message);

        if (fees > linkToken.balanceOf(address(this)))
            revert NotEnoughBalance(linkToken.balanceOf(address(this)), fees);

        bytes32 messageId;
        // Send the message through the router and store the returned message ID
        messageId = router.ccipSend(destinationChainSelector, message);
        emit MessageSent(messageId);
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function linkBalance (address account) public view returns (uint256) {
        return linkToken.balanceOf(account);
    }

    function withdrawLINK(
        address beneficiary
    ) public onlyOwner {
        uint256 amount = linkToken.balanceOf(address(this));
        if (amount == 0) revert NothingToWithdraw();
        linkToken.transfer(beneficiary, amount);
    }
}

Copy this code and paste it directly into your newly created CrossSourceMinter.sol file in Remix

Next, we deploy this smart contract. Its constructor function takes the following parameter: address of our deployed CrossDestinationMinter.sol (deployed on Sepolia).

(this is the contract that we deployed on Sepolia)

Example of Deployment tab with DESTMINTERADDRESS populated with my CrossDestinationMinter.sol address

If you've gotten this far congratulations! We're almost there!

Last updated