RFC: Multi-purpose NFT Draft Spec

Overview

This m-NFT proposal covers the following functionalities.

  • Publicity of the issuer information for authentication purpose
  • Class definition for multiple NFT items with the same attribute
  • Limited number definition for pre-defined issuance count
  • Set definition for a suit of collectibles across different classes
  • Claim action supported for ticket like scenarios
  • Exchangablility, permenent lock, and self destructibility enable bits configuration
  • Inscript by holders
  • Extensible information definition

The basic m-NFT issurance logic is to create an Issuer Cell first, which defines the issuer’s basic information. And then to create a NFT Class Cell with it, which consistes of the specific information for a class of NFT items. With the NFT Class Cell, one could issue NFT Cell to the users.

Issuer Cell

Data structure

Data:
	version: uint8
	class_count: uint32
	set_count: uint32
	info_size: uint16
	info: infomation in json format
Type:
	code_hash: ISSUER_TYPE
	args: type_id
Lock:
	--

version field is set to 0 for now, which indicates the data format version of this cell.

class_count field records the created class count for the issuer, which should be set to zero at creation, and auto increase when create new NFT Class Cells.

set_count is almost the same as class_count, only it is used for set creation count.

info field is assumed to record the issuer’s public information, such as SNS account, images, etc. And the info_size field should be set to the byte length of info.

The typescript of Issuer Cell is set to ISSUER_TYPE with args equals to type_id, where type_id follows the same rules of Type ID script.

Here we define the IssuerID = issuer_cell_typescript_hash[:20] for the future usage.

To make the info field more compatible across different front-end services, here we give some suggested varible keys:

  • name: issuer’s name in UTF-8 encoding
  • website: issuer’s website URL
  • email: email address
  • authentication: typically an SNS announcement about the issuance
  • image: image URL for issuer

NFT Class Cell

Every NFT Class Cell stands for a group of specific NFT itmes with the same properties. And different NFT Cells issued by the same NFT Class Cell will be marked with different TokenID as its identity.

Data Structure

Data:
	// must
	version: uint8
	total: uint32
	issued: uint32
	<<property: uint64>> // replaced by nft_cell.characteristic
	configure: uint8
	name: <size:uint16> + <vartext>
	description: <size:uint16> + <vartext>
	renderer: <size:uint16> + <vartext>
	// optional data array
	extinfo_data: <size:uint16> + <vartext>
	extinfo_data: <size:uint16> + <vartext>
	...
Type:
	code_hash: NFT_CLASS_TYPE
	args:  <IssuerID:byte20> | <class_id:uint32>
Lock:
	--

Where typescript.args equals to IssuerID join class_id, and the class_id is an auto-increment variable according to the class_count variable in the Issuer Cell.

total means the issurance number limitation, zero for unlimited.

issued means the item number that already issued for this class, it’s also used for the NFT Cell TokenID variable, equals to zero when initally created.

property is a user defined variable to set up the NFT characteristics, we could consider it as the DNA of the items. (It’s removed, and replaced by characteristic field in the NFT Cell with the same functionality.)

configure is a bitmap variable to constrain the behavior of the NFT items issued by this Class. The bitmap meaning table follows (the bit 0 or 1 means specific functionality enabled or disable).

bit index meaning notes
bit 0 claimable
bit 1 lockability permanent locked permit
bit 2 inscription NFT holder could append inscript to the cell
bit 3 reserved
bit 4 exchangable before claim
bit 5 exchangable after claim
bit 6 destructible before claim
bit 7 destructible after claim

name, description, and renderer are static informational fields for the tokens. But the front-end dApps could pass some specific data to the renderer URL, such as property to get runtime generated rich media data.

The RFC also allows user defined extinfo_data array to provide more information.

Creation Transaction

The Issuer Cell must be present at the input side of the transaction, and the NFT Class Cell could be batch created.

INPUT:
Issuer_cell:
	Data:
		class_count: k
	Type:
		code_hash: ISSUER_TYPE
		args: type_id
OUTPUT:
	Issuer_cell:
		Data:
			class_count: k + m
		Type:
			code_hash: ISSUER_TYPE
			args: type_id
	NFT_class_cell:
		Data:
			total: N_1
			issued: 0
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | <class_id = k>
	...
	NFT_class_cell:
		Data:
			total: N_m
			issued: 0
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | <class_id = k+m-1>

NFT Cell

NFT Cell is issued by NFT Class Cell, with the same configure and different TokenID and state field.

Data Structure

Data:
	// must
	version: uint8
	characteristic: byte[8]
	configure: uint8
	state: uint8 
	// optional
	extinfo_data: <size:uint16> + <vartext>
	...
Type:
	code_hash: NFT_TYPE
	args: IssuerID | class_id | TokenID
Lock:
	--

configure must be the same as nft_class_cell.configure. And state is different for different NFT Cell, which is used for indication of current NFT state.

bit index meaning note
bit 0 claim state 0/1 => unclaimed / claimed
bit 1 lock state 0/1 => free / locked
other reserved

typescript.args.IssuerID | class_id must be the same as that of nft_class_cell, and the TokenID field must be the issued index of nft_class_cell.issued field.

Issuance Transaction

The typescript allows batch issuance of the NFT Cells.

INPUT:
	NFT_class_cell:
		Data:
			total: N
			issued: < k >
			property: P
			configure: C
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | class_id
OUTPUT:
	NFT_class_cell:
		Data:
			total: N
			issued: < k + m <= N >
			property: P
			configure: C
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | class_id
	NFT_cell:
		Data:
			configure: C
			state: 0 
		Type:
			code_hash: NFT_TYPE
			args: IssuerID | class_id | <TokenID = k>
	...
	NFT_cell:
		Data:
			configure: C
			state: 0 
			...
		Type:
			code_hash: NFT_TYPE
			args: IssuerID | class_id | <TokenID = k+m-1>

NFT Set Cell

The issuer could create NFT Set Cell to define a suit of collectible of NFT Classes.

Data Structure

Data:
	// must
	version: uint8
	set_id: uint32
	set_name: <size:uint16> + <vartext>
	set_description: <size:uint16 + <vartext>
	item_list_size: uint16
	item_list: <class_id:uint32> * item_list_size
	// optional
	extinfo_data: <size:uint16> + <vartext>
	...
Type:
	code_hash: NFT_SET_TYPE
	args: IssuerID
11 Likes

Why is property a uint64 instead of bytes? Will byte type be more appropriate for “DNA”? Is 64 bits enough?

I’m also wondering in what scenarios should set be used? Could you give some examples? Thanks!

It could be bytes[8], the detailed explanation is maintained by the dApp operator. One famous use case of this property scenario is cryptokitties DNA. And other examples include weapon attack power variation, product batch number, and so on. It’s assumed to pass to the renderer url to show different result according to the value.

【翻译】RFC : 最简 NFT 草案规范

概述

本 S-NFT 协议具备了以下功能

  • 公开发行人用来作为认证的信息
  • 属性相同的多个 NFT 的类型设置
  • 可设置 NFT 的发行数量上限
  • 支持套装,不同的 NFT 可以构成一个套装,方便用户收集
  • 支持兑换功能,适用于票务等场景
  • 支持兑换后继续流转,适用于票根流转等场景
  • 支持转让配置,发行方可精细设置转让权限
  • 支持可交换性、永久锁定和自毁功能等配置
  • 支持拥有者(收藏家)进行数字落款
  • 支持定义扩展信息

基础的 S-NFT 发行逻辑首先是创建一个Issuer cell,该 Cell 定义了发行方的基本信息。然后用它来创建一个 NFT Class Cell,它包含了某一种 NFT 的特定信息。有了 NFT Class Cell,任何人可发行 NFT Cell 并给到用户们。

Issuer Cell

数据结构

Data:
	version: uint8
	class_count: uint32
	set_count: uint32
	info_size: uint16
	info: infomation in json format
Type:
	code_hash: ISSUER_TYPE
	args: type_id
Lock:
	--

version 字段当前的设置为 0,这表示了这个 cell 的数据结构的版本号

class_count字段记录着发行者创建的类别数,该数在创建时应设置为0,并在创建新的 NFT Class Cell时自动增加。

set_count 几乎和 class_count 相等,但他只代表该用户创建的套装数量

info 字段是设定来记录发行者的公开信息,例如 SNS 账户,图片等等。info_size 字段则设置来表示 info 的字节长度

Issue Cell 的type script设置为 ISSUER_TYPE,其 args 等于遵循 TYPE ID 脚本规则的 type id(注:保证了该 Cell 的独一无二性)。

这里我们为了未来的使用定义了
IssuerID = issuer_cell_typescript_hash[:20]

为了让 info 字段能够在前端服务之间更有兼容性,我们给定了几个建议的变数:

  • name: 发行人名称(以 UTF-8 编码)
  • website: 网址URL
  • email: 邮箱
  • authentication: 典型的关于发行人 SNS 认证声明
  • image: 图片 url

NFT Class Cell

每个 NFT Class Cell 代表了一群特定且相同属性的 NFT 物件。被同样的 NFT Class Cell 发出的不同种的 NFT Cells 将会以不同的 TokenID 标注,以表明他的身份。

数据结构

Data:
	// must
	version: uint8
	total: uint32
	issued: uint32
	property: uint64
	configure: uint8
	name: <size:uint16> + <vartext>
	description: <size:uint16> + <vartext>
	renderer: <size:uint16> + <vartext>
	// optional data array
	extinfo_data: <size:uint16> + <vartext>
	extinfo_data: <size:uint16> + <vartext>
	...
Type:
	code_hash: NFT_CLASS_TYPE
	args:  <IssuerID:byte20> | <class_id:uint32>
Lock:
	--

typescript.arg 等于 IssuerID 加上 class_id ,而且 class_id是一个在 Issuer Cell 中根据 class_count 变数自动递增的变数。

total 代表发行数量限制,可以从零到无限。

issued 代表这个类别的物件已经发行了多少,这也能够用在 NFT Cell TokenID的变数上,当初始被创建时他们都等于零。

property是一个用户定义的变数,用来设置 NFT 的特性,我们可以将它视作是 NFT 物件的 DNA。

configure 是个点阵图(bitmap)的变数,用来限制被 Class 发行 NFT 物件的行为。点阵图的含义如下表(bit 0 及 bit 1 分别代表特定的功能已经启用或者故障 )

bit index meaning notes
bit 0 claimable(可兑换)
bit 1 lockability(可锁定性) 永久锁定许可
bit 2 inscription(题字) NFT 持有人可在该 Cell 中题字
bit 3 reserved
bit 4 exchangable before claim
bit 5 exchangable after claim
bit 6 destructible before claim
bit 7 destructible after claim

name, description,和 renderer 对于代币而言是静态的信息字段。但前端的 dApps 可以通过一个特殊数据通往 renderer 的URL.例如property获得运行时生成的丰富的媒体数据。

本 RFC 也允许用户自定义的 extinfo_data 数组来提供更多的信息。

Creation Transaction 创建交易

Issuer Cell必须被置于交易的 input 端,然后 NFT Class Cell 可以被批量生产。

INPUT:
Issuer_cell:
	Data:
		class_count: k
	Type:
		code_hash: ISSUER_TYPE
		args: type_id
OUTPUT:
	Issuer_cell:
		Data:
			class_count: k + m
		Type:
			code_hash: ISSUER_TYPE
			args: type_id
	NFT_class_cell:
		Data:
			total: N_1
			issued: 0
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | <class_id = k>
	...
	NFT_class_cell:
		Data:
			total: N_m
			issued: 0
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | <class_id = k+m-1>

NFT Cell

NFT Cell 是 NFT Class Cell 所发行,有着相同的 configure和不同的 TokenID 以及 state字段。

Data Structure 数据结构

Data:
	// must
	version: uint8
	configure: uint8
	state: uint8 
	// optional
	extinfo_data: <size:uint16> + <vartext>
	...
Type:
	code_hash: NFT_TYPE
	args: IssuerID | class_id | TokenID
Lock:
	--

configure必须要和nft_class_cell.configure相同。而不同 NFT Cell 的 state 就是不同的,这用于指示当前 NFT 的状态。

bit index meaning note
bit 0 claim state 0/1 => unclaimed / claimed
bit 1 lock state 0/1 => free / locked
other reserved

typescript.args.IssuerID | class_id必须要和nft_class_cell相同, 然后 TokenID 必须是nft_class_cell字段的发行索引。

Issuance Transaction 发行交易

typescript 允许 NFT Cells 的批量发行

INPUT:
	NFT_class_cell:
		Data:
			total: N
			issued: < k >
			property: P
			configure: C
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | class_id
OUTPUT:
	NFT_class_cell:
		Data:
			total: N
			issued: < k + m <= N >
			property: P
			configure: C
		Type:
			code_hash: NFT_CLASS_TYPE
			args: IssuerID | class_id
	NFT_cell:
		Data:
			configure: C
			state: 0 
		Type:
			code_hash: NFT_TYPE
			args: IssuerID | class_id | <TokenID = k>
	...
	NFT_cell:
		Data:
			configure: C
			state: 0 
			...
		Type:
			code_hash: NFT_TYPE
			args: IssuerID | class_id | <TokenID = k+m-1>

NFT Set Cell

发行人可以创建 NFT Set Cell 来自定义一个 NFT 套装集合

数据结构

Data:
	// 必选
	version: uint8
	set_id: uint32
	set_name: <size:uint16> + <vartext>
	set_description: <size:uint16 + <vartext>
	item_list_size: uint16
	item_list: <class_id:uint32> * item_list_size
	// 可选
	extinfo_data: <size:uint16> + <vartext>
	...
Type:
	code_hash: NFT_SET_TYPE
	args: IssuerID
2 Likes

Honestly speaking, I recommend a different name here. I could understand there are many different feature requirements needed for the NFT spec, and I totally understand the needs here. But this is hardly a “simple” one. Removing “simple” from the name might be better.

Here I have a stupid question is that why you Nervos always prefer to emphasis “simplest “on the token standard.
Is it anything wrong to launch a concrete standard ?
So far have understood that in long term sudt could have more extension like xudt so that sUDT is the simplest one that any dApps devs hard to adopt directly.
But actually we meet a bull market,such applications scenarios are time-proven like NFT standard’s research from erc721 to erc1155.
And I believe that the advantage of Nervos is that we could have flexible access to general ppl with tools such as pw-core.
Why not write a standard ready to adopt instantly for NFT developer?
Not being simplest doesn’t matter if it could be as practical as possible.

You are absolutely correct, there is nothing wrong with a concrete standard, I’m not saying this standard is bad or anything, it could be just enough for many cases. I’m just saying it uses an improper name. If you name something as simple, it should be, well, simple, otherwise, it will be better to pick a different name.

1 Like

Name changed. Thanks for the suggestion.

Thanks for eliminating my confusion !
Tbh I am your big fan.
Btw has been any decentralized storage platform (IPFS) that the NFT on Nervos attempt to leverage for their data or info ? E.g. the graphic from artists .
As we know,there is no way to storage the picture or gif on Nervos chain due to unaffordable state occupation cost .

The implementation of the RFC: https://github.com/nervina-labs/ckb-nft-scripts