เนื้อหานี้ต้องเรียกได้ว่าเป็นการจับรวบรวมความรู้ ความเข้าใจหลายเรื่อง เพื่อให้เกิดนำไปใช้งานจริงๆได้แล้ว โดยช่วงแรก ผมจะแยกความรู้ทีละเรื่องออกมาจากกัน เพื่อให้ไม่ งง แล้วช่วงหลัง ผมจะอธิบาย พร้อมกับตัวอย่างโค้ด เพื่อให้เห็นภาพ และเข้าใจนะครับ
Smart Contract คืออะไร
ทบทวนกันอีกที เพราะอธิบายไปหลายรอบแล้ว สรุปสั้นๆ มันคือ Code ที่เก็บเอาไว้ใน Blockchain เท่านั้น ซึ่งมันจะเป็นในรูปของ Bytecode ไม่ใช่โค้ดที่เราอ่านออก และการจะเรียกใช้งาน ก็ต้องเรียกผ่าน ABI เพื่อให้มันไปคุยกับ EVM แล้วประมวลผลได้อย่างที่เราต้องการ
ERC-20 คืออะไร
อืม เรื่องนี้ถือว่าเป็นเรื่องระเบิดสมองอีกเรื่องนึงในตอนแรกที่ผมเข้าใจมันเลยก็ว่าได้ ต้องย้อนกลับไปที่ Blockchain network ขั้นพื้นฐานกันเลยทีเดียว โดยปกติแล้ว Blockchain network ใดๆ จะมี Native token ได้เพียง 1 Token เท่านั้น ซึ่งเค้าเรียกว่า Coin ตัวอย่าง Bitcoin Blockchain Network มี Native Token ชื่อ BTC หรือที่เราเรียกว่า Bitcoin ตรงกันเลย , Ethereum Blockchain Network มี Native Token ที่ชื่อว่า ETH หรือ Ethereum ด้วยเช่นกัน
ทีนี้ การที่เราจะมี Token ใหม่มาเป็นของตัวเราเอง ในช่วงยุคแรก ต้องใช้การ Fork project จาก bitcoin ต้นฉบับมา แล้วก็รัน node blockchain เป็นของตัวเอง เพื่อเอามาตั้งเป็นชื่อใหม่ ที่ไม่ซ้ำเดิม (หรือบางครั้ง ก็ fork กันมาเป็นทอดๆ) ในยุคแรกๆ จึงเกิด project clone Bitcoin ขึ้นมามากมายเต็มไปหมด ทีนี้ Vitalik (คนที่คิดค้น Ethereum) เค้าก็เล็งเห็นว่า มันไม่ work แน่ๆถ้าจะทำอย่างนี้กันต่อไป เพราะมันไม่ได้ประสิทธิภาพ เมื่อเทียบกับการลงทุนซ้ำยังมีปัญหาการ cross chain ที่ยังทำตรงๆไม่ได้ ยิ่งเพิ่มจำนวนก็ยิ่งสร้างความยากลำบากไปอีก (ทุกวันนี้ก็ยังทำไม่ได้นะ แต่มีหลาย service ถูกสร้างขึ้นมาเพื่อเป็นตัวกลางอีกทีต่างหาก) เค้าก็เลยสร้าง Smart Contract ขึ้นมาทำงานบน Blockchain อีกที เพื่อให้มันทำงานได้หลากหลาย ไม่ต้องมา fork กันรัวๆ ทำงานบน ethereum เลยทีเดียวจบ ซึ่งเจ้านี่คือสิ่งที่ปฏิวัติวงการเลยก็ว่าได้แหล่ะ
ที่นี้ด้วยความที่ smart contract มันก็เป็น a piece of code อะนะ เราจะเขียนให้มันทำอะไรยังไงก็ได้แล้ว เค้าก็เลยร่าง standard ขึ้นมาหลายตัว เพื่อให้มันทำหน้าที่แตกต่างกันไป ให้เป็นมาตรฐานการเอาไปต่อยอดการใช้งานในแต่ละเรื่อง มันคือ ERC standard ทั้งหลาย (คุ้นๆมั้ย ก่อนนี้เราคุยกันถึงเรื่อง BIP33 BIP39 BIP44 ซึ่งมันคือ BIP standard ที่ทำงานกับ Bitcoin นั่นแหล่ะ) ทีนี้ ความน่าสนใจ และที่เราจะคุยกันในวันนี้ก็คือ ERC20 นั่นเอง
ERC20 มันคือ standard ที่เอาไว้กำหนดพื้นฐานการสร้าง Token ขึ้นมา รวมทั้ง function พื้นฐานต่างๆ เพื่อใช้งานใน Smart Contract ของ Ethereum network และแน่นอน มันเป็น standard กลาง ดังนั้น ใครจะใช้ หรือใครจะไม่ใช้ก็ได้ แต่ถ้าใครที่ใช้งาน ก็จะมั่นใจได้ว่า application ที่ทำงานบน standard เดียวกัน จะทำงานร่วมกันได้นั่นเองครับ (คุ้นๆ เหมือน BIP อีกละ) และนอกเหนือจาก ERC20 ที่เราคุ้นเคยกันแล้ว มันมีที่เราคุ้นเคยกันอีก คือ NFT (Non Fungible Token) ซึ่งมันทำงานบน standard ERC721 นั่นเองครับ
การทำงานของ ERC20
คราวนี้แหล่ะ ที่ต้องบอกว่า ความคิดของคุณต่อ Token จะเปลี่ยนไป ย้อนกลับไปที่ code ที่เราลองเขียนกันก่อนหน้า ก็คือมี function transfer แล้วก็ตรวจสอบ balance ได้ แต่สิ่งเหล่านั้น ทำได้เฉพาะ Native Coin เท่านั้น หมายความว่า เราไม่สามารถโอน Token, เราไม่สามารถ รับโอน Token ได้เลย เพราะมันไม่มีคำสั่งให้ทำ ทีนี้ ERC20 ก็เลย เกิดขึ้นมาเพื่อแก้ปัญหานี้
ยังไง?
ง่ายๆ ด้วยการที่ สร้างสมุดบัญชีขึ้นมาหนึ่งเล่ม ต่อหนึ่ง Token แล้วใช้จดว่าใครมี Token นั้น จำนวนเท่าไร ส่วนการโอน Token และการรับโอน Token ก็เป็นเพียงการ ปรับตัวเลขในสมุดบัญชีเล่มนั้นเท่านั้น ซึ่งสมุดบัญชีที่ว่า ก็เก็บอยู่ใน Smart Contract อีกทีนี่แหล่ะ ในส่วนของ state นั่นเอง (ถ้าเราจำกันได้)
จบครับ ดังนั้น การที่เรามี Token ต่างๆ ใน ERC20 standard นั้น ไม่ได้แปลว่าเรามีในกระเป๋าเราจริงๆ แต่มันแค่จดไว้ว่า “เรามีเท่าไร” เท่านั้น แต่ก็นั่นล่ะครับ ด้วยความที่เป็น smart contract และข้อมูลที่เก็บใน blockchain ดังนั้น ตราบใดที่ smart contract ไม่มีรูรั่ว และ Blockchain ไม่ถูก hack ด้วย protocol เอง ก็ไม่มีวันที่เราจะเสีย token นั้นไปจากสมุดบัญชีอย่างแน่นอน
ทีนี้ก็น่าจะตอบคำถามข้อสงสัย ว่าทำไมเวลาที่เราดูเว็บ etherscan , bscscan แล้วมันต้องแยกหน้า transactions (หน้าแรก) ออกจาก Token ต่างๆเช่น ERC-20, ERC-721 เป็นต้น ก็ด้วยเหตุผลตามนี้นั่นเอง เพราะมันไม่ใช่ Native token ที่จะเอามากองรวมกันได้ (แล้วจะอธิบายต่อถึง function ที่ใช้อ่านค่า และวิธีการก็ต่างกันด้วยนะ)
การตรวจสอบ Balance Token ERC20 ด้วย Web3
และจากที่อธิบายทั้งหมดข้างบนนี้ จึงเป็นเหตุผลว่าทำไมเราไม่สามารถใช้คำสั่ง
w3.eth.get_balance(my_account)
เพื่ออ่าน Balance ของ Token ที่ไม่ใช่ Native Token ได้ แต่เราจะต้องใช้การอ่านค่าจากสมุดบัญชี หรือ การเรียก function อ่านข้อมูล balance จาก smart contract ของ Token นั้นๆอีกทีนั่นเอง
สิ่งที่เราต้องมี หากจะทำงานกับ Token ERC20 (ผมย้ำ ERC20 เพราะถ้าเป็น token ด้วย standard อื่น อาจจะไม่ได้ใช้ function name หรือ parameter แบบนี้ก็เป็นได้) ก็คือ Smart contract address ของ Token ที่เราสนใจ ซึ่งถ้าไม่รู้จะหาจากไหน ลองอ่าน เพิ่มเหรียญที่ Metamask มองไม่เห็น มีหลายวิธีอยู่ครับ
จาก concept ที่เรารู้แล้ว ว่า smart contract ที่เป็นสมุดบัญชีของ Token ตัวนั้นๆมันทำหน้าที่เก็บค่าของ token นั้นของทุกคนเอาไว้ เราจึงต้องไปติดต่อกับ smart contract ตัวที่เราต้องการนี่แหล่ะ ถึงจะตอบมาได้อย่างถูกต้อง และจริงๆจะมีอีกสิ่งที่เราต้องเตรียมก็คือ ABI แต่ด้วยความที่ token ที่ implement ด้วย ERC20 นั้น ก็จะมี ABI พื้นฐานที่เหมือนกันหมดอยู่แล้ว เรื่องนี้เราจึงไม่ต้องคิดมาก ลอกกันมาได้เลย
ไปขอรับ ERC20 Token
เหมือนเดิมครับ อยู่ดีๆ เราจะมี ERC20 token ในกระเป๋าเรานั้นไม่ได้ เราต้องสร้าง token ก่อน แล้วก็ค่อย mint ขึ้นมาให้เราเอง แต่อย่างนั้นมันยากอยู่ ต้องทำอีกเยอะเลยล่ะ ไปขอรับจากที่คนอื่นเค้าพร้อมโอนให้เราดีกว่า ซึ่งผมเลือกขอรับจาก https://erc20faucet.com/ ซึ่งเค้าจะโอน FAU Token มาให้เรา โดย Contract Address ของ Token นี้คือ 0xFab46E002BbF0b4509813474841E0716E6730136 ซึ่งเป็น Token ตาม standard ของ ERC20 อย่างที่เราต้องการนี่แหล่ะเราจะได้เอามาเช็ค balance กันต่อได้ ซึ่งผมขอรับมาแล้วเป็นจำนวน 1 Token
เริ่มต้นเขียน Code
เช่นเคยเหมือนทุกครั้ง เริ่มต้นที่ Google Colab ครับ เขียนโค้ด
!pip install web3
!pip install --force-reinstall jsonschema==3.2.0
เพื่อติดตั้ง web3 , reinstall jsonschema
จากนั้น ก็เหมือน เขียน Code เพื่อสร้าง Transaction ใน web3 Blockchain จริงๆ เลยครับ
from web3 import Web3
node_url = '{HTTP_URL_YOU_GET_FROM_ALCHEMY}'
w3 = Web3(Web3.HTTPProvider(node_url))
w3.isConnected()
เพื่อ connect node ให้เรียบร้อย ตรงนี้ไม่อธิบายแล้วนะครับ ในบทความก่อนหน้าอธิบายหลายรอบแล้วครับ
จากนั้นตั้งค่า ABI โดยปกติแล้ว ABI สำหรับ ERC20 จะหน้าตายาวๆแบบนี้ แต่ว่าเราจะอ่าน Balance กับดูทศนิยม และ ชื่อ symbol เราสามารถตัดสั้นๆมาก็ได้ครับ (จะใส่ตัวเต็มก็ได้ ไม่ผิดทั้งคู่ แต่ใส่แค่ไหน ก็เรียกใช้ได้แค่นั้น)
minABI = [
{
'inputs': [{'internalType': 'address', 'name': 'account', 'type': 'address'}],
'name': 'balanceOf',
'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
},
{
'inputs': [],
'name': 'decimals',
'outputs': [{'internalType': 'uint8', 'name': '', 'type': 'uint8'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
},
{
'inputs': [],
'name': 'symbol',
'outputs': [{'internalType': 'string', 'name': '', 'type': 'string'}],
'stateMutability': 'view', 'type': 'function', 'constant': True
}
]
อธิบาย ก็คือ
- function แรกคือ function balanceOf ที่เอาไว้เช็คยอดคงเหลือโดยรับ input เป็น address และ return output เป็น uint256
- function สอง คือ decimals เอาไว้หาว่า token นี้มีทศนิยมกี่ตำแหน่ง (เดี๋ยวอธิบายอีกที)
- function สาม คือ symbol เอาไว้อ่านว่า token นี้คือ symbol อะไร เดี๋ยวจะใช้ function นี้ประกอบร่างหน้าจอนึงของ metamask ให้ดู
ต่อมาเราก็เขียนโค้ดเพื่ออ่านค่าแบบนี้เลย
เนื้อหาที่ซ่อนอยู่ คือ เนื้อหาที่เป็นการอธิบายโค้ด และ ตัวอย่างโค้ด สำหรับการอ่านค่า token ออกมา พร้อม source code ตัวอย่างให้ download ไปทดลองเล่นใน google colab ได้ด้วยตัวเอง
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachโดยตอนนี้ contact_instance ก็พร้อมแล้ว สำหรับการเรียกใช้งาน function / event ที่อยู่ในนั้น
จากนั้น ก็อ่านออกมาได้เลย
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachก็จะได้
1000000000000000000
ทีนี้ Token ที่เราได้รับ อาจจะไม่ได้เป็นหน่วย wei เสมอไปนะครับ เรื่องนี้ต้องระวังให้ดีๆ ถ้าเป็นหน่วย wei ก็คือต้องมี ทศนิยม 18 ตำแหน่งเหมือนอย่าง Ethereum แต่ว่าหลายๆ Token ไม่ได้มี 18 ตำแหน่ง ดังนั้น การแปลงหน่วยไปมาต้องระวังให้ดีๆนะครับ ไม่อย่างนั้นตอนที่เราเอามาใช้คำนวณ หรือ ตอนที่ใช้ transfer อาจจะทำให้ผิดพลาดได้ทันทีครับ
สำหรับการตรวจสอบว่าทศนิยมดังกล่าวนั้น มีทศนิยมกี่ตำแหน่ง สามารถตรวจสอบได้จาก
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachก็จะได้
18
ดังนั้นแปลว่าเราทำอะไรกับตัวนี้ก็ได้ โดยเลขที่เราเห็นต้องคูณหรือ หาร 18 เสมอ เช่น ทำให้เป็นหน่วยจำนวนเต็ม 10 เราก็จะได้ 1000000000000000000/1e18 = 1 นั่นเองครับ
และการตรวจสอบว่า Token นี้มีชื่อ symbol อะไรก็ทำได้จาก
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachก็จะได้
FAU
ซึ่งทั้งสาม function นี้คุ้นๆมั้ยครับ มันเหมือนหน้าตาตอนที่เรา add new symbol ใน metamask เลยนั่นเอง
ทีนี้น่าจะนึกออกละ ว่าจริงๆแล้ว metamask ที่เราใช้งาน ก็มีคำสั่งเบื้องหลังแบบนี้แหล่ะครับ ใครที่เข้าใจ และควบคุมด้วยโค้ดได้ ก็จะสามารถสั่งให้ทำงานอย่างที่เราต้องการได้ ด้วย code ชุดนี้นี่เองครับ
สั้งโอน Token ERC20 ด้วย Web3
เอาล่ะครับ การ read data มันธรรมดาไปแล้ว เรามาสั่งโอนออกกันบ้างดีกว่า ให้เราเพิ่มส่วนนี้ลงไปใน ABI ครับ
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachจากนั้นต้องรัน ตัว contract_instance ใหม่อีกที เพื่อให้รู้จัก ABI ที่มี transfer function ด้วย
มันก็คือ ABI ส่วนที่ทำหน้าที่สั่ง transfer นั่นเองครับ
แต่ว่าการสั่งโอน ไม่ได้ง่ายเหมือนอย่างตอนที่ read value เพราะว่าตอนที่ read value ใครก็สามารถอ่านได้ แต่ว่าตอนที่สั่งโอนนั้น เราต้องมี private key ในการยืนยันว่าเราเป็นเจ้าของ address นี้นะ และต้องการสั่งโอนด้วยตัวเองนะ ขั้นตอนจะเหมือนกันกับ เขียน Code เพื่อสร้าง Transaction ใน web3 Blockchain จริงๆ นั่นล่ะครับ ต่างกันเล็กน้อยตอนที่ตั้งค่า transaction
หาค่า nonce ก่อน เพราะต้องเอาไปใช้ต่อ
nonce = w3.eth.get_transaction_count(my_address)
จากนั้น Initial function เพื่อเตรียมสำหรับการ transfer
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachจากนั้นเหมือนเดิมครับ เอา private key มา sign เข้าไป (ใช้ private key ให้ถูก account ด้วยนะครับ ต้องเป็น account ของคนที่กำลังทำรายการโอนออกนะครับ)
เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น
เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reachจากนั้นก็ส่งธุรกรรมเข้าไปโลด
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
และเช่นเดิม ตรวจสอบ transaction ได้ด้วย
w3.eth.get_transaction(tx_hash)
รอหลังจากที่ mine แล้ว ก็จะได้
AttributeDict({'accessList': [],
'blockHash': HexBytes('0xa66318ff8172dd9de840565a7cd6c5fa47f7cac356dc146cbe391f33fde1aa6c'),
'blockNumber': 12094372,
'chainId': '0x3',
'from': '0x34652b56ab34E1aDE3FD6106dD6098E61597B72D',
'gas': 120000,
'gasPrice': 100000000000,
'hash': HexBytes('0x680da1bc9bdc237f875b3ed3c82bd4747efa3dfdeb10b75e2c667475f09fdf71'),
'input': '0xa9059cbb00000000000000000000000014ca6e7eea0fa533986bc01501eff9393ab50780000000000000000000000000000000000000000000000000016345785d8a0000',
'maxFeePerGas': 100000000000,
'maxPriorityFeePerGas': 20000000000,
'nonce': 1,
'r': HexBytes('0x87619e517e910891491717f5d88d5bcd66e9e2d24321aa3c5d55585ffc09b78b'),
's': HexBytes('0x0b1327ba667335601687b77343c3f606de0e803deab986fda2b7813953eb16d8'),
'to': '0xFab46E002BbF0b4509813474841E0716E6730136',
'transactionIndex': 14,
'type': '0x2',
'v': 1,
'value': 0})
เป็นอันเรียบร้อย
แต่เอาชัวร์ ไหนลองเช็คปลายทางหน่อยซิ ได้จริงมั้ย สูตรเดิมเลย แต่เปลี่ยน address ที่ต้องการเช็ค
contract_instance.functions.balanceOf(to_destination).call()
จะได้
100000000000000000
ซึ่งมันก็เท่ากับ amount ที่เราสั่งโอนไปเลย คราวนี้ได้เลขตรงกัน เพราะว่า gas ที่จ่ายเราใช้ ether นั่นเองครับ เลขโอนออกกับรับเข้าก็จะตรงกัน และตรงกับเลข Token ที่ถูกหักจากต้นทางด้วย
เป็นอย่างไรกันบ้างครับ ทั้งหมดนี้ ก็คือครบแล้ว สำหรับเรื่องพื้นฐานที่เราจะได้เจอกันบ่อยมากๆ และเป็นสิ่งที่เราใช้งาน และจัดการกันอยู่เป็นประจำใน metamask นั่นล่ะครับ สำหรับใครที่ขี้เกียจทำมือ ก็เขียนโค้ดเพื่อสั่งงานให้มันทำงานแทนเราได้เลย อย่างเช่นการอ่านค่า Token ERC20 เนี่ย เราก็เขียนระบุลงไปเลย ว่าจะอ่าน Token ใดบ้างแล้วก็วน loop เอามาเก็บเข้า google sheet เราเลยในคราวเดียวก็ได้ ก็ไม่ต้องจดมืออีกต่อไป หรือว่าใครที่เอาไปเขียนเพื่ออ่าน smart contract DEFI ต่างๆ ก็ไปทำความเข้าใจ ABI / Function เพิ่มเติม และค่าทาง economic ต่างๆ เพื่อให้อ่านค่าออกมาแล้วเอามาคำนวณได้อย่างถูกต้อง แต่อันนั้นก็ไปขั้นที่เรียกได้ว่า advance แล้วล่ะ (ถ้าทำได้ขนาดนั้นก็ทำได้แทบทุกอย่างละ)
ขอให้สนุกกับการทดสอบและทดลอง และเช่นเคย ผมก็เอาโค้ดมาทิ้งเอาไว้ให้ download กันเช่นเดิมครับ