Open2

マークルツリーによるWLの実装

ぽけなぽけな

以下の素晴らしい記事と動画ついて、自分のメモをくっつけたもの
https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9
https://t.co/otVmm0Dgg8
https://github.com/davidrazmadzeExtra/Merkle_Tree_Whitelist_NFT

背景

NFTプロジェクトでホワイトリスト(WL)はほとんどのプロジェクトで採用されている。
ミンティングサイトにおいて、WLを持ってる人だけ早めにmintできるような実装をしたい。
簡単に思いつくのは、WLを持ってる人のアドレスをスマートコントラクトに登録しておくことだが、書き込むときのガス代がやばいのでなんとかしたい。

マークルツリー

詳細は上の記事が丁寧すぎるので割愛。
ちょっと情報をスマートコントラクトに与えてあげるだけで、WL持ちアドレスかどうかの判断ができる。

実装と構成

バックエンド

WL持ちのアドレスやマークルツリーの作成はバックエンド側で行った方が安全?

  • WLを持ったアドレスを格納しておく
let whitelistAddresses = [
  "0X5B38DA6A701C568545DCFCB03FCB875F56BEDDC4",
  "0X5A641E5FB72A2FD9137312E7694D42996D689D99",
  "0XDCAB482177A592E424D1C8318A464FC922E8DE40",
  "0X6E21D37E07A6F7E53C7ACE372CEC63D4AE4B6BD0",
  "0X09BAAB19FC77C19898140DADD30C4685C597620B",
  "0XCC4C29997177253376528C05D3DF91CF2D69061A",
  "0xdD870fA1b7C4700F2BD7f44238821C26f7392148", // The address in remix
];
  • 上のアドレスをleafnodesとして設定。マークルツリーを作る。
const leafnodes = whitelistAddresses.map((addr) => keccak256(addr));
const merkleTree = new MerkleTree(leafnodes, keccak256, { sortPairs: true });

フロントエンド

任意のアドレスからのmint要求に対して、バックエンドと通信し証明に必要な情報を取得する

  • 証明に必要なハッシュの取得(例えばleafのペアとか)
const hexProof = merkleTree.getHexProof(claimingAddress);

スマートコントラクト

rootHashが改竄されると終わりなので、これはスマートコントラクトに保持しておく
また、チェックを行う
下はjsのコードになってるけど、これで取得できたやつをスマコンにセットしようねということ

  • rootHashの取得
const rootHash = merkleTree.getRoot();
  • WL持ちのアドレスであればTrueが返される
merkleTree.verify(hexProof, claimingAddress, rootHash)

実例

KillerGFのコントラクトがこの方式を取っていた
https://etherscan.io/address/0x3a00c557a2a0b7d4c5e05679c7904a970e5caccd#code
rootHashの登録だと思われる

  function setPresaleRoots(bytes32 _whitelistRoot, bytes32 _uwulistRoot, bytes32 _teamRoot) external onlyOwner {
    whitelistRoot = _whitelistRoot;
    uwuRoot = _uwulistRoot;
    teamRoot = _teamRoot;
  }

販売開始前のトランザクション。なんで3回やってるかは謎。1回目と2回目は引数が違うので変更があったのかなと思うけど、2回目と3回目は同じに見える。。

こんな感じで購入時にチェックしてる

    if (merkleProof[TEAM_INDEX].length != 0) {
      require(teamRoot.length != 0, "team root not assigned");
      bytes32 node = keccak256(abi.encodePacked(indexes[TEAM_INDEX], msg.sender, amounts[TEAM_INDEX]));
      require(MerkleProof.verify(merkleProof[TEAM_INDEX], teamRoot, node), 'MerkleProof: Invalid team proof.');
      require(amountsToBuy[TEAM_INDEX] <= amounts[TEAM_INDEX], "Cant buy this many");
      count += amountsToBuy[TEAM_INDEX];
      teamMinted += uint64(amountsToBuy[TEAM_INDEX]);
    }