Back to Sui

Ts Asset Tokenization

docs/content/references/ts-asset-tokenization.mdx

latest21.1 KB
Original Source

npm run call commands (create-tp, mint, lock, list, purchase, join, burn, get-balance, get-supply, get-total-supply).

Move packages

As with all smart contracts on Sui, Move provides the logic that powers asset tokenization.

asset_tokenization package

:::info

This reference implementation uses the Kiosk standard to ensure that tokenized assets operate within their defined policy. Use the implementation as presented to have marketable tokenized assets that support rules like royalties, commissions, and so on.

If Kiosk is not a requirement, then you can exclude the unlock module and some of the proxy's methods related to transfer policies.

:::

Select a module to view its details:

<Tabs groupId="modules"> <TabItem label="tokenized_asset" value="tokenized_asset">

The tokenized_asset module operates in a manner similar to the coin library.

When it receives a new One-Time Witness type, it creates a unique representation of a fractional asset. This module employs similar implementations to some methods found in the Coin module. It encompasses functionalities pertinent to asset tokenization, including new asset creation, minting, splitting, joining, and burning. See One Time Witness in The Move Book for more information.

Structs

  • AssetCap

    Generate an AssetCap for each new asset represented as a fractional NFT. In most scenarios, create it as an owned object, which you can then transfer to the platform's administrator for access-restricted method invocation.

    rust
    struct AssetCap<phantom T> {
        id: UID,
        // the current supply in circulation
        supply: Supply<T>,
        // the total max supply allowed to exist at any time
        total_supply: u64,
        // Determines if the asset can be burned or not
        burnable: bool
    }
    
  • AssetMetadata

    The AssetMetadata struct defines the metadata representing the entire asset to fractionalize. This should be a shared object.

    rust
    struct AssetMetadata<phantom T> has key, store {
            id: UID,
            /// Name of the asset
            name: String,
            // the total max supply allowed to exist at any time
            total_supply: u64,
            /// Symbol for the asset
            symbol: ascii::String,
            /// Description of the asset
            description: String,
            /// URL for the asset logo
            icon_url: Option<Url>
        }
    
  • TokenizedAsset

    The TokenizedAsset is minted with a specified balance that is less than or equal to the remaining supply. If the VecMap of an asset is populated with values, indicating multiple unique entries, it is considered an NFT. Conversely, if the VecMap of an asset is not populated, indicating an absence of individual entries, it is considered an FT.

    rust
    struct TokenizedAsset<phantom T> has key, store {
            id: UID,
            /// The balance of the tokenized asset
            balance: Balance<T>,
            /// If the VecMap is populated, it is considered an NFT, else the asset is considered an FT.
            metadata: VecMap<String, String>,
            /// URL for the asset image (optional)
            image_url: Option<Url>,
        }
    
  • PlatformCap

    The PlatformCap refers to the capability issued to the individual who deploys the contract. This capability grants specific permissions or authority related to the platform's functionalities, allowing the deployer certain controlled actions or access rights within the deployed contract.

    rust
    /// Capability that is issued to the one deploying the contract
    struct PlatformCap has key, store { id: UID }
    

Functions

  • init

    This function creates a PlatformCap and transfers it to the transaction sender.

    rust
    fun init(ctx: &mut TxContext) {}
    
  • new_asset

    This function holds the responsibility of creating a fresh representation of an asset, defining its crucial attributes. Upon execution, it returns 2 distinct objects: the AssetCap and AssetMetadata. These objects encapsulate the necessary information and characteristics defining the asset within the system.

    rust
    public fun new_asset<T: drop>(
            witness: T,
            total_supply: u64,
            symbol: ascii::String,
            name: String,
            description: String,
            icon_url: Option<Url>,
            burnable: bool,
            ctx: &mut TxContext
        ): (AssetCap<T>, AssetMetadata<T>) {}
    
  • mint

    The function performs the minting of a tokenized asset. If new metadata is introduced during this process, the tokenized asset becomes unique, resulting in the creation of an NFT with a balance set to 1. Alternatively, if no new metadata is added, the system classifies the tokenized asset as an FT, permitting its balance to surpass 1, as specified by a provided argument. Upon execution, the function returns the tokenized asset object.

    rust
    public fun mint<T>(
            cap: &mut AssetCap<T>,
            keys: vector<String>,
            values: vector<String>,
            value: u64,
            ctx: &mut TxContext
        ): TokenizedAsset<T> {}
    
  • split

    This function receives a tokenized asset of the FT type and a balance greater than 1, along with a value less than the object's balance, and performs a split operation on the tokenized asset. The operation divides the existing tokenized asset into 2 separate tokenized assets. The newly created tokenized asset has a balance equal to the given value, while the balance of the provided object is reduced by the specified value. Upon completion, the function returns the newly created tokenized asset. This function does not accept or operate on tokenized assets of the NFT type.

    rust
    public fun split<T>(
            self: &mut TokenizedAsset<T>,
            split_amount: u64,
            ctx: &mut TxContext
        ): TokenizedAsset<T> {}
    
  • join

    This function receives 2 tokenized assets of the FT type and executes a merge operation on the tokenized assets. The operation involves increasing the balance of the first tokenized asset by the balance of the second one. Subsequently, the system burns or removes the second tokenized asset from circulation. After the process concludes, the function returns the ID of the burned tokenized asset.

    This function does not accept or operate on tokenized assets of the NFT type.

    rust
    public fun join<T>(
            self: &mut TokenizedAsset<T>,
            other: TokenizedAsset<T>
        ): ID {}
    
  • burn

    This function requires the assetCap as a parameter, restricting its invocation solely to the platform admin. Additionally, it accepts a tokenized asset that is burned as part of its operation. Upon burning the provided tokenized asset, the circulating supply decreases by the balance of the burnt item. It requires a tokenized asset that is burnable.

    rust
    public fun burn<T>(
            cap: &mut AssetCap<T>,
            tokenized_asset: TokenizedAsset<T>
        )
    
  • total_supply

    This function returns the maximum supply of the asset.

    rust
    public fun total_supply<T>(cap: &AssetCap<T>): u64 {}
    
  • supply

    This function returns the current circulating supply of the asset.

    rust
    public fun supply<T>(cap: &AssetCap<T>): u64 {}
    
  • value

    This function returns the balance of a tokenized asset.

    rust
    public fun value<T>(tokenized_asset: &TokenizedAsset<T>): u64 {}
    
  • create_vec_map_from_arrays

    This internal helper function populates a VecMap<String, String>. It assists in the process of filling or setting key-value pairs within the VecMap data structure.

    rust
    fun create_vec_map_from_arrays(
            keys: vector<String>,
            values: vector<String>
        ): VecMap<String, String> {}
    
</TabItem> <TabItem label="proxy" value="proxy">

The proxy module comprises methods that the type owner utilizes to execute publisher-related operations.

Structs

  • Proxy

    The PROXY struct represents the One-Time Witness (OTW) to claim the publisher.

    rust
    struct PROXY has drop {}
    
  • Registry

    This shared object serves as a repository for the Publisher object, specifically intended to control and restrict access to the creation and management of transfer policies for tokenized assets. Mutable access to this object is exclusively granted to the actual publisher.

    rust
    struct Registry has key {
            id: UID,
            publisher: Publisher
        }
    
  • ProtectedTP

    This is a shared object that stores an empty transfer policy. It is required to create one per type <T> generated by a user. Its involvement is apparent in the unlock module.

    rust
    struct ProtectedTP<phantom T> has key, store {
            id: UID,
            policy_cap: TransferPolicyCap<T>,
            transfer_policy: TransferPolicy<T>
        }
    

Functions

  • init

    This function is responsible for creating the Publisher object, encapsulating it within the registry, and subsequently sharing the Registry object.

    rust
    fun init(otw: PROXY, ctx: &mut TxContext) {}
    
  • setup_tp

    This function leverages the publisher nested within the registry and the sender's publisher. It generates and returns a transfer policy and the associated transfer policy cap specific to the TokenizedAsset<T>. This type 'T' is derived from the Publisher object.

    It also generates an empty transfer policy wrapped in a ProtectedTP<T> object, which is shared. You can use this functionality under specific conditions to override the Kiosk lock rule.

    rust
    public fun setup_tp<T: drop>(
            registry: &Registry,
            publisher: &Publisher,
            ctx: &mut TxContext
        ): (TransferPolicy<TokenizedAsset<T>>,
            TransferPolicyCap<TokenizedAsset<T>>) {}
    
  • new_display

    This function utilizes the publisher nested within the registry and the sender's publisher to generate and return an empty Display for the type TokenizedAsset<T>, where T is encapsulated within the Publisher object.

    rust
    public fun new_display<T: drop>(
            registry: &Registry,
            publisher: &Publisher,
            ctx: &mut TxContext
        ): Display<TokenizedAsset<T>> {}
    
  • transfer_policy

    This function, provided with the protectedTP, returns the transfer policy specifically designed for the type TokenizedAsset<T>

    rust
    public(friend) fun transfer_policy<T>(
            protected_tp: &ProtectedTP<T>
        ): &TransferPolicy<T> {}
    
    
  • publisher_mut

    This function can only be accessed by the owner of the platform cap. It requires the registry as an argument to obtain a mutable reference to the publisher.

    rust
    public fun publisher_mut(
        _: &PlatformCap,
        registry: &mut Registry
    ): &mut Publisher {}
    
</TabItem> <TabItem label="unlock" value="unlock">

The unlock module facilitates the unlocking of a tokenized asset specifically for authorized burning and joining.

It allows tokenized asset type creators to enable these operations for kiosk assets without necessitating adherence to the default set of requirements, such as rules or policies.

Structs

  • JoinPromise

    A promise object is established to prevent attempts of permanently unlocking an object beyond the intended scope of joining.

    rust
    struct JoinPromise {
        /// the item where the balance of the burnt tokenized asset will be added.
        item: ID,
        /// burned is the id of the tokenized asset that will be burned
        burned: ID,
        /// the expected final balance of the item after merging
        expected_balance: u64
    }
    
  • BurnPromise

    A promise object created to ensure the permanent burning of a specified object.

    rust
    struct BurnPromise {
      expected_supply: u64
    }
    

Functions

  • asset_from_kiosk_to_join

    This helper function is intended to facilitate the joining of tokenized assets locked in a kiosk. It aids in unlocking the tokenized asset that is set for burning and ensures that another tokenized asset of the same type will eventually contain its balance by returning a JoinPromise.

    rust
    public fun asset_from_kiosk_to_join<T>(
      self: &TokenizedAsset<T>, // A
      to_burn: &TokenizedAsset<T>, // B
      protected_tp: &ProtectedTP<TokenizedAsset<T>>, // unlocker
      transfer_request: TransferRequest<TokenizedAsset<T>> // transfer request for b
    ): JoinPromise {}
    
  • prove_join

    A function utilized to demonstrate that the unlocked tokenized asset is successfully burned and its balance is incorporated into an existing tokenized asset.

    rust
    public fun prove_join<T>(
      self: &TokenizedAsset<T>,
      promise: JoinPromise,
      proof: ID) {
    }
    
  • asset_from_kiosk_to_burn

    Helper function that facilitates the burning of tokenized assets locked in a kiosk. It assists in their unlocking while ensuring a promise that the circulating supply will be reduced, achieved by returning a BurnPromise.

    rust
    public fun asset_from_kiosk_to_burn<T>(
        to_burn: &TokenizedAsset<T>,
        asset_cap: &AssetCap<T>,
        protected_tp: &ProtectedTP<TokenizedAsset<T>>,
        transfer_request: TransferRequest<TokenizedAsset<T>>,
      ): BurnPromise {
    }
    
  • prove_burn

    Ensures that the circulating supply of the asset cap is reduced by the balance of the burned tokenized asset.

    rust
    public fun prove_burn<T>(
      asset_cap: &AssetCap<T>,
      promise: BurnPromise) {
    }
    
</TabItem> </Tabs>

template package

An example use case package that enables utilization of Rust WASM functionality to support seamless asset creation on the browser.

This is similar to the launchpad approach and serves as the template package whenever a new asset requires representation as a tokenized asset.

Effectively allowing users to edit fields of this template contract on the fly and publish it with the edits included. This package implements two essential modules, each catering to distinct functionalities required for asset tokenization. More details regarding how Rust WASM was implemented can be found in the Web Assembly section.

  • Modules

    • template

      This is the module that supports defining a new asset.

      When you need to represent a new asset as a fractional asset, modify this module to <template>::<TEMPLATE>, with the <template> (in capitals) being the OTW of this new asset.

      This module calls the asset_tokenization::tokenized_asset::new_asset(...) method, which facilitates the declaration of new fields for the asset:

      • witness: The OTW NEW_ASSET
      • total_supply: The total supply allowed to exist at any time
      • symbol: The symbol for the asset
      • name: The name of the asset
      • description: The description of the asset
      • icon_url: The URL for the asset logo (optional)
      • burnable: Boolean that defines if the asset can be burned by an admin
    • genesis

      A genesis type of module that includes a OTW so that the sender can claim the publisher.

TypeScript

Now, you can begin interacting with the deployed smart contract and your tokenized asset.

In a terminal or console within the project's setup directory, utilize the following commands:

  • create-tp

    First, create a TransferPolicy and a ProtectedTP with the following command:

    sh
    $ npm run call create-tp
    

    After executing the command, the console displays the effects of the transaction.

    By searching the transaction digest on a Sui network explorer, you can locate the created objects. Subsequently, select and save the TransferPolicy ID and the ProtectedTP ID from these objects into the respective fields within your .env file.

  • Add rules

    In the project's file transferPolicyRules.ts located in the directory setup/src/functions, you can modify the code to include the desired rules for your transfer policy.

    Code snippet to be modified:

    rust
    // A demonstration of using all the available rule add/remove functions.
        // You can chain these commands.
        tpTx
            .addFloorPriceRule('1000')
            .addLockRule()
            .addRoyaltyRule(percentageToBasisPoints(10), 0)
            // .addPersonalKioskRule()
            // .removeFloorPriceRule()
            // .removeLockRule()
            // .removeRoyaltyRule()
            // .removePersonalKioskRule()
    

    By running the command npm run call tp-rules, the rules are added to your transfer policy.

    Now, investors can trade the fractions of your asset according to the rules you've set.

  • select-kiosk

    You must place the tokenized assets within a kiosk if marketable assets are desired. Subsequently, you can list and sell them to other users. Lock the objects in the kiosk to prevent any future unauthorized usage outside the defined policy that you set.

    Best practices recommend a single, comprehensive kiosk for all operations. However, this might not always be the case. Therefore, this project requires the use of only 1 personal kiosk to ensure consistency and better management, even if you own multiple kiosks.

    To enforce this rule, execute the command npm run call select-kiosk. This provides you with the specific kiosk ID to use for this project.

    Then, store the provided Kiosk ID in the appropriate field within your .env file.

  • mint

    In the project's file mint.ts, found in the directory setup/src/functions, you can edit the code to mint the desired type (NFT/FT) and balance for your asset.

    As previously mentioned, if additional metadata is provided, the tokenized asset is treated as an NFT with a value of 1. However, if there is no extra metadata, the tokenized asset is regarded as an FT, and you have the flexibility to select its balance, which can exceed 1.

    Here is an example from the code that needs modification:

    rust
    // example without metadata -> FT
    function getVecMapValues() {
    
      const keys : string[] = [];
      const values : string[] = [];
    
      return { keys, values };
    }
    

    or

    rust
    // example with metadata -> NFT
    function getVecMapValues() {
    	const keys = [
    	  "Piece",
    	  "Is it Amazing?",
    	  "In a scale from 1 to 10, how good?",
      ];
      const values = ["8/100", "Yes", "11"];
    
      return { keys, values };
    }
    

    Upon executing the command npm run call mint, a new tokenized asset is minted. You can save the object's ID in the .env file for future reference.

  • lock

    Locking the objects within the kiosk is crucial to prevent any unauthorized usage beyond the established policy.

    Upon executing the command npm run call lock, your newly minted tokenized asset is secured within your kiosk.

    Before running the command, make sure that the field TOKENIZED_ASSET within your .env file is populated with the object you intend to lock.

  • mint-lock

    Executing the command npm run call mint-lock performs both the mint and lock functions sequentially, ensuring the minted asset is created and immediately locked within the kiosk.

  • list

    Now that your tokenized asset is placed and locked within your kiosk, you can proceed to list it for sale.

    In the project's file listItem.ts, found in the directory setup/src/functions, you can adjust the code to specify the desired asset for listing.

    Code snippet to be modified:

    rust
    const SALE_PRICE = '100000';
      kioskTx
        .list({
            itemId,
            itemType,
            price: SALE_PRICE,
        })
        .finalize();
    

    By running the command npm run call list, your tokenized asset is listed and made available for sale.

  • purchase

    When a user intends to purchase an item, it needs to be listed for sale. After the user selects the item to buy, they are required to modify the following snippet of code found in the file purchaseItem.ts, located in the setup/src/functions directory.

    rust
    const item = {
        itemType: tokenizedAssetType,
        itemId: tokenized_asset ?? tokenizedAssetID,
        price: "100000",
        sellerKiosk: targetKioskId,
    };
    

    Apart from specifying the item and its type, the buyer must set the specific price and the seller's kiosk ID to execute the purchase transaction successfully, accomplished by running npm run call purchase.

  • join

    When you execute the command npm run call join, 2 specified tokenized assets of the FT type are merged together. Before running the command, make sure that the fields FT1 and FT2 within your .env file are populated with the objects you intend to merge.

  • burn

    When you intend to burn a tokenized asset, execute the command npm run call burn. Following this action, the specified asset is destroyed. Before running the command, make sure that the field TOKENIZED_ASSET within your .env file is populated with the object you intend to burn.

  • get-balance

    By executing the command npm run call get-balance, you can retrieve the balance value associated with the specified tokenized asset.

  • get-supply

    By executing the command npm run call get-supply, you can retrieve the value representing the current circulating supply of the asset.

  • get-total-supply

    By executing the command npm run call get-total-supply, you can retrieve the value representing the current circulating supply of the asset.