I'm having problems doing what I thought would be a straightforward token transfer.
First some code:
#[derive(Accounts)]
#[instruction(amount: u64)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub sender: Signer<'info>,
#[account(mut)]
pub sender_tokens: Account<'info, TokenAccount>,
pub recipient_tokens: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
#[account(address = SYSTEM_PROGRAM_ID)]
pub system_program: Program<'info, System>,
#[account(address = TOKEN_PROGRAM_ID)]
pub token_program: Program<'info, Token>,
}
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> ProgramResult {
let sender = &ctx.accounts.sender;
let sender_tokens = &ctx.accounts.sender_tokens;
let recipient_tokens = &ctx.accounts.recipient_tokens;
let token_program = &ctx.accounts.token_program;
transfer(
CpiContext::new(
token_program.to_account_info(),
Transfer {
from: sender_tokens.to_account_info(),
to: recipient_tokens.to_account_info(),
authority: sender.to_account_info(),
},
),
amount,
)?;
return Ok(());
}
const mint = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); // USDC devnet
const sender = program.provider.wallet.publicKey;
const recipient = new PublicKey(otherPublicKey);
const senderATA = await getOrCreateAssociatedTokenAccount(...);
const recipientATA = await getOrCreateAssociatedTokenAccount(...);
let instructions: TransactionInstruction[];
if (senderATA.instruction) instructions.push(senderATA.instruction);
if (recipientATA.instruction) instructions.push(recipientATA.instruction);
if (instructions.length === 0) instructions = undefined;
const price = 1000;
await program.rpc.transferTokens(new BN(price), {
accounts: {
sender: sender,
senderTokens: senderATA.address,
recipientTokens: recipientATA.address,
mint,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID
},
instructions
});
When I'm running this I get:
Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
Program XXX invoke [1]
recipientATA.address's writable privilege escalated
Program XXX consumed 9908 of 200000 compute units
Program XXX failed: Cross-program invocation with unauthorized signer or writable account
Obviously the sender has to sign the transaction, but I guess I don't really understand why I need permission to send tokens to someone else.
The JS code is slightly abbreviated, but what I'm doing is that I'm adding instructions to create the token accounts unless they exist. The weird thing is that this works the first time when I'm including the instructions to create the token accounts, but after that the "writable privilege escalated" error happens.
Can anyone see what I'm doing wrong?