---
title: Composing Multiple Programs
description:
  Learn how to implement Cross Program Invocations (CPIs) in Solana programs
  using the Anchor framework. This tutorial demonstrates how to transfer SOL
  between accounts, interact with the System Program, and handle Program Derived
  Addresses (PDAs) in CPIs. Perfect for developers looking to build composable
  Solana programs.
h1: Cross Program Invocation
---

In this section, we'll update the CRUD program from the previous PDA section by
adding Cross Program Invocations (CPIs), a feature that enables Solana programs
to invoke each other.

We'll also demonstrate how programs can "sign" for Program Derived Addresses
(PDAs) when making Cross Program Invocations.

We'll modify the `update` and `delete` instructions to handle SOL transfers
between accounts by invoking the System Program.

The purpose of this section is to walk through the process of implementing CPIs
in a Solana program using the Anchor framework, building upon the PDA concepts
we explored in the previous section. For more details, refer to the
[Cross Program Invocation](/docs/core/cpi) page.

For reference, here is the
[final code](https://beta.solpg.io/668304cfcffcf4b13384d20a) after completing
both the PDA and CPI sections.

Here is the [starter code](https://beta.solpg.io/679d75eecffcf4b13384d604) for
this section, with just the PDA section completed.

<Steps>
<Step>

### Modify Update Instruction

First, we'll implement a simple "pay-to-update" mechanism by modifying the
`Update` struct and `update` function.

Begin by updating the `lib.rs` file to bring into scope items from the
`system_program` module.

```rs title="lib.rs"
use anchor_lang::system_program::{transfer, Transfer};
```

<Accordions>
<Accordion title="Diff">

```diff
  use anchor_lang::prelude::*;
+ use anchor_lang::system_program::{transfer, Transfer};
```

</Accordion>
</Accordions>

Next, update the `Update` struct to include an additional account called
`vault_account`. This account, controlled by our program, will receive SOL from
a user when they update their message account.

```rs title="lib.rs"
#[account(
    mut,
    seeds = [b"vault", user.key().as_ref()],
    bump,
)]
pub vault_account: SystemAccount<'info>,
```

<Accordions>
<Accordion title="Diff">

```diff
#[derive(Accounts)]
#[instruction(message: String)]
pub struct Update<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

+   #[account(
+       mut,
+       seeds = [b"vault", user.key().as_ref()],
+       bump,
+   )]
+   pub vault_account: SystemAccount<'info>,
    #[account(
        mut,
        seeds = [b"message", user.key().as_ref()],
        bump = message_account.bump,
        realloc = 8 + 32 + 4 + message.len() + 1,
        realloc::payer = user,
        realloc::zero = true,
    )]
    pub message_account: Account<'info, MessageAccount>,
    pub system_program: Program<'info, System>,
}
```

</Accordion>
<Accordion title="Explanation">

We're adding a new account called `vault_account` to our `Update` struct. This
account serves as a program-controlled "vault" that will receive SOL from users
when they update their messages.

By using a PDA for the vault, we create a program-controlled account unique to
each user, enabling us to manage user funds within our program's logic.

---

Key aspects of the `vault_account`:

- The address of the account is a PDA derived using seeds
  `[b"vault", user.key().as_ref()]`
- As a PDA, it has no private key, so only our program can "sign" for the
  address when performing CPIs
- As a `SystemAccount` type, it's owned by the System Program like regular
  wallet accounts

This setup allows our program to:

- Generate unique, deterministic addresses for each user's "vault"
- Control funds without needing a private key to sign for transactions.

In the `delete` instruction, we'll demonstrate how our program can "sign" for
this PDA in a CPI.

</Accordion>
</Accordions>

Next, implement the CPI logic in the `update` instruction to transfer 0.001 SOL
from the user's account to the vault account.

```rs title="lib.rs"
let transfer_accounts = Transfer {
    from: ctx.accounts.user.to_account_info(),
    to: ctx.accounts.vault_account.to_account_info(),
};
let cpi_context = CpiContext::new(
    ctx.accounts.system_program.to_account_info(),
    transfer_accounts,
);
transfer(cpi_context, 1_000_000)?;
```

<Accordions>
<Accordion title="Diff">

```diff
    pub fn update(ctx: Context<Update>, message: String) -> Result<()> {
        msg!("Update Message: {}", message);
        let account_data = &mut ctx.accounts.message_account;
        account_data.message = message;

+       let transfer_accounts = Transfer {
+           from: ctx.accounts.user.to_account_info(),
+           to: ctx.accounts.vault_account.to_account_info(),
+       };
+       let cpi_context = CpiContext::new(
+           ctx.accounts.system_program.to_account_info(),
+           transfer_accounts,
+       );
+       transfer(cpi_context, 1_000_000)?;
        Ok(())
    }
```

</Accordion>
<Accordion title="Explanation">

In the `update` instruction, we implement a Cross Program Invocation (CPI) to
invoke the System Program's `transfer` instruction. This demonstrates how to
perform a CPI from within our program, enabling the composability of Solana
programs.

The `Transfer` struct specifies the required accounts for the System Program's
transfer instruction:

- `from` - The user's account (source of funds)
- `to` - The vault account (destination of funds)

  ```rs title="lib.rs"
  let transfer_accounts = Transfer {
      from: ctx.accounts.user.to_account_info(),
      to: ctx.accounts.vault_account.to_account_info(),
  };
  ```

The `CpiContext` specifies:

- The program to be invoked (System Program)
- The accounts required in the CPI (defined in the `Transfer` struct)

  ```rs title="lib.rs"
  let cpi_context = CpiContext::new(
      ctx.accounts.system_program.to_account_info(),
      transfer_accounts,
  );
  ```

The `transfer` function then invokes the transfer instruction on the System
Program, passing in the:

- The `cpi_context` (program and accounts)
- The `amount` to transfer (1,000,000 lamports, equivalent to 0.001 SOL)

  ```rs title="lib.rs"
  transfer(cpi_context, 1_000_000)?;
  ```

---

The setup for a CPI matches how client-side instructions are built, where we
specify the program, accounts, and instruction data for a particular instruction
to invoke. When our program's `update` instruction is invoked, it internally
invokes the System Program's transfer instruction.

</Accordion>
</Accordions>

Rebuild the program.

```shell title="Terminal"
build
```

</Step>
<Step>

### Modify Delete Instruction

We'll now implement a "refund on delete" mechanism by modifying the `Delete`
struct and `delete` function.

First, update the `Delete` struct to include the `vault_account`. This allows us
to transfer any SOL in the vault back to the user when they close their message
account.

```rs title="lib.rs"
#[account(
    mut,
    seeds = [b"vault", user.key().as_ref()],
    bump,
)]
pub vault_account: SystemAccount<'info>,
```

Also add the `system_program` as the CPI for the transfer requires invoking the
System Program.

```rs title="lib.rs"
pub system_program: Program<'info, System>,
```

<Accordions>
<Accordion title="Diff">

```diff
#[derive(Accounts)]
pub struct Delete<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

+   #[account(
+       mut,
+       seeds = [b"vault", user.key().as_ref()],
+       bump,
+   )]
+   pub vault_account: SystemAccount<'info>,
    #[account(
        mut,
        seeds = [b"message", user.key().as_ref()],
        bump = message_account.bump,
        close= user,
    )]
    pub message_account: Account<'info, MessageAccount>,
+   pub system_program: Program<'info, System>,
}
```

</Accordion>
<Accordion title="Explanation">

The `vault_account` uses the same PDA derivation as in the Update struct.

Add the `vault_account` to the Delete struct enables our program to access the
user's vault account during the delete instruction to transfer any accumulated
SOL back to the user.

</Accordion>
</Accordions>

Next, implement the CPI logic in the `delete` instruction to transfer SOL from
the vault account back to the user's account.

```rs title="lib.rs"
let user_key = ctx.accounts.user.key();
let signer_seeds: &[&[&[u8]]] =
    &[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];

let transfer_accounts = Transfer {
    from: ctx.accounts.vault_account.to_account_info(),
    to: ctx.accounts.user.to_account_info(),
};
let cpi_context = CpiContext::new(
    ctx.accounts.system_program.to_account_info(),
    transfer_accounts,
).with_signer(signer_seeds);
transfer(cpi_context, ctx.accounts.vault_account.lamports())?;
```

Note that we updated `_ctx: Context<Delete>` to `ctx: Context<Delete>` as we'll
be using the context in the body of the function.

<Accordions>
<Accordion title="Diff">

```diff
-    pub fn delete(_ctx: Context<Delete>) -> Result<()> {
+    pub fn delete(ctx: Context<Delete>) -> Result<()> {
         msg!("Delete Message");

+        let user_key = ctx.accounts.user.key();
+        let signer_seeds: &[&[&[u8]]] =
+            &[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];
+
+        let transfer_accounts = Transfer {
+            from: ctx.accounts.vault_account.to_account_info(),
+            to: ctx.accounts.user.to_account_info(),
+        };
+        let cpi_context = CpiContext::new(
+            ctx.accounts.system_program.to_account_info(),
+            transfer_accounts,
+        ).with_signer(signer_seeds);
+        transfer(cpi_context, ctx.accounts.vault_account.lamports())?;
         Ok(())
     }

```

</Accordion>
<Accordion title="Explanation">

In the delete instruction, we implement another Cross Program Invocation (CPI)
to invoke the System Program's transfer instruction. This CPI demonstrates how
to make a transfer that requires a Program Derived Address (PDA) signer.

First, we define the signer seeds for the vault PDA:

```rs title="lib.rs"
let user_key = ctx.accounts.user.key();
let signer_seeds: &[&[&[u8]]] =
    &[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];
```

The `Transfer` struct specifies the required accounts for the System Program's
transfer instruction:

- from: The vault account (source of funds)
- to: The user's account (destination of funds)

  ```rs title="lib.rs"
  let transfer_accounts = Transfer {
      from: ctx.accounts.vault_account.to_account_info(),
      to: ctx.accounts.user.to_account_info(),
  };
  ```

The `CpiContext` specifies:

- The program to be invoked (System Program)
- The accounts involved in the transfer (defined in the Transfer struct)
- The signer seeds for the PDA

  ```rs title="lib.rs"
  let cpi_context = CpiContext::new(
      ctx.accounts.system_program.to_account_info(),
      transfer_accounts,
  ).with_signer(signer_seeds);
  ```

The transfer function then invokes the transfer instruction on the System
Program, passing:

- The `cpi_context` (program, accounts, and PDA signer)
- The amount to transfer (the entire balance of the vault account)

  ```rs title="lib.rs"
  transfer(cpi_context, ctx.accounts.vault_account.lamports())?;
  ```

This CPI implementation demonstrates how programs can utilize PDAs to manage
funds. When our program's delete instruction is invoked, it internally calls the
System Program's transfer instruction, signing for the PDA to authorize the
transfer of all funds from the vault back to the user.

</Accordion>
</Accordions>

Rebuild the program.

```shell title="Terminal"
build
```

</Step>
<Step>

### Redeploy Program

After making these changes, we need to redeploy our updated program. This
ensures that our modified program is available for testing. On Solana, updating
a program simply requires deploying the program at the same program ID.

<Callout type="info">

Ensure your Playground wallet has devnet SOL. Get devnet SOL from the
[Solana Faucet](https://faucet.solana.com/).

</Callout>

```shell title="Terminal"
deploy
```

<Accordions>
<Accordion title="Output">

```bash
$ deploy
Deploying... This could take a while depending on the program size and network conditions.
Deployment successful. Completed in 17s.
```

</Accordion>
<Accordion title="Explanation">

Only the upgrade authority of the program can update it. The upgrade authority
is set when the program is deployed, and it's the only account with permission
to modify or close the program. If the upgrade authority is revoked, then the
program becomes immutable and can never be closed or upgraded.

When deploying programs on Solana Playground, your Playground wallet is the
upgrade authority for all your programs.

</Accordion>
</Accordions>

</Step>
<Step>

### Update Test File

Next, we'll update our `anchor.test.ts` file to include the new vault account in
our instructions. This requires deriving the vault PDA and including it in our
update and delete instruction calls.

#### Derive Vault PDA

First, add the vault PDA derivation:

```ts title="anchor.test.ts"
const [vaultPda, vaultBump] = PublicKey.findProgramAddressSync(
  [Buffer.from("vault"), wallet.publicKey.toBuffer()],
  program.programId,
);
```

<Accordions>
<Accordion title="Diff">

```diff
describe("pda", () => {
  const program = pg.program;
  const wallet = pg.wallet;

  const [messagePda, messageBump] = PublicKey.findProgramAddressSync(
    [Buffer.from("message"), wallet.publicKey.toBuffer()],
    program.programId
  );

+  const [vaultPda, vaultBump] = PublicKey.findProgramAddressSync(
+    [Buffer.from("vault"), wallet.publicKey.toBuffer()],
+    program.programId
+  );

  // ...tests
  });
```

</Accordion>
</Accordions>

#### Modify Update Test

Then, update the update instruction to include the `vaultAccount`.

```ts title="anchor.test.ts"  {5}
const transactionSignature = await program.methods
  .update(message)
  .accounts({
    messageAccount: messagePda,
    vaultAccount: vaultPda,
  })
  .rpc({ commitment: "confirmed" });
```

<Accordions>
<Accordion title="Diff">

```diff
    const transactionSignature = await program.methods
      .update(message)
      .accounts({
        messageAccount: messagePda,
+       vaultAccount: vaultPda,
      })
      .rpc({ commitment: "confirmed" });
```

</Accordion>
</Accordions>

#### Modify Delete Test

Then, update the delete instruction to include the `vaultAccount`.

```ts title="anchor.test.ts"  {5}
const transactionSignature = await program.methods
  .delete()
  .accounts({
    messageAccount: messagePda,
    vaultAccount: vaultPda,
  })
  .rpc({ commitment: "confirmed" });
```

<Accordions>
<Accordion title="Diff">

```diff
    const transactionSignature = await program.methods
      .delete()
      .accounts({
        messageAccount: messagePda,
+       vaultAccount: vaultPda,
      })
      .rpc({ commitment: "confirmed" });
```

</Accordion>
</Accordions>

</Step>
<Step>

### Rerun Test

After making these changes, run the tests to ensure everything is working as
expected:

```shell title="Terminal"
test
```

<Accordions>
<Accordion title="Output">

```bash
$ test
Running tests...
  anchor.test.ts:
  pda
    {
  "user": "3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R",
  "message": "Hello, World!",
  "bump": 254
}
    Transaction Signature: https://solana.fm/tx/qGsYb87mUUjeyh7Ha7r9VXkACw32HxVBujo2NUxqHiUc8qxRMFB7kdH2D4JyYtPBx171ddS91VyVrFXypgYaKUr?cluster=devnet-solana
    ✔ Create Message Account (842ms)
    {
  "user": "3z9vL1zjN6qyAFHhHQdWYRTFAcy69pJydkZmSFBKHg1R",
  "message": "Hello, Solana!",
  "bump": 254
}
    Transaction Signature: https://solana.fm/tx/3KCDnNSfDDfmSy8kpiSrJsGGkzgxx2mt18KejuV2vmJjeyenkSoEfs2ghUQ6cMoYYgd9Qax9CbnYRcvF2zzumNt8?cluster=devnet-solana
    ✔ Update Message Account (946ms)
    Expect Null: null
    Transaction Signature: https://solana.fm/tx/3M7Z7Mea3TtQc6m9z386B9QuEgvLKxD999mt2RyVtJ26FgaAzV1QA5mxox3eXie3bpBkNpDQ4mEANr3trVHCWMC2?cluster=devnet-solana
    ✔ Delete Message Account (859ms)
  3 passing (3s)
```

</Accordion>
</Accordions>

You can then inspect the SolanaFM links to view the transaction details, where
you'll find the CPIs for the transfer instructions within the update and delete
instructions.

![Update CPI](/assets/docs/intro/quickstart/cpi-update.png)

![Delete CPI](/assets/docs/intro/quickstart/cpi-delete.png)

If you encounter any errors, you can reference the
[final code](https://beta.solpg.io/668304cfcffcf4b13384d20a).

</Step>
</Steps>

## Next Steps

Congratulations on completing the Solana Quickstart guide! You've gained
hands-on experience with key Solana concepts including:

- Fetching and reading data from accounts
- Building and sending transactions
- Deploying and updating Solana programs
- Working with Program Derived Addresses (PDAs)
- Making Cross-Program Invocations (CPIs)

To deepen your understanding of these concepts, check out the
[Core Concepts](/docs/core/accounts) documentation which provides detailed
explanations of the topics covered in this guide.

### Explore More Examples

If you prefer learning by example, check out the
[Program Examples Repository](https://github.com/solana-developers/program-examples)
for a variety of example programs.

Solana Playground offers a convenient feature allowing you to import or view
projects using their GitHub links. For example, open this
[Solana Playground link](https://beta.solpg.io/https://github.com/solana-developers/program-examples/tree/main/basics/hello-solana/anchor)
to view the Anchor project from this
[Github repo](https://github.com/solana-developers/program-examples/tree/main/basics/hello-solana/anchor).

Click the `Import` button and enter a project name to add it to your list of
projects in Solana Playground. Once a project is imported, all changes are
automatically saved and persisted.
