Typescript
Token2022 com Web3.js

Token2022 com Web3.js

A Extensão Metadata

A extensão Metadata é uma extensão de conta Mint que introduz a capacidade de embutir metadata diretamente nas contas mint nativamente e sem precisar usar outro programa.

Inicializando a Conta Mint

A extensão Metadata é um pouco diferente do que estamos acostumados a fazer porque é composta de 2 extensões diferentes que ambas vão em uma conta Mint:

  • A extensão Metadata que contém toda a informação de metadata como nome, símbolo, uri e contas adicionais.

  • A extensão MetadataPointer que referência a conta Mint onde a extensão Metadata reside.

Normalmente, quando usadas, estas 2 extensões residem na mesma conta Mint; e vamos fazer o mesmo para este exemplo.

Vamos começar com alguns conceitos básicos antes de mergulhar no código:

Enquanto a extensão MetadataPointer reside no pacote @solana/spl-token, para inicializar o Metadata precisamos usar o pacote @solana/spl-token-metadata.

Então vamos instalar o pacote necessário:

text
npm i @solana/spl-token-metadata

Adicionalmente, a extensão Metadata é uma das "únicas" extensões que exige que você inicialize a extensão após ter inicializado a conta Mint.

Isso porque a instrução de inicialização de metadata aloca dinamicamente o espaço necessário para o conteúdo de metadata de comprimento variável.

Ao mesmo tempo, isso significa que vamos precisar inicializar a conta Mint com lamports suficientes para ser isenta de aluguel com a extensão Metadata incluída, mas alocando espaço suficiente apenas para a extensão MetadataPointer, já que a instrução token_metadata_initialize() realmente aumenta o espaço corretamente.

No código, isso fica assim:

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"]],
};

// Tamanho da conta Mint com extensões
const mintLen = getMintLen([ExtensionType.MetadataPointer]);

// Tamanho da Extensão Metadata
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;

// Lamports mínimos necessários para a conta Mint
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 criada! Veja sua TX aqui: https://explorer.solana.com/tx/${signature}?cluster=devnet`);

A instrução initialize para a metadata-interface não permite additionalMetadata. Por esta razão, se queremos criar um ativo que as tenha, vamos precisar usar a instrução token_metadata_update_field() que vamos ver na próxima seção.

Atualizando a Metadata

É possível atualizar todos os campos da metadata usando a mesma instrução token_metadata_update_field().

Para o additionalMetadata isso funciona um pouco diferente porque podemos atualizar um campo existente apenas passando o mesmo Field com um novo valor ou simplesmente adicionar um novo campo ao Metadata.

Por baixo dos panos, o programa usa a mesma instrução com flag diferente baseada no que estamos tentando mudar. Isso significa que podemos mudar todos os campos assim:

ts
const newMetadata: TokenMetadata = {
    mint: mint.publicKey,
    name: "New Name",
    symbol: "TST2",
    uri: "https://example.com/metadata2.json",
    additionalMetadata: [
        ["customField1", "customValue1"],
        ["customField2", "customValue2"],
    ],
};

// Tamanho da conta Mint com extensões
const mintLen = getMintLen([ExtensionType.MetadataPointer]);

// Tamanho da Extensão Metadata
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(newMetadata).length;

// Lamports mínimos necessários para a conta Mint
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen + metadataLen);

// Obter o saldo antigo do keypair
const oldBalance = await connection.getBalance(mint.publicKey)

console.log(`Saldo antigo: ${oldBalance}`);
console.log(`Lamports: ${lamports}`);

// Adicionar lamports à Mint se necessário para cobrir a nova isenção de aluguel da metadata
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 adicionados à Mint! Veja sua TX aqui: 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 atualizada! Veja sua TX aqui: https://explorer.solana.com/tx/${signature}?cluster=devnet`);

Como você pode ver, sempre precisamos ter certeza de que a conta está isenta de aluguel, é por isso que fazemos todos os cálculos de aluguel no início e transferimos lamports adicionais se necessário.

Podemos remover Field na struct additionalMetadata também usando a instrução RemoveKey assim:

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