Create your own
Lesson illustration

Attaching Metadata to Solana Tokens via CPI

Hello! Welcome back to our journey into Solana token development.

In the last lesson, we built the conceptual foundation for NFTs on Solana. We learned that an NFT is just an SPL token with a specific configuration (supply of 1, 0 decimals) and that the Metaplex Token Metadata program is the key to attaching rich data (like a name, symbol, and image) to it via a PDA-linked Metadata Account.

Today, we transition from theory to practice. Our goal is to perform a Cross-Program Invocation (CPI) to the Metaplex program to attach metadata to a token mint. This is the crucial step where your Anchor program communicates with the Metaplex program to "bring an NFT to life" by creating its on-chain metadata. By the end of this lesson, you will have written the Rust code to create the metadata for any SPL token.

The Anatomy of a Metadata CPI

Attaching metadata involves your program calling an instruction on the Metaplex Token Metadata program. This requires a few key steps inside your Anchor instruction:

  1. Setting up Dependencies: You need to include the right crates in your Cargo.toml to interact with the SPL Token and Metaplex programs.
  2. Defining the Accounts Context: Your instruction will need access to all the accounts required by the Metaplex instruction, including the mint, the authority, and the Metaplex program itself.
  3. Preparing the Metadata: You'll construct the metadata (name, symbol, URI) inside your instruction, packaging it in a specific struct provided by the Metaplex crate.
  4. Executing the CPI: You'll use Anchor's helpers to build and invoke the call to the Metaplex program.

Let's work through a complete example. We will use a comprehensive article from Rareskills.io that provides a clean, focused implementation of this exact task.

Project Setup and Dependencies

First, any program interacting with other on-chain programs needs the right dependencies. For creating token metadata, two crates are essential.

Implementing Token Metadata with Metaplex

This article, 'Implementing Token Metadata with Metaplex,' will be our primary guide. Let's start with the project setup to see which dependencies we need.

Read the 'Project set up' section. Pay close attention to the additions in Cargo.toml: anchor-spl and mpl-token-metadata. These are the crates that give us the tools to work with the Token and Metaplex programs.

As the article shows, we need:

  • anchor-spl: Provides helpers for SPL programs, including the Token program.
  • mpl-token-metadata: This is the official Metaplex crate. It contains the function definitions, instruction data structures (DataV2), and CPI helpers for the Token Metadata program. Using this crate is far easier and safer than building the instruction manually.

Implementing the CPI in Anchor

Now for the core of our lesson. We will write an instruction called create_token_metadata that takes in the metadata as arguments and performs the CPI.

The following reading contains the full code for the instruction and its context. We will study it and then break it down piece by piece.

Implementing Token Metadata with Metaplex

Let's examine the complete Rust code for our instruction.

First, read through the code in the 'Add the Anchor Program Code' section. Then, read the detailed breakdown in the 'Explaining the create_token_metadata function' section. Focus on understanding the three main parts: defining the DataV2 struct, validating the metadata account PDA, and constructing the CPI call with CreateMetadataAccountV3Cpi.

Let's dissect the key components from that code.

1. The Instruction and its Context

Our function signature looks like this:

pub fn create_token_metadata(
    ctx: Context<CreateTokenMetadata>,
    name: String,
    symbol: String,
    uri: String,
    // ... other params
) -> Result<()> { ... }

Notice how name, symbol, and uri are passed as simple function parameters. Anchor handles the serialization of this instruction data for us, which is much simpler than the manual borsh serialization we did in native Solana development.

The CreateTokenMetadata Accounts struct defines all the accounts we need to give to the Metaplex program:

#[derive(Accounts)]
pub struct CreateTokenMetadata<'info> {
    #[account(mut)]
    pub metadata: AccountInfo<'info>, // The metadata PDA we are creating
    #[account(mut)]
    pub mint: Account<'info, Mint>,   // The token mint we're attaching metadata to
    pub authority: Signer<'info>,     // The mint authority, who must sign
    #[account(mut)]
    pub payer: Signer<'info>,         // Who pays for the account creation
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
    #[account(address = mpl_token_metadata::ID)]
    pub token_metadata_program: AccountInfo<'info>, // The Metaplex Program itself
}

The token_metadata_program account is constrained with #[account(address = ...)]. This is a critical security check to ensure your program is calling the legitimate Metaplex program and not a malicious impostor.

Test your understanding!

In the CreateTokenMetadata struct, the metadata account is of type AccountInfo<'info> and has a /// CHECK: doc comment, while mint is a typed Account<'info, Mint>. Why the difference?

Show answer

The metadata account does not exist when our create_token_metadata instruction begins. It is created by the Metaplex program during the CPI. Because it doesn't exist at the start, Anchor cannot perform its usual checks (e.g., that it's owned by the Metaplex program). We use the generic AccountInfo type and the /// CHECK comment to tell Anchor that we are responsible for ensuring its safety. In this case, we manually check that the address passed in matches the correct PDA derivation for the mint. The mint account, however, must already exist, so Anchor can safely deserialize it and check its properties.

2. Preparing the DataV2 Payload

Inside the function, the first step is to prepare the data payload for Metaplex using the DataV2 struct from the mpl-token-metadata crate.

let data = DataV2 {
    name,
    symbol,
    uri,
    seller_fee_basis_points: // e.g., 100 for 1% royalty
    creators: Some(vec![Creator {
        address: ctx.accounts.payer.key(),
        verified: true,
        share: 100,
    }]),
    collection: None,
    uses: None,
};

This struct cleanly maps to the fields stored in the on-chain Metadata Account.

3. Executing the CPI

This is the main event. The mpl-token-metadata crate provides a wonderful helper struct, CreateMetadataAccountV3Cpi, to build and execute the call.

CreateMetadataAccountV3Cpi::new(
    // 1. The program to call
    &ctx.accounts.token_metadata_program.to_account_info(),
    // 2. The accounts the CPI needs
    CreateMetadataAccountV3CpiAccounts {
        metadata: &ctx.accounts.metadata.to_account_info(),
        mint: &ctx.accounts.mint.to_account_info(),
        mint_authority: &ctx.accounts.authority.to_account_info(),
        payer: &ctx.accounts.payer.to_account_info(),
        update_authority: (&ctx.accounts.authority.to_account_info(), true),
        system_program: &ctx.accounts.system_program.to_account_info(),
        rent: Some(&ctx.accounts.rent.to_account_info()),
    },
    // 3. The instruction arguments
    CreateMetadataAccountV3InstructionArgs {
        data,
        is_mutable: true,
        collection_details: None,
    },
)
.invoke()?; // 4. Execute the call

This pattern is a cornerstone of advanced Anchor development. The invoke() method performs the CPI, and the ? operator will propagate any errors that occur within the Metaplex program back to your program's caller.

A Full NFT Minting Example

The Rareskills article focuses specifically on attaching metadata. For a broader context of how this fits into creating a full NFT (including creating the Master Edition account), the following video is an excellent walkthrough.

Rust Solana Tutorial #8 - Mint NFTs with Rust, Anchor, & Metaplex!

This video from Coding & Crypto demonstrates a complete NFT minting program. It reinforces the concepts we just covered and also introduces the CPI for creating the Master Edition account, which certifies the NFT's uniqueness.

Watch from the beginning of the 'Defining Program Accounts' section (4:35) to the end of the 'Performing CPI to Metaplex' section (16:31). Pay special attention to how the metadata and master_edition accounts are defined in the context, and how two separate CPIs are made—one for the metadata and one for the master edition.

This video shows how you can chain multiple CPIs together to perform complex actions. Creating a full NFT involves:

  1. Creating the mint account (CPI to SPL Token Program).
  2. Creating the token account (CPI to SPL Associated Token Account Program).
  3. Minting the token (CPI to SPL Token Program).
  4. Creating the metadata account (CPI to Metaplex).
  5. Creating the master edition account (CPI to Metaplex).

Our focus today is step 4, but seeing it in the full sequence is invaluable.

Calling the Program from a Client

Your background in frontend development makes this final piece especially relevant. How does a dApp or script call our new on-chain instruction? The test file for the Rareskills program is a perfect example. A particularly interesting part is how it handles the off-chain URI data.

Implementing Token Metadata with Metaplex

Let's look at the client-side TypeScript code that calls our program. This test script demonstrates a crucial real-world task: uploading metadata to permanent storage before calling the on-chain program.

Read the 'Testing our program' section. You don't need to run the code, but focus on understanding the logic in tests/spl_token_with_metadata.ts. Specifically, note how it uses Irys (a service for the Arweave network) to upload an image and then a JSON file, getting back a permanent URI. This URI is then passed into the call to our createTokenMetadata instruction.

This client-side workflow is standard practice for NFTs:

  1. Upload Assets: The image is uploaded to a decentralized storage network like Arweave.
  2. Upload JSON: A JSON file containing the name, symbol, and the image URI is then also uploaded.
  3. Call Program: The URI of the JSON file is what's passed to your Anchor program.

The on-chain program doesn't care what the URI points to; it just stores the link. It's up to clients like wallets and marketplaces to fetch and interpret that URI.

Conclusion

Congratulations! You've just walked through the complete process of programmatically creating token metadata on Solana. This is a massive step towards building any application that involves NFTs or even richly-detailed fungible tokens.

Let's recap the key takeaways:

  • The mpl-token-metadata crate is your primary tool for interacting with the Metaplex program.
  • You perform a CPI by defining the required accounts in your Context and using a helper struct like CreateMetadataAccountV3Cpi.
  • The metadata itself is prepared in a DataV2 struct and passed as an argument to the CPI.
  • Security is crucial: always validate that you are calling the official Metaplex program by constraining the address in your Accounts struct.
  • Client applications are responsible for preparing and uploading the off-chain JSON metadata and then passing the resulting URI to your program.

In the next lessons, we'll continue to build on these patterns, exploring how to perform CPIs to mint and transfer the SPL tokens we've now defined. You've created the blueprint for the token; next, you'll learn how to create and distribute the actual tokens.

Can't find a good explanation? Sign up and we'll make it for you

Sign up