[RFC] CoTA: A Compact Token Aggregator Standard for Extremely Low Cost NFTs and FTs

Related posts:
RFC: Compact UDT Lock
Random idea: compact sUDT / xUDT cell
RFC: Multi-purpose NFT Draft Spec
Compact Extension for m-NFT Protocol: The Key to Mass Adoption

Background

m-NFT protocol has been used by the community for several months, and thousands of users enjoy its products. Although it’s very user friendly and easy to use, the minting cost problem is increasingly apparent alone with the CKB price fluctuation. One has to create at least two cells to to start sending NFTs, and they need another more cell for every distribution. It will be an obvious barrier for the huge number of user adoption in the near future.

The compact extension for m-NFT proposed a primary solution for this problem, which uses an SMT to store the NFT instances. But it still require an issuer cell and an nft class cell to mint. Here we post an upgraded standard to eliminate the cost problem once and for all. There will be no state storage cost for token creating, minting, and transferring by adopting this standard.

Basic Ideas

Similar with m-NFT compact extension, here we introduce a CoTA /kəʊta/ cell to manage the token states SMT, including the holding, withdrawal, and claimed ones. And we use a global registry cell to enforce the uniqueness of users’ CoTA cell. And we also introduce the two-step-transfer method to avoid the cell collision problem during token transferring between different users.

Global CoTA registry cell

The main purpose of CoTA registry cell is to make sure every lockscript maintains only one cell.

# global_cota_registry_cell data structure
data:
    version: byte
    registry_smt_root: bytes32
type:
    code_hash: registry_type
    args: type_id
lock:
    always_success lock

CoTA Cell

cota_cell is used to store user’s token states, including those the user holds, sent, and claimed. To keep the cell unique and untransferable, its typescript.args must match its lockscript.

# cota cell data structure
data:
    version: byte     # must be 0
    smt_root: byte32
type:
    code_hash: cota_type
    args: lockscript_hash[0..20]   # must match self.lockscript
lock:
    user-defined

Note: the CoTA cell is indestructible to make sure the token cannot be claimed twice.

Ensure the token uniqueness

Every UTXO in Nervos chain is unique globally. So we could use it (outpoint hash) as the identity for created token without a shared state manager.

K-V structure for CoTA SMT

The cota.smt_root is the most important data field in the design. Data stored in the SMT is managed by key-value pair. There are several kinds of data stored and compressed in this hash root. We use a two-bytes variable smt_root.key.smt_type to identify different key types.

NFT related SMT k-v pair definition

# **cota-NFT-define** data structure
key:
	smt_type:  uint16		# type bytes for cota-NFT-define big endian
	cota_id:   Byte[20]		# first 20bytes of [tx's 1st input] outpoint hash
	reserved:  Byte[10]    
value:
	total:     uint32		# 0 for unlimited
	issued:    uint32
	configure: Byte[1]
	reserved:  Byte[23]

# cota-NFT-hold data structure
key:
	smt_type:  uint16			# type byte for cota-NFT-hold
	cota_id:   Byte[20]
	index_id:  uint32
	reserved:  Byte[6]      
value:
	configure: Byte[1]
	state:     Byte[1]
	characteristic: Byte[20]	# user-defined data
	reserved:  Byte[10]

# cota-NFT-withdraw data structure
key:  
	smt_type:  uint16       # type byte for cota-NFT-withdraw
	cota_id:   Byte[20]
	index_id:  uint32
	reserved:  Byte[6]
value:
  blake2b_hash of {
	configure: Byte[1]
	state:     Byte[1]
	characteristic: Byte[20]      # user-defined data
	to:        lock_hash[0..20]
	out_point: OutPoint[12..36]   # Outpoint of previous input cell with SMT
  }

# cota-NFT-claim data structure
key: 
  blake2b_hash of {
	smt_type:  uint16        # type byte for cota-NFT-claim
	cota_id:   Byte[20]
	index_id:  uint32    
	out_point: OutPoint[12..36] # Outpoint field recorded in the proof
  }
value:
	0x00...00 for nonclaimed
	0xFF...FF for claimed

FT related SMT k-v pair definition

# **cota-FT-define** data structure
key:
	smt_type:  uint16		# type bytes for cota-FT-define big endian
	cota_id:   Byte[20]		# first 20bytes of [tx's 1st input] outpoint hash
	reserved:  Byte[10]    
value:
  blake2b_hash of {
	total:     uint128		# 0 for unlimited
	issued:    uint128
	decimal:   uint8
  }

# cota-FT-hold data structure
key:
	smt_type:  uint16			# type byte for cota-FT-hold
	cota_id:   Byte[20]
	reserved:  Byte[10]      
value:
	amount: uint128
	reserved:  Byte[16]

# cota-FT-withdraw data structure
key:  
	smt_type:  uint16       # type byte for cota-FT-withdraw
	cota_id:   Byte[20]
	reserved:  Byte[10]
value:
  blake2b_hash of {
	amount: uint128
	to:        lock_hash[0..20]
	out_point: OutPoint[12..36]   # Outpoint of previous input cell with SMT
  }

# cota-FT-claimed data structure
key: 
  blake2b_hash of {
	smt_type:  uint16        # type byte for cota-NFT-claimed
	cota_id:   Byte[20]
	out_point: OutPoint[12..36] # Outpoint field recorded in the proof
  }
value:
	0x00...00 for nonclaimed
	0xFF...FF for claimed

smt_type table

name 1st byte 2nd byte note
cota-NFT-define 0x81 (NFT) 0x00 NFT definition
cota-NFT-held 0x81 (NFT) 0x01 NFT held record
cota-NFT-withdrawal 0x81 (NFT) 0x02 NFT withdrawal record
cota-NFT-claim 0x81 (NFT) 0x03 NFT claim record
cota-FT-define 0x82 (FT) 0x00 FT definition
cota-FT-held 0x82 (FT) 0x01 FT held record
cota-FT-withdrawal 0x82 (FT) 0x02 FT withdrawal record
cota-FT-claim 0x82 (FT) 0x03 FT claim record

Metadata definition

The k-v data in smt_root has stored the necessary dynamic data for token usage, but we still need more static data to facilitate the application level access. These static information includes the token name, issuer data, token URI, and so on. Here we use the CKB Transaction Metadata Standard Proposal to store these static metadata.

Here demos the usage of metadata in CoTA related transactions.

# User information update
input:
	cota_cell
output:
	cota_cell
witness:
	[0]: signature
	[1]: CTMeta - user information

### cota-NFT/FT creation
input:
	cota_cell:
		nft_smt_root:
			smt_root_old
output:
	cota_cell:
		nft_smt_root:
			smt_root_new:
				key: 
					cotaid: <outpoint_hash of input.cota_cell>
				value:
					...
witness:
	[0]: signature
	[1]: CTMeta - token information

Transaction types

tx type tx.inputs tx.outputs affected data
CoTA cell register capacity cell, registry cell cota cell, registry cell registry.smt
personal data update cota cell cota cell tx.witness.metadata
create new NFT cota cell cota cell cota.smt.nft_define, witness.metadata
distribute NFT cota cell cota cell cota.smt.nft_define, cota.smt.nft_withdraw
NFT claim cota cell cota cell cota.smt.nft_claim, cota.smt.nft_hold
NFT withdraw cota cell cota cell cota.smt.nft_hold, cota.smt.nft_withdraw
6 Likes