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!
systemProgram
,tokenProgram
,associatedTokenProgram
andrent
from the.accounts()
arg. Also yeah, please skip the preflight so we get a decent error back.,