Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
On Ethereum, composability means one contract calls another contract's function. On Solana, composability means one program reads and writes accounts that another program owns. The account-based model is more explicit — you literally pass every account a transaction will touch — but it enables aggregators like Jupiter to route through 10+ DEXs in a single transaction with no token approvals.
Cross-program invocation (CPI) reading state from a Token Program account.
Use these three in order. Each builds on the one before.
In one paragraph, explain how Solana's account model differs from Ethereum's contract model.
Walk me through how a PDA (program-derived address) signs a CPI without holding a private key.
Why does Solana require all accounts to be declared in the transaction upfront, and how does this affect Jupiter's routing?
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
#[derive(Accounts)]
pub struct Swap<'info> {
#[account(mut)]
pub user_source: Account<'info, TokenAccount>,
#[account(mut)]
pub user_dest: Account<'info, TokenAccount>,
#[account(mut)]
pub pool_source: Account<'info, TokenAccount>,
#[account(mut)]
pub pool_dest: Account<'info, TokenAccount>,
pub pool_authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
pub fn swap(ctx: Context<Swap>, amount_in: u64, min_out: u64) -> Result<()> {
// Transfer user -> pool (user signs)
let cpi_in = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.user_source.to_account_info(),
to: ctx.accounts.pool_source.to_account_info(),
authority: ctx.accounts.user_source.to_account_info(),
},
);
token::transfer(cpi_in, amount_in)?;
// Compute amount_out using x*y=k pricing on pool reserves
let reserve_in = ctx.accounts.pool_source.amount;
let reserve_out = ctx.accounts.pool_dest.amount;
let amount_out = (reserve_out * amount_in) / (reserve_in + amount_in);
require!(amount_out >= min_out, SwapError::Slippage);
// Transfer pool -> user (program-derived authority signs)
let cpi_out = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.pool_dest.to_account_info(),
to: ctx.accounts.user_dest.to_account_info(),
authority: ctx.accounts.pool_authority.to_account_info(),
},
&[],
);
token::transfer(cpi_out, amount_out)?;
Ok(())
}
#[error_code]
pub enum SwapError { Slippage }