Skip to main content
Quickstart Tutorials
Deploy an Omnichain NFT

Deploy an Omnichain NFT


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.


This tutorial will use the zetachain public repository as a starting point. This repository contains examples and references for development with ZetaChain.

  1. Make sure your machine has git installed:

  2. Install Node.js LTS (previous versions may, but are not guaranteed to work).

  3. Install yarn (make sure NPM has the right permissions to add global packages):

    npm i -g yarn

  4. Clone the zetachain repository to your machine

    git clone
    cd zetachain
  5. Install the dependencies:


  6. 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.

  7. 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.

  1. First, we need to set up our local environment by running yarn install in the project's root directory.

  2. Next, we need to set up our environment variables by updating, or creating if doesn’t exists, the .env file in zetachain/packages/example-contracts:


    You can acquire these by making a free account at More details on how to create keys are here: 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.

  3. We also need to set up our deployer's private key in the .env file:


    Note: There are several ways to create a new private key, if you are not familiar with that you can refer to ethers documentation 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.privateKey) // when entering this into .env, remove the `0x`
    • After saving this, you can run node wallet.js and it should output something like:
    • Your .env could look like (copy the entire key without the 0x! the ... is just here as an example):
      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
  4. 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 is crossChainNft.

    To deploy to Goerli, go to the packages/example-contracts/ directory and you will find several scripts. Take a look at packages/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.

  5. 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.

  6. 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:

    1. Retrieve the contract address on the current chain.
    2. 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).
    3. 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 and EXECUTE_PROGRAMMATICALLY=true to your environment (e. g. set ZETA_NETWORK=athens), and run the command starting with npx 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.