以太坊ERC721非同質(zhì)化代幣(NFT數(shù)字藏品)發(fā)行實(shí)戰(zhàn)

一、前期準(zhǔn)備

首先了解下EIP721提案的內(nèi)容https://learnblockchain.cn/docs/eips/eip-721.html。目前在MetaMask等數(shù)字錢包中只能顯示擁有的NFT的數(shù)量,并不能查看圖片等信息。一般是放在opensea.io這樣的網(wǎng)站上去展示和交易,所以你需要先能訪問https://testnets.opensea.io/網(wǎng)站。

二、開發(fā)環(huán)境搭建

需要用到vscode及Truffle插件,nodejs和npm,MetaMask錢包。參考我的上一篇文章:http://www.itdecent.cn/p/6d01a0020cdc

因?yàn)?a target="_blank">testnets.opensea.io不支持sepolia測(cè)試網(wǎng),所以只能用goerli網(wǎng)了。那就先去這個(gè)網(wǎng)址獲取以太坊goerli鏈測(cè)試幣:https://goerli-faucet.pk910.de/

三、開發(fā)實(shí)戰(zhàn)

3.1 新建項(xiàng)目

先新建一個(gè)空的目錄作為項(xiàng)目的根目錄,打開vscode開發(fā)工具,然后點(diǎn)擊菜單“查看”-“命令面板”,在輸入框中輸入“truffle”,點(diǎn)擊“Truffle: New Solidity Project” 命令,再點(diǎn)擊“Create empty project”創(chuàng)建一個(gè)空的項(xiàng)目,會(huì)彈出對(duì)話框選擇一個(gè)目錄來存儲(chǔ)代碼。


vscode中新建Truffle項(xiàng)目

3.2 安裝類庫

項(xiàng)目創(chuàng)建完成之后,再點(diǎn)擊菜單“查看”-“終端”,打開終端面板(如下圖1)。當(dāng)然你也可以在windows運(yùn)行cmd“命令提示符”或“終端”程序,切換到項(xiàng)目目錄下,執(zhí)行下面的命令:

# 設(shè)置國(guó)內(nèi)鏡像,提高下載速度
npm config set registry https://registry.npm.taobao.org 
npm i @openzeppelin/contracts
npm i @truffle/hdwallet-provider
npm i -D truffle-plugin-verify

第一個(gè)npm config命令是配置下載鏡像,提高下載速度,如果已經(jīng)執(zhí)行過可以忽略。

@openzeppelin/contracts是開源的solidity代幣或NFT代碼庫,官方的文檔:https://docs.openzeppelin.com/contracts/4.x/erc721
node_modules\@openzeppelin\contracts\token\ERC721 目錄下,可以看到等下要用到的代碼。
@truffle/hdwallet-provider是一個(gè)錢包工具類,類似前面安裝的MetaMask錢包。
truffle-plugin-verify為智能合約校驗(yàn)插件。

點(diǎn)擊4處資源管理器圖標(biāo),可以看到代碼的目錄結(jié)構(gòu)。


3.3 代碼編寫

3.3.1 定義外部JSON文件

你需要為每一個(gè)數(shù)字藏品定義一個(gè)JSON文件,因?yàn)槊總€(gè)NFT都是獨(dú)一無二的,發(fā)行幾個(gè)就定義幾個(gè)。如果是測(cè)試的話,重復(fù)使用一個(gè)JSON文件也是可以的。文件內(nèi)容參考如下:

{ "name": "Elwin721 hummingbird #6",
  "description": "Elwin721 hummingbird test6",
  "image": "https://liargame.net/hummingbird.png",
    "attributes": [
        { "trait_type": "Eyes","value": "Black"},
        {  "trait_type": "Wing","value": "Purple"}
    ],
    "date": "2022-12-02"}

name是NFT的名稱,description是說明,image是圖片,這三個(gè)屬性是必須要有的。attributes為附加屬性數(shù)組,用trait_typevalue來定義就可以了,上面的定義意思是眼睛是黑色、翅膀?yàn)樽仙?。你還可以增加增加其他屬性值,如日期等等。

定義完成之后,要把JSON文件上傳到互聯(lián)網(wǎng)上,可以通過http來訪問。假設(shè)你的網(wǎng)站關(guān)閉了,那鏈接就失效了,所以推薦發(fā)布到去中心化的網(wǎng)絡(luò)上,這樣你的NFT理論上就會(huì)永久存在且不會(huì)被篡改。image圖片最好也存儲(chǔ)在去中心化的網(wǎng)絡(luò)上。

去中心化的網(wǎng)絡(luò),一般用IPFS(星際文件系統(tǒng)),官網(wǎng)地址:https://ipfs.tech/。上傳文件到IPFS需要先安裝客戶端,因?yàn)榭蛻舳顺绦驎?huì)把別人上傳的文件也存儲(chǔ)在你的硬盤上,占用你的磁盤空間,我就沒有安裝,有興趣的話或者生產(chǎn)階段可以試一下。

舉個(gè)例子:大名鼎鼎的無聊猿NFT,就把圖片和json存儲(chǔ)在IPFS上,可以通過下面鏈接查看編號(hào)5809的無聊猿的JSON內(nèi)容:https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/5809,等下我也用這個(gè)ipfs鏈接來測(cè)試。

3.3.2 contacts/Demo721.sol

右擊contacts目錄,新建文件,輸入:Demo721.sol并回車,你也可以用別的文件名。輸入文件內(nèi)容如下:

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

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

contract Demo721 is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds; //計(jì)數(shù)器,用于NTF的編號(hào)
    address private creater; //合約創(chuàng)建者

    constructor() ERC721("Demo721 of Elwin", "ELW") {
        creater = msg.sender;
        mint(0x680341FF452F3276F7C132E39dB2b9c93b4c4197,"https://liargame.net/6.json");
        mint(0x680341FF452F3276F7C132E39dB2b9c93b4c4197,"ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/5742");
    }

    //產(chǎn)生一個(gè)NFT,錢包地址,JSON文件路徑
    function mint(address player, string memory tokenURI) public  returns (uint256)
    {
        //只有創(chuàng)建者才可以生產(chǎn)NFT
        require(msg.sender==creater,"you are not the creater of the contract!");
        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        _tokenIds.increment();
        return newItemId;
    }
}

這就是主合約文件,有一個(gè)構(gòu)造函數(shù)和一個(gè)產(chǎn)生NFT的函數(shù)mint。mint函數(shù)要合約的創(chuàng)建者才能調(diào)用,傳入合約創(chuàng)建者的錢包地址和上面提到的JSON文件存儲(chǔ)地址即可創(chuàng)建一個(gè)NFT。構(gòu)造函數(shù)調(diào)用了父合約的構(gòu)造函數(shù),兩個(gè)參數(shù)分別為代幣的說明和代幣的符號(hào)(縮寫)。構(gòu)造函數(shù)中也初始化了兩個(gè)NFT,一個(gè)是在普通網(wǎng)站上存儲(chǔ)的,一個(gè)是在IPFS系統(tǒng)上存儲(chǔ)的。

3.3.3 migrations/721_deploy.js

在migrations目錄下新建721_deploy.js文件,這是一個(gè)部署js文件,文件名中下劃線前面一定要為數(shù)字才能在部署時(shí)被執(zhí)行,如果migrations目錄下有多個(gè)文件,按前面數(shù)字從小到大的順序依次執(zhí)行。文件內(nèi)容如下:

const elwin721 =  artifacts.require("./contracts/Demo721.sol");
module.exports = function(deployer) {
  deployer.deploy(elwin721);
};

代碼也很少,第一行是導(dǎo)入Demo721.sol合約文件,deployer.deploy(elwin721);為部署導(dǎo)入的合約。

3.3.4 truffle-config.js

修改項(xiàng)目根目錄下的truffle-config.js文件,文件內(nèi)容如下:

var HDWalletProvider = require("@truffle/hdwallet-provider");
var MnemonicPhrase = "hello cat shed ……";  //填入你的MetaMask錢包助記詞
var ProviderUrl ="https://rpc.ankr.com/eth_goerli";   //goerli 網(wǎng)接口地址
var AddressIndex=0;  //錢包中的第幾個(gè)地址,從0開始;如果錢包中只有一個(gè)地址,可以去掉這個(gè)參數(shù)

module.exports = {
  networks: {
    sepoliaTestNet:{
      network_id:"*",
      provider: function() {
        return new HDWalletProvider(MnemonicPhrase, ProviderUrl, AddressIndex);//使用助記詞
      },
    network_id: "*",  // 匹配任意網(wǎng)絡(luò)
    gas: 3012388,
    gasPrice: 2000000000 //根據(jù)網(wǎng)絡(luò)行情設(shè)定燃料價(jià)格
    }
  },
  mocha: {},
  plugins: [
    'truffle-plugin-verify'
  ],
  api_keys: {
     etherscan: "*****"
  },
  compilers: {
    solc: {
      version: "0.8.17"
    }
  }
};

這個(gè)文件是項(xiàng)目的一些配置信息,MnemonicPhrase為MetaMask錢包助記詞(填寫你自己的,12個(gè)單詞用半角空格分隔),錢包中的地址為合約的發(fā)布者地址,所以這個(gè)地址要保證有余額;ProviderUrl 為網(wǎng)絡(luò)接口地址;gas為最大使用燃?xì)饬?,如果不夠?huì)報(bào)錯(cuò),如果超出會(huì)退回;gasPrice為燃?xì)鈨r(jià)格,每個(gè)網(wǎng)絡(luò)不同的,可以使用postman等工具查詢各網(wǎng)絡(luò)提供的JSON-RPC接口獲?。▍⒖嘉业那耙黄恼拢?。你可以把燃料價(jià)格設(shè)得比當(dāng)前價(jià)格高一點(diǎn)才能優(yōu)先被執(zhí)行,如果設(shè)置的低于當(dāng)前價(jià)格,部署時(shí)會(huì)報(bào)錯(cuò)。api_keys.etherscan的值,在后面合約驗(yàn)證的時(shí)候再填寫。

3.4 部署合約

保存上面的代碼后,在資源管理器中鼠標(biāo)右擊合約文件(如下圖),在彈出菜單的最下面有Build ContractsDeploy Contracts兩個(gè)菜單,Build Contracts為執(zhí)行編譯合約命令,Deploy Contracts為部署合約命令。因?yàn)閳?zhí)行部署命令時(shí)會(huì)先編譯,所以可以直接點(diǎn)擊Deploy Contracts菜單。

編譯或部署以太坊智能合約

然后在彈框中選擇剛才在truffle-config.js配置文件中配置的sepoliaTestNet網(wǎng)絡(luò)開始部署,如下圖:



有時(shí)右下角會(huì)提示再次提示安裝@truffle/hdwallet-provider錢包,點(diǎn)擊“安裝”即可。如果部署不成功,再次重復(fù)上面的操作來部署即可。

出現(xiàn)類似下面的結(jié)果即為部署成功,如果出現(xiàn)其他錯(cuò)誤提示,按提示內(nèi)容來處理。

721_deploy.js
=============
[Truffle: Execute command] 
   Deploying 'Demo721'
   -------------------
[Truffle: Execute command]    > transaction hash:    0x031d1b1a6e295fed5819d2ed8a02a3b2bb2a70ca2b9cdfe884889f466c94c426
[Truffle: Execute command] - Blocks: 0            Seconds: 0
[Truffle: Execute command] - Blocks: 0            Seconds: 5
[Truffle: Execute command] - Blocks: 1            Seconds: 9
[Truffle: Execute command] - Blocks: 1            Seconds: 13
[Truffle: Execute command] - Blocks: 1            Seconds: 17
[Truffle: Execute command] - Blocks: 2            Seconds: 21
[Truffle: Execute command]    > Blocks: 2            Seconds: 21
   > contract address:    0x45761E880De353444E02565d1bc7FeD820E6ff60
   > block number:        8059468
   > block timestamp:     1669970796
   > account:             0x680341FF452F3276F7C132E39dB2b9c93b4c4197
   > balance:             0.187160573275033315
   > gas used:            2839083 (0x2b522b)
   > gas price:           2 gwei
   > value sent:          0 ETH
   > total cost:          0.005678166 ETH

[Truffle: Execute command]    > Saving artifacts
   -------------------------------------
   > Total cost:         0.005678166 ETH
Summary
=======
> Total deployments:   1
> Final cost:          0.005678166 ETH

[Truffle: Execute command] Finished running command
[Truffle for VSCode] Deploy succeeded

0x45761E880De353444E02565d1bc7FeD820E6ff60 為成功部署的合約地址,可以去https://goerli.etherscan.io/去查看如下圖。0x680341FF452F3276F7C132E39dB2b9c93b4c4197為錢包賬戶地址,你會(huì)發(fā)現(xiàn)余額減少了,上面用了2839083個(gè)燃?xì)?,花費(fèi)0.005678166 個(gè)測(cè)試幣。

四、代幣合約驗(yàn)證

我們部署的合約,其實(shí)是部署了編譯后的字節(jié)碼;為了讓你的合約更可信,可以上傳合約源代碼到etherscan.io網(wǎng)驗(yàn)證一下,這樣顯得你的代幣合約更可信。上圖中點(diǎn)擊剛部署的合約頁簽,紅框位置有提示校驗(yàn)合約。詳細(xì)操作可以參考這篇文章:https://learnblockchain.cn/article/1314

注意要以管理員身份運(yùn)行校驗(yàn)命令truffle run verify Demo721 --network GoerliTestNet 兩個(gè)參數(shù)分別為合約名和網(wǎng)絡(luò)配置名:

管理員身份執(zhí)行校驗(yàn)命令

上圖因?yàn)榫W(wǎng)絡(luò)連接的原因無法校驗(yàn)。Failed to connect to Etherscan API at url https://api-goerli.etherscan.io/api

下圖是之前校驗(yàn)成功的合約,可以看到合約頁簽后面有綠色的圖標(biāo),點(diǎn)擊“Read Contract”按鈕,就可以調(diào)用合約中的視圖函數(shù),比如查詢NFT的存儲(chǔ)地址,以及所有者等操作。


校驗(yàn)成功的合約

五、查看鑄造的NFT

5.1 MetaMask錢包中查看

打開MetaMask錢包,切換到Geoerli測(cè)試網(wǎng)絡(luò),點(diǎn)擊最下面的“添加資產(chǎn)”鏈接,彈出如下界面:


輸入代幣合約地址,再點(diǎn)擊下一個(gè)輸入框,發(fā)現(xiàn)自動(dòng)識(shí)別了代幣符號(hào),小數(shù)填0,然后點(diǎn)擊“添加自定義代幣”按鈕,然后再次確認(rèn)點(diǎn)擊“添加代幣”按鈕,就可以看到剛發(fā)行的NFT了,如下圖。



這里只能看到該合約NFT數(shù)量為2,但看不到圖片,也不能交易。

5.2 testnets.opensea.io上查看

在瀏覽器上輸入上面的地址,然后點(diǎn)擊右上角的“個(gè)人資料”圖標(biāo)或錢包圖標(biāo),會(huì)彈出要連接錢包(如下圖)。選擇MetaMask錢包,并輸入密碼。


然后再點(diǎn)擊個(gè)人資料,就能看到剛才發(fā)布的兩個(gè)NFT了。點(diǎn)擊可以看到NFT的詳細(xì)資料,也可以出售。


testnets.opensea.io網(wǎng)上可以看到發(fā)行的NFT圖片

六、后續(xù)鑄造

上面是在構(gòu)造函數(shù)中鑄造的兩個(gè)NFT,如果還想要繼續(xù)鑄造怎么辦呢?下面介紹一種方法,使用ethers.js前端接口來調(diào)用發(fā)布的合約。先在終端中執(zhí)行命令npm i ethers 安裝依賴包。

6.1 test/mint721.js

在“test”目錄下新建一個(gè)“mint721.js”文件,內(nèi)容如下:

const ethers = require('ethers');

//調(diào)用合約的函數(shù)或者事件,不需要全部函數(shù)的定義都列出來
let abi = [
  "function mint(address player,string tokenURI) public returns (uint256)",
  "function tokenURI(uint256 tokenId) public view returns (string memory)",
];

// Connect to the network
let provider = new ethers.providers.JsonRpcProvider(
  "https://rpc.ankr.com/eth_goerli"
);

// 地址來自上面部署的合約
let contractAddress = "0x45761E880De353444E02565d1bc7FeD820E6ff60";

// 使用Provider 連接合約
let contract = new ethers.Contract(contractAddress, abi, provider);

// 錢包地址的私鑰;生產(chǎn)環(huán)境注意保密,用完可以清空
let privateKey="*****";

// 從私鑰獲取一個(gè)簽名器 Signer
let wallet = new ethers.Wallet(privateKey, provider);

// 使用簽名器創(chuàng)建一個(gè)新的合約實(shí)例,它允許使用可更新狀態(tài)的方法
let contractWithSigner = contract.connect(wallet);

// 調(diào)用前面定義的abi交易函數(shù),需要扣除交易費(fèi)。 只讀函數(shù)不用這種方式調(diào)用,可以在etherscan.io網(wǎng)上校驗(yàn)合約后直接調(diào)用
contractWithSigner
  .mint( 
    "0x680341FF452F3276F7C132E39dB2b9c93b4c4197",
    "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/4959"
  )
  .then(
    function (data) {
      if (data) {
        console.log("========data:");
        console.log(data);
      }
    },
    function (error) {
      if (error) {
        console.log("========error:");
        console.log(error);
      }
    }
  );

要調(diào)用合約的mint函數(shù),要在abi函數(shù)數(shù)組中寫出函數(shù)的定義,注意要把“view”、“memory”等修飾詞去掉,多余的空格也去掉。不必把全部的函數(shù)的定義都寫出來,需要調(diào)用什么就寫什么。合約地址,和錢包地址改為你自己的。錢包的私鑰privateKey,可以在MetaMask中,點(diǎn)擊數(shù)字錢包地址后面的三個(gè)點(diǎn)符號(hào),進(jìn)入“賬戶詳情”頁中“導(dǎo)出私鑰”獲取(如下圖)。

保存上面的js文件后,在終端中輸入命令node ./test/mint721.js并回車運(yùn)行,即可在指定的合約中再次鑄造一個(gè)NFT,運(yùn)行結(jié)果如下圖。

然后再打開opensea的測(cè)試網(wǎng),點(diǎn)擊“個(gè)人資料”,就能看到后面新鑄造的NFT數(shù)字藏品了,如果沒有看到,可以稍等一會(huì)兒再刷新頁面。


詳情中可以看到合約地址,點(diǎn)擊進(jìn)去可以看到合約的基本信息、交易、事件、驗(yàn)證等;代幣ID,點(diǎn)擊后面藍(lán)色的數(shù)字可以看到JSON定義文件內(nèi)容;JSON中定義的5個(gè)屬性已經(jīng)顯示在上面了。

至此,ERC721標(biāo)準(zhǔn)的數(shù)字藏品發(fā)行和后期鑄造已經(jīng)講完了。當(dāng)然你也可以不用寫代碼,直接在opensea網(wǎng)站上傳你的數(shù)字藏品,然后直接出售。opensea網(wǎng)站上直接上傳相當(dāng)于調(diào)用了網(wǎng)站發(fā)布的ERC1155標(biāo)準(zhǔn)合約上mint或batchMint函數(shù)。EIP1155是一個(gè)多代幣合約,可以將ERC20、ERC721都包含進(jìn)一個(gè)合約中。比較適合游戲環(huán)境中,游戲中的金子或銀兩就是同質(zhì)化的,而游戲中的武器裝備又是非同質(zhì)化的,這種情況就使用ERC1155標(biāo)準(zhǔn)合約,增加新的裝備就調(diào)用mint函數(shù)就可以了,而不用每次都部署一個(gè)新的合約了。同理opensea使用ERC1155也是為了減少頻繁部署新的合約,避免浪費(fèi)交易費(fèi)用以及方便后續(xù)的管理。

感謝閱讀,原創(chuàng)內(nèi)容,轉(zhuǎn)載請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容