Deploy an Omnichain NFT
Overview
ZetaChain offers two types of contracts: Cross Chain Messaging Contracts (CCM) and zEVM Contracts (Omnichain Smart Contracts).
This tutorial specifically focuses on deploying a CCM-based Omnichain NFT. This NFT may be transferred between different chains seamlessly by passing messages between blockchains.
Prerequisites/Setup
This tutorial will use the zetachain
public repository as a starting point. This repository contains examples and references for development with ZetaChain.
Make sure your machine has
git
installed: https://git-scm.com/Install Node.js LTS (previous versions may, but are not guaranteed to work).
Install
yarn
(make sure NPM has the right permissions to add global packages):npm i -g yarn
Clone the
zetachain
repository to your machinegit clone git@github.com:zeta-chain/zetachain.git cd zetachain
Install the dependencies:
yarn
From the root folder, compile the contracts:
yarn compile
This will compile all the contracts in the repository for you so that you can try deploying them. You can compile these later if you need to edit anything.
You should have an EVM-compatible wallet (not necessarily for this tutorial, but in general). We recommend Metamask as an easy default, but also XDEFI if you’re planning on developing dApps that incorporate Bitcoin.
Deploying a Cross Chain NFT (Cross-Chain Messaging)
To understand how cross chain messaging works, we will deploy a cross chain NFT on Goerli and BSC (also called BNB Chain). The NFT uses ZetaChain’s cross-chain messaging to “burn” and “mint” the NFT in order to transfer it across chains while still remaining unique. While normal NFTs are bound to the chain they’re minted on, these NFTs can move and interact with data/assets on any blockchain.
First, we need to set up our local environment by running
yarn install
in the project's root directory.Next, we need to set up our environment variables by updating, or creating if doesn’t exists, the
.env
file inzetachain/packages/example-contracts
:ALCHEMY_API_KEY_MAINNET=<your-key> ALCHEMY_API_KEY_GOERLI=<your-key>
You can acquire these by making a free account at https://www.alchemy.com/. More details on how to create keys are here: https://docs.alchemy.com/docs/alchemy-quickstart-guide#1key-create-an-alchemy-key. Note that you will need two different API keys, one for the Ethereum Mainnet and one for Goerli Testnet. You can use the same key for both for now, but keep in mind that if you choose Goerli when you get the key on Alchemy site, the key will not work for the Mainnet.
This project uses Alchemy (through your specific keys) to access RPC of external blockchains like Ethereum that ZetaChain connects. More broadly, you can choose other providers or even run your own nodes of those blockchains to access this data, and Alchemy is used here as an example.
We also need to set up our deployer's private key in the .env file:
PRIVATE_KEY=<your-key>
Note: There are several ways to create a new private key, if you are not familiar with that you can refer to ethers documentation https://docs.ethers.org/v5/api/signer/#Wallet-createRandom. This will let you generate a new Wallet with a random private key. You can get the address and private key using the following script:
- In the examples folder, create a new file called
wallet.js
// wallet.js const ethers = require('ethers'); const newWallet = ethers.Wallet.createRandom() console.log(newWallet.address) console.log(newWallet.privateKey) // when entering this into .env, remove the `0x`
- After saving this, you can run
node wallet.js
and it should output something like:0x4eC45...505e4d 0x74bd80...34c82772
- Your .env could look like (copy the entire key without the
0x
! the...
is just here as an example):PRIVATE_KEY=74bd80...34c82772
0x4eC45...505e4d
is your public address of the wallet you generated, and you can send assets to this address. Make sure you keep track of both so you have it on hand later! Be aware that this is not a recommendation of a secure way to manage your keys for a Mainnet application; this is solely for development and testing purposes.
When deploying a CCM contract, there are two steps involved. First, we need to deploy the contracts on each chain we want to use. Second, we need to set up each contract so they can recognize each other's addresses.
Our goal is to:
- Deploy the contract on Goerli
- Deploy the contract on BSC
- Set up each contract to function together
- In the examples folder, create a new file called
We have a logic in place that writes the deployed address to a JSON file in the addresses package. You will find the file at
packages/addresses/src/addresses.athens.json
and it is indexed by chain, with each attribute being a contract name. In this example, the contract name iscrossChainNft
.To deploy to Goerli, go to the
packages/example-contracts/
directory and you will find several scripts. Take a look atpackages/example-contracts/scripts/cross-chain-warriors/deploy.ts
. This script deploys a new instance of CrossChainWarriors (a demo NFT) and mints the first one to the deployer. We will use Hardhat, a powerful tool for working with smart contracts, to run this script.The command to run the script is as follows:
ZETA_NETWORK=athens EXECUTE_PROGRAMMATICALLY=true npx hardhat run scripts/cross-chain-warriors/deploy.ts --network goerli
Let's go over each flag in this command:
ZETA_NETWORK=athens
refers to the version of the ZetaChain you will use, which determines the connector address used to communicate with ZetaChain.EXECUTE_PROGRAMMATICALLY=true
is used to call other scripts while only executing the caller and importing the other.The
--network goerli
flag specifies the blockchain network where the deployment will take place. It is important to note that this command will be executed using the private key previously set up, so make sure there are sufficient funds in the chosen network to perform the deployment.To deploy the contract, run the following command:
npx hardhat run scripts/cross-chain-warriors/deploy.ts
. This Hardhat command will execute the script and add the deployer information, etc. Once the command is executed, you can verify that the address file has been updated in your Git changes. Repeat the command using the--network "bsc-testnet"
flag.Next, update the data to allow the contract to communicate with each other by using the following command:
ZETA_NETWORK=athens EXECUTE_PROGRAMMATICALLY=true npx hardhat run scripts/cross-chain-warriors/set-cross-chain-data.ts --network goerli
The script will perform the following steps:
- Retrieve the contract address on the current chain.
- Retrieve the contract address on the opposite chain (for testing purposes, we have defined this concept in our repository and Goerli is opposite to BSC, but you can choose any chain you desire).
- Call the method
crossChainWarriorsContract.setInteractorByChainId
with two parameters: the first is the destination chain, and the second is the address of the contract on that chain.
Please note that you will need to run this script twice, once on each chain.
If you are on a non-unix command line (Wiindows), please add
ZETA_NETWORK=athens
andEXECUTE_PROGRAMMATICALLY=true
to your environment (e. g.set ZETA_NETWORK=athens
), and run the command starting withnpx hardhat...
.
Congratulations, you have successfully deployed your first cross-chain NFT! This NFT can be transferred between chains using ZetaChain’s cross-chain messaging functionality. This way, a unique asset like this NFT can span and be used across all blockchains. You can read the actual contract you’ve deployed in the packages/example-contracts/contracts/cross-chain-warriors/CrossChainWarriors.sol
file. If you’re building on top of this, you can edit the file and redeploy using the same steps as above.