This provides a concise overview of a Concentrated Liquidity Automated Market Maker (CLAMM) and demonstrates how to integrate it into your Solana program using the Anchor framework.
- What Is CLAMM?
- Key Concepts
- Why Use CLAMM on Solana?
- Prerequisites
- Project Setup
- Anchor Program Structure
- Client Example (TypeScript)
- Testing
- Further Reading
A Concentrated Liquidity Automated Market Maker (CLAMM) is an AMM design where liquidity providers (LPs) allocate capital within custom price ranges (ticks), similar to Uniswap v3. By concentrating liquidity:
- LPs earn higher fees on their capital.
- Traders experience deeper liquidity and lower slippage around current prices.
Core components:
- Ticks: Discrete price boundaries defining where liquidity is active.
- Positions: LP deposits assigned to a tick range.
- Swap math: Calculates output amounts and updates liquidity using √price and tick data.
| Term | Description |
|---|---|
| Tick | A price step; liquidity is active between two ticks. |
| Liquidity | Depth provided by LPs within a tick range. |
| √Price | Square-root of price used in CLAMM formulas. |
| Fee Tier | Percentage fee (e.g., 0.3%) charged per swap. |
- High throughput: Solana’s parallel runtime executes CLAMM instructions quickly.
- Low fees: Enables micro-fee strategies for LPs.
- Deterministic PDAs: Anchor’s Program Derived Addresses manage tick arrays and vault accounts safely.
- Rust and Solana toolchain installed (
rustup,solana-cli). - Anchor framework (
npm install -g @project-serum/anchor-cli). - Node.js and Yarn or npm.
programs/
clamm-anchor/
src/
lib.rs # program entry
state.rs # account definitions (Pool, Position)
instruction.rs # instructions (initialize_pool, mint_position, swap)
Cargo.toml
tests/
clamm-anchor.ts # integration tests
lib.rs snippet:
use anchor_lang::prelude::*;
use clamm_core_math::{compute_swap, TickArray};
#[program]
pub mod clamm_anchor {
use super::*;
pub fn initialize_pool(ctx: Context<Init>, fee: u16, tick_spacing: i32) -> Result<()> {
let pool = &mut ctx.accounts.pool;
pool.fee = fee;
pool.tick_spacing = tick_spacing;
Ok(())
}
pub fn mint_position(ctx: Context<Mint>, lower_tick: i32, upper_tick: i32, liquidity: u128) -> Result<()> {
let pool = &mut ctx.accounts.pool;
// update tick arrays, position state
Ok(())
}
pub fn swap(ctx: Context<Swap>, amount_in: u64) -> Result<u64> {
let pool = &mut ctx.accounts.pool;
let amount_out = compute_swap(pool, amount_in)?;
Ok(amount_out)
}
}import * as anchor from "@project-serum/anchor";
import { ClammAnchor } from "../target/types/clamm_anchor";
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.ClammAnchor as anchor.Program<ClammAnchor>;
async function main() {
// Initialize pool
await program.rpc.initializePool(30 /* fee bps */, 64 /* tick spacing */, {
accounts: {
pool: poolPda,
payer: provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
// Mint position
await program.rpc.mintPosition(-128, 128, new anchor.BN(1_000_000), {
accounts: { pool: poolPda, position: positionPda, payer: provider.wallet.publicKey },
});
// Execute swap
const out = await program.rpc.swap(new anchor.BN(10_000), {
accounts: { pool: poolPda, payer: provider.wallet.publicKey },
});
console.log("Swapped out:", out.toString());
}
main();# Run local validator
anchor localnet --reset
# In another terminal, run tests
anchor test