
Bài 3: Triển khai Smart Contract CIP-68 Bằng Aiken
Chào mừng các bạn quay trở lại với series Thực Chiến CIP-68!
Trong Bài 3.0, chúng ta đã nắm vững triết lý cốt lõi của CIP-68: “Tách rời phần xác (User Token) và phần hồn (Reference Token)” để biến tài sản tĩnh thành tài sản động. Hôm nay, chúng ta sẽ bắt tay vào thực hành, đóng vai trò là một kỹ sư Smart Contract thực thụ để viết ra hợp đồng bảo vệ cơ chế này bằng ngôn ngữ Aiken.
Mục Tiêu Bài Học
Kết thúc bài học này, bạn sẽ:
-
Nắm được cách định nghĩa cấu trúc dữ liệu Metadata chuẩn CIP-68 trong Aiken.
-
Viết Minting Policy để giám sát việc đúc Cặp Token (Reference & User Token).
-
Viết Spending Validator để xử lý logic Cập nhật (Update) và Hủy bỏ (Burn) tài sản sử dụng cơ chế CKV.
-
Biên dịch dự án thành file
plutus.json.
1. Khởi Tạo Dự Án Aiken
Nếu bạn chưa cài Aiken, hãy tham khảo tài liệu chính thức. Chúng ta khởi tạo dự án bằng lệnh:
aiken new sonson0910/cip68_dynamic_asset
cd cip68_dynamic_asset
Hãy mở thư mục dự án lên, đi vào thư mục validators/ và tạo một file mới có tên là cip68.ak. Đây sẽ là nơi chứa toàn bộ linh hồn của hệ thống.
2. Định Nghĩa Cấu Trúc Datum (CIP-68 Metadata)
Khác với CIP-25 nơi metadata là một file JSON gắn ngoài, CIP-68 yêu cầu metadata phải được đóng gói vào Inline Datum nằm trên Smart Contract. Chuẩn CIP-68 quy định cấu trúc Datum này rất chặt chẽ.
Chúng ta sẽ định nghĩa nó trong file cip68.ak như sau:
use aiken/dict.{Dict}
// 1. Cấu trúc chuẩn của Metadata CIP-68
pub type CIP68Metadata {
metadata: Dict<ByteArray, Data>, // Chứa các thuộc tính (name, image, level,...)
version: Int, // Phiên bản chuẩn (thường là 1)
}
// 2. Định nghĩa các Hành động (Redeemer)
pub type CIP68Action {
Mint
Update
Burn
}
-
metadata: Là một từ điển (Dictionary/Map) ánh xạ từ Key (ByteArray) sang Value (Data). Đây là nơi bạn sẽ lưu trữ Tên, Hình ảnh, Cấp độ,… -
version: Bắt buộc theo chuẩn, thường đặt là1.
3. Viết Minting Policy (Hợp đồng Đúc)
Khi một tài sản CIP-68 được đúc, Smart Contract phải làm nhiệm vụ của một “nhân viên kiểm duyệt” khắt khe: Nó phải chắc chắn rằng người dùng đúc ra đúng 1 cặp token (Reference và User Token), và Reference Token phải được gửi vào chính nó kèm theo Datum.
validator cip68_asset {
// --- MINTING POLICY ---
mint(redeemer: CIP68Action, policy_id: PolicyId, transaction: Transaction) {
let Transaction { mint, outputs, .. } = transaction
when redeemer is {
Mint -> {
// Kiểm tra logic đúc (Mint):
// 1. Lọc danh sách các token được đúc dưới Policy ID này
// 2. Phải có ít nhất 1 token mang prefix 100 (Reference)
// 3. Phải có ít nhất 1 token mang prefix 222 hoặc 333 (User)
// 4. Output chứa Reference Token phải nằm ở địa chỉ script với Datum hợp lệ
// (Trong thực tế, logic kiểm tra prefix và outputs ở đây khá dài,
// sử dụng aiken/transaction/value và các hàm so sánh byte)
True // Giả định qua các bước kiểm tra
}
Burn -> {
// Kiểm tra logic đốt (Burn):
// Đảm bảo số lượng token đang đúc là số âm (< 0)
True
}
_ -> False // Không cho phép gọi hành động khác trong Mint
}
}
Lưu ý: Để code ngắn gọn và tập trung vào ý tưởng, phần logic kiểm tra chi tiết từng bytes prefix (000643b0…) sẽ được đóng gói bằng các hàm tiện ích của thư viện Aiken.
4. Viết Spending Validator (Hợp đồng Chi tiêu)
Đây là nơi “ma thuật” CIP-68 xảy ra: Cho phép Update (Cập nhật thông số) và Burn (Hủy tài sản). Chúng ta sử dụng cơ chế Continuing Key Validation (CKV) đã học ở Chapter trước.
Vẫn trong validator cip68_asset, chúng ta thêm khối spend:
// --- SPENDING VALIDATOR ---
spend(
datum_opt: Option<CIP68Metadata>,
redeemer: CIP68Action,
own_ref: OutputReference,
transaction: Transaction,
) {
let Transaction { inputs, outputs, extra_signatories, .. } = transaction
expect Some(datum) = datum_opt
when redeemer is {
Update -> {
// BƯỚC 1: XÁC THỰC QUYỀN CẬP NHẬT
// Kiểm tra xem giao dịch có được ký bởi Admin (người có quyền update) không.
// Bạn có thể truyền Admin PubKeyHash dưới dạng tham số validator.
let is_admin_signed = list.has(extra_signatories, admin_pub_key_hash)
// BƯỚC 2: CƠ CHẾ CKV (CONTINUING OUTPUT)
// Yêu cầu UTxO phải quay lại chính địa chỉ script này,
// Giữ nguyên Reference Token cũ, nhưng đính kèm Datum mới!
let has_continuing_output =
list.any(outputs, fn(out) {
out.address == script_address &&
value.amount_of(out.value, policy_id, ref_token_name) == 1
})
is_admin_signed && has_continuing_output
}
Burn -> {
// BƯỚC 1: XÁC THỰC RẰNG ĐANG ĐỐT TOKEN (MINT SỐ LƯỢNG ÂM)
// Smart Contract kiểm tra trường "mint" của giao dịch
let is_burning = value.amount_of(transaction.mint, policy_id, ref_token_name) < 0
// BƯỚC 2: RÀNG BUỘC PHẢI ĐỐT CẢ CẶP
// Muốn tiêu hủy Reference Token ở đây, phải đảm bảo User Token cũng đang bị đốt
let is_user_token_burning = value.amount_of(transaction.mint, policy_id, user_token_name) < 0
is_burning && is_user_token_burning
}
_ -> False
}
}
}
Giải Thích Logic:
-
Update: * Đầu tiên, hợp đồng kiểm tra chữ ký (
extra_signatories). Chỉ người cầm chìa khóa Admin mới được quyền sửa thông tin (tránh việc hacker tự ý sửa level NFT của họ).-
Thứ hai, nó bắt buộc phải có một output quay lại chính nó mang theo Reference Token. Bằng cách này, Reference Token không bao giờ rời khỏi Smart Contract!
-
-
Burn:
-
Nó không yêu cầu continuing output vì tài sản đã bị hủy.
-
Cực kỳ quan trọng: Nó ép buộc người dùng phải đốt cả User Token thì mới cho phép đốt Reference Token. Điều này giữ cho hệ sinh thái sạch sẽ, không có “hồn” thiếu “xác” hay ngược lại.
-
5. Biên Dịch Dự Án (Build)
Sau khi hoàn thành logic code, chúng ta cần biên dịch nó thành mã Plutus Core mà mạng lưới Cardano có thể hiểu được.
Chạy lệnh sau trên terminal:
aiken build
Nếu code không có lỗi, bạn sẽ nhận được thông báo thành công và thư mục của bạn sẽ xuất hiện file plutus.json.
Tệp tin plutus.json này chính là báu vật! Nó chứa mã Hex đã được biên dịch của Smart Contract. Ở bài tiếp theo, chúng ta sẽ tải file này vào Python để điều khiển blockchain.
Tổng Kết Bài 3
Tuyệt vời! Bạn vừa hoàn thành một trong những Smart Contract thú vị nhất trên Cardano.
Qua bài học này, chúng ta đã biến lý thuyết CIP-68 từ Bài 1 thành những dòng code thực tế:
-
Cấu trúc CIP68Metadata với cơ chế Map Key-Value.
-
Minting Policy khắt khe đảm bảo luôn sinh ra 1 Cặp Token hợp lệ.
-
Spending Validator áp dụng cơ chế CKV cực mạnh để Update Metadata, và cơ chế Đồng bộ hóa để hủy (Burn) an toàn.
Tiếp theo là gì? Bây giờ, Smart Contract của chúng ta đã sẵn sàng trên mạng lưới. Trong Bài 4, chúng ta sẽ đổi chiếc mũ “Kỹ sư Aiken” lấy chiếc mũ “Kỹ sư Python”. Chúng ta sẽ dùng PyCardano (trong thư mục offchain/) để tạo ra các giao dịch Mint, Update và Burn tương tác trực tiếp với hợp đồng này.
Hẹn gặp lại các bạn ở Bài 4!
CIP-68 Dynamic NFT Implementation
