Wskaźnik Metadanych i Metadane Tokena

Czym są rozszerzenia Metadata Pointer i Token Metadata?

Rozszerzenie mint MetadataPointer Token Extensions Program przechowuje dwa pola na mint:

  • Uprawnienie, które może aktualizować wskaźnik
  • Adres konta, które przechowuje metadane tokena

Ten wskaźnik może odwoływać się do dowolnego konta należącego do programu implementującego interfejs token-metadata.

Token Extensions Program implementuje również ten sam interfejs bezpośrednio poprzez rozszerzenie mint TokenMetadata. Dzięki TokenMetadata, mint przechowuje name, symbol, uri, update_authority oraz niestandardowe metadane tokena bezpośrednio na koncie mint.

URI metadanych off-chain

Pole uri wskazuje na metadane JSON przechowywane poza łańcuchem. Zobacz Format metadanych off-chain.

Oba rozszerzenia rozwiązują różne problemy:

  • MetadataPointer określa konto, które przechowuje metadane.
  • TokenMetadata przechowuje metadane bezpośrednio na koncie mint.

Rozszerzenie o zmiennej długości

TokenMetadata jest rozszerzeniem TLV o zmiennej długości. InitializeTokenMetadata, UpdateField i RemoveKey mogą zmieniać rozmiar danych konta mint, ale nie przekazują dodatkowych lamportów. Konto mint musi posiadać wystarczającą ilość lamportów, aby pozostać zwolnionym z czynszu dla przechowywanych metadanych. Jeśli metadane później się rozrosną, dodatkowe lamporty muszą zostać przekazane na konto mint przed instrukcją, która je powiększy.

Jak przechowywać metadane tokena na koncie mint

Aby przechowywać metadane na koncie mint:

  1. Oblicz rozmiar konta mint oraz wymagany czynsz dla mint, rozszerzeń i metadanych.
  2. Utwórz konto mint za pomocą CreateAccount, zainicjalizuj MetadataPointer i zainicjalizuj mint za pomocą InitializeMint.
  3. Zainicjalizuj TokenMetadata na koncie mint, a następnie użyj UpdateField, aby dodać lub zaktualizować metadane.
  4. Użyj RemoveKey, UpdateAuthority i Emit, aby usunąć niestandardowe metadane, zmienić lub wyczyścić uprawnienia do aktualizacji lub zwrócić bieżące metadane w danych zwrotnych transakcji.

Oblicz rozmiar konta

Oblicz rozmiar konta mint dla bazowego mint wraz z rozszerzeniem MetadataPointer. To jest rozmiar używany w CreateAccount.

Oblicz czynsz

Oblicz czynsz, używając maksymalnego rozmiaru potrzebnego po zapisaniu TokenMetadata na mint.

Utwórz konto mint

Utwórz konto mint z obliczoną przestrzenią i lamportami.

Zainicjalizuj MetadataPointer

Zainicjalizuj MetadataPointer i ustaw jego adres metadanych na adres mint.

Zainicjalizuj mint

Zainicjalizuj mint za pomocą InitializeMint w tej samej transakcji.

Zainicjalizuj i zaktualizuj metadane

Zainicjalizuj TokenMetadata na mint. Użyj UpdateField, aby dodać niestandardowe metadane; jeśli pole nie istnieje, UpdateField je dodaje.

Aktualizacja, usunięcie lub emisja metadanych

Po zainicjowaniu mint użyj UpdateField do aktualizacji metadanych, UpdateMetadataPointer do aktualizacji wskaźnika metadanych, RemoveKey do usunięcia niestandardowych metadanych, UpdateAuthority do wyczyszczenia uprawnień aktualizacji oraz Emit do zwrócenia bieżących metadanych.

Oblicz rozmiar konta

Oblicz rozmiar konta mint dla bazowego mint wraz z rozszerzeniem MetadataPointer. To jest rozmiar używany w CreateAccount.

Oblicz czynsz

Oblicz czynsz, używając maksymalnego rozmiaru potrzebnego po zapisaniu TokenMetadata na mint.

Utwórz konto mint

Utwórz konto mint z obliczoną przestrzenią i lamportami.

Zainicjalizuj MetadataPointer

Zainicjalizuj MetadataPointer i ustaw jego adres metadanych na adres mint.

Zainicjalizuj mint

Zainicjalizuj mint za pomocą InitializeMint w tej samej transakcji.

Zainicjalizuj i zaktualizuj metadane

Zainicjalizuj TokenMetadata na mint. Użyj UpdateField, aby dodać niestandardowe metadane; jeśli pole nie istnieje, UpdateField je dodaje.

Aktualizacja, usunięcie lub emisja metadanych

Po zainicjowaniu mint użyj UpdateField do aktualizacji metadanych, UpdateMetadataPointer do aktualizacji wskaźnika metadanych, RemoveKey do usunięcia niestandardowych metadanych, UpdateAuthority do wyczyszczenia uprawnień aktualizacji oraz Emit do zwrócenia bieżących metadanych.

Example
const client = await createClient()
.use(generatedPayer())
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900"
})
)
.use(rpcAirdrop())
.use(airdropPayer(lamports(1_000_000_000n)));
const mint = await generateKeyPairSigner();
const metadataPointerExtension = extension("MetadataPointer", {
authority: client.payer.address,
metadataAddress: mint.address
});
const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

Kolejność instrukcji

MetadataPointerInstruction::Initialize musi występować przed InitializeMint. CreateAccount, MetadataPointerInstruction::Initialize oraz InitializeMint muszą być uwzględnione w tej samej transakcji.

Źródło referencyjne

Wskaźnik metadanych

ElementOpisŹródło
MetadataPointerRozszerzenie mint przechowujące uprawnienia wskaźnika metadanych oraz adres konta metadanych.Źródło
MetadataPointerInstruction::InitializeInicjalizuje rozszerzenie wskaźnika metadanych przed InitializeMint.Źródło
MetadataPointerInstruction::UpdateAktualizuje adres metadanych przechowywany przez rozszerzenie wskaźnika metadanych mint.Źródło
process_initialize (MetadataPointer)Logika procesora wskaźnika metadanych wymagająca co najmniej uprawnień lub adresu metadanych podczas inicjalizacji.Źródło
process_update (MetadataPointer)Weryfikuje uprawnienia wskaźnika metadanych, a następnie nadpisuje przechowywany w mint adres metadanych.Źródło

Metadane tokena

ElementOpisŹródło
TokenMetadataStan interfejsu metadanych tokena o zmiennej długości przechowywany w wpisie TLV.Źródło
FieldEnum pola używany przez UpdateField do wskazywania name, symbol, uri lub niestandardowego klucza.Źródło
TokenMetadataInstruction::InitializeInicjalizuje podstawowe pola name, symbol oraz uri dla konta metadanych tokena.Źródło
TokenMetadataInstruction::UpdateFieldDodaje lub aktualizuje pole podstawowe lub niestandardowe pole metadanych w metadanych tokena.Źródło
TokenMetadataInstruction::RemoveKeyUsuwa niestandardowy klucz metadanych. Pól podstawowych nie można usunąć za pomocą tej instrukcji.Źródło
TokenMetadataInstruction::UpdateAuthorityRotuje uprawnienia aktualizacji metadanych lub całkowicie je usuwa, aby metadane stały się niezmienne.Źródło
TokenMetadataInstruction::EmitZwraca zserializowane metadane poprzez dane zwrotne transakcji, opcjonalnie dla zakresu bajtów.Źródło
process_initialize (TokenMetadata)Logika procesora metadanych tokena wymagająca, aby konto metadanych było samym mint, a mint posiadał MetadataPointer.Źródło
process_update_fieldRealokuje i nadpisuje wpis TLV TokenMetadata o zmiennej długości po aktualizacji pola.Źródło
process_remove_keyWeryfikuje uprawnienia aktualizacji, usuwa niestandardowy klucz metadanych i nadpisuje wpis TLV.Źródło
process_update_authorityWeryfikuje bieżące uprawnienia aktualizacji, następnie rotuje lub usuwa uprawnienia metadanych w miejscu.Źródło
process_emitOdczytuje zserializowane TokenMetadata z mint i zapisuje żądany fragment do danych zwrotnych.Źródło

Typescript

Poniższy przykład Kit wykorzystuje bezpośrednio wygenerowane instrukcje. Starsze przykłady używające @solana/web3.js, @solana/spl-token oraz @solana/spl-token-metadata zostały dołączone wyłącznie w celach informacyjnych.

Kit

Instructions
import {
lamports,
createClient,
generateKeyPairSigner,
unwrapOption
} from "@solana/kit";
import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";
import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";
import { unpack as unpackTokenMetadata } from "@solana/spl-token-metadata";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
extension,
fetchMint,
getEmitTokenMetadataInstruction,
getInitializeMetadataPointerInstruction,
getInitializeMintInstruction,
getInitializeTokenMetadataInstruction,
getMintSize,
getRemoveTokenMetadataKeyInstruction,
getUpdateMetadataPointerInstruction,
getUpdateTokenMetadataFieldInstruction,
getUpdateTokenMetadataUpdateAuthorityInstruction,
isExtension,
tokenMetadataField,
TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";
const client = await createClient()
.use(generatedPayer())
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900"
})
)
.use(rpcAirdrop())
.use(airdropPayer(lamports(1_000_000_000n)));
const mint = await generateKeyPairSigner();
const metadataPointerExtension = extension("MetadataPointer", {
authority: client.payer.address,
metadataAddress: mint.address
});
const mintSpace = BigInt(getMintSize([metadataPointerExtension]));
const maxTokenMetadataExtension = extension("TokenMetadata", {
updateAuthority: client.payer.address,
mint: mint.address,
name: "Example Token v2",
symbol: "EXMPL",
uri: "https://example.com/token.json",
additionalMetadata: new Map([
["description", "Metadata stored on mint account"]
])
});
const maxMintSpace = BigInt(
getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
.getMinimumBalanceForRentExemption(maxMintSpace)
.send();
await client.sendTransaction([
getCreateAccountInstruction({
payer: client.payer, // Account funding account creation.
newAccount: mint, // New mint account to create.
lamports: mintRent, // Lamports funding the mint account rent.
space: mintSpace, // Account size in bytes for the mint plus MetadataPointer.
programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.
}),
getInitializeMetadataPointerInstruction({
mint: mint.address, // Mint account that stores the MetadataPointer extension.
authority: client.payer.address, // Authority allowed to update the metadata pointer later.
metadataAddress: mint.address // Account address that stores the metadata.
}),
getInitializeMintInstruction({
mint: mint.address, // Mint account to initialize.
decimals: 0, // Number of decimals for the token.
mintAuthority: client.payer.address, // Authority allowed to mint new tokens.
freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.
}),
getInitializeTokenMetadataInstruction({
metadata: mint.address, // Mint account that stores the metadata.
updateAuthority: client.payer.address, // Authority allowed to update metadata later.
mint: mint.address, // Mint that the metadata describes.
mintAuthority: client.payer, // Signer authorizing metadata initialization for the mint.
name: "Example Token", // Token name stored in metadata.
symbol: "EXMPL", // Token symbol stored in metadata.
uri: "https://example.com/token.json" // URI pointing to off-chain JSON metadata.
}),
getUpdateTokenMetadataFieldInstruction({
metadata: mint.address, // Mint account that stores the metadata.
updateAuthority: client.payer, // Signer authorized to update metadata fields.
field: tokenMetadataField("Key", ["description"]), // Custom metadata field to add.
value: "Metadata stored on mint account" // Value stored for the custom metadata field.
})
]);
const initialMintAccount = await fetchMint(client.rpc, mint.address);
const initialExtensions =
unwrapOption(initialMintAccount.data.extensions) ?? [];
const initialTokenMetadata = initialExtensions.find((item) =>
isExtension("TokenMetadata", item)
);
console.dir(
{
mint: mint.address,
tokenMetadata: initialTokenMetadata
},
{ depth: null }
);
const updateMetadataTransaction = await client.sendTransaction([
getUpdateTokenMetadataFieldInstruction({
metadata: mint.address, // Mint account that stores the metadata.
updateAuthority: client.payer, // Signer authorized to update metadata fields.
field: tokenMetadataField("Name"), // Base metadata field to update.
value: "Example Token v2" // Updated value for the token name.
}),
getUpdateMetadataPointerInstruction({
mint: mint.address, // Mint account that stores the MetadataPointer extension.
metadataPointerAuthority: client.payer, // Signer authorized to update the metadata pointer.
metadataAddress: mint.address // Account address that stores the metadata.
}),
getRemoveTokenMetadataKeyInstruction({
metadata: mint.address, // Mint account that stores the metadata.
updateAuthority: client.payer, // Signer authorized to remove custom metadata.
key: "description" // Custom metadata key to remove.
}),
getUpdateTokenMetadataUpdateAuthorityInstruction({
metadata: mint.address, // Mint account that stores the metadata.
updateAuthority: client.payer, // Current signer authorized to change the update authority.
newUpdateAuthority: null // Clear the update authority so metadata can no longer be changed.
}),
getEmitTokenMetadataInstruction({
metadata: mint.address // Mint account that stores the metadata to emit.
})
]);
const updateMetadataResult = await client.rpc
.getTransaction(updateMetadataTransaction.context.signature, {
encoding: "json",
maxSupportedTransactionVersion: 0
})
.send();
const emittedDataBase64 = updateMetadataResult?.meta?.returnData?.data?.[0];
if (!emittedDataBase64) {
throw new Error("Expected token metadata return data");
}
const emittedTokenMetadata = unpackTokenMetadata(
Buffer.from(emittedDataBase64, "base64")
);
const mintAccount = await fetchMint(client.rpc, mint.address);
const extensions = unwrapOption(mintAccount.data.extensions) ?? [];
const metadataPointer = extensions.find((item) =>
isExtension("MetadataPointer", item)
);
const tokenMetadata = extensions.find((item) =>
isExtension("TokenMetadata", item)
);
console.dir(
{
mint: mint.address,
metadataPointer,
tokenMetadata,
emittedTokenMetadata
},
{ depth: null }
);
Console
Click to execute the code.

Web3.js

Instructions
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
sendAndConfirmTransaction,
SystemProgram,
Transaction
} from "@solana/web3.js";
import {
createInitializeMetadataPointerInstruction,
createInitializeMintInstruction,
createUpdateMetadataPointerInstruction,
ExtensionType,
getMetadataPointerState,
getMint,
getMintLen,
getTokenMetadata,
LENGTH_SIZE,
TOKEN_2022_PROGRAM_ID,
TYPE_SIZE
} from "@solana/spl-token";
import {
createInitializeInstruction,
createEmitInstruction,
createRemoveKeyInstruction,
unpack as unpackTokenMetadata,
createUpdateAuthorityInstruction,
createUpdateFieldInstruction,
pack,
type TokenMetadata
} from "@solana/spl-token-metadata";
const connection = new Connection("http://localhost:8899", "confirmed");
const latestBlockhash = await connection.getLatestBlockhash();
const feePayer = Keypair.generate();
const mint = Keypair.generate();
const airdropSignature = await connection.requestAirdrop(
feePayer.publicKey,
LAMPORTS_PER_SOL
);
await connection.confirmTransaction({
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
signature: airdropSignature
});
const maxMetadata: TokenMetadata = {
updateAuthority: feePayer.publicKey,
mint: mint.publicKey,
name: "Example Token v2",
symbol: "EXMPL",
uri: "https://example.com/token.json",
additionalMetadata: [["description", "Metadata stored on mint account"]]
};
const mintSpace = getMintLen([ExtensionType.MetadataPointer]);
const metadataSpace = TYPE_SIZE + LENGTH_SIZE + pack(maxMetadata).length;
const mintRent = await connection.getMinimumBalanceForRentExemption(
mintSpace + metadataSpace
);
await sendAndConfirmTransaction(
connection,
new Transaction().add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey, // Account funding account creation.
newAccountPubkey: mint.publicKey, // New mint account to create.
lamports: mintRent, // Lamports funding the mint account rent.
space: mintSpace, // Account size in bytes for the mint plus MetadataPointer.
programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
}),
createInitializeMetadataPointerInstruction(
mint.publicKey, // Mint account that stores the MetadataPointer extension.
feePayer.publicKey, // Authority allowed to update the metadata pointer later.
mint.publicKey, // Account address that stores the metadata.
TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
),
createInitializeMintInstruction(
mint.publicKey, // Mint account to initialize.
0, // Number of decimals for the token.
feePayer.publicKey, // Authority allowed to mint new tokens.
feePayer.publicKey, // Authority allowed to freeze token accounts.
TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
),
createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Program that owns the mint and metadata.
metadata: mint.publicKey, // Mint account that stores the metadata.
updateAuthority: feePayer.publicKey, // Authority allowed to update metadata later.
mint: mint.publicKey, // Mint that the metadata describes.
mintAuthority: feePayer.publicKey, // Signer authorizing metadata initialization for the mint.
name: "Example Token", // Token name stored in metadata.
symbol: "EXMPL", // Token symbol stored in metadata.
uri: "https://example.com/token.json" // URI pointing to off-chain JSON metadata.
}),
createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
metadata: mint.publicKey, // Mint account that stores the metadata.
updateAuthority: feePayer.publicKey, // Authority allowed to update metadata fields.
field: "description", // Custom metadata field to add.
value: "Metadata stored on mint account" // Value stored for the custom metadata field.
})
),
[feePayer, mint],
{ commitment: "confirmed" }
);
const initialTokenMetadata = await getTokenMetadata(
connection,
mint.publicKey,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
console.log(
JSON.stringify(
{
mint: mint.publicKey,
tokenMetadata: initialTokenMetadata
},
null,
2
)
);
const updateMetadataSignature = await sendAndConfirmTransaction(
connection,
new Transaction().add(
createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
metadata: mint.publicKey, // Mint account that stores the metadata.
updateAuthority: feePayer.publicKey, // Authority allowed to update metadata fields.
field: "name", // Base metadata field to update.
value: "Example Token v2" // Updated value for the token name.
}),
createUpdateMetadataPointerInstruction(
mint.publicKey, // Mint account that stores the MetadataPointer extension.
feePayer.publicKey, // Authority allowed to update the metadata pointer.
mint.publicKey, // Account address that stores the metadata.
[], // Additional signer accounts required by the instruction.
TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
),
createRemoveKeyInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
metadata: mint.publicKey, // Mint account that stores the metadata.
updateAuthority: feePayer.publicKey, // Authority allowed to remove custom metadata.
key: "description", // Custom metadata key to remove.
idempotent: false // Fail if the custom metadata key does not exist.
}),
createUpdateAuthorityInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
metadata: mint.publicKey, // Mint account that stores the metadata.
oldAuthority: feePayer.publicKey, // Current authority allowed to change the update authority.
newAuthority: null // Clear the update authority so metadata can no longer be changed.
}),
createEmitInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
metadata: mint.publicKey // Mint account that stores the metadata to emit.
})
),
[feePayer],
{ commitment: "confirmed" }
);
const updateMetadataTransaction = (await connection.getTransaction(
updateMetadataSignature,
{
maxSupportedTransactionVersion: 0
}
)) as any;
const emittedDataBase64 =
updateMetadataTransaction?.meta?.returnData?.data?.[0];
if (!emittedDataBase64) {
throw new Error("Expected token metadata return data");
}
const emittedTokenMetadata = unpackTokenMetadata(
Buffer.from(emittedDataBase64, "base64")
);
const mintAccount = await getMint(
connection,
mint.publicKey,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
const metadataPointer = getMetadataPointerState(mintAccount);
const tokenMetadata = await getTokenMetadata(
connection,
mint.publicKey,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
console.log(
JSON.stringify(
{
mint: mint.publicKey,
metadataPointer,
tokenMetadata,
emittedTokenMetadata
},
null,
2
)
);
Console
Click to execute the code.

Rust

Rust
use anyhow::{anyhow, Result};
use base64::prelude::{Engine as _, BASE64_STANDARD};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_client::rpc_config::RpcTransactionConfig;
use solana_commitment_config::CommitmentConfig;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
};
use solana_transaction_status_client_types::UiTransactionEncoding;
use solana_system_interface::instruction::create_account;
use spl_token_2022_interface::{
extension::{
metadata_pointer::{
instruction::{
initialize as initialize_metadata_pointer,
update as update_metadata_pointer,
},
MetadataPointer,
},
BaseStateWithExtensions, ExtensionType, StateWithExtensions,
},
instruction::initialize_mint,
state::Mint,
ID as TOKEN_2022_PROGRAM_ID,
};
use spl_token_metadata_interface::{
borsh::BorshDeserialize,
instruction::{
emit as emit_token_metadata, initialize as initialize_token_metadata,
remove_key as remove_token_metadata_key,
update_authority as update_token_metadata_authority,
update_field as update_token_metadata_field,
},
state::{Field, TokenMetadata},
};
#[tokio::main]
async fn main() -> Result<()> {
let client = RpcClient::new_with_commitment(
String::from("http://localhost:8899"),
CommitmentConfig::confirmed(),
);
let fee_payer = Keypair::new();
let airdrop_signature = client
.request_airdrop(&fee_payer.pubkey(), 1_000_000_000)
.await?;
loop {
let confirmed = client.confirm_transaction(&airdrop_signature).await?;
if confirmed {
break;
}
}
let mint = Keypair::new();
let max_token_metadata = TokenMetadata {
update_authority: Some(fee_payer.pubkey()).try_into()?,
mint: mint.pubkey(),
name: "Example Token v2".to_string(),
symbol: "EXMPL".to_string(),
uri: "https://example.com/token.json".to_string(),
additional_metadata: vec![(
"description".to_string(),
"Metadata stored on mint account".to_string(),
)],
};
let mint_space =
ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MetadataPointer])?;
let mint_rent = client
.get_minimum_balance_for_rent_exemption(mint_space + max_token_metadata.tlv_size_of()?)
.await?;
let create_mint_transaction = Transaction::new_signed_with_payer(
&[
create_account(
&fee_payer.pubkey(), // Account funding account creation.
&mint.pubkey(), // New mint account to create.
mint_rent, // Lamports funding the mint account rent.
mint_space as u64, // Account size in bytes for the mint plus MetadataPointer.
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
),
initialize_metadata_pointer(
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
&mint.pubkey(), // Mint account that stores the MetadataPointer extension.
Some(fee_payer.pubkey()), // Authority allowed to update the metadata pointer later.
Some(mint.pubkey()), // Account address that stores the metadata.
)?,
initialize_mint(
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
&mint.pubkey(), // Mint account to initialize.
&fee_payer.pubkey(), // Authority allowed to mint new tokens.
Some(&fee_payer.pubkey()), // Authority allowed to freeze token accounts.
0, // Number of decimals for the token.
)?,
initialize_token_metadata(
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint and metadata.
&mint.pubkey(), // Mint account that stores the metadata.
&fee_payer.pubkey(), // Authority allowed to update metadata later.
&mint.pubkey(), // Mint that the metadata describes.
&fee_payer.pubkey(), // Signer authorizing metadata initialization for the mint.
"Example Token".to_string(), // Token name stored in metadata.
"EXMPL".to_string(), // Token symbol stored in metadata.
"https://example.com/token.json".to_string(), // URI pointing to off-chain JSON metadata.
),
update_token_metadata_field(
&TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
&mint.pubkey(), // Mint account that stores the metadata.
&fee_payer.pubkey(), // Authority allowed to update metadata fields.
Field::Key("description".to_string()), // Custom metadata field to add.
"Metadata stored on mint account".to_string(), // Value stored for the custom metadata field.
),
],
Some(&fee_payer.pubkey()),
&[&fee_payer, &mint],
client.get_latest_blockhash().await?,
);
client
.send_and_confirm_transaction(&create_mint_transaction)
.await?;
let initial_mint_account = client.get_account(&mint.pubkey()).await?;
let initial_mint_state = StateWithExtensions::<Mint>::unpack(&initial_mint_account.data)?;
let initial_token_metadata = initial_mint_state.get_variable_len_extension::<TokenMetadata>()?;
println!("Mint: {}", mint.pubkey());
println!("Token Metadata: {:#?}", initial_token_metadata);
let update_metadata_transaction = Transaction::new_signed_with_payer(
&[
update_token_metadata_field(
&TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
&mint.pubkey(), // Mint account that stores the metadata.
&fee_payer.pubkey(), // Authority allowed to update metadata fields.
Field::Name, // Base metadata field to update.
"Example Token v2".to_string(), // Updated value for the token name.
),
update_metadata_pointer(
&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
&mint.pubkey(), // Mint account that stores the MetadataPointer extension.
&fee_payer.pubkey(), // Authority allowed to update the metadata pointer.
&[], // Additional signer accounts required by the instruction.
Some(mint.pubkey()), // Account address that stores the metadata.
)?,
remove_token_metadata_key(
&TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
&mint.pubkey(), // Mint account that stores the metadata.
&fee_payer.pubkey(), // Authority allowed to remove custom metadata.
"description".to_string(), // Custom metadata key to remove.
false, // Fail if the custom metadata key does not exist.
),
update_token_metadata_authority(
&TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
&mint.pubkey(), // Mint account that stores the metadata.
&fee_payer.pubkey(), // Current authority allowed to change the update authority.
None::<Pubkey>.try_into()?, // Clear the update authority so metadata can no longer be changed.
),
emit_token_metadata(
&TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
&mint.pubkey(), // Mint account that stores the metadata to emit.
None, // Start offset for the emitted metadata slice.
None, // End offset for the emitted metadata slice.
),
],
Some(&fee_payer.pubkey()),
&[&fee_payer],
client.get_latest_blockhash().await?,
);
let update_metadata_signature = client
.send_and_confirm_transaction(&update_metadata_transaction)
.await?;
let update_metadata_result = client
.get_transaction_with_config(
&update_metadata_signature,
RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::Json),
commitment: Some(CommitmentConfig::confirmed()),
max_supported_transaction_version: Some(0),
},
)
.await?;
let emitted_data = BASE64_STANDARD.decode(
update_metadata_result
.transaction
.meta
.and_then(|meta| meta.return_data.map(|return_data| return_data.data.0))
.ok_or_else(|| anyhow!("Expected token metadata return data"))?,
)?;
let emitted_token_metadata = TokenMetadata::try_from_slice(&emitted_data)?;
let mint_account = client.get_account(&mint.pubkey()).await?;
let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;
let metadata_pointer = mint_state.get_extension::<MetadataPointer>()?;
let token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
println!("Mint: {}", mint.pubkey());
println!("Metadata Pointer: {:#?}", metadata_pointer);
println!("Token Metadata: {:#?}", token_metadata);
println!("Emitted Token Metadata: {:#?}", emitted_token_metadata);
Ok(())
}
Console
Click to execute the code.

Is this page helpful?

Spis treści

Edytuj stronę

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco