Руководство для разработчиков APTOS | Ваше первое NFT | Typescript

egormajj

EgorMajj

Posted on August 23, 2022

Руководство для разработчиков APTOS | Ваше первое NFT | Typescript

*ВНИМАНИЕ
*
> Данное руководство является незавершенным. Спецификация Aptos (Non-Fungible) Token не была формализована.

Ваше первое NFT

NFT - это невзаимозаменяемый токен или данные, хранящиеся в блокчейне, которые однозначно определяют право собственности на актив. Впервые NFT были определены в EIP-721, а затем расширены в EIP-1155. Как правило, NFT состоят из следующих аспектов:

  • Имя, название актива, которое должно быть уникальным в пределах коллекции
  • Описание, характеристика актива
  • URL, не описанный указатель вне сети на дополнительную информацию об активе, это может быть медиа, например, изображение или видео, или дополнительные метаданные.
  • Предложение, общее количество единиц данного NFT, многие NFT имеют только одну единицу предложения, а те, которые имеют более одной, называются коллекциями.

Кроме того, большинство NFT являются частью коллекции или набора NFT с общим атрибутом, например, темой, создателем или минимальным количеством контрактов. Каждая коллекция имеет аналогичный набор атрибутов:

  • Имя, название коллекции, которое должно быть уникальным в рамках учетной записи создателя.
  • Описание, характеристика актива
  • URL, не описанный указатель вне сети на дополнительную информацию об активе, это может быть медиа, например, изображение или видео, или дополнительные метаданные.

Стандарт токенов цифровых активов Aptos

Стандарт токенов Aptos разрабатывается в соответствии со следующими принципами:

  • Обеспечить стандартную реализацию для улучшения совместимости между проектами экосистемы.
  • Достижение максимальной ликвидности путем определения NFT, Fungible (недесятичных) и Semi-Fungible токенов в одном контракте. Эти различные типы токенов могут легко храниться, передаваться и совершаться одинаковым образом.
  • Возможность настройки свойств токенов, пользователи могут определять свои собственные свойства и хранить их в сети.
  • Снизить затраты на майнинг большого количества токенов NFT. Мы поддерживаем lazy минт на сети с помощью Semi-Fungible токенов

РЕАЛИЗАЦИЯ APTOS ДЛЯ ОСНОВНЫХ NFT
Реализацию Aptos для основных NFT или токенов можно найти в Token.move.

Определения токенов Aptos

Модель данных токена

Image description

Данные, связанные с токеном, хранятся как в учетной записи создателя, так и в учетной записи владельца.

Ресурс, хранящийся по адресу создателя:

  • Collections - Содержит таблицу collection_data, которая сопоставляет имя коллекции с CollectionData. В ней также хранятся все TokenData, которые создал данный создатель.
  • CollectionData - Хранит метаданные коллекции. supply - количество токенов, созданных для текущей коллекции. maxium максимальное количество токенов в данной коллекции.
  • CollectionMutabilityConfig - Определение об изменении коллекции
  • TokenData - Основная структура для хранения метаданных токена. Properties - это место, где пользователь может добавить свои собственные свойства, которые не определены в данных токена. Пользователь может создавать несколько токенов на основе данных TokenData, и они используют одни и те же данные TokenData.
  • TokenMutabilityConfig - Контролирует, какие значения могут быть выполнены.
  • TokenDataId - Идентификатор, используемый для представления и запроса TokenData в сети. Этот идентификатор в основном содержит 3 значения, включая адрес создателя, имя коллекции и имя токена.
  • Royalty - Укажите знаменатель и числитель для расчета роялти. Здесь также указывается адрес учетной записи получателя для перечисления роялти.
  • PropertyValue - Содержит как значение свойства, так и тип свойства.

Ресурс хранящиеся по адресу владельца:

  • TokenStore - Основная структура для хранения токена, принадлежащего данному адресу. Она сопоставляет TokenId с фактическим токеном.
  • Token - amount - количество токенов.
  • TokenId - TokenDataId указывает на метаданные этого токена. Property_version представляет токен с измененной PropertyMap из default_properties в TokenData.

Руководство по токенам

Это руководство проведет вас через весь процесс:

  • Создание собственной коллекции токенов.
  • Создание токена с изображением любимой кошки.
  • Дарим этот токен кому-то другому.
  • Минт токена с помощью lazy в сети через изменение.

Это руководство основано на вашей первой транзакции в качестве библиотеки для данного примера. Следующее руководство содержит код примера, который можно полностью загрузить ниже:

В этом руководстве мы сосредоточимся на файле first_nft.ts и повторно используем библиотеку first_transaction.ts из предыдущего руководства.

Вы можете найти проект typescript здесь

Создание коллекции
Токен Aptos позволяет создателям создавать коллекции. maximum - это общее количество токенов, которое может быть создано для данной коллекции.

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

Эти функции скрипта можно вызывать через REST API. См. ниже:

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

Создание токена

Токены могут быть созданы после создания collection. Для этого в токене должна быть указана та же коллекция, что и в name ранее созданной коллекции. Функция скрипта Move имеет следующий вид:

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
  • Поле balance - это начальная сумма, которая будет создана для данного токена.
  • maximum определяет максимальное количество токенов, которое будет минтиться для данного созданного TokenData.
  • Поле royalty_payee_address - это адрес, на который выплачивается royalty.
  • Количество royalty_points_numerator / royalty_points_denominator - это процент от цены продажи (royalty), который должен быть выплачен на адрес получателя. Это может быть адрес учетной записи одного владельца или адрес общей учетной записи, принадлежащей группе создателей.
  • Свойство token_mutate_setting описывает, является ли поле TokenData изменяемым.
  • Property_keys, Property_values и Property_types - это пары ключ-значение свойства, которые можно хранить, читать и записывать в сети.

Эти функции скрипта можно вызывать через REST API. См. ниже:

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

Раздача токенов

В Aptos и Move каждый токен занимает место и имеет право собственности. В связи с этим передача токенов не является односторонней и требует двухэтапного процесса, похожего на доску объявлений. Сначала отправитель должен зарегистрировать, что токен доступен для получателя, а затем получатель должен затребовать этот токен. Это реализовано в пробном образце модуля Move под названием TokenTransfer.

SimpleToken предоставляет несколько обернутых функций для поддержки перевода на другую учетную запись, утверждения этого перевода или прекращения перевода.

Получение ID токена

Чтобы передать токен, отправитель должен сначала определить id токена, зная учетную запись создателя, имя коллекции и имя токена. Это можно получить с помощью запроса REST:

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

Предложение токена

Следующая функция скрипта Move в 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

*Заявление прав на токен *

Следующая функция скрипта Move в SimpleToken поддерживает получение токена, предоставленного предыдущей функцией, фактически заявляя права на токен:

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

Минт Lazy в сети

Когда Alice становится знаменитостью в своей общине, ее кошачьи NFT пользуются большим спросом. Однако Алиса не хочет оплачивать стоимость минта 10 миллионов NFT на начальном этапе. Она хочет оплачивать расходы только тогда, когда кто-то захочет получить NFT. Она может заминтить 10 миллионов не инициализированных взаимозаменяемых кошачьих токенов в одной транзакции.

Когда Jack хочет купить NFT у Alice, она может изменить один заменимый токен.

    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

Это создаст новую property_version и создаст новый TokenId для предыдущего не инициализированного заменимого токена (property_version = 0), который станет NFT. Затем Alice может передать NFT Jack. Alice нужно будет оплатить стоимость создания NFT из заменимого токена только тогда, когда кто-то захочет купить.

💖 💪 🙅 🚩
egormajj
EgorMajj

Posted on August 23, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related