Розширення метаданих
Розширення Metadata
є розширенням облікового запису Mint
, яке надає можливість вбудовувати метадані безпосередньо в облікові записи монет нативно та без необхідності використання іншої програми.
Initializing the Mint Account
Розширення Metadata
дещо відрізняється від того, до чого ми звикли, оскільки воно складається з 2 різних розширень, які обидва застосовуються до облікового запису Mint
:
- Розширення
Metadata
, яке містить усю інформацію метаданих, таку як назва, символ, URI та додаткові облікові записи. - Розширення
MetadataPointer
, яке посилається на обліковий записMint
, де знаходиться розширенняMetadata
.
Зазвичай, коли використовуються, ці 2 розширення знаходяться в одному обліковому записі Mint
; і ми зробимо те саме для цього прикладу.
Давайте почнемо з основ, перш ніж заглиблюватися в код:
Хоча розширення MetadataPointer
знаходиться в пакеті @solana/spl-token
, для ініціалізації Metadata
нам потрібно використовувати пакет @solana/spl-token-metadata
.
Отже, встановимо необхідний пакет:
npm i @solana/spl-token-metadata
Крім того, розширення Metadata
є одним із "єдиних" розширень, яке вимагає ініціалізації розширення після ініціалізації облікового запису Mint
.
Це тому, що інструкція ініціалізації метаданих динамічно виділяє необхідний простір для вмісту метаданих змінної довжини.
Водночас це означає, що нам потрібно буде ініціалізувати обліковий запис Mint
з достатньою кількістю лампортів, щоб бути звільненим від орендної плати з включеним розширенням Metadata
, але виділяючи достатньо місця лише для розширення MetadataPointer
, оскільки інструкція token_metadata_initialize()
фактично збільшує простір правильно.
У коді це виглядає так:
import {
Keypair,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from '@solana/web3.js';
import {
createInitializeMintInstruction,
TYPE_SIZE,
LENGTH_SIZE,
createInitializeMetadataPointerInstruction,
getMintLen,
ExtensionType,
TOKEN_2022_PROGRAM_ID,
} from '@solana/spl-token';
import {
createInitializeInstruction,
pack,
TokenMetadata
} from "@solana/spl-token-metadata";
const mint = Keypair.generate();
const metadata: TokenMetadata = {
mint: mint.publicKey,
name: "Test Token",
symbol: "TST",
uri: "https://example.com/metadata.json",
additionalMetadata: [["customField", "customValue"]],
};
// Size of Mint Account with extensions
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
// Size of the Metadata Extension
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;
// Minimum lamports required for Mint Account
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen + metadataLen);
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: keypair.publicKey,
newAccountPubkey: mint.publicKey,
space: mintLen,
lamports,
programId: TOKEN_2022_PROGRAM_ID,
});
const initializeMetadataPointer = createInitializeMetadataPointerInstruction(
mint.publicKey,
keypair.publicKey,
mint.publicKey,
TOKEN_2022_PROGRAM_ID,
);
const initializeMintInstruction = createInitializeMintInstruction(
mint.publicKey,
6,
keypair.publicKey,
null,
TOKEN_2022_PROGRAM_ID,
);
const initializeMetadataInstruction = createInitializeInstruction(
{
programId: TOKEN_2022_PROGRAM_ID,
mint: mint.publicKey,
metadata: mint.publicKey,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
mintAuthority: keypair.publicKey,
updateAuthority: keypair.publicKey,
}
);
const updateMetadataFieldInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: keypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: metadata.additionalMetadata[0][0],
value: metadata.additionalMetadata[0][1],
});
const transaction = new Transaction().add(
createAccountInstruction,
initializeMetadataPointer,
initializeMintInstruction,
initializeMetadataInstruction,
updateMetadataFieldInstructions,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair, mint]);
console.log(`Mint created! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
Оновлення метаданих
Можливо оновити всі поля метаданих, використовуючи ту саму інструкцію token_metadata_update_field()
.
Для additionalMetadata
це працює трохи інакше, оскільки ми можемо оновити існуюче поле, просто передавши той самий Field
з новим значенням, або просто додати нове поле до Metadata
.
Під капотом програма використовує ту саму інструкцію з різними прапорцями залежно від того, що ми намагаємося змінити. Це означає, що ми можемо змінити всі поля таким чином:
const newMetadata: TokenMetadata = {
mint: mint.publicKey,
name: "New Name",
symbol: "TST2",
uri: "https://example.com/metadata2.json",
additionalMetadata: [
["customField1", "customValue1"],
["customField2", "customValue2"],
],
};
// Size of Mint Account with extensions
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
// Size of the Metadata Extension
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(newMetadata).length;
// Minimum lamports required for Mint Account
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen + metadataLen);
// Get the old balance of the keypair
const oldBalance = await connection.getBalance(mint.publicKey)
console.log(`Old balance: ${oldBalance}`);
console.log(`Lamports: ${lamports}`);
// Add lamports to the Mint if needed to cover the new metadata rent exemption
if (oldBalance < lamports) {
const transferInstruction = SystemProgram.transfer({
fromPubkey: keypair.publicKey,
toPubkey: mint.publicKey,
lamports: lamports - oldBalance,
});
const transaction = new Transaction().add(transferInstruction);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair], {commitment: "finalized"});
console.log(`Lamports added to Mint! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
}
const updateMetadataNameInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: keypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: "Name", // Field | string
value: "New Name",
});
const updateMetadataSymbolInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: keypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: "Symbol", // Field | string
value: "TST2",
});
const updateMetadataUriInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: keypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: "Uri", // Field | string
value: "https://example.com/metadata2.json",
});
const updateMetadataAdditionalMetadataInstructions = createUpdateFieldInstruction({
metadata: mint.publicKey,
updateAuthority: keypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
field: "customField2", // Field | string
value: "customValue2",
});
const transaction = new Transaction().add(
updateMetadataNameInstructions,
updateMetadataSymbolInstructions,
updateMetadataUriInstructions,
updateMetadataAdditionalMetadataInstructions,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair]);
console.log(`Metadata updated! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
Ми також можемо видалити Field
у структурі additionalMetadata
, використовуючи інструкцію RemoveKey
ось так:
const removeMetadataKeyInstructions = createRemoveKeyInstruction({
metadata: mint.publicKey,
updateAuthority: keypair.publicKey,
programId: TOKEN_2022_PROGRAM_ID,
key: "customField", // Field | string
idempotent: true,
});