Skip to main content

Sui Kiosk

Kiosk 是 Sui 上的商业应用的分散系统。它由 Kiosks 组成 - 这些是由个体拥有的共享对象,用于存储资产并允许将其列出以供出售,同时还利用自定义交易功能,例如拍卖。尽管高度分散,Kiosk 提供了一系列强有力的保证:

  • Kiosk 拥有者在购买的瞬间保留其资产的所有权。
  • 创作者设置自定义策略 - 适用于每笔交易的一组规则(例如支付版税费用或执行某些任意操作 X)。
  • 市场可以索引 Kiosk 发出的事件并订阅单一的链上资产交易信息流。

实际上,Kiosk 是 Sui 框架的一部分,它是系统的一部分,可以直接对所有人开放。

info

查看 Kiosk SDK 文档 以获取使用 TypeScript 与 Kiosk 一起工作的示例。

Sui Kiosk 拥有者

任何人都可以创建一个 Sui Kiosk。拥有一个 kiosk 的所有权由 KioskOwnerCap 的所有者确定,这是一种特殊对象,赋予对单个 kiosk 的完全访问权限。作为所有者,你可以出售任何具有共享 TransferPolicy 的类型(T)的资产,或者你可以使用 kiosk 存储资产,即使没有共享策略也可以。你不能出售或转移你的 kiosk 中没有相关转移策略的任何资产。

要出售物品,如果类型(T)有现有的转移策略,你只需将资产添加到你的 kiosk,然后将其列出。在列出项目时,你需要指定一个报价金额。然后,任何人都可以按照列表中指定的 SUI 金额购买该项目。相关的转移策略确定买家可以对购买的资产执行哪些操作。

Kiosk 拥有者可以:

  • 放置和取回物品
  • 列出待售物品
  • 添加和删除扩展
  • 从销售中提取利润
  • 借用和改变拥有的资产
  • 访问完整的交易工具集,例如拍卖、彩票和集合竞标

Sui Kiosk 买家

买家是从 Kiosks 购买(或更一般地说,接收)物品的一方,网络上的任何人都可以是买家(例如,同时还可以是 Kiosk 拥有者)。

优势:

  • 买家可以获得全球流动性,并且可以得到最佳报价
  • 买家可以通过他们的 Kiosks 对集合进行投标
  • 在 Kiosks 中执行的大多数操作对于买家来说是免费的(无需 gas)

责任:

  • 如果策略中设置了费用,买家是支付费用的一方
  • 买家必须遵循由创作者设置的规则,否则交易将失败

保证:

  • 使用诸如拍卖等自定义交易逻辑时,物品在交易完成之前保持不变

Sui Kiosk 用于市场

作为市场运营商,你可以实现 Sui Kiosk,以监视在一组 kiosk 中提供的报价,并在市场网站上显示它们。你还可以使用 Kiosk 扩展实现自定义系统(由社区或第三方创建)。例如,市场可以使用 TransferPolicyCap 来实现特定于应用程序的转移规则。

Sui Kiosk 用于创作者

作为创作者,Sui Kiosk 支持对转移策略和相关规则的强制执行,以保护资产并执行资产所有权。Sui Kiosk 为创作者提供了对其作品更多控制权,并使创作者和所有者能够控制其作品的使用方式。

创作者是为单一类型创建并控制 TransferPolicy 的一方。例如,SuiFrens 的作者是 SuiFren<Capy> 类型的创作者,并在 Kiosk 生态系统中充当创作者。创作者设置策略,但他们也可能是通过 Kiosk 销售其资产的第一个卖家。

创作者可以:

  • 为交易设置任何规则
  • 设置多种方式("tracks")的规则
  • 使用策略随时启用或禁用交易
  • 在所有交易上强制执行策略(例如版税)
  • 通过 Kiosk 对其资产进行首次销售

所有这些都立即在全球范围内生效。

创作者不能:

  • 接管或修改存储在他人 kiosk 中的物品
  • 在策略中未设置“锁定”规则的情况下限制从 Kiosks 中取走物品

Sui Kiosk 保证

Sui Kiosk 提供了一套 Sui 通过智能合约强制执行的保证。 这些保证包括:

  • 在 Sui Kiosk 中的每笔交易都需要进行 TransferPolicy 解析。这使创作者能够控制其资产的交易方式。
  • 真正的所有权,这意味着只有 kiosk 拥有者可以取走、列出、借用或修改添加到其 kiosk 的资产。这类似于 Sui 上单一所有者对象的工作方式。
  • 强制执行策略,例如版税策略,允许创作者随时启用或禁用适用于附有该策略的平台上所有交易的策略。
  • 对于 TransferPolicy 的更改立即在全球范围内生效。

实际上,这些保证意味着:

  • 当你将物品列为待售时,没有人可以修改它或从 kiosk 中取走它。
  • 当你定义 PurchaseCap 时,物品保持锁定状态,除非交易使用或返回 PurchaseCap,否则你不能修改或从 kiosk 中取走该物品。
  • 你可以随时删除任何规则(作为所有者)。
  • 你可以随时禁用任何扩展(作为所有者)。
  • 扩展状态的状态始终对扩展可访问。

Sui kiosk 中的资产状态

Sui Kiosk 是一个共享对象,可以存储异构值,例如不同集合的资产收藏。当你将资产添加到你的 kiosk 时,它具有以下状态之一:

  • PLACED - 使用 kiosk::place 函数放置在 kiosk 中的物品。Kiosk 拥有者可以将其取回并直接使用,也可以以可变或不可变方式借用它,或将物品列为待售。
  • LOCKED - 使用 kiosk::lock 函数在 kiosk 中放置的物品。你无法从 kiosk 中取回已锁定的物品,但可以以可变方式借用并将其列为待售。放置在具有关联 Kiosk Lock 策略的 kiosk 中的任何物品都具有 LOCKED 状态。
  • LISTED - 使用 kiosk::listkiosk::place_and_list 函数列出的 kiosk 中的物品。你不能在列出时修改物品,但可以以不可变方式借用或将其撤回,从而将其返回到其先前的状态。
  • LISTED EXCLUSIVELY - 由扩展调用 kiosk::list_with_purchase_cap 函数在 kiosk 中放置或锁定的物品。只有 kiosk 拥有者可以批准调用该函数。所有者只能以不可变方式借用它。扩展必须提供撤回/解锁资产的功能,否则它可能永远保持锁定状态。鉴于此操作是由所有者明确执行的 - 所有者有责任选择经过验证和审核的扩展来使用。

当有人从 kiosk 中购买资产时,资产将离开 kiosk,并所有权将转移到买家的地址。

打开 Sui Kiosk

要使用 Sui Kiosk,你必须创建一个并拥有与 Kiosk 对象匹配的 KioskOwnerCap。你可以通过调用 kiosk::default 函数使用单个交易创建一个新的 kiosk。该函数创建并共享一个 Kiosk,并将 KioskOwnerCap 转移到你的地址。

使用可编程交易块创建 Kiosk

let tx = new TransactionBlock();
tx.moveCall({
target: '0x2::kiosk::default'
});

用 Sui CLI 创建 Kiosk

sui client call \
--package 0x2 \
--module kiosk \
--function default \
--gas-budget 1000000000

使用高级选项创建 Kiosk

对于更高级的用例,当你想要选择存储模型或立即执行操作时,你可以使用可编程交易块(PTB)友好的函数 kiosk::new。 Kiosk 被设计为共享的。如果你选择了不同的存储模型,比如 owned,你的 kiosk 可能无法按预期工作或无法让其他用户访问。你可以通过在 Sui Testnet 上进行测试来确保你的 Kiosk 正常运行。

使用可编程交易块使用高级选项创建 Kiosk

let tx = new TransactionBlock();
let [kiosk, kioskOwnerCap] = tx.moveCall({
target: '0x2::kiosk::new'
});

tx.transferObjects([ kioskOwnerCap ], sender);
tx.moveCall({
target: '0x2::transfer::public_share_object',
arguments: [ kiosk ],
typeArguments: '0x2::kiosk::Kiosk'
})

使用 SUI CLI 使用高级选项创建 Kiosk

SUI CLI 尚不支持 PTBs 和交易链接。你可以改为使用 kiosk::default 函数。

将物品放入和从你的 kiosk 中取出

作为 Kiosk 拥有者,你可以将任何资产放入你的 Sui Kiosk 中。你可以从尚未列出销售的 kiosk 中取出任何物品。 对于你可以放入 kiosk 中的物品,没有限制。然而,并不一定你可以列出和交易你放入 kiosk 中的所有物品。与物品类型相关联的 TransferPolicy 决定了你是否可以交易它。要了解更多信息,请参阅 从 Kiosk 购买物品 部分。

将物品放入你的 kiosk 中

要将物品放入 Kiosk,拥有者需要在 Kiosk 对象上调用 sui::kiosk::place 函数,并将 KioskOwnerCapItem 作为参数传递。

以下示例中的 ITEM_TYPE 表示物品的完整类型。

使用可编程交易块将物品放入

let tx = new TransactionBlock();

let itemArg = tx.object('<ID>');
let kioskArg = tx.object('<ID>');
let kioskOwnerCapArg = tx.object('<ID>');

tx.moveCall({
target: '0x2::kiosk::place',
arguments: [ kioskArg, kioskOwnerCapArg, itemArg ],
typeArguments: [ '<ITEM_TYPE>' ]
})

使用 Sui CLI 放置项目

sui client call \
--package 0x2 \
--module kiosk \
--function place \
--args "<KIOSK_ID>" "<CAP_ID>" "<ITEM_ID>" \
--type-args "<ITEM_TYPE>" \
--gas-budget 1000000000

从 kiosk 中取出物品

要从 kiosk 中取出物品,你必须是 kiosk 的所有者。作为所有者,请在 Kiosk 对象上调用 sui::kiosk::take 函数,并将 KioskOwnerCap 和物品的 ID 作为参数传递。

以下示例中的 ITEM_TYPE 表示物品的完整类型。

使用可编程交易块从 kiosk 中取出物品

let tx = new TransactionBlock();

let itemId = tx.pure.id('<ITEM_ID>');
let kioskArg = tx.object('<ID>');
let kioskOwnerCapArg = tx.object('<ID>');

let item = tx.moveCall({
target: '0x2::kiosk::take',
arguments: [ kioskArg, kioskOwnerCapArg, itemId ],
typeArguments: [ '<ITEM_TYPE>' ]
});

使用 SUI CLI 从 kiosk 中取出物品

kiosk::take 函数被设计为 PTB 友好,并返回资产。然而,SUI CLI 尚不支持交易链接。

在 kiosk 中锁定物品

某些策略要求资产永远不要从 kiosk 中移除,例如强制版税。为了支持这一点,Sui Kiosk 提供了一种锁定机制。锁定类似于放置,只是你不能将被锁定的资产从 Kiosk 中取出。

要在 kiosk 中锁定资产,请调用 sui::kiosk::lock 函数。为了确保稍后可以解锁资产,你必须将 TransferPolicy 与资产关联起来。

在 kiosk 中锁定物品

当你使用 lock 函数时,类似于使用 place 函数,你需要指定 KioskOwnerCapItem 作为参数。但要锁定物品,你还必须展示 TransferPolicy。

以下示例中的 <ITEM_TYPE> 表示资产的完整类型。

使用可编程交易块锁定物品

const tx = new TransactionBlock();

let kioskArg = tx.object('<ID>');
let kioskOwnerCapArg = tx.object('<ID>');
let itemArg = tx.object('<ID>');
let transferPolicyArg = tx.object('<ID>');

tx.moveCall({
target: '0x2::kiosk::lock',
arguments: [ kioskArg, kioskOwnerCapArg, transferPolicyArg, itemArg ],
typeArguments: [ '<ITEM_TYPE>' ]
});

使用 Sui CLI 锁定项目

sui client call \
--package 0x2 \
--module kiosk \
--function lock \
--args "<KIOSK_ID>" "<CAP_ID>" "<TRANSFER_POLICY_ID>" "<ITEM_ID>" \
--type-args "<ITEM_TYPE>" \
--gas-budget 1000000000

在 kiosk 中列出和取消列出物品

Sui Kiosk 提供基本的交易功能。作为 kiosk 的所有者,你可以列出资产供出售,而买家可以发现并购买它们。Sui Kiosk 默认支持通过三个主要函数列出物品:

  • kiosk::list - 以固定价格列出要出售的资产
  • kiosk::delist - 删除现有的列表
  • kiosk::purchase - 购买列出出售的资产

网络上的任何人都可以购买从 Sui Kiosk 列出的物品。要了解有关购买流程的更多信息,请参阅购买部分。要了解关于资产状态以及列出的物品可以执行的操作的更多信息,请参阅资产状态部分。

从 kiosk 中列出物品

作为 kiosk 的所有者,你可以使用 kiosk::list 函数列出你添加到 kiosk 中的任何资产。将要出售的物品和列出的价格作为参数。在 Sui 上的所有列出都以 SUI 代币计价。 当你列出物品时,Sui 会发出一个包含 Kiosk ID、Item ID、Item 类型和列出价格的 kiosk::ItemListed 事件。

使用可编程交易块列出物品

let tx = new TransactionBlock();

let kioskArg = tx.object('<ID>');
let capArg = tx.object('<ID>');
let itemId = tx.pure.id('<ID>');
let itemType = 'ITEM_TYPE';
let priceArg = tx.pure.u64('<price>'); // in MIST (1 SUI = 10^9 MIST)

tx.moveCall({
target: '0x2::kiosk::list',
arguments: [ kioskArg, capArg, itemId, priceArg ],
typeArguments: [ itemType ]
});

列出使用 Sui CLI 的项目

sui client call \
--package 0x2 \
--module kiosk \
--function list \
--args "<KIOSK_ID>" "<CAP_ID>" "<ITEM_ID>" "<PRICE>" \
--type-args "ITEM_TYPE" \
--gas-budget 1000000000

取消列出物品

作为 kiosk 的所有者,你可以使用 kiosk::delist 来取消列出任何当前列出的资产。将要取消列出的物品指定为参数。

当你取消列出物品时,Sui 会将用于列出物品的燃气费用退还给 kiosk 的所有者。

取消列出物品时,Sui 会发出一个包含 Kiosk ID、Item ID 和物品类型的 kiosk::ItemDelisted 事件。

使用可编程交易块取消列出物品

let tx = new TransactionBlock();
let kioskArg = tx.object('<ID>');
let capArg = tx.object('<ID>');
let itemId = tx.pure.id('<ID>');
let itemType = 'ITEM_TYPE';

tx.moveCall({
target: '0x2::kiosk::delist',
arguments: [ kioskArg, capArg, itemId ],
typeArguments: [ itemType ]
});

使用 Sui CLI 删除项目

sui client call \
--package 0x2 \
--module kiosk \
--function delist \
--args "<KIOSK_ID>" "<CAP_ID>" "<ITEM_ID>" \
--type-args "ITEM_TYPE" \
--gas-budget 1000000000

从 kiosk 购买物品

在 Sui 网络上拥有地址的任何人都可以购买从 Sui Kiosk 列出的物品。要购买物品,你可以使用 kiosk::purchase 函数。指定要购买的物品,并支付由 Kiosk 所有者设置的列表价格。

你可以使用 kiosk::ItemListed 事件查看在网络上列出的物品。

当你使用 kiosk::purchase 函数时,它会返回已购买的资产和与资产关联的 TransferRequest。要完成购买,你必须满足应用于资产的 TransferPolicy 中定义的条款。了解更多信息,请参阅 TransferPolicy 主题。

从 kiosk 借用物品

作为 kiosk 的所有者,你可以在不从 kiosk 取走资产的情况下访问放置或锁定在 kiosk 中的资产。你可以始终以不可变的方式借用资产。是否可以以可变方式借用资产取决于资产的状态。例如,你无法借用已列出的资产,因为在列出时无法修改它。可用的功能包括:

  • kiosk::borrow - 返回资产的不可变引用
  • kiosk::borrow_mut - 返回资产的可变引用
  • kiosk::borrow_val - borrow_mut 的 PTB 友好版本,允许你在同一事务中获取并放置资产。

不可变借用

你始终可以以不可变的方式从 kiosk 借用资产。你可以使用 kiosk::borrow 函数借用资产,但在可编程交易块中无法使用引用。要访问资产,你必须使用已发布的模块(函数)。

使用 Sui Move 不可变借用资产

module examples::immutable_borrow {
use sui::object::ID;
use sui::kiosk::{Self, Kiosk, KioskOwnerCap};

public fun immutable_borrow_example<T>(self: &Kiosk, cap: &KioskOwnerCap, item_id: ID): &T {
kiosk::borrow(self, cap, item_id)
}
}

使用 borrow_mut 进行可变借用

如果资产未列出,你可以从 kiosk 可变地借用资产。你可以使用 kiosk::borrow_mut 函数进行可变借用。但是,在 PTB 中无法使用引用,因此要访问可变借用的资产,你必须使用已发布的模块(函数)。

使用 Sui Move 进行可变借用资产

module examples::mutable_borrow
use sui::object::ID;
use sui::kiosk::{Self, Kiosk, KioskOwnerCap};

public fun mutable_borrow_example<T>(
self: &mut Kiosk, cap: &KioskOwnerCap, item_id: ID
): &mut T {
kiosk::borrow_mut(self, cap, item_id)
}
}

使用 borrow_val 进行可变借用

你可以使用 PTB-friendly 的 kiosk::borrow_val 函数。它允许你获取资产并在同一事务中放回。为确保资产被放回 kiosk,该函数通过 “Hot Potato” 强制调用者。

使用 borrow_val 进行可变借用,使用可编程交易块

let tx = new TransactionBlock();

let itemType = 'ITEM_TYPE';
let itemId = tx.pure.id('<ITEM_ID>');
let kioskArg = tx.object('<ID>');
let capArg = tx.object('<ID>');

let [item, promise] = tx.moveCall({
target: '0x2::kiosk::borrow_val',
arguments: [ kioskArg, capArg, itemId ],
typeArguments: [ itemType ],
});

// freely mutate or reference the `item`
// any calls are available as long as they take a reference
// `returnValue` must be explicitly called

tx.moveCall({
target: '0x2::kiosk::return_val',
arguments: [ kioskArg, item, promise ],
typeArguments: [ itemType ],
});

从已完成的销售中提取收益

当有人购买物品时,Sui 将销售收益存储在 Kiosk 中。作为 kiosk 拥有者,你可以随时通过调用 kiosk::withdraw 函数提取收益。该函数很简单,但由于它对 PTB 友好,目前不支持在 Sui CLI 中使用。

使用可编程交易块提取收益

let tx = new TransactionBlock();
let kioskArg = tx.object('<ID>');
let capArg = tx.object('<ID>');

// because the function uses an Option<u64> argument,
// constructing is a bit more complex
let amountArg = tx.moveCall({
target: '0x1::option::some',
arguments: [ tx.pure.u64('<amount>') ],
typeArguments: [ 'u64' ],
});

// alternatively
let withdrawAllArg = tx.moveCall({
target: '0x1::option::none',
typeArguments: [ 'u64' ],
});

let coin = tx.moveCall({
target: '0x2::kiosk::withdraw',
arguments: [ kioskArg, capArg, amountArg ],
typeArguments: [ 'u64' ],
});

使用 Sui CLI 提取收益

由于该函数对 PTB 友好,目前不支持在 CLI 环境中使用。