直接撸代码。
本文使用hardhat框架进行智能合约的编写、部署以及区块链浏览器代码验证开源。
开发环境需要安装node、npm、npx等。
#安装hardhat
npm install --save-dev hardhat
#创建工程目录并进入目录
mkdir nft-demo
cd nft-demo
#初始化hardhat工程
npx hardhat
运行完以上指令后,会提示安装hardhat包,直接回车。

选择第一项,创建一个简单的模板项目。
一路回车,之后等待安装npm包。然后开始撸代码。
hardhat目录结构以及详细内容可以查阅文档。
我们到contracts目录下开始编写智能合约,习惯上,我们先编写合约接口,预先定好合约除了继承的方法之外,还需要提供哪些方法。
创建contracts/interfaces/IDemoNft.sol文件。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IDemoNft {
/** NFT总供应量 */
function totalSupply() external view returns (uint256);
/** NFT创建,需要传入NFT接收地址 */
function mint(address to) external;
/** 设置储存matedate的baseURI */
function setBaseURI(string memory _newUri) external;
}
定义3个常见的NFT方法,然后开始编写主合约。
创建contracts/DemoNft.sol文件。
这里我们使用了openzeppelin提供的一些标准合约库,因此我们需要先安装openzeppelin。
npm i @openzeppelin/contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
//我们自己定义的合约接口
import "./interfaces/IDemoNft.sol";
//最常见的NFT标准合约ERC721
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
//计数器合约
import "@openzeppelin/contracts/utils/Counters.sol";
//所有权管理合约
import "@openzeppelin/contracts/access/Ownable.sol";
contract DemoNft is IDemoNft, ERC721, Ownable {
using Counters for Counters.Counter;
using Strings for uint256;
//定义baseURI
string public baseURI;
//定义一个私有变量,记录NFT tokenId,该变量为每mint一个NFT,累加1
Counters.Counter private _tokenIdTracker;
//合约构造函数,初始化ERC721的name和symbol
constructor() ERC721("MB DEMO NFT", "MDNFT") {}
/**
* 继承并覆盖ERC721中IERC721Enumerable的totalSupply方法
* 返回当前NFT的总供应量
*/
function totalSupply() public view override returns (uint256) {
return _tokenIdTracker.current();
}
/**
* 实现IDemoNft接口中的mint方法
* 传入NFT接收地址,创建一个新的NFT
*/
function mint(address to) public override{
//_tokenIdTracker自增1,作为此次NFT TOKEN ID
_tokenIdTracker.increment();
//调用ERC721标准mint方法,传入NFT接收地址和NFT TOKEN ID
_safeMint(to, _tokenIdTracker.current());
}
/**
* 实现IDemoNft接口中的setBaseURI方法
* 设置储存matedate的baseURI,并指定只有合约拥有者可以调用
*/
function setBaseURI(string memory _newUri) public override onlyOwner {
baseURI = _newUri;
}
/**
* 继承并覆盖ERC721中IERC721Metadata的tokenURI方法
* 根据NFT TOKEN ID 获取NFT元数据的储存链接
*/
function tokenURI(uint256 tokenId)
public
view
virtual
override
returns (string memory)
{
// 判断NFT TOKEN ID 是否存在
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
// 判断baseURI是否存在,存在则拼上TOKEN ID作为返回值
// 不存在则返回空字符串
string memory currentBaseURI = baseURI;
return
bytes(currentBaseURI).length > 0
? string(
abi.encodePacked(
currentBaseURI,
tokenId.toString()
)
)
: "";
}
}
由此,我们的ERC721合约就基本编写完成,接下来就是编译、上链。
开始编译合约。
npx hardhat compile
编译完成后,进行部署上链。
部署上链之前需要做一些配置。
首先配置网络,这里我们使用rinkeby测试网。
在rinkeby测试网上发布合约并mint之后可以在opensea测试网上显示我们的NFT。
其次配置区块链浏览器apikey,后面验证区块链源码需要用到。
编辑hardhat.config.js底部的module.exports部分。
module.exports = {
//网络配置
networks: {
rinkeby: {
//节点服务器链接
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
//rinkeby链id
chainId: 4,
accounts: {
//你的账户助记词
mnemonic: process.env.MNEMONIC,
},
}
},
//以太坊浏览器配置,用于验证浏览器合约代码开源。
etherscan: {
//以太坊浏览器apikey,注册账号即可申请。
apiKey: process.env.ETHERSCAN_APIKEY,
},
//solidity编译器版本
solidity: "0.8.4",
};
接下来配置我们上述代码中的env环境变量,在目录下创建.env文件,填写以下变量。
MNEMONIC=''
INFURA_API_KEY=''
ETHERSCAN_APIKEY=''
随后在hardhat.config.js中加载环境变量。
#加载.env环境变量
const dotenv = require("dotenv");
dotenv.config();
注:本文后面会贴上hardhat.config.js完整代码,以及整个工程代码。
接下来在hardhat.config.js中编写部署合约代码。
//部署合约
task("deploy", "deploy contract").setAction(
async (taskArgs, { ethers, run, network }) => {
await run("compile");
const signers = await ethers.getSigners();
//使用助记词账户中第一个账户进行合约部署
const signer = signers[0];
console.log("部署合约:DemoNft");
console.log("部署账户:", await signer.getAddress());
//获取合约工厂类
const factory = await ethers.getContractFactory("DemoNft", {
signer: signer
});
//部署合约
const contract = await factory.deploy();
console.log("合约部署地址:", contract.address);
console.log("合约部署哈希:", contract.deployTransaction.hash);
}
);
接下来运行部署合约指令,指定部署到rinkeby网络。
npx hardhat deploy --network rinkeby

部署成功,到区块链浏览器查看。

发现区块链浏览器中只有字节码,所以接下来我们使用hardhat对合约进行验证发布源代码。
运行以下指令。
npx hardhat verify --network rinkeby 0x6E7bf2Db13957648D83eD38a7450230301f00B21

运行这一步,因为跟rinkeby区块链浏览器交互,所以需要科学上网。
提示成功后,重新刷新区块链浏览器,发现可以读取源码,以及阅读、编写合约函数了。

所以我们接下来先设置NFT元数据的baseURI,本文使用IPFS来存储这些NFT元数据。
下篇文章我会详细描述NFT元数据怎么产生和如何上传到IPFS上。

连接小狐狸,填写参数调用setBaseURI函数。

成功之后,开始mint一个NFT,填写接收地址,调用mint函数。

成功之后,我们到opensea测试网查看。


我们发现刚刚发布的NFT显示出了图片,属性等信息,那么这些信息是怎么显示出来的,储存在哪里,下次再说~
附上hardhat.config.js完整代码:
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-etherscan");
const dotenv = require("dotenv");
dotenv.config();
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
//部署合约
task("deploy", "deploy contract").setAction(
async (taskArgs, { ethers, run, network }) => {
await run("compile");
const signers = await ethers.getSigners();
//使用助记词账户中第一个账户进行合约部署
const signer = signers[0];
console.log("部署合约:DemoNft");
console.log("部署账户:", await signer.getAddress());
//获取合约工厂类
const factory = await ethers.getContractFactory("DemoNft", {
signer: signer
});
//部署合约
const contract = await factory.deploy();
console.log("合约部署地址:", contract.address);
console.log("合约部署哈希:", contract.deployTransaction.hash);
// contract.deployed();
}
);
// 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 = {
//网络配置
networks: {
rinkeby: {
//节点服务器链接
url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`,
//rinkeby链id
chainId: 4,
accounts: {
//你的账户助记词
mnemonic: process.env.MNEMONIC,
},
}
},
//以太坊浏览器配置,用于验证浏览器合约代码开源。
etherscan: {
//以太坊浏览器apikey,注册账号即可申请。
apiKey: process.env.ETHERSCAN_APIKEY,
},
//solidity编译器版本
solidity: "0.8.4",
};
有什么问题,欢迎添加微信交流~

