2

I am trying to run a test on anchor/metaplex code that should be minting a new NFT/creating a master edition account, however my test is failing and I'm not sure why. Here is the error message:

0 passing (1s)
  1 failing

  1) mint-song-nft
       Mint!:
     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:5791:13)
      at processTicksAndRejections (node:internal/process/task_queues:95:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:5750:20)
      at sendAndConfirmRawTransaction (node_modules/@project-serum/anchor/src/provider.ts:318:21)
      at AnchorProvider.sendAndConfirm (node_modules/@project-serum/anchor/src/provider.ts:149:14)
      at MethodsBuilder.rpc [as _rpcFn] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:29:16)

Here is the section of code the I presume the error is coming from:

await program.methods.mint(
      testNFTTitle, testNFTSymbol, testNFTuri
    )
    .accounts({
      masterEdition: masterEditionAddress,
      metadata: metadataAddress,
      mint: mintKeypair.publicKey,
      tokenAccount: tokenAddress,
      mintAuthority: wallet.publicKey,
      tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
    })
    .signers([mintKeypair])
    .rpc();

Any ideas as to what account is the unauthoirzed signer/unwritable account? Here's my rust struct for context:

#[derive(Accounts)]
pub struct MintNft<'info> {
    /// CHECK: This will be created with Metaplex
    #[account(mut)]
    pub metadata: UncheckedAccount<'info>,
    /// CHECK: This will be created with Metaplex
    #[account(mut)]
    pub master_edition: UncheckedAccount<'info>,
    #[account(mut)]
    pub mint: Signer<'info>,
    /// CHECK: This will be created with Anchor
    #[account(mut)]
    pub token_account: UncheckedAccount<'info>,
    #[account(mut)]
    pub mint_authority: Signer<'info>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, token::Token>,
    pub associated_token_program: Program<'info, associated_token::AssociatedToken>,
    /// CHECK: Metaplex will check this
    pub token_metadata_program: UncheckedAccount<'info>,
}

1 Answer 1

2

To provide more specific guidance for your error, it would be helpful to see your mint instruction.

However, here's an example that you can adapt to suit your needs. This reference example creates a "sized collection" NFT, with a PDA as the mint authority/creator. (Anchor 0.26)

use anchor_lang::prelude::*;

use anchor_spl::{
    associated_token::AssociatedToken,
    metadata::{
        create_master_edition_v3, create_metadata_accounts_v3, sign_metadata,
        CreateMasterEditionV3, CreateMetadataAccountsV3, Metadata, SignMetadata,
    },
    token::{mint_to, Mint, MintTo, Token, TokenAccount},
};

use mpl_token_metadata::{
    pda::{find_master_edition_account, find_metadata_account},
    state::{CollectionDetails, Creator, DataV2},
};

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

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

    pub fn mint_nft(
        ctx: Context<MintNft>,
        uri: String,
        name: String,
        symbol: String,
    ) -> Result<()> {
        let signer_seeds: &[&[&[u8]]] = &[&[b"nft", &[*ctx.bumps.get("pda").unwrap()]]];

        mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    mint: ctx.accounts.nft_mint.to_account_info(),
                    to: ctx.accounts.token_account.to_account_info(),
                    authority: ctx.accounts.pda.to_account_info(),
                },
                signer_seeds,
            ),
            1,
        )?;

        create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.token_metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    metadata: ctx.accounts.metadata.to_account_info(),
                    mint: ctx.accounts.nft_mint.to_account_info(),
                    mint_authority: ctx.accounts.pda.to_account_info(),
                    update_authority: ctx.accounts.pda.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &signer_seeds,
            ),
            DataV2 {
                name: name,
                symbol: symbol,
                uri: uri,
                seller_fee_basis_points: 0,
                creators: Some(vec![Creator {
                    address: ctx.accounts.pda.key(),
                    verified: false,
                    share: 100,
                }]),
                collection: None,
                uses: None,
            },
            true,
            true,
            Some(CollectionDetails::V1 { size: 0 }),
        )?;

        create_master_edition_v3(
            CpiContext::new_with_signer(
                ctx.accounts.token_metadata_program.to_account_info(),
                CreateMasterEditionV3 {
                    payer: ctx.accounts.payer.to_account_info(),
                    mint: ctx.accounts.nft_mint.to_account_info(),
                    edition: ctx.accounts.master_edition.to_account_info(),
                    mint_authority: ctx.accounts.pda.to_account_info(),
                    update_authority: ctx.accounts.pda.to_account_info(),
                    metadata: ctx.accounts.metadata.to_account_info(),
                    token_program: ctx.accounts.token_program.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &signer_seeds,
            ),
            Some(0),
        )?;

        sign_metadata(CpiContext::new_with_signer(
            ctx.accounts.token_metadata_program.to_account_info(),
            SignMetadata {
                creator: ctx.accounts.pda.to_account_info(),
                metadata: ctx.accounts.metadata.to_account_info(),
            },
            &signer_seeds,
        ))?;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct MintNft<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    ///CHECK:
    #[account(
        seeds = [b"nft".as_ref()],
        bump,
    )]
    pub pda: UncheckedAccount<'info>,

    #[account(
        init,
        payer = payer,
        mint::decimals = 0,
        mint::authority = pda,
        mint::freeze_authority = pda
    )]
    pub nft_mint: Account<'info, Mint>,

    ///CHECK:
    #[account(
        mut,
        address=find_metadata_account(&nft_mint.key()).0
    )]
    pub metadata: UncheckedAccount<'info>,

    /// CHECK: master edition account
    #[account(
        mut,
        address=find_master_edition_account(&nft_mint.key()).0
    )]
    pub master_edition: UncheckedAccount<'info>,

    #[account(
        init,
        payer = payer,
        associated_token::mint = nft_mint,
        associated_token::authority = payer
    )]
    pub token_account: Account<'info, TokenAccount>,

    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub token_metadata_program: Program<'info, Metadata>,
    pub rent: Sysvar<'info, Rent>,
}

Here's a reference for how to set up the test:

import * as anchor from "@project-serum/anchor"
import * as spl from "@solana/spl-token"
import { Program } from "@project-serum/anchor"
import { AnchorNft } from "../target/types/anchor_nft"
import { Metaplex } from "@metaplex-foundation/js"
import {
  Metadata,
  PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID,
} from "@metaplex-foundation/mpl-token-metadata"
import { assert } from "chai"

describe("anchor-nft", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env())

  const program = anchor.workspace.AnchorNft as Program<AnchorNft>
  const wallet = anchor.workspace.AnchorNft.provider.wallet
  const connection = program.provider.connection
  const metaplex = Metaplex.make(connection)

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

  it("Is initialized!", async () => {
    const [pda] = anchor.web3.PublicKey.findProgramAddressSync(
      [Buffer.from("nft")],
      program.programId
    )

    const nftMint = anchor.web3.Keypair.generate()

    const metadata = await metaplex
      .nfts()
      .pdas()
      .metadata({ mint: nftMint.publicKey })

    const masterEdition = await metaplex
      .nfts()
      .pdas()
      .masterEdition({ mint: nftMint.publicKey })

    const tokenAddress = await spl.getAssociatedTokenAddress(
      nftMint.publicKey,
      wallet.publicKey
    )

    // Add your test here.
    const txSig = await program.methods
      .mintNft(testMetadata.uri, testMetadata.name, testMetadata.symbol)
      .accounts({
        payer: wallet.publicKey,
        pda: pda,
        nftMint: nftMint.publicKey,
        metadata: metadata,
        masterEdition: masterEdition,
        tokenAccount: tokenAddress,
        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
      })
      .signers([nftMint])
      .rpc()
    console.log("Your transaction signature", txSig)

    const accountInfo = await connection.getAccountInfo(metadata)
    const metadataData = Metadata.deserialize(accountInfo.data, 0)

    assert.ok(
      metadataData[0].data.uri.startsWith(testMetadata.uri),
      "URI in metadata does not start with expected URI"
    )
    assert.ok(
      metadataData[0].data.name.startsWith(testMetadata.name),
      "Name in metadata does not start with expected name"
    )
    assert.ok(
      metadataData[0].data.symbol.startsWith(testMetadata.symbol),
      "Symbol in metadata does not start with expected symbol"
    )

    assert.ok(
      metadataData[0].data.creators[0].address.equals(pda),
      "Creator is not PDA"
    )

    assert.ok(
      metadataData[0].data.creators[0].verified,
      "Creator is not verified"
    )

    assert.strictEqual(
      Number(
        (await connection.getTokenAccountBalance(tokenAddress)).value.amount
      ),
      1,
      "Token amount is not 1"
    )
  })
})

If you're testing locally, add this to Anchor.toml

[test.validator]
url = "https://api.mainnet-beta.solana.com"

[[test.validator.clone]]
address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"

Here are the dependencies in Cargo.toml

[dependencies]
anchor-lang = "0.26.0"
anchor-spl = {version = "0.26.0",features = ["metadata"]}
mpl-token-metadata = { version="1.8.3", features = [ "no-entrypoint" ] }
4
  • Hey John, thanks for the answer! One thing that I'm running into issues with is that the anchor_spl crate doesn't have a module called metadata. I'm not sure where that module can be found. Did you mean to use mpl_token_metadata? docs.rs/mpl-token-metadata/1.8.3/mpl_token_metadata/index.html
    – xkycc
    Feb 18, 2023 at 19:07
  • 1
    Just realized you have to explicitly enable metadata as a feature with anchor-spl, updated the response with the dependencies used in Cargo.toml
    – john
    Feb 18, 2023 at 20:44
  • The test is failing at this line: const txSig = await program.methods.mintNft(testMetadata.uri, testMetadata.name, testMetadata.symbol).accounts({payer: wallet.publicKey,pda: pda, nftMint: nftMint.publicKey, metadata: metadata, masterEdition: masterEdition, tokenAccount: tokenAddress, tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID, }).signers([nftMint]).rpc() Error Code: DeclaredProgramIdMismatch. Error Message: The declared program id does not match the actual program id.
    – xkycc
    Feb 19, 2023 at 0:11
  • check that declare_id! in lib.rs matches what's in Anchor.toml, then anchor build again. You can run anchor keys list to get the pubkey of the program_id that's generated for you in target/deploy. The example is just using the default placeholder one that comes with anchor init
    – john
    Feb 19, 2023 at 0:19

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.