NFT新手铸造指南

廖雪峰 / 文章 / ... / Reads: 37187 Edit

NFT最近炒作非常火热,许多同学都听说OpenSea上一个猴子卖出了几千甚至上万美元。但是到底怎么铸造NFT,怎么在OpenSea售卖,却很少有文章讲。

本文将从零开始讲解如何铸造NFT并在OpenSea出售。

首先我们讲讲什么是NFT。NFT是Non-Fungible Token(非同质化代币)的缩写。同质化代币就是BTC、ETH等,即张三手中的一个BTC和李四手中的一个BTC是完全等价的,而NFT则不然,张三手中的一个NFT和李四手中的一个NFT无法等价交换。最早的NFT是由加密猫搞出来的,所有的加密猫都是由同一个合约发行的,但每个猫都不一样,因此,每个猫都有唯一的Token ID,这就是NFT的特点:每个NFT都有一个唯一标识。

不过,需要注意的是,所谓唯一标识,仅仅指在同一个合约中发行的NFT,他们的Token ID都是唯一的,不同的合约发行的NFT,很可能有相同的Token ID。因此,一个NFT真正的唯一标识,实际上是合约地址+Token ID。

在OpenSea中,一个Collection,例如CryptoPunks,就是一个合约发出的所有NFT。所以,要发行一个Collection,首先要创建一个合约,然后,用这个合约发行的所有NFT就自动被归集到这个Collection中。

NFT有两个标准:EIP-721EIP-1155。721标准比较简单,它只需要保证一个NFT对应一个唯一的Token ID就行,因为一个Token ID对应的NFT只有一个,而1155就要宽松一点,一个NFT可以有多个,比如一个头像有10个,那最多允许10个人持有。如果每个NFT只发1个,那就和721没啥区别了。

所以我们只需要支持1155,就相当于兼容了721。1155的NFT接口主要有以下几个:

  • 根据一个Token ID返回Metadata的URL:uri(uint256 id)
  • 查询一个地址拥有的Token ID数量:balanceOf(address account, uint256 id)
  • 授权或取消授权一个地址有权转移NFT:setApprovalForAll(address operator, bool approved)
  • 转移一个NFT:safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data)

EIP-1155定义的接口和实现都可以在OpenZeppelin上找到,我们只需要在ERC1155的基础上修改即可。ERC1155的核心代码其实就是一个映射,记录Token ID到持有地址、再到持有数量:

contract ERC1155 {
    // Mapping from token ID to account balances
    mapping(uint256 => mapping(address => uint256)) private _balances;
}

我们做的主要修改是增加一个Token ID到URL的映射。因为我们准备将NFT的图片和Metadata数据都放到IPFS上,所以增加一个Token ID到IPFS文件哈希的映射:

contract ERC1155 {
    mapping(uint256 => string) private _metadataHashes;
    string private _uriPrefix = "https://ipfs.infura.io/ipfs/";

    // 返回"https://ipfs.infura.io/ipfs/QmasWH...re2Ych?filename=metadata.json"
    // 如果使用服务器API返回则可以固定uri为"https://api.example.com/metadata/{id}"
    function uri(uint256 id) public view returns (string memory) {
        return _concat(_uriPrefix, _metadataHashes[id], "?filename=metadata.json");
    }
}

第二个修改是增加一个mint()方法来铸造NFT:

function mint(uint256 amount, string memory metadataHash) public returns (uint256) {
    // 如果只允许合约部署者铸造,加上判断:
    // require(msg.sender == owner, "Not contract owner");
    nextTokenId++;
    uint256 tokenId = nextTokenId;
    _metadataHashes[tokenId] = metadataHash;
    _mint(msg.sender, tokenId, amount, "");
    return tokenId;
}

最后一步是在isApprovedForAll()中判断下当前转移操作的发起者是不是OpenSea的代理合约:

function isApprovedForAll(address account, address operator) public view returns (bool) {
    // Whitelist OpenSea proxy contract for easy trading.
    ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
    if (address(proxyRegistry.proxies(account)) == operator) {
        return true;
    }
    return _operatorApprovals[account][operator];
}

这么做的目的是将来在OpenSea售卖的时候,不需要授权操作,节省了gas费,缺点是无条件信任了OpenSea的代理合约,降低了一点安全性。

NFT铸造流程

理解NFT的铸造流程是非常重要的。首先,一个NFT关联了一个特定的资源,如图片、视频、3D模型、VR等。假定我们的NFT就是一个图片,铸造NFT的第一步是将图片上传并获得一个固定的URL。这里我们选择IPFS,上传成功后返回的URL类似:

https://ipfs.infura.io/ipfs/QmaCR37BEGv6aZzzfJ1ShT8tu52UWosVgN9ookYY94FVVt?filename=file.png

第二步是准备NFT的Metadata,也就是NFT的描述信息。标准的Metadata就是一个JSON文件,内容如下:

{
    "name": "EIP-1155 based NFT",
    "description": "Public Mintable EIP-1155 based NFT",
    "external_url": "https://michaelliao.github.io/simple-nft/",
    "image": "https://ipfs.infura.io/ipfs/QmaCR37BEGv6aZzzfJ1ShT8tu52UWosVgN9ookYY94FVVt?filename=file.png",
    "attributes": [
        {
            "trait_type": "Type",
            "value": "EIP-1155"
        },
        {
            "trait_type": "Author",
            "value": "Crypto Michael"
        }
    ]
}

attributes就是NFT的属性,有多少个就往里填多少个。JSON文件也需要一个URL,可以用服务器的API返回,也可以直接上传到IPFS拿到一个URL,这个JSON的URL就是NFT的Metadata的URL,也是合约方法uri(uint256 id)返回的URL。

最后一步,我们调用mint()方法并传入NFT的Metadata的IPFS哈希,就完成了一个NFT的铸造!

铸造后默认的持有人是铸造者本人。如果切换到OpenSea并以铸造者身份登录,就可以看到我们刚铸造出的NFT:

nft

那么问题来了:OpenSea是如何知道我们铸造的NFT并且获得了NFT的图片以及相关信息?

因为我们在铸造的时候,mint()方法写入了一条NFT转移日志,该日志记录了NFT的Token ID、数量和所有者,OpenSea读取链上的日志就可以知道该地址拥有了新铸造的NFT以及NFT的ID和数量。

紧接着,OpenSea通过调用合约方法uri(uint256 id)可以获得Metadata的URL,读取该JSON后,通过JSON文件内的"image":"https://..."可以获取到NFT对应的图片URL,这样,根据一个合约地址和Token ID,可以获得一个NFT的所有信息。

最后,我们还需要编写一个页面,能把上传图片、设置Metadata、上传Metadata、调用合约mint()方法一键完成。可以参考网页https://michaelliao.github.io/simple-nft/

mint-nft-page

有的同学已经看出来了,一个NFT除了Token ID和数量写在合约里不能变以外,返回的URL虽然链接是固定的,但是完全可以控制该URL以便返回修改后的Metadata,所以Metadata的内容是完全可以修改的,它对应的图片也是完全可以修改的,因此,所谓NFT不可修改,仅仅指Token ID和数量不能修改,NFT背后对应的元数据和资源文件都是可修改的,会不会修改完全看开发者的人品,并且元数据的URL也是有可能失效的。

最后我们总结一下发行NFT的5个步骤:

  1. 编写并部署一个1155合约;
  2. 上传资源文件(例如图片);
  3. 上传Metadata文件;
  4. 写入合约铸造;
  5. 在OpenSea售卖。

Comments

Make a comment

Author: 廖雪峰

Publish at: ...

关注公众号不定期领红包:

加入知识星球社群:

关注微博获取实时动态: