Ponteiro de Metadados e Metadados de Token

O Que São as Extensões Ponteiro de Metadados e Metadados de Token?

A extensão de cunhagem MetadataPointer do Token Extensions Program armazena dois campos numa cunhagem:

  • Uma autoridade que pode atualizar o ponteiro
  • O endereço da conta que armazena os metadados do token

Esse ponteiro pode referenciar qualquer conta pertencente a um programa que implemente a interface token-metadata.

O Token Extensions Program também implementa essa mesma interface diretamente através da extensão de cunhagem TokenMetadata. Com TokenMetadata, a cunhagem armazena o name, symbol, uri, update_authority do token e metadados personalizados na própria conta de cunhagem.

URI de Metadados Off-chain

O campo uri aponta para metadados JSON off-chain. Consulte Formato de Metadados Off-chain.

As duas extensões resolvem problemas diferentes:

  • MetadataPointer especifica a conta que armazena os metadados.
  • TokenMetadata armazena os metadados diretamente na conta de cunhagem.

Extensão de Comprimento Variável

TokenMetadata é uma extensão TLV de comprimento variável. InitializeTokenMetadata, UpdateField e RemoveKey podem redimensionar os dados da conta de cunhagem, mas não transferem lamports adicionais. A conta de cunhagem precisa de lamports suficientes para permanecer isenta de renda para os metadados que estão a ser armazenados. Se os metadados crescerem mais tarde, lamports adicionais precisam de ser transferidos para a conta de cunhagem antes da instrução que a redimensiona.

Como Armazenar Metadados de Token na Conta de Cunhagem

Para armazenar metadados na conta de cunhagem:

  1. Calcule o tamanho da conta mint e o aluguel necessário para o mint, extensões e metadados.
  2. Crie a conta mint com CreateAccount, inicialize MetadataPointer e inicialize o mint com InitializeMint.
  3. Inicialize TokenMetadata na conta mint e, em seguida, use UpdateField para adicionar ou atualizar metadados.
  4. Use RemoveKey, UpdateAuthority e Emit para remover metadados personalizados, alterar ou limpar a autoridade de atualização, ou retornar os metadados atuais nos dados de retorno da transação.

Calcular tamanho da conta

Calcule o tamanho da conta mint para o mint base mais a extensão MetadataPointer. Este é o tamanho usado em CreateAccount.

Calcular aluguel

Calcule o aluguel usando o tamanho máximo necessário depois que TokenMetadata for armazenado no mint.

Criar a conta mint

Crie a conta mint com o espaço e lamports calculados.

Inicializar MetadataPointer

Inicialize MetadataPointer e defina seu endereço de metadados como o endereço do mint.

Inicializar o mint

Inicialize o mint com InitializeMint na mesma transação.

Inicializar e atualizar metadados

Inicialize TokenMetadata no mint. Use UpdateField para adicionar metadados personalizados; se o campo não existir, UpdateField o adiciona.

Atualizar, remover ou emitir metadados

Após a inicialização do mint, use UpdateField para atualizar metadados, UpdateMetadataPointer para atualizar o ponteiro de metadados, RemoveKey para remover metadados personalizados, UpdateAuthority para limpar a autoridade de atualização e Emit para retornar os metadados atuais.

Calcular tamanho da conta

Calcule o tamanho da conta mint para o mint base mais a extensão MetadataPointer. Este é o tamanho usado em CreateAccount.

Calcular aluguel

Calcule o aluguel usando o tamanho máximo necessário depois que TokenMetadata for armazenado no mint.

Criar a conta mint

Crie a conta mint com o espaço e lamports calculados.

Inicializar MetadataPointer

Inicialize MetadataPointer e defina seu endereço de metadados como o endereço do mint.

Inicializar o mint

Inicialize o mint com InitializeMint na mesma transação.

Inicializar e atualizar metadados

Inicialize TokenMetadata no mint. Use UpdateField para adicionar metadados personalizados; se o campo não existir, UpdateField o adiciona.

Atualizar, remover ou emitir metadados

Após a inicialização do mint, use UpdateField para atualizar metadados, UpdateMetadataPointer para atualizar o ponteiro de metadados, RemoveKey para remover metadados personalizados, UpdateAuthority para limpar a autoridade de atualização e Emit para retornar os metadados atuais.

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

Ordem das Instruções

MetadataPointerInstruction::Initialize deve vir antes de InitializeMint. CreateAccount, MetadataPointerInstruction::Initialize e InitializeMint devem ser incluídos na mesma transação.

Referência da Fonte

Ponteiro de Metadados

ItemDescriçãoFonte
MetadataPointerExtensão de mint que armazena a autoridade do ponteiro de metadados e o endereço da conta de metadados.Fonte
MetadataPointerInstruction::InitializeInicializa a extensão de ponteiro de metadados antes de InitializeMint.Fonte
MetadataPointerInstruction::UpdateAtualiza o endereço de metadados armazenado pela extensão de ponteiro de metadados do mint.Fonte
process_initialize (MetadataPointer)Lógica do processador de ponteiro de metadados que requer pelo menos uma autoridade ou um endereço de metadados durante a inicialização.Fonte
process_update (MetadataPointer)Valida a autoridade do ponteiro de metadados e reescreve o endereço de metadados armazenado no mint.Fonte

Metadados de Token

ItemDescriçãoFonte
TokenMetadataEstado de interface de metadados de token de comprimento variável armazenado em uma entrada TLV.Fonte
FieldEnum de campo usado por UpdateField para segmentar name, symbol, uri ou uma chave personalizada.Fonte
TokenMetadataInstruction::InitializeInicializa os campos base name, symbol e uri para uma conta de metadados de token.Fonte
TokenMetadataInstruction::UpdateFieldAdiciona ou atualiza um campo base ou campo de metadados personalizado nos metadados do token.Fonte
TokenMetadataInstruction::RemoveKeyRemove uma chave de metadados personalizada. Campos base não podem ser removidos com esta instrução.Fonte
TokenMetadataInstruction::UpdateAuthorityAlterna a autoridade de atualização de metadados ou a limpa completamente para tornar os metadados imutáveis.Fonte
TokenMetadataInstruction::EmitRetorna os metadados serializados através dos dados de retorno da transação, opcionalmente para um intervalo de bytes.Fonte
process_initialize (TokenMetadata)Lógica do processador de metadados de token que requer que a conta de metadados seja o próprio mint e que o mint tenha MetadataPointer.Fonte
process_update_fieldRealoca e reescreve a entrada TLV de TokenMetadata de comprimento variável após uma atualização de campo.Fonte
process_remove_keyValida a autoridade de atualização, remove uma chave de metadados personalizada e reescreve a entrada TLV.Fonte
process_update_authorityValida a autoridade de atualização atual e alterna ou limpa a autoridade de metadados no local.Fonte
process_emitLê o TokenMetadata serializado do mint e grava o segmento solicitado nos dados de retorno.Fonte

Typescript

O exemplo abaixo usa as instruções geradas diretamente. Exemplos legados usando @solana/web3.js, @solana/spl-token e @solana/spl-token-metadata estão incluídos para referência.

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?

Índice

Editar Página

Gerenciado por

© 2026 Fundação Solana.
Todos os direitos reservados.
Conecte-se
  • Blog