Dynamiczne metadane NFT przy użyciu Token Extensions

Dzięki programowi Token Extension program możesz tworzyć NFT i zasoby cyfrowe przy użyciu rozszerzeń metadanych. Łącznie te rozszerzenia (metadata pointer i token metadata) umożliwiają przechowywanie dowolnych metadanych natywnie w łańcuchu bloków. Wszystko w konfigurowalnym magazynie danych klucz-wartość bezpośrednio na mint account tokena, co obniża koszty i złożoność.

Rozwiązanie to może być szczególnie przydatne dla gier web3, ponieważ teraz możemy mieć te "dodatkowe pola metadanych" w onchainowym magazynie klucz-wartość, co pozwala grom zapisywać i odczytywać unikalny stan w samym NFT (np. statystyki lub ekwipunek postaci w grze).

Budowanie programu onchain

W tym przewodniku dla deweloperów pokażemy, jak budować NFT oparte na Token Extension i niestandardowe metadane przy użyciu programu Anchor. Ten program zapisze poziom i zebrane zasoby gracza w NFT.

NFT zostanie stworzone przez program Anchor, dzięki czemu jego mintowanie z poziomu klienta JavaScript jest bardzo proste. Każde NFT będzie miało podstawową strukturę dostarczaną przez interfejs Token Metadata:

  • domyślne pola onchain – name, symbol i uri
    • uri to link do pliku JSON przechowywanego poza łańcuchem bloków, który zawiera offchain metadane NFT
  • zdefiniujemy również niestandardowe "dodatkowe pola"

Wszystkie te pola są zapisywane przy użyciu rozszerzenia metadanych wskazującego na mint account NFT, dzięki czemu są dostępne dla każdego użytkownika lub programu.

Wideo i kod źródłowy

Filmowy przewodnik po tym przykładzie znajdziesz na kanale YouTube Solana Foundation:

Inne przypadki użycia w grach

Tego typu NFT z konfigurowalnymi metadanymi onchain otwierają wiele interesujących możliwości dla deweloperów gier. Zwłaszcza że tymi metadanymi można bezpośrednio wchodzić w interakcję lub zarządzać za pomocą programu onchain.

Niektóre z tych zastosowań związanych z grami obejmują:

  • zapisywanie poziomu i XP gracza
  • aktualny ekwipunek i zbroja
  • bieżące zadanie
  • lista jest długa!

Mintowanie NFT

Aby stworzyć NFT, należy wykonać następujące kroki:

  1. Utwórz mint account
  2. Zainicjalizuj mint account
  3. Utwórz konto wskaźnika metadanych
  4. Zainicjalizuj konto wskaźnika metadanych
  5. Utwórz konto metadanych
  6. Zainicjalizuj konto metadanych
  7. Utwórz associated token account
  8. Mintuj token do associated token account
  9. Zablokuj uprawnienia mintowania

Kod programu w Rust

Oto kod Rust użyty do mintowania NFT przy użyciu programu Token Extension:

// 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,
)?;

Kod klienta JavaScript

Wywołanie mintowania NFT z poziomu klienta jest bardzo proste:

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`);

Przykład szybkiego startu

Powyższy przykład oparty jest na Solana Games Preset, który generuje szkielet projektu zawierający klienta JavaScript i Unity dla tej gry, w tym konfigurację do interakcji z programem Anchor w Solanie.

Możesz uruchomić go samodzielnie za pomocą następującego polecenia:

npx create-solana-game gameName

Konfiguracja lokalnego środowiska

Aby uruchomić ten przykład lokalnie, musisz upewnić się, że masz skonfigurowane lokalne środowisko do programowania w Solanie, w tym zainstalowane i skonfigurowane Anchor CLI. Jeśli jeszcze tego nie zrobiłeś, może skorzystać z wcześniej podanego przewodnika konfiguracji.

Struktura projektu

Projekt Anchor ma następującą strukturę:

Punkt wejścia znajduje się w pliku lib.rs. Tam definiujemy identyfikator programu i instrukcje. Instrukcje są zdefiniowane w folderze instructions. Stan jest zdefiniowany w folderze state.

Wywołania trafiają do pliku lib.rs i są następnie przekazywane do instrukcji. Instrukcje wywołują następnie stan, aby pobrać dane i je aktualizować.

Instrukcję mintowania NFT znajdziesz w folderze 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

Program Anchor

Aby dokończyć konfigurację programu Anchor wygenerowanego przez narzędzie create-solana-game:

  1. cd program aby przejść do katalogu programu
  2. Uruchom anchor build, aby zbudować program
  3. Uruchom anchor deploy, aby wdrożyć program
  4. Skopiuj identyfikator programu z terminala do plików lib.rs, anchor.toml oraz w projekcie Unity do AnchorService, a jeśli używasz JavaScript – do pliku anchor.ts
  5. Zbuduj i wdróż ponownie

Klient NextJS

Aby dokończyć konfigurację klienta NextJS wygenerowanego przez narzędzie create-solana-game:

  1. Skopiuj programId do app/utils/anchor.ts
  2. cd app aby przejść do katalogu aplikacji
  3. Uruchom yarn install, aby zainstalować zależności Node
  4. Uruchom yarn dev, aby uruchomić klienta
  5. Po wprowadzeniu zmian w programie Anchor pamiętaj, aby skopiować typy z programu do klienta, żeby móc z nich korzystać. Typy TypeScript znajdziesz w folderze target/idl.

Uruchamianie tego przykładu lokalnie

Użycie polecenia test Anchor z flagą --detach uruchomi i skonfiguruje lokalne środowisko testowe validator Solany z wdrożonym programem (i utrzyma validator uruchomiony po zakończeniu testów):

cd program
anchor test --detach

Następnie możesz ustawić Solana Explorer tak, aby korzystał z lokalnegośrodowiska testowego validator (uruchamianego podczas wykonywania polecenia anchor test), dzięki czemu możesz przeglądać transakcje:

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

Program jest już wdrożony w sieci, więc możesz go wypróbować na devnet. Klient JavaScript posiada również przycisk do mintowania NFT. Uruchamianie klienta JavaScript:

cd app
yarn install
yarn dev

Otwieranie projektu Unity

Najpierw otwórz projekt Unity w wersji Unity 2021.3.32.f1 (lub podobnej), następnie otwórz GameScene lub LoginScene i naciśnij play. Użyj przycisku logowania edytora w lewym dolnym rogu.

Jeśli nie możesz uzyskać SOL z devnet, możesz skopiować swój adres z konsoli i postępować zgodnie z instrukcjami zawartymi w tym przewodniku o tym, jak uzyskać devnet SOL

Połączenie z lokalnym środowiskiem testowym validator Solany w Unity

Jeśli chcesz uniknąć konieczności zarządzania SOL z devnet, możesz połączyć się z uruchomionym lokalnym środowiskiem testowym validator z poziomu Unity. Wystarczy dodać te linki do obiektu gry wallet holder:

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

Uruchamianie klienta JavaScript

Aby uruchomić klienta JavaScript i móc wchodzić w interakcję z grą i programem przez przeglądarkę internetową:

  • otwórz katalog app w repozytorium
  • zainstaluj zależności Node
  • uruchom polecenie dev, aby uruchomić serwer deweloperski
cd app
yarn install
yarn dev

Aby zacząć modyfikować program i połączyć się z własnym programem, wykonaj poniższe kroki.

Is this page helpful?