Course Content
Pycardano: The Ultimate Course for Python and AI Developers

Bài 2: Tương Tác Hợp Đồng “Vesting” (Khóa Token Theo Thời Gian) Bằng PyCardano

Chào mừng các bạn đến với bài học tiếp theo về Lập trình Off-chain trên Cardano!

Trong bài “Hello World”, chúng ta đã học cách khóa tiền bằng một “mật khẩu” và chữ ký. Tuy nhiên, trong thực tế tài chính phi tập trung (DeFi), chúng ta thường gặp bài toán: “Tôi muốn tặng bạn 1000 token, nhưng bạn chỉ được phép rút nó ra sau đúng 6 tháng nữa”.

Cơ chế đó được gọi là Vesting (Khóa tài sản theo thời gian). Hôm nay, chúng ta sẽ học cách viết mã Python tương tác với Smart Contract Vesting. Qua bài này, bạn sẽ nắm được cách xử lý Khung thời gian giao dịch (Validity Range) – một khái niệm cốt lõi của Cardano.

Mục Tiêu Bài Học

Trong bài này, bạn sẽ nắm được:

  1. Cách Smart Contract Cardano xử lý thời gian thông qua validity_range.

  2. Định nghĩa cấu trúc VestingDatum phức tạp hơn (chứa tham số thời gian).

  3. Xây dựng giao dịch Lock: Tính toán thời gian khóa (POSIX Time) và gửi ADA.

  4. Xây dựng giao dịch Unlock: Thiết lập validity_start cho giao dịch để chứng minh với hợp đồng rằng thời gian khóa đã trôi qua.

1. Phân Tích Smart Contract “Vesting” (Aiken)

Hãy cùng xem file vesting.ak. Hợp đồng này không cần “thần chú” (Redeemer) để mở khóa, thay vào đó nó kiểm tra Thời gianNgười thụ hưởng.

use aiken/hash.{Blake2b_224, Hash}
use aiken/interval.{contains}
use aiken/list
use aiken/time.{POSIXTime}
use aiken/transaction.{Transaction}
use aiken/transaction/credential.{VerificationKey}

// 1. Định nghĩa Datum: Ghi lại luật chơi
type Datum {
  lock_until: POSIXTime,                      // Thời điểm được phép mở khóa (mili-giây)
  owner: Hash<Blake2b_224, VerificationKey>,  // Người tạo ra hợp đồng này
  beneficiary: Hash<Blake2b_224, VerificationKey>, // Người được phép rút tiền
}

// 2. Logic Validator
validator {
  fn vesting(datum: Datum, _redeemer: Data, context: Transaction) -> Bool {
    // ĐIỀU KIỆN 1: Giao dịch rút tiền PHẢI được ký bởi người thụ hưởng (beneficiary)
    let must_be_signed =
      list.has(context.extra_signatories, datum.beneficiary)

    // ĐIỀU KIỆN 2: Thời gian giao dịch diễn ra PHẢI sau thời điểm lock_until
    let must_be_after =
      interval.contains(context.validity_range, datum.lock_until)

    must_be_signed && must_be_after
  }
}

Sự khác biệt lớn nhất: Ở đây chúng ta không dùng redeemer (nên nó được để là _redeemer: Data). Kẻ kiểm duyệt chính là context.validity_range (khoảng thời gian hợp lệ của giao dịch).

2. Ánh Xạ Dữ Liệu Sang Python (PyCardano)

Tương tự bài trước, chúng ta định nghĩa Datum bằng Python. Tuy nhiên, lưu ý trường lock_until sẽ dùng kiểu int (đại diện cho POSIX Time tính bằng mili-giây).

from dataclasses import dataclass
from pycardano import PlutusData

@dataclass
class VestingDatum(PlutusData):
    CONSTR_ID = 0
    lock_until: int         # POSIX Time (mili-giây)
    owner: bytes            # PubKeyHash của người gửi
    beneficiary: bytes      # PubKeyHash của người nhận

3. Giao Dịch Khóa (Lock ADA Theo Thời Gian)

Trong file lock.py, chúng ta sẽ gửi tiền vào hợp đồng. Điểm mấu chốt là tính toán thời điểm lock_until.

import time

# 1. Tính toán thời gian khóa (VD: khóa trong 60 giây kể từ hiện tại)
current_time = int(time.time() * 1000)  # Lấy thời gian hiện tại (mili-giây)
lock_until_time = current_time + 60000  # Cộng thêm 60,000 mili-giây (60 giây)

# 2. Tạo Datum
# Giả sử ở đây owner và beneficiary đều là ví của chúng ta để tiện demo
datum = VestingDatum(
    lock_until=lock_until_time,
    owner=payment_vkey.hash().payload,
    beneficiary=payment_vkey.hash().payload
)

# 3. Xây dựng giao dịch khóa tiền
builder = TransactionBuilder(context)
builder.add_input_address(address)

builder.add_output(
    TransactionOutput(
        address=script_address,
        amount=5000000, # Khóa 5 ADA
        datum=datum     # Gắn kèm điều kiện khóa
    )
)

# Ký và gửi lên blockchain...

Khi giao dịch này hoàn tất, 5 ADA sẽ nằm trong hợp đồng. Kể cả bạn là beneficiary, nếu bạn cố rút trước khi hết 60 giây, hợp đồng sẽ từ chối!

4. Giao Dịch Mở Khóa (Vượt Qua Rào Cản Thời Gian)

Sau khi đợi đủ 60 giây, chúng ta chạy script unlock.py. Đây là nơi nhiều lập trình viên mới gặp khó khăn nhất. Làm sao để chứng minh với Smart Contract rằng thời gian đã trôi qua?

Trên Cardano, mỗi giao dịch có thể khai báo một khoảng thời gian hiệu lực (validity_startttl – Time to Live). Hợp đồng sẽ kiểm tra xem cái validity_start này có nằm sau lock_until hay không.

# 1. Tìm UTxO đang bị khóa của chúng ta
utxo_to_spend = None
for utxo in context.utxos(script_address):
    if utxo.output.datum:
        try:
            stored_datum = VestingDatum.from_cbor(utxo.output.datum.cbor)
            if stored_datum.beneficiary == payment_vkey.hash().payload:
                utxo_to_spend = utxo
                break
        except Exception:
            pass

# 2. Xây dựng giao dịch mở khóa
builder = TransactionBuilder(context)
builder.add_input_address(address) # Trả phí mạng

# BÍ QUYẾT XỬ LÝ THỜI GIAN 
# Lấy slot block mới nhất của mạng lưới làm điểm bắt đầu hợp lệ cho giao dịch
current_slot = context.last_block_slot
builder.validity_start = current_slot
# (Khi giao dịch đưa lên mạng, slot này sẽ được Cardano Node chuyển đổi ngầm 
# thành POSIX Time để so sánh với lock_until trong Aiken).

# Rút tiền
builder.add_script_input(
    utxo=utxo_to_spend,
    script=vesting_script,
    redeemer=RedeemerTag.SPEND,
    redeemer_data=PlutusData() # Không cần redeemer cụ thể, truyền data rỗng
)

# Ký bằng ví của người thụ hưởng
builder.required_signers = [payment_vkey.hash()]

# Ký và Submit...

Tại sao phải có builder.validity_start = current_slot? Vì Smart Contract không thể tự động nhìn vào đồng hồ hệ thống. Mọi thứ trong Smart Contract phải mang tính xác định (Deterministic). Bằng cách gán validity_start (giao dịch chỉ có hiệu lực từ slot này trở đi), bạn đưa vào một bằng chứng thời gian bất biến. Validator Aiken sẽ lấy giá trị đó để đối chiếu với lock_until.

Tổng Kết

Khái niệm về thời gian trong Web3 khác biệt hoàn toàn so với Web2. Qua bài học này, bạn đã học được:

  1. Cách thiết kế một cấu trúc Datum mang các tham số điều kiện (thời gian, người nhận).

  2. Thời gian on-chain trong Aiken được đo bằng POSIXTime (mili-giây), nhưng khi build giao dịch off-chain bằng PyCardano ta thường sử dụng Slot.

  3. Để tương tác với các hợp đồng có yêu cầu về thời gian (như Vesting), giao dịch rút tiền bắt buộc phải gán validity_start (điểm bắt đầu hiệu lực) để chứng minh thời hạn đã trôi qua.

Cơ chế Vesting này chính là nền tảng cốt lõi để xây dựng các nền tảng phân bổ Token (Tokenomics Distribution) chuyên nghiệp. Hãy thử thay đổi tham số thời gian và chạy lại script để kiểm chứng nhé! 
Cảm ơn các bạn đã theo dõi bài học. Hẹn gặp lại vào bài học sau!!!

Chi viết về source code của bai bài học các bạn có thể tham khảo tại đây!!!

SLOT88
totoslot777
https://nextlevelacademygr.es/
SLOT88
CARVALHO-MANZON Digital Arts
https://kaizen7.pe/
slot maxwin
BANDAR SLOT
SLOT THAILAND
https://www.ui-academy.co.uk/faq/
https://harton.be/
SLOT THAILAND
ANGKATOTO
SLOT THAILAND