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:
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);
});
});
mint
and as well asmint_authority
. Mymint
isAyx6751KcLP74dTdasP7B7wYxPgCQyMX4Lahdv4sMwNs
, which is a PDA generated from my programId together with some seed. Themint_authority
shows as the exact same account on the explorer. So I passed in that account both asmint
and also asmint_authority
.