您的第一个 NFT

注意:以下教程正在进行中。 此外,Aptos(非同质化)代币规范尚未正式确定。

Aptos 中的代币和 NFT

NFT 是一种不可替代的代币或存储在区块链上的数据,它唯一地定义了资产的所有权。 NFT 最初是在 EIP-721 中定义的,后来在 EIP-1155 中进行了扩展。 NFT 通常包括以下几个方面:

  • 名称,资产的名称,在集合中必须是唯一的

  • 描述,资产的描述

  • URL,一个指向关于资产的更多信息的链外非描述指针可以是媒体,如图像或视频或更多元数据

  • 供应量,这个 NFT 的单位总数,许多 NFT 只有一个,而那些有多个的被称为版本

此外,大多数 NFT 是具有共同属性(例如主题、创建者或最小合约)的集合或一组 NFT 的一部分。每个集合都有一组相似的属性:

  • 名称,集合的名称,在创建者的帐户中必须是唯一的

  • 描述,资产的描述

  • URL,一个指向关于资产的更多信息的链外非描述指针可以是媒体,如图像或视频或更多元数据

核心 NFT 或代币的 Aptos 实现可以在 Token.move 中找到。

Aptos Token 定义

Token

Aptos Token 被定义为:

类型描述

id

GUID:ID

代币的全局唯一标识符也可用于识别创建者

name

ASCII::String

代币的名称,在集合中必须是唯一的

collection

GUID:ID

包含此代币的集合的全局唯一标识符

balance

u64

与供应相关的此代币的当前存储量, 1 <= balance <= supply

Aptos TokenData 被定义为:

类型描述

id

GUID:ID

代币的全局唯一标识符也可用于识别创建者

description

ASCII::String

代币描述信息

name

ASCII::String

代币的名称,在集合中必须是唯一的

supply

u64

此 Token 的总版本数

uri

ASCII::String

附加信息/媒体的 URL

metadata

TokenType

一个通用的、可选的用户定义结构,用于包含有关此代币链上的附加信息

Token 是使用 Move 属性 store 定义的,这意味着它们可以保存到全局存储中。Token 不能被隐式丢弃,必须被销毁以确保总余额等于供应。Token 不能被复制。也就是说,由于缺少复制操作,除了创建者之外,任何人都无法更改总余额或供应量。请注意,当前的 API 没有公开发布创建代币的能力。Token 可以通过其 id 或 TokenType、集合名称和 Token 名称的元组来唯一标识。

TokenData 具有 copy 属性,支持简单的代币余额拆分。每当余额大于 1 的个人向另一个人提供其余额的一部分少于其总余额时,就会发生代币拆分。交易代币的用户请注意,两个代币可以共享相同的 TokenData,因为 Aptos 标准不会尝试识别代币是否复制了另一个代币的属性。重复前面所说的,代币可以通过其 id 或 TokenType、集合名称和代币名称来唯一标识。创建者可以更改 TokenType、集合名称或代币名称集中的任何值,以创建相似但不完全相同的代币。

Token Collections

Aptos 定义了一组按其唯一 ID 分组在一起的集合:

struct Collections<TokenType: copy + drop + store> has key {
    collections: Table<ASCII::String, Collection>,
}

struct TokenMetadata<TokenType: store> has key {
    metadata: Table<ID, TokenType>,
}

由于 Collections 具有属性键,因此它直接存储到创建者帐户。 需要注意的是,如果没有 Collections 的概念,而 Collection 具有 key 属性,则 Aptos 帐户只能有一个集合,而通常情况并非如此。 可以按名称在集合集中查找集合,因此强制使用唯一的集合名称。

Token 和 TokenData 结构在其内容中是固定的。 资源 TokenMetadata 使创建者能够存储额外的 Token 数据。 表中的数据存储为 Token 的唯一 ID。 由于 script 函数不能支持结构或泛型的限制,使用它是可选的并且需要定制化 API。

每个集合都有以下字段:

类型描述

tokens

Table<ASCII::String, TokenMetadata<TokenType>>

跟踪与此集合关联的所有Token

claimed_tokens

Table<ASCII::String, address>

跟踪 supply == 1 的 Token 的存储地址

description

ASCII::String

集合描述

name

ASCII::String

此集合的名称在指定 TokenType 的创建者帐户中必须是唯一的。

uri

ASCII::String

附加信息/媒体的 URL

count

u64

此集合跟踪的不同 Token 总数

maximum

Option<u64>

可在此集合中铸造的可选 Token 的最大数量

集合不是用于累积 Token 的存储,因此它不包含 Token,而是包含 TokenData:

类型描述

id

GUID:ID

此 Token 的全球唯一标识符也可用于识别创建者

data

TokenData

关于此代币的附加数据,这是代币的供应量 > 1

Token 存储

为了获取和存储 Token,用户必须拥有一个 TokenType 库:

struct Gallery has key {
    gallery: Table<ID, Token>,
}

与 Collections 一样,它作为资源存储在 Aptos 帐户上。

Token 介绍

作为我们核心框架的一部分,Aptos 提供了一个基本的 Token 接口,没有额外的数据,或者明确的 TokenMetadata 资源没有该令牌的条目。 这样做的动机包括:

  • 创建新 Token 需要编写 Move 代码

  • 用于创建新标记的脚本函数必须定制化,因为 Move 不支持模板类型或结构作为输入参数

  • 脚本函数上的模板类型为编写脚本函数增加了额外的难度

本教程将引导您完成:

  • 创建自己的 Token 集合

  • 我们最喜欢的猫的 Token

  • 并将该 Token 给其他人。

请从下面的代码仓库中 clone 最新代码!

本教程以您的第一个事务为基础,作为此示例的库。 以下教程包含可以在下面完整下载的示例代码:

对于本教程,将重点关注 first_nft.py 并重新使用上一教程中的 first_transaction.py 库。 你可以在这里找到 python 项目

创建一个集合

Aptos Token 使创作者能够创建有限或无限的收藏。 许多 NFT 具有有限的性质,创造者只打算永远创造一定数量,这会导致稀缺性。 而其他代币可能具有无限的性质,例如,用于实用程序的集合可能会随着时间的推移出现新的代币。 SimpleToken 集合可以通过使用适当的脚本函数以任一行为实例化:

有限,即不能超过最大数量的代币可以铸造:

public(script) fun create_finite_collection_script(
    account: signer,
    description: vector<u8>,
    name: vector<u8>,
    uri: vector<u8>,
    maximum: u64,
)

无限制,即可以添加到集合中的 Token 数量没有限制:

public(script) fun create_unlimited_collection_script(
    account: signer,
    description: vector<u8>,
    name: vector<u8>,
    uri: vector<u8>,
)

这些脚本函数可以通过 REST API 调用。 下面演示了如何调用 ,如下所示:

    def create_collection(self, account: Account, description: str, name: str, uri: str):
        """Creates a new collection within the specified account"""

        payload = {
            "type": "script_function_payload",
            "function": f"0x1::Token::create_unlimited_collection_script",
            "type_arguments": [],
            "arguments": [
                description.encode("utf-8").hex(),
                name.encode("utf-8").hex(),
                uri.encode("utf-8").hex(),
            ]
        }
        self.submit_transaction_helper(account, payload)

创建 Token

可以在集合创建后创建 Token。 为此,Token 必须指定与先前创建的集合名称相同的 collection_name。 创建 SimpleToken 的 Move 脚本函数是:

public(script) fun create_token_script(
    account: signer,
    collection_name: vector<u8>,
    description: vector<u8>,
    name: vector<u8>,
    supply: u64,
    uri: vector<u8>,
)

这些脚本函数可以通过 REST API 调用。 下面演示了如何调用 ,如下所示:

    def create_token(
            self,
            account: Account,
            collection_name: str,
            description: str,
            name: str,
            supply: int,
            uri: str,
    ):
        payload = {
            "type": "script_function_payload",
            "function": f"0x1::Token::create_token_script",
            "type_arguments": [],
            "arguments": [
                collection_name.encode("utf-8").hex(),    
                description.encode("utf-8").hex(),
                name.encode("utf-8").hex(),
                str(supply),
                uri.encode("utf-8").hex(),
            ]
        }
        self.submit_transaction_helper(account, payload)

发送 Token

在 Aptos 和 Move 中,每个代币都占据空间并拥有所有权。 因此,代币转移不是单方面的,需要类似于公告板的两个阶段过程。 发送者必须首先注册一个 Token 可供接收者声明,然后接收者必须声明这个 Token。 这已在名为 TokenTransfer 的概念验证 Move 模块中实现。 SimpleToken 提供了一些包装函数来支持转账到另一个帐户、声明该转账或停止该转账。

获得 Token ID

为了发送代币,发送者必须首先基于知道创建者的账户、集合名称和代币名称来识别代币id。 这可以通过查询 REST 获得:

    def get_token_id(self, creator: str, collection_name: str, token_name: str) -> int:
        """ Retrieve the token's creation_num, which is useful for non-creator operations """

        resources = self.account_resources(creator)
        collections = []
        tokens = []
        for resource in resources:
            if resource["type"] == f"0x1::Token::Collections":
                collections = resource["data"]["collections"]["data"]
        for collection in collections:
            if collection["key"] == collection_name:
                tokens = collection["value"]["tokens"]["data"]
        for token in tokens:
            if token["key"] == token_name:
                return int(token["value"]["id"]["creation_num"])
            
        assert False

提供 Token

Token 中的以下 Move 脚本功能支持将 Token 转移到另一个帐户,有效地注册另一个帐户可以获取该 Token:

public(script) fun offer_script(
    sender: signer,
    receiver: address,
    creator: address,
    token_creation_num: u64,
    amount: u64,
)

    def offer_token(
            self,
            account: Account,
            receiver: str,
            creator: str,
            token_creation_num: int,
            amount: int
    ):
        payload = {
            "type": "script_function_payload",
            "function": f"0x1::TokenTransfers::offer_script",
            "type_arguments": [],
            "arguments": [
                receiver,
                creator,
                str(token_creation_num),
                str(amount),
            ]
        }
        self.submit_transaction_helper(account, payload)

领取 Token

SimpleToken 中的以下 Move 脚本函数支持接收上一个函数提供的 Token,有效地声明 Token:

public(script) fun claim_script(
    sender: signer,
    receiver: address,
    creator: address,
    token_creation_num: u64,
    amount: u64,
)

    def claim_token(
            self,
            account: Account,
            sender: str,
            creator: str,
            token_creation_num: int,
    ):
        payload = {
            "type": "script_function_payload",
            "function": f"0x1::TokenTransfers::claim_script",
            "type_arguments": [],
            "arguments": [
                sender,
                creator,
                str(token_creation_num),
            ]
        }
        self.submit_transaction_helper(account, payload)

待办

  • 添加额外的铸造能力

  • 确保在铸造时至少产生一个代币

  • 添加事件——需要关于什么事件的反馈

  • 为 Token 提供可变 API

  • 直接为泛型和简单 Token 写一个冒烟测试

  • 以安全的方式启用燃烧

Last updated