Using merkle trees for airdrop whitelists

ก่อนจะนำ Merkle tree มาใช้เนี่ยเราลองหาเหตุผลที่ต้องใช้กันก่อนดีกว่า เริ่มที่โจทย์ว่าเราอยากจะทำ Airdrop โดยจะแจกโทเคน ERC20 ให้กับผู้ใช้บางกลุ่มที่ผ่านการทำกิจกรรมบางอย่างกับ Platfrom ของเรา เช่น ตอบคำถามถูก 10 คนแรก ซึ่งเราจะให้สิทธิ์ผู้ใช้กลุ่มนี้ในการรับ Airdrop

User claim flow diagram.
User claim flow diagram.

ที้นี้ ถ้าเรามาลองลงมือเขียน Solidity แบบง่ายๆ โดยรับเป็น Array address ของกลุ่มที่มีสิทธิ์รับ Airdrop มาเก็บไว้แล้วก็ทำ Map ตัวนึงสำหรับเช็คว่าผู้ใช้รับไปเรียบร้อยแล้ว

Normal whitelist airdrop contract.
Normal whitelist airdrop contract.

ในการ deploy contract นี้เราต้องใช้ค่าแก๊สทั้งหมด 693054 gas สำหรับการใส่ address list จำนวน 1 คน แล้วถ้าเราต้องการใส่ 10 คนตามโจทย์ละ เราต้องใช้ค่าแก๊สทั้งหมด 929965 gas จะเห็นได้ว่าค่าแก๊สจะโตตามจำนวน address ที่เราใส่เข้าไป นี่ก็คือเหตุผลที่เราจะนำ Merkle tree มาใช้เพื่อช่วยลดค่าแก๊สนั่นเอง

Merkle tree

Merkel tree หรือเรียกว่า Hash tree จะประกอบไปด้วย Leaf node จะเกิดจากการ Hash data และแต่ละ Branch node จะมาจาก Hash ของ Children node

Structure of merkle tree.
Structure of merkle tree.

เราสามารถนำ Merkle tree มาใช้ในการเช็คได้ว่ามี Data อยู่บน Tree จริงๆ โดยไม่ต้องเก็บ Data ทั้งก้อนไว้ จะเรียกวิธีนี้ว่า Merkle proof

ยกตัวอย่างเช่น หากเราต้องการเช็คว่ามี xD2 อยู่บนต้นไม้ไหม ลำดับแรกเราต้องสร้าง Merkle tree ตามภาพด้านบนให้เสร็จเรียบร้อยแล้วเก็บ Root hash ไว้หรือก็คือ H5

Merkle proof node.
Merkle proof node.

จากนั้นเราจะ Proof ได้โดยการนำ xD2 ไป hash(xD2) ให้ได้ xH2 แล้วหยิบ H1 มา hash(H1+xH2) ให้ได้ xH4 แล้วสุดท้ายไปหยิบ H3 มา hash(xH4+xH3) เราก็จะได้ Root hash xH7 เมื่อนำไปเทียบกับ H7 แล้วตรงกันก็ถือว่า data xD2 อยู่บนต้นต้นไม้นี้จริง

Merkle airdrop

หลังจากเข้าใจ Merkle tree แล้วเราก็จะนำมาลองใช้งานจริงกับการทำ Airdrop ผมขออธิบายขั้นตอนคร่าวๆ ก่อน เราจะนำ address ของผู้ที่มีสิทธิ์รับเนี่ยมาประกอบ Merkle tree แล้วนำไป hash(address) ใส่ Leaf node ทั้งหมด จากนั้นเก็บแค่ Root hash ที่ได้ไว้ใน Airdrop contract แทน address ทั้งหมดของผู้รับ จากนั้นตอนที่ผู้ใช้กด Claim ก็จะส่งแค่ Proof hash กับ address ของคนรับ เข้ามา Verify ว่าได้ Root hash ตรงกันไหม ถึงจะโอน Token ให้ผู้ใช้

ข้างล่างนี่เป็น Libraries ทั้งหมดที่จะนำมาใช้ในการช่วยทำ Merkle airdrop

  • merkletreejs ใช้ในประกอบ Merkle tree

  • keccak256 ใช้เป็น hash function ใน Merkle tree

  • MerkleProof.sol ใช้ในการช่วย Verify root hash ใน Smart contract

Build a merkle tree root hash

เริ่มต้นที่การสร้าง Merkle tree โดยการสร้าง Node.JS Project กันก่อนเลยและเพิ่ม dependency ที่ต้องใช้ให้ครบ

pnpm[npm, yarn] init -y
pnpm[npm, yarn] add keccak256 merkletreejs

จากนั้นก็สร้าง index.js ขึ้นมาเริ่มเขียนโค้ดเพื่อหา Root hash

content of index.js file
content of index.js file

Using Merkle proof to verify in airdrop contract

หลังจากได้ Root hash แล้วเราก็จะนำมาใส่ Airdrop contract โดยรับมาทาง constructor แทนการรับ Address list

content of MerkleWhitelistAirdrop.sol file
content of MerkleWhitelistAirdrop.sol file

How user can claim airdrop?

ทีนี้ในตอนที่ผู้ใช้จะ claim airdrop เราจะต้องรู้ address ของผู้ใช้ที่ต้องการจะรับ โดยจะยกตัวอย่างเป็น 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db หรือนี่ก็คือ xD2 ในตัวอย่าง Merkle proof นั่นเอง เราจะนำมา hash keccak256 แล้วโยนให้ merkletreejs ช่วยหา Proof node

content of index.js file
content of index.js file

ทีนี้เราก็ส่ง proof ให้ผู้ใช้ไปเรียก claim(proof); เพียงเท่านี้ Token จาก Airdrop ก็จะโอนไปหาผู้ใช้ที่มีสิทธิ์รับแล้ว

ซึ่งในขั้นตอนในการหา proof เนี่ยเราสามารถนำไปเขียนต่อเป็น Front-end ได้โดยดึง address จาก Metamask ของผู้ใช้เพื่อไปสร้าง proof แล้วค่อยสั่งเรียก claim(proof); ให้ผู้ใช้

User claim flow with merkle proof diagram.
User claim flow with merkle proof diagram.

Conclusion

ทีนี้เราไปลองส่องค่าแก๊สจากการ Deploy MerkleWhitelistAirdrop contract กันหน่อย

Deploy contract transaction detail.
Deploy contract transaction detail.

จะเห็นได้ว่าเราใช้ไปเพียง 696206 gas สำหรับ address ผู้รับ 10 คน ซึ่งหากเราเพิ่มจำนวนผู้รับมากกว่านี้ค่าแก๊สก็จะไม่ได้โตไปมากกว่านี้มาก เพราะเราเก็บเพียงแค่ bytes32 ของ Merkle root ไว้ใน Contract นั่นเอง