Skip to main content
1 of 3
john
  • 7.8k
  • 2
  • 10
  • 26

Update the initialize_token_mint instruction to also create a token metadata account.

Here's a reference initialize instruction that:

  • initialize a mint
  • creates a metadata account for the mint
  • mints the token

You can remove the mint_to cpi from the initialize instruction for your use case.

use anchor_lang::{prelude::*, solana_program::program::invoke_signed};
    use anchor_spl::{
        associated_token::AssociatedToken,
        token::{mint_to, Mint, MintTo, Token, TokenAccount},
    };
    use borsh::BorshDeserialize;
    use mpl_token_metadata::{
        instruction::create_metadata_accounts_v3, pda::find_metadata_account, ID as MetadataID,
    };
    
    declare_id!("8LgkDyXcYXtX6h78eNRydLr2tkWHxduREnpb5TZdgE8b");
    
    #[program]
    pub mod token_with_metadata {
        use super::*;
    
        pub fn initialize(
            ctx: Context<InitializeMint>,
            uri: String,
            name: String,
            symbol: String,
        ) -> Result<()> {
            let seeds = &["mint".as_bytes(), &[*ctx.bumps.get("mint").unwrap()]];
            let signer = [&seeds[..]];
    
            let account_info = vec![
                ctx.accounts.metadata.to_account_info(),
                ctx.accounts.mint.to_account_info(),
                ctx.accounts.user.to_account_info(),
                ctx.accounts.token_metadata_program.to_account_info(),
                ctx.accounts.token_program.to_account_info(),
                ctx.accounts.system_program.to_account_info(),
                ctx.accounts.rent.to_account_info(),
            ];
    
            invoke_signed(
                &create_metadata_accounts_v3(
                    ctx.accounts.token_metadata_program.key(), // token metadata program
                    ctx.accounts.metadata.key(),               // metadata account PDA for mint
                    ctx.accounts.mint.key(),                   // mint account
                    ctx.accounts.mint.key(),                   // mint authority
                    ctx.accounts.user.key(),                   // payer for transaction
                    ctx.accounts.mint.key(),                   // update authority
                    name,                                      // name
                    symbol,                                    // symbol
                    uri,                                       // uri (offchain metadata)
                    None,                                      // (optional) creators
                    0,                                         // seller free basis points
                    true,                                      // (bool) update authority is signer
                    true,                                      // (bool) is mutable
                    None,                                      // (optional) collection
                    None,                                      // (optional) uses
                    None,                                      // (optional) collection details
                ),
                account_info.as_slice(),
                &signer,
            )?;
    
            mint_to(
                CpiContext::new_with_signer(
                    ctx.accounts.token_program.to_account_info(),
                    MintTo {
                        authority: ctx.accounts.mint.to_account_info(),
                        to: ctx.accounts.token_account.to_account_info(),
                        mint: ctx.accounts.mint.to_account_info(),
                    },
                    &signer,
                ),
                1,
            )?;
    
            Ok(())
        }
    }
    
    #[derive(Accounts)]
    pub struct InitializeMint<'info> {
        #[account(
            init,
            seeds = [b"mint"],
            bump,
            payer = user,
            mint::decimals = 6,
            mint::authority = mint,
        )]
        pub mint: Account<'info, Mint>,
        /// CHECK: Using "address" constraint to validate metadata account address
        #[account(
            mut,
            address=find_metadata_account(&mint.key()).0
        )]
        pub metadata: UncheckedAccount<'info>,
        #[account(
            init_if_needed,
            payer = user,
            associated_token::mint = mint,
            associated_token::authority = user
        )]
        pub token_account: Account<'info, TokenAccount>,
        #[account(mut)]
        pub user: Signer<'info>,
        pub token_program: Program<'info, Token>,
        pub token_metadata_program: Program<'info, TokenMetaData>,
        pub associated_token_program: Program<'info, AssociatedToken>,
        pub rent: Sysvar<'info, Rent>,
        pub system_program: Program<'info, System>,
    }
    
    #[derive(Clone)]
    pub struct TokenMetaData;
    impl anchor_lang::Id for TokenMetaData {
        fn id() -> Pubkey {
            MetadataID
        }
    }

here's how the test looks

import * as anchor from "@project-serum/anchor"
import { Program } from "@project-serum/anchor"
import { TokenWithMetadata } from "../target/types/token_with_metadata"
import { findMetadataPda } from "@metaplex-foundation/js"
import { getAccount, getAssociatedTokenAddressSync } from "@solana/spl-token"

describe("token-with-metadata", () => {
  const provider = anchor.AnchorProvider.env()
  anchor.setProvider(provider)

  const program = anchor.workspace
    .TokenWithMetadata as Program<TokenWithMetadata>

  const TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey(
    "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
  )

  const nft = {
    uri: "https://arweave.net/OwXDf7SM6nCVY2cvQ4svNjtV7WBTz3plbI4obN9JNkk",
    name: "NAME",
    symbol: "SYMBOL",
  }

  const [mintPDA] = anchor.web3.PublicKey.findProgramAddressSync(
    [Buffer.from("mint")],
    program.programId
  )

  const tokenAccount = getAssociatedTokenAddressSync(
    mintPDA,
    provider.wallet.publicKey
  )

  it("Is initialized!", async () => {
    // Add your test here.
    const metadataPDA = await findMetadataPda(mintPDA)

    const tx = await program.methods
      .initialize(nft.uri, nft.name, nft.symbol)
      .accounts({
        mint: mintPDA,
        metadata: metadataPDA,
        tokenAccount: tokenAccount,
        user: provider.wallet.publicKey,
        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
      })
      .rpc()
    console.log("Your transaction signature", tx)

    const account = await getAccount(provider.connection, tokenAccount)
    console.log(account.amount)
  })
})

Also update Cargo.toml, you can also remove init-if-needed if you're not using it

[dependencies]
anchor-lang = { version = "0.25.0", features = ["init-if-needed"] }
anchor-spl = "0.25.0"
mpl-token-metadata = {version = "1.5.0", features = ["no-entrypoint"]}

Check out the metaplex sdk to see how to create the uri for the off-chain metadata for your token

https://github.com/metaplex-foundation/js#readme

You'll need to:

  • upload an image to a storage service to get the image uri (ex. arweave)
  • then upload again with the other metadata you want for the token (name, symbol etc) and the include the image uri from the previous step

Here's an example of the BONK token metadata

image uri: https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY7I

token metadata uri (which points to the image uri): https://arweave.net/QPC6FYdUn-3V8ytFNuoCS85S2tHAuiDblh6u3CIZLsw

Also recommend looking through the metaplex docs, great diagrams for understanding relationship between accounts

https://docs.metaplex.com/programs/understanding-programs https://docs.metaplex.com/programs/token-metadata/overview

john
  • 7.8k
  • 2
  • 10
  • 26