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에는
TransferPolicyresolution이 필요하다. 이를 통해 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로 자산을 판매용 listkiosk::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을 참조한다.