1use anchor_lang::prelude::*;
47use anchor_spl::token::{self, Burn, Mint, MintTo, Token, TokenAccount, Transfer};
48
49pub mod events;
51pub mod services;
52
53use events::{
55 FeesWithdrawn, LiquidityAdded, LiquidityRemoved, PoolInitialized, SwapQuote, Swapped,
56};
57
58declare_id!("GPGGnKwnvKseSxzPukrNvch1CwYhifTqgj2RdW1P26H3");
59
60const SWAP_FEE_BPS: u64 = 30; const PROTOCOL_FEE_BPS: u64 = 5; #[allow(dead_code)]
70const LP_FEE_BPS: u64 = 25; #[program]
73pub mod tribewarez_swap {
74 use super::*;
75
76 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
593fn 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 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
641trait 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#[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#[account]
913#[derive(InitSpace)]
914pub struct LiquidityPool {
915 pub authority: Pubkey,
917 pub token_a_mint: Pubkey,
919 pub token_b_mint: Pubkey,
921 pub token_a_vault: Pubkey,
923 pub token_b_vault: Pubkey,
925 pub lp_mint: Pubkey,
927 pub reserve_a: u64,
929 pub reserve_b: u64,
931 pub total_lp_supply: u64,
933 pub swap_fee_bps: u64,
935 pub protocol_fee_bps: u64,
937 pub collected_fees_a: u64,
939 pub collected_fees_b: u64,
941 pub bump: u8,
943 pub is_active: bool,
945 pub created_at: i64,
947}
948
949#[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}