#![allow(unexpected_cfgs)] #![allow(clippy::diverging_sub_expression)] use anchor_lang::prelude::*; use anchor_lang::system_program::{transfer, Transfer}; declare_id!("EpB6hUZUf1vvvTVAYvEN57pjUWfYswaAuKGGQDHP5iH"); #[program] pub mod cardcli_fee_vault { use super::*; pub fn initialize( ctx: Context, fee_bps: u16, fixed_fee_usd_cents: u16, ) -> Result<()> { require!(fee_bps <= 10_000, FeeVaultError::InvalidFeeBasisPoints); let vault = &mut ctx.accounts.fee_vault; vault.authority = ctx.accounts.authority.key(); vault.fee_bps = fee_bps; vault.fixed_fee_usd_cents = fixed_fee_usd_cents; vault.total_lamports_collected = 0; vault.payment_count = 0; vault.bump = ctx.bumps.fee_vault; vault.created_at = Clock::get()?.unix_timestamp; Ok(()) } pub fn update_pricing( ctx: Context, fee_bps: u16, fixed_fee_usd_cents: u16, ) -> Result<()> { require!(fee_bps <= 10_000, FeeVaultError::InvalidFeeBasisPoints); let vault = &mut ctx.accounts.fee_vault; require_keys_eq!( vault.authority, ctx.accounts.authority.key(), FeeVaultError::Unauthorized ); vault.fee_bps = fee_bps; vault.fixed_fee_usd_cents = fixed_fee_usd_cents; Ok(()) } pub fn collect_fee( ctx: Context, amount_lamports: u64, fee_reference: [u8; 16], ) -> Result<()> { require!(amount_lamports > 0, FeeVaultError::InvalidFeeAmount); let cpi_accounts = Transfer { from: ctx.accounts.payer.to_account_info(), to: ctx.accounts.fee_vault.to_account_info(), }; let cpi_ctx = CpiContext::new(ctx.accounts.system_program.to_account_info(), cpi_accounts); transfer(cpi_ctx, amount_lamports)?; let vault = &mut ctx.accounts.fee_vault; vault.total_lamports_collected = vault .total_lamports_collected .checked_add(amount_lamports) .ok_or(FeeVaultError::MathOverflow)?; vault.payment_count = vault .payment_count .checked_add(1) .ok_or(FeeVaultError::MathOverflow)?; emit!(FeeCollected { payer: ctx.accounts.payer.key(), amount_lamports, fee_reference, payment_count: vault.payment_count, }); Ok(()) } pub fn withdraw(ctx: Context, lamports: u64) -> Result<()> { let vault = &ctx.accounts.fee_vault; require_keys_eq!( vault.authority, ctx.accounts.authority.key(), FeeVaultError::Unauthorized ); require!(lamports > 0, FeeVaultError::InvalidFeeAmount); let vault_info = ctx.accounts.fee_vault.to_account_info(); let recipient_info = ctx.accounts.recipient.to_account_info(); let vault_balance = vault_info.lamports(); require!( vault_balance >= lamports, FeeVaultError::InsufficientVaultBalance ); **vault_info.try_borrow_mut_lamports()? -= lamports; **recipient_info.try_borrow_mut_lamports()? += lamports; Ok(()) } } #[derive(Accounts)] pub struct Initialize<'info> { #[account( init, payer = authority, space = 8 + FeeVault::LEN, seeds = [b"fee_vault"], bump, )] pub fee_vault: Account<'info, FeeVault>, #[account(mut)] pub authority: Signer<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct UpdatePricing<'info> { #[account(mut, seeds = [b"fee_vault"], bump = fee_vault.bump)] pub fee_vault: Account<'info, FeeVault>, pub authority: Signer<'info>, } #[derive(Accounts)] pub struct CollectFee<'info> { #[account(mut, seeds = [b"fee_vault"], bump = fee_vault.bump)] pub fee_vault: Account<'info, FeeVault>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct Withdraw<'info> { #[account(mut, seeds = [b"fee_vault"], bump = fee_vault.bump)] pub fee_vault: Account<'info, FeeVault>, pub authority: Signer<'info>, #[account(mut)] pub recipient: SystemAccount<'info>, } #[account] pub struct FeeVault { pub authority: Pubkey, pub fee_bps: u16, pub fixed_fee_usd_cents: u16, pub total_lamports_collected: u64, pub payment_count: u64, pub bump: u8, pub created_at: i64, } impl FeeVault { pub const LEN: usize = 32 + 2 + 2 + 8 + 8 + 1 + 8; } #[event] pub struct FeeCollected { pub payer: Pubkey, pub amount_lamports: u64, pub fee_reference: [u8; 16], pub payment_count: u64, } #[error_code] pub enum FeeVaultError { #[msg("Fee basis points must be between 0 and 10_000")] InvalidFeeBasisPoints, #[msg("Fee amount must be greater than zero")] InvalidFeeAmount, #[msg("Math overflow")] MathOverflow, #[msg("Unauthorized authority")] Unauthorized, #[msg("Vault balance is insufficient")] InsufficientVaultBalance, } #[cfg(test)] mod tests { use super::*; #[test] fn fee_vault_length_stays_stable() { assert_eq!(FeeVault::LEN, 61); } }