用Hardhat开发智能合约,并发布ERC721 NFT
January 31st, 2023

1. 安装Hardhat

npm install --save-dev hardhat

2. 初始化Hardhat工程

npx hardhat

3. 安装官方contract库

npm install @openzeppelin/contracts

4. 完成ERC721 NFT智能合约代码

目录contracts/DrawNFT.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

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

contract DrawNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("DrawNFT", "DNFT") {}

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

    // The following functions are overrides required by Solidity.

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

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

5. 完成合约部署代码

目录scripts/deploy.ts

import { ethers } from "hardhat";

async function main() {
  // DrawNFT
  const DrawNFT = await ethers.getContractFactory("DrawNFT");
  const drawNFT = await DrawNFT.deploy();
  await drawNFT.deployed();
  console.log(`drawNFT deployed to ${drawNFT.address}`);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

6. 启动本地私有链

npx hardhat node

7. 部署合约

npx hardhat run --network localhost scripts/deploy.ts

得到合约地址

drawNFT deployed to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512

8. 启动nodejs,并初始化环境

# node
Welcome to Node.js v16.15.1.
Type ".help" for more information.
> const { ethers } = require("ethers");
undefined
> const provider = new ethers.providers.JsonRpcProvider();
undefined
> account1 = new ethers.Wallet('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',provider)
Wallet {
  _isSigner: true,
  _signingKey: [Function (anonymous)],
  _mnemonic: [Function (anonymous)],
  address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  provider: JsonRpcProvider {
    _isProvider: true,
    _events: [],
    _emitted: { block: -2 },
    disableCcipRead: false,
    formatter: Formatter { formats: [Object] },
    anyNetwork: false,
    _networkPromise: Promise {
      [Object],
      [Symbol(async_id_symbol)]: 47,
      [Symbol(trigger_async_id_symbol)]: 5,
      [Symbol(destroyed)]: [Object]
    },
    _maxInternalBlockNumber: -1024,
    _lastBlockNumber: -2,
    _maxFilterBlockRange: 10,
    _pollingInterval: 4000,
    _fastQueryDate: 0,
    connection: { url: 'http://localhost:8545' },
    _nextId: 43,
    _eventLoopCache: { detectNetwork: null, eth_chainId: null },
    _network: { chainId: 31337, name: 'unknown' }
  }
}

9. 初始化contract

> abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"string","name":"uri","type":"string"}],"name":"safeMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]
...
> contract = new ethers.Contract('0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',abi,account1)
...

调用合约基本方法

> await contract.name()
'DrawNFT'
> await contract.symbol()
'DNFT'

10. mint出token

> contractWithSigner = contract.connect(account1)
...
> contractWithSigner.safeMint(await account1.getAddress(),'ipfs://Qmxxx')
{
  type: 2,
  chainId: 31337,
  nonce: 2,
  maxPriorityFeePerGas: BigNumber { _hex: '0x59682f00', _isBigNumber: true },
  maxFeePerGas: BigNumber { _hex: '0xb5f20e1e', _isBigNumber: true },
  gasPrice: null,
  gasLimit: BigNumber { _hex: '0x01cd79', _isBigNumber: true },
  to: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  data: '0xd204c45e000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000c697066733a2f2f516d7878780000000000000000000000000000000000000000',
  accessList: [],
  hash: '0x2884d83034461908dbd3be73470d36af5aec159098a8d6d049027cb5acc2c61d',
  v: 0,
  r: '0x825d7e0024a83a417f5ead21fc07044fb6e74253dea5b4de788a15c7344f1679',
  s: '0x3da7ab8323ab485741f9d1fdaae7a9c22fd8969a1e52ea50d9f6ab477de1cbbf',
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  confirmations: 0,
  wait: [Function (anonymous)]
}

查询是否mint成功

> await contractWithSigner.tokenURI(0)
'ipfs://Qmxxx'
> await contractWithSigner.ownerOf(0)
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'

11. 发送NFT

> account2 = new ethers.Wallet('0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',provider)
...
> contractWithSigner.transferFrom(await account1.getAddress(), await account2.getAddress(), 0)
{
  type: 2,
  chainId: 31337,
  nonce: 3,
  maxPriorityFeePerGas: BigNumber { _hex: '0x59682f00', _isBigNumber: true },
  maxFeePerGas: BigNumber { _hex: '0xac5e7c24', _isBigNumber: true },
  gasPrice: null,
  gasLimit: BigNumber { _hex: '0xf791', _isBigNumber: true },
  to: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
  value: BigNumber { _hex: '0x00', _isBigNumber: true },
  data: '0x23b872dd000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000000000000',
  accessList: [],
  hash: '0x1179e89396f6c69b4f7cf5d14d673379bceca3ed86b0f4fefbe41da025aaaea9',
  v: 1,
  r: '0x06f35287e21c087138b8fae72c9b4e2008aded4e3fab4021358481d8424bc36f',
  s: '0x3cd8285a2ea1d9554eee14b6c799aca543d0c40006d0b7bb4a004f58fb6aa3fc',
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  confirmations: 0,
  wait: [Function (anonymous)]
}

查看NFT是否发送成功

> await contractWithSigner.tokenURI(0)
'ipfs://Qmxxx'
> await contractWithSigner.ownerOf(0)
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8'
Subscribe to zhongxuqi
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from zhongxuqi

Skeleton

Skeleton

Skeleton