เมื่อก่อนผมเคยคิดว่าการสร้าง NFT บน Solana หมายถึงการต้องรับมือกับความยุ่งยากของ Metaplex นั่นคือแนวทางที่ทุกบทเรียนแนะนำ: ต้องตั้งค่า Candy Machine, จัดการ metadata accounts, และต้องวุ่นวายกับโปรแกรมแยกส่วนกันเพียงเพื่อจะใส่ชื่อและรูปภาพให้กับโทเคน แต่ปรากฏว่าความเชื่อนั้นล้าสมัยไปแล้ว โปรแกรม Token Extensions หรือที่รู้จักกันในชื่อ Token-2022 ได้ยุบรวมความซับซ้อนเหล่านั้นเข้าไปไว้ในตัว mint เอง ตอนนี้คุณสามารถสร้าง NFT ที่ทำงานได้อย่างสมบูรณ์โดยไม่ต้องแตะต้อง metadata program หรือต้องสำรองเงินสำหรับบัญชีเพิ่มเติม เพียงแค่เปิดใช้งาน flag ไม่กี่ตัว เขียนข้อมูลลงใน mint account โดยตรง ก็เสร็จเรียบร้อย
สิ่งนี้เปลี่ยนวิธีที่นักพัฒนาควรคิดเกี่ยวกับสินทรัพย์ดิจิทัลบน Solana ในการพัฒนาเว็บแบบดั้งเดิม NFT ให้ความรู้สึกเหมือนเป็นโครงสร้างข้อมูลที่แยกต่างหาก เป็นสิ่งที่ต้องการตารางและ schema ของตัวเอง แต่บน Solana ความเป็นจริงนั้นเรียบง่ายและสง่างามกว่ามาก NFT ไม่ใช่ object พิเศษที่จัดการโดยโปรโตคอลภายนอก แต่มันคือ mint account ที่ถูกกำหนดค่าให้มี supply เท่ากับหนึ่งและมี decimals เป็นศูนย์เท่านั้น โทเคนมาตรฐานช่วยให้คุณแบ่งหน่วยได้เพราะมี supply จำนวนมากและมี decimals หลายตำแหน่ง ส่วน NFT จะล็อก supply ไว้ที่หน่วยเดียวที่ไม่สามารถแบ่งแยกได้ ทุกสิ่งที่ทำให้มันมีความเป็นเอกลักษณ์จะอาศัยอยู่ใน extensions ที่ทำงานควบคู่ไปกับ core mint account นั้น
วิธีแบบเก่าและวิธีแบบใหม่
ก่อนจะมี Token Extensions ชุดเครื่องมือมาตรฐานจะประกอบด้วย SPL Token program สำหรับตัว mint เอง บวกกับ Metaplex สำหรับ metadata, collections และบางครั้งก็รวมถึงการทำ off-chain indexing ด้วย โดย metadata จะอยู่ในบัญชีแยกต่างหาก ซึ่งเชื่อมโยงกันด้วย address ที่คุณต้องคอยติดตาม มันใช้งานได้ แต่ก็เพิ่มความซับซ้อนและจุดที่ต้องจัดการมากขึ้น การมีบัญชีมากขึ้นหมายถึงค่า rent ที่มากขึ้น, เส้นทางการลงนาม (signing paths) ที่มากขึ้น และต้องใช้ logic ฝั่ง client มากขึ้นเพื่อรวบรวมข้อมูลทั้งหมดของโทเคนให้ครบถ้วน
Token Extensions เข้ามาแทนที่ความกระจัดกระจายนั้นด้วยการฝังความสามารถต่างๆ ลงไปใน mint โดยตรง ต้องการชื่อ, symbol และตัวชี้ไปยังสื่อ off-chain ใช่ไหม? ก็แค่เปิดใช้งาน metadata extension ต้องการจัดกลุ่มโทเคนเข้าเป็น collection หรือไม่? ก็ใช้ Group และ Member extensions ตัว mint จะกลายเป็นแหล่งข้อมูลความจริงหนึ่งเดียว (single source of truth) สำหรับนักพัฒนาที่คุ้นเคยกับ relational databases การเปลี่ยนแปลงนี้จะให้ความรู้สึกเหมือนการเปลี่ยนจากสถาปัตยกรรมแบบ distributed microservices กลับมาเป็นตารางแบบ normalized ที่มีการออกแบบ foreign keys มาอย่างดี
โครงสร้างของ NFT ที่ใช้ Extension
การสร้าง NFT ด้วย Token Extensions จำเป็นต้องเข้าใจอย่างถ่องแท้ว่าอะไรที่ทำให้โทเคนกลายเป็น non-fungible บนเชนนี้ Supply ต้องเท่ากับหนึ่ง และ decimals ต้องเท่ากับศูนย์ ข้อจำกัดทั้งสองนี้จะช่วยป้องกันการแบ่งหน่วยย่อย (fractionalization) เมื่อตั้งค่าพารามิเตอร์เหล่านี้แล้ว คุณก็สามารถเปิดใช้งาน extensions เพื่อจัดเก็บฟิลด์เพิ่มเติมลงใน mint account ได้โดยตรง
metadata extension จะเก็บชื่อ, symbol และ URI โดย URI นั้นจะชี้ไปยังไฟล์ JSON ซึ่งมักจะถูกโฮสต์ไว้บน decentralized storage หรือ web server มาตรฐาน เพื่ออธิบายรูปภาพ, attributes และ traits โดยไม่ต้องมี metadata account แยกต่างหากให้ต้องค้นหาหรือทำ deserialization ข้อมูลจะถูกเก็บไว้ในตัว mint เอง ซึ่งหมายความว่า explorers, wallets และซอฟต์แวร์ฝั่ง client สามารถอ่านตัวตนหลักของโทเคนได้จากการตรวจสอบบัญชีเพียงบัญชีเดียว
ผมได้ทดสอบเรื่องนี้ด้วยตัวเองบน devnet ผมสร้าง mint ใหม่พร้อมเปิดใช้งาน metadata extension จากนั้นก็เขียนชื่อและ symbol ลงใน mint state โดยตรง ธุรกรรม (transaction) สำเร็จ และผลลัพธ์ก็ปรากฏใน Solana Explorer ทันที ไม่ต้องมีบัญชีที่สองให้ต้องสำรองเงินหรือตามหา ความเรียบง่ายนี้ทำให้ผมรู้สึกทึ่ง หลังจากที่ต้องทำงานกับ Metaplex metadata ที่มีหลายบัญชีมานานหลายสัปดาห์
การสร้าง Collection ให้เหมือนกับแถวในฐานข้อมูล
การสร้าง Collections คือขั้นตอนต่อไปที่สมเหตุสมผล ในโมเดลแบบเก่า การจัดกลุ่ม NFT มักจะต้องพึ่งพา Metaplex Certified Collections หรือ off-chain registries แต่ Token Extensions ได้นำเสนอ primitives เฉพาะสองอย่างคือ Group extension และ Member extension
นี่คือลำดับการทำงาน: คุณสร้าง mint เดียวที่ทำหน้าที่เป็นหัวหน้าของ collection (collection header) และเปิดใช้งาน Group extension บนนั้น จากนั้น สำหรับ NFT แต่ละรายการใน collection คุณจะสร้าง mint ที่เปิดใช้งาน Member extension โดยแต่ละ member mint จะเก็บตัวชี้ (pointer) กลับไปยัง address ของ collection mint ความสัมพันธ์นี้จะทำงานเหมือนกับ foreign key ใน relational database ทุกประการ โดยแถวของ collection จะมีเพียงหนึ่งเดียว และแต่ละแถวของ member จะอ้างอิงถึงมันโดยไม่ต้องทำซ้ำข้อมูลของ collection
ผมได้สร้าง collection ทดสอบเล็กๆ ด้วยวิธีนี้บน devnet โดย collection mint หลักจะถือ flag ของ group ส่วนโทเคนแต่ละรายการจะถือ flag ของ member และอ้างอิงไปยัง parent address การคิวรี (query) ข้อมูลบนเชนทำให้ผมได้โครงสร้างที่สะอาดและสามารถไล่ลำดับความสัมพันธ์ได้ง่าย ไม่จำเป็นต้องใช้ indexer ของบุคคลที่สามมาคอยเดาว่าโทเคนเหล่านั้นอยู่กลุ่มเดียวกันหรือไม่ เพราะความสัมพันธ์นี้ถูกระบุไว้อย่างชัดเจนและอยู่บนเชน (on-chain)
การทดลองแบบ Open Schema และ On-Chain
รายละเอียดหนึ่งที่โดดเด่นคือ open schema ของ metadata extension มาตรฐานเก่าๆ มักจะบังคับใช้รายการฟิลด์ที่ตายตัว หากคุณต้องการจัดเก็บข้อมูลที่ไม่เป็นไปตามมาตรฐานบนเชน (on-chain) คุณก็ต้องจำใจเก็บไว้ใน JSON นอกเชน (off-chain) หรือต้องใช้วิธีซิกแซกกับโครงสร้างบัญชี (account layouts) ที่ตายตัว
Token Extensions ใช้แนวทางที่แตกต่างออกไป เนื่องจาก metadata extension รองรับฟิลด์ที่กำหนดเอง (custom fields) ผมจึงสามารถเพิ่มคุณลักษณะความหายาก (rarity attribute) ลงใน mint account ได้โดยตรง ผมเขียนฟิลด์นั้น ส่งธุรกรรม และรีเฟรช Solana Explorer ค่าความหายากก็ปรากฏขึ้นทันทีควบคู่ไปกับชื่อและสัญลักษณ์ สำหรับนักพัฒนาเกมหรือใครก็ตามที่สร้างสินทรัพย์แบบไดนามิก (dynamic assets) ความยืดหยุ่นนี้มีความสำคัญมาก คุณสามารถแสดงคุณลักษณะสำคัญ (critical traits) บนเชนได้โดยไม่ต้องพึ่งพาตัวตรวจสอบภายนอก (external verifier) เพื่อมาอ่านค่าจาก JSON
ช่องว่างนอกเชน (Off-Chain Gap): URIs และการทำ Caching
แม้ว่าการจัดเก็บข้อมูลบนเชนจะมีความสง่างามเพียงใด แต่บทเรียนหนึ่งที่ชัดเจนมากคือ: อัตลักษณ์ (identity) ยังคงอยู่นอกเชน ตัว mint ไม่ได้เก็บรูปภาพของคุณ แต่มันเก็บ URI ไว้ เมื่อผมอัปเดต URI นั้นและบันทึกการเปลี่ยนแปลงลงใน devnet เชนก็สะท้อนตำแหน่ง (pointer) ใหม่ในทันที Block explorers แสดงลิงก์ที่อัปเดตแล้วโดยไม่มีความล่าช้า
แต่กระเป๋าเงิน (wallet) ของผมกลับล่าช้า มันยังคงแสดงรูปภาพเก่าอยู่หลายนาที โดยดึงเวอร์ชันที่ทำ cache ไว้มาแสดงอย่างดื้อดึง ทั้งที่ข้อมูลพื้นฐานบนเชนได้เปลี่ยนไปแล้ว นี่คือความเป็นจริงในทางปฏิบัติที่นักพัฒนาต้องวางแผนรับมือ Solana ledger นั้นรวดเร็ว เวลาในการยืนยันธุรกรรม (confirmation times) ก็สั้น ทว่าเลเยอร์ด้านภาพ (visual layer) ที่ผู้ใช้งานโต้ตอบด้วยนั้น ขึ้นอยู่กับ HTTP caches, การแพร่กระจายของ CDN (CDN propagation) และช่วงเวลาการรีเฟรชเฉพาะของแต่ละกระเป๋าเงิน หากคุณสร้าง dynamic NFT ที่เปลี่ยนแปลงตามเหตุการณ์ในโลกจริง คุณไม่สามารถคาดหวังได้ว่าผู้ใช้จะเห็นการเปลี่ยนแปลงทันทีที่ธุรกรรมสำเร็จ คุณจำเป็นต้องมีกลยุทธ์ cache-busting, การทำ versioning ในเส้นทาง URI หรือการสร้างตัวกระตุ้นการรีเฟรช (refresh triggers) ที่ชัดเจนใน frontend ของคุณ
สิ่งที่จะเกิดขึ้นต่อไป
การทดลองบน devnet ของผมได้วางรากฐานสำหรับโปรเจกต์ที่มีความไดนามิกมากขึ้น ขั้นตอนต่อไปคือการสร้างคอลเลกชัน
