Code in Move [6] — Minting NFTs with Kiosk on Sui

Thouny
Building on Sui
Published in
6 min readMar 27, 2024
stolen here

In the previous articles, we learnt about the basics of Sui Move and how to use Programmable Transaction Blocks. In this one, we’ll see how to issue an “nft collection” and for that we’ll use Kiosk with the TransferPolicy. Those are complex building primitives but they represent the uniqueness and power that Sui gives to developers and creators.

Objects are NFTs

You should have understand it by now, everything on Sui is an object and objects are NFTs. Objects have a unique identifier (UID) and have arbitrary fields or onchain metadata, exactly like NFTs on other chains. But they are also dynamic and composable, mutating a borrowed field or wrapping objects is straightforward. This is very much like virtual Legos. I know everybody is using this comparison but this is actually the only time I really felt it. As a developer, your creativity is not limited by the programming model. I suggest you to go through the best tweets of Brian, CTO of Studio Mirai, who is completely amazed by Sui Move and tells us his experience regularly.

Since everything is an object, there is no NFT standard and there is no such thing as NFT collection. So how does it work you may wonder? All objects have a type, when two objects have the same type we consider that they belong to the same collection. As a creator, you define a type and instantiate as many objects that you want in your “collection”. With this approach, you have huge flexibility to design your assets but the issue is how to display all this random objects and their collections on frontends (wallets, explorers, etc).

Standardizing with the Display Object

We use the object type to define the collection but that just gives us a name. To get richer information, there is the Display object (think it like the collection object). This object is associated to a type and hold all the common data for this one. You can define fields that will be displayed for each instance of this type (website, creator, etc) but also a template that will render fields in a specific way (image_url: “ipfs://{url}”), preventing data duplication.

It is created using the Publisher object during init. You can then update it as much as you need. Here is an example coming from Goose Bumps, you can see the code here.

Minting a NFT is then as simple as that:

True ownership with Kiosk

All this is pretty cool but it’s not very impressive. Kiosk enables developers and creators to do what they can’t on other chains. They can define and enforce complex behaviors for their type (collection).

Let’s take a simple example. You mint and sell a nft collection and you want traders to pay 5% royalties upon each purchase. On traditional blockchain there is no way to enforce this behavior. Marketplaces will be encouraged to implement a built-in function at best or their own custom system. Obviously nothing prevents users to bypass this system by trading in p2p or using an alternative marketplace.

At first, public_transfer and transfer were used to differentiate the logic for freely transferrable objects (with public) and objects needing a custom implementation to be transferred (like SBTs). Then people asked for a way to enforce royalties onchain so Damir built the Kiosk. No more FUD thanks to this!

Today, “NFTs” live in a Kiosk on Sui. This is a shared object where multiple permissions are defined. The owner has full ownership over all items present in the Kiosk thanks to the associated KioskOwnerCap. Different actions can be executed with a Kiosk:

  • place and take deposit or remove and item from the Kiosk
  • lock disallow the owner to take it out of the Kiosk
  • list and delist list it for purchase, anyone paying the minimum price can take it
  • list_with_purchase_cap enables only a particular account to purchase the item
  • borrow and borrow_mut allows the owner cap bearer to borrow mutably or not an item, meaning the ownership of the item never changes
  • and more with the extensions

As a NFT creator, you will usually create the object and directly place it into the user Kiosk (you need to pass the Kiosk and the KioskOwnerCap to your function) to prevent users bypassing the TransferPolicy you defined. Check a simple example here.

The Kiosk object has an owner field which is assigned the address who called kiosk::new(). This value is not reliable so there’s a KioskOwnerCap. One strategy is to assign the owner cap id (address) to this field to facilitate the tracking of the actual owner.

Custom behaviors with TransferPolicy

When creating a type that is supposed to used within the Kiosk ecosystem, at least one TransferPolicy<T> must be defined. Such a policy defines rules under which instances of the associated type can be moved between Kiosks. A policy can be empty if you don’t want to enforce any behavior but it can be as complex as you want. We could imagine that to sell my NFT, I would need to deposit SUI into a lending protocol, then borrow a Coin and stake it into another protocol to vote for a proposal. This doesn’t necessarily makes sense but it is just to illustrate the fact that the rules defined by a TransferPolicy can be absolutely anything!

Just like Display, a TransferPolicy is created using the Publisher object that you can obtain in the init function using a One-Time Witness (a type that guarantee to be instantiated once only).

example taken here

After creating the policy, one needs to append 0 or more rules. Rules are defined by developers in separate modules and Mysten Labs provide a set of standard rules for easier integration by the marketplaces. Then upon each transfer, a TransferRequest hot potato (struct that can’t be stored, discarded or copied) is emitted. This request will be field with the rules as action are executed within the PTB and eventually verified against the policy. For the transfer to succeed, the rules within the request must correspond to the ones in the policy.

example taken here

This is a little complex, so to keep this article short I strongly recommend you to go through this folder to get a real example of how it technically works. You can read the whole repository, it is goldmine for developers curious about Kiosk and TransferPolicy. When you’re done, you might want to check a real example of using Kiosk, TransferPolicy and managing all capability objects.

Wrapping up

As we saw, Kiosk is a powerful building primitive opening new patterns and behaviors that were impossible before. It is also rather complex, especially if you’re just getting started with Sui so I recommend you to check all the links I added in this article (including the docs) to get the complete overview. If you do so, you will still lack the extension feature that enables developers to upgrade the Kiosk as they need.

An interesting effect of this feature is an increased decentralization (like everything on Sui thanks to the object-centric model). Indeed, objects always remain in the possession of the owners. The items can be borrowed safely (e.g. for updating data in a game) and they are listed without living the Kiosk. Marketplaces don’t even need a smart contract, and act as an indexer and aggregator of all kiosk events across the entire ecosystem!

I really like the way Kiosk is designed and I can’t wait for its v2. It should give you ideas to design your own protocols. If you’re curious you can see how I used similar principles in this DeFi protocol. As always, feel free to drop a dm on Twitter if you have any questions.

--

--

Thouny
Building on Sui

Blockchain developer crafting educational and technical content on Web3 techs and philosophies. Digressions on life trying to make sense of it.