MerkleTree脚本
这一讲我们写一个利用Merkle Tree
白名单铸造NFT
的脚本
Merkle Tree
Merkle Tree
,也叫默克尔树或哈希树,是区块链的底层加密技术,被比特币和以太坊区块链广泛采用。Merkle Tree
是一种自下而上构建的加密树,每个叶子是对应数据的哈希,而每个非叶子为它的2
个子节点的哈希。

Merkle Tree
允许对大型数据结构的内容进行有效和安全的验证(Merkle Proof
)。对于有N
个叶子结点的Merkle Tree
,在已知root
根值的情况下,验证某个数据是否有效(属于Merkle Tree
叶子结点)只需要log(N)
个数据(也叫proof
),非常高效。如果数据有误,或者给的proof
错误,则无法还原出root
根植。下面的例子中,叶子L1
的Merkle proof
为Hash 0-1
和Hash 1
:知道这两个值,就能验证L1
的值是不是在Merkle Tree
的叶子中。

Merkle Tree
合约简述
WTF Solidity极简教程第36讲:Merkle Tree中的MerkleTree
合约利用Merkle Tree
验证白名单铸造NFT
。我们简单讲下这里用到的两个函数:
- 构造函数:初始化NFT的名称,代号,和
Merkle Tree
的root
。 mint()
:利用Merkle Proof
验证白名单地址并铸造。参数为白名单地址account
,铸造的tokenId
,和proof
。
MerkleTree.js
MerkleTree.js
是构建Merkle Tree
和Merkle Proof
的Javascript包(Github连接)。你可以用npm
安装他:
npm install merkletreejs
这里,我们演示如何生成叶子数据包含4
个白名单地址的Merkle Tree
。
创建白名单地址数组。
import { MerkleTree } from "merkletreejs"; // 白名单地址 const tokens = [ "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB" ];
将数据进行keccak256
哈希(与solidity使用的哈希函数匹配),创建叶子结点。
const leaf = tokens.map(x => ethers.keccak256(x))
创建Merkle Tree
,哈希函数仍然选择keccak256
,可选参数sortPairs: true
(constructor函数文档),与Merkle Tree
合约处理方式保持一致。
const merkletree = new MerkleTree(leaf, ethers.keccak256, { sortPairs: true });
获得Merkle Tree
的root
。
const root = merkletree.getHexRoot()
获得第0
个叶子节点的proof
。
const proof = merkletree.getHexProof(leaf[0]);
Merkle Tree
白名单铸造NFT
这里,我们举个例子,利用MerkleTree.js
和ethers.js
验证白名单并铸造NFT
。
生成Merkle Tree
。
// 1. 生成merkle tree console.log("\n1. 生成merkle tree") // 白名单地址 const tokens = [ "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB" ]; // leaf, merkletree, proof const leaf = tokens.map(x => ethers.keccak256(x)) const merkletree = new MerkleTree(leaf, ethers.keccak256, { sortPairs: true }); const proof = merkletree.getHexProof(leaf[0]); const root = merkletree.getHexRoot() console.log("Leaf:") console.log(leaf) console.log("\nMerkleTree:") console.log(merkletree.toString()) console.log("\nProof:") console.log(proof) console.log("\nRoot:") console.log(root)

创建provider和wallet
// 准备 alchemy API 可以参考https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md const ALCHEMY_GOERLI_URL = 'https://eth-goerli.alchemyapi.io/v2/GlaeWuylnNM3uuOo-SAwJxuwTdqHaY5l'; const provider = new ethers.JsonRpcProvider(ALCHEMY_GOERLI_URL); // 利用私钥和provider创建wallet对象 const privateKey = '0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b' const wallet = new ethers.Wallet(privateKey, provider)
创建合约工厂,为部署合约做准备。
// 3. 创建合约工厂 // NFT的abi const abiNFT = [ "constructor(string memory name, string memory symbol, bytes32 merkleroot)", "function name() view returns (string)", "function symbol() view returns (string)", "function mint(address account, uint256 tokenId, bytes32[] calldata proof) external", "function ownerOf(uint256) view returns (address)", "function balanceOf(address) view returns (uint256)", ]; // 合约字节码,在remix中,你可以在两个地方找到Bytecode // i. 部署面板的Bytecode按钮 // ii. 文件面板artifact文件夹下与合约同名的json文件中 // 里面"object"字段对应的数据就是Bytecode,挺长的,608060起始 // "object": "608060405260646000553480156100... const bytecodeNFT = contractJson.default.object; const factoryNFT = new ethers.ContractFactory(abiNFT, bytecodeNFT, wallet);
利用contractFactory部署NFT合约
console.log("\n2. 利用contractFactory部署NFT合约") // 部署合约,填入constructor的参数 const contractNFT = await factoryNFT.deploy("WTF Merkle Tree", "WTF", root) console.log(`合约地址: ${contractNFT.target}`); console.log("等待合约部署上链") await contractNFT.waitForDeployment() console.log("合约已上链")

调用mint()
函数,利用merkle tree
验证白名单,并给第0
个地址铸造NFT
。在mint
成功后可以看到NFT
余额变为1
。
console.log("\n3. 调用mint()函数,利用merkle tree验证白名单,给第一个地址铸造NFT") console.log(`NFT名称: ${await contractNFT.name()}`) console.log(`NFT代号: ${await contractNFT.symbol()}`) let tx = await contractNFT.mint(tokens[0], "0", proof) console.log("铸造中,等待交易上链") await tx.wait() console.log(`mint成功,地址${tokens[0]} 的NFT余额: ${await contractNFT.balanceOf(tokens[0])}\n`)

用于生产环境
在生产环境使用Merkle Tree
验证白名单发行NFT
主要有以下步骤:
- 确定白名单列表。
- 在后端生成白名单列表的
Merkle Tree
。 - 部署
NFT
合约,并将Merkle Tree
的root
保存在合约中。 - 用户铸造时,向后端请求地址对应的
proof
。 - 用户调用
mint()
函数进行铸造NFT
。
总结
这一讲,我们简单介绍了Merkle Tree
,并利用MerkleTree.js
和ethers.js
创建、验证白名单,铸造NFT
。