
Khởi tạo file test để kiểm thử logic của smart contract Marketplace viết bằng Aiken.
- Tạo file 
marketplace.testtrong thư mụctests/. - Viết test đầu tiên kiểm tra khi người mua gửi đúng số ADA, giao dịch hợp lệ.
 - Dùng lệnh 
aiken testđể chạy. 
Cách giải
Dùng lệnh tạo file test trong dự án Aiken và viết test cơ bản với expect true.
use cardano/assets.{add, from_lovelace}
use cardano/transaction.{InlineDatum, Input, Transaction}
use marketplace.{Buy, MarketplaceDatum, WithdrawOrUpdate}
use mocktail.{
  add_input, complete, mock_script_address, mock_script_output, mock_tx_hash,
  mock_utxo_ref, mocktail_tx, required_signer_hash, tx_in, tx_in_inline_datum,
  tx_out, tx_out_inline_datum,
}
use mocktail/virgin_address.{mock_pub_key_address}
use mocktail/virgin_key_hash.{mock_policy_id, mock_pub_key_hash}
fn mock_datum() -> MarketplaceDatum {
  MarketplaceDatum {
    seller: mock_pub_key_address(0, None),
    price: 200_000_000,
    asset_name: "Test NFT",
    policy_id: mock_policy_id(0),
  }
}
fn get_buy_test_tx(
  is_only_one_input_from_script: Bool,
  is_process_paid: Bool,
) -> Transaction {
  let input_value =
    from_lovelace(2_000_000) |> add(mock_policy_id(0), "Test NFT", 1)
  mocktail_tx()
    |> tx_out(
        True,
        mock_pub_key_address(0, None),
        if is_process_paid {
          from_lovelace(202_000_000)
        } else {
          from_lovelace(100_000_000)
        },
      )
    |> complete()
    |> add_input(
        True,
        Input {
          output_reference: mock_utxo_ref(0, 1),
          output: mock_script_output(
            mock_script_address(0, None),
            input_value,
            InlineDatum(Some(mock_datum())),
          ),
        },
      )
    |> add_input(
        !is_only_one_input_from_script,
        Input {
          output_reference: mock_utxo_ref(0, 2),
          output: mock_script_output(
            mock_script_address(0, None),
            input_value,
            InlineDatum(Some(mock_datum())),
          ),
        },
      )
}
fn get_withdraw_test_tx(is_seller_signed: Bool) {
  mocktail_tx()
    |> tx_in(
        True,
        mock_tx_hash(0),
        1,
        from_lovelace(1_000_000),
        mock_script_address(0, None),
      )
    |> tx_in_inline_datum(True, mock_datum())
    |> required_signer_hash(
        True,
        if is_seller_signed {
          mock_pub_key_hash(0)
        } else {
          mock_pub_key_hash(5)
        },
      )
    |> complete()
}
fn get_update_test_tx(is_seller_signed: Bool) {
  let new_datum =
    MarketplaceDatum {
      seller: mock_pub_key_address(0, None),
      price: 500_000_000,
      asset_name: "Test NFT",
      policy_id: mock_policy_id(0),
    }
  mocktail_tx()
    |> tx_in(
        True,
        mock_tx_hash(0),
        1,
        from_lovelace(1_000_000),
        mock_script_address(0, None),
      )
    |> tx_in_inline_datum(True, mock_datum())
    |> tx_out_inline_datum(True, new_datum)
    |> required_signer_hash(
        True,
        if is_seller_signed {
          mock_pub_key_hash(0)
        } else {
          mock_pub_key_hash(5)
        },
      )
    |> complete()
}
test success_buy() {
  let output_reference = mock_utxo_ref(0, 1)
  let redeemer = Buy
  let is_only_one_input_from_script = True
  let is_process_paid = True
  let tx = get_buy_test_tx(is_only_one_input_from_script, is_process_paid)
  marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_buy_with_mutiple_script_input() {
  let output_reference = mock_utxo_ref(0, 1)
  let redeemer = Buy
  let is_only_one_input_from_script = False
  let is_process_paid = True
  let tx = get_buy_test_tx(is_only_one_input_from_script, is_process_paid)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_buy_without_proceed_paid() {
  let output_reference = mock_utxo_ref(0, 1)
  let redeemer = Buy
  let is_only_one_input_from_script = True
  let is_process_paid = False
  let tx = get_buy_test_tx(is_only_one_input_from_script, is_process_paid)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test success_withdraw() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = True
  let tx = get_withdraw_test_tx(is_seller_signed)
  marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_withdraw_without_signature() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = False
  let tx = get_withdraw_test_tx(is_seller_signed)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test success_update() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = True
  let tx = get_update_test_tx(is_seller_signed)
  marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_update_without_signature() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = False
  let tx = get_update_test_tx(is_seller_signed)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
Kiểm tra trường hợp giao dịch không hợp lệ khi người mua gửi sai số tiền.
- Thêm test mới trong 
marketplace.test. - Kiểm tra giá trị ADA nhỏ hơn giá NFT.
 - Kết quả mong đợi: test phải fail.
 
Cách giải
So sánh giá trị ada_sent khác nft_price trong test.
use cardano/assets.{add, from_lovelace}
use cardano/transaction.{InlineDatum, Input, Transaction}
use marketplace.{Buy, MarketplaceDatum, WithdrawOrUpdate}
use mocktail.{
  add_input, complete, mock_script_address, mock_script_output, mock_tx_hash,
  mock_utxo_ref, mocktail_tx, required_signer_hash, tx_in, tx_in_inline_datum,
  tx_out, tx_out_inline_datum,
}
use mocktail/virgin_address.{mock_pub_key_address}
use mocktail/virgin_key_hash.{mock_policy_id, mock_pub_key_hash}
fn mock_datum() -> MarketplaceDatum {
  MarketplaceDatum {
    seller: mock_pub_key_address(0, None),
    price: 200_000_000,
    asset_name: "Test NFT",
    policy_id: mock_policy_id(0),
  }
}
fn get_buy_test_tx(
  is_only_one_input_from_script: Bool,
  is_process_paid: Bool,
) -> Transaction {
  let input_value =
    from_lovelace(2_000_000) |> add(mock_policy_id(0), "Test NFT", 1)
  mocktail_tx()
    |> tx_out(
        True,
        mock_pub_key_address(0, None),
        if is_process_paid {
          from_lovelace(202_000_000)
        } else {
          from_lovelace(100_000_000)
        },
      )
    |> complete()
    |> add_input(
        True,
        Input {
          output_reference: mock_utxo_ref(0, 1),
          output: mock_script_output(
            mock_script_address(0, None),
            input_value,
            InlineDatum(Some(mock_datum())),
          ),
        },
      )
    |> add_input(
        !is_only_one_input_from_script,
        Input {
          output_reference: mock_utxo_ref(0, 2),
          output: mock_script_output(
            mock_script_address(0, None),
            input_value,
            InlineDatum(Some(mock_datum())),
          ),
        },
      )
}
fn get_withdraw_test_tx(is_seller_signed: Bool) {
  mocktail_tx()
    |> tx_in(
        True,
        mock_tx_hash(0),
        1,
        from_lovelace(1_000_000),
        mock_script_address(0, None),
      )
    |> tx_in_inline_datum(True, mock_datum())
    |> required_signer_hash(
        True,
        if is_seller_signed {
          mock_pub_key_hash(0)
        } else {
          mock_pub_key_hash(5)
        },
      )
    |> complete()
}
fn get_update_test_tx(is_seller_signed: Bool) {
  let new_datum =
    MarketplaceDatum {
      seller: mock_pub_key_address(0, None),
      price: 500_000_000,
      asset_name: "Test NFT",
      policy_id: mock_policy_id(0),
    }
  mocktail_tx()
    |> tx_in(
        True,
        mock_tx_hash(0),
        1,
        from_lovelace(1_000_000),
        mock_script_address(0, None),
      )
    |> tx_in_inline_datum(True, mock_datum())
    |> tx_out_inline_datum(True, new_datum)
    |> required_signer_hash(
        True,
        if is_seller_signed {
          mock_pub_key_hash(0)
        } else {
          mock_pub_key_hash(5)
        },
      )
    |> complete()
}
test success_buy() {
  let output_reference = mock_utxo_ref(0, 1)
  let redeemer = Buy
  let is_only_one_input_from_script = True
  let is_process_paid = True
  let tx = get_buy_test_tx(is_only_one_input_from_script, is_process_paid)
  marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_buy_with_mutiple_script_input() {
  let output_reference = mock_utxo_ref(0, 1)
  let redeemer = Buy
  let is_only_one_input_from_script = False
  let is_process_paid = True
  let tx = get_buy_test_tx(is_only_one_input_from_script, is_process_paid)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_buy_without_proceed_paid() {
  let output_reference = mock_utxo_ref(0, 1)
  let redeemer = Buy
  let is_only_one_input_from_script = True
  let is_process_paid = False
  let tx = get_buy_test_tx(is_only_one_input_from_script, is_process_paid)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test success_withdraw() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = True
  let tx = get_withdraw_test_tx(is_seller_signed)
  marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_withdraw_without_signature() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = False
  let tx = get_withdraw_test_tx(is_seller_signed)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test success_update() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = True
  let tx = get_update_test_tx(is_seller_signed)
  marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
test fail_update_without_signature() {
  let output_reference = mock_utxo_ref(0, 0)
  let redeemer = WithdrawOrUpdate
  let is_seller_signed = False
  let tx = get_update_test_tx(is_seller_signed)
  !marketplace.marketplace.spend(
    Some(mock_datum()),
    redeemer,
    output_reference,
    tx,
  )
}
Cài đặt và cấu hình môi trường offchain với Mesh SDK.
- Cài Mesh SDK bằng npm.
 - Tạo file 
.envlưu Project ID của Blockfrost. - Cấu hình provider trong code.
 
Cài Mesh và tạo provider với Project ID từ Blockfrost.
Cách giải
npm install @meshsdk/core @meshsdk/common
import { BlockfrostProvider } from "@meshsdk/core";
const provider = new BlockfrostProvider("mainnet", process.env.BLOCKFROST_ID);
Tạo hàm TypeScript sale() để đăng bán NFT trên marketplace.
- Truyền vào tham số: 
asset,price,sellerAddress. - Dùng Mesh SDK để tạo giao dịch có metadata và output kèm datum.
 - Trả về hash giao dịch sau khi submit.
 
Cách giải
Dùng Mesh SDK với Transaction().sendAssets().attachMetadata() và submit().
import { Transaction } from "@meshsdk/core";
async function sale(asset, price, sellerAddress) {
  const tx = new Transaction({ initiator: sellerAddress })
    .sendAssets({ address: MARKETPLACE_ADDR, assets: { [asset]: 1 } })
    .attachMetadata(721, { price })
    .build();
  const txHash = await tx.submit();
  return txHash;
}
Viết test kiểm thử giao dịch sale() bằng Vitest.
- Cài Vitest và viết test đơn giản gọi 
sale(). - In ra 
txHashnếu giao dịch thành công. - Sử dụng mô phỏng (mock) provider khi test.
 
Cách giải
Sử dụng vi.fn() để tạo mock provider và xác minh hàm được gọi.
import { describe, it, expect, vi } from "vitest";
import { sale } from "./marketplace";
describe("Marketplace sale", () => {
  it("should return tx hash", async () => {
    const mockProvider = vi.fn().mockResolvedValue("mockTxHash123");
    const result = await sale("asset1", 100, "addr_test1...");
    expect(result).toBeDefined();
  });
});
