以太坊ERC721非同质化代币(NFT数字藏品)发行实战

一、前期准备

首先了解下EIP721提案的内容https://learnblockchain.cn/docs/eips/eip-721.html。目前在MetaMask等数字钱包中只能显示拥有的NFT的数量,并不能查看图片等信息。一般是放在opensea.io这样的网站上去展示和交易,所以你需要先能访问https://testnets.opensea.io/网站。

二、开发环境搭建

需要用到vscode及Truffle插件,nodejs和npm,MetaMask钱包。参考我的上一篇文章:https://www.jianshu.com/p/6d01a0020cdc

因为testnets.opensea.io不支持sepolia测试网,所以只能用goerli网了。那就先去这个网址获取以太坊goerli链测试币:https://goerli-faucet.pk910.de/

三、开发实战

3.1 新建项目

先新建一个空的目录作为项目的根目录,打开vscode开发工具,然后点击菜单“查看”-“命令面板”,在输入框中输入“truffle”,点击“Truffle: New Solidity Project” 命令,再点击“Create empty project”创建一个空的项目,会弹出对话框选择一个目录来存储代码。


vscode中新建Truffle项目

3.2 安装类库

项目创建完成之后,再点击菜单“查看”-“终端”,打开终端面板(如下图1)。当然你也可以在windows运行cmd“命令提示符”或“终端”程序,切换到项目目录下,执行下面的命令:

# 设置国内镜像,提高下载速度
npm config set registry https://registry.npm.taobao.org 
npm i @openzeppelin/contracts
npm i @truffle/hdwallet-provider
npm i -D truffle-plugin-verify

第一个npm config命令是配置下载镜像,提高下载速度,如果已经执行过可以忽略。

@openzeppelin/contracts是开源的solidity代币或NFT代码库,官方的文档:https://docs.openzeppelin.com/contracts/4.x/erc721
node_modules\@openzeppelin\contracts\token\ERC721 目录下,可以看到等下要用到的代码。
@truffle/hdwallet-provider是一个钱包工具类,类似前面安装的MetaMask钱包。
truffle-plugin-verify为智能合约校验插件。

点击4处资源管理器图标,可以看到代码的目录结构。


3.3 代码编写

3.3.1 定义外部JSON文件

你需要为每一个数字藏品定义一个JSON文件,因为每个NFT都是独一无二的,发行几个就定义几个。如果是测试的话,重复使用一个JSON文件也是可以的。文件内容参考如下:

{ "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是图片,这三个属性是必须要有的。attributes为附加属性数组,用trait_typevalue来定义就可以了,上面的定义意思是眼睛是黑色、翅膀为紫色。你还可以增加增加其他属性值,如日期等等。

定义完成之后,要把JSON文件上传到互联网上,可以通过http来访问。假设你的网站关闭了,那链接就失效了,所以推荐发布到去中心化的网络上,这样你的NFT理论上就会永久存在且不会被篡改。image图片最好也存储在去中心化的网络上。

去中心化的网络,一般用IPFS(星际文件系统),官网地址:https://ipfs.tech/。上传文件到IPFS需要先安装客户端,因为客户端程序会把别人上传的文件也存储在你的硬盘上,占用你的磁盘空间,我就没有安装,有兴趣的话或者生产阶段可以试一下。

举个例子:大名鼎鼎的无聊猿NFT,就把图片和json存储在IPFS上,可以通过下面链接查看编号5809的无聊猿的JSON内容:https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/5809,等下我也用这个ipfs链接来测试。

3.3.2 contacts/Demo721.sol

右击contacts目录,新建文件,输入:Demo721.sol并回车,你也可以用别的文件名。输入文件内容如下:

// 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; //计数器,用于NTF的编号
    address private creater; //合约创建者

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

    //产生一个NFT,钱包地址,JSON文件路径
    function mint(address player, string memory tokenURI) public  returns (uint256)
    {
        //只有创建者才可以生产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;
    }
}

这就是主合约文件,有一个构造函数和一个产生NFT的函数mint。mint函数要合约的创建者才能调用,传入合约创建者的钱包地址和上面提到的JSON文件存储地址即可创建一个NFT。构造函数调用了父合约的构造函数,两个参数分别为代币的说明和代币的符号(缩写)。构造函数中也初始化了两个NFT,一个是在普通网站上存储的,一个是在IPFS系统上存储的。

3.3.3 migrations/721_deploy.js

在migrations目录下新建721_deploy.js文件,这是一个部署js文件,文件名中下划线前面一定要为数字才能在部署时被执行,如果migrations目录下有多个文件,按前面数字从小到大的顺序依次执行。文件内容如下:

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

代码也很少,第一行是导入Demo721.sol合约文件,deployer.deploy(elwin721);为部署导入的合约。

3.3.4 truffle-config.js

修改项目根目录下的truffle-config.js文件,文件内容如下:

var HDWalletProvider = require("@truffle/hdwallet-provider");
var MnemonicPhrase = "hello cat shed ……";  //填入你的MetaMask钱包助记词
var ProviderUrl ="https://rpc.ankr.com/eth_goerli";   //goerli 网接口地址
var AddressIndex=0;  //钱包中的第几个地址,从0开始;如果钱包中只有一个地址,可以去掉这个参数

module.exports = {
  networks: {
    sepoliaTestNet:{
      network_id:"*",
      provider: function() {
        return new HDWalletProvider(MnemonicPhrase, ProviderUrl, AddressIndex);//使用助记词
      },
    network_id: "*",  // 匹配任意网络
    gas: 3012388,
    gasPrice: 2000000000 //根据网络行情设定燃料价格
    }
  },
  mocha: {},
  plugins: [
    'truffle-plugin-verify'
  ],
  api_keys: {
     etherscan: "*****"
  },
  compilers: {
    solc: {
      version: "0.8.17"
    }
  }
};

这个文件是项目的一些配置信息,MnemonicPhrase为MetaMask钱包助记词(填写你自己的,12个单词用半角空格分隔),钱包中的地址为合约的发布者地址,所以这个地址要保证有余额;ProviderUrl 为网络接口地址;gas为最大使用燃气量,如果不够会报错,如果超出会退回;gasPrice为燃气价格,每个网络不同的,可以使用postman等工具查询各网络提供的JSON-RPC接口获取(参考我的前一篇文章)。你可以把燃料价格设得比当前价格高一点才能优先被执行,如果设置的低于当前价格,部署时会报错。api_keys.etherscan的值,在后面合约验证的时候再填写。

3.4 部署合约

保存上面的代码后,在资源管理器中鼠标右击合约文件(如下图),在弹出菜单的最下面有Build ContractsDeploy Contracts两个菜单,Build Contracts为执行编译合约命令,Deploy Contracts为部署合约命令。因为执行部署命令时会先编译,所以可以直接点击Deploy Contracts菜单。

编译或部署以太坊智能合约

然后在弹框中选择刚才在truffle-config.js配置文件中配置的sepoliaTestNet网络开始部署,如下图:



有时右下角会提示再次提示安装@truffle/hdwallet-provider钱包,点击“安装”即可。如果部署不成功,再次重复上面的操作来部署即可。

出现类似下面的结果即为部署成功,如果出现其他错误提示,按提示内容来处理。

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为钱包账户地址,你会发现余额减少了,上面用了2839083个燃气,花费0.005678166 个测试币。

四、代币合约验证

我们部署的合约,其实是部署了编译后的字节码;为了让你的合约更可信,可以上传合约源代码到etherscan.io网验证一下,这样显得你的代币合约更可信。上图中点击刚部署的合约页签,红框位置有提示校验合约。详细操作可以参考这篇文章:https://learnblockchain.cn/article/1314

注意要以管理员身份运行校验命令truffle run verify Demo721 --network GoerliTestNet 两个参数分别为合约名和网络配置名:

管理员身份执行校验命令

上图因为网络连接的原因无法校验。Failed to connect to Etherscan API at url https://api-goerli.etherscan.io/api

下图是之前校验成功的合约,可以看到合约页签后面有绿色的图标,点击“Read Contract”按钮,就可以调用合约中的视图函数,比如查询NFT的存储地址,以及所有者等操作。


校验成功的合约

五、查看铸造的NFT

5.1 MetaMask钱包中查看

打开MetaMask钱包,切换到Geoerli测试网络,点击最下面的“添加资产”链接,弹出如下界面:


输入代币合约地址,再点击下一个输入框,发现自动识别了代币符号,小数填0,然后点击“添加自定义代币”按钮,然后再次确认点击“添加代币”按钮,就可以看到刚发行的NFT了,如下图。



这里只能看到该合约NFT数量为2,但看不到图片,也不能交易。

5.2 testnets.opensea.io上查看

在浏览器上输入上面的地址,然后点击右上角的“个人资料”图标或钱包图标,会弹出要连接钱包(如下图)。选择MetaMask钱包,并输入密码。


然后再点击个人资料,就能看到刚才发布的两个NFT了。点击可以看到NFT的详细资料,也可以出售。


testnets.opensea.io网上可以看到发行的NFT图片

六、后续铸造

上面是在构造函数中铸造的两个NFT,如果还想要继续铸造怎么办呢?下面介绍一种方法,使用ethers.js前端接口来调用发布的合约。先在终端中执行命令npm i ethers 安装依赖包。

6.1 test/mint721.js

在“test”目录下新建一个“mint721.js”文件,内容如下:

const ethers = require('ethers');

//调用合约的函数或者事件,不需要全部函数的定义都列出来
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);

// 钱包地址的私钥;生产环境注意保密,用完可以清空
let privateKey="*****";

// 从私钥获取一个签名器 Signer
let wallet = new ethers.Wallet(privateKey, provider);

// 使用签名器创建一个新的合约实例,它允许使用可更新状态的方法
let contractWithSigner = contract.connect(wallet);

// 调用前面定义的abi交易函数,需要扣除交易费。 只读函数不用这种方式调用,可以在etherscan.io网上校验合约后直接调用
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);
      }
    }
  );

要调用合约的mint函数,要在abi函数数组中写出函数的定义,注意要把“view”、“memory”等修饰词去掉,多余的空格也去掉。不必把全部的函数的定义都写出来,需要调用什么就写什么。合约地址,和钱包地址改为你自己的。钱包的私钥privateKey,可以在MetaMask中,点击数字钱包地址后面的三个点符号,进入“账户详情”页中“导出私钥”获取(如下图)。

保存上面的js文件后,在终端中输入命令node ./test/mint721.js并回车运行,即可在指定的合约中再次铸造一个NFT,运行结果如下图。

然后再打开opensea的测试网,点击“个人资料”,就能看到后面新铸造的NFT数字藏品了,如果没有看到,可以稍等一会儿再刷新页面。


详情中可以看到合约地址,点击进去可以看到合约的基本信息、交易、事件、验证等;代币ID,点击后面蓝色的数字可以看到JSON定义文件内容;JSON中定义的5个属性已经显示在上面了。

至此,ERC721标准的数字藏品发行和后期铸造已经讲完了。当然你也可以不用写代码,直接在opensea网站上传你的数字藏品,然后直接出售。opensea网站上直接上传相当于调用了网站发布的ERC1155标准合约上mint或batchMint函数。EIP1155是一个多代币合约,可以将ERC20、ERC721都包含进一个合约中。比较适合游戏环境中,游戏中的金子或银两就是同质化的,而游戏中的武器装备又是非同质化的,这种情况就使用ERC1155标准合约,增加新的装备就调用mint函数就可以了,而不用每次都部署一个新的合约了。同理opensea使用ERC1155也是为了减少频繁部署新的合约,避免浪费交易费用以及方便后续的管理。

感谢阅读,原创内容,转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容