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