0

I've tried creating a mint account with metadata stored on the mint account using the metadata pointer extension and the metadata interface, but I'm not sure how to read the metadata from the mint account.

@solana/spl-token-metadata includes an unpack helper function, but I'm not sure where the metadata starts in mintInfo.tlvData.

Also, is there a recommended way to correctly calculate the size of the mint account with metadata when initializing/reallocating the data?

import {
  Connection,
  Keypair,
  SystemProgram,
  Transaction,
  clusterApiUrl,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
  ExtensionType,
  TOKEN_2022_PROGRAM_ID,
  createInitializeMintInstruction,
  getMintLen,
  createInitializeMetadataPointerInstruction,
  getMint,
  getMetadataPointerState,
} from "@solana/spl-token";
import { getOrCreateKeypair } from "./utils";
import {
  createInitializeInstruction,
  createUpdateFieldInstruction,
  unpack,
} from "@solana/spl-token-metadata";

const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

// localkeypair
const payer = getOrCreateKeypair("wallet_1");

let transactionSignature: string;

const mintKeypair = Keypair.generate();
const mint = mintKeypair.publicKey;
const decimals = 9;
const mintAuthority = payer;

const mintLen = getMintLen([ExtensionType.MetadataPointer]);
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);

const createAccountInstruction = SystemProgram.createAccount({
  fromPubkey: payer.publicKey, // payer
  newAccountPubkey: mint, // new account
  space: mintLen, // size of account
  lamports: lamports * 2, // temporary x2 to account for metadata bytes
  programId: TOKEN_2022_PROGRAM_ID,
});

const initializeMetadataPointerInstruction =
  createInitializeMetadataPointerInstruction(
    mint,
    payer.publicKey, // update authority
    mint, // use mint as metadata address
    TOKEN_2022_PROGRAM_ID
  );

const initializeMintInstruction = createInitializeMintInstruction(
  mint,
  decimals,
  mintAuthority.publicKey, // mint authority
  null, // freeze authority
  TOKEN_2022_PROGRAM_ID
);

const initializeMetadataInstruction = createInitializeInstruction({
  programId: TOKEN_2022_PROGRAM_ID, // token 2022 as metadata program
  metadata: mint, // use mint as metadata address
  updateAuthority: payer.publicKey,
  mint: mint,
  mintAuthority: payer.publicKey,
  name: "Name",
  symbol: "Symbol",
  uri: "http://hello.world",
});

const updateFieldInstruction = createUpdateFieldInstruction({
  programId: TOKEN_2022_PROGRAM_ID, // token 2022 as metadata program
  metadata: mint, // use mint as metadata address
  updateAuthority: payer.publicKey,
  field: "Custom Field",
  value: "Custom Value",
});

const transaction = new Transaction().add(
  createAccountInstruction,
  initializeMetadataPointerInstruction,
  initializeMintInstruction,
  initializeMetadataInstruction,
  updateFieldInstruction
);

transactionSignature = await sendAndConfirmTransaction(
  connection,
  transaction,
  [payer, mintKeypair]
);

console.log(
  "\n",
  "Create Mint Account:",
  `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);

const mintInfo = await getMint(
  connection,
  mint,
  "confirmed",
  TOKEN_2022_PROGRAM_ID
);
const metadataPointer = getMetadataPointerState(mintInfo);
console.log("Metadata Pointer:", JSON.stringify(metadataPointer, null, 2));

console.log("Mint Data:", mintInfo.tlvData);

2 Answers 2

2

At the time of this writing, the Token Metadata extension for @solana/spl-token is still in development.

However, once it's complete you'll be able to do this:

const tokenMetadata = getTokenMetadataState(mintInfo);

Although manually iterating over the TLV data of a mint may seem like a viable alternative in the meantime, please be aware that you could end up unpacking unrelated bytes.

Functions such as getExtensionData(..) are designed specifically to iteratively move over a TLV-encoded data buffer and ensure once the proper discriminator is found it's in the proper position.

For example, if the discriminator for TokenMetadata is 19 and you have a buffer of length 1000, imagine the chaos if you unpacked TokenMetadata at every 19 you found!

1
  • This is live as of 0.3.10!
    – Joe C
    Jan 16 at 5:21
0

Offset is 72 for where the metadata starts for this specific example. Tried unpacking at each index until found.

function findValidUnpackIndex(tlvData) {
  for (let i = 0; i < tlvData.length; i++) {
    try {
      // Try to unpack starting from index 'i'
      const metadata = unpack(tlvData.slice(i));

      // If unpacking is successful, log the metadata and return the index
      console.log("Successful unpack at index:", i);
      console.log("Metadata:", JSON.stringify(metadata, null, 2));
      return i;
    } catch (error) {
      // If an error occurs, continue to the next index
      console.log("Unpack failed at index:", i, "Error:", error.message);
    }
  }
  // If no successful unpacking, return an indication
  return -1;
}

const validIndex = findValidUnpackIndex(mintInfo.tlvData);
console.log("Valid unpack index is:", validIndex);

Which logs:

Successful unpack at index: 72
Metadata: {
  "updateAuthority": "JA5m9q9aaFKuHvKcvAQW5Pu6kUWdPcX7BXQxmx6krriW",
  "mint": "Gz72Sd3EMJFj7vf9KfuSzLbRbcqHRJH1s395XRBE6m8s",
  "name": "Name",
  "symbol": "Symbol",
  "uri": "http://hello.world",
  "additionalMetadata": [
    [
      "Custom Field",
      "Custom Value"
    ]
  ]
}

Here's how you can calculate the correct size for a mint with metadata to then determine the lamports required for rent.

const metaData: TokenMetadata = {
  updateAuthority: payer.publicKey,
  mint: mint,
  name: "name",
  symbol: "symbol",
  uri: "uri",
  additionalMetadata: [["key1", "value1"]],
};

// 2 bytes for type, 2 bytes for length
const metadataExtension = 4;
// length of metadata
const metadataLen = pack(metaData).length;

const mintLen = getMintLen([ExtensionType.MetadataPointer]);
const lamports = await connection.getMinimumBalanceForRentExemption(
  mintLen + metadataExtension + metadataLen
);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.