본문으로 건너뛰기

Sui Kiosk

Kiosk는 Sui의 commerce application을 위한 decentralized system이다. Kiosk는 individual party가 소유하는 shared object인 Kiosk object로 구성되며, asset을 저장하고 판매용으로 listing할 수 있게 하며 auction 같은 custom trading functionality도 활용할 수 있게 한다. Sui Kiosk는 높은 decentralization을 유지하면서도 강력한 guarantee를 제공한다.

  • Kiosk owner는 purchase 시점까지 asset ownership을 유지한다.
  • Creator는 모든 trade에 적용되는 rule set인 custom policy를 설정한다(예: royalty fee 지불 또는 임의 action X 수행).
  • Marketplace는 Kiosk object가 emit하는 event를 index하고 onchain asset trading용 single feed를 subscribe할 수 있다.

실제로 Kiosk는 Sui framework의 일부이며 system native로 제공되고 즉시 누구나 사용할 수 있다.

정보

TypeScript로 Kiosk를 사용하는 예시는 Kiosk SDK documentation을 참조한다.

Sui Kiosk owner

누구나 Sui Kiosk를 만들 수 있다. kiosk의 ownership은 단일 kiosk에 대한 full access를 부여하는 special object인 KioskOwnerCap의 owner가 결정한다. owner는 shared TransferPolicy가 제공되는 type(T)의 asset을 판매할 수 있고, shared policy가 없더라도 kiosk를 asset storage로 사용할 수 있다. 연관된 transfer policy가 없는 asset은 kiosk에서 판매하거나 transfer할 수 없다.

item을 판매하려면 type(T)에 대한 existing transfer policy가 있을 때 asset을 kiosk에 추가한 뒤 list하면 된다. item을 list할 때 offer amount를 지정한다. 그러면 누구나 listing에 지정된 SUI amount로 item을 purchase할 수 있다. 연관된 transfer policy는 buyer가 구매한 asset으로 무엇을 할 수 있는지 결정한다.

kiosk owner는 다음을 할 수 있다.

  • item place 및 take
  • 판매용 item list
  • Extension 추가 및 제거
  • sale profit withdraw
  • owned asset borrow 및 mutate
  • auction, lottery, collection bidding 같은 전체 trading tool set에 접근

buyer를 위한 Sui Kiosk

buyer는 kiosk에서 item을 purchase(또는 더 일반적으로 receive)하는 party이다. network의 누구나 buyer가 될 수 있으며, 동시에 kiosk owner일 수도 있다.

이점:

  • buyer는 global liquidity에 접근하고 best offer를 얻을 수 있다.
  • buyer는 자신의 kiosk를 통해 collection에 bid할 수 있다.
  • kiosk에서 수행되는 대부분의 buyer action은 seller object를 정리하므로 free(gas-less) action이 된다.

책임:

  • policy에 fee가 설정되어 있으면 buyer가 fee를 지불하는 party이다.
  • buyer는 creator가 설정한 rule을 따라야 하며, 그렇지 않으면 transaction이 성공하지 않는다.

Guarantees:

  • auction 같은 custom trading logic을 사용할 때 item은 trade가 완료될 때까지 변경되지 않음이 보장된다.

marketplace를 위한 Sui Kiosk

marketplace operator는 Sui Kiosk를 구현해 kiosk collection에서 생성된 offer를 관찰하고 marketplace site에 표시할 수 있다. Kiosk extension(community 또는 third-party가 생성)을 사용하는 custom system도 구현할 수 있다. 예를 들어 marketplace는 TransferPolicyCap을 사용해 application-specific transfer rule을 구현할 수 있다.

creator를 위한 Sui Kiosk

creator에게 Sui Kiosk는 asset을 보호하고 asset ownership을 enforce하기 위해 transfer policy와 연관 rule을 강력하게 enforce한다. Sui Kiosk는 creator에게 creation에 대한 더 많은 control을 제공하고, creator와 owner가 work 사용 방식을 제어하도록 한다.

Creator는 단일 type에 대한 TransferPolicy를 만들고 제어하는 party이다. 예를 들어 SuiFrens의 author는 SuiFren<Capy> type의 Creator이며 Kiosk ecosystem에서 creator로 동작한다. Creator는 policy를 설정하지만 kiosk를 통해 자신의 asset을 처음 판매하는 seller일 수도 있다.

Creator가 할 수 있는 일:

  • trade에 대한 임의 rule 설정
  • 여러 rule 방식("tracks") 설정
  • policy를 통해 언제든 trade 활성화 또는 비활성화
  • 모든 trade에 policy(예: royalty) enforce
  • kiosk를 통해 asset의 primary sale 수행

위 항목은 모두 즉시 전역적으로 적용된다.

Creator가 할 수 없는 일:

  • 다른 사람의 kiosk에 저장된 item을 take하거나 modify
  • policy에 locking rule이 설정되지 않았다면 kiosk에서 item을 take하는 것을 제한

Sui Kiosk guarantees

Sui Kiosk는 Sui가 smart contract를 통해 enforce하는 guarantee set을 제공한다. 이 guarantee에는 다음이 포함된다.

  • Sui Kiosk의 모든 trade에는 TransferPolicy resolution이 필요하다. 이를 통해 creator는 자신의 asset이 어떻게 trade될 수 있는지 제어한다.
  • true ownership. 즉 kiosk owner만 자신의 kiosk에 추가된 asset을 take, list, borrow 또는 modify할 수 있다. 이는 Sui의 single-owner object 작동 방식과 유사하다.
  • Royalty policy 같은 강력한 policy enforcement. creator는 언제든 policy를 활성화하거나 비활성화할 수 있으며, 해당 policy가 attached된 object에 대해 platform의 모든 trade에 적용된다.
  • TransferPolicy 변경 사항은 즉시 전역적으로 적용된다.

실제로 이 guarantee는 다음을 의미한다.

  • item을 판매용으로 list하면 누구도 이를 modify하거나 kiosk에서 take할 수 없다.
  • PurchaseCap을 정의하면 trade가 PurchaseCap을 사용하거나 반환하지 않는 한 item은 locked 상태로 유지되고 kiosk에서 modify하거나 take할 수 없다.
  • owner는 언제든 rule을 제거할 수 있다.
  • owner는 언제든 extension을 비활성화할 수 있다.
  • extension state의 state는 extension에서 항상 접근할 수 있다.

Sui Kiosk의 asset state

Sui Kiosk는 서로 다른 asset collectible set 같은 heterogeneous value를 저장할 수 있는 shared object이다. kiosk에 asset을 추가하면 다음 state 중 하나를 가진다.

  • PLACED: kiosk::place function을 사용해 kiosk에 place된 item. kiosk owner는 이를 withdraw해 직접 사용하거나, mutably 또는 immutably borrow하거나, 판매용으로 list할 수 있다.
  • LOCKED: kiosk::lock function을 사용해 kiosk에 place된 item. Locked item은 kiosk에서 withdraw할 수 없지만, mutably borrow하고 판매용으로 list할 수 있다. 연관된 kiosk lock policy가 있는 kiosk에 place된 모든 item은 LOCKED state를 가진다.
  • LISTED: kiosk::list 또는 kiosk::place_and_list function을 사용해 판매용으로 list된 kiosk 내 item. listed 상태에서는 item을 modify할 수 없지만 immutably borrow하거나 delist하여 이전 state로 되돌릴 수 있다.
  • LISTED EXCLUSIVELY: kiosk::list_with_purchase_cap function을 호출하는 extension이 kiosk에 place하거나 lock한 item. kiosk owner만 function 호출을 approve할 수 있다. owner는 이를 immutably borrow만 할 수 있다. extension은 asset을 delist하거나 unlock하는 기능을 제공해야 하며, 그렇지 않으면 영원히 locked 상태로 남을 수 있다. 이 action은 owner가 명시적으로 수행하므로, 검증되고 audited된 extension을 선택해 사용하는 책임은 owner에게 있다.

누군가 kiosk에서 asset을 purchase하면 asset은 kiosk를 떠나 buyer address로 ownership이 transfer된다.

Sui Kiosk 열기

Sui Kiosk를 사용하려면 kiosk를 만들고 Kiosk object와 matching되는 KioskOwnerCap을 가져야 한다. kiosk::default function을 호출해 single transaction으로 새 kiosk를 만들 수 있다. 이 function은 Kiosk를 만들고 share한 뒤 KioskOwnerCap을 사용자의 address로 transfer한다.

programmable transaction block으로 Sui Kiosk 생성

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

Sui CLI로 Sui Kiosk 생성

Beginning with the Sui v1.24.1 release, the --gas-budget option is no longer required for CLI commands.

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

advanced option으로 Sui Kiosk 생성

더 advanced한 use case에서 storage model을 선택하거나 즉시 action을 수행하려면 programmable transaction block (PTB) 친화적인 kiosk::new function을 사용할 수 있다. Kiosk는 shared object로 설계되었다. owned 같은 다른 storage model을 선택하면 kiosk가 의도대로 동작하지 않거나 다른 사용자가 접근하지 못할 수 있다. Sui Testnet에서 테스트해 kiosk가 작동하는지 확인할 수 있다.

programmable transaction block으로 advanced option을 사용해 Sui Kiosk 생성

let tx = new Transaction();
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로 advanced option을 사용해 Sui Kiosk 생성

Sui CLI는 아직 PTB와 transaction chaining을 지원하지 않는다. 대신 kiosk::default function을 사용할 수 있다.

kiosk에 item place 및 take

kiosk owner는 어떤 asset이든 Sui Kiosk에 place할 수 있다. 현재 판매용으로 listed 상태가 아닌 item은 kiosk에서 take할 수 있다.

kiosk에 place할 수 있는 asset에는 제한이 없다. 다만 kiosk에 place한 모든 item을 반드시 list하고 trade할 수 있는 것은 아니다. item type과 연관된 TransferPolicy가 trade 가능 여부를 결정한다. 자세한 내용은 kiosk에서 item purchase 섹션을 참조한다.

kiosk에 item place

kiosk에 item을 place하려면 owner가 Kiosk object에서 sui::kiosk::place function을 호출하고 KioskOwnerCapItem을 argument로 전달해야 한다.

다음 예시의 ITEM_TYPE은 item의 full type을 나타낸다.

programmable transaction block으로 item place

let tx = new Transaction();

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로 item place

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

kiosk에서 item take

kiosk에서 item을 take하려면 kiosk owner여야 한다. owner로서 Kiosk object에서 sui::kiosk::take function을 호출하고 KioskOwnerCap과 item의 ID를 argument로 전달한다.

다음 예시의 ITEM_TYPE은 item의 full type을 나타낸다.

programmable transaction block으로 kiosk에서 item take

let tx = new Transaction();

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에서 item take

kiosk::take function은 PTB friendly하게 설계되어 asset을 반환한다. Sui CLI는 아직 transaction chaining을 지원하지 않는다.

kiosk에서 item lock

일부 policy는 strong royalty enforcement처럼 asset이 kiosk에서 절대 제거되지 않도록 요구한다. 이를 지원하기 위해 Sui Kiosk는 locking mechanism을 제공한다. locking은 placing과 유사하지만 locked asset을 kiosk 밖으로 take할 수 없다는 점이 다르다.

kiosk에서 asset을 lock하려면 sui::kiosk::lock function을 호출한다. 나중에 asset을 unlock할 수 있도록 하려면 asset과 TransferPolicy를 associate해야 한다.

정보

asset을 lock한 후에는 list 또는 list_with_purchase_cap function을 사용해 list해야 한다.

kiosk에서 item lock

lock function을 사용할 때는 place function과 유사하게 KioskOwnerCapItem을 argument로 지정한다. 하지만 item을 lock하려면 TransferPolicy도 보여줘야 한다.

다음 예시의 <ITEM_TYPE>은 asset의 full type을 나타낸다.

programmable transaction block으로 item lock

const tx = new Transaction();

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로 item lock

$ 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에서 item list 및 delist

Sui Kiosk는 기본 trading functionality를 제공한다. kiosk owner는 asset을 판매용으로 list할 수 있고 buyer는 이를 discover하고 purchase할 수 있다. Sui Kiosk는 기본적으로 세 가지 primary function으로 item listing을 지원한다.

  • kiosk::list - fixed price로 asset을 판매용 list
  • kiosk::delist - existing listing 제거
  • kiosk::purchase - 판매용 listed asset purchase

network의 누구나 Sui Kiosk에 listed된 item을 purchase할 수 있다. purchase flow에 대해 더 알아보려면 Purchase section을 참조한다. asset state와 listed item에 수행할 수 있는 작업은 Asset States 섹션을 참조한다.

kiosk에서 item list

kiosk owner는 kiosk::list function을 사용해 kiosk에 추가한 어떤 asset이든 list할 수 있다. 판매할 item과 list price를 argument로 포함한다. Sui의 모든 listing은 SUI token으로 표시된다. item을 list하면 Sui는 kiosk ID, item ID, item type, list price를 포함하는 kiosk::ItemListed event를 emit한다.

programmable transaction block으로 item list

let tx = new Transaction();

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로 item list

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

item delist

kiosk owner는 kiosk::delist를 사용해 현재 listed된 asset을 delist할 수 있다. delist할 item을 argument로 지정한다.

item을 delist하면 Sui는 item을 list할 때 charged된 gas fee를 kiosk owner에게 반환한다.

item을 delist하면 Sui는 kiosk ID, item ID, item type을 포함하는 kiosk::ItemDelisted event를 emit한다.

programmable transaction block으로 item delist

let tx = new Transaction();
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로 item delist

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

kiosk에서 item purchase

Sui network에 address가 있는 누구나 Sui Kiosk에서 listed된 item을 purchase할 수 있다. item을 purchase하려면 kiosk::purchase function을 사용한다. purchase할 item을 지정하고 kiosk owner가 설정한 list price를 지불한다.

network에 listed된 item은 kiosk::ItemListed event로 discover할 수 있다.

kiosk::purchase function을 사용하면 purchased asset과 asset type에 연관된 TransferRequest가 반환된다. purchase를 완료하려면 asset에 적용된 TransferPolicy에 정의된 조건을 충족해야 한다.

kiosk에서 item borrow

kiosk owner는 asset을 kiosk에서 take하지 않고도 kiosk에 placed 또는 locked된 asset에 접근할 수 있다. asset은 항상 immutably borrow할 수 있다. asset을 mutably borrow할 수 있는지는 asset state에 따라 달라진다. 예를 들어 listed asset은 listed 상태에서 modify할 수 없으므로 borrow할 수 없다. 사용 가능한 function은 다음과 같다.

  • kiosk::borrow: asset에 대한 immutable reference 반환
  • kiosk::borrow_mut: asset에 대한 mutable reference 반환
  • kiosk::borrow_val: borrow_mut PTB-friendly version으로, 같은 transaction 안에서 asset을 take하고 다시 place할 수 있다.

Immutable borrow

kiosk에서 asset은 언제나 immutably borrow할 수 있다. kiosk::borrow function을 사용해 asset을 borrow할 수 있지만 programmable transaction block 안에서는 reference를 사용할 수 없다. asset에 접근하려면 published module(function)을 사용해야 한다.

Sui Move로 asset을 immutably borrow

module examples::immutable_borrow;

use sui::kiosk::{Self, Kiosk, KioskOwnerCap};

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

borrow_mut을 사용한 mutable borrow

asset이 listed 상태가 아니면 kiosk에서 mutably borrow할 수 있다. kiosk::borrow_mut function을 사용해 asset을 mutably borrow할 수 있다. 하지만 PTB 안에서는 reference를 사용할 수 없으므로, mutably borrowed asset에 접근하려면 published module(function)을 사용해야 한다.

Sui Move로 asset을 mutably borrow

module examples::mutable_borrow;

use sui::kiosk::{Self, Kiosk, KioskOwnerCap};

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

borrow_val을 사용한 mutable borrow

PTB-friendly kiosk::borrow_val function을 사용해 asset을 take하고 같은 transaction 안에서 다시 place한다. asset이 kiosk에 다시 place되도록 하기 위해 이 function은 caller에게 Hot Potato를 obligate한다. Hot Potato pattern에 대한 자세한 내용은 The Move Book을 참조한다.

programmable transaction block으로 borrow_val을 사용한 mutable borrow

let tx = new Transaction();

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

completed sale의 proceed withdraw

누군가 item을 purchase하면 Sui는 sale proceed를 kiosk에 저장한다. kiosk owner는 kiosk::withdraw function을 호출해 언제든 proceed를 withdraw할 수 있다. function은 단순하지만 PTB friendly하므로 현재 Sui CLI에서는 지원되지 않는다.

programmable transaction block으로 proceed withdraw

let tx = new Transaction();
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로 proceed withdraw

이 action은 현재 CLI environment에서 지원되지 않는다.