猛戳订阅学习专栏🍁🍁 👉 solidity系列合约源码+解析 👈 🍁🍁
1 介绍
和ERC20一样,ERC721同样是一个以太坊代币标准,此代币英文是Non-Fungible Tokens,简写为NFT,即非同质化代币
。
非同质代币(NFT)是一种具有唯一性识别的代币 ,ERC-20 Token可以无限细分为10^18份,而ERC721的Token最小的单位为1,无法再分割。
由于ERC721代币具有唯一性,不可分割的特点,所以很容易和一些具体的物品关联起来,通过这样一个标准,可以更广泛的应用到实际场景中。
2 主要功能
该标准定义了开发者可以按照标准要求使用一些简单的功能如:
- 设定NFT名称
- 设定NFT总量
- 设定NFT元数据资源url
- 查看地址中NFT数量
- 查看NFT的归属地址
- 规范如何批准NFT转移
- 一定条件下,允许第三方地址使用某地址中的NFT
- 允许NFT和兼容ETH的智能合约兼容
以及一些简单的函数功能等等。
我们先来看一下标准的 ERC721 接口:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
下面我们解释一下上述每个接口函数的作用:
-
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
当任何NFT的所有权更改时(不管哪种方式),就会触发此事件。 -
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
当更改或确认NFT的授权地址时触发。零地址表示没有授权的地址。发生Transfer
事件时,同样表示该NFT的授权地址(如果有)被重置为“无”(零地址)。 -
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
所有者启用或禁用操作员时触发。(操作员可管理所有者所持有的NFTs) -
function name() external view returns (string _name);
NFTs 集合的名字 -
function symbol() external view returns (string _symbol);
NFTs 缩写代号 -
function tokenURI(uint256 _tokenId) external view returns (string);
一个给定资产的唯一的统一资源标识符(URI),一般为1个JSON文件。 -
function balanceOf(address owner) external view returns (uint256 balance);
统计owner 地址所持有的NFTs数量 -
function ownerOf(uint256 tokenId) external view returns (address owner);
返回指定NFT的所有者 -
function safeTransferFrom(address from,address to,uint256 tokenId,bytes calldata data) external;
将NFT的所有权从一个地址转移到另一个地址,data : 附加额外的参数(没有指定格式),传递给接收者。 -
function safeTransferFrom(address from,address to,uint256 tokenId) external;
将NFT的所有权从一个地址转移到另一个地址,功能同上,不带data参数。 -
function transferFrom(address from,address to,uint256 tokenId) external;
转移所有权 – 调用者负责确认_to
是否有能力接收NFTs,否则可能永久丢失。一般不建议使用该方法转移NFT -
function approve(address to, uint256 tokenId) external;
更改或确认NFT的授权地址 -
function setApprovalForAll(address operator, bool _approved) external;
启用或禁用第三方(操作员)管理msg.sender
所有NFT,触发 ApprovalForAll 事件。 -
function getApproved(uint256 tokenId) external view returns (address operator);
获取单个NFT的授权地址 -
function isApprovedForAll(address owner, address operator) external view returns (bool);
查询一个地址是否是另一个地址的授权操作员
3 代码示例
下面我们借助openzeppelin智能合约库来写一个ERC721合约,来实现NFT的铸造、销毁和URL设定等功能。
// SPDX-License-Identifier: MIT;
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract TestNFT is ERC721,ERC721Enumerable, ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenId;
constructor() ERC721("The First NFT","FFT") {}
function mint(address _recipient, string memory _tokenUrl) public returns(uint _mintTokenId){
require(bytes(_tokenUrl).length > 0,"The _tokenUrl must be have");
_tokenId.increment();
uint newTokenId = _tokenId.current();
_mint(_recipient, newTokenId);
_setTokenURI(newTokenId, _tokenUrl);
return newTokenId;
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
internal
override(ERC721, ERC721Enumerable)
{
super._beforeTokenTransfer(from, to, tokenId);
}
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
下面解释一下上面用到的合约和方法:
-
@openzeppelin/contracts/token/ERC721/ERC721.sol
该合约实现了我们上面列出的IERC721接口中的方法 -
@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol
该合约为一个枚举合约,包含了按索引获取到对应的NFT,可以提供NFTs的完整列表,以便NFT可被发现。 -
@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol
该合约帮我们实现了TOKEN的URI的设定和读取方法 -
@openzeppelin/contracts/utils/Counters.sol
该合约帮我们实现了Token的自增ID -
constructor()
我们在构造方法中实现了设定该合约生成的NFT的名称的设定(The First NFT)、简写的设定(FFT) -
mint()
为_recipient地址铸造一个NFT,并将其元数据URL设置为_tokenUrl -
_beforeTokenTransfer()
重写_beforeTokenTransfer方法 -
_burn()
销毁某个NFT -
tokenURI()
获取指定NFT的URL信息 -
supportsInterface()
重写supportsInterface方法
4 部署测试
我们选择要部署的合约为 TestNFT
,点击 Deploy 进行部署,可以看到部署后的合约地址和相应的合约方法
分别查看 name 和symbol为我们为合约设置的 名称 和 简写
我们选择一个地址,并设置一个URL,为其mint 1个NFT,可以看到output中输出的tokenid为 1 ,代表着我们成功为该地址铸造了一个tokenid为1的NFT,下面让我们查看一下:
我们可以查询到该地址的NFT数量为1,其token的URL为我们为其设定的URL,这样一个NFT的铸造就完成了~
如果我们部署时选择的链是测试链或者主链,那么我们就可以在opensea中看到我们刚才铸造的NFT啦!!!
如果有小伙伴想了解怎么把NFT的图片存储到IPFS上去的,可以先参考我之前的一篇文章,后面我们会展开详解关于IPFS的内容和教程:https://blog.csdn.net/qq_36228377/article/details/123154404