Skip to main content

Deploy an NFT

Learn how to create, deploy, and mint NFTs on Nexis Appchain using ERC721 standards.

Simple NFT Contract

contracts/SimpleNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract SimpleNFT is ERC721, ERC721URIStorage, Ownable {
    uint256 private _tokenIdCounter;

    constructor() ERC721("My NFT", "MNFT") Ownable(msg.sender) {}

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter++;
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // Required overrides
    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

Advanced NFT with Features

contracts/AdvancedNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract AdvancedNFT is
    ERC721,
    ERC721URIStorage,
    ERC721Burnable,
    ERC721Royalty,
    AccessControl
{
    using Counters for Counters.Counter;

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    Counters.Counter private _tokenIdCounter;

    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public mintPrice = 0.01 ether;
    bool public publicMintEnabled = false;

    event Minted(address indexed to, uint256 indexed tokenId);

    constructor() ERC721("Advanced NFT", "ANFT") {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
        
        // Set default royalty: 5% to creator
        _setDefaultRoyalty(msg.sender, 500); // 500 = 5%
    }

    function mint(address to, string memory uri) public payable {
        require(publicMintEnabled || hasRole(MINTER_ROLE, msg.sender), "Minting disabled");
        require(_tokenIdCounter.current() < MAX_SUPPLY, "Max supply reached");
        
        if (!hasRole(MINTER_ROLE, msg.sender)) {
            require(msg.value >= mintPrice, "Insufficient payment");
        }

        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
        
        emit Minted(to, tokenId);
    }

    function setMintPrice(uint256 _price) external onlyRole(DEFAULT_ADMIN_ROLE) {
        mintPrice = _price;
    }

    function togglePublicMint() external onlyRole(DEFAULT_ADMIN_ROLE) {
        publicMintEnabled = !publicMintEnabled;
    }

    function setRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        _setTokenRoyalty(tokenId, receiver, feeNumerator);
    }

    function withdraw() external onlyRole(DEFAULT_ADMIN_ROLE) {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    function totalSupply() public view returns (uint256) {
        return _tokenIdCounter.current();
    }

    // Required overrides
    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721URIStorage, ERC721Royalty, AccessControl)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

    function _burn(uint256 tokenId)
        internal
        override(ERC721, ERC721URIStorage, ERC721Royalty)
    {
        super._burn(tokenId);
    }
}

Metadata Structure

metadata.json
{
  "name": "NFT #1",
  "description": "An amazing NFT on Nexis Appchain",
  "image": "ipfs://QmXxxx.../image.png",
  "attributes": [
    {
      "trait_type": "Background",
      "value": "Blue"
    },
    {
      "trait_type": "Rarity",
      "value": "Legendary"
    }
  ]
}

Deploy and Mint

scripts/deploy-nft.js
const hre = require("hardhat");
const { ethers } = require("hardhat");

async function main() {
  const [deployer] = await ethers.getSigners();

  console.log("Deploying NFT with:", deployer.address);

  // Deploy NFT
  const NFT = await ethers.getContractFactory("AdvancedNFT");
  const nft = await NFT.deploy();
  await nft.waitForDeployment();

  const address = await nft.getAddress();
  console.log("NFT deployed:", address);

  // Mint first NFT
  const tokenURI = "ipfs://QmXxxx.../metadata.json";
  const mintTx = await nft.mint(deployer.address, tokenURI);
  await mintTx.wait();

  console.log("Minted NFT #0 to:", deployer.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Upload to IPFS

scripts/upload-to-ipfs.js
const { create } = require('ipfs-http-client');
const fs = require('fs');

const ipfs = create({ url: 'https://ipfs.infura.io:5001/api/v0' });

async function uploadToIPFS() {
  // Upload image
  const image = fs.readFileSync('./assets/image.png');
  const imageResult = await ipfs.add(image);
  console.log('Image CID:', imageResult.path);

  // Upload metadata
  const metadata = {
    name: "My NFT #1",
    description: "A cool NFT",
    image: `ipfs://${imageResult.path}`,
    attributes: [
      { trait_type: "Rarity", value: "Rare" }
    ]
  };

  const metadataResult = await ipfs.add(JSON.stringify(metadata));
  console.log('Metadata CID:', metadataResult.path);
  console.log('Full URI:', `ipfs://${metadataResult.path}`);

  return `ipfs://${metadataResult.path}`;
}

uploadToIPFS().catch(console.error);

Interact with NFT

const nft = new ethers.Contract(nftAddress, nftABI, signer);

// Mint NFT
const tx = await nft.mint(recipientAddress, "ipfs://Qm.../metadata.json");
await tx.wait();

// Get token URI
const uri = await nft.tokenURI(0);
console.log("Token URI:", uri);

// Transfer NFT
const transferTx = await nft.transferFrom(fromAddress, toAddress, tokenId);
await transferTx.wait();

// Check royalty info
const salePrice = ethers.parseEther("1");
const [receiver, royaltyAmount] = await nft.royaltyInfo(tokenId, salePrice);
console.log("Royalty:", ethers.formatEther(royaltyAmount), "to", receiver);

Next Steps