
Khởi tạo dự án Aiken mới và cấu hình cơ bản cho Marketplace.
- Cài đặt Aiken bằng lệnh:
npm install -g aiken@latest
- Kiểm tra phiên bản:
aiken -V
(phiên bản nên ≥
1.1.19) - Tạo dự án mới:
aiken new ft_marketplace
- Đổi tên thư mục chính thành
smart_contractvà mở bằng VSCode.
Cách giải
- Chạy các lệnh trên để khởi tạo.
- Xóa module mẫu không cần thiết, chỉ giữ
spend. - Cấu hình lại file
aiken.tomlnếu cần.
Sau khi hoàn thành, thư mục sẽ có cấu trúc:
ft_marketplace/
├── validators/
│ └── marketplace.ak
├── aiken.toml
└── lib/
Tạo kiểu dữ liệu MarketplaceDatum và MarketplaceRedeemer để mô tả thông tin NFT niêm yết và hành động của người dùng.
Datumchứa các trường:seller: Addressprice: Intpolicy_id: PolicyIdasset_name: ByteArray
Redeemercó 2 lựa chọn:BuyWithdrawOrUpdate
Cách giải
Thêm vào file marketplace.ak đoạn code sau:
type MarketplaceDatum {
seller: Address,
price: Int,
policy_id: PolicyId,
asset_name: ByteArray
}
type MarketplaceRedeemer {
Buy,
WithdrawOrUpdate
}
Hai kiểu dữ liệu này sẽ được dùng trong validator để phân biệt loại giao dịch và xác định điều kiện hợp lệ.
Viết logic kiểm tra tính hợp lệ khi người mua thực hiện lệnh Buy.
- Chỉ có 1 input đến từ script (đảm bảo không double-spend).
- Giá trị ADA trả cho
seller≥datum.price + minAda.
Cách giải
- Lấy địa chỉ script từ input hiện tại.
- Kiểm tra input duy nhất.
- Xác minh tổng ADA gửi cho người bán.
use aiken/collection/list
use cardano/address.{Address}
use cardano/assets.{AssetName, PolicyId, from_lovelace, lovelace_of}
use cardano/transaction.{OutputReference, Transaction, find_input}
use cocktail/vodka_address.{address_pub_key}
use cocktail/vodka_inputs.{inputs_at}
use cocktail/vodka_value.{get_all_value_to, value_geq}
pub type MarketplaceDatum {
seller: Address,
price: Int,
policy_id: PolicyId,
asset_name: AssetName,
}
pub type MarketplaceRedeemer {
Buy
WithdrawOrUpdate
}
validator marketplace {
spend(
datum: Option<MarketplaceDatum>,
redeemer: MarketplaceRedeemer,
utxo: OutputReference,
tx: Transaction,
) {
expect Some(datum) = datum
when redeemer is {
Buy -> {
expect Some(script_input) = find_input(tx.inputs, utxo)
let script_address = script_input.output.address
let is_only_one_input_from_script =
when inputs_at(tx.inputs, script_address) is {
[_] -> True
_ -> False
}
let is_process_paid =
get_all_value_to(tx.outputs, datum.seller)
|> value_geq(
from_lovelace(
datum.price + lovelace_of(script_input.output.value),
),
)
is_only_one_input_from_script && is_process_paid
}
WithdrawOrUpdate -> {
expect Some(pub_key) = address_pub_key(datum.seller)
list.has(tx.extra_signatories, pub_key)
}
}
}
else(_) {
fail @"unsupport this purpose"
}
}
Xác thực khi người bán muốn rút NFT hoặc cập nhật giá bán.
- Chỉ người bán (
datum.seller) có thể thực hiện hành động này. - Xác minh rằng địa chỉ của người ký (
tx.signatories) trùng vớidatum.seller.
Cách giải
Dùng hàm has_key() để kiểm tra xem seller có trong danh sách người ký không.
use aiken/collection/list
use cardano/address.{Address}
use cardano/assets.{AssetName, PolicyId, from_lovelace, lovelace_of}
use cardano/transaction.{OutputReference, Transaction, find_input}
use cocktail/vodka_address.{address_pub_key}
use cocktail/vodka_inputs.{inputs_at}
use cocktail/vodka_value.{get_all_value_to, value_geq}
pub type MarketplaceDatum {
seller: Address,
price: Int,
policy_id: PolicyId,
asset_name: AssetName,
}
pub type MarketplaceRedeemer {
Buy
WithdrawOrUpdate
}
validator marketplace {
spend(
datum: Option<MarketplaceDatum>,
redeemer: MarketplaceRedeemer,
utxo: OutputReference,
tx: Transaction,
) {
expect Some(datum) = datum
when redeemer is {
Buy -> {
expect Some(script_input) = find_input(tx.inputs, utxo)
let script_address = script_input.output.address
let is_only_one_input_from_script =
when inputs_at(tx.inputs, script_address) is {
[_] -> True
_ -> False
}
let is_process_paid =
get_all_value_to(tx.outputs, datum.seller)
|> value_geq(
from_lovelace(
datum.price + lovelace_of(script_input.output.value),
),
)
is_only_one_input_from_script && is_process_paid
}
WithdrawOrUpdate -> {
expect Some(pub_key) = address_pub_key(datum.seller)
list.has(tx.extra_signatories, pub_key)
}
}
}
else(_) {
fail @"unsupport this purpose"
}
}
Nếu điều kiện không đúng, giao dịch bị từ chối.
Kết hợp cả hai điều kiện “Buy” và “WithdrawOrUpdate” để hoàn thiện validator marketplace.
- Lấy
datum,redeemer, vàtxtừ context. - Gọi hàm kiểm tra tương ứng.
- Nếu điều kiện sai, trả lỗi qua
fail_if_false.
Cách giải
Viết logic phân nhánh dựa vào loại redeemer.
use aiken/collection/list
use cardano/address.{Address}
use cardano/assets.{AssetName, PolicyId, from_lovelace, lovelace_of}
use cardano/transaction.{OutputReference, Transaction, find_input}
use cocktail/vodka_address.{address_pub_key}
use cocktail/vodka_inputs.{inputs_at}
use cocktail/vodka_value.{get_all_value_to, value_geq}
pub type MarketplaceDatum {
seller: Address,
price: Int,
policy_id: PolicyId,
asset_name: AssetName,
}
pub type MarketplaceRedeemer {
Buy
WithdrawOrUpdate
}
validator marketplace {
spend(
datum: Option<MarketplaceDatum>,
redeemer: MarketplaceRedeemer,
utxo: OutputReference,
tx: Transaction,
) {
expect Some(datum) = datum
when redeemer is {
Buy -> {
expect Some(script_input) = find_input(tx.inputs, utxo)
let script_address = script_input.output.address
let is_only_one_input_from_script =
when inputs_at(tx.inputs, script_address) is {
[_] -> True
_ -> False
}
let is_process_paid =
get_all_value_to(tx.outputs, datum.seller)
|> value_geq(
from_lovelace(
datum.price + lovelace_of(script_input.output.value),
),
)
is_only_one_input_from_script && is_process_paid
}
WithdrawOrUpdate -> {
expect Some(pub_key) = address_pub_key(datum.seller)
list.has(tx.extra_signatories, pub_key)
}
}
}
else(_) {
fail @"unsupport this purpose"
}
}
Validator này xử lý được cả 2 loại giao dịch chính, đảm bảo:
- Người mua trả đủ ADA.
- Người bán duy nhất được quyền rút hoặc cập nhật NFT.
