dloom-flow 1.0.0

A Solana flow payment program created with Anchor
Documentation
// FILE: programs/dloom_flow/src/amm/instructions/claim_lp_fees.rs

use crate::{
    constants::PRECISION,
    errors::DloomError,
    amm::{state::{AmmPool, AmmPosition}}, 
    events::AmmFeesClaimed,
};
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};

pub fn handle_claim_lp_fees(ctx: Context<ClaimLpFees>) -> Result<()> {
    let amm_pool = &ctx.accounts.amm_pool;
    let position = &mut ctx.accounts.amm_position;

    // Calculate pending fees
    let fee_growth_a = amm_pool.fee_growth_per_lp_token_a
        .checked_sub(position.fee_growth_snapshot_a)
        .unwrap_or(0);
    let fee_growth_b = amm_pool.fee_growth_per_lp_token_b
        .checked_sub(position.fee_growth_snapshot_b)
        .unwrap_or(0);

    let fees_to_claim_a = fee_growth_a
        .checked_mul(position.lp_token_amount as u128)
        .unwrap_or(0)
        .checked_div(PRECISION)
        .unwrap_or(0) as u64;

    let fees_to_claim_b = fee_growth_b
        .checked_mul(position.lp_token_amount as u128)
        .unwrap_or(0)
        .checked_div(PRECISION)
        .unwrap_or(0) as u64;

    // Update snapshots
    position.fee_growth_snapshot_a = amm_pool.fee_growth_per_lp_token_a;
    position.fee_growth_snapshot_b = amm_pool.fee_growth_per_lp_token_b;

    // Prepare signer seeds
    let bump = &[ctx.accounts.amm_pool.bump][..];
    let signer_seeds = &[
        b"amm_pool",
        ctx.accounts.amm_pool.token_a_mint.as_ref(),
        ctx.accounts.amm_pool.token_b_mint.as_ref(),
        bump,
    ][..];

    // Transfer fees
    if fees_to_claim_a > 0 {
        token_interface::transfer_checked(
            CpiContext::new_with_signer(
                ctx.accounts.token_a_program.to_account_info(),
                TransferChecked {
                    from: ctx.accounts.token_a_vault.to_account_info(),
                    to: ctx.accounts.user_token_a_account.to_account_info(),
                    authority: ctx.accounts.amm_pool.to_account_info(),
                    mint: ctx.accounts.token_a_mint.to_account_info(),
                },
                &[signer_seeds],
            ),
            fees_to_claim_a,
            ctx.accounts.token_a_mint.decimals,
        )?;
    }

    if fees_to_claim_b > 0 {
        token_interface::transfer_checked(
            CpiContext::new_with_signer(
                ctx.accounts.token_b_program.to_account_info(),
                TransferChecked {
                    from: ctx.accounts.token_b_vault.to_account_info(),
                    to: ctx.accounts.user_token_b_account.to_account_info(),
                    authority: ctx.accounts.amm_pool.to_account_info(),
                    mint: ctx.accounts.token_b_mint.to_account_info(),
                },
                &[signer_seeds],
            ),
            fees_to_claim_b,
            ctx.accounts.token_b_mint.decimals,
        )?;
    }
    
    let amm_pool_mut = &mut ctx.accounts.amm_pool;
    if fees_to_claim_a > 0 {
        amm_pool_mut.reserves_a = amm_pool_mut.reserves_a.checked_sub(fees_to_claim_a).ok_or(DloomError::MathOverflow)?;
    }
    if fees_to_claim_b > 0 {
        amm_pool_mut.reserves_b = amm_pool_mut.reserves_b.checked_sub(fees_to_claim_b).ok_or(DloomError::MathOverflow)?;
    }

    emit!(AmmFeesClaimed {
        pool_address: ctx.accounts.amm_pool.key(),
        user: ctx.accounts.owner.key(),
        fees_claimed_a: fees_to_claim_a,
        fees_claimed_b: fees_to_claim_b,
    });

    Ok(())
}

#[derive(Accounts)]
pub struct ClaimLpFees<'info> {
    #[account(mut)]
    pub owner: Signer<'info>,

    #[account(
        mut, // The pool needs to be mutable to update reserves when fees are taken out
        seeds = [b"amm_pool", amm_pool.token_a_mint.as_ref(), amm_pool.token_b_mint.as_ref()],
        bump = amm_pool.bump,
    )]
    pub amm_pool: Box<Account<'info, AmmPool>>,

    #[account(
        mut,
        has_one = owner,
        constraint = amm_position.pool == amm_pool.key() @ DloomError::InvalidPool,
    )]
    pub amm_position: Box<Account<'info, AmmPosition>>,

    #[account(address = amm_pool.lp_mint)]
    pub lp_mint: InterfaceAccount<'info, Mint>,
    #[account(address = amm_pool.token_a_mint)]
    pub token_a_mint: InterfaceAccount<'info, Mint>,
    #[account(address = amm_pool.token_b_mint)]
    pub token_b_mint: InterfaceAccount<'info, Mint>,

    #[account(mut, address = amm_pool.token_a_vault)]
    pub token_a_vault: InterfaceAccount<'info, TokenAccount>,
    #[account(mut, address = amm_pool.token_b_vault)]
    pub token_b_vault: InterfaceAccount<'info, TokenAccount>,

    #[account(mut, has_one = owner)]
    pub user_token_a_account: InterfaceAccount<'info, TokenAccount>,
    #[account(mut, has_one = owner)]
    pub user_token_b_account: InterfaceAccount<'info, TokenAccount>,

    pub token_a_program: Interface<'info, TokenInterface>,
    pub token_b_program: Interface<'info, TokenInterface>,
}