---
title: CPIs with PDA Signers
description:
  How to use the invoke_signed function for Cross-Program Invocations on Solana
  when the calling program needs to sign as a PDA, with Anchor CpiContext and
  Native Rust examples.
url: /docs/core/cpi/cpi-with-pda
type: tutorial
prerequisites:
  - /docs/core/cpi
  - /docs/core/pda/pda-derivation
related:
  - /docs/core/cpi/cpi-without-pda
  - /docs/core/cpi/cpi-execution
  - /docs/core/pda/pda-accounts
---

<Callout type="info" title="Summary">
  Use _rs`invoke_signed`_ when the calling program needs to sign on behalf of a
  PDA it owns. The runtime derives PDA pubkeys from the provided signer seeds
  and adds them to the set of valid signers before privilege checking.
</Callout>

<WithMentions>

## CPIs with PDA signers

When a CPI requires a PDA signer, use
[`invoke_signed`](https://github.com/anza-xyz/solana-sdk/blob/clock%40v2.2.3/program/src/program.rs#L51)
with the [signer seeds](mention:signer-seeds) used to derive the
[PDA](/docs/core/pda). For details on how the runtime verifies PDA signatures,
see [PDA Signing](/docs/core/cpi/cpi-cost-model#pda-signing).

```rust title="Invoke signed"
pub fn invoke_signed(
    instruction: &Instruction,
    account_infos: &[AccountInfo],
    // !mention signer-seeds
    signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
    // --snip--
    invoke_signed_unchecked(instruction, account_infos, signers_seeds)
}
```

</WithMentions>

The examples below make a CPI with PDA signers using Anchor and Native Rust.
Each example includes a single instruction to transfer SOL from a PDA to a
recipient account, using a CPI signed by the PDA.

#### Anchor

The following examples show two approaches to implementing CPIs in an
[Anchor](https://www.anchor-lang.com/docs) program. The examples are
functionally equivalent, but demonstrate different levels of abstraction.

- **Example 1**: Uses Anchor's _rs`CpiContext`_ and helper function.
- **Example 2**: Uses the _rs`system_instruction::transfer`_ function from
  _rs`solana_program`_ crate. (Example 1 is an abstraction of this
  implementation.)
- **Example 3**: Constructs the CPI instruction manually. This approach is
  useful when there is no crate available to help build the instruction you want
  to invoke.

<CodeTabs>

```rs !! title="Example 1"
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};

declare_id!("BrcdB9sV7z9DvF9rDHG263HUxXgJM3iCQdF36TcxbFEn");

#[program]
pub mod cpi {
    use super::*;

    pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
        let from_pubkey = ctx.accounts.pda_account.to_account_info();
        let to_pubkey = ctx.accounts.recipient.to_account_info();
        let program_id = ctx.accounts.system_program.to_account_info();

        let seed = to_pubkey.key();
        let bump_seed = ctx.bumps.pda_account;
        let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

        let cpi_context = CpiContext::new(
            program_id,
            Transfer {
                from: from_pubkey,
                to: to_pubkey,
            },
        )
        .with_signer(signer_seeds);

        transfer(cpi_context, amount)?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct SolTransfer<'info> {
    #[account(
        mut,
        seeds = [b"pda", recipient.key().as_ref()],
        bump,
    )]
    pda_account: SystemAccount<'info>,
    #[account(mut)]
    recipient: SystemAccount<'info>,
    system_program: Program<'info, System>,
}
```

```rs !! title="Example 2"
use anchor_lang::prelude::*;
use anchor_lang::solana_program::{program::invoke_signed, system_instruction};

declare_id!("BrcdB9sV7z9DvF9rDHG263HUxXgJM3iCQdF36TcxbFEn");

#[program]
pub mod cpi {
    use super::*;

    pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
        let from_pubkey = ctx.accounts.pda_account.to_account_info();
        let to_pubkey = ctx.accounts.recipient.to_account_info();
        let program_id = ctx.accounts.system_program.to_account_info();

        let seed = to_pubkey.key();
        let bump_seed = ctx.bumps.pda_account;

        let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

        let instruction =
            &system_instruction::transfer(&from_pubkey.key(), &to_pubkey.key(), amount);

        invoke_signed(instruction, &[from_pubkey, to_pubkey, program_id], signer_seeds)?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct SolTransfer<'info> {
    #[account(
        mut,
        seeds = [b"pda", recipient.key().as_ref()],
        bump,
    )]
    pda_account: SystemAccount<'info>,
    #[account(mut)]
    recipient: SystemAccount<'info>,
    system_program: Program<'info, System>,
}
```

```rs !! title="Example 3"
use anchor_lang::prelude::*;
use anchor_lang::solana_program::{program::invoke_signed, instruction::{Instruction, AccountMeta}};

declare_id!("BrcdB9sV7z9DvF9rDHG263HUxXgJM3iCQdF36TcxbFEn");

#[program]
pub mod cpi {
    use super::*;

    pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
        let from_pubkey = ctx.accounts.pda_account.to_account_info();
        let to_pubkey = ctx.accounts.recipient.to_account_info();
        let program_id = ctx.accounts.system_program.to_account_info();

        // Get PDA signer seeds
        let seed = to_pubkey.key();
        let bump_seed = ctx.bumps.pda_account;
        let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];

        // Prepare instruction AccountMetas
        let account_metas = vec![
            AccountMeta::new(from_pubkey.key(), true),
            AccountMeta::new(to_pubkey.key(), false),
        ];

        // SOL transfer instruction discriminator
        let instruction_discriminator: u32 = 2;

        // Prepare instruction data
        let mut instruction_data = Vec::with_capacity(4 + 8);
        instruction_data.extend_from_slice(&instruction_discriminator.to_le_bytes());
        instruction_data.extend_from_slice(&amount.to_le_bytes());

        // Create instruction
        let instruction = Instruction {
            program_id: program_id.key(),
            accounts: account_metas,
            data: instruction_data,
        };

        // Invoke instruction with PDA signer
        invoke_signed(&instruction, &[from_pubkey, to_pubkey, program_id], signer_seeds)?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct SolTransfer<'info> {
    #[account(
        mut,
        seeds = [b"pda", recipient.key().as_ref()],
        bump,
    )]
    pda_account: SystemAccount<'info>,
    #[account(mut)]
    recipient: SystemAccount<'info>,
    system_program: Program<'info, System>,
}
```

```ts !! title="Test"
import * as anchor from "@coral-xyz/anchor";

import { BN, Program } from "@coral-xyz/anchor";
import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";

import { Cpi } from "../target/types/cpi";

describe("cpi", () => {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  const program = anchor.workspace.Cpi as Program<Cpi>;
  const connection = program.provider.connection;
  const wallet = provider.wallet as anchor.Wallet;

  const [PDA] = PublicKey.findProgramAddressSync(
    [Buffer.from("pda"), wallet.publicKey.toBuffer()],
    program.programId
  );

  const transferAmount = 0.1 * LAMPORTS_PER_SOL;

  before(async () => {
    // Request airdrop to fund PDA
    const signature = await connection.requestAirdrop(PDA, transferAmount);

    const { blockhash, lastValidBlockHeight } =
      await connection.getLatestBlockhash();

    await connection.confirmTransaction({
      signature,
      blockhash,
      lastValidBlockHeight
    });
  });

  it("SOL Transfer with PDA signer", async () => {
    const transactionSignature = await program.methods
      .solTransfer(new BN(transferAmount))
      .accounts({
        recipient: wallet.publicKey
      })
      .rpc();

    console.log(`\nTransaction Signature: ${transactionSignature}`);
  });
});
```

</CodeTabs>

#### Rust

The example below makes a CPI with PDA signers from a program written in Native
Rust. It includes a single instruction that transfers SOL from a PDA account to
another. The CPI is signed by the PDA account. (The test file uses
[LiteSVM](https://github.com/LiteSVM/litesvm) to test the program.)

<CodeTabs>

```rs !! title="Example"
use borsh::BorshDeserialize;
use solana_program::{
    account_info::AccountInfo,
    entrypoint,
    entrypoint::ProgramResult,
    program::invoke_signed,
    program_error::ProgramError,
    pubkey::Pubkey,
    system_instruction,
};

// Declare program entrypoint
entrypoint!(process_instruction);

// Define program instructions
#[derive(BorshDeserialize)]
enum ProgramInstruction {
    SolTransfer { amount: u64 },
}

impl ProgramInstruction {
    fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        Self::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)
    }
}

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    // Deserialize instruction data
    let instruction = ProgramInstruction::unpack(instruction_data)?;

    // Process instruction
    match instruction {
        ProgramInstruction::SolTransfer { amount } => {
            // Parse accounts
            let [pda_account_info, recipient_info, system_program_info] = accounts else {
                return Err(ProgramError::NotEnoughAccountKeys);
            };

            // Derive PDA and verify it matches the account provided by client
            let recipient_pubkey = recipient_info.key;
            let seeds = &[b"pda", recipient_pubkey.as_ref()];
            let (expected_pda, bump_seed) = Pubkey::find_program_address(seeds, program_id);

            if expected_pda != *pda_account_info.key {
                return Err(ProgramError::InvalidArgument);
            }

            // Create the transfer instruction
            let transfer_ix = system_instruction::transfer(
                pda_account_info.key,
                recipient_info.key,
                amount,
            );

            // Create signer seeds for PDA
            let signer_seeds: &[&[&[u8]]] = &[&[b"pda", recipient_pubkey.as_ref(), &[bump_seed]]];

            // Invoke the transfer instruction with PDA as signer
            invoke_signed(
                &transfer_ix,
                &[
                    pda_account_info.clone(),
                    recipient_info.clone(),
                    system_program_info.clone(),
                ],
                signer_seeds,
            )?;

            Ok(())
        }
    }
}
```

```ts !! title="Test"
import * as path from "path";
import {
  Keypair,
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram,
  Transaction,
  TransactionInstruction
} from "@solana/web3.js";
import { LiteSVM } from "litesvm";

test("sol transfer cpi with pda signer", () => {
  const svm = new LiteSVM();

  const programId = PublicKey.unique();
  const programPath = path.join(__dirname, "program.so");
  svm.addProgramFromFile(programId, programPath);

  // Create recipient
  const recipient = new Keypair();

  // Derive PDA that will hold and send funds
  const [pdaAddress] = PublicKey.findProgramAddressSync(
    [Buffer.from("pda"), recipient.publicKey.toBuffer()],
    programId
  );

  // Fund accounts
  const amount = BigInt(LAMPORTS_PER_SOL);
  svm.airdrop(recipient.publicKey, amount); // 1 SOL
  svm.airdrop(pdaAddress, amount); // 1 SOL

  // Create instruction data buffer
  const transferAmount = amount / BigInt(2); // 0.5 SOL
  const instructionIndex = 0; // instruction index 0 for SolTransfer enum

  const data = Buffer.alloc(9); // 1 byte for instruction enum + 8 bytes for u64
  data.writeUInt8(instructionIndex, 0); // first byte identifies the instruction
  data.writeBigUInt64LE(transferAmount, 1); // remaining bytes are instruction arguments

  // Create instruction
  const instruction = new TransactionInstruction({
    programId,
    keys: [
      { pubkey: pdaAddress, isSigner: false, isWritable: true },
      { pubkey: recipient.publicKey, isSigner: false, isWritable: true },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
    ],
    data
  });

  // Create and send transaction
  const transaction = new Transaction().add(instruction);
  transaction.recentBlockhash = svm.latestBlockhash();
  transaction.sign(recipient);

  svm.sendTransaction(transaction);

  // Check balances
  const recipientBalance = svm.getBalance(recipient.publicKey);
  const pdaBalance = svm.getBalance(pdaAddress);

  const transactionFee = BigInt(5000);
  // Recipient starts with 1 SOL, receives 0.5 SOL, pays tx fee
  expect(recipientBalance).toBe(amount + transferAmount - transactionFee);
  // PDA starts with 1 SOL, sends 0.5 SOL
  expect(pdaBalance).toBe(amount - transferAmount);
});
```

</CodeTabs>
