What Are Metadata Pointer and Token Metadata Extensions?
The Token Extension Program's MetadataPointer mint extension stores two
fields on a mint:
- An authority that can update the pointer
- The address of the account that stores the token's metadata
That pointer can reference any account owned by a program implementing the token-metadata interface.
The Token Extension Program also implements that same interface directly through
the TokenMetadata mint extension. With TokenMetadata, the mint
stores the token's name, symbol, uri, update_authority,
and custom metadata on the mint account itself.
Off-chain Metadata URI
The uri field points to off-chain JSON metadata. See Off-chain Metadata
Format.
The two extensions solve different problems:
MetadataPointerspecifies the account that stores metadata.TokenMetadatastores the metadata directly on the mint account.
Variable Length Extension
TokenMetadata is a variable length TLV extension.
InitializeTokenMetadata, UpdateField, and RemoveKey can
resize the mint account data, but they do not transfer additional lamports.
The mint account needs enough lamports to remain rent-exempt for the metadata
being stored. If the metadata grows later, additional lamports need to be
transferred to the mint account before the instruction that resizes it.
How to Store Token Metadata on the Mint Account
To store metadata on the mint account:
- Calculate the mint account size and rent needed for the mint, extensions, and metadata.
- Create the mint account with
CreateAccount, initializeMetadataPointer, and initialize the mint withInitializeMint. - Initialize
TokenMetadataon the mint account, then useUpdateFieldto add or update metadata. - Use
RemoveKey,UpdateAuthority, andEmitto remove custom metadata, change or clear the update authority, or return the current metadata in transaction return data.
Calculate account size
Calculate the mint account size for the base mint plus the MetadataPointer
extension. This is the size used in CreateAccount.
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]));
Calculate rent
Calculate rent using the maximum size needed after TokenMetadata is stored
on the mint.
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();
Create the mint account
Create the mint account with the calculated space and lamports.
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,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS})]);
Initialize MetadataPointer
Initialize MetadataPointer and set its metadata address to the mint
address.
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,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializeMetadataPointerInstruction({mint: mint.address,authority: client.payer.address,metadataAddress: mint.address})]);
Initialize the mint
Initialize the mint with InitializeMint in the same transaction.
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,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializeMetadataPointerInstruction({mint: mint.address,authority: client.payer.address,metadataAddress: mint.address}),getInitializeMintInstruction({mint: mint.address,decimals: 0,mintAuthority: client.payer.address,freezeAuthority: client.payer.address})]);
Initialize and update metadata
Initialize TokenMetadata on the mint. Use UpdateField to add custom
metadata; if the field does not exist, UpdateField adds it.
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,newAccount: mint,lamports: mintRent,space: mintSpace,programAddress: TOKEN_2022_PROGRAM_ADDRESS}),getInitializeMetadataPointerInstruction({mint: mint.address,authority: client.payer.address,metadataAddress: mint.address}),getInitializeMintInstruction({mint: mint.address,decimals: 0,mintAuthority: client.payer.address,freezeAuthority: client.payer.address}),getInitializeTokenMetadataInstruction({metadata: mint.address,updateAuthority: client.payer.address,mint: mint.address,mintAuthority: client.payer,name: "Example Token",symbol: "EXMPL",uri: "https://example.com/token.json"}),getUpdateTokenMetadataFieldInstruction({metadata: mint.address,updateAuthority: client.payer,field: tokenMetadataField("Key", ["description"]),value: "Metadata stored on mint account"})]);
Update, remove, or emit metadata
After the mint is initialized, use UpdateField to update metadata,
UpdateMetadataPointer to update the metadata pointer, RemoveKey to
remove custom metadata, UpdateAuthority to clear the update authority, and
Emit to return the current metadata.
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.})]);await client.sendTransaction([getUpdateTokenMetadataFieldInstruction({metadata: mint.address,updateAuthority: client.payer,field: tokenMetadataField("Name"),value: "Example Token v2"}),getUpdateMetadataPointerInstruction({mint: mint.address,metadataPointerAuthority: client.payer,metadataAddress: mint.address}),getRemoveTokenMetadataKeyInstruction({metadata: mint.address,updateAuthority: client.payer,key: "description"}),getUpdateTokenMetadataUpdateAuthorityInstruction({metadata: mint.address,updateAuthority: client.payer,newUpdateAuthority: null}),getEmitTokenMetadataInstruction({metadata: mint.address})]);
Instruction Order
MetadataPointerInstruction::Initialize must come before
InitializeMint. CreateAccount,
MetadataPointerInstruction::Initialize, and InitializeMint must be
included in the same transaction.
Source Reference
Metadata Pointer
| Item | Description | Source |
|---|---|---|
MetadataPointer | Mint extension that stores the metadata pointer authority and the metadata account address. | Source |
MetadataPointerInstruction::Initialize | Initializes the metadata pointer extension before InitializeMint. | Source |
MetadataPointerInstruction::Update | Updates the metadata address stored by the mint's metadata pointer extension. | Source |
process_initialize (MetadataPointer) | Metadata pointer processor logic that requires at least an authority or a metadata address during initialization. | Source |
process_update (MetadataPointer) | Validates the metadata pointer authority, then rewrites the mint's stored metadata address. | Source |
Token Metadata
| Item | Description | Source |
|---|---|---|
TokenMetadata | Variable-length token-metadata interface state stored in a TLV entry. | Source |
Field | Field enum used by UpdateField to target name, symbol, uri, or a custom key. | Source |
TokenMetadataInstruction::Initialize | Initializes the base name, symbol, and uri fields for a token-metadata account. | Source |
TokenMetadataInstruction::UpdateField | Adds or updates a base field or custom metadata field on the token metadata. | Source |
TokenMetadataInstruction::RemoveKey | Removes a custom metadata key. Base fields cannot be removed with this instruction. | Source |
TokenMetadataInstruction::UpdateAuthority | Rotates the metadata update authority, or clears it entirely to make metadata immutable. | Source |
TokenMetadataInstruction::Emit | Returns the serialized metadata through transaction return data, optionally for a byte range. | Source |
process_initialize (TokenMetadata) | Token-metadata processor logic that requires the metadata account to be the mint itself and the mint to have MetadataPointer. | Source |
process_update_field | Reallocates and rewrites the variable length TokenMetadata TLV entry after a field update. | Source |
process_remove_key | Validates the update authority, removes a custom metadata key, and rewrites the TLV entry. | Source |
process_update_authority | Validates the current update authority, then rotates or clears the metadata authority in place. | Source |
process_emit | Reads the serialized TokenMetadata from the mint and writes the requested slice to return data. | Source |
Typescript
The Kit example below uses the generated instructions directly. Legacy
examples using @solana/web3.js, @solana/spl-token, and
@solana/spl-token-metadata are included for reference.
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?