3

I am struggling with creating a program that creates an Associated Token Account (ATA) for a mint I created. I am using the anchor_spl::associated_token for the task. Here is my code:

use anchor_lang::prelude::*;

use anchor_spl::token::{Token, Mint};
use anchor_spl::associated_token::{self, AssociatedToken, Create};

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod token_util {

    use super::*;

    pub fn create_ata(ctx: Context<CreateAta>) -> Result<()> {

        let cpi_accounts = Create {
            payer: ctx.accounts.payer.to_account_info(),
            associated_token: ctx.accounts.token_account.to_account_info(),
            authority: ctx.accounts.payer.to_account_info(),
            mint: ctx.accounts.mint.to_account_info(),
            
            system_program: ctx.accounts.system_program.to_account_info(),
            token_program: ctx.accounts.token_program.to_account_info(),
            rent: ctx.accounts.rent.to_account_info(),
        };

        let cpi_program = ctx.accounts.associated_token_program.to_account_info();

        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
        associated_token::create(cpi_ctx)
    }
}

#[derive(Accounts)]
pub struct Purchase<'CreateAta> {

    #[account(mut)]
    pub payer: Signer<'info>,

    /// CHECK: new token account
    #[account(mut)]
    pub token_account: UncheckedAccount<'info>,

    pub mint: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub rent: Sysvar<'info, Rent>,
}

The PDA I pass via token_account is correctly derived using the seeds that Associated Token Program is expecting:

const [pda, bump] = await anchor.web3.PublicKey.findProgramAddress(
      [payer.publicKey.toBytes(), TOKEN_PROGRAM_ID.toBytes(), mint.toBytes()],
      ASSOCIATED_TOKEN_PROGRAM_ID
    );

Still, I am getting the following error message when invoking the function create_ata:

Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: insufficient account keys for instruction

Could it be, that a signer is missing? Maybe the token_account needs to sign the CPI. But I cannot do this via my own program since it is a PDA that needs to be derived from the Associated Token Program.

2 Answers 2

7

try using the init_if_needed constraint here's a Solpg reference you can test: https://beta.solpg.io/631a439777ea7f12846aee89

#[program]
mod example {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

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

You'll also need to update Cargo.toml in your project to use init_if_needed

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

You can get the associated token account address using getAssociatedTokenAddress

import { getAssociatedTokenAddress } from "@solana/spl-token"

const tokenAddress = await getAssociatedTokenAddress(
   mint,
   user.publicKey
)

You can then call the function in your Anchor tests file like this

import * as anchor from "@project-serum/anchor"
import { Program } from "@project-serum/anchor"
import {
  getAssociatedTokenAddress,
  createMint,
  TOKEN_PROGRAM_ID,
  ASSOCIATED_TOKEN_PROGRAM_ID,
  getAccount,
} from "@solana/spl-token"
import { Example } from "../target/types/example"

describe("Example", () => {
  anchor.setProvider(anchor.AnchorProvider.env())

  const program = anchor.workspace.Example as Program<Example>
  const connection = anchor.getProvider().connection
  const userWallet = anchor.workspace.Example.provider.wallet

  it("Test Instruction", async () => {
    const mint = await createMint(
      connection,
      userWallet.payer,
      userWallet.publicKey,
      userWallet.publicKey,
      6
    )

    const tokenAccountAddress = await getAssociatedTokenAddress(
      mint,
      userWallet.publicKey
    )

    await program.methods
      .initialize()
      .accounts({
        payer: userWallet.publicKey,
        tokenAccount: tokenAccountAddress,
        mint: mint,
        tokenProgram: TOKEN_PROGRAM_ID,
        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
        systemProgram: anchor.web3.SystemProgram.programId,
        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
      })
      .rpc()

    const account = await getAccount(connection, tokenAccountAddress)
    console.log(account)
  })
})

Anchor can infer some of the accounts, so you can also shorten to this

await program.methods
  .initialize()
  .accounts({
    tokenAccount: tokenAccountAddress,
    mint: mint,
  })
  .rpc()

You can run anchor expand to see all the code that Anchor generates for you. Below is the code snippet that relates to init_if_needed

let token_account: anchor_lang::accounts::account::Account<TokenAccount> = {
    if !true
    || AsRef::<AccountInfo>::as_ref(&token_account).owner
    == &anchor_lang::solana_program::system_program::ID
    {
      let payer = payer.to_account_info();
      let cpi_program = associated_token_program.to_account_info();
      let cpi_accounts = anchor_spl::associated_token::Create {
        payer: payer.to_account_info(),
        associated_token: token_account.to_account_info(),
        authority: payer.to_account_info(),
        mint: mint.to_account_info(),
        system_program: system_program.to_account_info(),
        token_program: token_program.to_account_info(),
        rent: rent.to_account_info(),
      };
      let cpi_ctx = anchor_lang::context::CpiContext::new(
        cpi_program,
        cpi_accounts,
      );
      anchor_spl::associated_token::create(cpi_ctx)?;
    }
  ...
}
9
  • Still getting the same error: insufficient account keys for instruction. Sep 8, 2022 at 19:51
  • Can you share any updates you've made to the instruction and how you're building the transaction? Also, you can get the ATA using getAssociatedTokenAddress from @solana/web3.js
    – john
    Sep 8, 2022 at 20:51
  • this is how I am calling the function: beta.solpg.io/631ae83477ea7f12846aee8b Sep 9, 2022 at 7:12
  • I tried to call the instruction the way you set it up and it seems to work, copy pasted into the post. Maybe double check the way you set up provider.wallet.publicKey?
    – john
    Sep 9, 2022 at 8:37
  • what network are you using? I tried to submit the transaction in an Anchor test, submitting to the default local network that is started when you execute a test suite on Anchor. I also tried with a local network that I manually started via solana-test-validator and then started the test with the --skip-local-validator. Sep 9, 2022 at 8:41
0

I found the problem: I was using an older Solana-CLI version that did not support the newer instructions of spl_associated_token_account that anchor_spl is using. Upgrading my Solana-CLI from 1.9.29 to 1.11.10 did the trick. Here is a more detailed explanation of the issue: solana.stackexchange.com/a/2873/1138

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.