6

Trying to create a program that has the authority to mint tokens to the wallet signing the transaction. However, one of my tests keeps failing with: "insufficient account keys for instruction"

  pda-airdrop
    ✔ can create airdrop account (444ms)
    1) can airdrop token to account


  1 passing (604ms)
  1 failing

  1) pda-airdrop
       can airdrop token to account:
     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: insufficient account keys for instruction
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:4480:13)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:4439:20)
      at sendAndConfirmRawTransaction (node_modules/@project-serum/anchor/src/provider.ts:288:21)
      at AnchorProvider.sendAndConfirm (node_modules/@project-serum/anchor/src/provider.ts:148:14)
      at MethodsBuilder.rpc [as _rpcFn] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:29:16)

Here is my program:

use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token::{mint_to, Mint, MintTo, Token, TokenAccount},
};

declare_id!("AUrpX9QjAFeKBSBC2acgYHzBUud6xeiR2VvmiJSdoHqk");

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

    pub fn create_airdop(_ctx: Context<CreateAirdrop>) -> Result<()> {
        Ok(())
    }

    pub fn execute_airdrop(ctx: Context<ExecuteAirdrop>, amount: u64) -> Result<()> {
        // Find bump for mint account created in create_airdrop
        let (_, bump) = Pubkey::find_program_address(
            &[], 
            ctx.program_id
        );

        // mint amount to associated token account
        let token_program = ctx.accounts.token_program.to_account_info();
        let mint_to_accounts = MintTo {
            mint: ctx.accounts.mint.to_account_info(),
            to: ctx.accounts.ata.to_account_info(),
            authority: ctx.accounts.mint.to_account_info(),
        };
        mint_to(
            CpiContext::new_with_signer(
                token_program, 
                mint_to_accounts, 
                &[&[
                    &[],
                    &[bump]
                ]]
            ), 
            amount
        )?;

        Ok(())
    }
}

// Instruction: Create Mint Account for Airdrop
#[derive(Accounts)]
pub struct CreateAirdrop<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init, 
        payer = signer, 
        seeds = [],
        bump,
        mint::decimals = 0, 
        mint::authority = mint
    )]
    pub mint: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}

// Instruction: Execute Airdrop
#[derive(Accounts)]
pub struct ExecuteAirdrop<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(mut)]
    pub mint: Account<'info, Mint>,
    #[account(init_if_needed, payer = signer, associated_token::mint = mint, associated_token::authority = signer)]
    pub ata: Account<'info, TokenAccount>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}

And here are the tests I'm running:

import * as anchor from "@project-serum/anchor";
import { Program, web3 } from "@project-serum/anchor";
import { PdaAirdrop } from "../target/types/pda_airdrop";
import { expect } from "chai";
import { getMint, getAssociatedTokenAddress } from "@solana/spl-token";

describe("pda-airdrop", () => {
  const program = anchor.workspace.PdaAirdrop as Program<PdaAirdrop>;
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  it("can create airdrop account", async () => {
    const [mint, _bump] = await web3.PublicKey.findProgramAddress(
      [],
      program.programId
    );

    await program.methods
      .createAirdop()
      .accounts({
        mint,
      })
      .rpc();

    const mintAccount = await getMint(provider.connection, mint);

    expect(mintAccount.isInitialized).eql(true);
  });

  it("can airdrop token to account", async () => {
    const [mint, _bump] = await web3.PublicKey.findProgramAddress(
      [],
      program.programId
    );

    const ata = await getAssociatedTokenAddress(
      mint,
      provider.wallet.publicKey
    );

    const supply = new anchor.BN(200);
    await program.methods
      .executeAirdrop(supply)
      .accounts({
        mint,
        ata,
      })
      .rpc();

    const mintAccount = await getMint(provider.connection, mint);

    expect(mintAccount.supply).equal(supply);
  });
});

The dependencies in my Cargo.toml file look like this if that helps:

[dependencies]
anchor-lang = { version = "0.25.0", features = ["init-if-needed"] }
anchor-spl = "0.25.0"

Any idea what I'm missing here?

3 Answers 3

5

Alright finally came up with a solution that worked for me. That is, a solution that

  • Didn't require me to pass in common accounts like system program
  • Allowed me to set the mint account as the mint authority
  • Used Anchor's "automatic bump calculation" feature

First I needed to set seeds to true in Anchor.toml in order to generate PDAs without me having to pass them into the function calls myself. You can read more on that here https://github.com/coral-xyz/anchor/issues/1004#issuecomment-1020478661

Anchor.toml

[features]
seeds = true
...

lib.rs was pretty much the same, but I made sure to include the seeds and bump constraints for the mint account so that I wouldn't have to pass these values into the function call myself

use anchor_lang::prelude::*;
use anchor_spl::{
    token::{mint_to, Mint, MintTo, Token, TokenAccount},
};

declare_id!("AUrpX9QjAFeKBSBC2acgYHzBUud6xeiR2VvmiJSdoHqk");

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

    pub fn create_airdrop_mint(_ctx: Context<CreateAirdropMint>) -> Result<()> {
        Ok(())
    }

    pub fn execute_airdrop(ctx: Context<ExecuteAirdrop>, amount: u64) -> Result<()> {
        // mint amount to associated token account
        let token_program = ctx.accounts.token_program.to_account_info();
        let mint_to_accounts = MintTo {
            mint: ctx.accounts.mint.to_account_info(),
            to: ctx.accounts.ata.to_account_info(),
            authority: ctx.accounts.mint.to_account_info(),
        };
        let bump = *ctx.bumps.get("mint").unwrap();
        mint_to(
            CpiContext::new_with_signer(
                token_program, 
                mint_to_accounts, 
                &[&[
                    b"mint",
                    &[bump]
                ]]
            ), 
            amount
        )?;

        Ok(())
    }
}

// Instruction: Create Mint Account for Airdrop
#[derive(Accounts)]
pub struct CreateAirdropMint<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init, 
        seeds = [b"mint".as_ref()], 
        bump,
        payer = signer,
        mint::decimals = 0, 
        mint::authority = mint
    )]
    pub mint: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}

// Instruction: Execute Airdrop
#[derive(Accounts)]
pub struct ExecuteAirdrop<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(mut, seeds = [b"mint".as_ref()], bump)]
    pub mint: Account<'info, Mint>,
    #[account(mut)]
    pub ata: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}

Finally, when testing, I needed to create the associated token account (using createAssociatedTokenAccount), then pass it to my program. Not sure why I kept getting errors when trying to init the associated token account within the program, but this is what worked for me. If anyone knows why I may have had issues definitely let me know!

import * as anchor from "@project-serum/anchor";
import { Program, web3 } from "@project-serum/anchor";
import { PdaAirdrop } from "../target/types/pda_airdrop";
import { expect } from "chai";
import { getMint, createAssociatedTokenAccount } from "@solana/spl-token";

describe("pda-airdrop", () => {
  const program = anchor.workspace.PdaAirdrop as Program<PdaAirdrop>;
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const signer = provider.wallet as anchor.Wallet;

  it("can create mint account for airdrop", async () => {
    await program.methods.createAirdropMint().rpc();

    const [mint] = await web3.PublicKey.findProgramAddress(
      [Buffer.from("mint")],
      program.programId
    );

    const mintAccount = await getMint(provider.connection, mint);

    expect(mintAccount.isInitialized).eql(true);
  });

  it("can airdrop token to associated token account", async () => {
    const [mint] = await web3.PublicKey.findProgramAddress(
      [Buffer.from("mint")],
      program.programId
    );

    const ata = await createAssociatedTokenAccount(
      provider.connection,
      signer.payer,
      mint,
      signer.publicKey
    );

    const supply = new anchor.BN(200);
    await program.methods
      .executeAirdrop(supply)
      .accounts({
        ata,
      })
      .rpc();

    const mintAccount = await getMint(provider.connection, mint);

    expect(Number(mintAccount.supply)).equal(supply.toNumber());
  });
});

Now everything is passing :)

  pda-airdrop
    ✔ can create mint account for airdrop (338ms)
    ✔ can airdrop token to associated token account (818ms)

Anchor Version: 0.25.0

Update: Fixed the issue with not being able to init the associated token account within my program by downgrading anchor to 0.24.2. Not sure what's going on in 0.25.0. Anyways, full repo can be found here

Update 2: Anchor 0.25.0 wasn't the issue. My solana cli being out of date was the issue. Upgrading from 1.9.x to 1.14.x allowed me to run my program with Anchor 0.25.0 https://docs.solana.com/cli/install-solana-cli-tools

3
  • Thanks for this! I was going crazy trying to figure out why using init_if_needed to create a new ATA wasn't working. I keep getting: Error: Signature verification failed. I'm using 0.25.0. Did you ever get it to work with 0.25.0 ? Commented Sep 15, 2022 at 2:34
  • 1
    Np! And unfortunately I did not. Still sticking with 0.24.2 for now :/
    – Mike
    Commented Sep 16, 2022 at 3:04
  • Alright I was FINALLY able to upgrade to 0.25 :). I just needed to upgrade my solana cli from 1.9.x to 1.14.x docs.solana.com/cli/install-solana-cli-tools Found the answer here solana.stackexchange.com/questions/3094/…
    – Mike
    Commented Sep 21, 2022 at 14:35
2

Try this:

// Instruction: Create Mint Account for Airdrop
#[derive(Accounts)]
pub struct CreateAirdrop<'info> {
    ...
    #[account(
        init, 
        payer = signer, 
        mint::decimals = 0, 
        mint::authority = mint_authority.key()
    )]
    pub mint_account: Account<'info, 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]
pub struct MintAuthorityPda {}

// Instruction: Execute Airdrop
#[derive(Accounts)]
#[instruction(amount: u64, mint_authority_pda_bump: u8)]
pub struct ExecuteAirdrop<'info> {
    ...
    #[account(
        mut,
        mint::decimals = 0,
        mint::authority = mint_authority.key(),
    )]
    pub mint_account: Account<'info, Mint>,
    #[account(
        mut, 
        seeds = [
            b"mint_authority_", 
            mint_account.key().as_ref()
        ],
        bump = mint_authority_pda_bump
    )]
    pub mint_authority: Account<'info, MintAuthorityPda>,
    ...
}

The biggest key here is make sure this annotation includes all parameters from your function header except for the context - the bump being the one we need:

#[instruction(amount: u64, mint_authority_pda_bump: u8)]

Then:

let mint_to_accounts = MintTo {
    mint: ctx.accounts.mint_account.to_account_info(),
    to: ctx.accounts.ata.to_account_info(),
    authority: ctx.accounts.mint_authority.to_account_info(),
};

You can make the seeds for the mint authority whatever you'd like.

You can see a full example here.

1
  • 1
    Thanks for the help here! I should have specified that I wanted to take advantage of Anchor's feature to automatically calculate bumps. I did try your suggestion though. I still got errors, but it's possible I missed something when trying to implement your answer. The repo you linked is amazing though and I'll definitely be referencing it when it comes to integrating Metaplex Token Metadata!
    – Mike
    Commented Jul 22, 2022 at 9:34
1

Hello so it can be seen in your Contexts that you use the system_program. Remember that even programs are accounts just executable versions of it.

It can be seen in this code block

#[derive(Accounts)]
 pub struct CreateAirdrop<'info> {
/* SNIPPED */
pub system_program: Program<'info, System>,

}

But in your client code you don't have the SystemProgram account in your rpc call. As seen below

 await program.methods
  .createAirdop()
  .accounts({
    mint,
    /* no system_program here */
  })
  .rpc()

I hope this gave you some clarity and direction.

2
  • Thanks for the help! System Program wasn't the culprit here. Anchor has actually put out some updates that removes the need to add system program when using the ts builder api twitter.com/anchorlang/status/…
    – Mike
    Commented Jul 22, 2022 at 9:26
  • I guess my methods are a bit outdated! Thanks for the heads up, Mike! Commented Jul 22, 2022 at 9:53

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.