Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An alternative design of user defined fungible token on Nervos Network's Common Knowledge Base

Base Fungible UDT Proposal Draft

Introduction

This document puts forth a proposed standard for Base Fungible user defined tokens (UDT) on Nervos Network’s Common Knowledge Base (CKB).

Relationship Between Properties, Transactions, and Operations

Transactions are the interface in CKB by which users perform operations (produce state changes). Transactions are composed of cells and cells can define type scripts that enforce constraints on any transaction in which the cell is included. Therefore descriptions of CKB operations are descriptions of transaction patterns, which in turn describe specific input, output, and dependency cells, as well as the constraints the input & output cells introduce into a transaction (via their type & lock scripts). The operations and their corresponding transaction patterns are the first two aspects of the standard that this document defines. The third aspect that this draft defines is the UDT extension mechanism through Lock.

The base standard is simple, defining only the bare minimum constraints for a fungible token. This will allow greater flexibility and opportunities to innovate on token models while still reaping the benefits of standard-compliance, namely: interoperability and predictable behavior.

Expected Background & Preliminary Resources

To understand the material in this paper, readers should have an understanding of the programming model - “Cell” model - of Nervos Network’s CKB. More specifically, readers should have an understanding of the following concepts:

  1. Cell model & fundamental CKB data structures
  2. Type ID
  3. Dep types
  4. Hash types on scripts
  5. Transaction verification workflow
  6. Molecule serialization format

Design Concerns

The many design concerns can be classified into three types: usability concerns, cost concerns, and security concerns. The standard should be usable in so far that it should be pleasant and easy to develop standard-compliant tokens as well extensions. It should not be too difficult to identify the right cells to use to generate transactions (i.e., perform an operation) and should therefore also be easy to query, parse, and understand on-chain data by off-chain parties such as wallets and exchanges. It should be convenient for UDT holders/users to perform the operations supported by the base standard and any custom extensions. Corollary to this, since holders will probably use a wallet or other tool to interface with the token, it should be easy for interface tools to add support for new UDTs and support for new extensions to an already-supported UDT.

The cost of UDT comes in the form of cost of operations due to transaction size & compute cycles utilized by the scripts themselves, as well as from the amount of storage it takes to store the required on-chain metadata, UDT scripts, and UDT instances themselves, as described in the Architecture section below. These should be minimized.

Standard Proposal

Architecture

Components Overview

  • Definition Script
  • Info Cell Script
  • UDT Information Cell
  • UDT Instance

Definition Script

It’s a Type script to constrain the behavior of UDT Instance.

Info Cell Script

It’s a Type script to constrain the behavior of Info Cell.

UDT Instance

A UDT Instance is any cell that has a UDT Definition type and contains the amount description. So, holders or users of some UDT would be most often operating on UDT instances; they are the deliverable, so to speak, of UDT development.

data:
    amount: uint256
type:
    code_hash: definition_script_type_hash
    hash_type: type
    args: UUID
lock:
    <user_defined>

UDT Information Cell

The UDT information cell is important, as it acts as an management and infomation center for specific UDT.

data: 
    meta_info_dictionary : molecule bytes
type:
    code_hash: info_cell_script_type_hash
    hash_type: type
    args: UUID
lock:
    <operator_defined>

Meta_info_dictionary

The meta_info_dictionary in the info_cell includes optional fields for operators to publish necessary data to users. None of the data in this field is mandatory. Here we list some recommended descriptors for meta_info_dictionary.

name: string
symbol: char[4]
decimal: uint8
total_supply: uint256
description: string
image: string       // URI
icon_uri: string    // URI
icon_binary: bytes[]

Operations

Deploy UDT Definition Script & Info Cell Script

binaries:
    definition_script => UDT_definition_script_cell
    info_cell_script  => UDT_info_script_cell

Issue New UDT

To create a new fungible token:

// Issue new UDT
Deps:
    outpoint_of_UDT_definition_script
    outpoint_of_UDT_info_script
    outpoint_of_user_lock_script
Inputs:
    <...>
Outputs:
    Info_Cell:
        Data:
            [optional] meta_info_dictionary : molecule
        Type:
            code_hash: info_cell_script_type_hash
            hash_type: type
            args: Hash(input0_outpoint) as UUID
        Lock:
            <user_defined_governance_lock>
    UDT_Instance:
        Data:
            amount: uint256
        Type:
            code_hash: definition_script_type_hash
            hash_type: type
            args: UUID
        Lock:
            <user_defined_custodian_lock>
    [optional] <vec>UDT_Instance

Transfer

// Transfer
Deps:
    outpoint_of_UDT_definition_script
    outpoint_of_user_lock_script
Inputs:
    <vec> UDT_Instance
    <...>
Outputs:
    <vec> UDT_Instance
    <...>

Mint

// Mint
Deps:
    outpoint_of_UDT_definition_script
    outpoint_of_UDT_info_script
    outpoint_of_user_defined_governance_lock
Inputs:
   Info_Cell
   <...>
Outputs:
   Info_Cell:
        Data:
            <update_if_necessary>
        Type:
            <unchanged>
        Lock:
            <unchanged>
   UDT_Instance:
        Data:
            amount: uint256
        Type:
            code_hash: definition_script_type_hash
            hash_type: type
            args: UUID
        Lock:
            <user_defined_custodian_lock>
   <...>

Burn

// Burn
Deps:
    outpoint_of_UDT_definition_script
    outpoint_of_UDT_info_script
    outpoint_of_user_defined_governance_lock
Inputs:
   Info_Cell
   UDT_Instance:
        Data:
            amount: uint256
        Type:
            code_hash: definition_script_type_hash
            hash_type: type
            args: UUID
        Lock:
            <user_defined_custodian_lock>
   <...>
Outputs:
   Info_Cell:
        Data:
            <update_if_necessary>
        Type:
            <unchanged>
        Lock:
            <unchanged>
   UDT_Instance:
        Data:
            amount: uint256
        Type:
            code_hash: definition_script_type_hash
            hash_type: type
            args: UUID
        Lock:
            <user_defined_custodian_lock>
   <...>

Script Logic

Notice: all the logic mentioned below is only valid in the same type verification group (by default).

Logic considerations:

All the logic designed here should not conflict with other potential parallel logic existing in the same UTXO transaction. Potential parallel logic includes:

  • Different Type logic
  • The same UDT Type logic with a different UDT UUID

Thanks to the group mechanisms in CKB transaction verification, it is possible to meet these demands.

Info Cell Script Logic (Creation)

This logic is triggered when operators issue a new type of fungible UDT.

The output info_cell must follow this logic:

  • Trigger conditions:
    • info_cell ONLY appears on the output side of the TX
  • Data field MUST be empty or a molecule-formatted dictionary
  • Type field MUST follow these rules:
    • code_hash is equal to info_cell_script_type_hash
    • hash_type is equal to type
    • args is equal to blake2b(outpoint_of_input_0)[:20](UUID)
  • There are no restrictions on the Lock field.

Info Cell Script Logic (Governance)

This logic is triggered when token operators utilize the governance functions of the UDT: minting, burning, modifying information and so on.

The output info_cell must follow the following logic:

  • Trigger conditions:
    • info_cell MUST appear on both the input and output side of the TX
  • Data field MUST be empty or a molecule-formatted dictionary
  • Type field MUST be equal to the Type field of input info_cell
  • There are no restrictions on the Lock field.

Info Cell Script Logic (Stop Logic)

This logic is triggered when a transaction contains a malicious or forbidden action.

  • Trigger conditions:
    • info_cell ONLY appears on the input side of the transaction.
  • Result
    • return ERROR

UDT Definition Script (Transfer)

This logic is triggered when a token holder initiates a token transfer.

The output UDT_instance_cell must follow this logic:

  • Trigger conditions:
    • No info_cell with the same UUID appears in inputs or outputs (user cannot modify info_cell in this operation)
  • The sum of amount fields in UDT_instance_cell’s input MUST be greater than or equal to amount fields in output cells
    • the “input greater than output” operation could help users get rid of spam tokens and re-claim their occupied capacity

UDT Definition Script (Management)

This logic is triggered when an info_cell appears in outputs

  • Trigger conditions:
    • An info_cell with the same UUID appears in outputs (some operations would include info_cell in inputs as well)
  • No restrictions on the sum of UDT_instance_cell’s amount in output

Appendix: Lock Script as Extensions

Motivation

There are two kinds of cells defined in this proposal: info_cell and UDT_instance. Each type of cell has different lock functionalities and both are highly user definable.

Governance Lock

The governance lock is the lock on the info_cell. By operating the info_cell, governance operations are enabled for the UDT. Below we have listed several governance lock examples for info_cell.

No Governance

An empty lock on an info_cell would disable the privilege/responsibility of UDT governance. Without this function, there will never be additional issuance of the UDT and the UDT info will never be modified after being created.

Arbitrary Governance

Using a pure signature algorithm as the lock of info_cell allows the owner of the lock to do anything to the UDT, including mint, burn and modify the info_cell. This is useful when the operator is an asset gateway, such as Tether.

Complex Governance

This design allows for the implementation of complex logic to make decisions, such as holder voting and governing committees. Constraints to bound the possibilities of info_cell modification and mint/burn limitations can also be added.

For example, minting logic can be embedded in a complex governance lock script with the following rules:

  • An aggregated signature of 2/3 of committees is required to alter the UDT
  • New minted token amount should not exceed 10% of current supply
  • The total_supply field in the info_cell MUST also be updated

Usage Lock

The usage lock is the lock of the UDT_instance, there are no restrictions placed on this field, it is free to be defined by token holders. This usage lock could interact with other Type logic to implement functionality for various use cases.

Common Use

Users may use any simple lock to secure the cells that store their UDT balances, such as a secp256k1 lock. This lock would only verify cell ownership and a signature.

Sub Token

To create a sub-token, we can create a special lock with two args: the first is a sub_id and the second is the holder’s public_key.

A condition will be added to the UDT transfer function requiring that the output lock includes the sub_id provided in the first arg. A single UDT Instance is effectively divided into multiple sub tokens and this design can be useful when the same UDT is being used across different regions with different regulations.

Interactions with Other dApps

Other dApps such as a DEX may have their own specific token processing logic, which would exist as a superset of the Base Fungible UDT standard. In this model, we could leverage a Usage Lock to implement processing logic changes.

Take a DEX as an example. Firstly, token holders would deposit their UDTs to a cell using the DEX’s lock, and receive a new DEX token as receipt.

// Deposit to DEX
Inputs:
    UDT_Instance:
        Lock:
            <holder_lock>
Outputs:
    UDT_Instance:
        Lock:
            <DEX_DEPOSIT_LOCK>
    DEX_Receipt_Cell:
        Data:
            amount,
            UDT UUID,
            ...
        Type:
            <DEX_Type>
        Lock:
            <DEX_Lock with holder_lock as args>

After deposit, users could conduct exchanges under the DEX logic.

// Exchange
Inputs:
    DEX_Receipt_Cell
    <...>
Outputs:
    DEX_Receipt_Cell
    <...>

Finally, users could withdraw from DEX.

// Withdraw
Inputs:
    DEX_Receipt_Cell
        Lock:
            <holder_lock>
    UDT_Instance:
        Data:
            reserve_amount
        Lock:
            <DEX_DEPOSIT_LOCK>
Outputs:
    UDT_Instance:
        Data:
            withdraw_amount
        Lock:
            <holder_lock>
    UDT_Instance:
        Data:
            reserve_amount - withdraw_amount
        Lock:
            <DEX_DEPOSIT_LOCK>     

Contributors

Author: Cipher Wang

Editors: Matt Quinn, Tannr Allard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment