Динамічні метадані NFT з використанням Token Extensions

За допомогою Token Extension program ви можете створювати NFT та цифрові активи, використовуючи розширення метаданих. Разом ці розширення (metadata pointer та token metadata) дозволяють розміщувати будь-які потрібні метадані безпосередньо в блокчейні. Все це зберігається в налаштовуваному сховищі даних у форматі ключ-значення прямо на token's mint account, що знижує витрати та складність.

Це може бути особливо корисно для web3-ігор, оскільки тепер ми можемо мати ці «додаткові поля метаданих» в onchain-сховищі ключ-значення, що дозволяє іграм зберігати та отримувати унікальний стан безпосередньо в NFT (наприклад, статистику чи інвентар ігрового персонажа).

Розробка onchain-програми

У цьому посібнику для розробників ми покажемо, як створювати NFT на основі Token Extension та користувацькі метадані за допомогою програми Anchor. Ця програма зберігатиме рівень та зібрані ресурси гравця у NFT.

Цей NFT створюватиметься програмою Anchor, тому його дуже легко карбувати з JavaScript-клієнта. Кожен NFT матиме базову структуру, надану через інтерфейс Token Metadata:

  • стандартні onchain-поля — name, symbol та uri
    • uri — це посилання на offchain json-файл, який містить offchain- метадані NFT
  • також ми матимемо користувацькі «додаткові поля», які визначаємо самостійно

Усі ці поля зберігаються за допомогою розширення метаданих, яке вказує на mint account NFT, що робить їх доступними для будь-кого або будь-якої програми.

Відео та вихідний код

Ви можете знайти відеоогляд цього прикладу на YouTube-каналі Solana Foundation:

Інші варіанти використання в іграх

Такі NFT з налаштовуваними onchain-метаданими відкривають безліч цікавих можливостей для розробників ігор. Особливо з огляду на те, що цими метаданими можна безпосередньо взаємодіяти або керувати за допомогою onchain-програми.

Деякі з цих ігрових варіантів використання включають:

  • збереження рівня та досвіду (XP) гравця
  • поточна зброя та броня
  • поточний квест
  • і це ще не все!

Карбування NFT

Для створення NFT необхідно виконати такі кроки:

  1. Створити mint account
  2. Ініціалізувати mint account
  3. Створити обліковий запис вказівника метаданих
  4. Ініціалізувати обліковий запис вказівника метаданих
  5. Створити обліковий запис метаданих
  6. Ініціалізувати обліковий запис метаданих
  7. Створити associated token account
  8. Карбувати токен до associated token account
  9. Заморозити повноваження карбування

Код програми на Rust

Ось код на Rust для карбування NFT з використанням Token Extensions Program:

// calculate the space need for the mint account with the desired extensions
let space = ExtensionType::try_calculate_account_len::<Mint>(
&[ExtensionType::MetadataPointer])
.unwrap();
// This is the space required for the metadata account.
// We put the metadata into the mint account at the end so we
// don't need to create and additional account.
// Then the metadata pointer points back to the mint account.
// Using this technique, only one account is needed for both the mint
// information and the metadata.
let meta_data_space = 250;
let lamports_required = (Rent::get()?).minimum_balance(space + meta_data_space);
msg!(
"Create Mint and metadata account size and cost: {} lamports: {}",
space as u64,
lamports_required
);
system_program::create_account(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
system_program::CreateAccount {
from: ctx.accounts.signer.to_account_info(),
to: ctx.accounts.mint.to_account_info(),
},
),
lamports_required,
space as u64,
&ctx.accounts.token_program.key(),
)?;
// Assign the mint to the token program
system_program::assign(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
system_program::Assign {
account_to_assign: ctx.accounts.mint.to_account_info(),
},
),
&token_2022::ID,
)?;
// Initialize the metadata pointer (Need to do this before initializing the mint)
let init_meta_data_pointer_ix =
spl_token_2022::extension::metadata_pointer::instruction::initialize(
&Token2022::id(),
&ctx.accounts.mint.key(),
Some(ctx.accounts.nft_authority.key()),
Some(ctx.accounts.mint.key()),
)
.unwrap();
invoke(
&init_meta_data_pointer_ix,
&[
ctx.accounts.mint.to_account_info(),
ctx.accounts.nft_authority.to_account_info()
],
)?;
// Initialize the mint cpi
let mint_cpi_ix = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token_2022::InitializeMint2 {
mint: ctx.accounts.mint.to_account_info(),
},
);
token_2022::initialize_mint2(
mint_cpi_ix,
0,
&ctx.accounts.nft_authority.key(),
None).unwrap();
// We use a PDA as a mint authority for the metadata account because
// we want to be able to update the NFT from the program.
let seeds = b"nft_authority";
let bump = ctx.bumps.nft_authority;
let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]];
msg!("Init metadata {0}", ctx.accounts.nft_authority.to_account_info().key);
// Init the metadata account
let init_token_meta_data_ix =
&spl_token_metadata_interface::instruction::initialize(
&spl_token_2022::id(),
ctx.accounts.mint.key,
ctx.accounts.nft_authority.to_account_info().key,
ctx.accounts.mint.key,
ctx.accounts.nft_authority.to_account_info().key,
"Beaver".to_string(),
"BVA".to_string(),
"https://arweave.net/MHK3Iopy0GgvDoM7LkkiAdg7pQqExuuWvedApCnzfj0".to_string(),
);
invoke_signed(
init_token_meta_data_ix,
&[ctx.accounts.mint.to_account_info().clone(), ctx.accounts.nft_authority.to_account_info().clone()],
signer,
)?;
// Update the metadata account with an additional metadata field in this case the player level
invoke_signed(
&spl_token_metadata_interface::instruction::update_field(
&spl_token_2022::id(),
ctx.accounts.mint.key,
ctx.accounts.nft_authority.to_account_info().key,
spl_token_metadata_interface::state::Field::Key("level".to_string()),
"1".to_string(),
),
&[
ctx.accounts.mint.to_account_info().clone(),
ctx.accounts.nft_authority.to_account_info().clone(),
],
signer
)?;
// Create the associated token account
associated_token::create(
CpiContext::new(
ctx.accounts.associated_token_program.to_account_info(),
associated_token::Create {
payer: ctx.accounts.signer.to_account_info(),
associated_token: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
},
))?;
// Mint one token to the associated token account of the player
token_2022::mint_to(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token_2022::MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.nft_authority.to_account_info(),
},
signer
),
1,
)?;
// Freeze the mint authority so no more tokens can be minted to make it an NFT
token_2022::set_authority(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token_2022::SetAuthority {
current_authority: ctx.accounts.nft_authority.to_account_info(),
account_or_mint: ctx.accounts.mint.to_account_info(),
},
signer
),
AuthorityType::MintTokens,
None,
)?;

Код JavaScript-клієнта

Виклик карбування NFT з клієнта дуже простий:

const nftAuthority = PublicKey.findProgramAddressSync(
[Buffer.from("nft_authority")],
program.programId
);
const mint = new Keypair();
const destinationTokenAccount = getAssociatedTokenAddressSync(
mint.publicKey,
publicKey,
false,
TOKEN_2022_PROGRAM_ID
);
const transaction = await program.methods
.mintNft()
.accounts({
signer: publicKey,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_2022_PROGRAM_ID,
tokenAccount: destinationTokenAccount,
mint: mint.publicKey,
rent: web3.SYSVAR_RENT_PUBKEY,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
nftAuthority: nftAuthority[0]
})
.signers([mint])
.transaction();
console.log("transaction", transaction);
const txSig = await sendTransaction(transaction, connection, {
signers: [mint],
skipPreflight: true
});
console.log(`https://explorer.solana.com/tx/${txSig}?cluster=devnet`);

Приклад швидкого старту

Наведений вище приклад базується на Solana Games Preset, який генерує шаблон, що включає JavaScript та Unity-клієнт для цієї гри, а також конфігурацію для взаємодії з програмою Solana Anchor.

Ви можете запустити його самостійно за допомогою такої команди:

npx create-solana-game gameName

Налаштування локального середовища

Щоб запустити цей приклад локально, вам необхідно переконатися, що ви налаштували локальне середовище для розробки на Solana, включаючи встановлення та налаштування Anchor CLI. Якщо ви ще не зробили цього, дотримуйтесь раніше згаданого посібника з налаштування.

Структура проєкту

Проєкт Anchor має таку структуру:

Точка входу знаходиться у файлі lib.rs. Тут ми визначаємо ідентифікатор програми та інструкції. Інструкції визначені в папці instructions. Стан визначений у папці state.

Таким чином, виклики надходять до файлу lib.rs, а потім перенаправляються до інструкцій. Інструкції звертаються до стану для отримання та оновлення даних.

Інструкцію карбування NFT можна знайти в папці instructions.

├── src
├── instructions
│ ├── chop_tree.rs
│ ├── init_player.rs
│ ├── mint_nft.rs
│ └── update_energy.rs
├── state
│ ├── game_data.rs
│ ├── mod.rs
│ └── player_data.rs
├── lib.rs
└── constants.rs
└── errors.rs

Програма Anchor

Щоб завершити налаштування програми Anchor, згенерованої інструментом create-solana-game:

  1. cd program для переходу в директорію програми
  2. Виконайте anchor build для збірки програми
  3. Виконайте anchor deploy для розгортання програми
  4. Скопіюйте ідентифікатор програми з термінала до lib.rs, anchor.toml та у проєкт Unity в AnchorService, а якщо використовуєте JavaScript — до файлу anchor.ts
  5. Виконайте збірку та розгортання знову

NextJS-клієнт

Щоб завершити налаштування NextJS-клієнта, згенерованого інструментом create-solana-game:

  1. Скопіюйте programId до app/utils/anchor.ts
  2. cd app для переходу в директорію застосунку
  3. Виконайте yarn install для встановлення залежностей Node
  4. Виконайте yarn dev для запуску клієнта
  5. Після внесення змін до програми Anchor переконайтеся, що ви скопіювали типи з програми до клієнта для їх використання. TypeScript-типи знаходяться в папці target/idl.

Запуск прикладу локально

Використання команди test Anchor з прапорцем --detach запустить і налаштує локальний тестовий validator Solana з розгорнутою програмою (і залишить validator запущеним після завершення тестів):

cd program
anchor test --detach

Потім ви можете налаштувати Solana Explorer для використання вашого локального тестового validator (який запускається під час виконання команди anchor test), щоб переглядати транзакції:

https://explorer.solana.com/?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899

Програма також вже розгорнута в мережі, тому ви можете спробувати її на devnet. JavaScript-клієнт також має кнопку для карбування NFT. Запуск JavaScript-клієнта:

cd app
yarn install
yarn dev

Відкриття проєкту Unity

Спочатку відкрийте проєкт Unity у Unity Version 2021.3.32.f1 (або подібній), потім відкрийте GameScene або LoginScene і натисніть «Play». Використовуйте кнопку входу в редакторі у нижньому лівому куті.

Якщо ви не можете отримати devnet SOL, ви можете скопіювати свою адресу з консолі та дотримуватись інструкцій у цьому посібнику про те, як отримати devnet SOL

Підключення до тестового validator Solana в Unity

Якщо ви хочете уникнути необхідності підтримувати баланс devnet SOL, ви можете підключитися до локального тестового validator безпосередньо з Unity. Просто додайте ці посилання на ігровий об'єкт wallet holder:

http://localhost:8899
ws://localhost:8900

Запуск JavaScript-клієнта

Щоб запустити JavaScript-клієнт і мати можливість взаємодіяти з грою та програмою через веб-браузер:

  • відкрийте директорію app у репозиторії
  • встановіть залежності Node
  • виконайте команду dev для запуску сервера розробки
cd app
yarn install
yarn dev

Щоб почати змінювати програму та підключатися до власної програми, виконайте наведені нижче кроки.

Is this page helpful?