1

Previously, we could get a TransactionInstruction using createCreateMetadataAccountV3Instruction and add the instruction to a transaction.

export declare function createCreateMetadataAccountV3Instruction(accounts: CreateMetadataAccountV3InstructionAccounts, args: CreateMetadataAccountV3InstructionArgs, programId?: web3.PublicKey): web3.TransactionInstruction;

The latest version (3.1.0) of @metaplex-foundation/mpl-token-metadata seems to be completely different and uses Umi. Has anyone figured out how to use this new version and is it required to also install Umi?

export declare function createMetadataAccountV3(context: Pick<Context, 'eddsa' | 'identity' | 'payer' | 'programs'>, input: CreateMetadataAccountV3InstructionAccounts & CreateMetadataAccountV3InstructionArgs): TransactionBuilder;

Basically trying to figure out how to do something like the following:

const instruction = createMetadataAccountV3(...)
const transaction = new Transaction().add(instruction);

How to get a TransactionInstruction from TransactionBuilder, and how to you set up the Umi context?

0

2 Answers 2

4

It was the last day of the Hyperdrive Hackathon and I was stuck exactly at this problem. Since, I have worked with the previous versions of mpl-token-metadata, I was assured that I will be able to build this part in minutes but Umi came as the surprise and took a good two hours of mine. :D

After playing with Umi for a couple of hours, I was able to figure it out. Not the cleanest of the solutions but it worked. You can improve it as you like:

//Imports
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
import { createMetadataAccountV3, CreateMetadataAccountV3InstructionAccounts, CreateMetadataAccountV3InstructionArgs } from '@metaplex-foundation/mpl-token-metadata'
import { clusterApiUrl, PublicKey, Keypair, Transaction, Connection, sendAndConfirmTransaction } from '@solana/web3.js';
import { fromWeb3JsPublicKey, toWeb3JsPublicKey } from '@metaplex-foundation/umi-web3js-adapters'

//Connection and Umi instance
const endpoint = clusterApiUrl("devnet");
const umi = createUmi(endpoint)
const connection = new Connection(endpoint);
const keypair: Keypair = Keypair.fromSecretKey(Uint8Array.from(secret));

//Constants
const mplProgramId = new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s");
const mint = new PublicKey("8Lw2vWmtXbHGj6PYEnRXtFwrnjz2sS7CtoRdHGRq9ndA");
const [metadata] = PublicKey.findProgramAddressSync([
    Buffer.from("metadata"),
    mplProgramId.toBytes(),
    mint.toBytes()
], mplProgramId);

//Metadata Account IX Args
const args: CreateMetadataAccountV3InstructionArgs = {
    data: {
        name: "My Token",
        symbol: "TOKN",
        uri: "",
        sellerFeeBasisPoints: 0,
        collection: null,
        creators: [
            {address: fromWeb3JsPublicKey(keypair.publicKey), verified: true, share: 100}
        ],
        uses: null
    },
    isMutable: true,
    collectionDetails: null
}

//The tx builder expects the type of mint authority and signer to be `Signer`, so built a dummy Signer instance
const signer = {
    publicKey: fromWeb3JsPublicKey(keypair.publicKey),
    signTransaction: null,
    signMessage: null,
    signAllTransactions: null
}

//Metadata account IX Accounts
const accounts: CreateMetadataAccountV3InstructionAccounts = {
    metadata: fromWeb3JsPublicKey(metadata),
    mint: fromWeb3JsPublicKey(mint), 
    payer: signer,
    mintAuthority: signer,
    updateAuthority: fromWeb3JsPublicKey(keypair.publicKey)
}

//Arguments merged to match the parameter required by the method
const fullArgs = {...accounts, ...args}

const metadataBuilder = createMetadataAccountV3(umi, fullArgs);

(async() => {
    const ix: any = metadataBuilder.getInstructions()[0];
    ix.keys = ix.keys.map(key => {
        const newKey = {...key};
        newKey.pubkey = toWeb3JsPublicKey(key.pubkey);
        return newKey;
    });

    const tx = new Transaction().add(ix);
    const sig = await sendAndConfirmTransaction(connection, tx, [keypair]);

    console.log(sig)
})()
2

A simpler solution, using mpl-token-metadata version 2 (no Umi).

@metaplex-foundation/mpl-token-metadata version 3 duplicates a lot of web3.js unnecessarily and requires learning a bunch of additional concepts specific to Metaplex.

The existing @metaplex-foundation/mpl-token-metadata version 2 is still maintained, and can send instructions to the latest onchain program (which is version 3).

Install @metaplex-foundation/mpl-token-metadata@2 (and esrun to run our TypeScript:

npm i "@metaplex-foundation/mpl-token-metadata@2 esrun"

Then make a file called create-token-metadata.ts:

// This uses "@metaplex-foundation/mpl-token-metadata@2" to create tokens
import "dotenv/config";
import {
  getKeypairFromEnvironment,
  getExplorerLink,
} from "@solana-developers/helpers";
import {
  Connection,
  clusterApiUrl,
  PublicKey,
  Transaction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import { createCreateMetadataAccountV3Instruction } from "@metaplex-foundation/mpl-token-metadata";

const user = getKeypairFromEnvironment("SECRET_KEY");

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

console.log(
  `🔑 We've loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`
);

const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
  "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
);

// Subtitute in your token mint account
const tokenMintAccount = new PublicKey("YOU_TOKEN_MINT_ADDRESS_HERE");

const metadataData = {
  name: "Solana Training Token",
  symbol: "TRAINING",
  // Arweave / IPFS / Pinata etc link using metaplex standard for off-chain data
  uri: "https://arweave.net/1234",
  sellerFeeBasisPoints: 0,
  creators: null,
  collection: null,
  uses: null,
};

const metadataPDAAndBump = PublicKey.findProgramAddressSync(
  [
    Buffer.from("metadata"),
    TOKEN_METADATA_PROGRAM_ID.toBuffer(),
    tokenMintAccount.toBuffer(),
  ],
  TOKEN_METADATA_PROGRAM_ID
);

const metadataPDA = metadataPDAAndBump[0];

const transaction = new Transaction();

const createMetadataAccountInstruction =
  createCreateMetadataAccountV3Instruction(
    {
      metadata: metadataPDA,
      mint: tokenMintAccount,
      mintAuthority: user.publicKey,
      payer: user.publicKey,
      updateAuthority: user.publicKey,
    },
    {
      createMetadataAccountArgsV3: {
        collectionDetails: null,
        data: metadataData,
        isMutable: true,
      },
    }
  );

transaction.add(createMetadataAccountInstruction);

const transactionSignature = await sendAndConfirmTransaction(
  connection,
  transaction,
  [user]
);

const transactionLink = getExplorerLink(
  "transaction",
  transactionSignature,
  "devnet"
);

console.log(`✅ Transaction confirmed, explorer link is: ${transactionLink}!`);

const tokenMintLink = getExplorerLink(
  "address",
  tokenMintAccount.toString(),
  "devnet"
);

console.log(`✅ Look at the token mint again: ${tokenMintLink}!`);

Then run this with esrun:

npx esrun create-token-metadata.ts

Your token will now have Metadata in Solana Explorer:

enter image description here

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.