第33集:ERC-1155多代币标准
学习目标
- 了解ERC-1155多代币标准的核心概念
- 掌握ERC-1155多代币的实现方法
- 学习ERC-1155的批量操作功能
- 了解ERC-1155的优势和应用场景
- 掌握ERC-1155开发的最佳实践
核心知识点讲解
1. ERC-1155多代币标准概述
ERC-1155是以太坊上的多代币标准,它允许在一个合约中管理多种类型的代币,包括:
- 同质化代币(Fungible Tokens):如ERC-20
- 非同质化代币(Non-Fungible Tokens):如ERC-721
- 半同质化代币(Semi-Fungible Tokens):介于两者之间的代币
ERC-1155的核心特点:
- 支持多种代币类型
- 批量操作功能
- 更高效的Gas消耗
- 灵活的代币管理
2. ERC-1155接口
ERC-1155标准定义了以下接口:
interface IERC1155 {
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
}3. ERC-1155实现
实现ERC-1155多代币需要:
- 实现所有必需的接口函数
- 维护代币余额和批准记录
- 处理代币的创建和转移
- 支持批量操作
- 管理代币元数据
4. ERC-1155的优势
相比ERC-20和ERC-721,ERC-1155具有以下优势:
- 更高效的Gas消耗:批量操作减少了交易数量
- 更灵活的代币管理:一个合约可以管理多种代币
- 支持半同质化代币:介于同质化和非同质化之间的代币
- 更好的互操作性:标准接口便于与其他合约交互
5. ERC-1155应用场景
ERC-1155的主要应用场景包括:
- 游戏物品:管理游戏中的各种物品和货币
- 数字收藏品:管理系列收藏品
- 代币化资产:管理多种类型的资产
- DeFi协议:管理多种类型的代币
实用案例分析
案例1:基本ERC-1155实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract BasicERC1155 {
mapping(address => mapping(uint256 => uint256)) private _balances;
mapping(address => mapping(address => bool)) private _operatorApprovals;
string private _uri;
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
constructor(string memory uri_) {
_uri = uri_;
}
function balanceOf(address account, uint256 id) external view returns (uint256) {
require(account != address(0), "Balance query for the zero address");
return _balances[account][id];
}
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory) {
require(accounts.length == ids.length, "Accounts and IDs length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; i++) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
function setApprovalForAll(address operator, bool approved) external {
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function isApprovedForAll(address account, address operator) external view returns (bool) {
return _operatorApprovals[account][operator];
}
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external {
require(from == msg.sender || isApprovedForAll(from, msg.sender), "Not approved");
require(to != address(0), "Transfer to the zero address");
_balances[from][id] -= amount;
_balances[to][id] += amount;
emit TransferSingle(msg.sender, from, to, id, amount);
// 这里应该添加接收者是否支持ERC1155的检查
}
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external {
require(from == msg.sender || isApprovedForAll(from, msg.sender), "Not approved");
require(to != address(0), "Transfer to the zero address");
require(ids.length == amounts.length, "IDs and amounts length mismatch");
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
_balances[from][id] -= amount;
_balances[to][id] += amount;
}
emit TransferBatch(msg.sender, from, to, ids, amounts);
// 这里应该添加接收者是否支持ERC1155的检查
}
function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal {
require(to != address(0), "Mint to the zero address");
_balances[to][id] += amount;
emit TransferSingle(msg.sender, address(0), to, id, amount);
}
function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal {
require(to != address(0), "Mint to the zero address");
require(ids.length == amounts.length, "IDs and amounts length mismatch");
for (uint256 i = 0; i < ids.length; i++) {
_balances[to][ids[i]] += amounts[i];
}
emit TransferBatch(msg.sender, address(0), to, ids, amounts);
}
function _burn(address from, uint256 id, uint256 amount) internal {
require(from != address(0), "Burn from the zero address");
_balances[from][id] -= amount;
emit TransferSingle(msg.sender, from, address(0), id, amount);
}
function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal {
require(from != address(0), "Burn from the zero address");
require(ids.length == amounts.length, "IDs and amounts length mismatch");
for (uint256 i = 0; i < ids.length; i++) {
_balances[from][ids[i]] -= amounts[i];
}
emit TransferBatch(msg.sender, from, address(0), ids, amounts);
}
function uri(uint256 id) external view returns (string memory) {
return string(abi.encodePacked(_uri, id, ".json"));
}
}案例2:使用OpenZeppelin实现ERC-1155
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyERC1155 is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant SWORD = 2;
uint256 public constant SHIELD = 3;
constructor() ERC1155("https://example.com/api/token/{id}.json") {
_mint(msg.sender, GOLD, 1000, "");
_mint(msg.sender, SILVER, 5000, "");
_mint(msg.sender, SWORD, 1, "");
_mint(msg.sender, SHIELD, 1, "");
}
function mint(address to, uint256 id, uint256 amount) public onlyOwner {
_mint(to, id, amount, "");
}
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) public onlyOwner {
_mintBatch(to, ids, amounts, "");
}
function burn(address from, uint256 id, uint256 amount) public {
require(msg.sender == from || isApprovedForAll(from, msg.sender), "Not approved");
_burn(from, id, amount);
}
function burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) public {
require(msg.sender == from || isApprovedForAll(from, msg.sender), "Not approved");
_burnBatch(from, ids, amounts);
}
}案例3:ERC-1155前端交互
// 安装依赖
// npm install ethers
const { ethers } = require('ethers');
// 连接到以太坊网络
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY');
// ERC-1155合约地址
const erc1155Address = '0x...';
// ERC-1155 ABI
const erc1155ABI = [
"function balanceOf(address, uint256) view returns (uint256)",
"function balanceOfBatch(address[], uint256[]) view returns (uint256[])",
"function setApprovalForAll(address, bool) returns (void)",
"function isApprovedForAll(address, address) view returns (bool)",
"function safeTransferFrom(address, address, uint256, uint256, bytes) returns (void)",
"function safeBatchTransferFrom(address, address, uint256[], uint256[], bytes) returns (void)",
"function uri(uint256) view returns (string)",
"event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)",
"event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)",
"event ApprovalForAll(address indexed owner, address indexed operator, bool approved)",
"event URI(string value, uint256 indexed id)"
];
// 创建ERC-1155合约实例
const erc1155Contract = new ethers.Contract(erc1155Address, erc1155ABI, provider);
// 读取单个代币余额
async function getBalance(address, tokenId) {
const balance = await erc1155Contract.balanceOf(address, tokenId);
console.log(`Balance of token ${tokenId}: ${balance.toString()}`);
}
// 读取批量代币余额
async function getBatchBalance(accounts, tokenIds) {
const balances = await erc1155Contract.balanceOfBatch(accounts, tokenIds);
console.log('Batch balances:', balances.map(b => b.toString()));
}
// 转账单个代币
async function transferToken(from, to, tokenId, amount) {
// 连接钱包
const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
const erc1155WithSigner = erc1155Contract.connect(wallet);
// 转账
const tx = await erc1155WithSigner.safeTransferFrom(from, to, tokenId, amount, '0x');
await tx.wait();
console.log('Token transfer successful:', tx.hash);
}
// 批量转账代币
async function transferBatchTokens(from, to, tokenIds, amounts) {
// 连接钱包
const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
const erc1155WithSigner = erc1155Contract.connect(wallet);
// 批量转账
const tx = await erc1155WithSigner.safeBatchTransferFrom(from, to, tokenIds, amounts, '0x');
await tx.wait();
console.log('Batch token transfer successful:', tx.hash);
}
// 读取代币URI
async function getTokenURI(tokenId) {
const uri = await erc1155Contract.uri(tokenId);
console.log(`Token ${tokenId} URI: ${uri}`);
}
// 运行示例
getBalance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 0);
getBatchBalance(['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'], [0, 1]);
transferToken('0x...', '0x...', 0, 10);
transferBatchTokens('0x...', '0x...', [0, 1], [10, 20]);
getTokenURI(0);案例4:ERC-1155游戏物品示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItems is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant SILVER = 1;
uint256 public constant HEALTH_POTION = 2;
uint256 public constant MANA_POTION = 3;
uint256 public constant SWORD = 4;
uint256 public constant SHIELD = 5;
constructor() ERC1155("https://example.com/api/game-items/{id}.json") {
// 初始化一些代币
_mint(msg.sender, GOLD, 1000, "");
_mint(msg.sender, SILVER, 5000, "");
}
// 铸造游戏货币
function mintCurrency(address to, uint256 amount) public onlyOwner {
_mint(to, GOLD, amount, "");
}
// 铸造游戏物品
function mintItem(address to, uint256 itemId) public onlyOwner {
_mint(to, itemId, 1, "");
}
// 批量铸造游戏物品
function mintBatchItems(address to, uint256[] memory itemIds, uint256[] memory amounts) public onlyOwner {
_mintBatch(to, itemIds, amounts, "");
}
// 游戏内交易
function tradeItems(address from, address to, uint256[] memory itemIds, uint256[] memory amounts) public {
require(msg.sender == from || isApprovedForAll(from, msg.sender), "Not approved");
safeBatchTransferFrom(from, to, itemIds, amounts, "");
}
}ERC-1155多代币标准最佳实践
实现标准:
- 严格按照ERC-1155标准实现接口
- 使用经过审计的库(如OpenZeppelin)
- 测试所有功能,特别是批量操作
安全性:
- 防止重入攻击
- 验证输入参数
- 限制敏感函数的访问
- 实现安全的转移函数
Gas优化:
- 利用批量操作减少交易数量
- 优化存储操作
- 减少不必要的计算
- 使用合适的数据类型
元数据管理:
- 使用IPFS存储元数据和图像
- 确保元数据的完整性和持久性
- 为不同类型的代币提供合适的元数据
部署和验证:
- 在测试网测试
- 部署到主网
- 验证合约代码
- 监控合约活动
前端集成:
- 提供清晰的用户界面
- 显示代币元数据和图像
- 处理批量操作
- 支持不同的钱包
总结
本教程介绍了ERC-1155多代币标准的实现和优势,包括基本ERC-1155的实现、使用OpenZeppelin实现ERC-1155、ERC-1155的前端交互,以及ERC-1155在游戏中的应用示例。通过本教程的学习,开发者可以掌握ERC-1155多代币标准的使用方法,为开发多代币应用打下基础。
在实际开发中,开发者应该遵循ERC-1155多代币标准的最佳实践,确保合约的安全性、可靠性和高效性。同时,开发者还应该不断学习新的代币标准和技术,以适应区块链生态系统的发展。