前言
本文借助openzeppelin库编写一个标准的NFT合约,从开发,测试,到部署上链全部流程。注意:ERC20标准的同质化代币和ERC721标准的非同质化代币的区别 需要metadata,要把信息上传到ipfs上,文中会有详细操作的步骤;
同质化代币和非同质化代币程序层面的区别
- ERC20:mapping(address=>uint)//地址指向余额
- ERC721:mapping(uint=>address)//id指向地址
开发
合约功能说明:铸造,销毁,权限控制:只有项目方可铸造,
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721Burnable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract BoykaNFT is ERC721, ERC721Enumerable, ERC721URIStorage, ERC721Burnable, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("BoykaNFT", "BNFT")
Ownable(initialOwner)
{}
//铸造nft 说明:地址,metadata
function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
// The following functions are overrides required by Solidity.
function _update(address to, uint256 tokenId, address auth)
internal
override(ERC721, ERC721Enumerable)
returns (address)
{
return super._update(to, tokenId, auth);
}
function _increaseBalance(address account, uint128 value)
internal
override(ERC721, ERC721Enumerable)
{
super._increaseBalance(account, value);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
测试
步骤:
- 在beforeEach中部署合约
- 拿到合约实例在describe调用相关方法验证
const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("NFT",async()=>{
let NFT;//合约
let firstAccount//第一个账户
let secondAccount//第二个账户;
//关于makedata的获取通过filebase工具生成的
let mekadata='https://zygomorphic-magenta-bobolink.myfilebase.com/ipfs/QmQT8VpmWQVhUhoDCEK1mdHXaFaJ3KawkRxHm96GUhrXLB';
beforeEach(async()=>{
await deployments.fixture(["nft"]);
firstAccount=(await getNamedAccounts()).firstAccount;
secondAccount=(await getNamedAccounts()).secondAccount;
const nftDeployment = await deployments.get("BoykaNFT");
NFT = await ethers.getContractAt("BoykaNFT",nftDeployment.address);//已经部署的合约交互
})
describe("NFT",async()=>{
it("铸造一个nft",async()=>{
//const owner = await NFT.owner();
await NFT.safeMint(firstAccount,mekadate);
})
})
})
//执行指令:部署nft合约到sepolia链上并铸造一个nft,成功后可以在opensea测试网上预览部署的nft
npx hardhat test --network sepolia
部署
- 通过部署插件部署合约
- 拿到合约地址,通过verify来验证合约是否部署成功
module.exports=async ({getNamedAccounts,deployments})=>{
let confirmations=5;//等待的区块数
const {deploy,log} = deployments;
const {firstAccount,secondAccount} = await getNamedAccounts();
const BoykaNFT=await deploy("BoykaNFT",{
from:firstAccount,
args: [firstAccount],//参数:权限账号
log: true,
wait/confirm/iations: /confirm/iations,//等待的区块数
})
//打印部署合约的地址
console.log('nft合约',BoykaNFT.address)
await hre.run("verify:verify", {
address: BoykaNFT.address,
constructorArguments: [firstAccount],
});
};
module.exports.tags = ["all", "nft"];
//执行指令:把合约部署到sepolia链上
npx hardhat deploy --network sepolia
文件配置
- 定义网络节点
- 通过定义别名获取钱包地址
# 在hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-verify");//验证合约
require('hardhat-deploy');//部署插件
require("dotenv").config();//使用此包读取.env文件的常量
const ALCHEMY_API_KEY=process.env.ALCHEMY_API_KEY;//在infura中创建项目会自动生成
const PRIVATE_KEY=process.env.PRIVATE_KEY;
const PRIVATE_KEY_1=process.env.PRIVATE_KEY_1;
const ETHERSCAN_API_KEY=process.env.ETHERSCAN_API_KEY//ETHERSCAN_API可以在ETHERSCAN浏览器网站注册
module.exports = {
solidity: "0.8.28",
networks:{
localhost: {
url:'http://127.0.0.1:8545/',
chainId: 31337,
},
sepolia:{
url: `https://sepolia.infura.io/v3/${ALCHEMY_API_KEY}`,//使用infura 创建项目会生成对应的api_key
accounts: [PRIVATE_KEY,PRIVATE_KEY_1]//对应的账号的私钥,账号1和账号2
chainId: 11155111
}
},
etherscan: {
apiKey: {
sepolia: ETHERSCAN_API_KEY
}
},
namedAccounts: {
firstAccount: {
default: 0
},
secondAccount: {
default: 1
}
},//可以用别名获取账号节点
}
# 详细的配置可以在hardhat官网查看
关于铸造NFT时metadata生成
使用filebase网站把matadata存储到ipfs上
把cattle.jpg和cattle.json文件上传到ipfs上
关于cattle.json 实例:
{
"description": "This is the zodiac sign chicken",//关于nft的描述
"external_url":"https://openseacreatures.io/3",
"image":"ipfs://QmWH3hY6J31Hcf61aM6A9cCVArKSfh8E4mYbL9yg68kUx8",//指向的图片的url
"name":"chicken",//nft的名字
"attributes": [
{
"trait_type": "Background",
"value": "Blue"
},
{
"trait_type": "Eyes",
"value": "Black"
},
{
"trait_type": "Mouth",
"value": "Smile"
}
]//关于属性的设置
}
如图所示
屏幕截图 2024-12-28 001841.png
工具
使用openSea网站查看合约是否部署成功
在openSea测试网上链接自己的钱包,可以查看到自己铸造的所有的项目
部署成功如图:
屏幕截图 2024-12-27 235740.png
总结
以上就是从开发测试到部署的全流程,NFT和同质化代币最主要的区别就是多了metadata上传到ipfs上的操作;