Skip to content

rustdevlabs/smartv21

Repository files navigation

smartv21 — Anchor program for creating Raydium CPMM pools with managed liquidity

This repository contains an Anchor-based Solana program that creates and manages Raydium CPMM pools via CPI, while adding a thin layer of business logic around:

  • Bootstrapping a new CPMM pool (Raydium’s constant‑product AMM) with wSOL + a target token.
  • Custodying LP tokens in a program‑owned vault as collateral for a short, configurable “loan period.”
  • Dynamic, size‑tiered service fees in lamports based on the initial SOL contributed.
  • Authorized early close / liquidation of the pool position if the loan expires or policy requires it.
  • Token‑2022 awareness (uses transfer_checked from the Token‑2022 interface; works with standard SPL tokens too).

Status: engineering/developer preview. Not audited. Mainnet use is at your own risk.


High‑level architecture

The program integrates with Raydium’s CPMM program (raydium-cpmm-cpi) using CPI calls to create a pool, mint LP, and withdraw liquidity. On top, it maintains a small set of program accounts and PDAs to track policy and collateral:

Program accounts (PDAs)

PDA Seed(s) Purpose
Config "config" Global settings: admin keys, service‑fee tiers, pause flag, running counter of vault balance.
PoolLoan "pool_loan", pool_state Per‑pool loan metadata: user, pool, LP mint, token mint, initial amounts, start time/duration, repaid flag.
Service Vault "vault" Program‑owned token account (ATA) for the asset used as service currency (typically wSOL).
Service LP "lp_token", pool_state Program‑owned ATA for the Raydium LP mint used to temporarily custody LP as collateral.

Raydium CPMM PDAs (e.g. POOL_SEED, POOL_VAULT_SEED, POOL_LP_MINT_SEED, OBSERVATION_SEED) are derived inside CPI calls and are not owned by this program.

Dynamic service fee

Config stores fixed lamport amounts for the following SOL tiers: 2, 5, 10, 20, 50, 100, 200. When bootstrapping a pool with a given wSOL amount, the program charges the corresponding fixed fee and moves it to the Service Vault upfront.

Loan / collateral model

  1. Create liquidity (Raydium CPI): user provides wSOL + token; pool is created and LP is minted.
  2. LP custody: all minted LP is transferred to Service LP (program PDA) as collateral using send_lp_tokens.
  3. User close (before expiry): user can call remove_liquidity within the loan window; the program returns LP (PDA‑signed), withdraws from the pool, and enforces repayment of the wSOL principal back to Service Vault. The user keeps the counter‑asset received from the withdrawal.
  4. Liquidation (after expiry / by operator): an authorized operator (admin, syncer, or verifier) can call liquidate_loan; the program unwinds liquidity, settles balances, marks the loan repaid, emits an event, and updates Config.amount accordingly.

On‑chain program ID

  • smartv21 (devnet): 94f9T2tk9gfUhvvAP4WUMbQNj99M6t7T1xDiX73dEfvJ
    (Configured in Anchor.toml[programs.devnet])

Raydium CPMM program IDs differ by cluster. In tests we use the mainnet CPMM id CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C (a devnet id is commented). Adjust for your environment.


Instruction reference

Below is a concise reference of public entrypoints defined by the program.

initialize(syncer: Pubkey, verifier: Pubkey)

Creates the global Config PDA, sets the admin to the signer, stores the syncer and verifier, writes default fee tiers, and initializes the Service Vault (program‑owned ATA) for the chosen token_mint (typically wSOL).

Key accounts (abridged):

  • admin (signer) – becomes config.admin
  • config (PDA "config") – created
  • token_mint – mint used for the Service Vault (commonly So111... for wSOL)
  • service_vault (PDA "vault") – ATA with authority = config
  • token_program (Token‑2022 interface) / system_program

Default fees (lamports):
2→200_000_000, 5→250_000_000, 10→300_000_000, 20→400_000_000, 50→1_000_000_000, 100→2_500_000_000, 200→5_000_000_000


update_service_fee(new_fixed_fee: u64, index: u16)

Admin‑only. Updates one of the fee tiers where index ∈ {2,5,10,20,50,100,200}.


create_liquidity_pool(init_amount_0: u64, init_amount_1: u64, open_time: u64, loan_duration: i64)

Creates a Raydium CPMM pool via CPI and records a PoolLoan row. Supports either ordering of wSOL (So111...) as token_0 or token_1. Enforces:

  • If wSOL is provided, its amount must equal one of 2/5/10/20/50/100/200 SOL (in lamports).
  • For the counter token: total supply must equal init_amount_* and both mint and freeze authorities must be revoked.

Also charges the dynamic service fee to Service Vault upfront and stores loan timing:

  • loan_start_time = Clock::get().unix_timestamp
  • loan_duration (e.g., 60*60*24 for 1 day)

Key accounts (abridged):

  • config (PDA "config")
  • pool_loan (PDA "pool_loan", seed includes pool_state) – created
  • service_vault (PDA "vault")
  • Raydium CPMM accounts: cp_swap_program, pool_state, lp_mint, token_0_vault, token_1_vault, observation_state, create_pool_fee
  • User token accounts for the two mints and the LP mint

send_lp_tokens()

Moves all LP minted to the user into the Service LP PDA for safekeeping (collateral).

Key accounts (abridged):

  • pool_loan (PDA "pool_loan")
  • service_token_lp (PDA "lp_token", ATA for lp_mint owned by program)
  • owner (signer) + owner_lp_token
  • pool_state, lp_mint, token_program

remove_liquidity(lp_token_amount, minimum_token_0_amount, minimum_token_1_amount)

User path (before expiry). Returns the specified LP amount from Service LP back to the user (PDA‑signed), executes Raydium withdraw via CPI, and then enforces repayment of the wSOL principal by transferring it back to Service Vault. Marks loan repaid when conditions are met.

Key constraints:

  • Caller must be pool_loan.user.
  • Must be within loan_start_time + loan_duration.
  • Fails if pool_loan.is_repaid is already true.

liquidate_loan(lp_token_amount, minimum_token_0_amount, minimum_token_1_amount)

Operator path (after expiry). Authorized caller (config.admin / config.syncer / config.verifier) returns LP (PDA‑signed), unwinds via Raydium withdraw, settles balances (including wSOL back to Service Vault), marks as repaid, and emits:

#[event]
pub struct LoanLiquidatedEvent {
  pub pool: Pubkey,
  pub user: Pubkey,
  pub liquidator: Pubkey,
  pub amount: u64,
  pub timestamp: i64,
}

Service Vault management

deposit(amount: u64) / withdraw(amount: u64)

Admin‑only helper to fund / empty the program’s Service Vault ("vault" PDA). Uses Token‑2022 transfer_checked and updates config.amount as a running balance.


Errors (selected)

  • ProgramPaused
  • InvalidFeeIndex
  • InsufficientBalance
  • InsufficientTokenBalance (counter‑asset supply must match expected at pool creation)
  • InvalidDuration
  • InvalidInitSolAmount (must be one of 2/5/10/20/50/100/200 SOL)
  • Unauthorized
  • LoanExpired / LoanNotExpired
  • InvalidFee, InvalidTreasury
  • MintAuthorityNotRevoked / FreezeAuthorityNotRevoked

See programs/smartv21/src/error.rs for the full list.


Local development

Prerequisites

  • Rust & Solana toolchain
  • Anchor 0.29.0 (see Cargo.toml / program uses anchor-lang = "=0.29.0")
  • Node 18+ and yarn (for tests)
  • A devnet wallet with SOL for fees

Configure your provider

Do not commit private RPC keys. In Anchor.toml, set your own cluster & wallet, e.g.:

[provider]
cluster = "https://api.devnet.solana.com"
wallet = "~/.config/solana/id.json"

Build

anchor build

Test

yarn install
anchor test
# or, as configured:
yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/smartv21.ts

The test suite demonstrates a full flow:

  1. Program initialize and Service Vault setup.
  2. Creating a Raydium CPMM pool with wSOL + token.
  3. Custodying LP to Service LP via send_lp_tokens.
  4. Withdrawing liquidity via remove_liquidity (user) and via liquidate_loan (operator).

Raydium CPMM program id is configurable in tests/config.ts. Use the correct id for your cluster.


Usage notes & gotchas

  • Counter token mint authorities must be revoked prior to pool creation:
    • mint_authority.is_none() and freeze_authority.is_none() are enforced.
  • Allowed SOL sizes: 2/5/10/20/50/100/200 SOL (lamports).
  • The program is Token‑2022 aware and uses transfer_checked. Standard SPL tokens are supported as well.
  • The Service Vault is an ATA controlled by the Config PDA. All movements from it require PDA signing.
  • Config.amount is a running balance field for the Service Vault (for accounting/telemetry).
  • Program emits LoanLiquidatedEvent on liquidation for off-chain indexing.

Directory layout

programs/smartv21/
 ├─ src/
 │   ├─ lib.rs                # entrypoints and instruction routing
 │   ├─ constants.rs          # PDA seed constants
 │   ├─ state/mod.rs          # Config, PoolLoan
 │   ├─ error.rs              # custom errors
 │   ├─ event.rs              # events
 │   └─ instructions/
 │        ├─ initialize.rs    # initialize, update_service_fee, create_liquidity_pool, send_lp_tokens
 │        ├─ manage.rs        # deposit, withdraw (Service Vault)
 │        ├─ withdraw_pool.rs # remove_liquidity (user path)
 │        └─ liquidate_loan.rs# liquidate_loan (operator path)
tests/
 ├─ smartv21.ts               # end‑to‑end tests
 └─ utils/                    # PDA helpers, instruction wrappers

Security & audits

This code has not undergone a formal audit. It interacts with external programs (Raydium CPMM, Token‑2022). Use on mainnet at your own risk. Review constraints, authority checks, and fee logic for your specific use case.


License

Add a license file (e.g., LICENSE) of your choice. If you’re unsure, MIT is a common choice for open‑source Solana programs.


Acknowledgements

About

Anchor program for creating Raydium CPMM pools with managed liquidity

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published