สรุป Building an NFT Airdrop by OpenZeppelin (Part 1)
โอเค มาเริ่มจากพื้นฐานกันก่อน
การ mint NFT จะต้องระบุเจ้าของ และ unique id ของ NFT โดยปกติแล้วจะกำหนด role ในการ mint เพื่อให้ NFT สามารถรักษามูลค่าได้
ถ้าอยากให้มี role ที่ซับซ้อนกว่า owner ก็สามารถใช้ AccessControl ได้
การ mint NFT จะขึ้นกับเคสการใช้งาน เช่น POAP user ไม่ควรจะ mint เองได้ หรือ เกม cryptokitty ที่ user เจ้าของ NFT สามารถ burn 2 NFT เพื่อสร้าง NFT ใหม่ได้
Lazy minting คืออะไร ?
คือการ delay การจ่ายค่าแก๊สในการ mint NFT โดยใช้การให้สิทธิ์ในการ จองการ mint แก่ user ไปก่อน โดยยังไม่มีการ mint จริงๆเกิดขึ้น
โดย challenge คือในการให้สิทธิ์ในการ จองการ mint แก่ user ไป ต้องทำ offchain จะได้ไม่ต้องเสียค่าแก๊ส
วิธีการ lazy mint ทำได้หลายวิธีดังนี้
โอเค มาเริ่มที่วิธีแรกกัน คือการใช้ Signature
Signature คืออะไร?
คือผลลัพธ์ของการเข้ารหัส data ด้วย private key
โดย signature สามารถใช้ยืนยันตัวตนได้ เนื่องจากหากนำ signature มาเข้ารหัสกับ data ที่ใช้ในการสร้าง signature นั้น … จะได้ public key ที่คู่กับ private key นั้นมา ดังนั้นเราสามารถยืนยันได้ว่า signature มาจาก private key ที่ถูกต้อง
ดังนั้น เมื่อเราใช้ private key สร้าง signature จาก data … ปลายทางจะสามารถเช็คได้ว่า signature นี้มาจาก data และ wallet เราจริงๆรึเปล่า
Option 1 การ lazy mint ด้วย plain text signature
- minter ทำการ sign ว่า mint NFT id อะไร ให้กับ user address ไหนโดยทำการ sign offchain
- เมื่อต้องการ mint จริงๆ ให้ทำการเรียก function redeem (on chain) โดย function นี้จะเช็คว่า signature ของ data (ประกอบด้วย user adress เจ้าของ NFT และ id ของ NFT) มาจาก minter จริงๆรึเปล่า ถ้าใช่ก็จะทำการ mint
จะสังเกตุว่า user ไม่ต้องจ่ายค่า mint เอง เพราะ minter เป็นคนยิง transaction แทนนั่นเอง โอเคมาดู code กัน
การ sign message ทำที่ offchain
จากนั้นค่อยเรียก redeem เพื่อ mint ทีหลัง
note
อันนี้เป็นการเขียน test การ redeem สังเกตว่าการ mint จะทำให้เกิด event Transfer โดยมี from เป็น AddressZero
code solidity ของ function redeem เพื่อ mint ทำที่ onchain
ข้อเสียของวิธีนี้คือ signature สามารถนำไปใช้ใน context อื่นๆได้ เช่น นำไปใช้ใน chain อื่น เพราะเราใส่แค่ data ไปใน signature แต่ไม่ได้ระบุ metadata ลงไป ซึ่งการ reuse signature จะนำไปสู่ security issue อื่นๆตามมา
จริงๆ เราสามารถใส่ metadata ลงไปกับผสมกับ data ได้เช่นกัน แต่ข้อเสียคือ เราต้องสร้างมาตรฐานในการแกะ metadata ออกมาจาก data เอง และ third party จะไม่เข้าใจว่าเราทำอะไร เช่น metamask จะแสดง text ที่อ่านไม่ออก
ดังนั้นเราจึงต้องมีมาตรฐานในการ sign data และ meta data ร่วมกัน นั่นคือ ERC712
Option 2 การ lazy mint ด้วย ERC712 text signature
note
ERC712 คือ standard ที่มาจาก propose EIP712
โดยจะมี flow ตามเดิมคือ
- minter ทำการ sign ว่า mint NFT id อะไร ให้กับ user address ไหนโดยทำการ sign offchain (การ sign ใช้ ERC712)
- เมื่อต้องการ mint จริงๆ ให้ทำการเรียก function redeem (on chain) โดย function นี้จะเช็คว่า signature ของ data (ประกอบด้วย user adress เจ้าของ NFT และ id ของ NFT) มาจาก minter จริงๆรึเปล่า ถ้าใช่ก็จะทำการ mint (การ verify ใช้ ERC712)
โอเค มาดูโค้ดกัน
minter ทำการ sign ว่า mint NFT id อะไร ให้กับ user address ไหนโดยทำการ sign offchain (การ sign ใช้ ERC712) และใส่ metadata ไปด้วย ประกอบด้วย chainId ของ contract และ address ของ contract ซึ่งการนำ signature ไปใช้ จะต้องตรงตาม metadata นี้ (name กับ version ไม่แน่ใจว่าจะใช้เช็คยังไงแหะ)
minter ทำการส่ง signature ให้ function redeem onchain
function redeem (on chain) จะเช็คว่า signature ของ data (ประกอบด้วย user adress เจ้าของ NFT และ id ของ NFT) มาจาก minter จริงๆรึเปล่า ถ้าใช่ก็จะทำการ mint (การ verify ใช้ ERC712) โดยจะสังเกตว่าที่ function _hash ต้องระบุ structure ของ data เพื่อแกะ data ออกมา
โดยเมื่อจะ sign metamask จะแสดงข้อมูลที่อ่านออกละ
ข้อเสียของวิธีนี้คือ รองรับแค่ EOA (เรียกง่ายๆว่า wallet ธรรมดา) แต่ถ้าใช้พวก smart wallet หรือ wallet ที่เป็น contract อีกทีเช่น Gnosis จะต้องไปใช้วิธีต่อไปแทน นั่นคือ
Option 3 การ lazy mint ด้วย ERC1271 signature
โอเคโดย flow การทำงานจะเป็นแบบนี้
- Assign ให้ EOA เป็น owner ของ smart wallet contract
- Assign ให้ smart wallet contract เป็น minter
- Sign ว่า mint NFT id อะไร ให้กับ user address ไหนโดยทำการ sign offchain (การ sign ใช้ ERC1271 และ EOA เป็นคน sign)
- เมื่อต้องการ mint จริงๆ ให้ทำการเรียก function redeem (on chain) โดย function นี้จะเช็คว่า signature ของ data (ประกอบด้วย user adress เจ้าของ NFT และ id ของ NFT) มาจาก minter จริงๆรึเปล่า ถ้าใช่ก็จะทำการ mint (การ verify ใช้ ERC1271 โดย EOA จะส่ง address ของ smart wallet contract ไป ถ้า message ถูก sign ด้วย owner smart wallet นั้น จะ verify ผ่าน)
โอเค มาดูทีละขั้นกัน
Assign ให้ EOA เป็น owner ของ smart wallet contract
Assign ให้ smart wallet contract เป็น minter
Sign ว่า mint NFT id อะไร ให้กับ user address ไหนโดยทำการ sign offchain (การ sign ใช้ ERC1271 และ EOA เป็นคน sign)
ทำการเรียกการ redeem โดยส่ง address ของ smart wallet contract เพิ่มไปจาก data ปกติ
function _hash จะทำการ hash data ตาม structure ปกติ เหมือน option ก่อนหน้า
แต่ function _verify จะทำการเช็คว่า signature ถูก sign มาจากเจ้าของ smart wallet มั้ย
โดย isValidSignatureNow จะเช็คว่า address ที่ได้จากการ verify signature ตรงกับ owner ของ contract signer มั้ย
ถ้า signature มาจาก owner smart wallet จริงๆ เราก็สามารถ implement อย่างอื่นเพิ่มเติมได้ เช่น ให้ smart wallet เป็น minter ดังนั้นถ้า smart wallet รองรับ owner หลายคน หลายๆ EOA ก็สามารถเป็น minter ผ่าน smart wallet อันเดียวได้ ( use case ประมาณนี้มั้งนะ ต้องลองเล่น smart wallet อีกที)
ข้อเสียของทั้ง Option 1,2,3 คือ เป็นการ mint แบบแยกครั้งกัน ถ้าเทียบกับ merkle-drop ซึ่งเป็นการ mint แบบครั้งเดียวจบแล้ว user จะมั่นใจมากกว่าว่าจะไม่มีการ mint เหรียญ ที่ไม่ได้อยู่ใน merkle tree เพิ่ม
ส่วนการ mint แบบ merkle drop เป็นยังไง ไว้ไปต่อบทความหน้าดีกว่า
Ref