第一个 NFT

本教程通过以下的分步步骤描述如何在 Aptos 区块链上创建和转移 NFT。核心 NFT 或代币的 Aptos 实例可以在 token.move 中找到。

第 1 步:选择一个 SDK

第 2 步:运行示例

每个 SDK 都提供一个示例目录。本教程涵盖了该 simple-nft 示例。

克隆 aptos-core

git clone git@github.com:aptos-labs/aptos-core.git ~/aptos-core
  • Typescript

    导航至 Typescript SDK 示例目录:

    cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript

    安装必要的依赖项:

    yarn install

    运行 simple-nft 示例:

    yarn run simple_nft
  • Python

    导航至 Python SDK 示例目录:

    cd ~/aptos-core/ecosystem/python/sdk

    安装必要的依赖项:

    curl -sSL https://install.python-poetry.org | python3
    poetry update

    运行 simple-nft 示例:

    poetry run python -m examples.simple-nft
  • Rust

    待发布

第 3 步:理解输出

执行 simple-nft 示例后,应出现以下输出,但某些值会有所不同:

=== Addresses ===
Alice: 0x9df0f527f3a0b445e4d5c320cfa269cdefafc7cd1ed17ffce4b3fd485b17aafb
Bob: 0xfcc74af84dde26b0050dce35d6b3d11c60f5c8c58728ca3a0b11035942a0b1de

=== Initial Coin Balances ===
Alice: 20000
Bob: 20000

=== Creating Collection and Token ===
Alice's collection: {
    "description": "Alice's simple collection",
    "maximum": "18446744073709551615",
    "mutability_config": {
        "description": false,
        "maximum": false,
        "uri": false
    },
    "name": "Alice's",
    "supply": "1",
    "uri": "https://aptos.dev"
}
Alice's token balance: 1
Alice's token data: {
    "default_properties": {
        "map": {
            "data": []
        }
    },
    "description": "Alice's simple token",
    "largest_property_version": "0",
    "maximum": "1",
    "mutability_config": {
        "description": false,
        "maximum": false,
        "properties": false,
        "royalty": false,
        "uri": false
    },
    "name": "Alice's first token",
    "royalty": {
        "payee_address": "0x9df0f527f3a0b445e4d5c320cfa269cdefafc7cd1ed17ffce4b3fd485b17aafb",
        "royalty_points_denominator": "1000000",
        "royalty_points_numerator": "0"
    },
    "supply": "1",
    "uri": "https://aptos.dev/img/nyan.jpeg"
}

=== Transferring the token to Bob ===
Alice's token balance: 0
Bob's token balance: 1

=== Transferring the token back to Alice using MultiAgent ===
Alice's token balance: 1
Bob's token balance: 0

该示例演示了:

  • 初始化 REST 和 Faucet 客户端

  • 创建 Alice 和 Bob 两个帐户

  • 为 Alice 和 Bob 的帐户提供资金

  • 使用 Alice 的帐户创建一个集合和一个代币

  • Alice 提供代币,Bob 领取代币

  • Bob 通过一个多代理交易单方面地将代币发送给 Alice

第 4 步:深入了解 SDK

  • Typescript

    💡 见完整示例

    查看 [simple_nft] 完整代码,按照以下步骤操作

  • Python

    💡 见完整示例

    查看 [simple_nft] 完整代码,按照以下步骤操作

  • Rust

    待发布

第 4.1 步:初始化客户端

在第 1 步中,示例初始化 API 和 Faucet 客户端。

  • API 客户端与 REST API 交互,并且

  • Faucet 客户端与 devnet Faucet 服务交互以创建和注资帐户

  • Typescript

    const client = new AptosClient(NODE_URL);
    const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);

    使用 API 客户端,我们可以创建一个 TokenClient,用于常见的代币操作,例如创建、转移和领取集合和代币等。

    const tokenClient = new TokenClient(client);

    common.ts 将 URL 值初始化为这样:

    export const NODE_URL = process.env.APTOS_NODE_URL || "https://fullnode.devnet.aptoslabs.com";
    export const FAUCET_URL = process.env.APTOS_FAUCET_URL || "https://faucet.devnet.aptoslabs.com";
  • Python

    rest_client = RestClient(NODE_URL)
    faucet_client = FaucetClient(FAUCET_URL, rest_client)

    common.py 将这些值初始化为这样:

    NODE_URL = os.getenv("APTOS_NODE_URL", "https://fullnode.devnet.aptoslabs.com/v1")
    FAUCET_URL = os.getenv(
        "APTOS_FAUCET_URL", "https://faucet.devnet.aptoslabs.com"
    )

    💡 提示

    默认情况下,两个服务等 URL 都指向 Aptos devnet 服务。然而,它们可以通过以下环境变量进行配置。

  • Rust

    待发布

第 4.2 步:创建本地账户

下一步是在本地创建两个帐户。帐户代表链上和链下的状态。链下状态包括一个地址和用户验证所有权的公钥、私钥对。这一步演示了如何生成链下状态。

  • Typescript

    const alice = new AptosAccount();
    const bob = new AptosAccount();
  • Python

    alice = Account.generate()
    bob = Account.generate()
  • Rust

    待发布

第 4.3 步:创建链上账户

在 Aptos 中,每个帐户都必须有一个链上表示,以支持接收代币和货币,以及在其他 dApp 中进行互动。一个帐户代表了一个存储资产的媒介,因此必须明确创建。这个示例利用 Faucet 来创建 Alice 和 Bob 的帐户。只有 Alice 帐户里有资金:

  • Typescript

    await faucetClient.fundAccount(alice.address(), 20_000);
    await faucetClient.fundAccount(bob.address(), 20_000);
  • Python

    faucet_client.fund_account(alice.address(), 20_000)
    faucet_client.fund_account(bob.address(), 20_000)
  • Rust

    待发布

第 4.4 步:创建一个集合

现在开始创建代币的过程。首先,创建者必须创建一个集合来存储代币。一个集合可以包含零个、一个或多个不同的代币。集合只是一个容器,并不限制代币的属性:

  • Typescript

    应用程序将调用 creatColletion

    const txnHash1 = await tokenClient.createCollection(
      alice,
      collectionName,
      "Alice's simple collection",
      "https://alice.com",
    );

    createCollection的函数签名。它返回一个交易哈希值:

    async createCollection(
      account: AptosAccount,
      name: string,
      description: string,
      uri: string,
      maxAmount: BCS.AnyNumber = MAX_U64_BIG_INT,
    ): Promise<string> {
  • Python

    应用程序将调用 create_colletion

    txn_hash = rest_client.create_collection(
        alice, collection_name, "Alice's simple collection", "https://aptos.dev"
    )

    create_colletion的函数签名。它返回一个交易哈希值:

    def create_collection(
        self, account: Account, name: str, description: str, uri: str
    ) -> str:
  • Rust

    待发布

第 4.5 步:创建一个代币

要创建一个代币,创建者必须指定一个相关的集合。一个代币必须与一个集合相关联,并且该集合必须有可以被铸造的剩余代币。有许多与代币相关的属性,但辅助 API 只公开了创建静态内容所需的最小数量。

  • Typescript

    应用程序将调用 creatToken

    const txnHash2 = await tokenClient.createToken(
      alice,
      collectionName,
      tokenName,
      "Alice's simple token",
      1,
      "https://aptos.dev/img/nyan.jpeg",
    );

    createToken的函数签名。它返回一个交易哈希值:

    async createToken(
      account: AptosAccount,
      collectionName: string,
      name: string,
      description: string,
      supply: number,
      uri: string,
      max: BCS.AnyNumber = MAX_U64_BIG_INT,
      royalty_payee_address: MaybeHexString = account.address(),
      royalty_points_denominator: number = 0,
      royalty_points_numerator: number = 0,
      property_keys: Array<string> = [],
      property_values: Array<string> = [],
      property_types: Array<string> = [],
    ): Promise<string> {
  • Python

    应用程序将调用 create_token

    txn_hash = rest_client.create_token(
        alice,
        collection_name,
        token_name,
        "Alice's simple token",
        1,
        "https://aptos.dev/img/nyan.jpeg",
        0,
    )

    create_token的函数签名。它返回一个交易哈希值:

    def create_token(
        self,
        account: Account,
        collection_name: str,
        name: str,
        description: str,
        supply: int,
        uri: str,
        royalty_points_per_million: int,
    ) -> str:
  • Rust

    待发布

第 4.6 步:读取代币和集合的元数据

集合和代币元数据都存储在创建者帐户的 Collection 表中。SDK 为查询这些特定的表格提供了便利的封装器:

  • Typescript

    读取集合的元数据:

    const collectionData = await tokenClient.getCollectionData(alice.address(), collectionName);
    console.log(`Alice's collection: ${JSON.stringify(collectionData, null, 4)}`);

    读取代币的元数据:

    const tokenData = await tokenClient.getTokenData(alice.address(), collectionName, tokenName);
    console.log(`Alice's token data: ${JSON.stringify(tokenData, null, 4)}`);

    以下是 getTokenData 如何查询代币元数据的:

    async getTokenData(
      creator: MaybeHexString,
      collectionName: string,
      tokenName: string,
    ): Promise<TokenTypes.TokenData> {
      const creatorHex = creator instanceof HexString ? creator.hex() : creator;
      const collection: { type: Gen.MoveStructTag; data: any } = await this.aptosClient.getAccountResource(
        creatorHex,
        "0x3::token::Collections",
      );
      const { handle } = collection.data.token_data;
      const tokenDataId = {
        creator: creatorHex,
        collection: collectionName,
        name: tokenName,
      };
    
      const getTokenTableItemRequest: Gen.TableItemRequest = {
        key_type: "0x3::token::TokenDataId",
        value_type: "0x3::token::TokenData",
        key: tokenDataId,
      };
    
      // We know the response will be a struct containing TokenData, hence the
      // implicit cast.
      return this.aptosClient.getTableItem(handle, getTokenTableItemRequest);
    }
  • Python

    读取集合的元数据:

    collection_data = rest_client.get_collection(alice.address(), collection_name)
    print(
        f"Alice's collection: {json.dumps(collection_data, indent=4, sort_keys=True)}"
    )

    读取代币的元数据:

    token_data = rest_client.get_token_data(
        alice.address(), collection_name, token_name, property_version
    )
    print(
        f"Alice's token data: {json.dumps(token_data, indent=4, sort_keys=True)}"
    )

    以下是 get_token_data如何查询代币元数据的:

    def get_token_data(
        self,
        creator: AccountAddress,
        collection_name: str,
        token_name: str,
        property_version: int,
    ) -> Any:
        token_data_handle = self.account_resource(creator, "0x3::token::Collections")[
            "data"
        ]["token_data"]["handle"]
    
        token_data_id = {
            "creator": creator.hex(),
            "collection": collection_name,
            "name": token_name,
        }
    
        return self.get_table_item(
            token_data_handle,
            "0x3::token::TokenDataId",
            "0x3::token::TokenData",
            token_data_id,
        )
  • Rust

    待发布

第 4.7 步:读取代币余额

Aptos 内的每一个代币都是一个独立的资产,用户拥有的资产都存储在他们的 TokenStore 内。为读取余额:

  • Typescript

    const aliceBalance1 = await tokenClient.getToken(
      alice.address(),
      collectionName,
      tokenName,
      `${tokenPropertyVersion}`,
    );
    console.log(`Alice's token balance: ${aliceBalance1["amount"]}`);
  • Python

    balance = rest_client.get_token_balance(
        alice.address(), alice.address(), collection_name, token_name, property_version
    )
    print(f"Alice's token balance: {balance}")
  • Rust

    待发布

第 4.8 步:提供和领取代币

许多用户因收到不需要的代币,而可能造成尴尬甚至是严重后果。Aptos 赋予每个帐户所有者权力,决定是否接受单边转账。默认情况下,是不支持单边转账的。所以 Aptos 提供了一个提供和领取代币的框架。

提供代币:

  • Typescript

    const txnHash3 = await tokenClient.offerToken(
      alice,
      bob.address(),
      alice.address(),
      collectionName,
      tokenName,
      1,
      tokenPropertyVersion,
    );
  • Python

    txn_hash = rest_client.offer_token(
        alice,
        bob.address(),
        alice.address(),
        collection_name,
        token_name,
        property_version,
        1,
    )
  • Rust

    待发布

领取代币:

  • Typescript

    const txnHash4 = await tokenClient.claimToken(
      bob,
      alice.address(),
      alice.address(),
      collectionName,
      tokenName,
      tokenPropertyVersion,
    );
  • Python

    txn_hash = rest_client.claim_token(
        bob,
        alice.address(),
        alice.address(),
        collection_name,
        token_name,
        property_version,
    )
  • Rust

    待发布

第 4.9 步:代币的安全单边转账

为支持代币安全单边转账,发送方可以首先要求接收方确认链下的待定转移。这里以多代理交易请求的形式出现。多代理交易包含多个签名,每个链上帐户都有一个签名。然后,Move 可以利用这一点给所有签名的人以签名者级别的权限。对于代币转移,这样确保了接收方确实希望收到这个代币,而不需要使用上述的代币转移框架。

  • Typescript

    let txnHash5 = await tokenClient.directTransferToken(
      bob,
      alice,
      alice.address(),
      collectionName,
      tokenName,
      1,
      tokenPropertyVersion,
    );
  • Python

    txn_hash = rest_client.direct_transfer_token(
        bob, alice, alice.address(), collection_name, token_name, 0, 1
    )
  • Rust

    待发布

第 4.10 步:实现代币单边转账

即将上线!

Last updated