ERC-721协议是以太坊开发中最常使用的第二大协议,第一大协议当然是我们的ERC-20。
ERC-721官方简要解释是Non-Fungible Tokens,简写为NFTs,多翻译为非同质代币。
ERC-721最早进入大家的视野都是因为 《CryptoKitties》 加密猫的功劳。所以我下面会结合加密猫游戏的案例来说说我们的ERC-721。
ERC-721特点:
1.无法分割,ERC-20Token可以无限细分为10^18份,而ERC721的Token最小的单位为1,无法再分割。
2.独一无二,每一个Token完全不同,并且每个Token对不同的用户都有不同的价值。
回想一下,加密猫最小单位为 1只,并且每一只猫的基因长相也完全不同。
下面我会附上以太坊EIP721接口标准。来自:https://eips.ethereum.org/EIPS/eip-721
pragma solidity ^0.4.20;
/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface ERC721 /* is ERC165 */ {
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @dev This emits when the approved address for an NFT is changed or
/// reaffirmed. The zero address indicates there is no approved address.
/// When a Transfer event emits, this also indicates that the approved
/// address for that NFT (if any) is reset to none.
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
/// @dev This emits when an operator is enabled or disabled for an owner.
/// The operator can manage all NFTs of the owner.
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param _owner An address for whom to query the balance
/// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner) external view returns (uint256);
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return The address of the owner of the NFT
function ownerOf(uint256 _tokenId) external view returns (address);
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId) external payable;
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved) external;
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId) external view returns (address);
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
接口说明:
balanceOf(): 返回由_owner 持有的NFTs的数量。
ownerOf(): 返回tokenId代币持有者的地址。
approve(): 授予地址_to具有_tokenId的控制权,方法成功后需触发Approval 事件。
setApprovalForAll(): 授予地址_operator具有所有NFTs的控制权,成功后需触发ApprovalForAll事件。
getApproved()、isApprovedForAll(): 用来查询授权。
safeTransferFrom(): 转移NFT所有权,一次成功的转移操作必须发起 Transer 事件。安全是指如果目标地址为合约地址,则执行onERC721Received进行资产转移,否则的话交易会回滚;如果目标地址为外部账号地址,则正常转移。
细心的朋友应该有发现 ERC-721协议继承自 ERC-165,ERC-165同样也是一个合约标准,这个标准要求合约提供其实现了哪些接口,这样在与合约进行交互的时候可以先调用此接口进行查询。
下面我会截取一些加密猫的源码分析给大家看。
你也可以在Etherscan上查看完整源码:https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#contracts
struct Kitty {
uint256 genes;
uint64 birthTime;
uint64 cooldownEndBlock;
uint32 matronId;
uint32 sireId;
uint32 siringWithId;
uint16 cooldownIndex;
uint16 generation;
}
首先,声明了Kitty结构体,其中包含
genes — 是256位的整数,主要作为猫的遗传密码,是决定猫外貌的核心数据;
birthTime — 猫出生时所打包的区块的时间戳;
cooldownEndBlock — 猫可以再次繁殖的最小时间;
matronId 和 sireId — 分辨是猫母亲的ID号和猫父亲的ID号;
siringWithId — 如果猫怀孕了,则设置为父亲的ID,否则为零;
cooldownIndex — 猫繁殖所需的冷却时间(猫需要等待多久才能繁殖);
generation — 猫的“世代号”(指明这是第几代猫)。合约创造的第一只猫是0代,新一代猫的“世代号”是其父母中较大的一代再加1。
Kitty[] kitties;
Kitty数组,存在“链上”所有猫咪数据。可通过ID号获取猫咪具体信息,也可通过kitties.Length获取猫咪数量。
mapping (uint256 => address) public kittyIndexToOwner;
mapping (address => uint256) ownershipTokenCount;
kittyIndexToOwner 所有猫咪对应的用户地址。
ownershipTokenCount 用户地址下的猫咪数量。
function _transfer(address _from, address _to, uint256 _tokenId) internal {
// Since the number of kittens is capped to 2^32 we can't overflow this
ownershipTokenCount[_to]++;
// transfer ownership
kittyIndexToOwner[_tokenId] = _to;
// When creating new kittens _from is 0x0, but we can't account that address.
if (_from != address(0)) {
ownershipTokenCount[_from]--;
// once the kitten is transferred also clear sire allowances
delete sireAllowedToAddress[_tokenId];
// clear any previously approved ownership exchange
delete kittyIndexToApproved[_tokenId];
}
// Emit the transfer event.
Transfer(_from, _to, _tokenId);
}
这是交易函数
function _createKitty(
uint256 _matronId,
uint256 _sireId,
uint256 _generation,
uint256 _genes,
address _owner
)
internal
returns (uint)
{
require(_matronId == uint256(uint32(_matronId)));
require(_sireId == uint256(uint32(_sireId)));
require(_generation == uint256(uint16(_generation)));
// New kitty starts with the same cooldown as parent gen/2
uint16 cooldownIndex = uint16(_generation / 2);
if (cooldownIndex > 13) {
cooldownIndex = 13;
}
Kitty memory _kitty = Kitty({
genes: _genes,
birthTime: uint64(now),
cooldownEndBlock: 0,
matronId: uint32(_matronId),
sireId: uint32(_sireId),
siringWithId: 0,
cooldownIndex: cooldownIndex,
generation: uint16(_generation)
});
uint256 newKittenId = kitties.push(_kitty) - 1;
require(newKittenId == uint256(uint32(newKittenId)));
// emit the birth event
Birth(
_owner,
newKittenId,
uint256(_kitty.matronId),
uint256(_kitty.sireId),
_kitty.genes
);
// This will assign ownership, and also emit the Transfer event as
// per ERC721 draft
_transfer(0, _owner, newKittenId);
return newKittenId;
}
新的猫咪的产生函数
总结
随着区块链技术的发展,初创公司和开发者开始对ERC721越来越感兴趣,这是一种让加密数字资产更容易普及的方式。
ERC721可能会比ERC20应用的更加广泛。虚拟资产预计将来会有几万亿的市场,届时,将是ERC721标准大放异彩的时候。
下一篇文章我会为大家详细解析一个非常冷门的Token协议,ERC918