♈
Bootcamp-2024
  • Intro
  • 1. Blockchain & Wallet Fundamentals
    • Blockchain Introduction
    • State Machines
    • Cryptography
    • Distributed Networks
    • Game Theory
    • What is Web3
    • MetaMask Wallet Installation
    • Transferring Tokens with MetaMask
  • 2. Smart Contract & Solidity Fundamentals
    • Using Remix
    • Create, compile and publish your first smart contract
    • Interact with already published smart contracts
    • Blockchain Explorer
    • Verify source code on Etherscan
  • 3. Oracles, ERC20 & Chainlink Data Feeds
    • Oracles
    • Create & Deploy ERC20
    • Data Feeds
  • 4. Cross-Chain Tokens With Chainlink CCIP
    • Setting up MetaMask
    • Getting USDC Testnet Tokens
    • Create Smart Contract In Remix
    • Compile and Deploy
    • Approve USDC
    • Send LINK to your Contract
    • Send USDC from Fuji to Sepolia
    • USDC on Sepolia
  • 5. Mentoring Session
  • 6. NFTs & Chainlink Automation
    • NFT Basics
    • Dynamic NFTs
    • Creating an NFT Smart Contract
    • Deploying Your Dynamic NFTs
  • 7. Chainlink CCIP & Cross-Chain NFT dApps
    • Create and deploy CCIP NFT Contracts
    • Mint on Source Chain
    • Fund Contract
    • Mint On Sepolia From Fuji
    • Mint from Destination 2 - Base Sepolia
  • 8. Random Numbers with Chainlink VRF
    • Introduction to Chainlink VRF
    • Hands On Game Using VRF
  • 9. Off-Chain Data with Chainlink Functions
    • Chainlink Functions Playground
    • Setting up MetaMask
    • Remix
    • Functions Subscription
    • Creating The Functions Consumer Contract
    • Sending a Request from Remix
    • City Weather and Examples
    • City Weather on Chainlink Functions
  • 10. Connecting the 🌏 with Chainlink
  • Glossary
Powered by GitBook
On this page
  • MINT
  • CrossDestinationMinter.sol
  • CrossSourceMinter.sol
  1. 7. Chainlink CCIP & Cross-Chain NFT dApps

Mint on Source Chain

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

PreviousCreate and deploy CCIP NFT ContractsNextFund Contract

Last updated 1 year ago

MINT

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

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.

  • Confirm the transaction in your Metamask Wallet.



CrossDestinationMinter.sol

On Remix open 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.

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

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


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.

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

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)

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

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

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

testnet.opensea.io
chainlist
Remix
This is what the deployed contract looks like in your Remix IDE
Include your Wallet address as the Parameter
Your New NFT Should have populated
Example of the File Explorer
Example of the Deployment tab with my CrossChainPriceNFT.sol Contract address in the NFTADDRESS parameter field
CrossDestinationMinter Deployed
Example of Opensea collection after testMint & testMessage
In the Top left of your Metamask select the Network
Select Avalanche Fuji
Example of File Explorer
Example of Deployment tab with DESTMINTERADDRESS populated with my CrossDestinationMinter.sol address
ChainList
Avalanche Fuji Chainlist Quicklink
Logo