Typescript
使用Web3.js的Token2022

使用Web3.js的Token2022

元數據擴展

Metadata 擴展是一個 Mint 賬戶擴展,允許將元數據直接嵌入到鑄幣賬戶中,無需使用其他程序。

初始化鑄幣賬戶

Metadata 擴展與我們習慣的操作有些不同,因為它由兩個不同的擴展組成,並且都應用於 Mint 賬戶:

  • 包含所有元數據信息(如名稱、符號、URI 和附加賬戶)的 Metadata 擴展。

  • 引用 Mint 賬戶的 MetadataPointer 擴展,其中 Metadata 擴展存在。

通常,當使用時,這兩個擴展都存在於同一個 Mint 賬戶中;在這個例子中,我們也會這樣做。

在深入代碼之前,讓我們先了解一些基礎知識:

雖然 MetadataPointer 擴展位於 @solana/spl-token 包中,但要初始化 Metadata,我們需要使用 @solana/spl-token-metadata 包。

因此,讓我們安裝所需的包:

text
npm i @solana/spl-token-metadata

此外,Metadata 擴展是少數需要在初始化 Mint 賬戶後再初始化的擴展之一。

這是因為元數據初始化指令會動態分配可變長度元數據內容所需的空間。

同時,這意味著我們需要為 Mint 賬戶分配足夠的 lamports,以使其在包含 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`,
);
Expand
[72 more lines]

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

如你所見,我們始終需要確保帳戶是免租金的,這就是為什麼我們在開始時進行所有有關租金的計算,並在需要時轉移額外的 lamports。

我們也可以使用 RemoveKey 指令從 additionalMetadata 結構中移除 Field,如下所示:

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