메타데이터 포인터 및 토큰 메타데이터 확장이란?
Token Extensions Program의 MetadataPointer 민트 확장은 민트에 두 가지
필드를 저장합니다:
- 포인터를 업데이트할 수 있는 권한
- 토큰의 메타데이터를 저장하는 계정의 주소
이 포인터는 token-metadata 인터페이스를 구현하는 프로그램이 소유한 모든 계정을 참조할 수 있습니다.
Token Extensions Program은 TokenMetadata 민트 확장을 통해 동일한
인터페이스를 직접 구현합니다. *rsTokenMetadata*를 사용하면 민트는 토큰의
name, symbol, uri, update_authority 및 사용자 지정
메타데이터를 민트 계정 자체에 저장합니다.
오프체인 메타데이터 URI
uri 필드는 오프체인 JSON 메타데이터를 가리킵니다. 오프체인 메타데이터
형식을 참조하세요.
두 확장은 서로 다른 문제를 해결합니다:
- *rs
MetadataPointer*는 메타데이터를 저장하는 계정을 지정합니다. - *rs
TokenMetadata*는 메타데이터를 민트 계정에 직접 저장합니다.
가변 길이 확장
_rsTokenMetadata_는 가변 길이 TLV 확장입니다. InitializeTokenMetadata,
UpdateField, _rsRemoveKey_는 민트 계정 데이터의 크기를 조정할 수
있지만, 추가 lamport를 전송하지는 않습니다. 민트 계정은 저장되는 메타데이터에
대해 임대료 면제를 유지하기에 충분한 lamport를 보유해야 합니다. 메타데이터가
나중에 증가하면 크기를 조정하는 명령어 이전에 민트 계정으로 추가 lamport를
전송해야 합니다.
민트 계정에 토큰 메타데이터를 저장하는 방법
민트 계정에 메타데이터를 저장하려면:
- 민트, 확장 기능 및 메타데이터에 필요한 민트 계정 크기와 렌트를 계산합니다.
- *rs
CreateAccount*로 민트 계정을 생성하고, *rsMetadataPointer*를 초기화한 다음, *rsInitializeMint*로 민트를 초기화합니다. - 민트 계정에서 *rs
TokenMetadata*를 초기화한 후, *rsUpdateField*를 사용하여 메타데이터를 추가하거나 업데이트합니다. RemoveKey,UpdateAuthority, *rsEmit*를 사용하여 커스텀 메타데이터를 제거하거나, 업데이트 권한을 변경 또는 삭제하거나, 트랜잭션 반환 데이터에서 현재 메타데이터를 반환합니다.
계정 크기 계산
기본 민트와 MetadataPointer 확장 기능을 포함한 민트 계정 크기를
계산합니다. 이 크기는 *rsCreateAccount*에서 사용됩니다.
렌트 계산
*rsTokenMetadata*가 민트에 저장된 후 필요한 최대 크기를 사용하여 렌트를
계산합니다.
민트 계정 생성
계산된 공간과 lamport로 민트 계정을 생성합니다.
MetadataPointer 초기화
*rsMetadataPointer*를 초기화하고 해당 메타데이터 주소를 민트 주소로
설정합니다.
민트 초기화
동일한 트랜잭션에서 *rsInitializeMint*로 민트를 초기화합니다.
메타데이터 초기화 및 업데이트
민트에서 *rsTokenMetadata*를 초기화합니다. *rsUpdateField*를 사용하여 커스텀
메타데이터를 추가합니다. 필드가 존재하지 않으면 *rsUpdateField*가 이를
추가합니다.
메타데이터 업데이트, 제거 또는 발행
민트가 초기화된 후, *rsUpdateField*를 사용하여 메타데이터를 업데이트하고,
*rsUpdateMetadataPointer*를 사용하여 메타데이터 포인터를 업데이트하며,
*rsRemoveKey*를 사용하여 사용자 정의 메타데이터를 제거하고,
*rsUpdateAuthority*를 사용하여 업데이트 권한을 지우며, *rsEmit*를 사용하여
현재 메타데이터를 반환합니다.
명령어 순서
_rsMetadataPointerInstruction::Initialize_는 InitializeMint 이전에
실행되어야 합니다. CreateAccount,
MetadataPointerInstruction::Initialize, 그리고 _rsInitializeMint_는
동일한 트랜잭션에 포함되어야 합니다.
소스 참조
메타데이터 포인터
| 항목 | 설명 | 소스 |
|---|---|---|
MetadataPointer | 메타데이터 포인터 권한과 메타데이터 계정 주소를 저장하는 민트 확장입니다. | 소스 |
MetadataPointerInstruction::Initialize | InitializeMint 이전에 메타데이터 포인터 확장을 초기화합니다. | 소스 |
MetadataPointerInstruction::Update | 민트의 메타데이터 포인터 확장에 저장된 메타데이터 주소를 업데이트합니다. | 소스 |
process_initialize (MetadataPointer) | 초기화 중에 최소한 권한 또는 메타데이터 주소를 요구하는 메타데이터 포인터 프로세서 로직입니다. | 소스 |
process_update (MetadataPointer) | 메타데이터 포인터 권한을 검증한 후, 민트에 저장된 메타데이터 주소를 다시 작성합니다. | 소스 |
토큰 메타데이터
| 항목 | 설명 | 소스 |
|---|---|---|
TokenMetadata | TLV 항목에 저장된 가변 길이 토큰 메타데이터 인터페이스 상태입니다. | 소스 |
Field | *rsUpdateField*에서 name, symbol, uri 또는 사용자 정의 키를 대상으로 하는 필드 열거형입니다. | 소스 |
TokenMetadataInstruction::Initialize | 토큰 메타데이터 계정의 기본 name, symbol, 그리고 uri 필드를 초기화합니다. | 소스 |
TokenMetadataInstruction::UpdateField | 토큰 메타데이터의 기본 필드 또는 사용자 정의 메타데이터 필드를 추가하거나 업데이트합니다. | 소스 |
TokenMetadataInstruction::RemoveKey | 사용자 정의 메타데이터 키를 제거합니다. 기본 필드는 이 명령어로 제거할 수 없습니다. | 소스 |
TokenMetadataInstruction::UpdateAuthority | 메타데이터 업데이트 권한을 순환하거나 완전히 지워 메타데이터를 불변으로 만듭니다. | 소스 |
TokenMetadataInstruction::Emit | 트랜잭션 반환 데이터를 통해 직렬화된 메타데이터를 반환하며, 선택적으로 바이트 범위를 지정할 수 있습니다. | 소스 |
process_initialize (TokenMetadata) | 메타데이터 계정이 민트 자체여야 하고 민트가 *rsMetadataPointer*를 가져야 하는 토큰 메타데이터 프로세서 로직입니다. | 소스 |
process_update_field | 필드 업데이트 후 가변 길이 TokenMetadata TLV 항목을 재할당하고 다시 작성합니다. | 소스 |
process_remove_key | 업데이트 권한을 검증하고 사용자 정의 메타데이터 키를 제거한 후 TLV 항목을 다시 작성합니다. | 소스 |
process_update_authority | 현재 업데이트 권한을 검증한 후 메타데이터 권한을 제자리에서 순환하거나 지웁니다. | 소스 |
process_emit | 민트에서 직렬화된 *rsTokenMetadata*를 읽고 요청된 슬라이스를 반환 데이터에 작성합니다. | 소스 |
Typescript
아래 Kit 예제는 생성된 명령어를 직접 사용합니다. @solana/web3.js,
@solana/spl-token, @solana/spl-token-metadata를 사용하는 레거시 예제는
참고용으로 포함되어 있습니다.
Kit
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 });
Web3.js
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));
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(())}
Is this page helpful?