How to design a cross-chain NFT smart contract
A cross-chain NFT is a smart contract that can exist on any blockchain, abstracting away the need for users to understand which blockchain they’re using.
Typically, NFT movements from one chain to another are eye-catching events, but this simple fact brings up a larger question of the reliability and security of Web3’s middleware infrastructure. In a seamless cross-chain world, moving digital assets from one chain to another should be as normal as submitting a transaction on the same blockchain.
When an NFT moves from one blockchain to another, it becomes a cross-chain NFT.
At a high level, an NFT is a digital token on a blockchain with a unique identifier different from any other token on the chain.
Any NFT is implemented by a smart contract that is intrinsically connected to a single blockchain. The smart contract is arguably the most important part of this equation because it controls the NFT implementation: How many are minted, when, what conditions need to be met to distribute them, and more. This means that any cross-chain NFT implementation requires at least two smart contracts on two blockchains and interconnection between them.
This is what a cross-chain NFT looks like - equivalent NFTs that exist across multiple blockchains.
With this in mind, cross-chain NFTs can be implemented in three ways:
Burn-and-mint: An NFT owner puts their NFT into a smart contract on the source chain and burns it, in effect removing it from that blockchain. Once this is done, an equivalent NFT is created on the destination blockchain from its corresponding smart contract. This process can occur in both directions.
Lock-and-mint: An NFT owner locks their NFT into a smart contract on the source chain, and an equivalent NFT is created on the destination blockchain. When the owner wants to move their NFT back, they burn the NFT and it unlocks the NFT on the original blockchain.
Lock and unlock: The same NFT collection is minted on multiple blockchains. An NFT owner can lock their NFT on a source blockchain to unlock the equivalent NFT on a destination blockchain. This means only a single NFT can actively be used at any point in time, even if there are multiple instances of that NFT across blockchains.
At this Masterclass, we are going to implement the Burn-and-Mint mechanism
In each scenario, a cross-chain messaging protocol in the middle is necessary to send data instructions from one blockchain to another.
To build Cross-Chain NFTs with Chainlink CCIP, let's first understand what one can do with Chainlink CCIP. With Chainlink CCIP, one can:
Transfer (supported) tokens
Send any kind of data
Send both tokens and data
CCIP sender can be:
EOA
Any smart contract
CCIP receiver can be:
EOA
Any smart contract that implements CCIPReceiver.sol
To implement Burn-and-Mint model using Chainlink CCIP, we will on cross-chain transfer function burn an NFT on the source blockchain (Arbitrum Sepolia) and send the cross-chain message using Chainlink CCIP. We will need to encode to
and from
addresses, NFT's tokenId
and tokenURI
so we can mint exactly the same NFT on the destination blockchain once it receives a cross-chain message.
The Arbitrum Sepolia side:
The Ethereum Sepolia side:
This design allows the cross-chain transfer and vice-versa, from Ethereum Sepolia back to Arbitrum Sepolia using the exact same codebase and Burn-and-Mint mechanism.
For this Cross-Chain NFT we will use Four Chainlink Warriors hosted on IPFS, as a Metadata.
Chainlink Elf, available at: https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1
Chainlink Knight, available at: https://ipfs.io/ipfs/QmZGQA92ri1jfzSu61JRaNQXYg1bLuM7p8YT83DzFA2KLH
Chainlink Orc, available at: https://ipfs.io/ipfs/QmW1toapYs7M29rzLXTENn3pbvwe8ioikX1PwzACzjfdHP
Chainlink Witch, available at: https://ipfs.io/ipfs/QmPMwQtFpEdKrUjpQJfoTeZS1aVSeuJT6Mof7uV29AcUpF
For this exercise we will try to follow some of the CCIP Best Practices. For the full list you should always refer to the Chainlink Official Documentation.
It's crucial for this exercise that sending cross-chain messages is between Cross-Chain NFT smart contracts. To accomplish that, we need to track a record of these addresses on different blockchains using their CCIP chain selectors.
When you implement the ccipReceive
method in the contract residing on the destination chain, validate that the msg.sender
is the correct Router address. This verification ensures that only the Router contract can call the ccipReceive
function on the receiver contract and is for developers that want to restrict which accounts are allowed to call ccipReceive
.
gasLimit
The gasLimit
specifies the maximum amount of gas CCIP can consume to execute ccipReceive()
on the contract located on the destination blockchain. It is the main factor in determining the fee to send a message. Unspent gas is not refunded.
extraArgs
The purpose of extraArgs
is to allow compatibility with future CCIP upgrades. To get this benefit, make sure that extraArgs
is mutable in production deployments. This allows you to build it off-chain and pass it in a call to a function or store it in a variable that you can update on-demand.
If extraArgs
are left empty, a default of 200000 gasLimit
will be set.
To make extraArgs
mutable, set them as described previously in the enableChain
function. To calculate which bytes
value to pass, you can create a helper script like this: