Typescript
Token2022 з Web3.js

Token2022 з Web3.js

Розширення метаданих

Розширення 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() фактично збільшує простір правильно.

У коді це виглядає так:

ts
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`);

Інструкція initialize для metadata-interface не дозволяє використовувати additionalMetadata. З цієї причини, якщо ми хочемо створити актив, який має це, нам потрібно використовувати інструкцію token_metadata_update_field(), яку ми розглянемо в наступному розділі.

Оновлення метаданих

Можливо оновити всі поля метаданих, використовуючи ту саму інструкцію token_metadata_update_field().

Для additionalMetadata це працює трохи інакше, оскільки ми можемо оновити існуюче поле, просто передавши той самий Field з новим значенням, або просто додати нове поле до Metadata.

Під капотом програма використовує ту саму інструкцію з різними прапорцями залежно від того, що ми намагаємося змінити. Це означає, що ми можемо змінити всі поля таким чином:

ts
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 ось так:

ts
const removeMetadataKeyInstructions = createRemoveKeyInstruction({
    metadata: mint.publicKey,
    updateAuthority: keypair.publicKey,
    programId: TOKEN_2022_PROGRAM_ID,
    key: "customField", // Field | string
    idempotent: true,
});
Blueshift © 2025Commit: 6d01265