Solidity 以太坊智能合约标准-ERC721(2023版)

ERC721 定义了一种以太坊生态中不可分割的、具有唯一性的Token交互、流通的接囗规范。官网简要称为 Non-Fungible Token Standard(简称NFT标准规范),即非同质化Token(或不可替代的Token)。

ERC721合约标准提供了在实现ERC721 Token 时必须要遵守的协议,要求每个ERC721标准合约需要实现ERC721接囗及ERC165接囗。

ERC721特性:

  • 在该合约内,tokenId唯一
  • tokenId只能被一个owner所拥有
  • 一个owner可以拥有多个NFT,balance函数只能查询owner拥有多少个token
  • NFT可通过approve、transfer等接囗方法进行流通,即NFT所有权转移

ERC721接口实现及详解:

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.7;
interface IERC721 /* is ERC165 */ {

    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    //转移事件:token发生转移时进行event通知;
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    //授权事件:发生授权事件时进行通知;
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
    //全部授权事件,发生全部授权时进行通知;
    function balanceOf(address _owner) external view returns (uint256);
    //账户余额:账户有多少个tokenID
    function ownerOf(uint256 _tokenId) external view returns (address);
    //拥有权查询:按照tokenID查询其归属账户
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable;
    //tokenID转移,参数不同;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    //tokenID安全转移:转移三要素要存在;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    //tokenID转移
    function approve(address _approved, uint256 _tokenId) external payable;
    //授权:这里的授权指to有权利将委托方的tokenID进行转移;
    function setApprovalForAll(address _operator, bool _approved) external;
    //全部授权,指将用户名下的所有tokenID进行转移;
    function getApproved(uint256 _tokenId) external view returns (address);
    //授权查询:查询tokenID对应的被授权者;
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
    //查看全部授权:查看owner是否对operator赋予了全部授权;
}

ERC721具体示例代码如下:

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.7;

import "./IERC165.sol";
import "./IERC721.sol";
import "./Address.sol";        //导入库

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface ERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the recipient
    ///  after a `transfer`. This function MAY throw to revert and reject the
    ///  transfer. Return of other than the magic value MUST result in the
    ///  transaction being reverted.
    ///  Note: the contract address is always the message sender.
    /// @param _operator The address which called `safeTransferFrom` function
    /// @param _from The address which previously owned the token
    /// @param _tokenId The NFT identifier which is being transferred
    /// @param _data Additional data with no specified format
    /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    ///  unless throwing
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes memory _data) external returns(bytes4);
}

contract ERC721 is IERC721,IERC165 {
    mapping(bytes4 => bool)supportsInterfaces;
    bytes4 invalidID = 0xffffffff;

    bytes4 constant ERC165_InterfaceID=0x01ffc9a7;     //erc165
    bytes4 constant ERC721_InterfaceID=0x80ac58cd;     //erc721 
    mapping(address=>uint256)ercTokenCount;            //记录用户token count;
    mapping(uint256=>address)ercTokenOwner;            //记录token的拥有者;
    mapping(uint256=>address)ercTokenApproved; 
    mapping(address=>mapping(address=>bool))ercOperatorForAll;

    using Address for address;

    constructor(){
        _registerInterface(ERC165_InterfaceID);
        _registerInterface(ERC721_InterfaceID);
    }

    //授权
    modifier canOperator(uint256 _tokenId){
        address owner = ercTokenOwner[_tokenId];
        require(msg.sender == owner || ercOperatorForAll[owner][msg.sender]);
        _;
    }

    //转账
    modifier canTransfer(uint256 _tokenId,address _from){
        address owner = ercTokenOwner[_tokenId];
        require(owner==_from);
        require(msg.sender == owner || msg.sender == ercTokenApproved[_tokenId] || ercOperatorForAll[owner][msg.sender]);
        _;
    }

    function _registerInterface(bytes4 interfaceID)internal {
        supportsInterfaces[interfaceID]=true;
    }
    function supportsInterface(bytes4 interfaceID) override  external view returns (bool) {
        require(invalidID != interfaceID);
        return supportsInterfaces[interfaceID];
    }

    //721
    function balanceOf(address _owner) override external view returns (uint256){
        return ercTokenCount[_owner];

    }

    function ownerOf(uint256 _tokenId) override external view returns (address){
        return ercTokenOwner[_tokenId];
    }

    function getApproved(uint256 _tokenId) override external view returns (address){
        return ercTokenApproved[_tokenId];
    }

    function isApprovedForAll(address _owner, address _operator) external view returns (bool){
        return ercOperatorForAll[_owner][_operator];
    }

    function approve(address _approved, uint256 _tokenId) override external payable{
        ercTokenApproved[_tokenId]=_approved;
        emit Approval(msg.sender,_approved,_tokenId);
    }

    function setApprovalForAll(address _operator, bool _approved) override external{
        ercOperatorForAll[msg.sender][_operator]=_approved;
        emit ApprovalForAll(msg.sender,_operator,_approved);
    }

    function transferFrom(address _from, address _to, uint256 _tokenId) override  external payable{
        _transferFrom(_from,_to,_tokenId);
    }

    function _transferFrom(address _from, address _to, uint256 _tokenId)internal canTransfer(_tokenId,_from){
        ercTokenOwner[_tokenId] = _to;      //更改属主;
        ercTokenCount[_from]-=1;
        ercTokenCount[_to]+=1;
        ercTokenApproved[_tokenId] = address(0);
        emit Transfer(_from, _to,  _tokenId);
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) override external payable{
        _safeTransferFrom(_from,_to,_tokenId,data);
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId)override external payable{
        _safeTransferFrom(_from,_to,_tokenId,"");
    }

    function _safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) internal { //安全转账即判断对方是否有资格持有ERCtoken,避免转错
        _transferFrom(_from, _to, _tokenId);

        //add safe code
        if(_to.isContract()){           //首先判断对方是否是合约
            bytes4 retval = ERC721TokenReceiver(_to).onERC721Received(msg.sender,_from,_tokenId,data);   //是否实现了ERC721Received
            require(retval == ERC721TokenReceiver.onERC721Received.selector);
        }
    }

    function mint(address _to,uint256 _tokenId,bytes memory data)external{
        require(_to != address(0));
        require(ercTokenOwner[_tokenId]==address(0));
         
        ercTokenOwner[_tokenId]= _to;
        ercTokenCount[_to]+=1;

        if(_to.isContract()){           //首先判断对方是否是合约
            bytes4 retval = ERC721TokenReceiver(_to).onERC721Received(msg.sender,address(0),_tokenId,data);   //是否实现了ERC721Received
            require(retval == ERC721TokenReceiver.onERC721Received.selector);
        }

        emit Transfer(address(0),_to,_tokenId);
    }

}

注意事项:

  • 引用Address识别地址,在Solidity合约中需要了解一个地址是普通钱包地址还是合约地址。 OpenZeppelin的Address库提供了一个方法isContract()可以帮我们解决这个问题。
  • safeTransferFrom()要转移NFT所有权,如果_to是一个合约,那么它必须实现ERC721TokenReceiver接口。
  • 这里使用了modifier关键字,对转账和授权进行了判断处理。
     
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值