NFT 质押挖矿分币系统开发模式定制
怎么写 NFT 铸造合约
自己要写的并不多,一般 200~300 行就差不多了。
共性的那些内容,尤其是 ERC721 的实现,NFT 铸造质押系统开发对接唯 hkkf5566,可以使用现成的别人写好的代码,比如 OpenZeppelin1(以下简称 OZ)就提供了很多实用的功能。
用的时候,继承 OZ 的合约即可,比如:
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
……
contract MyNFT is Ownable, ERC721Enumerable, PaymentSplitter {
……
1、mint 功能实现
虽然可以直接调用 OZ 的 ERC721.sol 的_safeMint 函数来实现 mint,但最好外面再封装一层,写自己的 mint 函数,对于虎虎生威而言,你可以写一个 huhu_mint,里面调用 OZ 的_safeMint 即可。
自己写 mint 的好处是:至少可以控制铸造 NFT 的价格,以及每个地址可以 mint 的数量。
类似的可以考虑销毁(burn)功能,burn 就是取消某 tokenID 和具体地址的绑定,或者理解为把这个 tokenID 转给地址 0。直接用 OZ 的_burn 函数即可。
2、转移功能实现
不用自己写,直接用 OZ 的 ERC721.sol。
3、查询功能实现
不用自己写,用 OZ 的 ERC721.sol 及 ERC721Enumerable.sol(枚举)即可。
ERC721 主要提供的查询是:
balanceOf 函数,查询某个地址持有的 token 数量。
ownerOf 函数,查询某 token 的持有者地址。
ERC721Enumerable 提供了如下 3 个功能:
注意最重要的是:totalSupply 函数,调用它返回目前已经铸造出来的 NFT 的个数。
tokenByIndex 函数用来查询第 index 个 token 的 ID 是多少,也就是说通过这个函数和 totalSupply 函数,就可以遍历所有铸造出来的 token。
tokenOfOwnerByIndex 函数,给它一个地址和一个编号 index,可以告诉你该地址拥有的第 index 个 token 是啥。结合 balanceOf 函数,就可以遍历一个地址拥有的所有 token 的 ID。
4、元数据功能实现
OZ 提供了 IERC721Metadata 接口,但功能是在 ERC721.sol 中实现的。
主要是实现了 name、symbol 和 tokenURI 函数,调用后分别返回 NFT 名、NFT 的缩写符号、token 元数据的链接。
尤其注意 tokenURI 函数,给它一个 tokenID,它返回该 token 元数据所在的 URI。
你还需要自己实现一个外部可见的函数,用来设置 baseURI(注意使用 onlyOwner)。这样,如果原先的存储不可用了,就可以换一个地方存。
然后,重写_baseURI 这个 ERC721.sol 中的内部函数,使之可以返回正确的根目录 URI。
function setBaseURI(string memory _newBaseURI) public onlyOwner {
baseURI = _newBaseURI;
}
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}
比如对于 BAYC 这个 NFT,他的 baseURI 在:
ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
然后,第 23 号猿猴的 tokenURI 就在:
ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/23
读取其中的内容,就是:
{image:ipfs://QmadJd1GgsSgXn7RtrcL8FePionDyf4eQEsREcvdqh6eQe,attributes:[{trait_type:Mouth,value:Bored Pipe},{trait_type:Background,value:Aquamarine},{trait_type:Fur,value:Trippy},{trait_type:Eyes,value:Bored},{trait_type:Hat,value:Beanie}]}
5、合约元数据功能实现
实现一个 contractURI 函数 2,告诉 OpenSea 你的 NFT collection(收藏集)的元数据,比如收藏集的名字、描述、背景图、外部链接等。
比如可以写成这样:
{
"name": "虎虎生威",
"description": "在 2022 年农历虎年发行的专门逗你玩的 NFT",
"image": "https://weisir.com/huhu.png",
"external_link": "https://weisir.com/huhu",
"seller_fee_basis_points": 100, # Indicates a 1% seller fee.
"fee_recipient": "0xA97F337c39cccE66adfeCB2BF99C1DdC54C2D721"
}
6、其他功能实现
分账功能可以使用 OZ 提供的 PaymentSplitter.sol。
白名单功能可以自己写,比较简单。
7、细节注意
a、每个符合 ERC721 的智能合约必须同时符合 ERC721 和 ERC165,ERC165 告诉外部自己支持哪些接口,外界通过调用 supportsInterface 函数获悉一个合约是否支持 ERC721。
b、如果你的合约被设计能够接受 NFT 转账,则需要实现 ERC721TokenReceiver 接口。
其他
1、安全考虑
最主要是防止重入攻击,所以要加上非重入保护,实现很简单,就是加锁。
OZ 有个 nonReentrant 修饰符专门解决这个问题,对于涉及资金交易的函数,加上此修饰符即可。
这个修饰符是在 ReentrancyGuard.sol 中实现的,直接使用即可。
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
2、铸造入口
如前所述,OpenSea 上展现的 NFT 都是铸造好的。
对于一个有自己智能合约的 NFT,铸造过程并不是在 OpenSea 上完成的,而是通过自有途径完成。
通常,你需要自己做一个网页,通过 web3.js,让用户自己来 mint(花用户的 gas 费)。
当然,你也可以自己 mint 所有的 token,这就不用做网页了,调用合约接口就可以。不过,这需要花自己的 gas 费,现在的人,都舍不得花 gas 费,千方百计让用户来花,挺有意思的。
六、更底层的细节
这里简单说一下 ERC721,给有一定基础的同学观看,详细的内容可以自行搜索。
小白不用看这些,说实话,我都懒得看。
balanceOf 函数,参数 owner,它返回由 owner 持有的 token 的数量。
ownerOf 函数,参数 tokenId,它返回该 token 的持有者地址。
transferFrom 函数,3 个参数:from、to、tokenID,主人或被授权人调用后,把第 tokenID 号 token 从 from 转给 to。
safeTransferFrom 主要是实现可靠的转移,尤其是当 to 为一个合约时,调用该合约的 onERC721Received 方法,并且检查其返回值,如果该合约没有这个方法或返回值不对,则回退,避免 token 丢失。
approve 函数,两个参数:地址 to 和 tokenID。tokenID 的主人调用此函数,授权 to 可以转移此 token。比如张三 approve 了一个 token 给李四,李四就可以用 transferFrom 函数转走该 token,from 填李四的地址就行。(如果主人 approve 一个 token 给地址 0,就取消了原先的授权。)
setApprovalForAll 函数,两个参数:地址 operator 和布尔值 approved,通过此函数,张三可以授予李四(operator)获取自己所有 NFT 的控制权(approved 为 True),也可以通过为 False 的 approved 收回此授权(说实话,这个函数设计得不太好,应该分成两个函数,而不是一个函数干两件事)。
getApproved 函数,参数 tokenID,可以得知主人将 token 授权给谁了。
isApprovedForAll 函数,两个参数:owner 和 oprator,调用此函数,可以查询 owner 是否把自己所有 token 都授权给 operator 了。
上面就是 ERC721 的接口函数,当然,发行一个 NFT,只有上面这些是不够的。
评论