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
| Project | Description | Concepts Covered |
|---|---|---|
| Token Swap | AMM with constant product | PDAs, CPIs, Math |
| NFT Marketplace | Buy/sell/auction NFTs | Metaplex, Escrow |
| Lending Protocol | Borrow against collateral | Interest rates, Liquidations |
| DAO Governor | Proposal and voting system | SPL Governance |
| Staking Platform | Stake tokens for rewards | Time-weighted rewards |
| CEX Platform | Centralized exchange | Web2/Web3 hybrid, Deposits |
| Prediction Market | Bet on future outcomes | LMSR, Oracles, Settlement |
Next: Glossary - Key terms and definitions.