В этом руководстве описывается, как создавать и передавать NFT на блокчейне Aptos. Реализацию Aptos для основных NFT можно найти в модуле token.move Move.
Шаг 1: Выберите SDK
Установите предпочтительный SDK из приведенного ниже списка:
Шаг 2: Запустите пример
Каждый SDK содержит каталог examples
. В этом руководстве рассматривается пример simple-nft
.
Клонируйте репозиторий aptos-core
:
Copy git clone git@github.com:aptos-labs/aptos-core.git ~/aptos-core
Typescript Python Rust
Перейдите в каталог примеров Typescript SDK:
Copy cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript
Установите необходимые зависимости:
Запустите пример Typescript simple_nft
:
Перейдите в каталог Python SDK:
Copy cd ~/aptos-core/ecosystem/python/sdk
Установите необходимые зависимости:
Copy curl -sSL https://install.python-poetry.org | python3
poetry update
Запустите пример Python simple-nft
:
Copy poetry run python -m examples.simple-nft
Шаг 3: Понимание вывода
После выполнения примера simple-nft
должен появиться следующий вывод, хотя некоторые значения будут отличаться:
Copy === 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 Python Rust
СМОТРИТЕ ПОЛНЫЙ КОД
Смотрите simple_nft
для получения полного кода при выполнении следующих шагов.
СМОТРИТЕ ПОЛНЫЙ КОД
Смотрите simple-nft
для получения полного кода при выполнении следующих шагоd
Шаг 4.1: Инициализация клиентов
На первом шаге примера инициализируются клиенты API и faucet.
Клиент API взаимодействует с REST API, и
Клиент faucet взаимодействует с сервисом devnet Faucet для создания и пополнения учетных записей.
Typescript Python Rust
Copy const client = new AptosClient ( NODE_URL );
const faucetClient = new FaucetClient ( NODE_URL, FAUCET_URL );
Используя клиент API, мы можем создать TokenClient
, который мы используем для обычных операций с токенами, таких как создание коллекций и токенов, их передача, утверждение и так далее.
Copy const tokenClient = new TokenClient ( client );
common.ts
инициализирует значения URL таким образом:
Copy 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" ;
ПОДСКАЗКА
По умолчанию URL-адреса обеих служб указывают на службы Aptos devnet. Однако их можно настроить с помощью следующих переменных среды:
Copy rest_client = RestClient ( NODE_URL )
faucet_client = FaucetClient ( FAUCET_URL, rest_client )
common.py
инициализирует эти значения следующим образом:
Copy 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. Однако их можно настроить с помощью следующих переменных среды:
ПОДСКАЗКА
По умолчанию URL-адреса обеих служб указывают на службы Aptos devnet. Однако их можно настроить с помощью следующих переменных среды:
Шаг 4.2: Создание локальных учетных записей
Следующим шагом будет создание двух учетных записей на локальном уровне. Учетные записи представляют собой состояние в сети и вне сети. Состояние вне сети состоит из адреса и пары открытого и закрытого ключей, используемых для аутентификации владельца. Этот шаг демонстрирует, как генерировать состояние вне сети.
Typescript Python Rust
Copy const alice = new AptosAccount ();
const bob = new AptosAccount ();
Copy alice = Account.generate ()
bob = Account.generate ()
Шаг 4.3: Создание учетных записей в блокчейне
В Aptos каждая учетная запись должна иметь представление в сети, чтобы поддерживать получение токенов и coins, а также взаимодействие с другими dApps. Учетная запись представляет собой носитель для хранения активов, поэтому она должна быть явно создана. В данном примере для создания учетных записей Alice и Bob используется Faucet:
Typescript Python Rust
Copy await faucetClient.fundAccount ( alice.address () , 100_000_000);
await faucetClient.fundAccount ( bob.address () , 100_000_000);
Copy faucet_client.fund_account(alice.address( ), 100_000_000)
faucet_client.fund_account(bob.address( ), 100_000_000)
Шаг 4.4: Создание коллекции
Теперь начинается процесс создания токенов. Сначала создатель должен создать коллекцию для хранения токенов. Коллекция может содержать ноль, один или много отдельных токенов. Коллекция не ограничивает атрибуты токенов, поскольку она является лишь хранилищем.
Typescript Python Rust
Ваше приложение будет вызывать createCollection
:
Copy const txnHash1 = await tokenClient.createCollection (
alice,
collectionName,
"Alice's simple collection" ,
"https://alice.com" ,
);
Подпись функции createCollection
. Она возвращает хэш транзакции:
Copy async createCollection (
account: AptosAccount,
name: string,
description: string,
uri: string,
maxAmount: AnyNumber = MAX_U64_BIG_INT,
extraArgs?: OptionalTransactionArgs,
) : Promise < strin g > {
Ваше приложение будет вызывать create_collection
:
Copy txn_hash = rest_client.create_collection(
alice, collection_name, "Alice's simple collection", "https://aptos.dev"
)
Подпись функции create_collection
. Возвращает хэш транзакции:
Copy def create_collection (
self, account: Account, name: str, description: str, uri: str
) - > str:
Шаг 4.5: Создание токена
Чтобы создать токен, создатель должен указать связанную с ним коллекцию. Токен должен быть связан с коллекцией, и в этой коллекции должны быть оставшиеся токены, которые можно минтить. Существует множество атрибутов, связанных с токеном, но API-помощник раскрывает только минимальное количество, необходимое для создания статического контента.
Typescript Python Rust
Ваше приложение будет вызывать createToken
:
Copy const txnHash2 = await tokenClient.createToken (
alice,
collectionName,
tokenName,
"Alice's simple token" ,
1,
"https://aptos.dev/img/nyan.jpeg" ,
);
Подпись функции createToken
. Возвращает хэш транзакции:
Copy async createToken (
account: AptosAccount,
collectionName: string,
name: string,
description: string,
supply: number,
uri: string,
max: AnyNumber = MAX_U64_BIG_INT,
royalty_payee_address: MaybeHexString = account.address () ,
royalty_points_denominator: number = 0 ,
royalty_points_numerator: number = 0 ,
property_keys: Array < strin g > = [],
property_values: Array < strin g > = [],
property_types: Array < strin g > = [],
extraArgs?: OptionalTransactionArgs,
) : Promise < strin g > {
Ваше приложение будет вызывать create_token
:
Copy 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
. Возвращает хэш транзакции:
Copy def create_token (
self,
account: Account,
collection_name: str,
name: str,
description: str,
supply: int,
uri: str,
royalty_points_per_million: int,
) - > str:
Шаг 4.6: Чтение метаданных токена и коллекции
Метаданные коллекции и токена хранятся на учетной записи создателя в таблице Collections
. SDK предоставляют удобные оболочки для запросов к этим конкретным таблицам:
Typescript Python Rust
Чтобы прочитать метаданные коллекции:
Copy const collectionData = await tokenClient.getCollectionData ( alice.address () , collectionName);
console.log( ` Alice's collection: ${JSON.stringify(collectionData, null, 4)}`);
Чтобы прочитать метаданные токена:
Copy const tokenData = await tokenClient.getTokenData(alice.address(), collectionName, tokenName); console.log(Alice's token data: ${JSON.stringify(tokenData, null, 4)});
Вот как getTokenData
запрашивает метаданные токена:
Copy async getTokenData (
creator: MaybeHexString,
collectionName: string,
tokenName: string,
) : Promise < TokenTypes.TokenDat a > {
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 );
}
Чтобы прочитать метаданные коллекции:
Copy collection_data = rest_client.get_collection ( alice.address () , collection_name)
print (
f "Alice's collection: {json.dumps(collection_data, indent=4, sort_keys=True)}"
)
Чтобы прочитать метаданные токена:
Copy 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
запрашивает метаданные токена:
Copy 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,
)
Шаг 4.7: Чтение баланса токена
Каждый токен в Aptos является отдельным активом, активы, принадлежащие пользователю, хранятся в его TokenStore. Чтобы получить баланс:
Typescript Python Rust
Copy const aliceBalance1 = await tokenClient.getToken (
alice.address () ,
collectionName,
tokenName,
`${tokenPropertyVersion}` ,
);
console.log( ` Alice's token balance: ${aliceBalance1["amount"]}`);
Copy balance = rest_client.get_token_balance (
alice.address () , alice.address () , collection_name, token_name, property_version
)
print (f "Alice's token balance: {balance}" )
Шаг 4.8: Предложение и требование токена
Многие пользователи получили нежелательные токены, которые могут вызвать как минимальный дискомфорт, так и серьезные последствия. Aptos предоставляет каждому владельцу аккаунта право решать, принимать или не принимать односторонние переводы. По умолчанию односторонние переводы не поддерживаются. Таким образом, Aptos обеспечивает основу для предложения и требования токенов.
Чтобы предложить токен:
Typescript Python Rust
Copy const txnHash3 = await tokenClient.offerToken (
alice,
bob.address () ,
alice.address () ,
collectionName,
tokenName,
1,
tokenPropertyVersion,
);
Чтобы получить токен:
Copy const txnHash4 = await tokenClient.claimToken (
bob,
alice.address () ,
alice.address () ,
collectionName,
tokenName,
tokenPropertyVersion,
);
Copy txn_hash = rest_client.offer_token(
alice,
bob.address(),
alice.address(),
collection_name,
token_name,
property_version,
1,
)
Чтобы получить токен:
Copy txn_hash = rest_client.claim_token (
bob,
alice.address () ,
alice.address () ,
collection_name,
token_name,
property_version,
)
Шаг 4.9: Безопасная односторонняя передача токена
Для поддержки безопасной односторонней передачи токена отправитель может сначала попросить получателя подтвердить вне сети о предстоящей передаче. Это происходит в форме запроса мультиагентной транзакции. Мультиагентные транзакции содержат несколько подписей, по одной для каждой учетной записи на сети. Move затем может использовать это для предоставления разрешений на уровне signer
всем подписантам. Для передачи токенов это гарантирует, что принимающая сторона действительно желает получить этот токен, не требуя использования описанной выше структуры передачи токенов.
Typescript Python Rust
Copy let txnHash5 = await tokenClient.directTransferToken (
bob,
alice,
alice.address () ,
collectionName,
tokenName,
1,
tokenPropertyVersion,
);
Copy txn_hash = rest_client.direct_transfer_token (
bob, alice, alice.address () , collection_name, token_name, 0 , 1
)
Шаг 4.10: Включение односторонней передачи токенов
В скором времени.