IOTA Kiosk
Kiosk is a decentralized system for commerce applications on IOTA. It consists of Kiosks - shared objects owned by individual parties that store assets and allow listing them for sale as well as utilize custom trading functionality - for example, an auction. While being highly decentralized, Kiosk provides a set of strong guarantees:
- Kiosk owners retain ownership of their assets to the moment of purchase.
- Creators set custom policies - sets of rules applied to every trade (such as pay royalty fee or do some arbitrary action X).
- Marketplaces can index events the Kiosk emits and subscribe to a single feed for on-chain asset trading.
Practically, Kiosk is a part of the IOTA framework, and it is native to the system and available to everyone out of the box.
See the Kiosk SDK documentation for examples of working with Kiosk using TypeScript.
IOTA Kiosk Owners
Anyone can create an IOTA Kiosk. Ownership of a kiosk is determined by the owner of the KioskOwnerCap
, a special object that grants full access to a single kiosk. As the owner, you can sell any asset with a type (T) that has a shared TransferPolicy
available, or you can use a kiosk to store assets even without a shared policy. You can’t sell or transfer any assets from your kiosk that do not have an associated transfer policy available.
To sell an item, if there is an existing transfer policy for the type (T), you just add your assets to your kiosk and then list them. You specify an offer amount when you list an item. Anyone can then purchase the item for the amount of IOTA specified in the listing. The associated transfer policy determines what the buyer can do with the purchased asset.
A Kiosk owner can:
- Place and take items
- List items for sale
- Add and remove Extensions
- Withdraw profits from sales
- Borrow and mutate owned assets
- Access the full set of trading tools, such as auctions, lotteries, and collection bidding
IOTA Kiosk for Buyers
A buyer is a party that purchases (or - more generally - receives) items from Kiosks, anyone on the network can be a Buyer (and, for example, a Kiosk Owner at the same time).
Benefits:
- Buyers get access to global liquidity and can get the best offer
- Buyers can place bids on collections through their Kiosks
- Most of the actions performed in Kiosks are free (gas-less) for Buyers
Responsibilities:
- Buyer is the party that pays the fees if they're set in the policy
- Buyer must follow the rules set by creators or a transaction won't succeed
Guarantees:
- When using a custom trading logic such as an Auction, the items are guaranteed to be unchanged until the trade is complete
IOTA Kiosk for Marketplaces
As a marketplace operator, you can implement IOTA Kiosk to watch for offers made in a collection of kiosks and display them on a marketplace site. You can also implement a custom system using Kiosk extensions (created by the community or third-parties). For example, marketplaces can use a TransferPolicyCap
to implement application-specific transfer rules.
IOTA Kiosk for Creators
As a creator, IOTA Kiosk supports strong enforcement for transfer policies and associated rules to protect assets and enforce asset ownership. IOTA Kiosk gives creators more control over their creations, and puts creators and owners in control of how their works can be used.
A creator is a party that creates and controls the TransferPolicy for a single type. Creators set the policy, but they might also be the first sellers of their assets through a Kiosk.
Creators can:
- Set any rules for trades
- Set multiple ways ("tracks") of rules
- Enable or disable trades at any moment with a policy
- Enforce policies (eg royalties) on all trades
- Perform a primary sale of their assets through a Kiosk
All of the above is effective immediately and globally.
Creators cannot:
- Take or modify items stored in someone else's Kiosk
- Restrict taking items from Kiosks if the "locking" rule was not set in the policy
IOTA Kiosk Guarantees
IOTA Kiosk provides a set of guarantees that IOTA enforces through smart contracts. These guarantees include:
- Every trade in IOTA Kiosk requires a
TransferPolicy
resolution. This gives creators control over how their assets can be traded. - True Ownership, which means that only a kiosk owner can take, list, borrow, or modify the assets added to their kiosk. This is similar to how single-owner objects work on IOTA.
- Strong policy enforcement, for example Royalty policies, that lets creators enable or disable policies at any time that applies to all trades on the platform for objects with that policy attached.
- Changes to a
TransferPolicy
apply instantly and globally.
In practice, these guarantees mean that:
- When you list an item for sale, no one can modify it or take it from the kiosk.
- When you define a
PurchaseCap
, an item remains locked and you can’t modify or take the item from the kiosk unless the trade uses or returns thePurchaseCap
. - You can remove any rule at any time (as the owner).
- You can disable any extension at any time (as the owner).
- The state of an extension state is always accessible to the extension.
Asset States in IOTA Kiosk
IOTA Kiosk is a shared object that can store heterogeneous values, such as different sets of asset collectibles. When you add an asset to your kiosk, it has one of the following states:
- PLACED - an item placed in the kiosk using the
kiosk::place
function. The Kiosk Owner can withdraw it and use it directly, borrow it (mutably or immutably), or list an item for sale. - LOCKED - an item placed in the kiosk using the
kiosk::lock
function. You can’t withdraw a Locked item from a kiosk, but you can borrow it mutably and list it for sale. Any item placed in a kiosk that has an associated Kiosk Lock policy has a LOCKED state. - LISTED - an item in the kiosk that is listed for sale using the
kiosk::list
orkiosk::place_and_list
functions. You can’t modify an item while listed, but you can borrow it immutably or delist it, which returns it to its previous state. - LISTED EXCLUSIVELY - an item placed or locked in the kiosk by an extension that calls the
kiosk::list_with_purchase_cap
function. Only the kiosk owner can approve calling the function. The owner can only borrow it immutably. The extension must provide the functionality to delist / unlock the asset, or it might stay locked forever. Given that this action is explicitly performed by the Owner - it is the responsibility of the Owner to choose verified and audited extensions to use.
When someone purchases an asset from a kiosk, the asset leaves the kiosk and ownership transfers to the buyer’s address.
Open an IOTA Kiosk
To use an IOTA Kiosk, you must create one and have the KioskOwnerCap
that matches the Kiosk
object. You can create a new kiosk using a single transaction by calling the kiosk::default
function. The function creates and shares a Kiosk
, and transfers the KioskOwnerCap
to your address.
Create an IOTA Kiosk Using Programmable Transaction Blocks
let tx = new Transaction();
tx.moveCall({
target: '0x2::kiosk::default'
});
Create an IOTA Kiosk Using the IOTA CLI
iota client call \
--package 0x2 \
--module kiosk \
--function default
Create an IOTA Kiosk With Advanced Options
For more advanced use cases, when you want to choose the storage model or perform an action right away, you can use the programmable transaction block (PTB) friendly function kiosk::new
.
Kiosk is designed to be shared. If you choose a different storage model, such as owned, your kiosk might not function as intended or not be accessible to other users. You can make sure your Kiosk works by testing it on IOTA Testnet.
Create an Iota Kiosk With Advanced Options Using Programmable Transaction Blocks
let tx = new Transaction();
let sender = "0x...";
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']
})
Create an IOTA Kiosk With Advanced Options Using a PTB in the IOTA CLI
iota client ptb \
--move-call 0x2::kiosk::new \
--assign kiosk \
--move-call 0x2::transfer::public_share_object "<0x2::kiosk::Kiosk>" kiosk.0 \
--transfer-objects [kiosk.1] <YOUR_ADDRESS>
Since 0x2::kiosk::new
returns a tuple, kiosk.0
refers to the Kiosk
itself and kiosk.1
to the KioskOwnerCap
.
Place Items in and Take Items From Your Kiosk
As a kiosk owner, you can place any assets into your IOTA Kiosk. You can take any item from your kiosk that is not currently listed for sale.
There's no limitations on which assets you can place in your kiosk. However, you can’t necessarily list and trade all of the items you place in your kiosk. The TransferPolicy
associated with the type for the item determines whether you can trade it. To learn more, see the Purchase items from a Kiosk section.
Place an Item in Your Kiosk
To place an item to the Kiosk, the owner needs to call the iota::kiosk::place
function on the Kiosk
object and pass the KioskOwnerCap
and the Item
as arguments.
ITEM_TYPE
in the following examples represents the full type of the item.
Place an Item Using Programmable Transaction Blocks
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>' ]
})
Place an Item Using the IOTA CLI
iota client call \
--package 0x2 \
--module kiosk \
--function place \
--args "<KIOSK_ID>" "<CAP_ID>" "<ITEM_ID>" \
--type-args "<ITEM_TYPE>"
Take Items from a Kiosk
To take an item from a kiosk you must be the kiosk owner. As the owner, call the iota::kiosk::take
function on the Kiosk
object, and pass the KioskOwnerCap
and ID
of the item as arguments.
ITEM_TYPE
in the following examples represents the full type of the item.
Take an Item From a Kiosk Using Programmable Transaction Blocks
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>' ]
});
Take an Item from a Kiosk Using the IOTA CLI
The kiosk::take
function is built to be PTB friendly and returns the asset. In this IOTA CLI PTB example we transfer the taken item to the given address.
iota client ptb \
--assign kiosk @<KIOSK_ID> \
--assign kiosk_owner_cap @<KIOSK_OWNER_CAP_ID> \
--assign item @<ITEM_ID> \
--move-call 0x2::kiosk::take "<ITEM_TYPE>" kiosk kiosk_owner_cap item \
--assign taken_item \
--transfer-objects [taken_item] <YOUR_ADDRESS>
Lock Items in a Kiosk
Some policies require that assets never get removed from a kiosk, such as for strong royalty enforcement. To support this, IOTA Kiosk provides a locking mechanism. Locking is similar to placing except that you can't take a locked asset out of the kiosk.
To lock an asset in a kiosk, call the iota::kiosk::lock
function. To ensure that you can later unlock the asset you must associate a TransferPolicy
with the asset.
After you lock an asset, you must use list
or list_with_purchase_cap
functions to list it.
Lock an Item in a Kiosk
When you use the lock
function, similar to using the place
function, you specify the KioskOwnerCap
and the Item
as arguments. But to lock the item, you must also show the TransferPolicy
.
<ITEM_TYPE>
in the following examples represents the full type of the asset.
Lock an Item Using Programmable Transaction Blocks
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>' ]
});
Lock an Item Using the IOTA CLI
iota client call \
--package 0x2 \
--module kiosk \
--function lock \
--args "<KIOSK_ID>" "<CAP_ID>" "<TRANSFER_POLICY_ID>" "<ITEM_ID>" \
--type-args "<ITEM_TYPE>"
List and Delist Items From a Kiosk
IOTA Kiosk provides basic trading functionality. As a kiosk owner, you can list assets for sale, and buyers can discover and purchase them. IOTA Kiosk supports listing items by default with three primary functions:
kiosk::list
- list an asset for sale for a fixed pricekiosk::delist
- remove an existing listingkiosk::purchase
- purchase an asset listed for sale
Anyone on the network can purchase an item listed from an IOTA Kiosk. To learn more about the purchase flow, see the Purchase section. To learn more about asset states and what can be done with a listed item, see the Asset States section.
List an Item From a Kiosk
As a kiosk owner, you can use the kiosk::list
function to list any asset you added to your kiosk by including the item to sell and the list price as arguments. All listings on IOTA are in IOTA coins.
When you list an item, IOTA emits a kiosk::ItemListed
event that contains the Kiosk ID, Item ID, the type of the Item, and the list price.
List an Item Using Programmable Transaction Blocks
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 NANOS (1 IOTA = 10^9 NANOS)
tx.moveCall({
target: '0x2::kiosk::list',
arguments: [ kioskArg, capArg, itemId, priceArg ],
typeArguments: [ itemType ]
});
List an Item Using the IOTA CLI
iota client call \
--package 0x2 \
--module kiosk \
--function list \
--args "<KIOSK_ID>" "<CAP_ID>" "<ITEM_ID>" "<PRICE>" \
--type-args "ITEM_TYPE"
Delist an Item
As a kiosk owner you can use the kiosk::delist
to delist any currently listed asset. Specify the item to delist as an argument.
When you delist an item, IOTA returns to the kiosk owner the gas fees charged to list the item.
When you delist an item, IOTA emits a kiosk::ItemDelisted
event that contains the Kiosk ID, Item ID, and the type of the item.
Delist an Item Using Programmable Transaction Blocks
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 ]
});
Delist an Item Using the IOTA CLI
iota client call \
--package 0x2 \
--module kiosk \
--function delist \
--args "<KIOSK_ID>" "<CAP_ID>" "<ITEM_ID>" \
--type-args "ITEM_TYPE"
Purchase an Item from a Kiosk
Anyone that has an address on the IOTA network can purchase an item listed from an IOTA Kiosk. To purchase an item, you can use the kiosk::purchase
function. Specify the item to purchase and pay the list price set by the Kiosk Owner.
You can discover the items listed on the network with the kiosk::ItemListed
event.
When you use the kiosk::purchase
function, it returns the purchased asset and the TransferRequest
for the type associated with the asset. To complete the purchase, you must meet the terms defined in the TransferPolicy
applied to the asset.
Purchase an Item Using the IOTA CLI
iota client ptb \
--assign kiosk @<KIOSK_ID> \
--assign item @<ITEM_ID> \
--assign coin @<COIN_ID> \
--assign transfer_policy @<TRANSFER_POLICY_ID> \
# We have to purchase the item with a `Coin<IOTA>` whose balance matches the item price exactly.
# In this example we take one of our coins as input and split off a `Coin<IOTA>` with the required balance.
# The remainder will be sent back to us.
--split-coins coin [<ITEM_PRICE_NANOS>] \
--assign coins \
--move-call 0x2::kiosk::purchase '<ITEM_TYPE>' kiosk item coins.0 \
# This is a tuple consisting of the purchased item and a `TransferRequest<ITEM_TYPE>` that we need to resolve in this PTB.
--assign purchase_result \
# Resolve the `TransferRequest<ITEM_TYPE>` by confirming it with `TransferPolicy<ITEM_TYPE>`.
--move-call 0x2::transfer_policy::confirm_request '<ITEM_TYPE>' transfer_policy purchase_result.1 \
# Transfer the purchased item to ourselves.
--transfer-objects [purchase_result.0] <YOUR_ADDRESS>
Borrow an Item from a Kiosk
As a kiosk owner, you can access an asset placed or locked in a kiosk without taking the asset from the kiosk. You can always borrow the asset immutably. Whether you can mutably borrow an asset depends on the state of the asset. For example, you can’t borrow a listed asset because you can’t modify it while listed. The functions available include:
kiosk::borrow
- returns an immutable reference to the assetkiosk::borrow_mut
- returns a mutable reference to the assetkiosk::borrow_val
- a PTB-friendly version ofborrow_mut
, which allows you to take an asset and place it back in the same transaction.
Immutable Borrow
You can always borrow an asset from a kiosk immutably. You can use the kiosk::borrow
function to borrow an asset, however, it is not possible to use references within a programmable transaction block. To access the asset you must use a published module (function).
Immutably Borrow an Asset Using IOTA Move
module examples::immutable_borrow {
use iota::object::ID;
use iota::kiosk::{Self, Kiosk, KioskOwnerCap};
public fun immutable_borrow_example<T>(self: &Kiosk, cap: &KioskOwnerCap, item_id: ID): &T {
kiosk::borrow(self, cap, item_id)
}
}
Mutable Borrow with borrow_mut
You can mutably borrow an asset from a kiosk if it is not listed. You can use the kiosk::borrow_mut
function to mutably borrow an asset. However, it is not possible to use references within a PTB, so to access the mutably borrowed asset you must use a published module (function).
Mutably Borrow an Asset Using IOTA Move
module examples::mutable_borrow {
use iota::object::ID;
use iota::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)
}
}
Mutable Borrow with borrow_val
You can use the PTB-friendly kiosk::borrow_val
function. It allows you to take an asset and place it back in the same transaction. To make sure the asset is placed back into the kiosk, the function "obliges" the caller with a “Hot Potato”.
Mutable Borrow with borrow_val
Using Programmable Transaction Blocks
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.
// `return_val` must be explicitly called.
tx.moveCall({
target: '0x2::kiosk::return_val',
arguments: [ kioskArg, item, promise ],
typeArguments: [ itemType ],
});
Withdraw Proceeds from a Completed Sale
When someone purchases an item, IOTA stores the proceeds from the sale in the Kiosk. As the kiosk owner, you can withdraw the proceeds at any time by calling the kiosk::withdraw
function.
Withdraw Proceeds Using Programmable Transaction Blocks
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' ],
});
Withdraw Proceeds Using the IOTA CLI
iota client ptb \
--assign kiosk @<KIOSK_ID> \
--assign kiosk_owner_cap @<KIOSK_OWNER_CAP_ID> \
--move-call 0x2::kiosk::withdraw kiosk kiosk_owner_cap none \
--assign withdrawn_coin \
--transfer-objects [withdrawn_coin] <YOUR_ADDRESS>
Passing none
withdraws all profits, while passing some(x)
will attempt to withdraw a Coin<IOTA>
with a balance of x
. This amount x
can be smaller or equal to the contained profits.