Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kiosk 标准

Kiosk 是 Sui 的去中心化商业基础设施,为 NFT 交易提供了标准化的上架、购买和转移机制。每个用户可以拥有自己的 Kiosk(类似于虚拟商店),在其中展示和出售 NFT。本节将介绍 Kiosk 的核心概念和操作流程。

Kiosk 概念

Kiosk 是一个共享对象,扮演用户的个人商店角色:

  • 持有者通过 KioskOwnerCap 管理自己的 Kiosk
  • NFT 可以放置(place)到 Kiosk 中
  • 放置的 NFT 可以上架(list)出售
  • 买家可以购买(purchase)上架的 NFT
  • 所有转移受 TransferPolicy 约束
  卖家                     买家
   │                       │
   ├─ 创建 Kiosk           │
   ├─ 放置 NFT             │
   ├─ 上架(设定价格)      │
   │                       ├─ 浏览 Kiosk
   │                       ├─ 购买 NFT
   │                       ├─ 满足 TransferPolicy
   │                       └─ 获得 NFT
   └─ 提取收益

创建 Kiosk

use sui::kiosk;

// 创建 Kiosk 和 KioskOwnerCap
let (mut kiosk, kiosk_cap) = kiosk::new(ctx);

// 共享 Kiosk,转移 Cap
transfer::public_share_object(kiosk);
transfer::public_transfer(kiosk_cap, ctx.sender());

使用 TypeScript SDK:

import { KioskClient, KioskTransaction } from "@mysten/kiosk";

const tx = new Transaction();
const kioskTx = new KioskTransaction({ transaction: tx, kioskClient });

kioskTx.create();
kioskTx.finalize();

const result = await client.signAndExecuteTransaction({ transaction: tx, signer: keypair });
if (result.$kind === 'FailedTransaction') {
  throw new Error(result.FailedTransaction.status.error?.message ?? 'Transaction failed');
}
await client.waitForTransaction({ digest: result.Transaction.digest });

放置和上架

放置 NFT

将 NFT 放入 Kiosk(不出售):

use sui::kiosk;

public fun place_in_kiosk<T: key + store>(
    kiosk: &mut Kiosk,
    cap: &KioskOwnerCap,
    item: T,
) {
    kiosk::place(kiosk, cap, item);
}

上架出售

设定价格后上架:

public fun list_in_kiosk<T: key + store>(
    kiosk: &mut Kiosk,
    cap: &KioskOwnerCap,
    item_id: ID,
    price: u64,
) {
    kiosk::list<T>(kiosk, cap, item_id, price);
}

放置并上架(一步完成)

public fun place_and_list<T: key + store>(
    kiosk: &mut Kiosk,
    cap: &KioskOwnerCap,
    item: T,
    price: u64,
) {
    kiosk::place_and_list(kiosk, cap, item, price);
}

TypeScript 版本:

const kioskTx = new KioskTransaction({
  transaction: tx,
  kioskClient,
  kioskCap: myKioskCap,
});

kioskTx.placeAndList({
  itemType: `${PACKAGE_ID}::sword::Sword`,
  item: swordId,
  price: 1_000_000_000n, // 1 SUI
});

kioskTx.finalize();

购买

买家从 Kiosk 购买 NFT:

use sui::kiosk;
use sui::coin::Coin;
use sui::sui::SUI;
use sui::transfer_policy::TransferPolicy;

public fun purchase_from_kiosk<T: key + store>(
    kiosk: &mut Kiosk,
    item_id: ID,
    payment: Coin<SUI>,
    policy: &TransferPolicy<T>,
    ctx: &mut TxContext,
) {
    let (item, mut request) = kiosk::purchase<T>(kiosk, item_id, payment);

    // 满足 TransferPolicy 的规则
    // (如果 Policy 为空则无需额外操作)

    // 确认转移
    transfer_policy::confirm_request(policy, request);

    // 转移给买家
    transfer::public_transfer(item, ctx.sender());
}

TypeScript 版本:

const kioskTx = new KioskTransaction({
  transaction: tx,
  kioskClient,
  kioskCap: buyerKioskCap,
});

await kioskTx.purchase({
  itemType: `${PACKAGE_ID}::sword::Sword`,
  itemId: swordId,
  price: 1_000_000_000n,
  sellerKiosk: sellerKioskId,
});

kioskTx.finalize();

下架和取回

下架

取消出售但保留在 Kiosk 中:

kiosk::delist<Sword>(kiosk, cap, item_id);

取回

从 Kiosk 中取回 NFT:

let item = kiosk::take<Sword>(kiosk, cap, item_id);

提取收益

卖家从 Kiosk 中提取销售收益:

let profits = kiosk::withdraw(kiosk, cap, option::none(), ctx);
// option::none() 表示提取全部,也可指定金额
transfer::public_transfer(profits, ctx.sender());

TransferPolicy

每种 NFT 类型需要一个 TransferPolicy 来定义转移规则。没有 Policy 的类型无法通过 Kiosk 交易。

创建空 Policy

use sui::transfer_policy;
use sui::package;

fun create_policy<T>(publisher: &package::Publisher, ctx: &mut TxContext) {
    let (policy, policy_cap) = transfer_policy::new<T>(publisher, ctx);
    transfer::public_share_object(policy);
    transfer::public_transfer(policy_cap, ctx.sender());
}

空的 Policy 意味着无需额外条件即可完成转移。

完整交易流程示例

#[test]
fun kiosk_trading() {
    use sui::test_scenario;
    use sui::kiosk;
    use sui::transfer_policy;
    use sui::sui::SUI;
    use sui::coin;

    let seller = @0xSELLER;
    let buyer = @0xBUYER;
    let mut scenario = test_scenario::begin(seller);

    // 卖家创建 Kiosk 并上架 Sword
    {
        let (mut kiosk, cap) = kiosk::new(scenario.ctx());
        let sword = new_sword(b"Flame Sword".to_string(), 50, vector[], scenario.ctx());
        let sword_id = object::id(&sword);
        kiosk::place_and_list(&mut kiosk, &cap, sword, 1_000_000_000);
        transfer::public_share_object(kiosk);
        transfer::public_transfer(cap, seller);
    };

    // 创建 TransferPolicy
    scenario.next_tx(seller);
    // ... 使用 Publisher 创建 Policy

    // 买家购买
    scenario.next_tx(buyer);
    {
        let mut kiosk = scenario.take_shared<kiosk::Kiosk>();
        let payment = coin::mint_for_testing<SUI>(1_000_000_000, scenario.ctx());
        // ... 购买逻辑
        test_scenario::return_shared(kiosk);
    };

    scenario.end();
}

小结

  • Kiosk 是 Sui 的去中心化商店标准,每个用户可拥有自己的 Kiosk
  • 操作流程:创建 Kiosk → 放置 NFT → 上架定价 → 买家购买 → 满足 Policy → 转移
  • KioskOwnerCap 是管理权凭证,持有者可放置、上架、下架、提取收益
  • TransferPolicy 定义 NFT 转移规则,是 Kiosk 交易的必要组件
  • TypeScript SDK 的 KioskClientKioskTransaction 提供了便捷的客户端操作