본문으로 건너뛰기

Sui Kiosk

Kiosk는 Sui의 commerce 애플리케이션을 위한 decentralized system이다. Kiosk는 individual party가 소유하는 공유 객체인 Kiosk 객체로 구성되며, 자산을 저장하고 판매용으로 listing할 수 있게 하며 auction 같은 custom trading 기능도 활용할 수 있게 한다. Sui Kiosk는 높은 decentralization을 유지하면서도 강력한 guarantee를 제공한다.

  • Kiosk 소유자는 purchase 시점까지 자산 소유권을 유지한다.
  • Creator는 모든 trade에 적용되는 규칙 set인 커스텀 정책을 설정한다(예: 로열티 fee 지불 또는 임의 액션 X 수행).
  • Marketplace는 Kiosk 객체가 emit하는 event를 index하고 onchain 자산 trading용 single feed를 subscribe할 수 있다.

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

정보

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

Sui Kiosk 소유자​

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

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

kiosk 소유자는 다음을 할 수 있다.

  • item place 및 take
  • 판매용 item list
  • Extension 추가 및 제거
  • sale profit 출금
  • owned 자산 차입 및 mutate
  • auction, lottery, collection bidding 같은 전체 trading tool set에 접근

buyer를 위한 Sui Kiosk​

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

이점:

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

책임:

  • 정책에 fee가 설정되어 있으면 buyer가 fee를 지불하는 party이다.
  • buyer는 creator가 설정한 규칙을 따라야 하며, 그렇지 않으면 트랜잭션이 성공하지 않는다.

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을 사용해 애플리케이션-specific transfer 규칙을 구현할 수 있다.

creator를 위한 Sui Kiosk​

creator에게 Sui Kiosk는 자산을 보호하고 자산 소유권을 enforce하기 위해 transfer 정책와 연관 규칙을 강력하게 enforce한다. Sui Kiosk는 creator에게 creation에 대한 더 많은 control을 제공하고, creator와 소유자가 work 사용 방식을 제어하도록 한다.

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

Creator가 할 수 있는 일:

  • trade에 대한 임의 규칙 설정
  • 여러 규칙 방식("tracks") 설정
  • 정책을 통해 언제든 trade 활성화 또는 비활성화
  • 모든 trade에 정책(예: 로열티) enforce
  • kiosk를 통해 자산의 primary sale 수행

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

Creator가 할 수 없는 일:

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

Sui Kiosk guarantees​

Sui Kiosk는 Sui가 스마트 계약을 통해 enforce하는 guarantee set을 제공한다. 이 guarantee에는 다음이 포함된다.

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

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

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

Sui Kiosk의 자산 state​

Sui Kiosk는 서로 다른 자산 collectible set 같은 heterogeneous value를 저장할 수 있는 공유 객체이다. kiosk에 자산을 추가하면 다음 state 중 하나를 가진다.

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

누군가 kiosk에서 자산을 purchase하면 자산은 kiosk를 떠나 buyer 주소로 ownership이 transfer된다.

Sui Kiosk 열기​

Sui Kiosk를 사용하려면 kiosk를 만들고 KioskOwnerCap과 matching되는 Kiosk 객체를 가져야 한다. kiosk::default 함수를 호출해 single 트랜잭션으로 새 kiosk를 만들 수 있다. 이 함수는 Kiosk를 만들고 share한 뒤 KioskOwnerCap을 사용자의 주소로 전송한다.

programmable 트랜잭션 블록으로 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한 사용 사례에서 storage 모델을 선택하거나 즉시 액션을 수행하려면 programmable 트랜잭션 블록 (PTB) 친화적인 kiosk::new 함수를 사용할 수 있다. Kiosk는 공유 객체로 설계되었다. owned 같은 다른 storage 모델을 선택하면 kiosk가 의도대로 동작하지 않거나 다른 사용자가 접근하지 못할 수 있다. Sui Testnet에서 테스트해 kiosk가 작동하는지 확인할 수 있다.

programmable 트랜잭션 블록으로 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와 트랜잭션 chaining을 지원하지 않는다. 대신 kiosk::default 함수를 사용할 수 있다.

kiosk에 item place 및 take​

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

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

kiosk에 item place​

kiosk에 item을 place하려면 소유자가 sui::kiosk::place 함수를 Kiosk 객체에서 호출하고 KioskOwnerCap과 Item을 argument로 전달해야 한다.

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

programmable 트랜잭션 블록으로 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 소유자여야 한다. 소유자로서 sui::kiosk::take 함수를 Kiosk 객체에서 호출하고 KioskOwnerCap과 item의 ID를 argument로 전달한다.

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

programmable 트랜잭션 블록으로 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 함수는 PTB friendly하게 설계되어 자산을 반환한다. Sui CLI는 아직 트랜잭션 chaining을 지원하지 않는다.

kiosk에서 item lock​

일부 정책는 strong 로열티 집행처럼 자산이 kiosk에서 절대 제거되지 않도록 요구한다. 이를 지원하기 위해 Sui Kiosk는 locking 메커니즘을 제공한다. locking은 placing과 유사하지만 locked 자산을 kiosk 밖으로 take할 수 없다는 점이 다르다.

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

정보

자산을 lock한 후에는 list 또는 list_with_purchase_cap 함수를 사용해 list해야 한다.

kiosk에서 item lock​

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

다음 예시의 <ITEM_TYPE>은 자산의 full 타입을 나타낸다.

programmable 트랜잭션 블록으로 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 기능을 제공한다. kiosk 소유자는 자산을 판매용으로 list할 수 있고 buyer는 이를 discover하고 purchase할 수 있다. Sui Kiosk는 기본적으로 세 가지 primary 함수으로 item listing을 지원한다.

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

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

kiosk에서 item list​

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

programmable 트랜잭션 블록으로 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 소유자는 kiosk::delist를 사용해 현재 listed된 자산을 delist할 수 있다. delist할 item을 argument로 지정한다.

item을 delist하면 Sui는 item을 list할 때 charged된 가스 수수료를 kiosk 소유자에게 반환한다.

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

programmable 트랜잭션 블록으로 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에 주소가 있는 누구나 Sui Kiosk에서 listed된 item을 purchase할 수 있다. item을 purchase하려면 kiosk::purchase 함수를 사용한다. purchase할 item을 지정하고 kiosk 소유자가 설정한 list price를 지불한다.

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

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

kiosk에서 item 차입​

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

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

Immutable 차입​

kiosk에서 자산은 언제나 immutably 차입할 수 있다. kiosk::borrow 함수를 사용해 자산을 차입할 수 있지만 programmable 트랜잭션 블록 안에서는 레퍼런스를 사용할 수 없다. 자산에 접근하려면 published 모듈(함수)을 사용해야 한다.

Sui Move로 자산을 immutably 차입​

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 차입​

자산이 listed 상태가 아니면 kiosk에서 mutably 차입할 수 있다. kiosk::borrow_mut 함수를 사용해 자산을 mutably 차입할 수 있다. 하지만 PTB 안에서는 레퍼런스를 사용할 수 없으므로, mutably borrowed 자산에 접근하려면 published 모듈(함수)을 사용해야 한다.

Sui Move로 자산을 mutably 차입​

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 차입​

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

programmable 트랜잭션 블록으로 borrow_val을 사용한 mutable 차입​

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 출금​

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

programmable 트랜잭션 블록으로 proceed 출금​

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 출금​

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