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

自定义转移策略

TransferPolicy 是 Kiosk 系统中控制 NFT 转移行为的核心机制。通过附加不同的规则(Rule),你可以实现版税收取、锁定要求、个人 Kiosk 限制等高级功能。本节将介绍如何创建和配置 TransferPolicy 及其规则。

TransferPolicy 概述

当买家从 Kiosk 购买 NFT 时,会生成一个 TransferRequest。这个请求必须满足 TransferPolicy 中所有已添加的规则后才能被确认,NFT 才能完成转移。

购买 NFT → 生成 TransferRequest → 满足所有 Rule → confirm_request → NFT 转移完成

创建 TransferPolicy

创建 Policy 需要 Publisher 对象证明你是该 NFT 类型的发布者:

use sui::transfer_policy::{Self, TransferPolicy, TransferPolicyCap};
use sui::package::Publisher;

fun create_transfer_policy<T>(
    publisher: &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());
}

规则机制说明

Sui Framework 只提供 TransferPolicy 原语(add_ruleget_ruleadd_receiptadd_to_balance 等),不提供现成的 sui::royalty_rulesui::kiosk_lock_rule 等模块。版税、锁定、个人 Kiosk 等规则需要:

  • 在 Move 中自行基于 transfer_policy::add_rule 实现,或
  • 使用生态包(如 MystenLabs Kiosk 包)中提供的规则。

下面以「版税规则」为例说明如何在 Move 中实现并满足规则;TS SDK 的用法仍可与 Kiosk 包或自定义规则配合使用。

版税规则(Royalty Rule)示例

每次交易按比例收取版税。前端可使用 @mysten/kioskRoyaltyRule(依赖 Kiosk 包)与已有 Policy 交互;在 Move 中需自行实现规则逻辑。

在 Move 中(自定义规则,仅用 framework):

// 自定义 Rule 与 Config,使用 transfer_policy::add_rule
module game::royalty_rule;

use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::transfer_policy::{Self as policy, TransferPolicy, TransferPolicyCap, TransferRequest};

const MAX_BP: u16 = 10_000;

public struct Rule has drop {}
public struct Config has store, drop { amount_bp: u16 }

public fun add<T: key + store>(
    policy: &mut TransferPolicy<T>,
    cap: &TransferPolicyCap<T>,
    amount_bp: u16,
) {
    assert!(amount_bp <= MAX_BP, 0);
    policy::add_rule(Rule {}, policy, cap, Config { amount_bp })
}

public fun pay<T: key + store>(
    policy: &mut TransferPolicy<T>,
    request: &mut TransferRequest<T>,
    payment: &mut Coin<SUI>,
    ctx: &mut TxContext,
) {
    let paid = policy::paid(request);
    let config = policy::get_rule(Rule {}, policy);
    let amount = ((paid as u128) * (config.amount_bp as u128) / (MAX_BP as u128)) as u64;
    assert!(coin::value(payment) >= amount, 1);
    let fee = coin::split(payment, amount, ctx);
    policy::add_to_balance(Rule {}, policy, fee);
    policy::add_receipt(Rule {}, request)
}

添加 5% 版税并创建 Policy 后,购买时需先调用该规则的 payconfirm_request

// 购买时:先 pay 版税,再 confirm
let (item, mut request) = kiosk::purchase<Sword>(kiosk, item_id, payment);
royalty_rule::pay(policy, &mut request, &mut royalty_payment, ctx);
transfer_policy::confirm_request(policy, request);
transfer::public_transfer(item, ctx.sender());

锁定规则(Kiosk Lock Rule)

要求买家将 NFT 锁定在自己的 Kiosk 中,不能直接取出。锁定规则的实现不在 Sui Framework 内,而是由 Kiosk 包 提供(如 kiosk::kiosk_lock_rule)。

前端可用 TypeScript 添加规则:

import { KioskLockRule } from "@mysten/kiosk/rules";

KioskLockRule.add(tx, {
  policy: policyId,
  policyCap: policyCapId,
});

若在 Move 中依赖 Kiosk 包,则添加与满足规则的方式类似:

// 依赖 Kiosk 包时
use kiosk::kiosk_lock_rule;

kiosk_lock_rule::add(policy, cap);

// 购买后锁入买家 Kiosk 并证明
let (item, mut request) = kiosk::purchase<Sword>(seller_kiosk, item_id, payment);
kiosk::lock(buyer_kiosk, buyer_cap, policy, item);
kiosk_lock_rule::prove(&mut request, buyer_kiosk);

个人 Kiosk 规则(Personal Kiosk Rule)

要求买家使用个人 Kiosk(不可转让 KioskOwnerCap 的 Kiosk)。该规则同样由 Kiosk 生态包提供,Framework 中无对应模块。

import { PersonalKioskRule } from "@mysten/kiosk/rules";

PersonalKioskRule.add(tx, {
  policy: policyId,
  policyCap: policyCapId,
});

创建个人 Kiosk:

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

kioskTx.createPersonal();
kioskTx.finalize();

组合多个规则

可以同时添加多个规则,所有规则都必须满足。版税类规则可在本包内用 transfer_policy::add_rule 实现;锁定、个人 Kiosk 等需依赖 Kiosk 包:

// 假设本包有 game::royalty_rule,并依赖 Kiosk 包
use game::royalty_rule;
use kiosk::kiosk_lock_rule;
use kiosk::personal_kiosk_rule;

public fun setup_strict_policy(
    policy: &mut TransferPolicy<Sword>,
    cap: &TransferPolicyCap<Sword>,
) {
    royalty_rule::add(policy, cap, 500);      // 5% 版税(自定义规则)
    kiosk_lock_rule::add(policy, cap);       // 必须锁定在 Kiosk(Kiosk 包)
    personal_kiosk_rule::add(policy, cap);   // 必须使用个人 Kiosk(Kiosk 包)
}

购买时满足所有规则:

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,
});

// SDK 会自动解析 Policy 中的规则并生成对应的满足逻辑
kioskTx.finalize();

自定义规则

除了内置规则,你还可以创建自定义规则:

module game::level_rule;

use sui::transfer_policy::{Self, TransferPolicy, TransferPolicyCap, TransferRequest};

public struct LevelRule() has drop;

public struct Config has store, drop {
    min_level: u64,
}

/// 添加等级要求规则
public fun add<T>(
    policy: &mut TransferPolicy<T>,
    cap: &TransferPolicyCap<T>,
    min_level: u64,
) {
    transfer_policy::add_rule(LevelRule(), policy, cap, Config { min_level });
}

/// 验证买家等级
public fun prove<T>(
    request: &mut TransferRequest<T>,
    player_level: u64,
    policy: &TransferPolicy<T>,
) {
    let config: &Config = transfer_policy::get_rule(LevelRule(), policy);
    assert!(player_level >= config.min_level);
    transfer_policy::add_receipt(LevelRule(), request);
}

提取版税收益

Policy 持有者可提取收集的版税:

let profits = transfer_policy::withdraw(
    policy,
    cap,
    option::none(), // none 表示全部提取
    ctx,
);
transfer::public_transfer(profits, ctx.sender());

小结

  • TransferPolicy 控制 NFT 通过 Kiosk 交易时的转移行为
  • Framework 只提供 add_rule / get_rule / add_receipt 等原语,无现成「内置」版税/锁定/个人 Kiosk 模块;版税等需自行实现或使用 Kiosk 包 中的规则
  • 多个规则可组合使用,所有规则都必须满足后转移才能完成
  • 可创建自定义规则实现特定业务逻辑
  • TypeScript SDK 的 KioskClient 可自动解析 Policy 规则并生成满足逻辑