ERC721

简介

非同质化token (NFT,Non-Fungible Token)
通常使用在艺术品、房子、虚拟收藏品等不可替代的物品中。

最小单元

9个函数,3个事件

// 获取分配给_owner的所有NFTs的数量
function balanceOf(address _owner) external view returns (uint256);
// 查询拥有token ID号为_tokenId的NFT所属者的地址
function ownerOf(uint256 _tokenId) external view returns (address);
// 将token ID号为_tokenId 的NFT 从_form 转给 _to,调用者需是NFT拥有者或者是被授权能操纵此NFT的人
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
// 同transferFrom,但转账双方地址不能为合约地址。 带data
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
// 同transferFrom,但转账双方地址不能为合约地址。不带data
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
// 授权,调用者是_tokenId的拥有者,授权给_approved。授权后_approved可以自行调用transferFrom函数将此tokenId的NFT转到自己账户
function approve(address _approved, uint256 _tokenId) external payable;
// 查看token ID号为 _tokenId的NFT被授权给了哪个地址
function getApproved(uint256 _tokenId) external view returns (address);
// 调用者 授权给第三方_operator进行管理个人的erc721代币token资产 
function setApprovalForAll(address _operator, bool _approved) external;
// 查看_owner 是否授权给了_operator 管理所有的erc721代币token资产
function isApprovedForAll(address _owner, address _operator) external view returns (bool);

授权的场景可以理解为:我授权给第三方去中心化交易所。然后挂上价钱。当有人买时,交易所调用我的 transferFrom函数然后卖给别人,我收到钱。

// NFTs资产转移事件
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
// NFTs资产授权地址发生变更事件
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
// 某owner所有NFTs资产被第三方授权的事件
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);
// 由tokenId获取NFT详细信息
function tokenURI(uint256 _tokenId) external view returns (string);

官方ERC721.sol代码

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    // 代币名称
    string private _name;
    // 代币缩写
    string private _symbol;
    // token ID --> owner address
    mapping(uint256 => address) private _owners;
    // owner address --> token count
    mapping(address => uint256) private _balances;
    // token ID --> approved address
    mapping(uint256 => address) private _tokenApprovals;
    // owner address --> operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;
	// 初始化token,设置name和symbol
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

	// 判断合约是否实现了接口id为interfaceId的接口 
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

	// 返回owner拥有的代币总数量
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }

	// 传入token id, 判断属于谁,返回一个地址
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: invalid token ID");
        return owner;
    }

   	// 查看token名称
    function name() public view virtual override returns (string memory) {
        return _name;
    }

	// 查看token缩称
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    // 传入token id,查看此id的详细信息,比如一个图片的URL
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

 	// 是用于计算tokenURI的基础URI,默认为空
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

	// 给出tokenId,调用者将其 授权给 to 
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner nor approved for all"
        );
        _approve(to, tokenId);
    }

	// 给出tokenId,判断其授权给谁了
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }

	// 调用者将自己的所有token 授权给 第三方operator (approved为true)false为取消授权
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

	// 判断 owner 是否把自己的所有token授权给operator了
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

	// from 给 to 转移 token
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        // 判断调用者是否被授权tokenId
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");

        _transfer(from, to, tokenId);
    }

	// 安全转移tokenId,两方地址不能为合约
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

	// 安全转移tokenId,两方地址不能为合约,且带data数据
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
        _safeTransfer(from, to, tokenId, data);
    }
	// 
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }

	// 判断某个tokenId 是否存在
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

	// 判断spender是否有权管理tokenId
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

	// 安全的铸造tokenId,并将其转移到 to
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

	// 还是铸造tokenId,转移给 to,多了个data 
    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

 	// 铸币的具体实现,一般不直接调用此方法,而是调用_safeMint
    function _mint(address to, uint256 tokenId) internal virtual {
    	// 转移的地址不能是0地址、新生成的tokenId不能已存在
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);
		// 维护全局状态
        _balances[to] += 1;
        _owners[tokenId] = to;
		// 触发转账事件
        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId);
    }

	// 销毁代币的具体实现
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // 清理所有此tokenId的授权
        _approve(address(0), tokenId);
		// 维护全局状态
        _balances[owner] -= 1;
        delete _owners[tokenId];
		// 触发转账事件,销毁就是转向0地址
        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId);
    }

	// 转账的具体实现
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
    	// tokenId需要属于from
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
       	// 转向的地址不能是0地址
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // 此tokenId转走时,清理掉之前的所有授权
        _approve(address(0), tokenId);
		// 维护全局状态
        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;
		// 触发转账事件
        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId);
    }

	// 授权的具体实现
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        // 触发授权事件
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

	// 授权owner所有token的具体实现
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
    	// 授权的第三方不能是自己
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        // 触发全部授权事件
        emit ApprovalForAll(owner, operator, approved);
    }

	// 如果此tokenId还不存在,重新去挖
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

	// 检查是否是合约地址等..
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

	// 
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

	// 
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}

发行

只需要编写一个编写一个 contract 继承自 ERC721URIStorage
ERC721URIStorage 是一个继承自 ERC721.sol 的抽象contract。

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/extensions/ERC721URIStorage.sol)

pragma solidity ^0.8.0;

import "../ERC721.sol";

/**
 * @dev ERC721 token with storage based token URI management.
 */
abstract contract ERC721URIStorage is ERC721 {
    using Strings for uint256;

    // Optional mapping for token URIs
    mapping(uint256 => string) private _tokenURIs;

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }

        return super.tokenURI(tokenId);
    }

    /**
     * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }

    /**
     * @dev See {ERC721-_burn}. This override additionally checks to see if a
     * token-specific URI was set for the token, and if so, it deletes the token URI from
     * the storage mapping.
     */
    function _burn(uint256 tokenId) internal virtual override {
        super._burn(tokenId);

        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }
    }
}

我们编写的合约:(包含一个自己写的 awardItem 方法,用于调用父类的 mint 币方法)

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract GameItem is ERC721URIStorage {
    using Counters for Counters.Counter;
    // 统计已经mint的币数量
    Counters.Counter private _tokenIds;
	// 调用父合约 ERC721 的构造方法
    constructor() ERC721("LmhNFT", "mhNFT") {}

    // 生成NFT
    function awardItem(address player, string memory tokenURI) public returns (uint256) {
        // nft编号从0开始,获取当前nft数量作为新的nft的编号
        uint256 newItemId = _tokenIds.current();
        // 造币
        _mint(player, newItemId);
        // 设置nft对应的URL
        _setTokenURI(newItemId, tokenURI);
        // 已经造的币数++
        _tokenIds.increment();
        return newItemId;
    }
}

通过 remix 直接编译并部署:(我是连接到了 goerli 测试网)

在这里插入图片描述

小狐狸弹出信息,pay 过gas费后。部署成功,在测试网查看:

在这里插入图片描述

调用方法 awardItem 用来给指定的地址生成 nft

在这里插入图片描述

调用后,发送transaction,在测试网查看信息:

在这里插入图片描述

然后在小狐狸中导入nft,输入部署的nft合约地址,就会自动识别出nft名称。导入即可。如下所示,刚才铸币时在此地址造了一个nft,显示 1 mhNFT。

在这里插入图片描述

再发送一笔transaction,再铸造一个代币

在这里插入图片描述

在这里插入图片描述

调用转移代币方法 safeTransferFrom :

在这里插入图片描述

小狐狸弹出信息框确认一下:

在这里插入图片描述

然后在测试网查看transaction:

在这里插入图片描述

转移过后:

在这里插入图片描述
在这里插入图片描述

部署的合约中还有很多方法都可以调用,这里不再一 一测试。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

henulmh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值