Sol.College

Cross-Program Invocations (CPIs)

A CPI is one program calling an instruction on another program, such as the Token Program or System Program. The caller passes the accounts the callee needs. When a PDA must authorise the call, the caller uses invoke_signed (or Anchor's CpiContext::new_with_signer) with the PDA's seeds, and the runtime treats the PDA as a signer for that call only.

Reference: Solana docs: Cross Program Invocation · Anchor docs: CPIs

Real code, line by line

6 excerpts from different projects. Each caption links the exact lines at the fork's pinned commit. The notes below each excerpt explain the lines that matter.

restacked-dev/valhalla · anchor/programs/valhalla/src/instructions/create.rs L231–256 @ 7aa9ddf · MIT · original
            to,            authority,            mint,        };        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);         transfer_checked(cpi_ctx, amount, self.mint.decimals)    }     fn mint_governance_tokens(&self, bumps: &CreateVaultBumps) -> Result<()> {        let signer_seeds: &[&[&[u8]]] = &[&[            constants::GOVERNANCE_TOKEN_MINT_SEED,            &[bumps.governance_token_mint],        ]];         let cpi_program = self.governance_token_program.to_account_info();        let cpi_accounts = MintTo {            mint: self.governance_token_mint.to_account_info(),            to: self.creator_governance_ata.to_account_info(),            authority: self.governance_token_mint.to_account_info(),        };        let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);         mint_to(cpi_context, self.config.governance_token_amount)    }}
  1. Line 235 let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);: Makes a cross-program invocation into another program.
  2. Line 237 transfer_checked(cpi_ctx, amount, self.mint.decimals): A checked token transfer, which also verifies the mint and decimals.
  3. Line 247 let cpi_accounts = MintTo {: Mints new tokens. The mint authority must sign.
  4. Line 252 let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
KryptPay/kryptpay-solana-contract · programs/kryptpay/src/instructions/swap_exact_tokens_for_tokens.rs L73–98 @ a528c51 · MIT · original
    ];    let signer_seeds = &[&authority_seeds[..]];    if swap_a {        token::transfer(            CpiContext::new(                ctx.accounts.token_program.to_account_info(),                Transfer {                    from: ctx.accounts.trader_account_a.to_account_info(),                    to: ctx.accounts.pool_account_a.to_account_info(),                    authority: ctx.accounts.trader.to_account_info(),                },            ),            input,        )?;        token::transfer(            CpiContext::new_with_signer(                ctx.accounts.token_program.to_account_info(),                Transfer {                    from: ctx.accounts.pool_account_b.to_account_info(),                    to: ctx.accounts.trader_account_b.to_account_info(),                    authority: ctx.accounts.pool_authority.to_account_info(),                },                signer_seeds,            ),            output,        )?;
  1. Line 74 let signer_seeds = &[&authority_seeds[..]];: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 76 token::transfer(: An SPL token transfer through the Token Program.
  3. Line 77 CpiContext::new(: Makes a cross-program invocation into another program.
  4. Line 88 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
ProofOfEstimate/poe-solana · programs/poe/src/contexts/register_user.rs L44–64 @ 29a7c77 · MIT · original
    }     pub fn mint_tokens(&mut self, bumps: &RegisterUserBumps) -> Result<()> {        mint_to(            CpiContext::new_with_signer(                self.token_program.to_account_info(),                MintTo {                    authority: self.mint.to_account_info(),                    to: self.token_account.to_account_info(),                    mint: self.mint.to_account_info(),                },                &[&["poeken_mint".as_bytes(), &[bumps.mint]]],            ),            1000 * 1000000000,        )?;         msg!("Minted tokens");         Ok(())    }}
  1. Line 46 pub fn mint_tokens(&mut self, bumps: &RegisterUserBumps) -> Result<()> {: Mints new tokens. The mint authority must sign.
  2. Line 48 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
  3. Line 60 msg!("Minted tokens");: Logs to the program log, which is visible in explorers and the test output.
e-mk/peer-pay · escrow/programs/mm_escrow/src/instructions/accept.rs L67–92 @ ca496be · Apache-2.0 · original
            &[self.escrow.bump],        ]];         transfer(            CpiContext::new_with_signer(                self.token_program.to_account_info(),                cpi_accounts,                escrow_signer_seeds,            ),            x_requested,        )    }     pub fn transfer_y_to_seller_if_needed(&self, x_requested: u64, y_amount: u64) -> Result<()> {        require!(            check_price(x_requested, y_amount, self.escrow.y_price),            Errors::PriceMismatch,        );         let ixs = self.instructions.to_account_info();        let current_index = load_current_index_checked(&ixs)? as usize;        if current_index == 0 {          // wallet payment case          self.transfer_y_to_seller(y_amount)        } else {            match load_instruction_at_checked(current_index - 1, &ixs) {
  1. Line 67 &[self.escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  2. Line 71 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
  3. Line 81 require!(: An explicit validation that aborts the instruction with an error.
asimnecip/gree · anchor/programs/gree/src/lib.rs L52–77 @ 7ef8c89 · MIT · original
                payer: ctx.accounts.signer.to_account_info(),                system_program: ctx.accounts.system_program.to_account_info(),                rent: ctx.accounts.rent.to_account_info(),            };            let cpi_context = CpiContext::new(ctx.accounts.token_metadata_program.to_account_info(), cpi_accounts);            create_metadata_accounts_v3(cpi_context, data_v2, false, true, None)?;        }         // Create master edition with minimized context creation        {            let cpi_accounts = CreateMasterEditionV3 {                edition: ctx.accounts.master_edition_account.to_account_info(),                mint: ctx.accounts.mint.to_account_info(),                update_authority: ctx.accounts.signer.to_account_info(),                mint_authority: ctx.accounts.signer.to_account_info(),                payer: ctx.accounts.signer.to_account_info(),                metadata: ctx.accounts.metadata_account.to_account_info(),                token_program: ctx.accounts.token_program.to_account_info(),                system_program: ctx.accounts.system_program.to_account_info(),                rent: ctx.accounts.rent.to_account_info(),            };            let cpi_context = CpiContext::new(ctx.accounts.token_metadata_program.to_account_info(), cpi_accounts);            create_master_edition_v3(cpi_context, None)?;        }         Ok(())
  1. Line 56 let cpi_context = CpiContext::new(ctx.accounts.token_metadata_program.to_account_info(), c: Makes a cross-program invocation into another program.
supermetalmons/mons-dot-rehab · anchor/programs/mons-dot-rehab/src/lib.rs L22–47 @ 0359070 · CC0-1.0 · original
            ctx.accounts.host.to_account_info().key,            ctx.accounts.game.to_account_info().key,            GAME_COST,        );        invoke(            &transfer_instruction,            &[                ctx.accounts.host.to_account_info(),                ctx.accounts.game.to_account_info(),                ctx.accounts.system_program.to_account_info(),            ],        )?;         Ok(())    }     pub fn join_game(ctx: Context<JoinGame>, game_id: u64) -> Result<()> {        let game = &mut ctx.accounts.game;        require!(game.game_id == game_id, ErrorCode::GameNotFound);        require!(game.guest_id == Pubkey::default(), ErrorCode::GameAlreadyJoined);            game.guest_id = ctx.accounts.guest.key();            let transfer_instruction = system_instruction::transfer(            ctx.accounts.guest.to_account_info().key,            ctx.accounts.game.to_account_info().key,
  1. Line 26 invoke(: Makes a cross-program invocation into another program.
  2. Line 40 require!(game.game_id == game_id, ErrorCode::GameNotFound);: An explicit validation that aborts the instruction with an error.

Go further