NFT初学者教程
百事可乐的NFT系列
百事可乐在2021年12月投放了其创世的NFT集合(来源),直到今天,这个项目的交易量为2.3k ETH(560万美元)。我仔细研究了百事可乐的NFT合约,以了解他们是如何实施这个项目的。在本教程中,我将逐条解释该智能合约。
谁是目标受众?
如果你是NFT的新手,想学习如何自己开发一个NFT智能合约,这篇文章就适合你。如果你好奇NFT背后的技术是如何运作的,这篇文章也会有帮助。
前提条件
如果你了解ERC-721并且有编码经验,那么你可以跳过这一部分。
- Solidity。到目前为止,大多数NFT合约都使用Solidity。你不需要知道Solidity来理解这个概念。但是如果你想在之后自己实现一个NFT合同,一些编码经验将是非常有帮助的。要学习Solidity,我强烈建议阅读官方文件。
- ERC-721:百事通NFT是建立在Open Zeppelin实现的ERC-721智能合约上。简而言之,ERC-721标准提供了NFT的基本功能,包括转账、检查账户余额、造币等。这篇文章给出了一个很好的概述。ERC-721不可伪造代币标准。
免责声明
像很多人一样,我是这个行业的新手。我试图仔细检查内容的正确性,但可能会出现错误。如果你发现任何错误,请让我知道,我将非常感谢你的帮助和更新本教程。
智能合约在哪里?
许多项目会将他们的合约开源,人们可以在做出任何(投资)决定之前得到教育。透明度也是区块链行业中我最喜欢的功能之一。
百事可乐在官网上公布了它的NFT合约地址。你可以简单地复制该地址并在Etherscan中搜索,Etherscan是查询以太坊区块链信息的首选之地。然后你会看到以下页面。
图1 Etherscan合约页面的屏幕截图
点击Contract 按钮,你会看到14个文件。然而,我们只需要第一个文件,PepsiMicDrop.sol 。这个文件包含了Pepsi NFT合约。其余的是Open Zeppelin的ERC-721合约,我们将不涉及。
为了你的方便,我把代码放在GitHub上,你可以在这里找到它。我建议你把代码和这篇文章并排打开。
合同中的NFT
在深入研究代码之前,首先让我们了解一下智能合约是如何定义NFT的。在引擎盖下,一个NFT有两部分信息。(1) Id (2) URI。
根据ERC-721标准,每个NFT都是唯一的。如何做到的?合同为每个NFT分配了一个唯一的Id,从而实现了唯一性。在一个基于ERC-721的合同中,你不会发现两个不同的NFT有相同的Id。
那么,图像(也可以是视频、文件等)在哪里?这就是URI发挥作用的地方。URI是通用资源标识符的意思。把它想象成一个URL,通过它你可以得到与NFT相关的所有元数据。元数据可以是图像URL、描述、属性等。目前最流行的NFT市场OpenSea有其元数据标准。你可以找到一个全面的元数据列表。
现在,你可能会问:URI和Id能确保NFT的唯一性吗?
理想情况下,基于ERC-721的NFT将有其独特的图像或元数据,这就是为什么它们是不可伪造的。然而,你可以将相同的URI分配给不同的Id,从而创造出两个从表面上看起来相同的NFT。然而,这不是ERC-721的目的。有另一个标准,ERC-1155,它支持半风化的NFT。你可以在这里了解更多。
此外,你可能想知道。为什么元数据不存储在合同中?因为在区块链上存储数据是超级昂贵的,尤其是图片或视频。然而,你仍然可以在链上存储元数据,并支付高额的气体费用。一些项目使用SVG格式的图片,这大大减少了数据的大小,从而降低了气体费用。然而,这已经超出了本教程的范围。如果你想了解更多关于基于SVG的NFT,请告诉我。
合同结构
合同乍看之下可能很复杂,但它组织得很好。图2直观地展示了这个合同的结构。让我们来看看每个组成部分。
图2 百事可乐NFT合同的结构
- SPDX许可标识符 是注释后的第一行。它表明其他人可以如何使用这段代码。(第48行)
- Solidity Version让编译器正确翻译代码,然后EVM可以理解。(第49行)
- 导入ERC-721合约 ,那么Pepsi NFT合约将使用ERC-721合约作为其蓝图。
- PepsiMicDrop是我们将在下一节进一步讨论的NFT合约。它有三个子部分。(1)状态变量 (2)构造器 (3)函数。
- 状态变量是变量,其值被永久地保存在合约存储中。
- 构造器是一个特殊的函数,只在合约创建时执行。你可以运行合约的初始化代码。
- 函数是不言自明的。大多数函数是用来设置或获取状态值的。
PepsiMicDrop合约
让我们来看看PepsiMicDrop 契约。我将按照合同的功能来解释,每个功能都与一些函数和状态变量有关。
(1) 构造函数
constructor() ERC721("Pepsi Mic Drop", "PEPSIMICDROP") {
reserveMicDropsId = 1; // item 1-50
micDropsId = 51; // item 51-1893
}
构造函数需要2个输入。
- "Pepsi Mic Drop "是NFT代币的名称
- "PEPSIMICDROP "是代币符号。
赋值发生在ERC721合约代码中,该合约在第51行导入。这就是为什么你找不到与这个构造函数体有关的东西。这就是继承的魅力,我们不需要重做它。
在构造函数内部,我们可以看到2个状态变量得到了新的值,reserveMicDropsId ,和micDropsId 。为什么需要它们?在这个NFT投放中,百事可乐把前50个NFT留给了自己,所以公开的NFT从id 51开始。
等等,NFT不是一个图像吗?为什么他们在这里用数字来代表NFT?如果你有这些问题,我是和你一起的。为了理解这一点,我们需要研究一下什么是NFT。
(2) Mint
现在让我们来谈谈造币厂的问题。
function mint(bytes32[] memory proof, bytes32 leaf) public returns (uint256) {
// merkle tree
if (merkleEnabled) {
require(keccak256(abi.encodePacked(msg.sender)) == leaf, "This leaf does not belong to the sender");
require(proof.verify(merkleRoot, leaf), "You are not in the list");
}
require(saleStarted == true, "The sale is paused");
require(msg.sender != address(0x0), "Public address is not correct");
require(alreadyMinted[msg.sender] == false, "Address already used");
require(micDropsId <= maxMint, "Mint limit reached");
_safeMint(msg.sender, micDropsId++);
alreadyMinted[msg.sender] = true;
return micDropsId;
}
第一部分 - 默克尔证明
造币厂函数有两个输入,证明,和叶子。它们被用于第3至第7行的Merkle Proof。本质上,它检查一个用户是否有资格为NFT造币。有一个概念叫**白名单。**有时你必须进入这个白名单,以便以后能够铸造NFT。我不会解释这一部分,因为有人已经做得很好了。请查看这篇文章,它对Merkle Proof进行了全面的解释。
第二部分 - 先决条件
下一部分,第8至11行,定义了造币的4个先决条件。让我们逐一看一下。
(1) 在NFT开始销售之前,状态变量saleStarted 被设置为false。所以它将不会通过第8行的检查。当销售开始时,合同所有者可以调用函数startSale() 来改变saleStarted 的值。
require(saleStarted == true, "The sale is paused");
(2) 这个检查确保了用户地址不是0x0。0x0是Ethereum的创世地址,没有用户会使用它。我也不确定为什么这个检查是必要的。如果你理解的话,请告诉我。
require(msg.sender != address(0x0), "Public address is not correct");
(3) 在Pepsi Drop中,每个地址只能铸造一个NFT。alreadyMinted 是一个映射类型的状态变量,就像一个字典,它记录了所有铸造NFT的地址。
require(alreadyMinted[msg.sender] == false, "Address already used");
(4) 供应有限,最多1983个NFTs。这将检查是否所有的NFT都已被认领。micDropsId 是我们上面讨论的唯一的token id。
require(micDropsId <= maxMint, "Mint limit reached");
很简单,对吗?而我们几乎已经完成了,挂在那里。
第三部分。实际的造币。
这是造币实际发生的地方。好消息是我们不需要实现它,因为ERC-721已经做到了这一点。
_safeMint(msg.sender, micDropsId++);
为了造币,我们用用户的钱包地址和唯一的代币ID调用函数_safemint() ,就这样。在引擎盖下,有一个状态变量(像一个字典)来跟踪每个代币的所有权。通过调用_safemint() 函数,我们更新这个状态变量,分配或改变相应代币的所有权。
这里有一点要注意,状态变量micDropId 代表代币的ID,它 ,在每次造币前都会递增1。
第四部分:更新铸币地址
正如第二部分中提到的,每个地址只能铸造一个NFT。
alreadyMinted[msg.sender] = true;
这一行确保成功铸造NFT的人被记录下来。
第5部分:返回
铸币后,代币ID被返回到前端。
(3) 更新URI
我们需要讨论的最后一部分是更新URI的函数。
function setBaseURI(string memory _baseUri) public onlyOwner {
baseURI = _baseUri;
}
正如你已经知道的,每个NFT都有其URI。而这是用来改变基础URI的函数。
基本URI是每个NFT的URI之间的相互部分。默认情况下,URI是baseURI/tokenId 。我们设置baseURI的原因是,它可以节省汽油费。想象一下,如果你为每个NFT设置URI,那是非常昂贵的。
你可以改变如何结合baseURI 和tokenId ,通过覆盖函数 **tokenURI** 定义在ERC-721合同中。例如,如果URI的格式是这样的,你可以做一些类似baseURI/tokenId + ‘.json’ 。
你可能想知道,什么是 **public** 和 **onlyOwner**.这些是函数修饰符,它定义了一个函数运行的条件。public 意味着 "在合同之外"。部署后,我们通过网站(更确切地说,是JavaScript)与合同互动,这就是public的意思。onlyOwner 意味着只有合同所有者可以调用这个函数。当然,我们只希望合同拥有者有能力改变令牌URI。
(4) 其他部分
揭开的部分只是获取或设置状态变量的函数,我相信你不需要我的帮助。
结束
那么,就这样了。谢谢你阅读这篇文章。我希望你觉得它对你有帮助。