4

I am trying to make a SOL transfer from the bank account into the client account, but the operation fails with the following error: Cross-program invocation with unauthorized signer or writable account which is strange because all involved accounts are writable and a program derived address (PDA) should be able to sign the instruction (If I understood the docs correctly).

This is my code:

#[derive(Accounts)]
pub struct ClaimPayment<'info> {
    #[account(mut)]
    pub client: Signer<'info>,
    #[account(mut)]
    pub bank: Account<'info, Bank>,
    #[account(
        mut,
        seeds = [client.key().as_ref(), bank.key().as_ref()],
        bump = client_pda.bump
    )]
    pub client_pda: Account<'info, ClientPDA>,
    pub system_program: Program<'info, System>,
}

pub fn claim_payment(ctx: Context<ClaimPayment>, payment: u64) -> Result<()> {
    let seeds = &[
        ctx.accounts.client.to_account_info().key.as_ref(),
        ctx.accounts.bank.to_account_info().key.as_ref(),
        &[ctx.accounts.client_pda.bump],
    ];
    let signer_seeds = &[&seeds[..]];
    let transfer_cpi_context = CpiContext::new(
        ctx.accounts.system_program.to_account_info(),
        system_program::Transfer {
            from: ctx.accounts.bank.to_account_info(),
            to: ctx.accounts.client.to_account_info(),
        },
    )
    .with_signer(signer_seeds);
    system_program::transfer(transfer_cpi_context, payment)?;
    let client_pda = &mut ctx.accounts.client_pda;
    client_pda.paid = true;
    let bank = &mut ctx.accounts.bank;
    bank.payments += 1;
    Ok(())
}

And the Javascript for testing the method:

let [clientPda, bump] = await PublicKey.findProgramAddress([provider.publicKey.toBuffer(), BANK_KEYPAIR.publicKey.toBuffer()], program.programId);
await program.methods
  .claimPayment(1_000_000_000)
  .accounts({
    client: provider.publicKey,
    bank: BANK_KEYPAIR.publicKey,
    clientPda: clientPda,
  })
  .rpc();

Somebody knows which could be the problem? Would it make any difference if I use a PDA for bank instead of a regular account?

2 Answers 2

4

For a system program transfer instruction, the funding account (the one sending the SOL) must sign the instruction, in your case it needs to be signed by the bank account. But since it's not a PDA this can't be done inside the program.

You are using the client_pda to sign the transfer instead of using the bank account which is why you see this Cross-program invocation with unauthorized signer or writable account.

You can use the code suggested in Solana Cookbook to transfer lamports from your program owned accounts to any account at all:

/// Transfers lamports from one account (must be program owned)
/// to another account. The recipient can by any account
fn transfer_service_fee_lamports(
    from_account: &AccountInfo,
    to_account: &AccountInfo,
    amount_of_lamports: u64,
) -> ProgramResult {
    // Does the from account have enough lamports to transfer?
    if **from_account.try_borrow_lamports()? < amount_of_lamports {
        return Err(CustomError::InsufficientFundsForTransaction.into());
    }
    // Debit from_account and credit to_account
    **from_account.try_borrow_mut_lamports()? -= amount_of_lamports;
    **to_account.try_borrow_mut_lamports()? += amount_of_lamports;
    Ok(())
}

/// Primary function handler associated with instruction sent
/// to your program
fn instruction_handler(accounts: &[AccountInfo]) -> ProgramResult {
    // Get the 'from' and 'to' accounts
    let account_info_iter = &mut accounts.iter();
    let from_account = next_account_info(account_info_iter)?;
    let to_service_account = next_account_info(account_info_iter)?;

    // Extract a service 'fee' of 5 lamports for performing this instruction
    transfer_service_fee_lamports(from_account, to_service_account, 5u64)?;

    // Perform the primary instruction
    // ... etc.

    Ok(())
}
1
  • Can I still use instruction to transfer SOL? Using CPI will show clearly show sol transfer as an instruction in explorer/solscan.
    – Neo.Mxn0
    Commented Jan 14, 2023 at 7:46
0

It looks like you're deriving seeds to sign this transaction on behalf of client_pda, but the transfer is from bank to client. Your bank will need to sign for this transfer, not client_pda.

2
  • I thought normal accounts like bank cannot be used to sign CPI calls, even if the account is owned by the Program. Do you know how could I perform a CPI sign using the bank account? Or should I rather look into doing all this using PDAs instead? I mean replacing bank account by a bankPDA so I can transfer funds via CPI. Commented Sep 27, 2022 at 10:48
  • This is correct–I assumed bank was your PDA. In this case, yeah, you should do what Ander said: make bank a PDA owned by your program. Commented Sep 27, 2022 at 18:07

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.