在 claim()函数铸造主体NFT,会附带铸造生成了全套装备。本文就研究装备的生成算法。
1 函数调用流程
claim()
_afterTokenMint()
计算装备的tokenId
mintSubToken()
_mint(address(this), subId);
attach(subId, attr, 1, bytes(""), true);
synthesizedTokens[tokenId].push(SynthesizedToken(_msgSender(), subId));
2 主体NFT和装备NFT的 tokenId计算方法
主体NFT的最大发行数量是 _totalSupply = 8000, 范围是【1~8000】
每个主体NFT拥有8个装备,所以装备的tokenId范围是 【8001~72000】
计算公式:
id = _totalSupply + (tokenId - 1) * 8 + 1;
3 装备的属性值如何随机化?
其实这是一个非常令人困扰的事情, 在mintSubToken中,你找不到任何属性具体值的代码, 那么到底在什么时间点/代码中确定出装备的属性的呢?
答案揭晓: 属性值并不是在铸造时设置的,而是在你访问合约读取tokenURI时根据装备的tokenId计算得到的。所以决定关系如下:
主体tokenId --> 装备的tokenId ----> 装备的属性值
【结论】主体的tokenId已经决定了一切,
😮
是不是让你大吃一惊,这种想法太惊人了,从来没有碰到过这种脑回路。
先说明计算细节:
示例: head, tokenId= 69411, 属性值:
"Ghoul Tear"
War Cap
of Rage
"attributes":[
{
"trait_type":"HEAD NAME",
"value":"War Cap"
},
{
"trait_type":"HEAD ID",
"value":"69411"
},
{
"trait_type":"HEAD suffix",
"value":"of Rage"
},
{
"trait_type":"HEAD namePrefixes",
"value":"Ghoul"
},
{
"trait_type":"HEAD nameSuffixes",
"value":"Tear"
},
在pluckAttribute()中, 计算随机数:
input = "HEAD" + "69411"
rand = uint256(keccak256(abi.encodePacked(input)))
output = sourceArray[rand % sourceArray.length] // headArmor[7] = "War Cap" rand%15=7
继续计算后缀随机数: greatness = rand % 21, 必须保证greatness>14才能有后缀。 rand%21>14
suffix = suffixes[rand % suffixes.length] // suffixes[9] = "
of Rage" rand%16=9
必须保证greatness>19才能有名称前缀+后缀。
namePrefixes[] = "
Ghoul " // namePrefixes[28] = "Ghoul" rand%69 = 28
nameSuffixes[] = "
Tear" //nameSuffixes[13] = "Tear" rand%18 = 13
最终生成的data是
{
"trait_type":"HEAD NAME",
"value":"War Cap"
},
{
"trait_type":"HEAD ID",
"value":"69411"
},
{
"trait_type":"HEAD suffix",
"value":"of Rage"
},
{
"trait_type":"HEAD namePrefixes",
"value":"Ghoul"
},
{
"trait_type":"HEAD nameSuffixes",
"value":"Tear"
},
4 获取NFT属性的组装过程
一般的NFT元数据文件是从合约中得到URI,然后再从外部存储网络(IPFS)获得。
Legoot合约的元数据文件并非如此, 是根据属性参数自动拼装出来的字符串,完全内置化,无需外部存储。
json格式:name:
description:
image:
attributes:
调用流程:
tokenURI( tokenId )
getImageText(tokenId, 20)
getAttributes(tokenId)
Base64.encode(...);