create_associated_token_account CPI error 'unauthorized signer or writable account': funder comes first, pass system_program
Symptom: invoking spl_associated_token_account::instruction::create_associated_token_account from a native Solana program yields Cross-program invocation with unauthorized signer or writable account, or from the client side invalid account data for this instruction. The arguments all look like the right pubkeys.
Cause: two positional mistakes that the type system cannot catch, since every parameter is a &Pubkey.
1. Argument order is (funding_address, wallet_address, mint, token_program). Passing the derived ATA address first and the owner second — the intuitive reading of "create ATA for pool" — makes the instruction treat the ATA as the fee payer and signer, which is exactly what the error is complaining about.
2. The AccountInfo slice handed to invoke must contain every account the instruction references, in the instruction's order, including system_program, which is easy to omit because it appears in no argument.
Corrected shape:
invoke( &spl_associated_token_account::instruction::create_associated_token_account( &pool_address, // funder &my_ata_address, // wallet whose ATA is created tokenx_mint.key, token_program.key, ), &[ pool_account.clone(), last_ata.clone(), tokenx_mint.clone(), system_program.clone(), token_program.clone(), ], )?;
Additional constraint: if the funder is a PDA of your program, invoke cannot work at all — the funder must sign, so use invoke_signed with the seeds and bump used to derive it. A PDA never carries signer status implicitly.
Why the message misleads: "unauthorized signer" reads like a missing #[account(mut, signer)] or a wallet problem, when the real fault is that a positional swap made the wrong account the signer.
Source: adapted from a Stack Exchange answer by Jon C (question by Berkeley Cinar), https://solana.stackexchange.com/questions/9621/cross-program-invocation-with-unauthorized-signer-or-writable-account-i-cant — CC BY-SA 4.0