本篇文章次要介绍如何将您的 NFT(ERC-721 Token) 通过智能合约部署到去中心化网络中。
Init Project
//创立一款ocean的NFTmkdir nft-ocean//进入目录cd nft-ocean//初始化我的项目,依据提醒填写即可,packname和description填写即可npm init//增加hardhat依赖npm install --save-dev hardhat/*应用脚手架搭建我的项目,咱们抉择Create a basic sample project能够帮忙咱们创立1个demo工程并依照所需的依赖*/npx hardhat
这是初始化实现后的代码构造
- contracts目录用来寄存咱们的智能合约代码
- scripts用来寄存咱们的脚本,比方合约部署就会依赖其中的脚本
- test目录用来寄存咱们为智能合约编写的测试代码
- hardhat.config.js是对于hardhat框架的一些配置,比方solidity版本等
- package.json是npm的相干配置
编写合约代码
目前就能够开始写合约代码了,本文次要是介绍NFT的发行,咱们简略来实现一份NFT智能合约代码。
//装置openzeppelin/contracts依赖,内置较多合约协定的实现以及工具代码npm install @openzeppelin/contracts
咱们在contracts目录下创立NFT_OCEAN.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_OCEAN is ERC721, ERC721Enumerable, Ownable { string private _baseURIextended; //Set mint limit uint256 public constant MAX_SUPPLY = 5; //0.01eth per mint uint256 public constant PRICE_PER_TOKEN = 0.01 ether; constructor() ERC721("nft_ocean", "NFT_OCEAN") { } function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, firstTokenId, batchSize); } 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); }}
该代码不具备上线规范,因为没有白名单。每个地址限度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_ocean = await hre.ethers.getContractFactory("NFT_OCEAN"); const nft_ocean = await Nft_ocean.deploy(); await nft_ocean.deployed(); console.log("NFT_OCEAN deployed to:", nft_ocean.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); });
抉择节点服务代理
有很多办法能够向以太坊区块链发出请求,但为了让事件变得简略,咱们将应用 Alchemy 上的收费帐户,这是一个区块链开发人员平台和 API,容许咱们与以太坊链,无需运行咱们本人的节点。创立过程参考文档,别离创立一个Ethereum Mainnet 和 Ethereum Sepolia 的APP
测试网络环境搭建
咱们首先须要在测试网络中部署,咱们抉择Sepolia作为咱们的测试网络。
因为在代码执行过程中会用到公钥、私钥、API等数据,但这些数据又不能编码到代码中,因为如果咱们应用git来治理我的项目,信息将会泄露。咱们应用dotenv寄存部署合约以及和合约交互须要用到的数据。
//增加dotenv依赖npm install dotenv//工程根目录下创立变量存储文件touch .env
为.env减少内容
PRIVATE_KEY= 导出你metamask的私钥填在这里API=Alchemy APP中的HTTPSPUBLIC_KEY= metamask地址NETWORK=sepoliaAPI_KEY=Alchemy的API KEY
- metamask私钥导出:关上钱包-账号详情->导出私钥。
- API、API_KEY:关上Alchemy 中 Ethereum Sepolia 的API KEY,复制HTTPS和API_KEY
批改hardhat.config.js
/*** @type import('hardhat/config').HardhatUserConfig*/require("@nomicfoundation/hardhat-toolbox");require("@nomiclabs/hardhat-ethers");//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.htmlrequire('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.19", defaultNetwork: "sepolia", networks: { hardhat: {}, sepolia: { url: API, accounts: [`0x${PRIVATE_KEY}`] } },};
获取测试网络ETH 因为合约测试须要耗费ETH,咱们须要获取一些测试用的ETH,咱们关上https://sepoliafaucet.com/,填入咱们的钱包地址即可获取。
此时咱们将钱包切换到sepolia测试网络,能够看到钱包中曾经有一些ETH了。
执行合约部署
首先装置 ETHERS.JS
npm install --save-dev @nomiclabs/hardhat-ethers ethers@^5.7.2
执行合约部署
npx hardhat --network sepolia run scripts/deploy.js//执行实现后失去提醒,代表合约部署实现NFT_WEB3_EXPOLRER deployed to: {合约地址}
此时咱们能够在https://sepolia.etherscan.io/搜寻咱们的合约地址找到咱们的合约信息
为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 rescd res//寄存图片mkdir nfts//寄存metadata信息mkdir metadata
因为本文是示例我的项目,没有找专门的设计师设计图片,咱们网上找了5张图片。将5张图片放入img目录下,顺次命名为1.jpeg ~ 5.jpeg
咱们资源文件上传到ipfs中,这里和以太坊网络一样,因为咱们不想运行本地ipfs节点,所以咱们寻找节点服务提供商进行上传。咱们应用https://dashboard.4everland.org/bucket/storage/将nfts文件夹上传
上传实现后的图片资源
选中图片文件夹进行snapshots,即可生成root cid,网关拜访成果如下:
接下来咱们筹备metadata文件,在metadata目录下新建5个文件顺次命名为0~4,咱们看下0号文件信息
{ "name": "nft-ocean", "attributes": [ { "trait_type": "tokenID", "value": "0" } ], "description": "nft-ocean image", //填入下面刚刚上传实现的图片地址 "image": "ipfs://bafybeief75v57gu3khb5ftjkgldyzyea4x3r6sesekcia6lk2pijgl5idm/01.jpeg"}
咱们将metadata文件夹上传并snapshot,获取文件夹CID后进行publish
设置BaseURI
咱们须要将刚刚上传后的metadata文件地址设置给合约的baseURI,这样各个平台在应用tokenURI获取资源信息才能够获取到。
编写代码与合约交互设置BaseURI,在scripts目录下新建setBaseURI.js文件。
setBaseURI.js
require("dotenv").config()const hre = require("hardhat");const PRIVATE_KEY = process.env.PRIVATE_KEYconst NETWORK = process.env.NETWORKconst API_KEY = process.env.API_KEYconst provider = new hre.ethers.providers.AlchemyProvider("sepolia",API_KEY);//编译实现合约会主动生成const abi = require("../artifacts/contracts/NFT_OCEAN.sol/NFT_OCEAN.json").abiconst contractAddress = "填合约地址" const contract = new hre.ethers.Contract(contractAddress, abi, provider)const wallet = new hre.ethers.Wallet(PRIVATE_KEY, provider)const baseURI = "ipfs://{metadata文件的root cid}/" //填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); });
执行setBaseURI脚本
npx hardhat --network sepolia run scripts/setBaseURI.js
Mint 测试
因为只有mint过的tokenID才会展现在交易市场中,所以咱们须要编写代码进行mint测试(理论场景,该操作应该由前端页面调用实现)。
mint.js
require("dotenv").config()const hre = require("hardhat");const PRIVATE_KEY = process.env.PRIVATE_KEYconst NETWORK = process.env.NETWORKconst API_KEY = process.env.API_KEYconst provider = new hre.ethers.providers.AlchemyProvider("sepolia",API_KEY);const abi = require("../artifacts/contracts/NFT_OCEAN.sol/NFT_OCEAN.json").abiconst contractAddress = "0x531D6B8fBe5FAC349D05642E17F5E998A4DfEd68"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 sepolia run scripts/mint.js
Mint 胜利
查看NFT
此时咱们去opensea 测试网 https://testnets.opensea.io/ 查看咱们mint的NFT 即可。