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