6

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?

2 Answers2

2

When you transfer tokens from one user to another, both TokenAccounts must be marked as mutable, because under the hood you're subtracting from the amount in one and adding to the amount in the other!

In other words, you need to add #[account(mut)] to both the receiving token account AND the mint it's associated with.

#[account(mut)]
pub recipient_tokens: Account<'info, TokenAccount>,

// ...

#[account(mut)]
pub mint: Account<'info, Mint>,
Evan Conrad
  • 3,993
  • 4
  • 28
  • 46
0

Both the sender_tokens and recipient_tokens need to be mut (writable) because the token program needs to change both of their balances.

The mint account can be omitted completely because the token program just makes sure both accounts are from the same mint pubkey. The actual data contained in the mint account is not needed for anything during a transfer. Same goes for system program, it is not needed. And the sender doesn't need to be writable, only sender tokens. Also the address constraint on the token program is redundant with Program<'info, Token>. Also I don't see a need for the amount parameter being provided in the accounts struct.

#[derive(Accounts)]
pub struct TransferTokens<'info> {
    pub sender: Signer<'info>,
    #[account(mut)]
    pub sender_tokens: Account<'info, TokenAccount>,
    #[account(mut)]
    pub recipient_tokens: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
}
Drew Nutter
  • 955
  • 8
  • 14