本文整理出了,NFT 合约开发过程中,优秀且常见的开发模式
最近开发了不少 NFT 合约, 之前一直想总结一篇文章介绍 NFT 开发中的各种技巧,奈何总是各种事情没有动手,今天看到老外的这篇总结,非常全面,就翻译一下。以下是原文翻译:
我读过很多NFT合约,以下是我在最好的合约中看到的最常见的开发模式:
首先,简单介绍一下背景,ERC-721标准由2个扩展组成:
核心721标准是非常简单的,你只需要实现以下函数,就能符合核心721标准:
2个扩展在上面添加了这些函数:
那么,ERC721Enumerable的问题在哪里?
OpenZeppelin为所有这些接口提供了现成的实现。它们中没有一个是完美的,但2个实现(ERC721和ERC721Metadata)做得相当好。然而,ERC721Enumerable的实现是非常浪费gas。它耗费了大量的Gas并浪费了存储空间。看看他们是如何实现ERC721Enumerable接口的:
你可以想象,跟踪这么多的映射和数组是多么的浪费。(顺便说一句,它们在转账前/后被更新)。上面的代码(错误地)针对读函数进行了优化,而它应该针对写函数进行优化(因为读函数大部分是免费的)。大多数合约开发人员都太懒了,只是从OpenZeppelin继承了所有3个接口。但你可以做得更好。
解决方案:如果你从ERC721Enumerable接口中唯一需要的函数是totalSupply
,那么你可以只使用一个整数,并把它作为一个计数器来记录NFT的铸币数量。你的合约将不再实现整个Enumerable接口,但你将节省大量的Gas。而好消息是,你只需要实现核心的ERC721接口,就能符合ERC721的要求。(也就是说,即使你不实现Enumerable接口,NFT 交易市场在解析你的合约时也不会有问题)。
要注意事项是,你将不再有从tokenID到所有者的映射,反之亦然,但可以使用以太坊事件在链外跟踪这些信息。我认为OpenSea API已经提供了这一点。另外,使用The Graph可以简化与以太坊事件相关的web2基础设施。
合约使用一个计数器,而不是继承OZ的ERC721Enumerable的有:
归功于Shiny Object的探索。
大多数NFT合约都扩展了OpenZeppelin的实现。但它并没有为批量铸币进行优化。批量铸币与一次铸币相比,可以大大节省一些费用。
例如,你可以不为每一次铸币触发一个转移事件,而只触发一个事件,并在事件中指定整个批次的铸币。另一个例子是每个批次只更新一次所有者余额,而不是在每一次铸币之后。
意识到对批量铸币有一些可能的优化,Azuki团队创建了ERC721A--一个为批量铸币优化的ERC721的实现。以下是它与OZ的实现方式的比较:
ERC721A是如何实现这种节约的?主要是利用这些优化:
你可以在Azuki网站上阅读更多关于ERC721A的信息。
如果你需要Enumerable接口的所有功能,你可以使用Azuki的ERC721AQueryable接口,这是ERC721Enumerable的优化版本。
使用ERC721A的合约:
safeMint
最初是为了防止NFT丢失到合约中。如果NFT的接收者是一个合约,而它没有转移NFT方法,NFT将永远停留在合约内。
所以,接收合约是需要实现ERC721Receiver接口,以便允许NFT合约检查NFT是否被接收方正确接收。如果一个合约实现了接收者接口,它就表明它知道收到NFT后知道如何处理:
如果你的接收方只是一个普通的账户,而不是一个合约,你就不需要使用safeMint
。如果你100%确定接收方的合约可以处理NFT,你也不需要使用safeMint
。
通过使用mint而不是safeMint,你可以节省一些Gas。使用transfer
而不是safeTransfer
也是如此。
这样做的合约有:
如果你使用Merkle树来实现你的白名单,你可以节省大量的存储(和Gas)。Merkle树是一种高效的数据结构,它允许你以单个地址的代价存储一堆地址。其代价是,查找时间不是O(1)。不过O(n)也是相当不错的。
你需要添加到合约中的是这些函数:
你可以使用OpenZeppelin的MerkleProof库来完成验证步骤。
然后你会像这样修改你的铸造函数:
基本上,就是在mint函数中添加一个额外的参数:merkleProof
。这是一个地址的哈希数组,构成了从可以铸币地址到根地址的路径。你可以在你的网站上为每个允许铸币地址计算这个路径。这里可阅读更多相关信息。
使用默克尔树的合约:
如果你以后想升级你的NFT的展现,或者在链上和链下的渲染之间切换,你应该让元数据合约可被替换。像这样:
使用这个方式的合约有:
你可以采取2个保障措施来防止机器人将你的代币全部挖走。
msg.sender == tx.origin
。当一个合约调用你的铸币功能时,msg.sender
将是合约地址,但tx.origin
将是调用该合约的人的地址。在这里可查看更多信息。NFT狙击是指有人知道哪些代币是稀有的,并且知道代币被铸造的顺序。因此,他们选择适当的时间去铸造一堆NFT,目标获取稀有的NFT。
你要避免NFT被狙击,以保证每个人都能公平地分配代币。让我们来谈谈如何防止NFT狙击(至少在某种程度上)。NFT狙击由2个问题组成:
你可以通过在代币被铸造后才披露元数据来解决第一个问题(更多信息这里)。或者你可以使用分批渐进式披露。另外所有的链上数据都会被读取和利用。所以在铸币开始前不要验证你的合约。
第二个问题可以通过随机化铸币顺序来解决。链上随机化是很难的。以太坊没有内置的随机数生成器,所以人们一直在使用各种技巧,如使用当前区块号作为种子和/或将其与矿工地址相结合以获得额外的随机性。由于这不是真正的随机性,这些类型的技巧很容易被高级狙击手识破。
你可以使用一个随机化的预言机(Chainlink),但即使如此,高级狙击手也可以通过 偷看
NFT来绕过它,其可以实现:如果铸币的NFT被证明并不罕见,就回退交易(例子这里)。因此,不幸的是,没有100%的方法来解决第二个问题。你可以做的一件事是添加白名单机制,但这只有在整个NFT集合可以被限制在白名单的社区内时才有效。
以太坊真的是一个黑暗的森林,如果你不小心,你可能就会被狙击。阅读更多关于NFT狙击攻击的信息可参看这里。
setApproval
。但随着Seaport的引入,这不再是必要的(关于实施的例子,请查看Crypto Coven 合约)。原文: https://www.solidnoob.com/blog/good-nft-contract-patterns
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!