Projects Overview

Build complete Solana applications with these end-to-end project guides.

Project Categories

Text
┌─────────────────────────────────────────────────────────────────┐
│                    Project Catalog                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  DeFi                        NFT                                │
│  ┌────────────────────┐     ┌────────────────────┐             │
│  │ • Token Swap       │     │ • NFT Marketplace  │             │
│  │ • Lending Protocol │     │ • Generative Art   │             │
│  │ • Yield Aggregator │     │ • Gaming Assets    │             │
│  └────────────────────┘     └────────────────────┘             │
│                                                                 │
│  Gaming                      DAO                                │
│  ┌────────────────────┐     ┌────────────────────┐             │
│  │ • On-chain Game    │     │ • Governance Token │             │
│  │ • Battle Arena     │     │ • Treasury Mgmt    │             │
│  │ • Virtual World    │     │ • Voting System    │             │
│  └────────────────────┘     └────────────────────┘             │
│                                                                 │
│  Infrastructure              Social                             │
│  ┌────────────────────┐     ┌────────────────────┐             │
│  │ • Oracle Network   │     │ • Social Protocol  │             │
│  │ • Bridge           │     │ • Content Platform │             │
│  │ • Name Service     │     │ • Reputation       │             │
│  └────────────────────┘     └────────────────────┘             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Featured Project: Token Swap AMM

A complete automated market maker implementation demonstrating core DeFi concepts.

Architecture

Text
┌─────────────────────────────────────────────────────────────────┐
│                   Token Swap Architecture                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  User Flow                                                      │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  1. Connect wallet                                       │   │
│  │  2. Select tokens to swap                               │   │
│  │  3. Enter amount                                        │   │
│  │  4. Preview price impact                                │   │
│  │  5. Approve & execute swap                              │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  Program Accounts                                               │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                                                          │   │
│  │  Pool Account                                           │   │
│  │  ├── Token A Vault (PDA)                               │   │
│  │  ├── Token B Vault (PDA)                               │   │
│  │  ├── LP Token Mint                                     │   │
│  │  ├── Fee Account                                       │   │
│  │  └── Config (fee %, curve type)                        │   │
│  │                                                          │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  Instructions                                                   │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐               │
│  │ Initialize │  │    Swap    │  │  Add/Remove│               │
│  │    Pool    │  │   Tokens   │  │  Liquidity │               │
│  └────────────┘  └────────────┘  └────────────┘               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Program Implementation

Rust
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer};

declare_id!("Swap1111111111111111111111111111111111111111");

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

    pub fn initialize_pool(
        ctx: Context<InitializePool>,
        fee_bps: u16,
    ) -> Result<()> {
        require!(fee_bps <= 10000, ErrorCode::InvalidFee);

        let pool = &mut ctx.accounts.pool;
        pool.authority = ctx.accounts.authority.key();
        pool.token_a_mint = ctx.accounts.token_a_mint.key();
        pool.token_b_mint = ctx.accounts.token_b_mint.key();
        pool.token_a_vault = ctx.accounts.token_a_vault.key();
        pool.token_b_vault = ctx.accounts.token_b_vault.key();
        pool.lp_mint = ctx.accounts.lp_mint.key();
        pool.fee_bps = fee_bps;
        pool.bump = ctx.bumps.pool;

        Ok(())
    }

    pub fn add_liquidity(
        ctx: Context<AddLiquidity>,
        amount_a: u64,
        amount_b: u64,
        min_lp_tokens: u64,
    ) -> Result<()> {
        let pool = &ctx.accounts.pool;

        // Calculate LP tokens to mint
        let vault_a_amount = ctx.accounts.token_a_vault.amount;
        let vault_b_amount = ctx.accounts.token_b_vault.amount;
        let lp_supply = ctx.accounts.lp_mint.supply;

        let lp_tokens = if lp_supply == 0 {
            // Initial liquidity - use geometric mean
            (amount_a as u128)
                .checked_mul(amount_b as u128)
                .and_then(|v| v.checked_sqrt())
                .ok_or(ErrorCode::MathOverflow)? as u64
        } else {
            // Proportional to smallest ratio
            let ratio_a = (amount_a as u128)
                .checked_mul(lp_supply as u128)
                .ok_or(ErrorCode::MathOverflow)?
                .checked_div(vault_a_amount as u128)
                .ok_or(ErrorCode::MathOverflow)? as u64;

            let ratio_b = (amount_b as u128)
                .checked_mul(lp_supply as u128)
                .ok_or(ErrorCode::MathOverflow)?
                .checked_div(vault_b_amount as u128)
                .ok_or(ErrorCode::MathOverflow)? as u64;

            ratio_a.min(ratio_b)
        };

        require!(lp_tokens >= min_lp_tokens, ErrorCode::SlippageExceeded);

        // Transfer tokens to vault
        token::transfer(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: ctx.accounts.user_token_a.to_account_info(),
                    to: ctx.accounts.token_a_vault.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                },
            ),
            amount_a,
        )?;

        token::transfer(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: ctx.accounts.user_token_b.to_account_info(),
                    to: ctx.accounts.token_b_vault.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                },
            ),
            amount_b,
        )?;

        // Mint LP tokens
        let pool_key = pool.key();
        let seeds = &[b"pool", pool_key.as_ref(), &[pool.bump]];
        let signer_seeds = &[&seeds[..]];

        token::mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                token::MintTo {
                    mint: ctx.accounts.lp_mint.to_account_info(),
                    to: ctx.accounts.user_lp.to_account_info(),
                    authority: ctx.accounts.pool.to_account_info(),
                },
                signer_seeds,
            ),
            lp_tokens,
        )?;

        emit!(LiquidityAdded {
            pool: pool.key(),
            user: ctx.accounts.user.key(),
            amount_a,
            amount_b,
            lp_tokens,
        });

        Ok(())
    }

    pub fn swap(
        ctx: Context<Swap>,
        amount_in: u64,
        minimum_amount_out: u64,
        a_to_b: bool,
    ) -> Result<()> {
        let pool = &ctx.accounts.pool;

        let (source_vault, dest_vault) = if a_to_b {
            (
                ctx.accounts.token_a_vault.amount,
                ctx.accounts.token_b_vault.amount,
            )
        } else {
            (
                ctx.accounts.token_b_vault.amount,
                ctx.accounts.token_a_vault.amount,
            )
        };

        // Calculate output using constant product formula
        // x * y = k
        // (x + dx) * (y - dy) = k
        // dy = y * dx / (x + dx)
        let amount_out = calculate_swap_output(
            amount_in,
            source_vault,
            dest_vault,
            pool.fee_bps,
        )?;

        require!(
            amount_out >= minimum_amount_out,
            ErrorCode::SlippageExceeded
        );

        // Transfer input tokens
        let (user_source, vault_source, user_dest, vault_dest) = if a_to_b {
            (
                &ctx.accounts.user_token_a,
                &ctx.accounts.token_a_vault,
                &ctx.accounts.user_token_b,
                &ctx.accounts.token_b_vault,
            )
        } else {
            (
                &ctx.accounts.user_token_b,
                &ctx.accounts.token_b_vault,
                &ctx.accounts.user_token_a,
                &ctx.accounts.token_a_vault,
            )
        };

        token::transfer(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: user_source.to_account_info(),
                    to: vault_source.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                },
            ),
            amount_in,
        )?;

        // Transfer output tokens (PDA signs)
        let pool_key = pool.key();
        let seeds = &[b"pool", pool_key.as_ref(), &[pool.bump]];
        let signer_seeds = &[&seeds[..]];

        token::transfer(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: vault_dest.to_account_info(),
                    to: user_dest.to_account_info(),
                    authority: ctx.accounts.pool.to_account_info(),
                },
                signer_seeds,
            ),
            amount_out,
        )?;

        emit!(SwapExecuted {
            pool: pool.key(),
            user: ctx.accounts.user.key(),
            amount_in,
            amount_out,
            a_to_b,
        });

        Ok(())
    }

    pub fn remove_liquidity(
        ctx: Context<RemoveLiquidity>,
        lp_amount: u64,
        min_a: u64,
        min_b: u64,
    ) -> Result<()> {
        let pool = &ctx.accounts.pool;

        let vault_a_amount = ctx.accounts.token_a_vault.amount;
        let vault_b_amount = ctx.accounts.token_b_vault.amount;
        let lp_supply = ctx.accounts.lp_mint.supply;

        // Calculate proportional amounts
        let amount_a = (lp_amount as u128)
            .checked_mul(vault_a_amount as u128)
            .ok_or(ErrorCode::MathOverflow)?
            .checked_div(lp_supply as u128)
            .ok_or(ErrorCode::MathOverflow)? as u64;

        let amount_b = (lp_amount as u128)
            .checked_mul(vault_b_amount as u128)
            .ok_or(ErrorCode::MathOverflow)?
            .checked_div(lp_supply as u128)
            .ok_or(ErrorCode::MathOverflow)? as u64;

        require!(amount_a >= min_a && amount_b >= min_b, ErrorCode::SlippageExceeded);

        // Burn LP tokens
        token::burn(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                token::Burn {
                    mint: ctx.accounts.lp_mint.to_account_info(),
                    from: ctx.accounts.user_lp.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                },
            ),
            lp_amount,
        )?;

        // Transfer tokens back (PDA signs)
        let pool_key = pool.key();
        let seeds = &[b"pool", pool_key.as_ref(), &[pool.bump]];
        let signer_seeds = &[&seeds[..]];

        token::transfer(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: ctx.accounts.token_a_vault.to_account_info(),
                    to: ctx.accounts.user_token_a.to_account_info(),
                    authority: ctx.accounts.pool.to_account_info(),
                },
                signer_seeds,
            ),
            amount_a,
        )?;

        token::transfer(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                Transfer {
                    from: ctx.accounts.token_b_vault.to_account_info(),
                    to: ctx.accounts.user_token_b.to_account_info(),
                    authority: ctx.accounts.pool.to_account_info(),
                },
                signer_seeds,
            ),
            amount_b,
        )?;

        emit!(LiquidityRemoved {
            pool: pool.key(),
            user: ctx.accounts.user.key(),
            lp_amount,
            amount_a,
            amount_b,
        });

        Ok(())
    }
}

fn calculate_swap_output(
    amount_in: u64,
    source_reserve: u64,
    dest_reserve: u64,
    fee_bps: u16,
) -> Result<u64> {
    // Apply fee
    let amount_in_with_fee = (amount_in as u128)
        .checked_mul((10000 - fee_bps) as u128)
        .ok_or(ErrorCode::MathOverflow)?;

    // x * y = k formula
    let numerator = amount_in_with_fee
        .checked_mul(dest_reserve as u128)
        .ok_or(ErrorCode::MathOverflow)?;

    let denominator = (source_reserve as u128)
        .checked_mul(10000)
        .ok_or(ErrorCode::MathOverflow)?
        .checked_add(amount_in_with_fee)
        .ok_or(ErrorCode::MathOverflow)?;

    Ok((numerator / denominator) as u64)
}

#[account]
pub struct Pool {
    pub authority: Pubkey,
    pub token_a_mint: Pubkey,
    pub token_b_mint: Pubkey,
    pub token_a_vault: Pubkey,
    pub token_b_vault: Pubkey,
    pub lp_mint: Pubkey,
    pub fee_bps: u16,
    pub bump: u8,
}

#[event]
pub struct LiquidityAdded {
    pub pool: Pubkey,
    pub user: Pubkey,
    pub amount_a: u64,
    pub amount_b: u64,
    pub lp_tokens: u64,
}

#[event]
pub struct SwapExecuted {
    pub pool: Pubkey,
    pub user: Pubkey,
    pub amount_in: u64,
    pub amount_out: u64,
    pub a_to_b: bool,
}

#[event]
pub struct LiquidityRemoved {
    pub pool: Pubkey,
    pub user: Pubkey,
    pub lp_amount: u64,
    pub amount_a: u64,
    pub amount_b: u64,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Invalid fee")]
    InvalidFee,
    #[msg("Math overflow")]
    MathOverflow,
    #[msg("Slippage exceeded")]
    SlippageExceeded,
}

Frontend Integration

TypeScript
"use client";

import { useState, useEffect } from "react";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";

interface PoolInfo {
  tokenA: { mint: PublicKey; symbol: string; balance: number };
  tokenB: { mint: PublicKey; symbol: string; balance: number };
  lpSupply: number;
  feeBps: number;
}

export function SwapInterface({ poolAddress }: { poolAddress: string }) {
  const { connection } = useConnection();
  const { publicKey, signTransaction } = useWallet();

  const [poolInfo, setPoolInfo] = useState<PoolInfo | null>(null);
  const [amountIn, setAmountIn] = useState("");
  const [amountOut, setAmountOut] = useState("");
  const [aToB, setAToB] = useState(true);
  const [slippage, setSlippage] = useState(0.5);
  const [loading, setLoading] = useState(false);

  // Calculate output amount
  useEffect(() => {
    if (!poolInfo || !amountIn) {
      setAmountOut("");
      return;
    }

    const input = parseFloat(amountIn);
    if (isNaN(input) || input <= 0) {
      setAmountOut("");
      return;
    }

    const sourceReserve = aToB ? poolInfo.tokenA.balance : poolInfo.tokenB.balance;
    const destReserve = aToB ? poolInfo.tokenB.balance : poolInfo.tokenA.balance;

    const amountInWithFee = input * (1 - poolInfo.feeBps / 10000);
    const output = (amountInWithFee * destReserve) / (sourceReserve + amountInWithFee);

    setAmountOut(output.toFixed(6));
  }, [amountIn, aToB, poolInfo]);

  const handleSwap = async () => {
    if (!publicKey || !signTransaction || !poolInfo) return;

    setLoading(true);
    try {
      const inputAmount = parseFloat(amountIn);
      const expectedOutput = parseFloat(amountOut);
      const minOutput = expectedOutput * (1 - slippage / 100);

      // Build and send transaction
      // ... (transaction building code)

      alert("Swap successful!");
    } catch (error) {
      console.error("Swap failed:", error);
      alert("Swap failed");
    } finally {
      setLoading(false);
    }
  };

  const priceImpact = () => {
    if (!poolInfo || !amountIn || !amountOut) return 0;

    const input = parseFloat(amountIn);
    const output = parseFloat(amountOut);
    const sourceReserve = aToB ? poolInfo.tokenA.balance : poolInfo.tokenB.balance;
    const destReserve = aToB ? poolInfo.tokenB.balance : poolInfo.tokenA.balance;

    const spotPrice = destReserve / sourceReserve;
    const executionPrice = output / input;

    return ((spotPrice - executionPrice) / spotPrice) * 100;
  };

  return (
    <div className="max-w-md mx-auto p-6 bg-card rounded-xl border">
      <h2 className="text-xl font-bold mb-4">Swap</h2>

      {/* Input */}
      <div className="p-4 bg-muted rounded-lg mb-2">
        <div className="flex justify-between mb-2">
          <span className="text-sm text-muted-foreground">From</span>
          <span className="text-sm">
            Balance: {aToB ? poolInfo?.tokenA.balance : poolInfo?.tokenB.balance}
          </span>
        </div>
        <div className="flex gap-2">
          <input
            type="number"
            value={amountIn}
            onChange={(e) => setAmountIn(e.target.value)}
            placeholder="0.0"
            className="flex-1 bg-transparent text-2xl outline-none"
          />
          <button className="px-3 py-1 bg-background rounded-lg">
            {aToB ? poolInfo?.tokenA.symbol : poolInfo?.tokenB.symbol}
          </button>
        </div>
      </div>

      {/* Swap direction */}
      <div className="flex justify-center -my-2 relative z-10">
        <button
          onClick={() => setAToB(!aToB)}
          className="p-2 bg-background rounded-full border hover:bg-muted"
        ></button>
      </div>

      {/* Output */}
      <div className="p-4 bg-muted rounded-lg mb-4">
        <div className="flex justify-between mb-2">
          <span className="text-sm text-muted-foreground">To</span>
          <span className="text-sm">
            Balance: {aToB ? poolInfo?.tokenB.balance : poolInfo?.tokenA.balance}
          </span>
        </div>
        <div className="flex gap-2">
          <input
            type="text"
            value={amountOut}
            readOnly
            placeholder="0.0"
            className="flex-1 bg-transparent text-2xl outline-none"
          />
          <button className="px-3 py-1 bg-background rounded-lg">
            {aToB ? poolInfo?.tokenB.symbol : poolInfo?.tokenA.symbol}
          </button>
        </div>
      </div>

      {/* Info */}
      {amountIn && amountOut && (
        <div className="p-3 bg-muted/50 rounded-lg mb-4 text-sm space-y-1">
          <div className="flex justify-between">
            <span>Price Impact</span>
            <span className={priceImpact() > 5 ? "text-red-500" : ""}>
              {priceImpact().toFixed(2)}%
            </span>
          </div>
          <div className="flex justify-between">
            <span>Minimum received</span>
            <span>
              {(parseFloat(amountOut) * (1 - slippage / 100)).toFixed(6)}
            </span>
          </div>
          <div className="flex justify-between">
            <span>Fee ({poolInfo?.feeBps} bps)</span>
            <span>
              {((parseFloat(amountIn) * (poolInfo?.feeBps || 0)) / 10000).toFixed(6)}
            </span>
          </div>
        </div>
      )}

      <button
        onClick={handleSwap}
        disabled={!publicKey || !amountIn || loading}
        className="w-full py-3 bg-primary text-primary-foreground rounded-lg font-medium disabled:opacity-50"
      >
        {loading ? "Swapping..." : publicKey ? "Swap" : "Connect Wallet"}
      </button>
    </div>
  );
}

Project List

ProjectDescriptionConcepts Covered
Token SwapAMM with constant productPDAs, CPIs, Math
NFT MarketplaceBuy/sell/auction NFTsMetaplex, Escrow
Lending ProtocolBorrow against collateralInterest rates, Liquidations
DAO GovernorProposal and voting systemSPL Governance
Staking PlatformStake tokens for rewardsTime-weighted rewards
CEX PlatformCentralized exchangeWeb2/Web3 hybrid, Deposits
Prediction MarketBet on future outcomesLMSR, Oracles, Settlement

Next: Glossary - Key terms and definitions.