初遇NFT-IPFS
- 2021 年 12 月 31 日
- 筆記
初遇NFT-IPFS
本次學習如何使用Hardhat框架製作可預售的NFT並利用IPFS存儲元數據。
-
NFT簡介
-
NFT全稱Non-fungible Token(即非同質化通證)。不可分割性(目前有碎片化協議暫且不談)且獨一無二。這有點對應現實資產比如古董、藝術品。即使是同一批廠家或者藝術家創作也難保證一模一樣。
- NFT 目前主要用於藝術品、域名、虛擬資產、遊戲。代表性的就是CryptoKitties、CryptoPunk、NBA Top Shot等
- NFT的價值就是仁者見仁智者見智了,稀有程度,品牌價值,認可度等都是維持價格的因素。------情懷、藝術無價。
- 常用的NFT工具:
- 交易網站Opensea://opensea.io/
- 市場數據分析:歐科雲鏈 //www.oklink.com/zh-cn/nft NFT流行應用//degendata.io/
- NFT應用數據:Dappradder //degendata.io/,給使用過的用戶發過空投價值幾百刀
- 重點推薦NFTSCAN //nftscan.com/ 可以看到鏈上NFT的交易情況,也算挖掘最新NFT的常用工具
- 交易網站Opensea://opensea.io/
-
-
開發工具
-
hardhat :在以太坊上構建的開發環境。它幫助開發人員管理和自動化構建智慧合約和 dApp 。需要先安裝Node.js。Node.js安裝教程 //blog.csdn.net/fangchunyang/article/details/118178742
-
ipfs:InterPlanetary File System 星際文件系統。IPFS是一種內容可定址、版本化、點對點超媒體的分散式存儲、傳輸協議。在IPFS的網路里,是根據內容定址,每一個上傳到IPFS上面去的文件、文件夾,都是以Qm為開頭字母的哈希值,無需知道文件存儲在哪裡,通過哈希值就能夠找到這個文件,這種方式叫內容定址。ipfs教程可以看 tiny熊大佬的附上傳送門link。還需要使用Pinata 有免費版,這個可以上傳文件。附上鏈接//app.pinata.cloud/ 如果想在chrome訪問ipfs 需要安裝插件。附上鏈接 下載
-
Remix :Remix是以太坊智慧合約程式語言Solidity的一個基於瀏覽器的IDE。附上鏈接//remix.ethereum.org/
-
-
教程開始
- 默認已經安裝配置好npm了如果沒有請百度後按照。安裝hardhat 並初始項目
-
1 mkdir nft_1 2 cd nft_1 3 npm init -y 4 npm install --save-dev hardhat
運行hardhat
npx hardhat
我們選擇:basic sample project 然後全程默認回車
-
創建好的項目目錄:

文件contracts 是存放我們寫的智慧合約
node_modules 是安裝的一些插件
scripts 是部署合約調用合約
test 是編寫測試腳本 - 現在來為hardhat 安裝一些插件。
安裝waffle 、ethers chai web3等插件
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-web3 web3 安裝openzeppelin 合約 npm install @openzeppelin/contracts 安裝驗證etherscan插件 npm install --save-dev @nomiclabs/hardhat-etherscan 為了安全 使用.env npm install dotenv
-
插件安裝完成後我們需要在項目里添加。
- 找到文件 hardhat.config.js 編輯 然後添加我們剛才安裝的插件
-
require("@nomiclabs/hardhat-waffle"); require("@nomiclabs/hardhat-web3"); require("@nomiclabs/hardhat-ethers"); require("@nomiclabs/hardhat-etherscan"); require('dotenv').config();結果如圖所示

- 現在來清理一些不要的文件,
- 刪除contracts文件下的Greeter.sol
- 刪除test文件下的sample-test.js
-
-
- 文件配置已經完成 接下來我們來編寫智慧合約
- 在contracts 中新建一個合約 MyNFT.sol
- 程式碼

MyNFT.sol//編譯器版本 pragma solidity ^0.8.0; //導入openzeppelin庫 //Ownable 是openzeppelin實現管理員 import '@openzeppelin/contracts/access/Ownable.sol'; //ERC721是NFT常用的標準協議 import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; //可以枚舉 import '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol'; //我們創建的合約需要繼承上面的合約 contract MyNFT is ERC721, ERC721Enumerable, Ownable { //開啟售賣nft bool public _isSaleActive = false; // Constants //設置NFT數量 uint256 constant public MAX_SUPPLY = 2048; //售賣NFT的價格 uint256 public mintPrice = 0.01 ether; //賬戶最多擁有的NFT數量 uint256 public maxBalance = 6; //賬戶一次性最多鑄造的數量 uint256 public maxMint = 3; //基本url string private _baseURIExtended; event TokenMinted(uint256 supply); event SaleStarted(); event SalePaused(); event AuctionStarted(); event AuctionPaused(); constructor() ERC721('MoonStart', 'ms') {} //開始預售 function startSale() public onlyOwner { _isSaleActive = true; emit SaleStarted(); } //暫停預售 function pauseSale() public onlyOwner { _isSaleActive = false; emit SalePaused(); } //設置價格 function setMintPrice(uint256 _mintPrice) public onlyOwner { mintPrice = _mintPrice; } //設置擁有NFT數量 function setMaxBalance(uint256 _maxBalance) public onlyOwner { maxBalance = _maxBalance; } //設置最大mint數量 function setMaxMint(uint256 _maxMint) public onlyOwner { maxMint = _maxMint; } //提取合約中的ETH function withdraw(address to) public onlyOwner { uint256 balance = address(this).balance; payable(to).transfer(balance); } //返回當前總量 function getTotalSupply() public view returns (uint256) { return totalSupply(); } //獲取地址擁有的nft function getMoonStarByOwner(address _owner) public view returns (uint256[] memory) { uint256 tokenCount = balanceOf(_owner); uint256[] memory tokenIds = new uint256[](tokenCount); for (uint256 i; i < tokenCount; i++) { tokenIds[i] = tokenOfOwnerByIndex(_owner, i); } return tokenIds; } //mint NFT function mintMoonStar(uint numMoonStars) public payable { require(_isSaleActive, 'Sale must be active to mint MoonStars'); require(totalSupply() + numMoonStars <= MAX_SUPPLY, 'Sale would exceed max supply'); require(balanceOf(msg.sender) + numMoonStars <= maxBalance, 'Sale would exceed max balance'); require(numMoonStars <= maxMint, 'Sale would exceed max mint'); require(numMoonStars * mintPrice <= msg.value, 'Not enough ether sent'); _mintMoonStar(numMoonStars, msg.sender); emit TokenMinted(totalSupply()); } //mint 函數具體實現 function _mintMoonStar(uint256 numMoonStars, address recipient) internal { uint256 supply = totalSupply(); for (uint256 i = 0; i < numMoonStars; i++) { _safeMint(recipient, supply + i); } } //設置基本url 主要後面拼接 function setBaseURI(string memory baseURI_) external onlyOwner { _baseURIExtended = baseURI_; } //獲取基本url function _baseURI() internal view virtual override returns (string memory) { return _baseURIExtended; } //得到nft的url function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { require(_exists(tokenId), 'ERC721Metadata: URI query for nonexistent token'); return string(abi.encodePacked(_baseURI(), tokenId.toString())); } //轉移這裡使用了hooks function _beforeTokenTransfer( address from, address to, uint256 tokenId ) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, tokenId); } // IERC165 介面的實現 function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(interfaceId); } }
- 程式碼
- 在scripts文件創建run.js
- 程式碼

View Codeconst main = async () => { const nftContractFactory = await hre.ethers.getContractFactory('MyNFT'); const nftContract = await nftContractFactory.deploy(); await nftContract.deployed(); console.log("Contract deployed to:", nftContract.address); }; const runMain = async () => { try { await main(); process.exit(0); } catch (error) { console.log(error); process.exit(1); } }; runMain();
由於我喜歡在remix 調試,這裡就只用部署合約,沒有進行函數調用
- 我們直接使用hardhat的測試網先試著編譯運行一次。
npx hardhat run scripts/run.js

無報錯,智慧合約沒問題。
- 程式碼
- 在contracts 中新建一個合約 MyNFT.sol
- 現在我們來部署在Rinkeby鏈上,因為它有opensea來顯示我們的NFT。如果測試網沒有eth,可以去水龍頭領,我常用的是chainlink的 //faucets.chain.link/rinkeby 很好用,就是每次量少了一些,多點幾次。
- 修改hardhat.config.js
- 初始的module.exports
module.exports = { solidity: "0.8.4", };
修改完成的是
module.exports = { solidity: "0.8.4", networks: { rinkeby: { url:process.env.STAGING_ALCHEMY_KEY, accounts:[process.env.PRIVATE_KEY], }, },
etherscan: {
apiKey:process.env.API_KEY
}
};- url:我們使用節點才能和智慧合約交互,市面上有很多節點 常用的infura //infura.io/, alchemy //www.alchemy.com/ 我這裡使用的alchemy,只需要註冊賬戶、新建項目然後複製網站給我們的key 格式差不多是 //eth-rinkeby.alchemyapi.io/v2/xxxxxxxxxxxxx
- accounts 這裡需要提供部賬戶的私鑰.為了安全我們使用了env
- apiKey: 這是為了將我們的智慧合約通過etherscan的驗證 ,需要註冊一個賬戶 並且複製apikey //etherscan.io
- 初始的module.exports
- 創建.env
- 內容 url STAGING_ALCHEMY_KEY="你的key //eth-rinkeby.alchemyapi.io/v2/xxxxxxxxxxxxx"
- 私鑰 PRIVATE_KEY='xxxxxxx'
- API_KEY='xxxxx'
- hardhat 利用 hardhat-etherscan 插件自動在etherscan 驗證合約失敗,大概是網路問題但我科 學也失敗了,有解決辦法的老哥可以交流下。因此我們需要手動去etherscan 驗證合約。後面寫一篇如何怎麼在etherscan 驗證合約
- 首先我們在rineby 部署合約,
-
npx hardhat run scripts/run.js --network rinkeby

上面的0xe4 就是合約地址,部署成功,我們現在打開remix //remix.ethereum.org/
- 在remix中新建一個合約,粘貼智慧合約程式碼 MyNFT.sol

-
點擊左邊欄的 Deploy & run transactions 在ENVIRIONMENT 選擇Injected Web3 這個可以讓remix 鏈接我們的小狐狸錢包,然後再CONTRACT 找到 MyNFT -contracts/MyNFT.sol接著下面 At Address 粘貼我們上面得到的合約地址,然後點擊 At Address。

-
現在我們來交互我們的智慧合約, 點擊箭頭部分,再找到startsale 必須使用部署這個合約的帳號
-
先開啟 售賣


-
我們再來mint 1個NFT,此時需要我們發送eth。由於在remix 無法直接填寫0.01ether,需要單位換成'wei' 這是最小單位10000000000000000 wei==0.01ether 1ether==10^18 wei。我這裡演示mint 2個


看鏈上返回結果,已經成功。

-
-
- 現在是NFT已經鑄造好了,但是沒有NFT的一些資訊。NFT的資訊我們成為元數據 他是一個json 格式 我們通過函數 tokenURI 讓我們的NFT加上元數據
{ "name": "MoonStart", "description": "test NFT", "attributes": [ { "trait_type": "Background", "value": "Galaxy" }, { "trait_type": "color", "value": "blue" } ], "image": "//ipfs.io/ipfs/xxx/1.png" }
- 我們先新建一個文件夾存放我們的圖片,圖片名字 1.jpg ....然後上傳到ipfs.ipfs我們可以使用pinata 網站 //app.pinata.cloud/
就像這樣,我們需要複製CID,待會添加到json里


- 現在來新建文件 來存放元數據 image 哪裡填寫我們剛剛上傳的圖片的 CID。

然後又通過pinata上傳

然後複製CID,返回remix中 調用函數 setbaseURI

這裡我們填寫的是 ipfs://Qmxxxx/
- 然後我們耐心等待一段時間,打開opensea的測試網: //testnets.opensea.io/
未完待續---------散花
- 我是等了很久,如果pinata 太慢了 建議大家下個ipfs桌面版。

- 修改hardhat.config.js
- 默認已經安裝配置好npm了如果沒有請百度後按照。安裝hardhat 並初始項目
在驗證合約這裡折騰了很久,但也學習到了很多。對應這種導入了第三方庫的合約 解決辦法就是通過 flattener。remix-ide本地版本有這個插件 flattener ,或者使用truffle-flattener把這些合約全都壓縮在一個文件 並刪除多餘的spdx。最佳的方式還是使用插件來自動化驗證比如 hardhat truffle 的自動驗證合約。
新年快樂























