
Lập trình PyCardano Bài 5: Tối ưu Hóa Phí Giao Dịch Bằng Cách Gộp UTxO (Consolidate UTxOs)
Xin chào các bạn, chào mừng đã quay trở lại với chuỗi bài hướng dẫn lập trình PyCardano!
Trong các bài trước, chúng ta đã biết cách tạo ví, nhận tiền testnet và thực hiện các giao dịch cơ bản. Tuy nhiên, khi tài khoản của bạn nhận tiền nhiều lần từ các nguồn khác nhau, một vấn đề sẽ nảy sinh: Sự phân mảnh UTxO. Bài học hôm nay sẽ giúp bạn giải quyết triệt để vấn đề này bằng kỹ thuật “Gộp UTxO” (Consolidate UTxOs).
1. Tại sao phải gộp UTxO?
Trong giao dịch trên mạng lưới Cardano (sử dụng mô hình EUTxO), mỗi lần bạn nhận tiền, hệ thống không cộng dồn vào một con số tổng duy nhất. Thay vào đó, bạn sẽ nhận được một UTxO (Unspent Transaction Output) riêng biệt.
Hãy tưởng tượng bạn có một con heo đất chứa đầy những đồng xu lẻ. Khi bạn cần mua một món đồ giá trị lớn, bạn phải mang một vốc tiền xu đi trả. Trong blockchain, việc dùng nhiều “đồng xu lẻ” (nhiều UTxO đầu vào) sẽ làm tăng kích thước dữ liệu của giao dịch, từ đó dẫn đến phí mạng (fee) cao hơn.
Mục tiêu của bài học: Viết một đoạn script gom toàn bộ các UTxO lẻ tẻ trong ví của bạn và gộp chúng lại thành một UTxO duy nhất. Điều này giúp:
-
Giảm số lượng đầu vào (inputs) cho các giao dịch trong tương lai.
-
Tiết kiệm đáng kể phí giao dịch.
-
Quản lý tài sản gọn gàng và dễ dàng hơn.
2. Chuẩn bị Môi trường
Trước khi bắt tay vào code, hãy đảm bảo môi trường Python của bạn đã sẵn sàng và tách biệt.
Bước 1: Khởi tạo môi trường ảo (Virtual Environment) Mở terminal và chạy lệnh:
python -m venv venv
Bước 2: Kích hoạt môi trường
-
Trên Linux/Ubuntu/Mac:
source venv/bin/activate -
Trên Windows (Powershell):
.venvScriptsActivate.ps1
Khi thành công, bạn sẽ thấy chữ (venv) xuất hiện ở đầu dòng lệnh.
Bước 3: Cài đặt các thư viện cần thiết
pip install pycardano blockfrost-python python-dotenv
3. Bắt tay vào Code: Từng bước gộp UTxO
Hãy tạo một file Python (ví dụ: consolidate_utxo.py) và tạo một file .env chứa các biến môi trường (BLOCKFROST_NETWORK, BLOCKFROST_PROJECT_ID, MNEMONIC).
Bước 1 & 2: Cấu hình môi trường và Khôi phục ví
Đầu tiên, chúng ta sẽ load các biến môi trường và sử dụng chuỗi Mnemonic (24 từ) để khôi phục lại địa chỉ ví cùng với khóa ký (Signing Key).
import os
import sys
import time
from blockfrost import BlockFrostApi, ApiError, ApiUrls
from dotenv import load_dotenv
from pycardano import *
# Tải biến môi trường
load_dotenv()
network_env = os.getenv("BLOCKFROST_NETWORK")
blockfrost_api_key = os.getenv("BLOCKFROST_PROJECT_ID")
wallet_mnemonic = os.getenv("MNEMONIC")
# Thiết lập mạng lưới
if network_env == "testnet":
network = Network.TESTNET
api_url = ApiUrls.preprod.value
else:
network = Network.MAINNET
api_url = ApiUrls.mainnet.value
# Khôi phục ví từ mnemonic
new_wallet = crypto.bip32.HDWallet.from_mnemonic(wallet_mnemonic)
payment_key = new_wallet.derive_from_path("m/1852'/1815'/0'/0/0")
staking_key = new_wallet.derive_from_path("m/1852'/1815'/0'/2/0")
payment_skey = ExtendedSigningKey.from_hdwallet(payment_key)
staking_skey = ExtendedSigningKey.from_hdwallet(staking_key)
# Tạo địa chỉ ví chính
main_address = Address(
payment_skey.to_verification_key().hash(),
staking_skey.to_verification_key().hash(),
network=network
)
print(f"Địa chỉ ví của bạn: {main_address}")
Bước 3: Truy vấn danh sách UTxO hiện có
Tiếp theo, chúng ta kết nối với Blockfrost để kéo về toàn bộ UTxO đang nằm rải rác trong ví.
api = BlockFrostApi(project_id=blockfrost_api_key, base_url=api_url)
try:
utxos = api.address_utxos(main_address.encode())
except Exception as e:
if hasattr(e, 'status_code') and e.status_code == 404:
print("Địa chỉ chưa có UTxO nào. Vui lòng Faucet thêm ADA.")
sys.exit(1)
else:
print(f"Lỗi khi lấy UTxO: {e}")
sys.exit(1)
print(f"Danh sách UTxO sẽ được gộp: {len(utxos)} UTxOs")
Bước 4: Xây dựng giao dịch (Điểm mấu chốt)
Thư viện PyCardano cung cấp 2 cách để thêm Input vào giao dịch:
-
builder.add_input_address(address): PyCardano tự động chọn UTxO (Coin Selection). Cách này tiện nhưng bạn mất quyền kiểm soát. -
builder.add_input(utxo): Thêm thủ công từng UTxO. Đây là cách chúng ta dùng hôm nay để chắc chắn gom “sạch” mọi đồng xu lẻ.
context = BlockFrostChainContext(project_id=blockfrost_api_key, base_url=api_url)
builder = TransactionBuilder(context)
for i, utxo in enumerate(utxos):
print(f"n[{i+1}]/{len(utxos)} UTxO: {utxo.tx_hash} #Index {utxo.tx_index}")
# Khởi tạo Input
tx_input = TransactionInput.from_primitive([utxo.tx_hash, utxo.tx_index])
lovelace_amount = 0
multi_asset = {}
# Bóc tách tài sản (ADA và Token) trong từng UTxO
for asset in utxo.amount:
if asset.unit == "lovelace":
lovelace_amount = int(asset.quantity)
print(f" - ADA: {lovelace_amount / 1_000_000} ADA")
else:
policy_id = asset.unit[:56]
asset_name = asset.unit[56:]
quantity = int(asset.quantity)
# Giải mã tên Token (nếu có thể)
try:
asset_name_str = bytes.fromhex(asset_name).decode("utf-8")
except:
asset_name_str = asset_name
print(f" - Asset: {policy_id}.{asset_name_str} - Quantity: {quantity}")
if policy_id not in multi_asset:
multi_asset[policy_id] = {}
multi_asset[policy_id][asset_name] = quantity
# Đóng gói lại giá trị và tạo Output
if multi_asset:
value = Value.from_primitive([lovelace_amount, multi_asset])
else:
value = Value.from_primitive([lovelace_amount])
tx_output = TransactionOutput(address=main_address, amount=value)
utxo_to_add = UTxO(tx_input, tx_output)
# Add đích danh UTxO này vào giao dịch
builder.add_input(utxo_to_add)
Bước 5: Ký, Gửi giao dịch và Chờ xác nhận
Sau khi đã đưa tất cả “tiền lẻ” vào Input, chúng ta chỉ định change_address trả về chính ví của mình. Hệ thống sẽ tự trừ phí mạng và gom toàn bộ số còn lại thành 1 UTxO đầu ra duy nhất.
signed_tx = builder.build_and_sign(
[payment_skey],
change_address=main_address
)
print(f"nSố lượng UTxO trước khi gộp: {len(signed_tx.transaction_body.inputs)} UTxOs")
print(f"Số lượng UTxO sau khi gộp: {len(signed_tx.transaction_body.outputs)} UTxOs")
print(f"Phí giao dịch: {signed_tx.transaction_body.fee / 1_000_000} ADA")
try:
tx_id = context.submit_tx(signed_tx.to_cbor())
print(f"Giao dịch đã được gửi thành công! Tx ID: {tx_id}")
except Exception as e:
print(f"Lỗi khi gửi giao dịch: {e}")
sys.exit(1)
# Hàm kiểm tra trạng thái giao dịch trên mạng lưới
def wait_for_tx(tx_hash):
for i in range(30):
try:
tx = api.transaction(tx_hash)
if tx:
print("n✅ Giao dịch đã được xác nhận trên blockchain!")
return True
except ApiError:
print("Đang chờ giao dịch được xác nhận (chờ 10s)...")
time.sleep(10)
return False
# Chờ và in ra kết quả cuối cùng
if wait_for_tx(tx_id):
print("Đang đồng bộ dữ liệu...")
time.sleep(20) # Chờ Blockfrost index dữ liệu
try:
new_utxos = api.address_utxos(main_address.encode())
print(f"n🎉 Số lượng UTxO hiện tại sau khi gộp: {len(new_utxos)} UTxOs")
except Exception as e:
print(f"Lỗi đồng bộ: {e}")
4. Tổng kết
Chúc mừng bạn! Bằng cách chạy đoạn script này, bạn đã dọn dẹp sạch sẽ ví của mình, gom hàng tá mảnh UTxO vụn vặt thành một cục tài sản duy nhất. Không chỉ giúp ví gọn gàng hơn, thao tác này còn giúp các ứng dụng dApp hoặc các đoạn code sau này của bạn chạy mượt mà và tốn ít phí mạng (fee) hơn rất nhiều.
