Skip to main content

Module 0x2::kiosk_extension

This module implements the Kiosk Extensions functionality. It allows exposing previously protected (only-owner) methods to third-party apps.

A Kiosk Extension is a module that implements any functionality on top of the Kiosk without discarding nor blocking the base. Given that Kiosk itself is a trading primitive, most of the extensions are expected to be related to trading. However, there's no limit to what can be built using the kiosk_extension module, as it gives certain benefits such as using Kiosk as the storage for any type of data / assets.

Flow:

  • An extension can only be installed by the Kiosk Owner and requires an authorization via the KioskOwnerCap.
  • When installed, the extension is given a permission bitmap that allows it to perform certain protected actions (eg place, lock). However, it is possible to install an extension that does not have any permissions.
  • Kiosk Owner can disable the extension at any time, which prevents it from performing any protected actions. The storage is still available to the extension until it is completely removed.
  • A disabled extension can be enabled at any time giving the permissions back to the extension.
  • An extension permissions follow the all-or-nothing policy. Either all of the requested permissions are granted or none of them (can't install).

Examples:

  • An Auction extension can utilize the storage to store Auction-related data while utilizing the same Kiosk object that the items are stored in.
  • A Marketplace extension that implements custom events and fees for the default trading functionality.

Notes:

use 0x2::bag;
use 0x2::dynamic_field;
use 0x2::kiosk;
use 0x2::object;
use 0x2::transfer_policy;
use 0x2::tx_context;

Struct Extension

The Extension struct contains the data used by the extension and the configuration for this extension. Stored under the ExtensionKey dynamic field.

struct Extension has store
Fields
storage: bag::Bag
Storage for the extension, an isolated Bag. By putting the extension into a single dynamic field, we reduce the amount of fields on the top level (eg items / listings) while giving extension developers the ability to store any data they want.
permissions: u128
Bitmap of permissions that the extension has (can be revoked any moment). It's all or nothing policy - either the extension has the required permissions or no permissions at all.

1st bit - place - allows to place items for sale 2nd bit - lock and place - allows to lock items (and place)

For example:

  • 10 - allows to place items and lock them.
  • 11 - allows to place items and lock them (lock includes place).
  • 01 - allows to place items, but not lock them.
  • 00 - no permissions.
is_enabled: bool
Whether the extension can call protected actions. By default, all extensions are enabled (on add call), however the Kiosk owner can disable them at any time.

Disabling the extension does not limit its access to the storage.

Struct ExtensionKey

The ExtensionKey is a typed dynamic field key used to store the extension configuration and data. Ext is a phantom type that is used to identify the extension witness.

struct ExtensionKey<Ext> has copy, drop, store
Fields
dummy_field: bool

Constants

Trying to add an extension while not being the owner of the Kiosk.

const ENotOwner: u64 = 0;

Extension is trying to access a permissioned action while not having the required permission.

const EExtensionNotAllowed: u64 = 2;

Extension is not installed in the Kiosk.

const EExtensionNotInstalled: u64 = 3;

Value that represents the lock and place permission in the permissions bitmap.

const LOCK: u128 = 2;

Value that represents the place permission in the permissions bitmap.

const PLACE: u128 = 1;

Function add

Add an extension to the Kiosk. Can only be performed by the owner. The extension witness is required to allow extensions define their set of permissions in the custom add call.

public fun add<Ext: drop>(_ext: Ext, self: &mut kiosk::Kiosk, cap: &kiosk::KioskOwnerCap, permissions: u128, ctx: &mut tx_context::TxContext)
Implementation
public fun add<Ext: drop>(
    _ext: Ext,
    self: &mut Kiosk,
    cap: &KioskOwnerCap,
    permissions: u128,
    ctx: &mut TxContext
) {
    assert!(self.has_access(cap), ENotOwner);
    df::add(
        self.uid_mut_as_owner(cap),
        ExtensionKey<Ext> {},
        Extension {
            storage: bag::new(ctx),
            permissions,
            is_enabled: true,
        }
    )
}

Function disable

Revoke permissions from the extension. While it does not remove the extension completely, it keeps it from performing any protected actions. The storage is still available to the extension (until it's removed).

public fun disable<Ext: drop>(self: &mut kiosk::Kiosk, cap: &kiosk::KioskOwnerCap)
Implementation
public fun disable<Ext: drop>(
    self: &mut Kiosk,
    cap: &KioskOwnerCap,
) {
    assert!(self.has_access(cap), ENotOwner);
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);
    extension_mut<Ext>(self).is_enabled = false;
}

Function enable

Re-enable the extension allowing it to call protected actions (eg place, lock). By default, all added extensions are enabled. Kiosk owner can disable them via disable call.

public fun enable<Ext: drop>(self: &mut kiosk::Kiosk, cap: &kiosk::KioskOwnerCap)
Implementation
public fun enable<Ext: drop>(
    self: &mut Kiosk,
    cap: &KioskOwnerCap,
) {
    assert!(self.has_access(cap), ENotOwner);
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);
    extension_mut<Ext>(self).is_enabled = true;
}

Function remove

Remove an extension from the Kiosk. Can only be performed by the owner, the extension storage must be empty for the transaction to succeed.

public fun remove<Ext: drop>(self: &mut kiosk::Kiosk, cap: &kiosk::KioskOwnerCap)
Implementation
public fun remove<Ext: drop>(
    self: &mut Kiosk, cap: &KioskOwnerCap
) {
    assert!(self.has_access(cap), ENotOwner);
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);

    let Extension {
        storage,
        permissions: _,
        is_enabled: _,
    } = df::remove(self.uid_mut_as_owner(cap), ExtensionKey<Ext> {});

    storage.destroy_empty();
}

Function storage

Get immutable access to the extension storage. Can only be performed by the extension as long as the extension is installed.

public fun storage<Ext: drop>(_ext: Ext, self: &kiosk::Kiosk): &bag::Bag
Implementation
public fun storage<Ext: drop>(
    _ext: Ext, self: &Kiosk
): &Bag {
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);
    &extension<Ext>(self).storage
}

Function storage_mut

Get mutable access to the extension storage. Can only be performed by the extension as long as the extension is installed. Disabling the extension does not prevent it from accessing the storage.

Potentially dangerous: extension developer can keep data in a Bag therefore never really allowing the KioskOwner to remove the extension. However, it is the case with any other solution (1) and this way we prevent intentional extension freeze when the owner wants to ruin a trade (2) - eg locking extension while an auction is in progress.

Extensions should be crafted carefully, and the KioskOwner should be aware of the risks.

public fun storage_mut<Ext: drop>(_ext: Ext, self: &mut kiosk::Kiosk): &mut bag::Bag
Implementation
public fun storage_mut<Ext: drop>(
    _ext: Ext, self: &mut Kiosk
): &mut Bag {
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);
    &mut extension_mut<Ext>(self).storage
}

Function place

Protected action: place an item into the Kiosk. Can be performed by an authorized extension. The extension must have the place permission or a lock permission.

To prevent non-tradable items from being placed into Kiosk the method requires a TransferPolicy for the placed type to exist.

public fun place<Ext: drop, T: store, key>(_ext: Ext, self: &mut kiosk::Kiosk, item: T, _policy: &transfer_policy::TransferPolicy<T>)
Implementation
public fun place<Ext: drop, T: key + store>(
    _ext: Ext, self: &mut Kiosk, item: T, _policy: &TransferPolicy<T>
) {
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);
    assert!(can_place<Ext>(self) || can_lock<Ext>(self), EExtensionNotAllowed);

    self.place_internal(item)
}

Function lock

Protected action: lock an item in the Kiosk. Can be performed by an authorized extension. The extension must have the lock permission.

public fun lock<Ext: drop, T: store, key>(_ext: Ext, self: &mut kiosk::Kiosk, item: T, _policy: &transfer_policy::TransferPolicy<T>)
Implementation
public fun lock<Ext: drop, T: key + store>(
    _ext: Ext, self: &mut Kiosk, item: T, _policy: &TransferPolicy<T>
) {
    assert!(is_installed<Ext>(self), EExtensionNotInstalled);
    assert!(can_lock<Ext>(self), EExtensionNotAllowed);

    self.lock_internal(item)
}

Function is_installed

Check whether an extension of type Ext is installed.

public fun is_installed<Ext: drop>(self: &kiosk::Kiosk): bool
Implementation
public fun is_installed<Ext: drop>(self: &Kiosk): bool {
    df::exists_(self.uid(), ExtensionKey<Ext> {})
}

Function is_enabled

Check whether an extension of type Ext is enabled.

public fun is_enabled<Ext: drop>(self: &kiosk::Kiosk): bool
Implementation
public fun is_enabled<Ext: drop>(self: &Kiosk): bool {
    extension<Ext>(self).is_enabled
}

Function can_place

Check whether an extension of type Ext can place into Kiosk.

public fun can_place<Ext: drop>(self: &kiosk::Kiosk): bool
Implementation
public fun can_place<Ext: drop>(self: &Kiosk): bool {
    is_enabled<Ext>(self) && extension<Ext>(self).permissions & PLACE != 0
}

Function can_lock

Check whether an extension of type Ext can lock items in Kiosk. Locking also enables place.

public fun can_lock<Ext: drop>(self: &kiosk::Kiosk): bool
Implementation
public fun can_lock<Ext: drop>(self: &Kiosk): bool {
    is_enabled<Ext>(self) && extension<Ext>(self).permissions & LOCK != 0
}

Function extension

Internal: get a read-only access to the Extension.

fun extension<Ext: drop>(self: &kiosk::Kiosk): &kiosk_extension::Extension
Implementation
fun extension<Ext: drop>(self: &Kiosk): &Extension {
    df::borrow(self.uid(), ExtensionKey<Ext> {})
}

Function extension_mut

Internal: get a mutable access to the Extension.

fun extension_mut<Ext: drop>(self: &mut kiosk::Kiosk): &mut kiosk_extension::Extension
Implementation
fun extension_mut<Ext: drop>(self: &mut Kiosk): &mut Extension {
    df::borrow_mut(self.uid_mut_internal(), ExtensionKey<Ext> {})
}