What Is a Liquidity Locker?
A liquidity locker is a smart contract that holds LP tokens or project tokens for a fixed period. It proves to investors that a team cannot rug-pull by locking liquidity in a trustless, on-chain vault. This is a critical trust primitive for any token launch on Solana.
In this tutorial you will build a complete liquidity locker program with Anchor that supports SPL tokens, configurable lock durations, and permissionless unlocking after expiry.
Prerequisites
- Rust toolchain (
rustup— stable channel) - Solana CLI v1.18+
- Anchor CLI v0.30+
- Node.js 18+
solana --version
anchor --version1. Scaffold the Project
anchor init liquidity-locker
cd liquidity-locker2. Define the Lock Account
Every lock needs to track the depositor, the token mint, the amount, and the unlock timestamp.
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
declare_id!("YOUR_PROGRAM_ID");
#[account]
pub struct LockAccount {
pub authority: Pubkey,
pub mint: Pubkey,
pub amount: u64,
pub unlock_ts: i64,
pub vault: Pubkey,
pub unlocked: bool,
pub bump: u8,
}
3. Create Lock Instruction
The create_lock instruction transfers SPL tokens from the depositor into a PDA-controlled vault and records the lock parameters.
#[program]
pub mod liquidity_locker {
use super::*;
pub fn create_lock(
ctx: Context<CreateLock>,
amount: u64,
unlock_ts: i64,
) -> Result<()> {
let clock = Clock::get()?;
require!(unlock_ts > clock.unix_timestamp, LockerError::UnlockInPast);
require!(amount > 0, LockerError::ZeroAmount);
let lock = &mut ctx.accounts.lock_account;
The PDA that owns the vault must be the lock_account itself (or a separate vault PDA). Using CpiContext::new_with_signer lets the program sign the transfer on behalf of the PDA. This is the core escrow pattern for DeFi on Solana.
4. Account Structs
#[derive(Accounts)]
pub struct CreateLock<'info> {
#[account(
init,
payer = authority,
space = LockAccount::SIZE,
seeds = [b"lock", authority.key().as_ref(), mint.key().as_ref()],
bump,
)]
pub lock_account: Account<'info, LockAccount>,
#[account(
init,
payer = authority,
token::mint = mint,
token::authority = lock_account,
)]
pub vault: Account<'info, TokenAccount>,
5. Error Codes and Events
#[error_code]
pub enum LockerError {
#[msg("Unlock timestamp must be in the future")]
UnlockInPast,
#[msg("Amount must be greater than zero")]
ZeroAmount,
#[msg("Lock has already been unlocked")]
AlreadyUnlocked,
#[msg("Cannot unlock before the unlock timestamp")]
TooEarly,
}
#[event]
pub struct LockCreated {
pub authority: Pubkey,
pub mint: Pubkey,
pub amount: u64,
pub unlock_ts: i64,
}
#[event]
pub
6. Build and Deploy
anchor build
anchor keys list
# Update declare_id!() and Anchor.toml
anchor deploy --provider.cluster devnet7. TypeScript Client
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PublicKey, SystemProgram } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from "@solana/spl-token";
import { LiquidityLocker } from "../target/types/liquidity_locker";
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.LiquidityLocker as Program<
8. Integration Tests
describe("liquidity-locker", () => {
it("creates a lock and prevents early unlock", async () => {
// Create lock with 30-day duration
// Attempt unlock immediately → expect LockerError::TooEarly
});
it("allows unlock after expiry", async () => {
// Use solana-test-validator with --warp-slot to advance time
// Unlock → verify tokens returned to depositor
});
});anchor testProduction Considerations
| Concern | Solution |
|---|---|
| Multiple locks per user | Add a nonce or counter to the PDA seeds |
| Partial unlock | Track amount_unlocked and allow claiming in portions |
| Lock extension | Add an extend_lock instruction that pushes unlock_ts forward |
| Frontend | Display countdown timer and lock status from on-chain data |
| Verification | Build a public explorer page so anyone can verify locks |
Wrapping Up
A liquidity locker is the trust layer every token launch needs. The pattern — PDA-controlled vault, time-gated unlock, SPL token CPI — applies to vesting schedules, treasury management, and any time-locked DeFi primitive.
For more Anchor patterns, see our Coinflip dApp tutorial and the P2P swap guide.