1

I have a token mint address that is a PDA.

Because of that, I cannot just add metadata the usual way. No wallet can sign for the PDA, so my Program needs to add the metadata.

So I created a new instruction "tokMetadata" to my program.

The new instruction is working nicely. I am calling create_metadata_accounts_v3 from within my instruction, and that works without errors - neither compile time nor runtime errors. As long as the actual invoke_signed (to execute the inner instruction via CPI) is commented out...

BUT as soon as I give the invoke_signed a try, I am getting a runtime error:

Runtime Error

Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: An account required by the instruction is missing

PLEASE NOTE: The error message must be for the INNER instruction (called via CPI invoke_signed), not the outer one. The outer instruction is being called without issues when the invoke_signed is commented out.

However, I have supplied all accounts that are needed as per documentation (and as per source code of create_metadata_accounts_v3!)

Here's my code fragments:

pub fn tok_meta(ctx: Context<TokMeta>, okubump: u8) -> Result<()> {
    let a = Pubkey::from_str(ADMINACC);
    let admin = match a {
        Ok(key) => key,
        Err(_e) => return err!(ErrorCode::AdminOnly),
    };
    require!(ctx.accounts.payer.to_account_info().key == &admin, ErrorCode::AdminOnly);
    let ix = create_metadata_accounts_v3(
        mpl_token_metadata::id(), // program_id,
        *ctx.accounts.metadata_pda.to_account_info().key, // metadata_account,
        *ctx.accounts.mint.to_account_info().key, //mint,
        *ctx.accounts.mintauth.to_account_info().key, //mint_authority,
        *ctx.accounts.payer.to_account_info().key, //payer,
        *ctx.accounts.updauth.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,
        false, // 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
            ctx.accounts.mintauth.to_account_info().clone(), // Mint Authority
            ctx.accounts.payer.to_account_info().clone(), // Payer
            ctx.accounts.updauth.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(())
}
#[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>,
    /// CHECK: This is not dangerous because it will be checked in the inner instruction
    pub mintauth: AccountInfo<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    /// CHECK: This is not dangerous because it will be checked in the inner instruction
    pub updauth: AccountInfo<'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>,
}

tests/tokmeta.js:

const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram, PublicKey, SYSVAR_RENT_PUBKEY } = anchor.web3;
const { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddress } = require("@solana/spl-token");
const { findMetadataPda } = require("@metaplex-foundation/js");
var g = require("./globals");

describe("nodeshop", () => {
  /* create and set a Provider */
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  usr = provider.wallet.publicKey;
  let isLocal = provider.connection.rpcEndpoint.includes("localhost");
  console.log("isLocal:", isLocal);
  const program = anchor.workspace.Nodestore;

  it("Sets the token metadata", async () => {
    const [okuPDA, okubump] = await PublicKey.findProgramAddress([anchor.utils.bytes.utf8.encode(g.OKUSEED)], program.programId);
    console.log("TokMeta: okuPDA is [", okuPDA.toString(), "], bump ", okubump);
    const metadataPDA = await findMetadataPda(okuPDA);
    console.log("TokMeta: metadataPDA is [", metadataPDA.toString(), "], bump ", okubump);

    await program.methods
      .tokMeta(okubump)
      .accounts({
        mint: okuPDA,
        mintauth: okuPDA,
        payer: usr,
        updauth: g.ADMINACC,
        metadataPda: metadataPDA,
        systemProgram: SystemProgram.programId,
        tokenProgram: TOKEN_PROGRAM_ID,
        rent: SYSVAR_RENT_PUBKEY,
      })
      .rpc();

      console.log("TokMeta done.");
    assert.ok(true);
  });

 });
3
  • Maybe this is important: I'm passing the exact same account as both mint and as well as mint_authority. My mint is Ayx6751KcLP74dTdasP7B7wYxPgCQyMX4Lahdv4sMwNs, which is a PDA generated from my programId together with some seed. The mint_authority shows as the exact same account on the explorer. So I passed in that account both as mint and also as mint_authority. Commented Jul 17, 2022 at 2:49
  • 1
    please post the error as text in the description. search engines won't index screenshots
    – trent.sol
    Commented Jul 17, 2022 at 16:52
  • 1
    Done. Also working on stripping down my (closed source) repo to the bare essentials of adding metadata to a program-owned mint, so that people can try debug. Could act as a template in the future. Commented Jul 17, 2022 at 17:01

1 Answer 1

1

I found the answer after some experimenting. I need to pass another account in the context, namely the metadata_program_id.

So the new code for invoke_signed is:

        invoke_signed(
            &ix,
            &[
                ctx.accounts.metadata_program.to_account_info().clone(), // N E W : Metadata program id
                ctx.accounts.metadata_pda.to_account_info().clone(), // Metadata account
                ctx.accounts.mint.to_account_info().clone(), // Mint
                ctx.accounts.mintauth.to_account_info().clone(), // Mint Authority
                ctx.accounts.payer.to_account_info().clone(), // Payer
                ctx.accounts.updauth.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.as_ref(), &[okubump]],
            ],
        )?;
2
  • can you elaborate the answer? where exactly did the missing account need added?
    – trent.sol
    Commented Jul 23, 2022 at 4:38
  • Done. Edited the answer. Commented Jul 23, 2022 at 5:23

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.