مؤشر البيانات الوصفية وبيانات التوكن الوصفية

ما هي امتدادات مؤشر البيانات الوصفية وبيانات التوكن الوصفية؟

يخزن امتداد الإصدار MetadataPointer في برنامج Token Extensions حقلين على حساب الإصدار:

  • صلاحية يمكنها تحديث المؤشر
  • عنوان الحساب الذي يخزن البيانات الوصفية للتوكن

يمكن لذلك المؤشر أن يشير إلى أي حساب مملوك لبرنامج ينفذ واجهة token-metadata.

ينفذ Token Extensions Program أيضاً نفس الواجهة مباشرة من خلال امتداد الإصدار TokenMetadata. مع TokenMetadata، يخزن حساب الإصدار حقول name وsymbol وuri وupdate_authority والبيانات الوصفية المخصصة للتوكن على حساب الإصدار نفسه.

رابط البيانات الوصفية خارج السلسلة

يشير حقل uri إلى بيانات JSON الوصفية خارج السلسلة. راجع تنسيق البيانات الوصفية خارج السلسلة.

يحل كل من الامتدادين مشكلة مختلفة:

  • يحدد MetadataPointer الحساب الذي يخزن البيانات الوصفية.
  • يخزن TokenMetadata البيانات الوصفية مباشرة على حساب الإصدار.

امتداد متغير الطول

TokenMetadata هو امتداد TLV متغير الطول. يمكن لـ InitializeTokenMetadata و_rsUpdateField_ و_rsRemoveKey_ تغيير حجم بيانات حساب الإصدار، لكنها لا تنقل لامبورت إضافية. يحتاج حساب الإصدار إلى لامبورت كافية للبقاء معفياً من الإيجار للبيانات الوصفية المخزنة. إذا نمت البيانات الوصفية لاحقاً، يجب نقل لامبورت إضافية إلى حساب الإصدار قبل التعليمة التي تغير حجمه.

كيفية تخزين البيانات الوصفية للتوكن على حساب الإصدار

لتخزين البيانات الوصفية على حساب الإصدار:

  1. احسب حجم حساب السك والإيجار المطلوب للسك والإضافات والبيانات الوصفية.
  2. أنشئ حساب السك باستخدام CreateAccount، وقم بتهيئة MetadataPointer، ثم قم بتهيئة السك باستخدام InitializeMint.
  3. قم بتهيئة TokenMetadata على حساب السك، ثم استخدم UpdateField لإضافة أو تحديث البيانات الوصفية.
  4. استخدم RemoveKey وUpdateAuthority وEmit لإزالة البيانات الوصفية المخصصة، أو تغيير أو مسح صلاحية التحديث، أو إرجاع البيانات الوصفية الحالية في بيانات إرجاع المعاملة.

احسب حجم الحساب

احسب حجم حساب السك للسك الأساسي بالإضافة إلى إضافة MetadataPointer. هذا هو الحجم المستخدم في CreateAccount.

احسب الإيجار

احسب الإيجار باستخدام الحجم الأقصى المطلوب بعد تخزين TokenMetadata على السك.

أنشئ حساب السك

أنشئ حساب السك بالمساحة والـ lamports المحسوبة.

قم بتهيئة MetadataPointer

قم بتهيئة MetadataPointer وعيّن عنوان البيانات الوصفية الخاص به إلى عنوان السك.

قم بتهيئة السك

قم بتهيئة السك باستخدام InitializeMint في نفس المعاملة.

قم بتهيئة وتحديث البيانات الوصفية

قم بتهيئة TokenMetadata على السك. استخدم UpdateField لإضافة بيانات وصفية مخصصة؛ إذا لم يكن الحقل موجوداً، فإن UpdateField يضيفه.

تحديث أو إزالة أو إصدار البيانات الوصفية

بعد تهيئة السك، استخدم UpdateField لتحديث البيانات الوصفية، UpdateMetadataPointer لتحديث مؤشر البيانات الوصفية، RemoveKey لإزالة البيانات الوصفية المخصصة، UpdateAuthority لمسح صلاحية التحديث، و Emit لإرجاع البيانات الوصفية الحالية.

احسب حجم الحساب

احسب حجم حساب السك للسك الأساسي بالإضافة إلى إضافة MetadataPointer. هذا هو الحجم المستخدم في CreateAccount.

احسب الإيجار

احسب الإيجار باستخدام الحجم الأقصى المطلوب بعد تخزين TokenMetadata على السك.

أنشئ حساب السك

أنشئ حساب السك بالمساحة والـ lamports المحسوبة.

قم بتهيئة MetadataPointer

قم بتهيئة MetadataPointer وعيّن عنوان البيانات الوصفية الخاص به إلى عنوان السك.

قم بتهيئة السك

قم بتهيئة السك باستخدام InitializeMint في نفس المعاملة.

قم بتهيئة وتحديث البيانات الوصفية

قم بتهيئة TokenMetadata على السك. استخدم UpdateField لإضافة بيانات وصفية مخصصة؛ إذا لم يكن الحقل موجوداً، فإن UpdateField يضيفه.

تحديث أو إزالة أو إصدار البيانات الوصفية

بعد تهيئة السك، استخدم UpdateField لتحديث البيانات الوصفية، UpdateMetadataPointer لتحديث مؤشر البيانات الوصفية، RemoveKey لإزالة البيانات الوصفية المخصصة، UpdateAuthority لمسح صلاحية التحديث، و Emit لإرجاع البيانات الوصفية الحالية.

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

ترتيب التعليمات

يجب أن تأتي MetadataPointerInstruction::Initialize قبل InitializeMint. يجب تضمين CreateAccount، MetadataPointerInstruction::Initialize، و InitializeMint في نفس المعاملة.

المرجع المصدري

مؤشر البيانات الوصفية

العنصرالوصفالمصدر
MetadataPointerامتداد السك الذي يخزن صلاحية مؤشر البيانات الوصفية وعنوان حساب البيانات الوصفية.المصدر
MetadataPointerInstruction::Initializeيهيئ امتداد مؤشر البيانات الوصفية قبل InitializeMint.المصدر
MetadataPointerInstruction::Updateيحدث عنوان البيانات الوصفية المخزن بواسطة امتداد مؤشر البيانات الوصفية للسك.المصدر
process_initialize (MetadataPointer)منطق معالج مؤشر البيانات الوصفية الذي يتطلب صلاحية أو عنوان بيانات وصفية على الأقل أثناء التهيئة.المصدر
process_update (MetadataPointer)يتحقق من صلاحية مؤشر البيانات الوصفية، ثم يعيد كتابة عنوان البيانات الوصفية المخزن في السك.المصدر

البيانات الوصفية للرمز

العنصرالوصفالمصدر
TokenMetadataحالة واجهة البيانات الوصفية للرمز ذات الطول المتغير المخزنة في إدخال TLV.المصدر
Fieldتعداد الحقل المستخدم بواسطة UpdateField لاستهداف name، symbol، uri، أو مفتاح مخصص.المصدر
TokenMetadataInstruction::Initializeيهيئ حقول name، symbol، و uri الأساسية لحساب البيانات الوصفية للرمز.المصدر
TokenMetadataInstruction::UpdateFieldيضيف أو يحدّث حقلاً أساسياً أو حقل بيانات وصفية مخصص على البيانات الوصفية للرمز.المصدر
TokenMetadataInstruction::RemoveKeyيزيل مفتاح بيانات وصفية مخصص. لا يمكن إزالة الحقول الأساسية بهذه التعليمة.المصدر
TokenMetadataInstruction::UpdateAuthorityيغير صلاحية تحديث البيانات الوصفية، أو يمسحها بالكامل لجعل البيانات الوصفية غير قابلة للتغيير.المصدر
TokenMetadataInstruction::Emitيرجع البيانات الوصفية المتسلسلة من خلال بيانات إرجاع المعاملة، اختيارياً لنطاق بايتات معين.المصدر
process_initialize (TokenMetadata)منطق معالج البيانات الوصفية للرمز الذي يتطلب أن يكون حساب البيانات الوصفية هو السك نفسه وأن يحتوي السك على MetadataPointer.المصدر
process_update_fieldيعيد تخصيص ويعيد كتابة إدخال TokenMetadata TLV ذي الطول المتغير بعد تحديث الحقل.المصدر
process_remove_keyيتحقق من صلاحية التحديث، يزيل مفتاح بيانات وصفية مخصص، ويعيد كتابة إدخال TLV.المصدر
process_update_authorityيتحقق من صلاحية التحديث الحالية، ثم يغير أو يمسح صلاحية البيانات الوصفية في مكانها.المصدر
process_emitيقرأ TokenMetadata المتسلسل من السك ويكتب الجزء المطلوب إلى بيانات الإرجاع.المصدر

تايب سكريبت

يستخدم المثال Kit أدناه التعليمات المُنشأة مباشرةً. تم تضمين الأمثلة القديمة التي تستخدم @solana/web3.js و @solana/spl-token و @solana/spl-token-metadata للرجوع إليها.

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?

تدار بواسطة

© 2026 مؤسسة سولانا.
جميع الحقوق محفوظة.
تواصل معنا