NFT(非同质化代币):类似于明朝、宋朝的青花瓷。虽然都是青花瓷。但是都具有唯一的典藏价值。而且价值可能不同。 NFT就是具有唯一价值的代币。
ERC721: 是以太坊规定实现NFT的一种标准了。实现ERC21标准的智能合约就是NFT代币了。
定义接口参考:ERC 721 - OpenZeppelin 文档
下面是以太坊官方定义的标准,由于就是我写的代码运行环境不支持payable关键字,因此我打算围绕官方接口定义,按照自己要求稍微增删一下。
pragma solidity ^0.4.25;
interface ERC721 {
///
Event
///
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
///
function balanceOf(address _owner) external view returns (uint256); // 返回所有者代币的总个数
function ownerOf(uint256 _tokenId) external view returns (address); // 返回代币id对应所有者的账户地址
// 安全的转账
// _to:是已经被指定 id 代币的所有者授予的账户 and (接受者不是智能合约 or 接受者实现ERC721Receiver接口的智能合约
// 将给定id的代币转移到接受者账户
// data是元数据,可有可不有(我觉得)
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;
// 这个转账对比上述安全转账(少了一个接受者地址实现是否是ERC721Receiver接口的智能合约地址的判断)
function transferFrom(address _from, address _to, uint256 _tokenId) external;
// 授权将代币转移到另一个账户的权限
function approve(address _approved, uint256 _tokenId) external;
// 授权接受者使用所有代币
function setApprovalForAll(address _operator, bool _approved) external;
// 返回授权指定id 代币的接受者账户
function getApproved(uint256 _tokenId) external view returns (address);
// 判断某账户代币的拥有者是否能被某账户全部使用
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
以下就是ERC21的元数据接口,这是可选地。名称、标识符、每一个token对应的tokenURI。
pragma solidity ^0.4.25;
interface ERC721Metadata {
function name() external view returns (string);
function symbol() external view returns (string);
function tokenURI(uint256 tokenId) external view returns (string); // 返回指定id的代币所对对应的uri
}
另一个额外的可选接口是枚举, 它包含了按索引获取到对应的代币。
pragma solidity ^0.4.25;
interface ERC721Enumerable {
// 确定合约当前全部的nft数量(出去销毁)
function totalSupply() external view returns (uint256);
// 从代币列表返回第n个代币
function tokenByIndex(uint256 _index) external view returns (uint256);
// 返回所有者代币列表的第n个代币
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}
pragma solidity ^0.4.25;
// 资产合约
interface ERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes data) external
returns (bytes);
}
这是我针对ERC721接口的合约实现。基本满足官方接口标准。
pragma solidity ^0.4.25;
import "./ERC721.sol";
import "./ERC721Metadata.sol";
contract Jzm721 is ERC721,ERC721Metadata {
///
Filed
///
string public name;
string public symbol;
uint256 nftCount;
mapping (address => uint[]) balanceMap; // owner => tokenId[]
mapping (uint256=>string) tokenURIMap; // tokenId => tokenURI
mapping (uint256=>address) tokenIdMap; // tokenId => owner
mapping (uint256 => address) approveMap; // tokenId => operator(经营方)
mapping (address=>mapping (address=>bool)) approveAllMap; // operator =>(owner => true/false)
///
Function
///
constructor(string memory _name,string memory _symbol)
public {
name = _name;
symbol = _symbol;
}
function name() external view returns (string) {
return name;
}
function symbol() external view returns (string) {
return symbol;
}
function tokenURI(uint256 tokenId) external view returns (string) {
return tokenURIMap[tokenId];
}
// 创建代币
function mint(address _owner,string _tokenURI) external returns (uint256) {
require(_owner != address(0),"owner is not empty address!");
uint256 tokenId = _mint(_owner);
_setTokenURI(tokenId, _tokenURI);
return tokenId;
}
function balanceOf(address _owner) external view returns (uint256) {
return balanceMap[_owner].length;
}
function ownerOf(uint256 _tokenId) external view returns (address) {
return tokenIdMap[_tokenId];
}
// 这里我忽略了data这个元数据的作用
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external {
_transferFrom(_from, _to, _tokenId);
if(_isContractAdd(_to)) {
if(_checkIfFunctionExists(_to)) {
_externalTransfer(_from, _to, _tokenId);
emit Transfer(_from, _to, _tokenId);
}
}
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
_transferFrom(_from, _to, _tokenId);
if(_isContractAdd(_to)) {
if(_checkIfFunctionExists(_to)) {
_externalTransfer(_from, _to, _tokenId);
emit Transfer(_from, _to, _tokenId);
}
}
}
function transferFrom(address _from, address _to, uint256 _tokenId) external {
_transferFrom(_from, _to, _tokenId);
}
function approve(address _approved, uint256 _tokenId) external {
require(_approved != address(0),"approved is not empty address!");
address owner = msg.sender;
approveMap[_tokenId] = _approved;
emit Approval(owner,_approved,_tokenId);
}
function setApprovalForAll(address _operator, bool _approved) external {
require(_operator != address(0),"operator is not empty address!");
address owner = msg.sender;
approveAllMap[_operator][owner] = _approved;
emit ApprovalForAll(owner,_operator,_approved);
}
function getApproved(uint256 _tokenId) external view returns (address) {
return _getApproved(_tokenId);
}
function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
return _isApprovedForAll(_owner,_operator);
}
function _checkIfFunctionExists(address _add)public returns (bool) {
bytes4 functionSelector = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); // 函数选择器,基础原型前4个字节
bytes memory data = abi.encodeWithSelector(functionSelector, address(0),address(0),0,"");
bool success = _add.call(data);
return success;
}
function _transferFrom(address _from, address _to, uint256 _tokenId) private {
// from不能是零地址。
require(_from != address(0),"from is not empty address!");
// to不能是零地址。
require(_to != address(0),"from is not empty address!");
// tokenId令牌必须存在并由from拥有。
require(tokenIdMap[_tokenId] == _from,"The tokenId must exist and be owned by from!");
// 接受者一方不是合约地址
if (!_isContractAdd(_to)) {
if (_isApproved(_from, _to, _tokenId)) {
// TODO
_externalTransfer(_from, _to, _tokenId);
emit Transfer(_from, _to, _tokenId);
}
}
}
function _externalTransfer(address _from,address _to,uint256 _tokenId) private {
// 删除代币批准
if(_getApproved(_tokenId) == _to) {
approveMap[_tokenId] = address(0);
}
// 转账
_deleteAccountToken(_from,_tokenId);
tokenIdMap[_tokenId] = _to;
balanceMap[_to].push(_tokenId);
}
function _deleteAccountToken(address _owner,uint256 _tokenId) private {
uint256[] storage tokenIds = balanceMap[_owner];
uint len = tokenIds.length;
for (uint i = 0; i < len; i++) {
if(tokenIds[i] == _tokenId) {
// 交换
uint swap;
swap = tokenIds[i];
tokenIds[i] = tokenIds[len - 1];
tokenIds[len - 1] = swap;
}
}
tokenIds.length--;
}
function _mint(address _owner) private returns (uint256) {
nftCount += 1;
uint256 tokenId = nftCount + block.timestamp;
balanceMap[_owner].push(tokenId);
tokenIdMap[tokenId] = _owner;
emit Transfer(address(0),_owner,tokenId);
return tokenId;
}
function _setTokenURI(uint256 _tokenId,string _tokenURI) private{
tokenURIMap[_tokenId] = _tokenURI;
}
// 判断该地址是否合约地址
function _isContractAdd(address _addr) private view returns (bool) {
uint size;
assembly {
size := extcodesize(_addr) // 返回地址关联代码的长度
}
return size > 0;
}
function _getApproved(uint256 _tokenId) private view returns(address) {
return approveMap[_tokenId];
}
function _isApproved(address _owner, address _operator,uint256 _tokenId) private view returns (bool) {
bool approved = _getApproved(_tokenId) == _operator;
return approved || _isApprovedForAll(_owner, _operator);
}
function _isApprovedForAll(address _owner, address _operator) private view returns(bool) {
return approveAllMap[_operator][_owner];
}
}
在这里我就是想要满足合约地址的合约实现ERC721Receiver接口的标准。原合约,这里该函数涉及代币的转账,由于环境的原因,不支持payable关键字,我这里是无法满足的。
pragma solidity ^0.4.25;
import "./ERC721Receiver.sol";
contract Jzm721Receiver is ERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes data) external
returns (bytes) {
return data;
}
}