Skip to main content

tribewarez_swap/
lib.rs

1//! # tribewarez-swap
2//!
3//! Automated Market Maker (AMM) Swap Program for Tribewarez DeFi.
4//!
5//! This crate implements a constant product AMM (x * y = k) for PTtC token swaps on Solana.
6//! It provides liquidity pools, enables decentralized trading, and collects protocol fees with
7//! optional tensor network support for dynamic fee discounts based on network coherence metrics.
8//!
9//! ## Core Features
10//!
11//! - **Liquidity Pools**: Create pools for trading pairs with constant product formula
12//! - **Token Swaps**: Execute swaps with automatic price discovery and slippage protection
13//! - **Liquidity Provision**: Add and remove liquidity with LP token minting/burning
14//! - **Fee Collection**: Automatic protocol fee collection and distribution
15//! - **Tensor-Aware Fees**: v0.2.0 feature for dynamic fee discounts based on network metrics
16//! - **Price Quotes**: Query swap quotes before executing trades
17//!
18//! ## Key Instructions
19//!
20//! - `initialize_pool`: Create a new liquidity pool for a trading pair
21//! - `add_liquidity`: Provide liquidity to a pool and receive LP tokens
22//! - `remove_liquidity`: Withdraw liquidity by burning LP tokens
23//! - `swap`: Execute a swap with specified slippage protection
24//! - `get_swap_quote`: Query the output amount for a given input
25//! - `withdraw_protocol_fees`: Withdraw accumulated protocol fees (admin-only)
26//!
27//! ## Events
28//!
29//! This program emits events for pool initialization, liquidity changes, swaps, and fee withdrawals.
30//! See the [`events`] module for detailed event documentation.
31//!
32//! ## AMM Formula
33//!
34//! The constant product formula ensures: `token_a_reserve * token_b_reserve = k`
35//!
36//! For a swap of `amount_in` of token A:
37//! - `amount_out = (amount_in * token_b_reserve) / (token_a_reserve + amount_in)`
38//! - Less protocol and LP fees
39//!
40//! ## Fee Structure
41//!
42//! - **Swap Fee**: 0.30% to liquidity providers
43//! - **Protocol Fee**: 0.05% retained by protocol
44//! - **Tensor Discount**: Applied to fees for network participants (v0.2.0)
45
46use anchor_lang::prelude::*;
47use anchor_spl::token::{self, Burn, Mint, MintTo, Token, TokenAccount, Transfer};
48
49// Module declarations
50pub mod events;
51pub mod services;
52
53// Re-export events for use in instructions
54use events::{
55    FeesWithdrawn, LiquidityAdded, LiquidityRemoved, PoolInitialized, SwapQuote, Swapped,
56};
57
58declare_id!("GPGGnKwnvKseSxzPukrNvch1CwYhifTqgj2RdW1P26H3");
59
60/// Tribewarez Swap Program
61/// Constant Product AMM (x * y = k) for PTtC token swaps.
62/// Supports liquidity provision, swaps, and fee collection.
63///
64/// v0.2.0 includes tensor network support for dynamic fee discounts
65/// based on coherence and quantum entanglement calculations.
66// Fee configuration (basis points - 10000 = 100%)
67const SWAP_FEE_BPS: u64 = 30; // 0.30% swap fee
68const PROTOCOL_FEE_BPS: u64 = 5; // 0.05% protocol fee
69#[allow(dead_code)]
70const LP_FEE_BPS: u64 = 25; // 0.25% to LPs
71
72#[program]
73pub mod tribewarez_swap {
74    use super::*;
75
76    /// Initialize a new liquidity pool
77    pub fn initialize_pool(ctx: Context<InitializePool>, pool_bump: u8) -> Result<()> {
78        let pool = &mut ctx.accounts.pool;
79
80        pool.authority = ctx.accounts.authority.key();
81        pool.token_a_mint = ctx.accounts.token_a_mint.key();
82        pool.token_b_mint = ctx.accounts.token_b_mint.key();
83        pool.token_a_vault = ctx.accounts.token_a_vault.key();
84        pool.token_b_vault = ctx.accounts.token_b_vault.key();
85        pool.lp_mint = ctx.accounts.lp_mint.key();
86        pool.reserve_a = 0;
87        pool.reserve_b = 0;
88        pool.total_lp_supply = 0;
89        pool.swap_fee_bps = SWAP_FEE_BPS;
90        pool.protocol_fee_bps = PROTOCOL_FEE_BPS;
91        pool.collected_fees_a = 0;
92        pool.collected_fees_b = 0;
93        pool.bump = pool_bump;
94        pool.is_active = true;
95        pool.created_at = Clock::get()?.unix_timestamp;
96
97        emit!(PoolInitialized {
98            pool: pool.key(),
99            token_a_mint: pool.token_a_mint,
100            token_b_mint: pool.token_b_mint,
101            lp_mint: pool.lp_mint,
102        });
103
104        Ok(())
105    }
106
107    /// Add liquidity to the pool
108    pub fn add_liquidity(
109        ctx: Context<AddLiquidity>,
110        amount_a: u64,
111        amount_b: u64,
112        min_lp_tokens: u64,
113    ) -> Result<()> {
114        require!(amount_a > 0 && amount_b > 0, SwapError::InvalidAmount);
115        require!(ctx.accounts.pool.is_active, SwapError::PoolInactive);
116
117        let pool = &mut ctx.accounts.pool;
118        let lp_tokens_to_mint: u64;
119
120        if pool.total_lp_supply == 0 {
121            // First liquidity provider - mint sqrt(a * b) LP tokens
122            lp_tokens_to_mint = (amount_a as u128)
123                .checked_mul(amount_b as u128)
124                .ok_or(SwapError::MathOverflow)?
125                .integer_sqrt() as u64;
126
127            require!(lp_tokens_to_mint > 0, SwapError::InsufficientLiquidity);
128        } else {
129            // Calculate proportional LP tokens
130            let lp_from_a = (amount_a as u128)
131                .checked_mul(pool.total_lp_supply as u128)
132                .ok_or(SwapError::MathOverflow)?
133                .checked_div(pool.reserve_a as u128)
134                .ok_or(SwapError::MathOverflow)? as u64;
135
136            let lp_from_b = (amount_b as u128)
137                .checked_mul(pool.total_lp_supply as u128)
138                .ok_or(SwapError::MathOverflow)?
139                .checked_div(pool.reserve_b as u128)
140                .ok_or(SwapError::MathOverflow)? as u64;
141
142            // Use minimum to prevent manipulation
143            lp_tokens_to_mint = lp_from_a.min(lp_from_b);
144        }
145
146        require!(
147            lp_tokens_to_mint >= min_lp_tokens,
148            SwapError::SlippageExceeded
149        );
150
151        // Transfer token A to pool
152        let cpi_accounts_a = Transfer {
153            from: ctx.accounts.user_token_a.to_account_info(),
154            to: ctx.accounts.token_a_vault.to_account_info(),
155            authority: ctx.accounts.user.to_account_info(),
156        };
157        token::transfer(
158            CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts_a),
159            amount_a,
160        )?;
161
162        // Transfer token B to pool
163        let cpi_accounts_b = Transfer {
164            from: ctx.accounts.user_token_b.to_account_info(),
165            to: ctx.accounts.token_b_vault.to_account_info(),
166            authority: ctx.accounts.user.to_account_info(),
167        };
168        token::transfer(
169            CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts_b),
170            amount_b,
171        )?;
172
173        // Mint LP tokens to user using PDA signer
174        let token_a_mint = pool.token_a_mint;
175        let token_b_mint = pool.token_b_mint;
176        let seeds = &[
177            b"pool",
178            token_a_mint.as_ref(),
179            token_b_mint.as_ref(),
180            &[pool.bump],
181        ];
182        let signer = &[&seeds[..]];
183
184        let cpi_accounts_mint = MintTo {
185            mint: ctx.accounts.lp_mint.to_account_info(),
186            to: ctx.accounts.user_lp_account.to_account_info(),
187            authority: pool.to_account_info(),
188        };
189        token::mint_to(
190            CpiContext::new_with_signer(
191                ctx.accounts.token_program.to_account_info(),
192                cpi_accounts_mint,
193                signer,
194            ),
195            lp_tokens_to_mint,
196        )?;
197
198        // Update pool state
199        pool.reserve_a = pool
200            .reserve_a
201            .checked_add(amount_a)
202            .ok_or(SwapError::MathOverflow)?;
203        pool.reserve_b = pool
204            .reserve_b
205            .checked_add(amount_b)
206            .ok_or(SwapError::MathOverflow)?;
207        pool.total_lp_supply = pool
208            .total_lp_supply
209            .checked_add(lp_tokens_to_mint)
210            .ok_or(SwapError::MathOverflow)?;
211
212        emit!(LiquidityAdded {
213            pool: pool.key(),
214            user: ctx.accounts.user.key(),
215            amount_a,
216            amount_b,
217            lp_tokens: lp_tokens_to_mint,
218        });
219
220        Ok(())
221    }
222
223    /// Remove liquidity from the pool
224    pub fn remove_liquidity(
225        ctx: Context<RemoveLiquidity>,
226        lp_amount: u64,
227        min_amount_a: u64,
228        min_amount_b: u64,
229    ) -> Result<()> {
230        require!(lp_amount > 0, SwapError::InvalidAmount);
231
232        let pool = &mut ctx.accounts.pool;
233
234        // Calculate token amounts to return
235        let amount_a = (lp_amount as u128)
236            .checked_mul(pool.reserve_a as u128)
237            .ok_or(SwapError::MathOverflow)?
238            .checked_div(pool.total_lp_supply as u128)
239            .ok_or(SwapError::MathOverflow)? as u64;
240
241        let amount_b = (lp_amount as u128)
242            .checked_mul(pool.reserve_b as u128)
243            .ok_or(SwapError::MathOverflow)?
244            .checked_div(pool.total_lp_supply as u128)
245            .ok_or(SwapError::MathOverflow)? as u64;
246
247        require!(
248            amount_a >= min_amount_a && amount_b >= min_amount_b,
249            SwapError::SlippageExceeded
250        );
251
252        // Burn LP tokens
253        let cpi_accounts_burn = Burn {
254            mint: ctx.accounts.lp_mint.to_account_info(),
255            from: ctx.accounts.user_lp_account.to_account_info(),
256            authority: ctx.accounts.user.to_account_info(),
257        };
258        token::burn(
259            CpiContext::new(
260                ctx.accounts.token_program.to_account_info(),
261                cpi_accounts_burn,
262            ),
263            lp_amount,
264        )?;
265
266        // Transfer tokens back to user using PDA signer
267        let token_a_mint = pool.token_a_mint;
268        let token_b_mint = pool.token_b_mint;
269        let seeds = &[
270            b"pool",
271            token_a_mint.as_ref(),
272            token_b_mint.as_ref(),
273            &[pool.bump],
274        ];
275        let signer = &[&seeds[..]];
276
277        let cpi_accounts_a = Transfer {
278            from: ctx.accounts.token_a_vault.to_account_info(),
279            to: ctx.accounts.user_token_a.to_account_info(),
280            authority: pool.to_account_info(),
281        };
282        token::transfer(
283            CpiContext::new_with_signer(
284                ctx.accounts.token_program.to_account_info(),
285                cpi_accounts_a,
286                signer,
287            ),
288            amount_a,
289        )?;
290
291        let cpi_accounts_b = Transfer {
292            from: ctx.accounts.token_b_vault.to_account_info(),
293            to: ctx.accounts.user_token_b.to_account_info(),
294            authority: pool.to_account_info(),
295        };
296        token::transfer(
297            CpiContext::new_with_signer(
298                ctx.accounts.token_program.to_account_info(),
299                cpi_accounts_b,
300                signer,
301            ),
302            amount_b,
303        )?;
304
305        // Update pool state
306        pool.reserve_a = pool
307            .reserve_a
308            .checked_sub(amount_a)
309            .ok_or(SwapError::MathOverflow)?;
310        pool.reserve_b = pool
311            .reserve_b
312            .checked_sub(amount_b)
313            .ok_or(SwapError::MathOverflow)?;
314        pool.total_lp_supply = pool
315            .total_lp_supply
316            .checked_sub(lp_amount)
317            .ok_or(SwapError::MathOverflow)?;
318
319        emit!(LiquidityRemoved {
320            pool: pool.key(),
321            user: ctx.accounts.user.key(),
322            amount_a,
323            amount_b,
324            lp_tokens: lp_amount,
325        });
326
327        Ok(())
328    }
329
330    /// Swap token A for token B
331    pub fn swap_a_for_b(ctx: Context<Swap>, amount_in: u64, min_amount_out: u64) -> Result<()> {
332        require!(amount_in > 0, SwapError::InvalidAmount);
333        require!(ctx.accounts.pool.is_active, SwapError::PoolInactive);
334
335        let pool = &mut ctx.accounts.pool;
336
337        // Calculate output using constant product formula with fee
338        let amount_out =
339            calculate_swap_output(amount_in, pool.reserve_a, pool.reserve_b, pool.swap_fee_bps)?;
340
341        require!(amount_out >= min_amount_out, SwapError::SlippageExceeded);
342        require!(
343            amount_out < pool.reserve_b,
344            SwapError::InsufficientLiquidity
345        );
346
347        // Calculate and track fees
348        let fee = calculate_fee(amount_in, pool.swap_fee_bps)?;
349        let protocol_fee = calculate_fee(amount_in, pool.protocol_fee_bps)?;
350        pool.collected_fees_a = pool
351            .collected_fees_a
352            .checked_add(protocol_fee)
353            .ok_or(SwapError::MathOverflow)?;
354
355        // Transfer token A from user to pool
356        let cpi_accounts_in = Transfer {
357            from: ctx.accounts.user_token_a.to_account_info(),
358            to: ctx.accounts.token_a_vault.to_account_info(),
359            authority: ctx.accounts.user.to_account_info(),
360        };
361        token::transfer(
362            CpiContext::new(
363                ctx.accounts.token_program.to_account_info(),
364                cpi_accounts_in,
365            ),
366            amount_in,
367        )?;
368
369        // Transfer token B from pool to user using PDA signer
370        let token_a_mint = pool.token_a_mint;
371        let token_b_mint = pool.token_b_mint;
372        let seeds = &[
373            b"pool",
374            token_a_mint.as_ref(),
375            token_b_mint.as_ref(),
376            &[pool.bump],
377        ];
378        let signer = &[&seeds[..]];
379
380        let cpi_accounts_out = Transfer {
381            from: ctx.accounts.token_b_vault.to_account_info(),
382            to: ctx.accounts.user_token_b.to_account_info(),
383            authority: pool.to_account_info(),
384        };
385        token::transfer(
386            CpiContext::new_with_signer(
387                ctx.accounts.token_program.to_account_info(),
388                cpi_accounts_out,
389                signer,
390            ),
391            amount_out,
392        )?;
393
394        // Update reserves
395        pool.reserve_a = pool
396            .reserve_a
397            .checked_add(amount_in)
398            .ok_or(SwapError::MathOverflow)?;
399        pool.reserve_b = pool
400            .reserve_b
401            .checked_sub(amount_out)
402            .ok_or(SwapError::MathOverflow)?;
403
404        emit!(Swapped {
405            pool: pool.key(),
406            user: ctx.accounts.user.key(),
407            token_in: pool.token_a_mint,
408            token_out: pool.token_b_mint,
409            amount_in,
410            amount_out,
411            fee,
412        });
413
414        Ok(())
415    }
416
417    /// Swap token B for token A
418    pub fn swap_b_for_a(ctx: Context<Swap>, amount_in: u64, min_amount_out: u64) -> Result<()> {
419        require!(amount_in > 0, SwapError::InvalidAmount);
420        require!(ctx.accounts.pool.is_active, SwapError::PoolInactive);
421
422        let pool = &mut ctx.accounts.pool;
423
424        // Calculate output using constant product formula with fee
425        let amount_out =
426            calculate_swap_output(amount_in, pool.reserve_b, pool.reserve_a, pool.swap_fee_bps)?;
427
428        require!(amount_out >= min_amount_out, SwapError::SlippageExceeded);
429        require!(
430            amount_out < pool.reserve_a,
431            SwapError::InsufficientLiquidity
432        );
433
434        // Calculate and track fees
435        let fee = calculate_fee(amount_in, pool.swap_fee_bps)?;
436        let protocol_fee = calculate_fee(amount_in, pool.protocol_fee_bps)?;
437        pool.collected_fees_b = pool
438            .collected_fees_b
439            .checked_add(protocol_fee)
440            .ok_or(SwapError::MathOverflow)?;
441
442        // Transfer token B from user to pool
443        let cpi_accounts_in = Transfer {
444            from: ctx.accounts.user_token_b.to_account_info(),
445            to: ctx.accounts.token_b_vault.to_account_info(),
446            authority: ctx.accounts.user.to_account_info(),
447        };
448        token::transfer(
449            CpiContext::new(
450                ctx.accounts.token_program.to_account_info(),
451                cpi_accounts_in,
452            ),
453            amount_in,
454        )?;
455
456        // Transfer token A from pool to user using PDA signer
457        let token_a_mint = pool.token_a_mint;
458        let token_b_mint = pool.token_b_mint;
459        let seeds = &[
460            b"pool",
461            token_a_mint.as_ref(),
462            token_b_mint.as_ref(),
463            &[pool.bump],
464        ];
465        let signer = &[&seeds[..]];
466
467        let cpi_accounts_out = Transfer {
468            from: ctx.accounts.token_a_vault.to_account_info(),
469            to: ctx.accounts.user_token_a.to_account_info(),
470            authority: pool.to_account_info(),
471        };
472        token::transfer(
473            CpiContext::new_with_signer(
474                ctx.accounts.token_program.to_account_info(),
475                cpi_accounts_out,
476                signer,
477            ),
478            amount_out,
479        )?;
480
481        // Update reserves
482        pool.reserve_b = pool
483            .reserve_b
484            .checked_add(amount_in)
485            .ok_or(SwapError::MathOverflow)?;
486        pool.reserve_a = pool
487            .reserve_a
488            .checked_sub(amount_out)
489            .ok_or(SwapError::MathOverflow)?;
490
491        emit!(Swapped {
492            pool: pool.key(),
493            user: ctx.accounts.user.key(),
494            token_in: pool.token_b_mint,
495            token_out: pool.token_a_mint,
496            amount_in,
497            amount_out,
498            fee,
499        });
500
501        Ok(())
502    }
503
504    /// Get quote for swap (view function - doesn't modify state)
505    pub fn get_swap_quote(ctx: Context<GetQuote>, amount_in: u64, is_a_to_b: bool) -> Result<()> {
506        let pool = &ctx.accounts.pool;
507
508        let (reserve_in, reserve_out) = if is_a_to_b {
509            (pool.reserve_a, pool.reserve_b)
510        } else {
511            (pool.reserve_b, pool.reserve_a)
512        };
513
514        let amount_out =
515            calculate_swap_output(amount_in, reserve_in, reserve_out, pool.swap_fee_bps)?;
516        let fee = calculate_fee(amount_in, pool.swap_fee_bps)?;
517        let price_impact = calculate_price_impact(amount_in, reserve_in)?;
518
519        emit!(SwapQuote {
520            pool: pool.key(),
521            amount_in,
522            amount_out,
523            fee,
524            price_impact_bps: price_impact,
525        });
526
527        Ok(())
528    }
529
530    /// Admin: Withdraw collected protocol fees
531    pub fn withdraw_fees(ctx: Context<WithdrawFees>) -> Result<()> {
532        let pool = &mut ctx.accounts.pool;
533
534        let fees_a = pool.collected_fees_a;
535        let fees_b = pool.collected_fees_b;
536
537        require!(fees_a > 0 || fees_b > 0, SwapError::NoFeesToWithdraw);
538
539        let token_a_mint = pool.token_a_mint;
540        let token_b_mint = pool.token_b_mint;
541        let seeds = &[
542            b"pool",
543            token_a_mint.as_ref(),
544            token_b_mint.as_ref(),
545            &[pool.bump],
546        ];
547        let signer = &[&seeds[..]];
548
549        if fees_a > 0 {
550            let cpi_accounts = Transfer {
551                from: ctx.accounts.token_a_vault.to_account_info(),
552                to: ctx.accounts.fee_receiver_a.to_account_info(),
553                authority: pool.to_account_info(),
554            };
555            token::transfer(
556                CpiContext::new_with_signer(
557                    ctx.accounts.token_program.to_account_info(),
558                    cpi_accounts,
559                    signer,
560                ),
561                fees_a,
562            )?;
563            pool.collected_fees_a = 0;
564        }
565
566        if fees_b > 0 {
567            let cpi_accounts = Transfer {
568                from: ctx.accounts.token_b_vault.to_account_info(),
569                to: ctx.accounts.fee_receiver_b.to_account_info(),
570                authority: pool.to_account_info(),
571            };
572            token::transfer(
573                CpiContext::new_with_signer(
574                    ctx.accounts.token_program.to_account_info(),
575                    cpi_accounts,
576                    signer,
577                ),
578                fees_b,
579            )?;
580            pool.collected_fees_b = 0;
581        }
582
583        emit!(FeesWithdrawn {
584            pool: pool.key(),
585            amount_a: fees_a,
586            amount_b: fees_b,
587        });
588
589        Ok(())
590    }
591}
592
593// ============ Helper Functions ============
594
595/// Calculate swap output using constant product formula: x * y = k
596/// output = (reserve_out * amount_in * (10000 - fee_bps)) / (reserve_in * 10000 + amount_in * (10000 - fee_bps))
597fn calculate_swap_output(
598    amount_in: u64,
599    reserve_in: u64,
600    reserve_out: u64,
601    fee_bps: u64,
602) -> Result<u64> {
603    let amount_in_with_fee = (amount_in as u128)
604        .checked_mul((10000 - fee_bps) as u128)
605        .ok_or(SwapError::MathOverflow)?;
606
607    let numerator = amount_in_with_fee
608        .checked_mul(reserve_out as u128)
609        .ok_or(SwapError::MathOverflow)?;
610
611    let denominator = (reserve_in as u128)
612        .checked_mul(10000)
613        .ok_or(SwapError::MathOverflow)?
614        .checked_add(amount_in_with_fee)
615        .ok_or(SwapError::MathOverflow)?;
616
617    let output = numerator
618        .checked_div(denominator)
619        .ok_or(SwapError::MathOverflow)? as u64;
620
621    Ok(output)
622}
623
624fn calculate_fee(amount: u64, fee_bps: u64) -> Result<u64> {
625    Ok((amount as u128)
626        .checked_mul(fee_bps as u128)
627        .ok_or(SwapError::MathOverflow)?
628        .checked_div(10000)
629        .ok_or(SwapError::MathOverflow)? as u64)
630}
631
632fn calculate_price_impact(amount_in: u64, reserve_in: u64) -> Result<u64> {
633    // Price impact in basis points
634    Ok((amount_in as u128)
635        .checked_mul(10000)
636        .ok_or(SwapError::MathOverflow)?
637        .checked_div(reserve_in as u128)
638        .ok_or(SwapError::MathOverflow)? as u64)
639}
640
641/// Integer square root helper
642trait IntegerSqrt {
643    fn integer_sqrt(self) -> Self;
644}
645
646impl IntegerSqrt for u128 {
647    fn integer_sqrt(self) -> Self {
648        if self == 0 {
649            return 0;
650        }
651        let mut x = self;
652        let mut y = x.div_ceil(2);
653        while y < x {
654            x = y;
655            y = (x + self / x) / 2;
656        }
657        x
658    }
659}
660
661// ============ Account Structs ============
662
663#[derive(Accounts)]
664#[instruction(pool_bump: u8)]
665pub struct InitializePool<'info> {
666    #[account(mut)]
667    pub authority: Signer<'info>,
668
669    #[account(
670        init,
671        payer = authority,
672        space = 8 + LiquidityPool::INIT_SPACE,
673        seeds = [b"pool", token_a_mint.key().as_ref(), token_b_mint.key().as_ref()],
674        bump,
675    )]
676    pub pool: Account<'info, LiquidityPool>,
677
678    pub token_a_mint: Account<'info, Mint>,
679    pub token_b_mint: Account<'info, Mint>,
680
681    #[account(
682        init,
683        payer = authority,
684        token::mint = token_a_mint,
685        token::authority = pool,
686    )]
687    pub token_a_vault: Account<'info, TokenAccount>,
688
689    #[account(
690        init,
691        payer = authority,
692        token::mint = token_b_mint,
693        token::authority = pool,
694    )]
695    pub token_b_vault: Account<'info, TokenAccount>,
696
697    #[account(
698        init,
699        payer = authority,
700        mint::decimals = 6,
701        mint::authority = pool,
702    )]
703    pub lp_mint: Account<'info, Mint>,
704
705    pub token_program: Program<'info, Token>,
706    pub system_program: Program<'info, System>,
707    pub rent: Sysvar<'info, Rent>,
708}
709
710#[derive(Accounts)]
711pub struct AddLiquidity<'info> {
712    #[account(mut)]
713    pub user: Signer<'info>,
714
715    #[account(
716        mut,
717        seeds = [b"pool", pool.token_a_mint.as_ref(), pool.token_b_mint.as_ref()],
718        bump = pool.bump,
719    )]
720    pub pool: Account<'info, LiquidityPool>,
721
722    #[account(
723        mut,
724        constraint = user_token_a.owner == user.key(),
725        constraint = user_token_a.mint == pool.token_a_mint,
726    )]
727    pub user_token_a: Account<'info, TokenAccount>,
728
729    #[account(
730        mut,
731        constraint = user_token_b.owner == user.key(),
732        constraint = user_token_b.mint == pool.token_b_mint,
733    )]
734    pub user_token_b: Account<'info, TokenAccount>,
735
736    #[account(
737        init_if_needed,
738        payer = user,
739        associated_token::mint = lp_mint,
740        associated_token::authority = user,
741    )]
742    pub user_lp_account: Account<'info, TokenAccount>,
743
744    #[account(
745        mut,
746        constraint = token_a_vault.key() == pool.token_a_vault,
747    )]
748    pub token_a_vault: Account<'info, TokenAccount>,
749
750    #[account(
751        mut,
752        constraint = token_b_vault.key() == pool.token_b_vault,
753    )]
754    pub token_b_vault: Account<'info, TokenAccount>,
755
756    #[account(
757        mut,
758        constraint = lp_mint.key() == pool.lp_mint,
759    )]
760    pub lp_mint: Account<'info, Mint>,
761
762    pub token_program: Program<'info, Token>,
763    pub associated_token_program: Program<'info, anchor_spl::associated_token::AssociatedToken>,
764    pub system_program: Program<'info, System>,
765}
766
767#[derive(Accounts)]
768pub struct RemoveLiquidity<'info> {
769    #[account(mut)]
770    pub user: Signer<'info>,
771
772    #[account(
773        mut,
774        seeds = [b"pool", pool.token_a_mint.as_ref(), pool.token_b_mint.as_ref()],
775        bump = pool.bump,
776    )]
777    pub pool: Account<'info, LiquidityPool>,
778
779    #[account(
780        mut,
781        constraint = user_token_a.owner == user.key(),
782        constraint = user_token_a.mint == pool.token_a_mint,
783    )]
784    pub user_token_a: Account<'info, TokenAccount>,
785
786    #[account(
787        mut,
788        constraint = user_token_b.owner == user.key(),
789        constraint = user_token_b.mint == pool.token_b_mint,
790    )]
791    pub user_token_b: Account<'info, TokenAccount>,
792
793    #[account(
794        mut,
795        constraint = user_lp_account.owner == user.key(),
796        constraint = user_lp_account.mint == pool.lp_mint,
797    )]
798    pub user_lp_account: Account<'info, TokenAccount>,
799
800    #[account(
801        mut,
802        constraint = token_a_vault.key() == pool.token_a_vault,
803    )]
804    pub token_a_vault: Account<'info, TokenAccount>,
805
806    #[account(
807        mut,
808        constraint = token_b_vault.key() == pool.token_b_vault,
809    )]
810    pub token_b_vault: Account<'info, TokenAccount>,
811
812    #[account(
813        mut,
814        constraint = lp_mint.key() == pool.lp_mint,
815    )]
816    pub lp_mint: Account<'info, Mint>,
817
818    pub token_program: Program<'info, Token>,
819}
820
821#[derive(Accounts)]
822pub struct Swap<'info> {
823    #[account(mut)]
824    pub user: Signer<'info>,
825
826    #[account(
827        mut,
828        seeds = [b"pool", pool.token_a_mint.as_ref(), pool.token_b_mint.as_ref()],
829        bump = pool.bump,
830    )]
831    pub pool: Account<'info, LiquidityPool>,
832
833    #[account(
834        mut,
835        constraint = user_token_a.owner == user.key(),
836        constraint = user_token_a.mint == pool.token_a_mint,
837    )]
838    pub user_token_a: Account<'info, TokenAccount>,
839
840    #[account(
841        mut,
842        constraint = user_token_b.owner == user.key(),
843        constraint = user_token_b.mint == pool.token_b_mint,
844    )]
845    pub user_token_b: Account<'info, TokenAccount>,
846
847    #[account(
848        mut,
849        constraint = token_a_vault.key() == pool.token_a_vault,
850    )]
851    pub token_a_vault: Account<'info, TokenAccount>,
852
853    #[account(
854        mut,
855        constraint = token_b_vault.key() == pool.token_b_vault,
856    )]
857    pub token_b_vault: Account<'info, TokenAccount>,
858
859    pub token_program: Program<'info, Token>,
860}
861
862#[derive(Accounts)]
863pub struct GetQuote<'info> {
864    pub pool: Account<'info, LiquidityPool>,
865}
866
867#[derive(Accounts)]
868pub struct WithdrawFees<'info> {
869    #[account(
870        constraint = authority.key() == pool.authority,
871    )]
872    pub authority: Signer<'info>,
873
874    #[account(
875        mut,
876        seeds = [b"pool", pool.token_a_mint.as_ref(), pool.token_b_mint.as_ref()],
877        bump = pool.bump,
878    )]
879    pub pool: Account<'info, LiquidityPool>,
880
881    #[account(
882        mut,
883        constraint = token_a_vault.key() == pool.token_a_vault,
884    )]
885    pub token_a_vault: Account<'info, TokenAccount>,
886
887    #[account(
888        mut,
889        constraint = token_b_vault.key() == pool.token_b_vault,
890    )]
891    pub token_b_vault: Account<'info, TokenAccount>,
892
893    #[account(
894        mut,
895        constraint = fee_receiver_a.mint == pool.token_a_mint,
896    )]
897    pub fee_receiver_a: Account<'info, TokenAccount>,
898
899    #[account(
900        mut,
901        constraint = fee_receiver_b.mint == pool.token_b_mint,
902    )]
903    pub fee_receiver_b: Account<'info, TokenAccount>,
904
905    pub token_program: Program<'info, Token>,
906}
907
908// ============ State Accounts ============
909
910/// Constant product liquidity pool for token swaps.
911/// Maintains reserves of two token types and enables trading via x*y=k formula.
912#[account]
913#[derive(InitSpace)]
914pub struct LiquidityPool {
915    /// Authority/admin who can update pool configuration
916    pub authority: Pubkey,
917    /// Mint of the first token in the pair
918    pub token_a_mint: Pubkey,
919    /// Mint of the second token in the pair
920    pub token_b_mint: Pubkey,
921    /// Token account holding reserves of token A
922    pub token_a_vault: Pubkey,
923    /// Token account holding reserves of token B
924    pub token_b_vault: Pubkey,
925    /// LP token mint for this pool
926    pub lp_mint: Pubkey,
927    /// Current reserve amount of token A
928    pub reserve_a: u64,
929    /// Current reserve amount of token B
930    pub reserve_b: u64,
931    /// Total LP tokens outstanding (shares in the pool)
932    pub total_lp_supply: u64,
933    /// Swap fee in basis points (100 = 1%)
934    pub swap_fee_bps: u64,
935    /// Protocol fee in basis points
936    pub protocol_fee_bps: u64,
937    /// Accumulated token A fees awaiting withdrawal
938    pub collected_fees_a: u64,
939    /// Accumulated token B fees awaiting withdrawal
940    pub collected_fees_b: u64,
941    /// PDA bump seed for this account
942    pub bump: u8,
943    /// Whether this pool is accepting new swaps and liquidity changes
944    pub is_active: bool,
945    /// Timestamp when this pool was created
946    pub created_at: i64,
947}
948
949// ============ Errors ============
950
951#[error_code]
952pub enum SwapError {
953    #[msg("Invalid amount")]
954    InvalidAmount,
955    #[msg("Pool is not active")]
956    PoolInactive,
957    #[msg("Insufficient liquidity in pool")]
958    InsufficientLiquidity,
959    #[msg("Slippage tolerance exceeded")]
960    SlippageExceeded,
961    #[msg("No fees to withdraw")]
962    NoFeesToWithdraw,
963    #[msg("Math overflow")]
964    MathOverflow,
965}