Δείκτης Μεταδεδομένων & Μεταδεδομένα Token

Τι Είναι οι Επεκτάσεις Metadata Pointer και Token Metadata;

Η επέκταση mint MetadataPointer του Token Extensions Program αποθηκεύει δύο πεδία σε ένα mint:

  • Μια εξουσιοδότηση που μπορεί να ενημερώσει τον δείκτη
  • Τη διεύθυνση του λογαριασμού που αποθηκεύει τα μεταδεδομένα του token

Αυτός ο δείκτης μπορεί να αναφέρεται σε οποιοδήποτε λογαριασμό που ανήκει σε πρόγραμμα που υλοποιεί τη διεπαφή token-metadata.

Το Token Extensions Program υλοποιεί επίσης την ίδια διεπαφή απευθείας μέσω της επέκτασης mint TokenMetadata. Με το TokenMetadata, το mint αποθηκεύει το name, το symbol, το uri, το update_authority του token, καθώς και προσαρμοσμένα μεταδεδομένα απευθείας στο λογαριασμό mint.

URI Μεταδεδομένων Εκτός Αλυσίδας

Το πεδίο uri δείχνει σε μεταδεδομένα JSON εκτός αλυσίδας. Δείτε Μορφή Μεταδεδομένων Εκτός Αλυσίδας.

Οι δύο επεκτάσεις επιλύουν διαφορετικά προβλήματα:

  • Το MetadataPointer καθορίζει τον λογαριασμό που αποθηκεύει τα μεταδεδομένα.
  • Το TokenMetadata αποθηκεύει τα μεταδεδομένα απευθείας στο λογαριασμό mint.

Επέκταση Μεταβλητού Μήκους

Το TokenMetadata είναι μια επέκταση TLV μεταβλητού μήκους. Τα InitializeTokenMetadata, UpdateField και RemoveKey μπορούν να αλλάξουν το μέγεθος των δεδομένων του λογαριασμού mint, αλλά δεν μεταφέρουν επιπλέον lamport. Ο λογαριασμός mint χρειάζεται αρκετά lamport για να παραμείνει απαλλαγμένος από ενοίκιο για τα μεταδεδομένα που αποθηκεύονται. Εάν τα μεταδεδομένα αυξηθούν αργότερα, πρέπει να μεταφερθούν επιπλέον lamport στον λογαριασμό mint πριν από την εντολή που αλλάζει το μέγεθός του.

Πώς να Αποθηκεύσετε Μεταδεδομένα Token στον Λογαριασμό Mint

Για να αποθηκεύσετε μεταδεδομένα στον λογαριασμό mint:

  1. Υπολογίστε το μέγεθος του λογαριασμού mint και το rent που απαιτείται για το mint, τις επεκτάσεις και τα μεταδεδομένα.
  2. Δημιουργήστε τον λογαριασμό mint με CreateAccount, αρχικοποιήστε το MetadataPointer και αρχικοποιήστε το mint με InitializeMint.
  3. Αρχικοποιήστε το TokenMetadata στον λογαριασμό mint και στη συνέχεια χρησιμοποιήστε το UpdateField για να προσθέσετε ή να ενημερώσετε μεταδεδομένα.
  4. Χρησιμοποιήστε τα RemoveKey, UpdateAuthority και Emit για να αφαιρέσετε προσαρμοσμένα μεταδεδομένα, να αλλάξετε ή να διαγράψετε την αρχή ενημέρωσης ή να επιστρέψετε τα τρέχοντα μεταδεδομένα στα δεδομένα επιστροφής της συναλλαγής.

Υπολογισμός μεγέθους λογαριασμού

Υπολογίστε το μέγεθος του λογαριασμού mint για το βασικό mint συν την επέκταση MetadataPointer. Αυτό είναι το μέγεθος που χρησιμοποιείται στο CreateAccount.

Υπολογισμός rent

Υπολογίστε το rent χρησιμοποιώντας το μέγιστο μέγεθος που απαιτείται αφού το TokenMetadata αποθηκευτεί στο mint.

Δημιουργία του λογαριασμού mint

Δημιουργήστε τον λογαριασμό mint με τον υπολογισμένο χώρο και τα lamport.

Αρχικοποίηση MetadataPointer

Αρχικοποιήστε το MetadataPointer και ορίστε τη διεύθυνση μεταδεδομένων του στη διεύθυνση του mint.

Αρχικοποίηση του mint

Αρχικοποιήστε το mint με το InitializeMint στην ίδια συναλλαγή.

Αρχικοποίηση και ενημέρωση μεταδεδομένων

Αρχικοποιήστε το TokenMetadata στο mint. Χρησιμοποιήστε το UpdateField για να προσθέσετε προσαρμοσμένα μεταδεδομένα· αν το πεδίο δεν υπάρχει, το UpdateField το προσθέτει.

Ενημέρωση, αφαίρεση ή εκπομπή μεταδεδομένων

Μετά την αρχικοποίηση του mint, χρησιμοποιήστε το UpdateField για να ενημερώσετε τα μεταδεδομένα, το UpdateMetadataPointer για να ενημερώσετε το metadata pointer, το RemoveKey για να αφαιρέσετε προσαρμοσμένα μεταδεδομένα, το UpdateAuthority για να διαγράψετε την εξουσία ενημέρωσης και το Emit για να επιστρέψετε τα τρέχοντα μεταδεδομένα.

Υπολογισμός μεγέθους λογαριασμού

Υπολογίστε το μέγεθος του λογαριασμού mint για το βασικό mint συν την επέκταση MetadataPointer. Αυτό είναι το μέγεθος που χρησιμοποιείται στο CreateAccount.

Υπολογισμός rent

Υπολογίστε το rent χρησιμοποιώντας το μέγιστο μέγεθος που απαιτείται αφού το TokenMetadata αποθηκευτεί στο mint.

Δημιουργία του λογαριασμού mint

Δημιουργήστε τον λογαριασμό mint με τον υπολογισμένο χώρο και τα lamport.

Αρχικοποίηση MetadataPointer

Αρχικοποιήστε το MetadataPointer και ορίστε τη διεύθυνση μεταδεδομένων του στη διεύθυνση του mint.

Αρχικοποίηση του mint

Αρχικοποιήστε το mint με το InitializeMint στην ίδια συναλλαγή.

Αρχικοποίηση και ενημέρωση μεταδεδομένων

Αρχικοποιήστε το TokenMetadata στο mint. Χρησιμοποιήστε το UpdateField για να προσθέσετε προσαρμοσμένα μεταδεδομένα· αν το πεδίο δεν υπάρχει, το UpdateField το προσθέτει.

Ενημέρωση, αφαίρεση ή εκπομπή μεταδεδομένων

Μετά την αρχικοποίηση του mint, χρησιμοποιήστε το UpdateField για να ενημερώσετε τα μεταδεδομένα, το UpdateMetadataPointer για να ενημερώσετε το metadata pointer, το 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 πρέπει να συμπεριληφθούν στην ίδια συναλλαγή.

Αναφορά Πηγής

Metadata Pointer

ΣτοιχείοΠεριγραφήΠηγή
MetadataPointerΕπέκταση mint που αποθηκεύει την εξουσία metadata pointer και τη διεύθυνση του λογαριασμού μεταδεδομένων.Πηγή
MetadataPointerInstruction::InitializeΑρχικοποιεί την επέκταση metadata pointer πριν από το InitializeMint.Πηγή
MetadataPointerInstruction::UpdateΕνημερώνει τη διεύθυνση μεταδεδομένων που αποθηκεύεται από την επέκταση metadata pointer του mint.Πηγή
process_initialize (MetadataPointer)Λογική επεξεργασίας metadata pointer που απαιτεί τουλάχιστον μία εξουσία ή μία διεύθυνση μεταδεδομένων κατά την αρχικοποίηση.Πηγή
process_update (MetadataPointer)Επικυρώνει την εξουσία metadata pointer και στη συνέχεια επαναγράφει την αποθηκευμένη διεύθυνση μεταδεδομένων του mint.Πηγή

Μεταδεδομένα Token

ΣτοιχείοΠεριγραφήΠηγή
TokenMetadataΚατάσταση διεπαφής μεταδεδομένων token μεταβλητού μήκους που αποθηκεύεται σε εγγραφή TLV.Πηγή
FieldEnum πεδίου που χρησιμοποιείται από το UpdateField για να στοχεύσει το name, symbol, uri ή ένα προσαρμοσμένο κλειδί.Πηγή
TokenMetadataInstruction::InitializeΑρχικοποιεί τα βασικά πεδία name, symbol και uri για έναν λογαριασμό μεταδεδομένων token.Πηγή
TokenMetadataInstruction::UpdateFieldΠροσθέτει ή ενημερώνει ένα βασικό πεδίο ή προσαρμοσμένο πεδίο μεταδεδομένων στα μεταδεδομένα token.Πηγή
TokenMetadataInstruction::RemoveKeyΑφαιρεί ένα προσαρμοσμένο κλειδί μεταδεδομένων. Τα βασικά πεδία δεν μπορούν να αφαιρεθούν με αυτή την εντολή.Πηγή
TokenMetadataInstruction::UpdateAuthorityΕναλλάσσει την εξουσία ενημέρωσης μεταδεδομένων ή την διαγράφει εντελώς για να καταστήσει τα μεταδεδομένα αμετάβλητα.Πηγή
TokenMetadataInstruction::EmitΕπιστρέφει τα σειριοποιημένα μεταδεδομένα μέσω δεδομένων επιστροφής συναλλαγής, προαιρετικά για ένα εύρος byte.Πηγή
process_initialize (TokenMetadata)Λογική επεξεργασίας μεταδεδομένων token που απαιτεί ο λογαριασμός μεταδεδομένων να είναι το ίδιο το mint και το mint να έχει MetadataPointer.Πηγή
process_update_fieldΕπαναδιανέμει και επαναγράφει την εγγραφή TLV μεταβλητού μήκους TokenMetadata μετά από ενημέρωση πεδίου.Πηγή
process_remove_keyΕπικυρώνει την εξουσία ενημέρωσης, αφαιρεί ένα προσαρμοσμένο κλειδί μεταδεδομένων και επαναγράφει την εγγραφή TLV.Πηγή
process_update_authorityΕπικυρώνει την τρέχουσα εξουσία ενημέρωσης και στη συνέχεια εναλλάσσει ή διαγράφει την εξουσία μεταδεδομένων επί τόπου.Πηγή
process_emitΔιαβάζει τα σειριοποιημένα TokenMetadata από το mint και γράφει το ζητούμενο τμήμα στα δεδομένα επιστροφής.Πηγή

Typescript

Το παρακάτω παράδειγμα 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 Ίδρυμα Solana.
Με επιφύλαξη παντός δικαιώματος.
Συνδεθείτε