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`,
);
Expand
[72 more lines]

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`,
);
Expand
[74 more lines]

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: 3c44267