HD Wallet และ Transaction บน Tron network

ก่อนนี้เราคุยกันแต่เรื่อง Ethereum ตอนนี้เราจะลองไปจับ Tron network กันดูบ้าง แต่เอาจริงๆแล้ว Tron network เนี่ยก็ได้กลิ่นอาย Ethereum มาเยอะมาก code หลายส่วน โดยเฉพาะ EVM นี่ก็ลอกกันมา (แต่เอามาปรับปรุงบางเรื่อง) ดังนั้น สิ่งที่เราจะได้ ก็คือการทำงานที่คล้ายกันนั้นเอง แต่เราจะมาลองพิสูจน์กันในบทความนี้ ว่า HDWallet ที่เราใช้มาก่อนหน้า สามารถเอามาใช้งานได้หรือไม่ และการทำ Transaction บน Tron network นั้นต้องทำอย่างไร แตกต่างกันมากน้อยแค่ไหน

สร้างกระเป๋า Tron ด้วย HD Wallet

อ้างอิงมาจาก ลองเขียน Code เพื่อสร้าง Wallet ตาม BIP32 BIP39 BIP44 เลย แต่ว่าเปลี่ยนการตั้งค่าของ SYMBOL ให้ไปเรียก ‘TRX’ แทน และตัด entropy ออก เพราะเราจะใช้ mnemonic เดิมจากบทความนั้นมา จะได้หน้าตาแบบนี้

from hdwallet import HDWallet
from hdwallet.utils import generate_entropy
from hdwallet.symbols import TRX as SYMBOL
from typing import Optional

import json

# Choose strength 128, 160, 192, 224 or 256
STRENGTH: int = 128  # Default is 128
# Choose language english, french, italian, spanish, chinese_simplified, chinese_traditional, japanese or korean
LANGUAGE: str = "english"  # Default is english
# Secret passphrase for mnemonic
PASSPHRASE: Optional[str] = None  # 

จากนั้น เราก็กำหนด MNEMONIC โดยอ้างอิงจาก MNEMONIC ของบทความก่อนหน้าที่เราได้สร้างเอาไว้เลยครับ ลงไปดังนี้

# Initialize Bitcoin mainnet HDWallet
hdwallet: HDWallet = HDWallet(symbol=SYMBOL, use_default_path=False)

# Get Symbol wallet from mnemonic
MNEMONIC: str = "swear today race input hollow luxury green dignity little rather damp sudden"
hdwallet.from_mnemonic(
    mnemonic=MNEMONIC, language=LANGUAGE, passphrase=PASSPHRASE
)

แล้วก็สร้าง wallet แรกขึ้นมา โดยกำหนด path ให้เป็น m/44’/195’/0’/0/0 ที่เป็น 195 เพราะอ้างอิงจาก SLIP44 เราต้องกำหนดให้เป็น 195 เพราะ coin type เป็น TRX นั่นเอง (แต่ถึงเราใส่ 0 ก็สร้างได้นะ แต่มันจะให้ผลที่แตกต่างกัน และแปลว่าเราไม่ได้ทำตาม standard)

#create wallet number 0 of tron network
hdwallet.from_path("m/44'/195'/0'/0/0")
print(json.dumps(hdwallet.dumps(), indent=4, ensure_ascii=False))

ผลที่ได้ก็จะเป็นแบบนี้เลย

{
    "cryptocurrency": "Tron",
    "symbol": "TRX",
    "network": "mainnet",
    "strength": 128,
    "entropy": "db9c6ac1ba56cf0a9991f0829648dcec",
    "mnemonic": "swear today race input hollow luxury green dignity little rather damp sudden",
    "language": "english",
    "passphrase": null,
    "seed": "e0b23f31926a9b633764bb11434b9e3c3d084a5cf765c170c1c797a390749548b1868dc20137eab85fa21929924723f6f83b0bedf0f8ec253fd3f0700fbe60d9",
    "root_xprivate_key": "xprv9s21ZrQH143K3orJm7v9BANJh2JGze5VZLdwdD8WHduLLdaHs1STdFXSubTH9HVHrThibSnZh7ca2pRHmNTTwfvnkd9xuDPnftRAUGznVNS",
    "root_xpublic_key": "xpub661MyMwAqRbcGHvms9T9YJK3F48mQ6oLvZZYRbY7qySKDRuSQYkiB3qvkrij5Mg2hNL4Rxh2wKKBAdQTUE8mjMcVuJ2EPofuKsGsai3EfrJ",
    "xprivate_key": "xprvA36mofhF5QrSZZNcPH7nQ6Yhs2oRTA1MMYZtZB2LfvsxpenAmrVz2JkXB5w2yue9FTB9GiqRm3ZQ4fyXJVW7dQX1EX2FUbzUZVZtJHcJy2w",
    "xpublic_key": "xpub6G68DBE8unQjn3T5VJenmEVSR4durcjCimVVMZRxEGQwhT7KKPpEa7512PA69GcuKvk8ezNkQ3aK5v32koE7ERMCm33SPFQdNiXfuLHFb5n",
    "uncompressed": "2584c8f77a0bd49db088673e2ed57d614b3422e5c8a0f71cf835b7edf5d1178fd7ad31760b294dae1dc93d5f342acd4e158aab57d73450d17e08290f33b0e603",
    "compressed": "032584c8f77a0bd49db088673e2ed57d614b3422e5c8a0f71cf835b7edf5d1178f",
    "chain_code": "69246855708c9f6da41012a860ab174f84e48eb947c0437fa9de4172f1d9963b",
    "private_key": "047ebfd46c61bdb1abc7c474863bd08f6903c73314dde0845fac0d3894b9889d",
    "public_key": "032584c8f77a0bd49db088673e2ed57d614b3422e5c8a0f71cf835b7edf5d1178f",
    "wif": "KwNSz1GSviUE3D7Wimj3QRP1M2DdAs7tHQFiXfdL82SFwg34hhwP",
    "finger_print": "aad2e625",
    "semantic": "p2pkh",
    "path": "m/44'/195'/0'/0/0",
    "hash": "aad2e625e3ec206a6af844ae846ff4aa5b4afd76",
    "addresses": {
        "p2pkh": "TT1LWHMdgCPgyp7zJm58dL3uwxRLDg4ptR",
        "p2sh": "3CLqPNkgXvKQpWxYuuuLNvWNKsMcsW7LzM",
        "p2wpkh": "bc1q4tfwvf0rassx56hcgjhggml54fd54ltkkq89zf",
        "p2wpkh_in_p2sh": "3PaFFJDtaiLC1sgRGwW5Cfu5gK9gKBEo5H",
        "p2wsh": "bc1qnu2jdv383ln48vqvg0j7gurmd63xzy5a2dak2pxhhz8f5ea9jv8qsz9jzq",
        "p2wsh_in_p2sh": "3NttEw9PCxS4aHeAcZ1QCThBXHKgQ9tRaD"
    }
}

พิสูจน์ความจริงหน่อย ว่าเราทำได้ตาม standard ด้วยการเอา mnemonic ไป import เข้า Tronlink wallet app ที่ Tron network เค้าแนะนำ ว่าได้ Address ตามนี้มั้ย และนี่คือผลที่ได้

แม้ว่า ไม่ได้เห็นเต็มๆทั้งหมด แต่มันก็คือ address เดียวกันแน่ๆกับ TT1LWHMdgCPgyp7zJm58dL3uwxRLDg4ptR เป็นอันว่า เราก็สามารถสร้างได้แล้ว และใช้งานได้จริงตรงกันด้วย

ขอรับ Coin, token จาก Faucet

คราวนี้ เราก็ไปกันต่อเลย กับการสร้าง Transaction เพื่อเอามาใช้งาน ซึ่งแน่นอน ถ้าเราจะสร้างได้ เราจะต้องมี gas fee ไว้จ่ายก่อน ดังนั้น เราก็ไปขอรับได้จาก Faucet โดยผมใช้ Nile testnet ดังนั้นสามารถขอรับได้จากที่นี่ครับ https://nileex.io/join/getJoinPage โดยขอมาทั้ง TRX, USDJ เลย เอาไว้ใช้ทดสอบทั้ง Coin , Token (TRC20)

เมื่อเรามี gas fee แล้ว เราก็มาลุยกัน

เชื่อมต่อกับ Tron network และ call smart contract

ก็จะคล้ายกันกับ ลองเขียน Code เพื่อสร้าง Transaction ใน web3 Tester provider และ เขียน Code เพื่อสร้าง Transaction ใน web3 Blockchain จริงๆ แต่เราจะเปลี่ยนมาใช้ Library tronpy กันแทน ดังนั้นเริ่มต้นติดตั้งก่อน

!pip install tronpy

จากนั้น ก็เชื่อมต่อ testnet ได้เลย ข้อดีของ Tron network ก็คือ เชื่อม testnet ตรงๆได้เลย ไม่ต้องตั้ง node เอง หรือไปแชร์ node จาก ผู้ให้บริการอื่นๆ ดังนั้น เราก็เชื่อมตรงๆเลยแบบนี้

from tronpy import Tron

client = Tron(network="nile")  # The Nile Testnet is preset
client.get_block()

โดยบรรทัดสุดท้าย จะเป็นการตรวจสอบ ว่าสามารถดึงข้อมูล Block ล่าสุดออกมาได้จริง ก็แปลได้ว่าว่าเชื่อมได้แล้ว จากนั้น เราจะอ่านว่า wallet เรามี coin อยู่เท่าไร ด้วยการกำหนด Address ของกระเป๋าเรา และ เรียกคำสั่งอ่าน balance ออกมาดังนี้

MYADDRESS = 'TT1LWHMdgCPgyp7zJm58dL3uwxRLDg4ptR'
client.get_account_balance(MYADDRESS)

จะได้

Decimal('2000')

เป็นจำนวนเต็ม 10 ก็คือ สองพัน TRX นั่นเอง

และแน่นอน ถ้าเราต้องการอ่าน USDJ ที่เป็น TRC20 เราก็จะต้องเพิ่มท่าไปอีกนิด แต่หลังจากที่ทำท่ายากมาใน EVM แล้ว พบว่าของ Tron นั้น ง่ายกว่ามาก ก็คือเพียงแค่

USDJCONTRACT = 'TLBaRhANQoJFTqre9Nf1mjuwNWjCJeYqUL'
cntr = client.get_contract(USDJCONTRACT)
precision = cntr.functions.decimals()
print('Balance:', cntr.functions.balanceOf(MYADDRESS) / 10 ** precision)

ก็จะได้ return เป็น 100.0 เลย อธิบายโค้ด ก็คือ บรรทัดแรก คือประกาศว่า USDJ มี contract address อะไร จากนั้น ก็เรียกให้ client ไปเชื่อมต่อ contract นั้น เก็บใน cntr แล้วก็เรียก function ที่อยู่ข้างในคือ decimals เพื่อหาว่าตัวนี้ ทำงานด้วยตัวเลขกี่ digit (เพื่อเอาไปใช้ในการแปลงหน่วยขั้นต่อไป) จากนั้น ก็เรียก function balanceOf ออกมา แล้วหาด้วยจำนวน digit และ print balance ที่มี ก็จะได้ 100. ตามที่เราต้องการนั่นเองครับ เรียบง่ายกว่า EVM เยอะเลย

ทีนี้ ถ้าเราอยากรู้ว่าใน contract ตัวนั้น มีอะไรให้ใช้งานบ้าง ก็ทำตามนี้ได้เลย

for f in cntr.functions:
    print(f)  # prints function signature(i.e. type info)

จะได้

function allowance(address src, address guy) view returns (uint256 )
function approve(address guy, uint256 wad) returns (bool )
function approve(address guy, uint256 wad) returns (bool )
function authority() view returns (address )
function balanceOf(address src) view returns (uint256 )
function burn(uint256 wad)
function burn(uint256 wad)
function decimals() view returns (uint256 )
function mint(address guy, uint256 wad)
function mint(address guy, uint256 wad)
function move(address src, address dst, uint256 wad)
function name() view returns (string )
function owner() view returns (address )
function pull(address src, uint256 wad)
function push(address dst, uint256 wad)
function setAuthority(address authority_) returns (bool result)
function setName(string name_)
function setOwner(address owner_)
function setSymbol(string symbol_)
function start()
function stop()
function stopped() view returns (bool )
function symbol() view returns (string )
function totalSupply() view returns (uint256 )
function transfer(address dst, uint256 wad) returns (bool )
function transferFrom(address src, address dst, uint256 wad) returns (bool )

นี่ก็คือ function ทั้งหมด ที่เราจะสามารถเรียกใช้งานได้ตาม contrct ตัวนี้นั่นเองครับ (แต่บางอัน ก็เฉพาะคนที่ได้สิทธ์เท่านั้นนะ ไม่ใช่ว่าใครก็ได้)

Transfer TRC20 token

เอาล่ะมาสั่งโอนกันบ้าง ก็ไม่น่าจะมีปัญหาอะไรแต่ก็มีท่ายากขึ้น เพราะต้อง sign transaction ก่อนนั้นเอง

เราก็ใช้ Tronlink สร้างกระเป๋าที่สองขึ้นมา เพื่อใช้รับโอน Token จากนั้นเราก็สั่งโอน token เข้าไปเก็บดังนี้

from tronpy.keys import PrivateKey
priv_key = PrivateKey(bytes.fromhex("047ebfd46c61bdb1abc7c474863bd08f6903c73314dde0845fac0d3894b9889d"))

TOADDRESS = 'TV2MnY9n3vuTd1D5Ao9d3W4KnRVp4CRNTz'
txn = (
    cntr.functions.transfer(TOADDRESS,2_000)
    .with_owner(MYADDRESS)  # address of the private key
    .fee_limit(20_000_000)
    .build()
    .sign(priv_key)
)

txn.broadcast()

ก็เป็นอันเรียบร้อย เช็คปลายทางสักหน่อยว่าได้จริง

print('Balance:', cntr.functions.balanceOf(TOADDRESS) / 10 ** precision)

ข้อสังเกตหนึ่งก็คือ TRON network เค้าใช้การคั่นหลักพัน ด้วย _ แทนลูกน้ำครับ ทำให้เราอ่านได้ง่ายขึ้น แต่ถ้าถามว่า ไม่ใช้ _ คั่นแล้วทำงานได้มั้ย คำตอบคือ ได้เหมือนเดิมครับ

Gas fee near zero

เอาจริงๆแล้ว ถ้าเราบริหาร Tron ให้ดีๆ เราสามารถทำธุรกรรมบน Tron network โดยไม่ต้องจ่ายค่า Gas เลยก็ได้นะครับ

อธิบายคร่าวๆ ให้เข้าใจ ก็คือ ทุกครั้งที่ทำธุรกรรม Tron จะใช้ Bandwidth และบางธุรกรรม ก็จะต้องการ Energy ด้วย ซึ่ง bandwidth , energy นี้เราเอาเงินไปซื้อมา ไม่ได้ และถ้าเราไม่มีเพียงพอสำหรับการใช้งาน tron network ก็จะมาหัก TRX เราไปใช้แทนทั้ง bandwidth และ energy นั่นเอง

แต่ว่าเราสามารถเอา TRX ไป freeze เอาไว้ เพื่อขอรับเป็น Bandwidth และ Energy ได้ โดยการไปที่ https://nile.tronscan.io/#/wallet และกด Connect wallet ที่ขวาบน จากนั้นตรงกลางจะมีปุ่ม obtain เพื่อให้เอา Tron เข้าไป lock เพื่อรับ Bandwidth / Energy ออกมาใช้งาน

แต่ระหว่างที่ lock เอาไว้ 3 วัน เราจะไม่สามารถเอา TRX นั้นออกมาใช้งานได้อีกเลย ต้องพ้น 3 วันเท่านั้น และในทุกๆวัน bandwidth / energy ก็จะเพิ่มขึ้นเต็มหลอดอัตโนมัติเอง ดังนั้นเราแค่รอวันใหม่ก็ได้หากไม่อยาก lock เพิ่ม ส่วน ถ้าเราไม่ lock หรือไม่ทำอะไรเลย เดิมๆ จะมี 1500 bandwidth สำหรับทุกคนทุกวัน และ 0 energy ดังนั้น ยังไงก็ต้อง Lock เพื่อให้ได้ energy มา แต่ข้อแนะนำคือ lock น้อยๆ หลัก10 หรือ หลัก 100 ก็ได้มาเยอะแยะมากมายแล้วครับ ไม่พอค่อยๆ lock เพิ่มเข้าไปอีกทีก็ได้ และการ lock จะต้อง lock แยกกัน ระหว่าง bandwidth และ energy ดังนั้น ต้องทำสองรอบ สองทีนะครับ ก็ลองบริหารดูครับ

ถ้าอยากเขียนโค้ดเพื่อสำรวจ resource แล้วล่ะก็ นี่เลย

client.get_account_resource(MYADDRESS)

จะได้

{'EnergyLimit': 449722,
 'EnergyUsed': 13384,
 'NetLimit': 7249,
 'TotalEnergyLimit': 90000000000,
 'TotalEnergyWeight': 300185119,
 'TotalNetLimit': 43200000000,
 'TotalNetWeight': 595885178,
 'freeNetLimit': 1500,
 'freeNetUsed': 1357,
 'tronPowerLimit': 1600}

และถ้าอยากเขียนโค้ด เพื่อ freeze / unfreeze ก็ได้เช่นกัน

txn = (
    client.trx.freeze_balance(MYADDRESS, 1_000_000, "ENERGY")
    .build()
    .sign(priv_key)
)
print(txn.txid)
txn.broadcast()

คือการ freeze 1 trx เข้าไปเพื่อรับเป็น energy นั่นเอง

และเหมือนเดิม ก็คือ source code python ในรูปแบบ python notebook (ใช้ได้กับ jupyter notebook / google colab) เอาไปรันดูตัวอย่างได้เลยครับ (อยู่ในเนื้อหาที่ซ่อนอยู่ด้านล่างนี้

--------------------------------------------------------------

เนื้อหาพิเศษ ต้องแลกด้วย Reach เท่านั้น

เนื้อหาส่วนนี้เป็นเนื้อหาพิเศษ จะต้องใช้ reach ในการเข้าอ่านเนื้อหาจุดนี้ เมื่อแลกด้วย reach แล้วจะสามารถอ่านเนื้อหาที่ซ่อนอยู่เพิ่มเติมได้ หากมี reach แล้วกรุณา login ก่อน อ่านรายละเอียดเพิ่มเติมเรื่อง Reach

Leave a Reply