1

I was working on an anchor program that when I tested gave me the error "Transaction simulation failed: Error processing Instruction 0: An account required by the instruction is missing". I was wondering if I'm missing any accounts from my definition! The instruction I defined here is meant to transfer an spl token from my escrow wallet (created as a PDA) into my receiving user (who I set as the signer of the transaction). Thank you! My code is as follows:

   #[derive(Accounts)]
    #[instruction(application_idx: i64)]
    pub struct CompleteExchange<'info> {
        #[account(
            mut,
            seeds=[
                b"exchange".as_ref(), 
                user_sending.key.as_ref(), 
                ],
            bump,
            has_one = user_sending,
            has_one = user_receiving,
            has_one = mint_of_token_being_sent,
        )]
        application_exchange: Account<'info, Exchange>,
    
        #[account(
            mut,
            seeds=[
                b"wallet".as_ref(), 
                user_sending.key.as_ref(), 
                ],
            bump,
        )]
        escrow_wallet_exchange: Account<'info, TokenAccount>,
    
        #[account(
            init_if_needed,
            payer = user_receiving,
            associated_token::mint = mint_of_token_being_sent,
            associated_token::authority = user_receiving,
        )]
        //Initialized user_receiving wallet if it is initially non existent
        wallet_to_deposit_to: Account<'info, TokenAccount>,
    
        ///CHECK: Not dangerous
        user_sending: AccountInfo<'info>, 
    
        #[account(mut)]
        user_receiving: Signer<'info>, 
        #[account(mut)]
        mint_of_token_being_sent: Account<'info, Mint>,
    
        //App level programs
        system_program: Program<'info, System>,
        token_program: Program<'info, Token>,
        //AssociatedToken allows program to send to user_receiving even if they dont have the required token account
        //Done so with the init_if_needed clause in account definition
        associated_token_program: Program<'info, AssociatedToken>,
        rent: Sysvar<'info, Rent>,
    }

pub fn handler(
    ctx: Context<CompleteExchange>, 
    application_idx: i64,  ) -> Result<()> {
        if stages::Stage::from(ctx.accounts.application_exchange.stage)? != stages::Stage::FundsDeposited {
            msg!("Current stage invalid for exchange, stage is {}", ctx.accounts.application_exchange.stage);
            return err!(ErrorCodes::StageInvalid);
        }

        //Run transfer function below
        transfer_escrow_out(
            ctx.accounts.user_sending.to_account_info(),
            ctx.accounts.user_receiving.to_account_info(),
            ctx.accounts.mint_of_token_being_sent.to_account_info(),
            &mut ctx.accounts.escrow_wallet_exchange,
            application_idx,
            *ctx.bumps.get("exchange").unwrap(),
            ctx.accounts.application_exchange.to_account_info(),
            ctx.accounts.token_program.to_account_info(),
            ctx.accounts.wallet_to_deposit_to.to_account_info(),
            ctx.accounts.application_exchange.amount_tokens.try_into().unwrap(),
        )?;

        let exchange = &mut ctx.accounts.application_exchange;
        exchange.stage = stages::Stage::EscrowComplete.to_code();
        Ok(())
    }

//Function to close wallet once payment is completed //user_sending gets back rent lamports, and blockchain won't be polluted 

fn transfer_escrow_out<'info>(
        user_sending: AccountInfo<'info>,
        user_receiving: AccountInfo<'info>,
        mint_of_token_being_sent: AccountInfo<'info>,
        escrow_wallet: &mut Account<'info, TokenAccount>,
        application_idx: i64,
        exchange_bump: u8,
        exchange: AccountInfo<'info>,
        token_program: AccountInfo<'info>,
        //Public key of where to send funds to
        destination_wallet: AccountInfo<'info>,
        amount: u64 ) -> Result<()> {
        //Computing signer seeds fo signing on behalf of PDA
        //Taking unique keys of all relevant accounts
        let bump_vector = exchange_bump.to_le_bytes();
        let mint_of_token_being_sent_pk = mint_of_token_being_sent.key().clone();
        let application_idx_bytes = application_idx.to_le_bytes();
        let inner = vec![
            b"exchange".as_ref(),
            user_sending.key.as_ref(),
        ];
        let outer = vec![inner.as_slice()];
    
        //Actual transfer from escrow wallet to user_receiving
        let transfer_instruction = Transfer{
            from: escrow_wallet.to_account_info(),
            to: destination_wallet.to_account_info(),
            authority: exchange.to_account_info(),
        };
    
        let cpi_ctx = CpiContext::new_with_signer(
            token_program.to_account_info(),
            transfer_instruction,
            outer.as_slice(),
        );
        anchor_spl::token::transfer(cpi_ctx, amount)?;
    
        //Reload function to set amount variable back to zero
        let should_close = {
            escrow_wallet.reload()?;
            escrow_wallet.amount == 0
        };
    
        //Wipe out token account once transfer is made
        if should_close {
            let ca = CloseAccount{
                account: escrow_wallet.to_account_info(),
                destination: user_sending.to_account_info(),
                authority: exchange.to_account_info(),
            };
    
            let cpi_ctx = CpiContext::new_with_signer(
                token_program.to_account_info(),
                ca,
                outer.as_slice(),
            );
            
            anchor_spl::token::close_account(cpi_ctx)?;
        }
        Ok(()) }

And the test code is as such:

const createUserAndAssociatedWallet = async (
    mint?: anchor.web3.PublicKey, //Optional value   ): Promise<[anchor.web3.Keypair, anchor.web3.PublicKey | undefined]> => {
    const user = new anchor.web3.Keypair();
    //Create account for user to store given mint
    //Initialize as undefined first
    let userAssociatedTokenAccount: anchor.web3.PublicKey | undefined = undefined;

    //Add some SOL to user account to keep it rent exempt
    let txFund = new anchor.web3.Transaction();

    txFund.add(
      anchor.web3.SystemProgram.transfer({
        fromPubkey: anchorProvider.wallet.publicKey,
        toPubkey: user.publicKey,
        lamports: 50 * LAMPORTS_PER_SOL,
      })
    );

    const sigTxFund = await anchorProvider.sendAndConfirm(
      txFund, 
    );

    console.log(user.publicKey.toBase58().substring(0,5), "funded new account with 5 SOL: ", sigTxFund.substring(0,5));

    //If mint is provided, create token account and mint some tokens
    if (mint) {
      userAssociatedTokenAccount = await spl.getAssociatedTokenAddress(
          mint,
          user.publicKey,
          false,
          spl.TOKEN_PROGRAM_ID,
          spl.ASSOCIATED_TOKEN_PROGRAM_ID,
      );

      const txFundTokenAccount = new anchor.web3.Transaction();
      txFundTokenAccount.add(
          spl.createAssociatedTokenAccountInstruction(     
              user.publicKey,
              userAssociatedTokenAccount,
              user.publicKey,
              mint,
              spl.TOKEN_PROGRAM_ID,
              spl.ASSOCIATED_TOKEN_PROGRAM_ID,
          )
      );
    
      txFundTokenAccount.add(
        spl.createMintToInstruction(
          mint,
          userAssociatedTokenAccount,
          anchorProvider.wallet.publicKey,
          1337000000,
          [],
          spl.TOKEN_PROGRAM_ID,
      ));

      console.log("")
      console.log(txFundTokenAccount.signatures.toString().substring(0,5));

      const txFundTokenSig = await anchorProvider.sendAndConfirm(txFundTokenAccount, [user]);
      console.log(userAssociatedTokenAccount.toBase58().substring(0,5), "New associated account for mint", mint.toBase58().substring(0,5), ":", txFundTokenSig.substring(0,5));
    }
      return [user, userAssociatedTokenAccount];   }

    const getPDAparams = async(
        connection: anchor.web3.Connection, 
        sender?: anchor.web3.PublicKey,
        receiver?: anchor.web3.PublicKey,
        token?: anchor.web3.PublicKey): Promise<PDAParams> => {
        //Get timestamp for transaction
        const uid = new anchor.BN(parseInt((Date.now() / 1000).toString()));
        const uidbuffer = uid.toBuffer("le", 8);
    
        //findProgramAddress: 
        //Takes in seeds and program ID
        //Return a public key of PDA and checks that the pubkey is on the ed25519 curve
        let [exchangePubKey,] = await anchor.web3.PublicKey.findProgramAddress(
          [
            utf8.encode("exchange"), 
            sender.toBuffer(), 
          ],
          program.programId,
        );
    
        let [walletPubKey,] = await anchor.web3.PublicKey.findProgramAddress(
          [
            utf8.encode("wallet"), 
            sender.toBuffer(), 
          ],
          program.programId,
        );
    
        return {
          escrowWalletKey: walletPubKey,
          exchangeKey: exchangePubKey,
          idx: uid,
        }

beforeEach(async() => {
      mintAddress = await createMint();
      [sender, senderWallet] = await createUserAndAssociatedWallet(
          mintAddress!,
      );
      console.log(sender.publicKey.toString().substring(0,5));

      let _rest;
      [receiver, ..._rest] = await createUserAndAssociatedWallet();

      pda = await getPDAparams(
          anchorProvider.connection,
          sender.publicKey,
          receiver.publicKey,
          mintAddress,
      )
    });
    
    const receiverTokenAccount = await spl.getAssociatedTokenAddress(
              mintAddress,
              spl.ASSOCIATED_TOKEN_PROGRAM_ID,
              true,
              spl.TOKEN_PROGRAM_ID,
              receiver.publicKey,
          )
    
          console.log("ASSOCIATED TOKEN ACCOUNT OBTAINED");
      
          const tx2 = await program.methods
          .completeExchange(
            pda.idx,
          )
          .accounts({
            applicationExchange: pda.exchangeKey,
            escrowWalletExchange: pda.escrowWalletKey,
            walletToDepositTo: receiverTokenAccount,
            userSending: sender.publicKey,
            userReceiving: receiver.publicKey,
            mintOfTokenBeingSent: mintAddress,
            systemProgram: anchor.web3.SystemProgram.programId,
            tokenProgram: spl.TOKEN_PROGRAM_ID,
            associatedTokenProgram: spl.ASSOCIATED_TOKEN_PROGRAM_ID,
            rent: anchor.web3.SYSVAR_RENT_PUBKEY,
          })
          .signers([receiver])
          .rpc();

I have an instruction InitExchange that created the escrow PDA successfully and transferred the tokens to it successfully but did not include that code to be concise. Thank you!

3
  • Since you are getting a simulation error can you replace .rpc() with .rpc({ skipPreflight: true, }) and run the transaction again and check the error
    – Pratik.js
    Commented Sep 1, 2022 at 18:53
  • What happens if you drop systemProgram, tokenProgram, associatedTokenProgram and rent from the .accounts() arg. Also yeah, please skip the preflight so we get a decent error back.,
    – sohrab
    Commented Sep 2, 2022 at 12:27
  • This gives me a new error: Error: Raw transaction 3oqwPUcuD2kP28XTLq9XKEa5H3W4DC2RBk4MjJpXoLxoATi41mniiWWNQZZTuwP78s8wAzUvxehGSVDZJxR4XUK failed ({"err":{"InstructionError":[0,"MissingAccount"]}}) not very sure what this one means!
    – Aengus
    Commented Sep 4, 2022 at 9:40

1 Answer 1

1
+50

As far as I can see you're passing in all the account arguments. The problem is likely that one of those parameters is a mismatch and so the program still doesn't get all the accounts it expects. You're probably generating the receiver's token account wrongly.

Derive the receiver's ATA like this instead:

const receiverTokenAccount = await spl.createAssociatedTokenAccount(
  connection,
  receiver,
  mintAddress,
  receiver.publicKey  
);

OR using the same function you used above but correctly:

const receiverTokenAccount = await spl.getAssociatedTokenAddress(
  mintAddress.publicKey, receiver);

There's more than one problem with how the spl.getAssociatedTokenAddress() function is called in your code.

2
  • I actually did this already (i think) in a beforeEach hook that I have now added into the code above. But when I changed the code to this it told me that there is no record of a prior credit in the account.
    – Aengus
    Commented Sep 4, 2022 at 9:57
  • Yeah that's likely because your receiver wallet has to pay to create its ATA. The error is telling you that the wallet has not been credited and thus doesn't have enough sol to pay for the transaction. If you're writing test code on devnet, airdrop sol to the receiver account.
    – Ademola
    Commented Sep 4, 2022 at 20:50

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.