Pycardano: The Ultimate Course for Python and AI Developers
About Lesson

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:

Bash

 
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

Bash

 
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).

Python

 
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í.

Python

 
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:

  1. 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.

  2. 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ẻ.

Python

 
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.

Python

 
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.