Typescript
Token2022 avec Web3.js

Token2022 avec Web3.js

L'Extension Metadata

L'extension Metadata (Métadonnées) est une extension de compte de Mint qui introduit la possibilité d'intégrer directement des métadonnées dans les comptes de Mint de manière native, sans avoir à utiliser un autre programme.

Initialisation du Compte de Mint

L'extension Metadata est un peu différente de ce à quoi nous sommes habitués car elle est composée de deux extensions différentes qui sont toutes deux associées à un compte Mint :

  • L'extension Metadata qui contient toutes les métadonnées telles que le nom, le symbole, l'URI et les comptes supplémentaires.
  • L'extension MetadataPointer qui fait référence au compte de Mint où se trouve l'extension Metadata.

En général, lorsqu'elles sont utilisées, ces 2 extensions se trouvent dans le même compte de Mint. C'est ce que nous allons faire dans cet exemple.

Commençons par quelques notions de base avant de nous plonger dans le code :

Bien que l'extension MetadataPointer se trouve dans le package @solana/spl-token, pour initialiser les Metadata, nous devons utiliser le package @solana/spl-token-metadata.

Installons donc le package nécessaire :

 
npm i @solana/spl-token-metadata

De plus, l'extension Metadata est l'une des "seules" extensions qui nécessite d'initialiser l'extension après avoir initialisé le compte de Mint.

Cela s'explique par le fait que l'instruction d'initialisation des métadonnées alloue de manière dynamique l'espace nécessaire au contenu des métadonnées de longueur variable.

Dans le même temps, cela signifie que nous allons devoir initialiser le compte de Mint avec suffisamment de lamports pour être exempt de rente avec l'extension Metadata incluse mais en allouant suffisamment d'espace uniquement pour l'extension MetadataPointer car l'instruction token_metadata_initialize() augmente automatiquement l'espace de manière correcte.

Voici à quoi cela ressemble :

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

L'instruction initialize de metadata-interface n'autorise pas des additionalMetdata. Pour cette raison, si nous voulons créer un asset qui en possède, nous devrons utiliser l'instruction token_metadata_update_field() que nous verrons dans la section suivante

Mise à Jour des Métadonnées

Il est possible de mettre à jour tous les champs des métadonnées à l'aide de la même instruction token_metadata_update_field().

Pour les additionalMetadata cela fonctionne un peu différemment car nous pouvons mettre à jour un champ existant en transmettant juste le même Field avec une nouvelle valeur ou juste ajouter un nouveau champ aux Metadata.

Le programme utilise la même instruction mais avec différents drapeaux (flag) en fonction de ce que nous essayons de modifier. Cela signifie que nous pouvons modifier tous les champs comme ceci :

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

Comme vous pouvez le constater, nous devons toujours nous assurer que le compte est exempt de rente. C'est pourquoi nous effectuons tous les calculs relatifs à la rente dès le début et transférons des lamports supplémentaires si nécessaire.

Nous pouvons également supprimer Field dans la structure additionalMetadata à l'aide de l'instruction removeKey() comme ceci :

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