CardCLI Bot
Deploy CardCLI Hugging Face Space
d2948d0
#![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<Initialize>,
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<UpdatePricing>,
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<CollectFee>,
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<Withdraw>, 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);
}
}