创建一个ERC721标准的NFT

打开CHAINPIP社区,进入应用,创建一个新应用;



将模板代码删除,上传本次测试的合约代码。



这是一个符合ERC721标准的NFT合约代码,其中包括了几个常用的标准接口和合约,下面是个合约的具体信息:

IERC65.sol

pragma solidity ^0.8.0;


/**

 * @dev ERC165标准接口, 详见

 * https://eips.ethereum.org/EIPS/eip-165[EIP].

 *

 * 合约可以声明支持的接口,供其他合约检查

 *

 */

interface IERC165 {

    /**

     * @dev 如果合约实现了查询的`interfaceId`,则返回true

     * 规则详见:https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]

     *

     */

    function supportsInterface(bytes4 interfaceId) external view returns (bool);

}


IERC721.sol

// SPDX-License-Identifier: MIT


pragma solidity ^0.8.0;


import "./IERC165.sol";


/**

 * @dev ERC721标准接口.

 */

interface IERC721 is IERC165 {

    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 balanceOf(address owner) external view returns (uint256 balance);


    function ownerOf(uint256 tokenId) external view returns (address owner);


    function safeTransferFrom(

        address from,

        address to,

        uint256 tokenId,

        bytes calldata data

    ) external;


    function safeTransferFrom(

        address from,

        address to,

        uint256 tokenId

    ) external;


    function transferFrom(

        address from,

        address to,

        uint256 tokenId

    ) external;


    function approve(address to, uint256 tokenId) external;


    function setApprovalForAll(address operator, bool _approved) external;


    function getApproved(uint256 tokenId) external view returns (address operator);


    function isApprovedForAll(address owner, address operator) external view returns (bool);

}

IERC721Receiver.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;


// ERC721接收者接口:合约必须实现这个接口来通过安全转账接收ERC721

interface IERC721Receiver {

    function onERC721Received(

        address operator,

        address from,

        uint tokenId,

        bytes calldata data

    ) external returns (bytes4);

}


IERC721Metadata.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;


interface IERC721Metadata {

    function name() external view returns (string memory);


    function symbol() external view returns (string memory);


    function tokenURI(uint256 tokenId) external view returns (string memory);

}


Strings.sol

// SPDX-License-Identifier: MIT

// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)


pragma solidity ^0.8.4;


/**

 * @dev String operations.

 */

library Strings {

    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    uint8 private constant _ADDRESS_LENGTH = 20;


    /**

     * @dev Converts a `uint256` to its ASCII `string` decimal representation.

     */

    function toString(uint256 value) internal pure returns (string memory) {

        // Inspired by OraclizeAPI's implementation - MIT licence

        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol


        if (value == 0) {

            return "0";

        }

        uint256 temp = value;

        uint256 digits;

        while (temp != 0) {

            digits++;

            temp /= 10;

        }

        bytes memory buffer = new bytes(digits);

        while (value != 0) {

            digits -= 1;

            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));

            value /= 10;

        }

        return string(buffer);

    }


    /**

     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.

     */

    function toHexString(uint256 value) internal pure returns (string memory) {

        if (value == 0) {

            return "0x00";

        }

        uint256 temp = value;

        uint256 length = 0;

        while (temp != 0) {

            length++;

            temp >>= 8;

        }

        return toHexString(value, length);

    }


    /**

     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.

     */

    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {

        bytes memory buffer = new bytes(2 * length + 2);

        buffer[0] = "0";

        buffer[1] = "x";

        for (uint256 i = 2 * length + 1; i > 1; --i) {

            buffer[i] = _HEX_SYMBOLS[value & 0xf];

            value >>= 4;

        }

        require(value == 0, "Strings: hex length insufficient");

        return string(buffer);

    }


    /**

     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.

     */

    function toHexString(address addr) internal pure returns (string memory) {

        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);

    }

}


Address.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.1;


// Address库

library Address {

    // 利用extcodesize判断一个地址是否为合约地址

    function isContract(address account) internal view returns (bool) {

        uint size;

        assembly {

            size := extcodesize(account)

        }

        return size > 0;

    }

}


ERC721.sol

// SPDX-License-Identifier: MIT

// by 0xAA

pragma solidity ^0.8.4;


import "./IERC165.sol";

import "./IERC721.sol";

import "./IERC721Receiver.sol";

import "./IERC721Metadata.sol";

import "./Address.sol";

import "./String.sol";


contract ERC721 is IERC721, IERC721Metadata{

    using Address for address; // 使用Address库,用isContract来判断地址是否为合约

    using Strings for uint256; // 使用String库,


    // Token名称

    string public override name;

    // Token代号

    string public override symbol;

    // tokenId 到 owner address 的持有人映射

    mapping(uint => address) private _owners;

    // address 到 持仓数量 的持仓量映射

    mapping(address => uint) private _balances;

    // tokenID 到 授权地址 的授权映射

    mapping(uint => address) private _tokenApprovals;

    //  owner地址。到operator地址 的批量授权映射

    mapping(address => mapping(address => bool)) private _operatorApprovals;


    /**

     * 构造函数,初始化`name` 和`symbol` .

     */

    constructor(string memory name_, string memory symbol_) {

        name = name_;

        symbol = symbol_;

    }


    // 实现IERC165接口supportsInterface

    function supportsInterface(bytes4 interfaceId)

        external

        pure

        override

        returns (bool)

    {

        return

            interfaceId == type(IERC721).interfaceId ||

            interfaceId == type(IERC165).interfaceId ||

            interfaceId == type(IERC721Metadata).interfaceId;

    }


    // 实现IERC721的balanceOf,利用_balances变量查询owner地址的balance。

    function balanceOf(address owner) external view override returns (uint) {

        require(owner != address(0), "owner = zero address");

        return _balances[owner];

    }


    // 实现IERC721的ownerOf,利用_owners变量查询tokenId的owner。

    function ownerOf(uint tokenId) public view override returns (address owner) {

        owner = _owners[tokenId];

        require(owner != address(0), "token doesn't exist");

    }


    // 实现IERC721的isApprovedForAll,利用_operatorApprovals变量查询owner地址是否将所持NFT批量授权给了operator地址。

    function isApprovedForAll(address owner, address operator)

        external

        view

        override

        returns (bool)

    {

        return _operatorApprovals[owner][operator];

    }


    // 实现IERC721的setApprovalForAll,将持有代币全部授权给operator地址。调用_setApprovalForAll函数。

    function setApprovalForAll(address operator, bool approved) external override {

        _operatorApprovals[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);

    }


    // 实现IERC721的getApproved,利用_tokenApprovals变量查询tokenId的授权地址。

    function getApproved(uint tokenId) external view override returns (address) {

        require(_owners[tokenId] != address(0), "token doesn't exist");

        return _tokenApprovals[tokenId];

    }


    // 授权函数。通过调整_tokenApprovals来,授权 to 地址操作 tokenId,同时释放Approval事件。

    function _approve(

        address owner,

        address to,

        uint tokenId

    ) private {

        _tokenApprovals[tokenId] = to;

        emit Approval(owner, to, tokenId);

    }


    // 实现IERC721的approve,将tokenId授权给 to 地址。条件:to不是owner,且msg.sender是owner或授权地址。调用_approve函数。

    function approve(address to, uint tokenId) external override {

        address owner = _owners[tokenId];

        require(

            msg.sender == owner || _operatorApprovals[owner][msg.sender],

            "not owner nor approved for all"

        );

        _approve(owner, to, tokenId);

    }


    // 查询 spender地址是否被可以使用tokenId(他是owner或被授权地址)。

    function _isApprovedOrOwner(

        address owner,

        address spender,

        uint tokenId

    ) private view returns (bool) {

        return (spender == owner ||

            _tokenApprovals[tokenId] == spender ||

            _operatorApprovals[owner][spender]);

    }


    /*

     * 转账函数。通过调整_balances和_owner变量将 tokenId 从 from 转账给 to,同时释放Tranfer事件。

     * 条件:

     * 1. tokenId 被 from 拥有

     * 2. to 不是0地址

     */

    function _transfer(

        address owner,

        address from,

        address to,

        uint tokenId

    ) private {

        require(from == owner, "not owner");

        require(to != address(0), "transfer to the zero address");


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


        _balances[from] -= 1;

        _balances[to] += 1;

        _owners[tokenId] = to;


        emit Transfer(from, to, tokenId);

    }


    // 实现IERC721的transferFrom,非安全转账,不建议使用。调用_transfer函数

    function transferFrom(

        address from,

        address to,

        uint tokenId

    ) external override {

        address owner = ownerOf(tokenId);

        require(

            _isApprovedOrOwner(owner, msg.sender, tokenId),

            "not owner nor approved"

        );

        _transfer(owner, from, to, tokenId);

    }


    /**

     * 安全转账,安全地将 tokenId 代币从 from 转移到 to,会检查合约接收者是否了解 ERC721 协议,以防止代币被永久锁定。调用了_transfer函数和_checkOnERC721Received函数。条件:

     * from 不能是0地址.

     * to 不能是0地址.

     * tokenId 代币必须存在,并且被 from拥有.

     * 如果 to 是智能合约, 他必须支持 IERC721Receiver-onERC721Received.

     */

    function _safeTransfer(

        address owner,

        address from,

        address to,

        uint tokenId,

        bytes memory _data

    ) private {

        _transfer(owner, from, to, tokenId);

        require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver");

    }


    /**

     * 实现IERC721的safeTransferFrom,安全转账,调用了_safeTransfer函数。

     */

    function safeTransferFrom(

        address from,

        address to,

        uint tokenId,

        bytes memory _data

    ) public override {

        address owner = ownerOf(tokenId);

        require(

            _isApprovedOrOwner(owner, msg.sender, tokenId),

            "not owner nor approved"

        );

        _safeTransfer(owner, from, to, tokenId, _data);

    }


    // safeTransferFrom重载函数

    function safeTransferFrom(

        address from,

        address to,

        uint tokenId

    ) external override {

        safeTransferFrom(from, to, tokenId, "");

    }


    /**

     * 铸造函数。通过调整_balances和_owners变量来铸造tokenId并转账给 to,同时释放Tranfer事件。铸造函数。通过调整_balances和_owners变量来铸造tokenId并转账给 to,同时释放Tranfer事件。

     * 这个mint函数所有人都能调用,实际使用需要开发人员重写,加上一些条件。

     * 条件:

     * 1. tokenId尚不存在。

     * 2. to不是0地址.

     */

    function _mint(address to, uint tokenId) internal virtual {

        require(to != address(0), "mint to zero address");

        require(_owners[tokenId] == address(0), "token already minted");


        _balances[to] += 1;

        _owners[tokenId] = to;


        emit Transfer(address(0), to, tokenId);

    }


    // 销毁函数,通过调整_balances和_owners变量来销毁tokenId,同时释放Tranfer事件。条件:tokenId存在。

    function _burn(uint tokenId) internal virtual {

        address owner = ownerOf(tokenId);

        require(msg.sender == owner, "not owner of token");


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


        _balances[owner] -= 1;

        delete _owners[tokenId];


        emit Transfer(owner, address(0), tokenId);

    }


    // _checkOnERC721Received:函数,用于在 to 为合约的时候调用IERC721Receiver-onERC721Received, 以防 tokenId 被不小心转入黑洞。

    function _checkOnERC721Received(

        address from,

        address to,

        uint tokenId,

        bytes memory _data

    ) private returns (bool) {

        if (to.isContract()) {

            return

                IERC721Receiver(to).onERC721Received(

                    msg.sender,

                    from,

                    tokenId,

                    _data

                ) == IERC721Receiver.onERC721Received.selector;

        } else {

            return true;

        }

    }


    /**

     * 实现IERC721Metadata的tokenURI函数,查询metadata。

     */

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {

        require(_owners[tokenId] != address(0), "Token Not Exist");


        string memory baseURI = _baseURI();

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

    }


    /**

     * 计算{tokenURI}的BaseURI,tokenURI就是把baseURI和tokenId拼接在一起,需要开发重写。

     * BAYC的baseURI为ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/

     */

    function _baseURI() internal view virtual returns (string memory) {

        return "";

    }

}


编译、部署合约,设置NFT名称为HHTest,符号为HH。



合约部署成功后,进入ABI操作页面,开始测试合约的mint函数,测试铸造一个NFT。



铸造完成后使用合约地址在opensea的测试链版中查询,可以发现我们刚刚铸造的NFT可以在其中查询出来,由于合约用的是无聊猿的tokenURI地址,所以显示的是无聊猿的信息。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342