learn.sol
Week 3 • Escrow Mechanism Project

Project: Escrow with PDA Vault

Build an Anchor escrow flow with deterministic vault PDAs, explicit state transitions, cancellation paths, and safer release logic.

Escrow is a perfect intermediate Anchor project because it combines:

  • PDA state accounts
  • Token CPI transfers
  • Clear state machine design

Problem Statement

A maker deposits tokens into escrow. A taker can fulfill conditions. Then funds release according to program rules.

Minimal Escrow State

#[account]
#[derive(InitSpace)]
pub struct Escrow {
    pub maker: Pubkey,
    pub taker: Pubkey,
    pub mint: Pubkey,
    pub amount: u64,
    pub status: EscrowStatus,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, InitSpace)]
pub enum EscrowStatus {
    Initialized,
    Funded,
    Completed,
    Cancelled,
}

PDA Design

  • Escrow PDA: b"escrow" + maker + nonce
  • Vault authority PDA: b"vault-authority" + escrow_pubkey
  • Vault ATA owned by vault authority PDA

Keep seed formulas fixed and documented.

Instruction Flow

  1. initialize_escrow - writes escrow config
  2. fund_escrow - maker transfers tokens into vault
  3. complete_escrow - releases vault tokens to taker
  4. cancel_escrow - returns funds to maker if rules permit

Release Guardrails

  • Only valid actor can transition current state
  • status must match expected prior state
  • Transfer amount must match escrow agreement
  • All token accounts must be validated against expected mint
require!(escrow.status == EscrowStatus::Funded, EscrowError::InvalidState);
require_keys_eq!(escrow.taker, taker.key(), EscrowError::Unauthorized);

Common Escrow Bugs

  • Missing state transition checks (double release)
  • Using client-side booleans instead of on-chain status
  • Forgetting to close escrow account and reclaim rent
  • Not testing race-like repeated calls

If an instruction can be called twice, assume someone will call it twice. Protect every state transition.

Test Matrix

  1. Maker initializes + funds successfully
  2. Unauthorized taker cannot complete
  3. Correct taker completes once
  4. Second complete attempt fails
  5. Cancel path only works in valid states

Try This Next

  1. Add expiry timestamp and allow timeout-based cancellation.
  2. Support partial fills with remaining balance tracking.
  3. Emit events for each state transition and index them in your backend.

Solana Assistant

AI-powered documentation helper

Welcome to Solana Assistant

Ask specific questions about Solana development:

Ask specific questions for better results400px
    Project: Escrow with PDA Vault | learn.sol