1

My token's mint authority is my Program.

I need to add metadata so that the token appears nicely in my users' wallets.

So in order to add the metadata, I need to create a new instruction into my Program, and do a CPI there.

I'm having a hard time defining all the correct parameters there.

Here's my try:

    pub fn tok_meta(ctx: Context<TokMeta>, okubump: u8) -> Result<()> {
        let (metadata_pda, _bump) = find_metadata_account(ctx.accounts.mint.to_account_info().key);
        let ix = create_metadata_accounts_v3(
            mpl_token_metadata::id(), // program_id,
            metadata_pda, // metadata_account,
            *ctx.accounts.mint.to_account_info().key, //mint,
            , //mint_authority, *** HOW TO GET MY OWN PROGRAM ID?? ***
            *ctx.accounts.payer.to_account_info().key, //payer,
            *ctx.accounts.payer.to_account_info().key, //update_authority,
            String::from("OKU - Bitoku Storage"), // name,
            String::from("OKU"), // symbol,
            String::from("https://okupub.gitlab.io/imgs/Oku.json"), // uri,
            None, // creators,
            0u16, //seller_fee_basis_points,
            true, // update_authority_is_signer,
            true, // is_mutable,
            None, // collection,
            None, // uses,
            None, // collection_details
        );
        invoke_signed(
            &ix,
            &[
                ctx.accounts.metadata_pda.to_account_info().clone(), // Metadata account
                ctx.accounts.mint.to_account_info().clone(), // Mint
                , // Mint Authority *** HOW TO GET MY OWN PROGRAM ID ***
                ctx.accounts.payer.to_account_info().clone(), // Payer
                ctx.accounts.payer.to_account_info().clone(), // Update Authority
                ctx.accounts.system_program.to_account_info().clone(), // System Program
                ctx.accounts.rent.to_account_info().clone(), // Rent Sysvar
            ],
            &[&[&OKUSEED, &[okubump]]],
        )?;
        Ok(())
    }

}

A few burning questions here:

  1. How can I get my own ProgramID in the form of AccountInfo, so that I can pass it as an account?

  2. How can I get the metadata_pda as AccountInfo? I can get the pubkey and bump using find_metadata_account, but that doesn't get me the AccountInfo. And it's tough to pass the metadata_pda as an account to the outer instruction because it depends not on my Program_id, but on the token_metadata program's ID.

  3. Am I doing everything correctly with the signers_seeds? These are the seeds used to get from my ProgramID to the mint address. Are these the ones needed here?

  4. Are all the clone()s correct and needed?

Any help greatly appreciated!

PS: Here's the corresponding Accounts struct:

#[derive(Accounts)]
#[instruction(okubump: u8)]
pub struct TokMeta<'info> {
    #[account(seeds = [OKUSEED.as_ref()], bump = okubump, mint::decimals = 6, mint::authority = mint)]
    pub mint: Account<'info, Mint>,
    #[account(mut)]
    pub payer: Signer<'info>,
    /// CHECK: This is not dangerous because it's being checked by the inner instruction
    #[account(mut)]
    pub metadata_pda: AccountInfo <'info>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
}

2 Answers 2

2

Answer for question 1: In a Solana program while calling another program from your program which is called CPI(Cross program invocation) in this you want your program to sign the instruction in this case you dont require the program address. Solana has a concept called PDA(Program derived Address), PDAs are addresses that are generated deterministicly using seeds and it does not have private key so PDAs are actually used for signing ix in your program.Check the PDA and CPI section from anchor website things would get more clear here.

2nd Question

You can pass the address from the derive macro and then use .to_account_info() to get the account info of any account

3rd Question Looking at this code it seams good did you try running it you can better share the github and we can debug more on this issue.In case of Seeds you have to create a Account in your program first and then pass its seed.

4th Question Ya it seams like its correct

1
  • 1
    Thanks. I am aware of PDAs and CPIs, have been using it a lot in my program already. The only reason I want my own program address is not to sign anything, but in order to satisfy metaplex's CreateMatadataAccountsV3, which requires me to pass in the Mint Authority. So in my case, the Mint Authority is my program address. But it seems the only to get that is on the client side. So will try passing it form the client to the outer instruction, and form there to the inner (CPI) instruction. Will write an update. Jul 16, 2022 at 17:44
2

@Pratik.js is correct, you'll want to use a Program Derived Address (PDA) account as the mint authority.

Here's an example of making a PDA the mint authority.

In short, you'll create an account derived from your program (a PDA) and that account will be the mint authority. Then you'll use the seeds & bump from the PDA to run a Cross-Program Invocation (CPI) with invoke_signed - which will effectively let your program sign for a transaction via an account it owns (the PDA).

Here's a few snippets from that example:

Program code (Rust):

use {
    anchor_lang::{
        prelude::*,
        solana_program::program::invoke_signed,
        system_program,
    },
    anchor_spl::token,
    mpl_token_metadata::instruction as mpl_instruction,
};


pub fn create_token_mint(
    ctx: Context<CreateTokenMint>, 
    metadata_title: String, 
    metadata_symbol: String, 
    metadata_uri: String,
    mint_authority_pda_bump: u8,
) -> Result<()> {

    let mint_authority = &mut ctx.accounts.mint_authority;

    msg!("Creating metadata account...");
    msg!("Metadata account address: {}", &ctx.accounts.metadata_account.key());
    invoke_signed(
        &mpl_instruction::create_metadata_accounts_v2(
            ctx.accounts.token_metadata_program.key(),      // Program ID (the Token Metadata Program)
            ctx.accounts.metadata_account.key(),            // Metadata account
            ctx.accounts.mint_account.key(),                // Mint account
            mint_authority.key(),                   // Mint authority
            ctx.accounts.payer.key(),               // Payer
            mint_authority.key(),                   // Update authority
            metadata_title,                                 // Name
            metadata_symbol,                                // Symbol
            metadata_uri,                                   // URI
            None,                                           // Creators
            0,                                              // Seller fee basis points
            true,                                           // Update authority is signer
            false,                                          // Is mutable
            None,                                           // Collection
            None,                                           // Uses
        ),
        &[
            ctx.accounts.metadata_account.to_account_info(),
            ctx.accounts.mint_account.to_account_info(),
            mint_authority.to_account_info(),
            ctx.accounts.payer.to_account_info(),
            mint_authority.to_account_info(),
            ctx.accounts.token_metadata_program.to_account_info(),
            ctx.accounts.rent.to_account_info(),
        ],
        &[&[
            b"mint_authority_", 
            ctx.accounts.mint_account.key().as_ref(),
            &[mint_authority_pda_bump],
        ]]
    )?;

    msg!("Token mint created successfully.");

    Ok(())
}


#[derive(Accounts)]
pub struct CreateTokenMint<'info> {
    /// CHECK: We're about to create this with Metaplex
    #[account(mut)]
    pub metadata_account: UncheckedAccount<'info>,
    #[account(
        init,
        payer = payer,
        mint::decimals = 9,
        mint::authority = mint_authority.key(),
        owner = token_program.key(),
    )]
    pub mint_account: Account<'info, token::Mint>,
    #[account(
        init, 
        payer = payer,
        space = 8 + 32,
        seeds = [
            b"mint_authority_", 
            mint_account.key().as_ref(),
        ],
        bump
    )]
    pub mint_authority: Account<'info, MintAuthorityPda>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, token::Token>,
    /// CHECK: Metaplex will check this
    pub token_metadata_program: UncheckedAccount<'info>,
}

#[account]
pub struct MintAuthorityPda {}

Notice we're passing in the bump to make it available for invoke_signed.

Here's what it looks like to send instructions to this program:

Client-Side (TypeScript)


...

const [mintAuthorityPda, mintAuthorityPdaBump] = await anchor.web3.PublicKey.findProgramAddress(
  [
    Buffer.from("mint_authority_"),
    mintKeypair.publicKey.toBuffer(),
  ],
  program.programId,
);

const metadataAddress = (await anchor.web3.PublicKey.findProgramAddress(
  [
    Buffer.from("metadata"),
    TOKEN_METADATA_PROGRAM_ID.toBuffer(),
    mintKeypair.publicKey.toBuffer(),
  ],
  TOKEN_METADATA_PROGRAM_ID
))[0];

await program.methods.createTokenMint(
  testTokenTitle, testTokenSymbol, testTokenUri, mintAuthorityPdaBump
)
.accounts({
  metadataAccount: metadataAddress,
  mintAccount: mintKeypair.publicKey,
  mintAuthority: mintAuthorityPda,
  payer: payer.publicKey,
  rent: anchor.web3.SYSVAR_RENT_PUBKEY,
  systemProgram: anchor.web3.SystemProgram.programId,
  tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
  tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
})
.signers([mintKeypair, payer.payer])
.rpc();

Note: I omitted the bump for the metadataAddress in this example because I only needed the metadata address once for this demo. You may want to retain that bump and/or use it as a parameter if you plan to access the metadata account again after it's created.

You can also check out how to use this PDA as a mint authority when minting tokens to a wallet (new token account) in this file and test #2 of the example.

Here's a snippet from minting to a new token address, where again we're using the seeds and bump, and also demonstrating Anchor's CpiContext::new_with_signer which is akin to invoke_signed:

token::mint_to(
    CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        token::MintTo {
            mint: ctx.accounts.mint_account.to_account_info(),
            to: ctx.accounts.token_account.to_account_info(),
            authority: mint_authority.to_account_info(),
        },
        &[&[
            b"mint_authority_", 
            ctx.accounts.mint_account.key().as_ref(),
            &[mint_authority_pda_bump],
        ]]
    ),
    amount,
)?;
2
  • Thanks for all the input. My issue, however, is not making a PDA a mint authority, or minting tokens with that. That I've already done, my token mint and mint authority are PDAs, and my program already minted a lot of tokens to my customers. The issue I'm facing is how to add METADATA to the token, so that it appears nicely in my customers' wallets, rather than as "unknown token". It seems I have to use metaplex's create_matadata_accounts_v3, and that's where I'm running into my issues. Jul 18, 2022 at 21:54
  • That first Rust snippet is using create_metadata_accounts_v2 - it should be very similar to create_metadata_accounts_v3
    – Joe C
    Jul 18, 2022 at 22:54

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.