payment-program 0.1.7

Schedule one-time and recurring token transfers on Solana
Documentation
use {
    super::utils::PERCENTAGE_BASE,
    crate::{errors::*, state::*},
    anchor_lang::{prelude::*, solana_program::system_program},
    anchor_spl::token::{transfer, Token, TokenAccount, Transfer},
};

#[derive(Accounts)]
pub struct ProcessTask<'info> {
    #[account(
        mut, 
        seeds = [SEED_AUTHORITY], 
        bump = authority.bump,
        owner = crate::ID,
    )]
    pub authority: Box<Account<'info, Authority>>,

    pub clock: Sysvar<'info, Clock>,

    #[account(
        seeds = [SEED_CONFIG], 
        bump = config.bump,
        owner = crate::ID,
    )]
    pub config: Account<'info, Config>,

    #[account(
        mut,
        constraint = creditor_tokens.owner == payment.creditor,
        constraint = creditor_tokens.mint == payment.mint,
        owner = token_program.key(),
    )]
    pub creditor_tokens: Account<'info, TokenAccount>,

    #[account(
        mut,
        constraint = debtor_tokens.owner == payment.debtor,
        constraint = debtor_tokens.mint == payment.mint,
        owner = token_program.key(),
    )]
    pub debtor_tokens: Account<'info, TokenAccount>,

    #[account(
        mut,
        seeds = [
            SEED_PAYMENT,
            payment.debtor.key().as_ref(),
            payment.id.to_be_bytes().as_ref(),
        ],
        bump = payment.bump,
        has_one = debtor_tokens,
        has_one = creditor_tokens,
        owner = crate::ID,
    )]
    pub payment: Account<'info, Payment>,

    #[account(mut)]
    pub signer: Signer<'info>,

    #[account(address = system_program::ID)]
    pub system_program: Program<'info, System>,

    #[account(
        mut,
        seeds = [
            SEED_TASK,
            payment.key().as_ref(),
            task.process_at.to_string().as_bytes()
        ],
        bump = task.bump,
        owner = crate::ID,
        has_one = payment,
        constraint = task.status == TaskStatus::Pending @ ErrorCode::TaskNotPending,
        constraint = task.process_at <= clock.unix_timestamp as u64 @ ErrorCode::TaskNotDue
    )]
    pub task: Account<'info, Task>,

    #[account(
        mut, 
        seeds = [SEED_TREASURY], 
        bump = treasury.bump,
        owner = crate::ID,
    )]
    pub treasury: Account<'info, Treasury>,

    #[account(address = anchor_spl::token::ID)]
    pub token_program: Program<'info, Token>,
}

pub fn handler(ctx: Context<ProcessTask>) -> ProgramResult {
    // Get accounts.
    let authority = &ctx.accounts.authority;
    let config = &ctx.accounts.config;
    let creditor_tokens = &ctx.accounts.creditor_tokens;
    let debtor_tokens = &ctx.accounts.debtor_tokens;
    let payment = &mut ctx.accounts.payment;
    let signer = &ctx.accounts.signer;
    let task = &mut ctx.accounts.task;
    let treasury = &ctx.accounts.treasury;
    let token_program = &ctx.accounts.token_program;

    // Calculate the transfer amount.
    let amount = 
        if payment.amount_percent > 0 { ((payment.amount_percent as f64) / (PERCENTAGE_BASE as f64) * (debtor_tokens.amount as f64)) as u64 } 
        else { payment.amount_raw };

    // Validate the transfer status.
    if !debtor_tokens.delegate.is_some() || debtor_tokens.delegate.unwrap() != authority.key() {
        task.transfer_status = TransferStatus::FailedNotAuthorized;
    } else if debtor_tokens.delegated_amount < amount {
        task.transfer_status = TransferStatus::FailedInsufficientDelegateBalance;
    } else if debtor_tokens.amount < amount {
        task.transfer_status = TransferStatus::FailedInsufficientBalance;
    } else {
        task.transfer_status = TransferStatus::Succeeded;
    }

    // Update the task status.
    let next_transfer_at = task.process_at + payment.recurrence_interval;
    if payment.recurrence_interval > 0 && next_transfer_at < payment.end_at {
        task.status = TaskStatus::Repeat; 
    } else {
        task.status = TaskStatus::Done; 
    }

    // Transfer tokens from debtor to creditor.
    if task.transfer_status == TransferStatus::Succeeded {
        transfer(
            CpiContext::new_with_signer(
                token_program.to_account_info(),
                Transfer {
                    authority: authority.to_account_info(),
                    from: debtor_tokens.to_account_info(),
                    to: creditor_tokens.to_account_info(),
                },
                &[&[SEED_AUTHORITY, &[authority.bump]]],
            ),
            amount,
        )?;
    }

    // Pay transfer fee to distributor.
    **payment.to_account_info().try_borrow_mut_lamports()? -= config.transfer_fee_distributor;
    **signer.to_account_info().try_borrow_mut_lamports()? += config.transfer_fee_distributor;

    // Pay transfer fee to treasury.
    **payment.to_account_info().try_borrow_mut_lamports()? -= config.transfer_fee_program;
    **treasury.to_account_info().try_borrow_mut_lamports()? += config.transfer_fee_program;

    return Ok(());
}