# 如何发行一款NFT(上)

By [explorer_of_web3](https://paragraph.com/@explorer-of-web3) · 2022-05-02

---

希望通过本文，可以让有发行NFT想法的小伙伴，发行一款自己的NFT。

如何发行一款社区概念的NFT，一般是有4个步骤

1.  发起人完成白皮书
    
2.  完成NFT设计并编写智能合约代码发布到链上
    
3.  完成NFT社区官网，一般包含白皮书简介，mint,NFT列表展览等功能
    
4.  社区运营以及持续为达成白皮书的目标而合作迭代
    

我们选择技术点涉及较多的2、3步骤和大家讨论。分为上下两篇文章，本文将重点讲解第2点如何完成NFT智能合约的开发、部署、交互以及在交易市场查看自己发行的NFT。下篇文章会重点讲解第3点NFT社区官网的搭建。

### 合约开发环境选择

目前主流的智能合约开发环境有3个:remix,hardhat, truffle。remix是浏览器IDE，虽然开箱即用，上手简单，但在灵活性和功能完善度上不如hardhat, truffle。hardhat, truffle相比较，truffle是老牌主流框架，hardhat是新起之秀，但目前也足够稳定。 我们最终选择hardhat作为我们的本地合约开发环境。

### 初始化工程

    //创建一款web3探索者的nft
    mkdir  nft-web3-explorer
    
    //进入目录
    cd nft-web3-explorer
    
    //初始化项目，根据提示填写即可，packname和description填写即可
    npm init
    
    //添加hardhat依赖
    npm install --save-dev hardhat
    
    
    /*使用脚手架搭建项目,我们选择Create a basic sample project
    可以帮助我们创建1个demo工程并按照所需的依赖*/
    npx hardhat
    

创建完成后，我们看看我们的目录结构

![](https://storage.googleapis.com/papyrus_images/6ebb4d31707de45d74fe7a7a3246c96edb42d996e8926d1728f0792274c46bf9.jpg)

*   contracts目录用来存放我们的智能合约代码
    
*   scripts用来存放我们的脚本，比如合约部署就会依赖其中的脚本
    
*   test目录用来存放我们为智能合约编写的测试代码
    
*   hardhat.config.js是关于hardhat框架的一些配置，比如solidity版本等
    
*   package.json是npm的相关配置
    

使用vscode打开该工程,最好安装vscode的solidity插件。

### 编写合约代码

目前就可以开始写合约代码了，本文主要是介绍NFT的发行，我们简单来实现一份NFT智能合约代码。

    //安装openzeppelin/contracts依赖，内置较多合约协议的实现以及工具代码
    npm install @openzeppelin/contracts
    

我们在contracts目录下创建NFT\_WEB3\_EXPOLRER.sol的文件。

    pragma solidity ^0.8.0;
    
    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
    
    contract NFT_WEB3_EXPLORER is ERC721, ERC721Enumerable, Ownable {
        string private _baseURIextended;
        //我们设置最多可以mint1000个
        uint256 public constant MAX_SUPPLY = 1000;
        //每个mint的价格是0.01EHT
        uint256 public constant PRICE_PER_TOKEN = 0.01 ether;
    
        constructor() ERC721("nft_web3_explorer", "NFT_WEB3_EXPLORER") {
        }
    
        function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
            super._beforeTokenTransfer(from, to, tokenId);
        }
    
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
            return super.supportsInterface(interfaceId);
        }
    
        function setBaseURI(string memory baseURI_) external onlyOwner() {
            _baseURIextended = baseURI_;
        }
    
        function _baseURI() internal view virtual override returns (string memory) {
            return _baseURIextended;
        }
    
        function mint(uint numberOfTokens) public payable {
            uint256 ts = totalSupply();
            require(ts + numberOfTokens <= MAX_SUPPLY, "Purchase would exceed max tokens");
            require(PRICE_PER_TOKEN * numberOfTokens <= msg.value, "Ether value sent is not correct");
    
            for (uint256 i = 0; i < numberOfTokens; i++) {
                _safeMint(msg.sender, ts + i);
            }
        }
    
        function withdraw() public onlyOwner {
            uint balance = address(this).balance;
            payable(msg.sender).transfer(balance);
        }
    }
    

非常简单，这源于ERC721协议的帮助。该代码不具备上线标准，因为没有白名单，每个地址限制mint多少个等，仅供示意。建议发行NFT前需要好好设计合约，

### 合约编译

    //合约编译
    npx hardhat compile
    

### 合约部署

修改scripts目录下自动生成的脚本改名为deploy.js并修改内部代码

    const hre = require("hardhat");
    
    async function main() {
      // Hardhat always runs the compile task when running scripts with its command
      // line interface.
      //
      // If this script is run directly using `node` you may want to call compile
      // manually to make sure everything is compiled
      // await hre.run('compile');
    
      // We get the contract to deploy
      const Nft_web3_explrer = await hre.ethers.getContractFactory("NFT_WEB3_EXPLORER");
      const nft_web3_explrer = await Nft_web3_explrer.deploy();
    
      await nft_web3_explrer.deployed();
    
      console.log("NFT_WEB3_EXPLORER deployed to:", nft_web3_explorer.address);
    }
    
    // We recommend this pattern to be able to use async/await everywhere
    // and properly handle errors.
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    

#### 选择节点服务代理

此时，因为我们并不想在本地运行1个以太坊节点，所以我们使用节点服务代理(管理节点)，我们与他们进行api交互，他们会在云上管理的节点中执行对应的节点操作。这里我们选用与MetaMask同一服务代理 [https://infura.io/](https://infura.io/) 注册账号后，我们创建1个项目

![](https://storage.googleapis.com/papyrus_images/f561bf62fdc1a7aeff6f1b1d7327fae98becc5a5a2a33b36bf0cbba43b0ca6ba.png)

#### 测试网络环境搭建

我们首先需要在测试网络中部署，由于很多NFT交易市场只支持rinkeby，所以我们选择rinkeby作为我们的测试网络。

因为在代码执行过程中会用到公钥、私钥、API等数据，但这些数据又不能编码到代码中，因为如果我们使用git来管理项目，信息将会泄露。我们使用dotenv存放部署合约以及和合约交互需要用到的数据。

    //添加dotenv依赖
    npm install dotenv
    
    //工程根目录下创建变量存储文件
    touch .env
    

为.env增加内容

    PRIVATE_KEY=导出你metamask的私钥填在这里
    API=找到节点服务代理URL填在这里
    PUBLIC_KEY=metamask地址
    NETWORK=rinkeby
    API_KEY=节点服务代理的ProjectID
    

*   metamask私钥导出:打开钱包-账号详情->导出私钥。
    
*   服务代理URL:将网络切换到ROPSTEN，复制下面的https链接。
    

![](https://storage.googleapis.com/papyrus_images/ceaae2eae36ceacc39ee11cbcbdf942c8352983978ca09f298c8716848a98fee.jpg)

修改hardhat.config.js

    require("@nomiclabs/hardhat-waffle");
    
    // This is a sample Hardhat task. To learn how to create your own go to
    // https://hardhat.org/guides/create-task.html
    require('dotenv').config();
    const { API, PRIVATE_KEY } = process.env;
    
    // You need to export an object to set up your config
    // Go to https://hardhat.org/config/ to learn more
    
    /**
     * @type import('hardhat/config').HardhatUserConfig
     */
    module.exports = {
      solidity: "0.8.4",
      defaultNetwork: "rinkeby",
      networks: {
        hardhat: {},
        rinkeby: {
          url: API,
          accounts: [`0x${PRIVATE_KEY}`]
        }
      },
    };
    

获取测试网络ETH 因为合约测试需要消耗ETH，我们需要获取一些测试完了的ETH，我们打开[https://fauceth.komputing.org/](https://fauceth.komputing.org/) 选择rinkeby网络，填入我们的钱包地址即可获取。

此时我们将钱包切换到rinkeby测试网络，可以看到钱包中已经有一些ETH了。

#### 执行合约部署

    npx hardhat --network rinkeby run scripts/deploy.js
    
    //执行完成后得到提示，代表合约部署完成
    NFT_WEB3_EXPOLRER deployed to: {合约地址}
    

此时我们可以在[https://rinkeby.etherscan.io/](https://rinkeby.etherscan.io/)搜索我们的合约地址找到我们的合约信息

![](https://storage.googleapis.com/papyrus_images/203c748b6ef33756fadfcec1a89c4397b38c7fa09febc0027a96dd9ef5d6ad97.jpg)

### 为NFT设置资源

现在的NFT仅仅包含tokenID，我们需要为其设置资源, 在ERC721的实现中我们可以看到tokenURI方法的实现，各个交易市场就是调用该方法来读取NFT资源信息的

        /**
         * @dev See {IERC721Metadata-tokenURI}.
         */
        function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
            require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
    
            string memory baseURI = _baseURI();
            return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
        }
    

我们可以看到这里是将BaseURI和tokenID拼接来读取tokenURI的，所以我们也按照这种格式来准备资源。

#### 准备资源

    //创建资源文件
    mkdir res
    cd res
    //存放图片
    mkdir  img
    //存放metadata信息
    mkdir  metadata
    

由于本文是示例项目，没有找专门的设计师设计图片，我们网上找了7张图片。将7张图片放入img目录下，依次命名为0.png ~ 7.png

![](https://storage.googleapis.com/papyrus_images/d0f0c4901e38e74059cea382ee3802cecd096afeeaea871dc56c8a39c4a3d402.png)

我们将7张图片上传到ipfs中，这里和以太坊网络一样，由于我们不想运行本地ipfs节点，所以我们寻找节点服务提供商进行上传。我们使用[https://app.pinata.cloud/](https://app.pinata.cloud/)选择上传目录进行将img目录上传 上传完成后的图片资源

![](https://storage.googleapis.com/papyrus_images/2fed12411762963795e2cdeceb690e3e36d506e7b286cc6a21e93eef6381c07a.jpg)

接下来我们准备metadata文件,在metadata目录下新建7个文件依次命名为0~7，我们看下0号文件信息

    {
        "name": "nft-web3-explorer",
        "attributes": [
            {
                "trait_type": "tokenID",
                "value": "0"
            }
        ],
        "description": "nft-web3-explorer image",
        //填入上面刚刚上传完成的图片地址
        "image": "ipfs://QmZ3Y31SwLU77CDfBoL5MphuSmrv414d2ZyunVcbNAJQRQ/0.png"
    }
    

![](https://storage.googleapis.com/papyrus_images/a41718bc8236a3681c2179193ac6af4bf0ff7ca95a7fed25b8bfc1b7f913339e.jpg)

![](https://storage.googleapis.com/papyrus_images/fd781ca0e341f620b11a66b607d5c04fd92b9dabb7bfa8774f8b75c7cf8ce109.jpg)

#### 设置BaseURI

我们需要将刚刚上传后的metadata文件地址设置给合约的baseURI，这样各个平台在使用tokenURI获取资源信息才可以获取到。

编写代码与合约交互设置BaseURI，在scripts目录下新建setBaseURI.js文件。

setBaseURI.js

    require("dotenv").config()
    const hre = require("hardhat");
    const PRIVATE_KEY = process.env.PRIVATE_KEY
    const NETWORK = process.env.NETWORK
    const API_KEY = process.env.API_KEY
    
    
    const provider = new hre.ethers.providers.InfuraProvider(NETWORK, API_KEY);
    //编译完成合约会自动生成
    const abi = require("../artifacts/contracts/NFT_WEB3_EXPLORER.sol/NFT_WEB3_EXPLORER.json").abi
    const contractAddress = "合约地址"
    const contract = new hre.ethers.Contract(contractAddress, abi, provider)
    const wallet = new hre.ethers.Wallet(PRIVATE_KEY, provider)
    const baseURI = "metadata文件地址"
    
    async function main() {
      const contractWithSigner = contract.connect(wallet);
      //调用setBaseURI方法
      const tx = await contractWithSigner.setBaseURI(baseURI)
      console.log(tx.hash);
      await tx.wait();
      console.log("setBaseURL success");
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    

在这里我们与合约交互主要是使用ethers，目前主流封装与合约交互的js库有2个web3.js与ethers.js，此处使用ethers的原因是hardhat默认和ethers配合。

    //执行setBaseURI脚本
    npx hardhat --network rinkeby run scripts/setBaseURI.js
    

### mint测试

因为只有mint过的tokenID才会展示在交易市场中，所以我们需要编写代码进行mint测试(实际场景，该操作应该由前端页面调用完成)。mint.js

    require("dotenv").config()
    const hre = require("hardhat");
    const PRIVATE_KEY = process.env.PRIVATE_KEY
    const NETWORK = process.env.NETWORK
    const API_KEY = process.env.API_KEY
    
    
    const provider = new hre.ethers.providers.InfuraProvider(NETWORK, API_KEY);
    const abi = require("../artifacts/contracts/NFT_WEB3_EXPOLRER.sol/NFT_WEB3_EXPOLRER.json").abi
    const contractAddress = "合约地址"
    const contract = new hre.ethers.Contract(contractAddress, abi, provider)
    const wallet = new hre.ethers.Wallet(PRIVATE_KEY, provider)
    
    async function main() {
        const contractWithSigner = contract.connect(wallet);
        //获取mint需要多少ETH
        const price = await contract.PRICE_PER_TOKEN();
        console.log("price is" + price);
        //调用mint方法，支付mint费用
        const tx = await contractWithSigner.mint(1, { value: price});
        console.log(tx.hash);
        await tx.wait();
        console.log("mint success");
    }
    
    main()
        .then(() => process.exit(0))
        .catch((error) => {
            console.error(error);
            process.exit(1);
        });
    

    //执行mint脚本
    npx hardhat --network rinkeby run scripts/mint.js
    

### 查看NFT

此时我们去opensea或looksrare等NFT市场查看我们的NFT即可(个人建议looksrare查看，opensea测试网络速度很慢，而且metadata信息更新也比较慢)

*   opensea测试网络地址:[https://testnets.opensea.io/](https://testnets.opensea.io/)
    
*   looksrare测试网络地址:[https://rinkeby.looksrare.org/](https://rinkeby.looksrare.org/)
    

![](https://storage.googleapis.com/papyrus_images/5434a0a8a5a7eb7d4985084e6a58d731046e5bf56ad006b648d871e8bc14051e.jpg)

结尾
--

本篇文章我们介绍了如何一步步编写合约，部署以及与合约交互完成将NFT发布到链上，下篇文章将介绍如何搭建NFT的官网。

\========

往期文章

Layer2: [真正理解 Layer2](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483734&idx=1&sn=57f3834e10f6de540c4a1534ba8d9e70&chksm=c1c9975cf6be1e4a2ac9d0d6608ee0b3f2994420ad805c02946915c3d90a7cdb15a10a018fae&scene=21#wechat_redirect) [Immutable X白皮书(译)](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483774&idx=1&sn=aeffb5195a63c227410aa5f234b3d3bd&chksm=c1c99774f6be1e62e03fad2647da7b3601459c7b5d3a254d8ed1ab4c9a4655d2de2e54a2b329&scene=21#wechat_redirect) 跨链: [关于Cosmos的研究](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483792&idx=1&sn=bee0ccb5301576342289bf48e806a9ad&chksm=c1c9979af6be1e8cc188486e253e0bb9b15b1b3db7bcb02f6501c10a8bb8e3862290f45b51f2&scene=21#wechat_redirect) 基础理论: [关于区块链不可能三角的研究](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483691&idx=1&sn=9e92b314e70d813f17d7766295eb7ab4&chksm=c1c99721f6be1e379cb937216fd2443583c4f4a333c399ba0b2f5039165e15071850f98961e0&scene=21#wechat_redirect) [关于零知识证明的研究](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483824&idx=1&sn=88599bec868ca1d2d85e8056fb7857fa&chksm=c1c997baf6be1eac9128170e0d34504b4e57e21319fe02e405ba8243f7551e6afa56bb891fdb&scene=21#wechat_redirect) 以太坊: [以太坊技术系列-以太坊数据结构](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483679&idx=1&sn=ad595ee13b0c85b1ce6f2cf11ba8df2e&chksm=c1c99715f6be1e03e21cc83c36cf4e2d395416d36294b23a828cffd3140be0cec276922f4aab&scene=21#wechat_redirect) [以太坊技术系列-以太坊共识机制](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483704&idx=1&sn=8421aad1400b24aa38ec06a160220c03&chksm=c1c99732f6be1e24fa8358e898fba6e73043688760b7feffd2c27d3008cfa71722f8ad1cad4b&scene=21#wechat_redirect) [以太坊技术系列-钱包-以太坊中的账户](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483681&idx=1&sn=4e6ccde6bee60503086b07734e08baf0&chksm=c1c9972bf6be1e3dc094c893ca748a33f13180ebe7449d70b42dcb583d390bef2080bb800ddc&scene=21#wechat_redirect) [聊一聊智能合约](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483716&idx=1&sn=79b371ca4ad39ac02cdca46763e8ff26&chksm=c1c9974ef6be1e5834b9cf367afd92f95c2f7b47920f36fdec17a7c7cce306e697e5a71e3d13&scene=21#wechat_redirect) 去中心化存储: [去中心化存储的那些事(上)](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483753&idx=1&sn=c6686a22cc8b34e55387c788fcaaf7cc&chksm=c1c99763f6be1e759df17122d52c420c7af69e516212d1aa7bacc3d8cf76d52ba293a08abb31&scene=21#wechat_redirect) [去中心化存储那些事(下)](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483761&idx=1&sn=7ac0f7ac5fb9c92631c954476e96c792&chksm=c1c9977bf6be1e6d12de1c68c86c58c561ecb88fcb2790525ab05f534b3d5c6a7c5d656524b7&scene=21#wechat_redirect) 其他公链: [Flow白皮书1-共识和计算分离(译)](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483807&idx=1&sn=116947704b21817f9f01b69f3adc4732&chksm=c1c99795f6be1e834a4a502276613ef1200c70497e3e434566adbf94735e896011ede6f45643&scene=21#wechat_redirect) 智能合约实战: [智能合约升级详解](http://mp.weixin.qq.com/s?__biz=MzkyNTI4NzI2OQ==&mid=2247483847&idx=1&sn=ef48c48445cb1e621617b0455daede13&chksm=c1c997cdf6be1edb78d77675d0e8fdae7df8a9454323ea8aecd26c0e9ead53b299cfc919928e&scene=21#wechat_redirect)

---

*Originally published on [explorer_of_web3](https://paragraph.com/@explorer-of-web3/nft)*
