一、ERC1155 和ERC721 有什么区别
开始介绍ERC1155 智能合约的写法之前,我觉得先要说说ERC1155 和ERC721 到底有什么区别。简单来说,ERC1155 是ERC721 的升级版,ERC1155 在ERC721 的基础上,主要增加或改善了如下功能:
- 同时支持可替换代币(同质化代币)和不可替换代币(非同质化代币);
- 批量转账: 仅需要一次智能合约调用,就可以转账多种代币资产;
- 批量查询余额:一次智能合约调用可以查询多种代币余额资料;
- 批量授权:一次智能合约调用可以向指定地址授权多种代币的使用权;
用再简单一些的人话来讲,就是一张ERC1155 智能合约,里面可以同时包含类似USDT 这种代币,和类似MekaVerse,无聊猿等这种独一无二的NFT 代币。
另外,传统的ERC721 合约,如果要转移NFT,必须一个一个操作,没有办法做到批量转账。
而ERC1155,则宛如加入了类似「购物车」的概念,可以一堆代币一次过转移。
不要小看这个「批量」功能,要知道每次和区块链交互,都要消耗gas,如果有某些应用场景需要大量,频繁转移资产,那ERC1155 相比ERC721 则能节约大量的gas 成本。
我们可以试想一下,假如现在想要创作一款区块链游戏,里面有类似USDT 的可替换同质化代币以进行金融操作,也需要有各种游戏装备,英雄角色等独一无二不可替换代币来进行游戏,这两种代币也需要透过一定的机制进行交易互换,这个场景,使用ERC1155 则可以完美实现。
我们不妨对比一下ERC721 和ERC1155 合约的规范接口:
ERC721
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool)
ERC1155
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
function balanceOf(address _owner, uint256 _id) external view returns (uint256);
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
function setApprovalForAll(address _operator, bool _approved) external;
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
可以明显看到,ERC1155 在原有ERC721 基础上增加了很多batch 的接口。
再看看OpenZeppelin 中的ERC1155 实现,其中的mint function 增加了amount参数, 不难想象,如果amount = 1, 则代表这个代币总共只发行一个,那这个代币就是类似ERC721 的NFT 了。
function _mint(
address account,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(account != address(0), "ERC1155: mint to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data);
_balances[id][account] += amount;
emit TransferSingle(operator, address(0), account, id, amount);
_doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data);
}
说完了基本概念,下面我们就来get your hands dirty,亲手尝试一下写一个ERC1155 智能合约,你会发现,其实远比你想象的简单。
二、代码示例
下面我们借助openzeppelin智能合约库来写一个ERC1155合约,来实现NFT的铸造、批量铸造、单个NFT的URI信息获取和批量URI获取等功能。
代码:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract rockPaperScissors is ERC1155 {
uint256 public constant Rock = 1;
uint256 public constant Paper = 2;
uint256 public constant Scissors = 3;
constructor() ERC1155("https://ipfs.io/ipfs/bafybeihjjkwdrxxjnuwevlqtqmh3iegcadc32sio4wmo7bv2gbf34qs34a/{id}.json") {
_mint(msg.sender, Rock, 1, "");
_mint(msg.sender, Paper, 1, "");
_mint(msg.sender, Scissors, 1, "");
}
function uri(uint256 _tokenid) override public pure returns (string memory) {
return string(
abi.encodePacked(
"https://ipfs.io/ipfs/bafybeihjjkwdrxxjnuwevlqtqmh3iegcadc32sio4wmo7bv2gbf34qs34a/",
Strings.toString(_tokenid),".json"
)
);
}
}
三、使用Remix IDE
Remix是Ethereum 官方提供的IDE 。包含完整的编译器、执行合约、发布合约等等的功能。无须安装,只要用浏览器开启Remix - Ethereum IDE即可。
Remix 默认情况下会将所有资料存储在浏览器的Local Storage 中,所以当你清除浏览器缓存快取,或者在另外一台电脑打开Remix,你的资料便会丢失。Remix 也提供了使用本地文件系统来进行存储的功能,但需要安装一个remixd 本地程式,具体方法可以参考这里。
当你安装好remixd 后,便可以透过运行下面的指令来建立Remix 和本地档案的连结:
remixd -s <absolute-path-to-the-shared-folder> --remix-ide <your-remix-ide-URL-instance>
四、编译,发布智能合约
编辑完成后, 就可以切换到Compile 面板进行编译。留意要选择正确的编译器版本,编译器版本要和你合约中声明的版本相符合,否则无法编译。
编译完成后,便可以准备发布(deploy)了。切换到Deploy 面板。此时我们要在左上角的Environment 中选择【Injected Web 3】,此时便会弹出MetaMask 进行连结。如果还没有安装MetaMask,可以先在Chrome Web Store中安装。
我们开始尝试时一般都会选择在测试网路(testnet)中进行,测试网络和Ethereum 主网在技术上没有区别,但在测试网路中不需要花费真实的ETH ,而是使用测试网路中的ETH。一般我们开发的Blockchain 程式,都要先在测试网路上做全面测试,之后才在主网中上线。要知道Ethereum 主网的Gas Fee 可高的惊人。
在Ethereum 网路上任何导致更改数据的动作都需要花费ETH,因此发布智能合约自然需要ETH。如果没有testnet 的ETH 可以在网上找到很多testnet faucet 可以免费获得testnet 的ETH 。一切就绪后,就可以按下【Deploy】,开始发布你的智能合约,中间会弹出MetaMask 进行付款确认。
发布完成后,Remix 会显示对应的Transaction Hash,例如我自己上面的例子,对应的Transaction 可以在区块链浏览器上看到,里面亦可以看到我们建立的ERC1155 智能合约地址。
五、验证合约
验证合约
选择合约类型
上传合约代码
合约验证成功
六、测试合约
Mint NFT
回到Remix或者浏览器合约页面,可以看到Deployed Contracts 中已经有我们刚才发布的智能合约,点开他之后可以看到里面有一个mint 的Function,现在我们就可以调用这个Function 来Mint NFT 了。
这个mint function 需要传入四个参数,分别是 拥有者地址,token id,发行数量,附加数据
附加数据在ERC1155 规范中并没有指定用途,因此这个参数可以根据自己的需要来传递任何数据,如果没有的话传递空值即可。
可以查看一下这笔交易的详情
query balance
然后我们可以查看一下刚刚创建的NFT余额,点开他之后可以看到里面有一个balanceOf 的Function,现在我们就可以调用这个Function 来query balance 了。
这个balanceOf function 需要传入四个参数,分别是 拥有者地址,token id
查询token ID的附加数据
转移NFT
然后我们可以再对刚刚mint的NFT进行转移,点开他之后可以看到里面有一个safeTransferFrom 的Function,现在我们就可以调用这个Function 来转移了。
这个safeTransferFrom function 需要传入四个参数,分别是 from,to,token id,amount,data。
我们经常在solidity合约方法的参数里看到的“bytes _data”,那么他究竟是做什么用的?
可以把 bytes 理解为一个泛型参数。因为bytes 可以转化为各种不同的基本类型。
例如,在 safeTransferFrom 中,你可以在转账的时候把一个附加的信息给目标方,这个附加的信息可以一个单品 ID(uint), 或者是一个推荐人地址。
一个高级的用法时:bytes 包含了一个函数选择器编码及参数, 这样可以通过这个参数去实现动态调用其他的方法。
可以查看一下这笔交易的详情