4

I would like to initialize in my Anchor program a reward token to mint it when the user create a new movie review:

use anchor_lang::prelude::*;
use anchor_spl::token::{mint_to, MintTo, Mint, TokenAccount, Token};
use anchor_spl::associated_token::AssociatedToken;
use anchor_lang::solana_program::program::invoke_signed;
use mpl_token_metadata::instruction::create_metadata_accounts_v3;

declare_id!("A54JaBpNgAnocNmK3Axa9Teq5SxrAXTmVHgM5NV7hZrX");

#[program]
pub mod solana_movies_tokens {
    use super::*;

    pub fn add_movie_review(ctx: Context<AddMovieReview>, title: String, description: String, rating: u8) -> Result<()> {
        
        let movie_review = &mut ctx.accounts.movie_review;
        movie_review.reviewer = ctx.accounts.initializer.key();
        movie_review.title = title;
        movie_review.description = description;
        movie_review.rating = rating;

        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()
                },
                &[&[
                    b"mint",
                    &[*ctx.bumps.get("mint").unwrap()]
                ]]
            ),
            10*10^6
        )?;

        msg!("Minted tokens");
        Ok(())
    }

    pub fn initialize_token_mint(ctx: Context<InitializeMint>) -> Result<()> {
        msg!("Token mint initialized");
        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(title: String, description: String)]
pub struct AddMovieReview<'info> {
    #[account(
        init, 
        seeds=[title.as_bytes(), initializer.key().as_ref()], 
        bump, 
        payer = initializer, 
        space = 8 + 32 + 1 + 4 + title.len() + 4 + description.len()
    )]
    pub movie_review: Account<'info, MovieAccountState>,
    #[account(mut)]
    pub initializer: Signer<'info>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    #[account(
        seeds=[b"mint"],
        bump,
        mut
    )]
    pub mint: Account<'info, Mint>,
    #[account(
        init_if_needed,
        payer = initializer,
        associated_token::mint = mint,
        associated_token::authority = initializer
    )]
    pub token_account: Account<'info, TokenAccount>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub rent: Sysvar<'info, Rent>
}

#[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>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct MovieAccountState {
    pub reviewer: Pubkey,
    pub rating: u8,
    pub title: String,
    pub description: String,
}

This code works, but I don't know how I can set a logo, name, and symbol to my token. I would like to set this information in the same program.

Phantom wallet without token name

1 Answer 1

5

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 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 (edit: to clarify, not an endorsement of this token)

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

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

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

0

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.