APTOS Developer’s Guide | Your First NFT | Typescript


*> THIS MANUAL IS INCOMPLETE.
*> This manual is incomplete. The Aptos (Non-Fungible) Token specification has not been formalized.

Your first NFT.

An NFT is a non-fungible token or data stored in a blockchain that uniquely identifies ownership of an asset. NFTs were first defined in EIP-721 and then expanded in EIP-1155. NFTs generally consist of the following aspects:

  • Name, the name of the asset, which must be unique within the collection
  • Description, a characteristic of the asset
  • URL, an undescribed off-line pointer to additional information about the asset, this could be media, such as an image or video, or additional metadata.
  • Offer, the total number of units of a given NFT, many NFTs have only one offer unit, and those with more than one are called collections.

In addition, most NFTs are part of a collection or set of NFTs with a common attribute, such as theme, creator, or minimum number of contracts. Each collection has a similar set of attributes:

  • Name, the name of the collection, which must be unique within the creator account.
  • Description, a characteristic of the asset.
  • URL, an undescribed off-line pointer to additional information about the asset, this could be media, such as an image or video, or additional metadata.

Aptos digital asset token standard

The Aptos token standard is being developed according to the following principles:

  • Provide a standard implementation to improve interoperability between ecosystem projects.
  • Achieve maximum liquidity by defining NFT, Fungible (non-decimal) and Semi-Fungible tokens in a single contract. These different types of tokens can easily be stored, transferred and committed in the same way.
  • Ability to customize token properties, users can define their own properties and store them on the network.
  • Reduce the cost of mining a large number of NFT tokens. We support lazy<###code> mines on the network using Semi-Fungible tokens

APTOS IMPLEMENTATION FOR CORE NFT
The Aptos implementation for basic NFTs or tokens can be found in Token.move.

Aptos token definitions

Token data model.

The data associated with a token is stored in both the creator account and the owner account.

The resource stored at the creator's address:

  • Collections - Contains a table collection_data<###code> that maps the collection name to CollectionData. It also stores all TokenData that this creator created.
  • CollectionData - Stores collection metadata. supply - The number of tokens created for the current collection. maxium the maximum number of tokens in this collection.
  • CollectionMutabilityConfig - Definition about collection modification
  • TokenData - The main structure for storing token metadata. Properties - This is where the user can add their own properties that are not defined in the token data. The user can create multiple tokens based on TokenData, and they use the same TokenData.
  • TokenMutabilityConfig - Controls which values can be executed.
  • TokenDataId - Identifier used to represent and query TokenData on the network. This identifier basically contains 3 values including creator address, collection name, and token name.
  • Royalty - Specify the denominator and numerator for the royalty calculation. This also specifies the address of the recipient's account for the royalty transfer.
  • PropertyValue - Contains both the property value and the property type.

The resource is stored at the address of the owner:

  • TokenStore - The main structure for storing the token belonging to this address. It maps TokenId to the actual token.
  • Token - amount - the number of tokens.
  • TokenId - TokenDataId indicates the metadata of that token. Property_version represents the token with the modified PropertyMap from default_properties in TokenData.

Token Guide.

This guide will walk you through the process:

  • Creating your own token collection.
  • Creating a token with an image of your favorite cat.
  • Giving this token to someone else.
  • Mint a token using lazy online through change.

This tutorial is based on your first transaction as the library for this example. The following tutorial contains the example code, which can be downloaded in full below:

In this tutorial, we will focus on the first_nft.ts file and reuse the first_transaction.ts library from the previous tutorial.

You can find the typescript project here

Creating a collection
The Aptos token allows creators to create collections. maximum is the total number of tokens that can be created for a given collection.

public(script) fun create_collection_script (
    creator: &signer,
    name: String,
    description: String,
    uri: String,
    maximum: u64,
    mutate_setting: vector<bool>,
)
Enter fullscreen mode Exit fullscreen mode

These script functions can be called via the REST API. See below:

function serializeVectorBool(vecBool: boolean[]) {
  const serializer = new BCS.Serializer();
  serializer.serializeU32AsUleb128(vecBool.length);
  vecBool.forEach((el) => {
    serializer.serializeBool(el);
  });
  return serializer.getBytes();
}

const NUMBER_MAX: number = 9007199254740991;
const client = new AptosClient(NODE_URL);
/** Creates a new collection within the specified account */
async function createCollection(account: AptosAccount, name: string, description: string, uri: string) {
  const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
    TxnBuilderTypes.EntryFunction.natural(
      "0x3::token",
      "create_collection_script",
      [],
      [
        BCS.bcsSerializeStr(name),
        BCS.bcsSerializeStr(description),
        BCS.bcsSerializeStr(uri),
        BCS.bcsSerializeUint64(NUMBER_MAX),
        serializeVectorBool([false, false, false]),
      ],
    ),
  );

  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(account.address()),
    client.getChainId(),
  ]);

  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(account.address()),
    BigInt(sequenceNumber),
    entryFunctionPayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );

  const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
  const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
  await client.waitForTransaction(pendingTxn.hash);
}
Enter fullscreen mode Exit fullscreen mode

Token Creation

Tokens can be created after creating collection. To do this, the token must contain the same collection as the name previously created collection. The Move script function looks like this:

public entry fun create_token_script(
    creator: &signer,
    collection: String,
    name: String,
    description: String,
    balance: u64,
    maximum: u64,
    uri: String,
    royalty_payee_address: address,
    royalty_points_denominator: u64,
    royalty_points_numerator: u64,
    token_mutate_setting: vector<bool>,
    property_keys: vector<String>,
    property_values: vector<vector<u8>>,
    property_types: vector<String>,
)
Enter fullscreen mode Exit fullscreen mode
  • The field balance is the initial amount that will be created for this token.
  • The royalty_payee_address field is the address to which royalty is paid.
  • The royalty_points_numerator / royalty_points_denominator is the percentage of the sale price (royalty) that should be paid to the recipient address. This can be the address of a single owner account or the address of a shared account belonging to a group of creators.
  • The token_mutate_setting property describes whether the TokenData field is modifiable.

These script functions can be called via the REST API. See below:

async function createToken(
  account: AptosAccount,
  collection_name: string,
  name: string,
  description: string,
  supply: number | bigint,
  uri: string,
) {
  // Serializes empty arrays
  const serializer = new BCS.Serializer();
  serializer.serializeU32AsUleb128(0);

  const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
    TxnBuilderTypes.EntryFunction.natural(
      "0x3::token",
      "create_token_script",
      [],
      [
        BCS.bcsSerializeStr(collection_name),
        BCS.bcsSerializeStr(name),
        BCS.bcsSerializeStr(description),
        BCS.bcsSerializeUint64(supply),
        BCS.bcsSerializeUint64(NUMBER_MAX),
        BCS.bcsSerializeStr(uri),
        BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(account.address())),
        BCS.bcsSerializeUint64(0),
        BCS.bcsSerializeUint64(0),
        serializeVectorBool([false, false, false, false, false]),
        serializer.getBytes(),
        serializer.getBytes(),
        serializer.getBytes(),
      ],
    ),
  );

  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(account.address()),
    client.getChainId(),
  ]);

  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(account.address()),
    BigInt(sequenceNumber),
    entryFunctionPayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );

  const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
  const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
  await client.waitForTransaction(pendingTxn.hash);
}
Enter fullscreen mode Exit fullscreen mode

Token Distribution

In Aptos and Move, each token takes up space and ownership. As such, token transfers are not one-way and require a two-step process similar to a bulletin board. First, the sender must register that the token is available to the recipient, and then the recipient must claim the token. This is implemented in a trial sample Move module called TokenTransfer.

SimpleToken provides several wrapped functions to support transferring to another account, approving that transfer, or terminating the transfer.

Obtaining a token ID

To transfer a token, the sender must first determine the token id by knowing the creator account, collection name, and token name. This can be obtained via a REST request:

async function tableItem(handle: string, keyType: string, valueType: string, key: any): Promise<any> {
  const getTokenTableItemRequest = {
    key_type: keyType,
    value_type: valueType,
    key,
  };
  return client.getTableItem(handle, getTokenTableItemRequest);
}

async function getTokenBalance(
  owner: HexString,
  creator: HexString,
  collection_name: string,
  token_name: string,
): Promise<number> {
  const token_store = await client.getAccountResource(owner, "0x3::token::TokenStore");

  const token_data_id = {
    creator: creator.hex(),
    collection: collection_name,
    name: token_name,
  };

  const token_id = {
    token_data_id,
    property_version: "0",
  };

  const token = await tableItem(
    (token_store.data as any)["tokens"]["handle"],
    "0x3::token::TokenId",
    "0x3::token::Token",
    token_id,
  );

  return token.amount;
}

async function getTokenData(creator: HexString, collection_name: string, token_name: string): Promise<any> {
  const collections = await client.getAccountResource(creator, "0x3::token::Collections");

  const token_data_id = {
    creator: creator.hex(),
    collection: collection_name,
    name: token_name,
  };

  const token = await tableItem(
    (collections.data as any)["token_data"]["handle"],
    "0x3::token::TokenDataId",
    "0x3::token::TokenData",
    token_data_id,
  );
  return token;
}
Enter fullscreen mode Exit fullscreen mode

Token Offer

The following Move script function in Token supports offering a token to another account, effectively registering that another account can claim the token:

public entry fun offer_script(
    sender: signer,
    receiver: address,
    creator: address,
    collection: String,
    name: String,
    property_version: u64,
    amount: u64,
)
Enter fullscreen mode Exit fullscreen mode
async function offerToken(
  account: AptosAccount,
  receiver: HexString,
  creator: HexString,
  collection_name: string,
  token_name: string,
  amount: number,
) {
  const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
    TxnBuilderTypes.EntryFunction.natural(
      "0x3::token_transfers",
      "offer_script",
      [],
      [
        BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(receiver.hex())),
        BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(creator.hex())),
        BCS.bcsSerializeStr(collection_name),
        BCS.bcsSerializeStr(token_name),
        BCS.bcsSerializeUint64(0),
        BCS.bcsSerializeUint64(amount),
      ],
    ),
  );

  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(account.address()),
    client.getChainId(),
  ]);

  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(account.address()),
    BigInt(sequenceNumber),
    entryFunctionPayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );

  const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
  const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
  await client.waitForTransaction(pendingTxn.hash);
}
Enter fullscreen mode Exit fullscreen mode

*Assigning rights to token

The following Move script function in SimpleToken supports getting the token provided by the previous function, effectively claiming rights to the token:

public entry fun claim_script(
    receiver: signer,
    sender: address,
    creator: address,
    collection: String,
    name: String,
    property_version: u64,
)
Enter fullscreen mode Exit fullscreen mode
async function claimToken(
  account: AptosAccount,
  sender: HexString,
  creator: HexString,
  collection_name: string,
  token_name: string,
) {
  const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
    TxnBuilderTypes.EntryFunction.natural(
      "0x3::token_transfers",
      "claim_script",
      [],
      [
        BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(sender.hex())),
        BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(creator.hex())),
        BCS.bcsSerializeStr(collection_name),
        BCS.bcsSerializeStr(token_name),
        BCS.bcsSerializeUint64(0),
      ],
    ),
  );

  const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
    client.getAccount(account.address()),
    client.getChainId(),
  ]);

  const rawTxn = new TxnBuilderTypes.RawTransaction(
    TxnBuilderTypes.AccountAddress.fromHex(account.address()),
    BigInt(sequenceNumber),
    entryFunctionPayload,
    1000n,
    1n,
    BigInt(Math.floor(Date.now() / 1000) + 10),
    new TxnBuilderTypes.ChainId(chainId),
  );

  const bcsTxn = AptosClient.generateBCSTransaction(account, rawTxn);
  const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
  await client.waitForTransaction(pendingTxn.hash);
}
Enter fullscreen mode Exit fullscreen mode

Mint Lazy online.

When Alice becomes a celebrity in her community, her feline NFTs are in high demand. However, Alice doesn't want to pay the cost of a 10 million NFT mint at the outset. She only wants to pay the cost when someone wants NFT. She can mint 10 million uninitialized interchangeable cat tokens in a single transaction.

When Jack wants to buy an NFT from Alice, she can change one interchangeable token.

    public entry fun mutate_token_properties(
        account: &signer,
        token_owner: address,
        creator: address,
        collection_name: String,
        token_name: String,
        token_property_version: u64,
        amount: u64,
        keys: vector<String>,
        values: vector<vector<u8>>,
        types: vector<String>,
    )
Enter fullscreen mode Exit fullscreen mode

This will create a new property_version<###code> and create a new TokenId for the previously uninitialized replaceable token (property_version = 0), which becomes the NFT. Alice can then pass the NFT to Jack. Alice will only need to pay the cost of creating the NFT from the replaceable token when someone wants to buy.

Оцените статью
devanswers.ru
Добавить комментарий