Metadata Pointer e Token Metadata

Cosa Sono le Estensioni Metadata Pointer e Token Metadata?

L'estensione mint MetadataPointer del Token Extensions Program memorizza due campi su un mint:

  • Un'autorità che può aggiornare il puntatore
  • L'indirizzo dell'account che memorizza i metadati del token

Tale puntatore può fare riferimento a qualsiasi account di proprietà di un programma che implementa l'interfaccia token-metadata.

Il Token Extensions Program implementa anche la stessa interfaccia direttamente attraverso l'estensione mint TokenMetadata. Con TokenMetadata, il mint memorizza name, symbol, uri, update_authority e metadati personalizzati direttamente sull'account mint stesso.

URI dei Metadati Off-chain

Il campo uri punta ai metadati JSON off-chain. Vedi Formato dei Metadati Off-chain.

Le due estensioni risolvono problemi diversi:

  • MetadataPointer specifica l'account che memorizza i metadati.
  • TokenMetadata memorizza i metadati direttamente sull'account mint.

Estensione a Lunghezza Variabile

TokenMetadata è un'estensione TLV a lunghezza variabile. InitializeTokenMetadata, UpdateField e RemoveKey possono ridimensionare i dati dell'account mint, ma non trasferiscono lamport aggiuntivi. L'account mint necessita di sufficienti lamport per rimanere esente da affitto per i metadati memorizzati. Se i metadati crescono successivamente, è necessario trasferire lamport aggiuntivi all'account mint prima dell'istruzione che lo ridimensiona.

Come Memorizzare i Metadati del Token sull'Account Mint

Per memorizzare i metadati sull'account mint:

  1. Calcola la dimensione dell'account mint e il rent necessario per il mint, le estensioni e i metadati.
  2. Crea l'account mint con CreateAccount, inizializza MetadataPointer, e inizializza il mint con InitializeMint.
  3. Inizializza TokenMetadata sull'account mint, quindi utilizza UpdateField per aggiungere o aggiornare i metadati.
  4. Utilizza RemoveKey, UpdateAuthority, e Emit per rimuovere i metadati personalizzati, modificare o cancellare l'autorità di aggiornamento, o restituire i metadati correnti nei dati di ritorno della transazione.

Calcola la dimensione dell'account

Calcola la dimensione dell'account mint per il mint di base più l'estensione MetadataPointer. Questa è la dimensione utilizzata in CreateAccount.

Calcola il rent

Calcola il rent utilizzando la dimensione massima necessaria dopo che TokenMetadata viene memorizzato sul mint.

Crea l'account mint

Crea l'account mint con lo spazio e i lamport calcolati.

Inizializza MetadataPointer

Inizializza MetadataPointer e imposta il suo indirizzo dei metadati all'indirizzo del mint.

Inizializza il mint

Inizializza il mint con InitializeMint nella stessa transazione.

Inizializza e aggiorna i metadati

Inizializza TokenMetadata sul mint. Utilizza UpdateField per aggiungere metadati personalizzati; se il campo non esiste, UpdateField lo aggiunge.

Aggiornare, rimuovere o emettere metadati

Dopo l'inizializzazione del mint, utilizzare UpdateField per aggiornare i metadati, UpdateMetadataPointer per aggiornare il puntatore dei metadati, RemoveKey per rimuovere metadati personalizzati, UpdateAuthority per cancellare l'autorità di aggiornamento e Emit per restituire i metadati correnti.

Calcola la dimensione dell'account

Calcola la dimensione dell'account mint per il mint di base più l'estensione MetadataPointer. Questa è la dimensione utilizzata in CreateAccount.

Calcola il rent

Calcola il rent utilizzando la dimensione massima necessaria dopo che TokenMetadata viene memorizzato sul mint.

Crea l'account mint

Crea l'account mint con lo spazio e i lamport calcolati.

Inizializza MetadataPointer

Inizializza MetadataPointer e imposta il suo indirizzo dei metadati all'indirizzo del mint.

Inizializza il mint

Inizializza il mint con InitializeMint nella stessa transazione.

Inizializza e aggiorna i metadati

Inizializza TokenMetadata sul mint. Utilizza UpdateField per aggiungere metadati personalizzati; se il campo non esiste, UpdateField lo aggiunge.

Aggiornare, rimuovere o emettere metadati

Dopo l'inizializzazione del mint, utilizzare UpdateField per aggiornare i metadati, UpdateMetadataPointer per aggiornare il puntatore dei metadati, RemoveKey per rimuovere metadati personalizzati, UpdateAuthority per cancellare l'autorità di aggiornamento e Emit per restituire i metadati correnti.

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

Ordine delle istruzioni

MetadataPointerInstruction::Initialize deve precedere InitializeMint. CreateAccount, MetadataPointerInstruction::Initialize e InitializeMint devono essere inclusi nella stessa transazione.

Riferimento sorgente

Puntatore metadati

ElementoDescrizioneSorgente
MetadataPointerEstensione del mint che memorizza l'autorità del puntatore metadati e l'indirizzo dell'account metadati.Sorgente
MetadataPointerInstruction::InitializeInizializza l'estensione del puntatore metadati prima di InitializeMint.Sorgente
MetadataPointerInstruction::UpdateAggiorna l'indirizzo dei metadati memorizzato dall'estensione del puntatore metadati del mint.Sorgente
process_initialize (MetadataPointer)Logica del processore del puntatore metadati che richiede almeno un'autorità o un indirizzo metadati durante l'inizializzazione.Sorgente
process_update (MetadataPointer)Convalida l'autorità del puntatore metadati, quindi riscrive l'indirizzo metadati memorizzato nel mint.Sorgente

Metadati token

ElementoDescrizioneSorgente
TokenMetadataStato dell'interfaccia dei metadati token a lunghezza variabile memorizzato in una voce TLV.Sorgente
FieldEnum di campo utilizzato da UpdateField per indirizzare name, symbol, uri o una chiave personalizzata.Sorgente
TokenMetadataInstruction::InitializeInizializza i campi base name, symbol e uri per un account di metadati token.Sorgente
TokenMetadataInstruction::UpdateFieldAggiunge o aggiorna un campo base o un campo metadati personalizzato sui metadati del token.Sorgente
TokenMetadataInstruction::RemoveKeyRimuove una chiave metadati personalizzata. I campi base non possono essere rimossi con questa istruzione.Sorgente
TokenMetadataInstruction::UpdateAuthorityRuota l'autorità di aggiornamento dei metadati o la cancella completamente per rendere i metadati immutabili.Sorgente
TokenMetadataInstruction::EmitRestituisce i metadati serializzati tramite i dati di ritorno della transazione, opzionalmente per un intervallo di byte.Sorgente
process_initialize (TokenMetadata)Logica del processore dei metadati token che richiede che l'account metadati sia il mint stesso e che il mint abbia MetadataPointer.Sorgente
process_update_fieldRialloca e riscrive la voce TLV TokenMetadata a lunghezza variabile dopo l'aggiornamento di un campo.Sorgente
process_remove_keyConvalida l'autorità di aggiornamento, rimuove una chiave metadati personalizzata e riscrive la voce TLV.Sorgente
process_update_authorityConvalida l'autorità di aggiornamento corrente, quindi ruota o cancella l'autorità metadati sul posto.Sorgente
process_emitLegge i TokenMetadata serializzati dal mint e scrive la porzione richiesta nei dati di ritorno.Sorgente

Typescript

L'esempio Kit riportato di seguito utilizza direttamente le istruzioni generate. Gli esempi legacy che utilizzano @solana/web3.js, @solana/spl-token e @solana/spl-token-metadata sono inclusi per riferimento.

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?

Indice dei contenuti

Modifica pagina

Gestito da

© 2026 Solana Foundation.
Tutti i diritti riservati.
Resta connesso