Skip to content

Instantly share code, notes, and snippets.

@francis-codex
Last active July 25, 2025 22:42
Show Gist options
  • Save francis-codex/5f756d7ca211c99f9b591599ce73c989 to your computer and use it in GitHub Desktop.
Save francis-codex/5f756d7ca211c99f9b591599ce73c989 to your computer and use it in GitHub Desktop.
Solana Automated Reward Distribution System - Complete Implementation Guide

Solana Automated Reward Distribution System - Complete Implementation Guide

πŸ—οΈ System Architecture Overview

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Frontend DApp β”‚    β”‚  Backend API    β”‚    β”‚ Solana Program  β”‚
β”‚                 β”‚    β”‚                 β”‚    β”‚  (Smart Contract)β”‚
β”‚ β€’ Withdraw UI   │◄──►│ β€’ Reward Calc   │◄──►│ β€’ Reward Storageβ”‚
β”‚ β€’ Batch Withdrawβ”‚    β”‚ β€’ Event Listen  β”‚    β”‚ β€’ Fee Collectionβ”‚
β”‚ β€’ Balance Check β”‚    β”‚ β€’ State Sync    β”‚    β”‚ β€’ Multi-token   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                       β”‚                       β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   Solana RPC    β”‚
                    β”‚ β€’ Transaction   β”‚
                    β”‚ β€’ Account Data  β”‚
                    β”‚ β€’ Event Logs    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Š Account Architecture & Data Structures

Core Program Accounts

1. Platform Authority Account

#[account]
pub struct PlatformAuthority {
    pub authority: Pubkey,           // Platform admin authority
    pub treasury: Pubkey,            // Platform treasury wallet
    pub platform_fee_rate: u16,     // Fee rate in basis points (1000 = 10%)
    pub is_paused: bool,             // Emergency pause flag
    pub total_fees_collected: u64,  // Total platform fees collected
    pub supported_tokens: Vec<Pubkey>, // Supported SPL token mints
    pub bump: u8,                    // PDA bump seed
}

2. Factory Account

#[account]
pub struct Factory {
    pub id: u64,                     // Unique factory ID
    pub creator: Pubkey,             // Factory creator authority
    pub reward_token: Pubkey,        // Token mint for rewards ($CLONES, $USDC, etc.)
    pub total_rewards_deposited: u64, // Total rewards deposited by creator
    pub total_rewards_withdrawn: u64, // Total rewards withdrawn by farmers
    pub is_active: bool,             // Factory status
    pub created_at: i64,             // Creation timestamp
    pub bump: u8,                    // PDA bump seed
}

3. Farmer Reward Account

#[account]
pub struct FarmerReward {
    pub farmer: Pubkey,              // Farmer's wallet address
    pub factory_id: u64,             // Associated factory ID
    pub total_earned: u64,           // Total rewards earned
    pub total_withdrawn: u64,        // Total rewards withdrawn
    pub last_updated: i64,           // Last reward calculation timestamp
    pub pending_rewards: u64,        // Current withdrawable amount
    pub bump: u8,                    // PDA bump seed
}

4. Reward Vault Account

#[account]
pub struct RewardVault {
    pub factory_id: u64,             // Associated factory ID
    pub token_mint: Pubkey,          // Token mint address
    pub vault_authority: Pubkey,     // PDA authority for vault
    pub total_deposited: u64,        // Total tokens deposited
    pub total_withdrawn: u64,        // Total tokens withdrawn
    pub bump: u8,                    // PDA bump seed
}

πŸ”§ Smart Contract Implementation

Core Program Instructions

1. Initialize Platform

#[derive(Accounts)]
pub struct InitializePlatform<'info> {
    #[account(
        init,
        payer = authority,
        space = 8 + std::mem::size_of::<PlatformAuthority>(),
        seeds = [b"platform"],
        bump
    )]
    pub platform_authority: Account<'info, PlatformAuthority>,
    
    #[account(mut)]
    pub authority: Signer<'info>,
    
    /// CHECK: Treasury wallet address
    pub treasury: UncheckedAccount<'info>,
    
    pub system_program: Program<'info, System>,
}

pub fn initialize_platform(
    ctx: Context<InitializePlatform>,
    platform_fee_rate: u16,
) -> Result<()> {
    require!(platform_fee_rate <= 2000, ErrorCode::FeeTooHigh); // Max 20%
    
    let platform_authority = &mut ctx.accounts.platform_authority;
    platform_authority.authority = ctx.accounts.authority.key();
    platform_authority.treasury = ctx.accounts.treasury.key();
    platform_authority.platform_fee_rate = platform_fee_rate;
    platform_authority.is_paused = false;
    platform_authority.total_fees_collected = 0;
    platform_authority.bump = *ctx.bumps.get("platform_authority").unwrap();
    
    Ok(())
}

2. Create Factory

#[derive(Accounts)]
#[instruction(factory_id: u64)]
pub struct CreateFactory<'info> {
    #[account(
        init,
        payer = creator,
        space = 8 + std::mem::size_of::<Factory>(),
        seeds = [b"factory", factory_id.to_le_bytes().as_ref()],
        bump
    )]
    pub factory: Account<'info, Factory>,
    
    #[account(
        init,
        payer = creator,
        space = 8 + std::mem::size_of::<RewardVault>(),
        seeds = [b"vault", factory_id.to_le_bytes().as_ref(), reward_token.key().as_ref()],
        bump
    )]
    pub reward_vault: Account<'info, RewardVault>,
    
    #[account(
        init,
        payer = creator,
        token::mint = reward_token,
        token::authority = vault_authority,
    )]
    pub vault_token_account: Account<'info, TokenAccount>,
    
    /// CHECK: PDA authority for vault
    #[account(
        seeds = [b"vault_authority", factory_id.to_le_bytes().as_ref()],
        bump
    )]
    pub vault_authority: UncheckedAccount<'info>,
    
    #[account(mut)]
    pub creator: Signer<'info>,
    
    pub reward_token: Account<'info, Mint>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
}

pub fn create_factory(
    ctx: Context<CreateFactory>,
    factory_id: u64,
) -> Result<()> {
    let factory = &mut ctx.accounts.factory;
    factory.id = factory_id;
    factory.creator = ctx.accounts.creator.key();
    factory.reward_token = ctx.accounts.reward_token.key();
    factory.total_rewards_deposited = 0;
    factory.total_rewards_withdrawn = 0;
    factory.is_active = true;
    factory.created_at = Clock::get()?.unix_timestamp;
    factory.bump = *ctx.bumps.get("factory").unwrap();
    
    let vault = &mut ctx.accounts.reward_vault;
    vault.factory_id = factory_id;
    vault.token_mint = ctx.accounts.reward_token.key();
    vault.vault_authority = ctx.accounts.vault_authority.key();
    vault.total_deposited = 0;
    vault.total_withdrawn = 0;
    vault.bump = *ctx.bumps.get("reward_vault").unwrap();
    
    Ok(())
}

3. Update Farmer Rewards (Backend Only)

#[derive(Accounts)]
#[instruction(factory_id: u64, farmer: Pubkey)]
pub struct UpdateFarmerRewards<'info> {
    #[account(
        init_if_needed,
        payer = authority,
        space = 8 + std::mem::size_of::<FarmerReward>(),
        seeds = [b"farmer_reward", factory_id.to_le_bytes().as_ref(), farmer.as_ref()],
        bump
    )]
    pub farmer_reward: Account<'info, FarmerReward>,
    
    #[account(
        seeds = [b"factory", factory_id.to_le_bytes().as_ref()],
        bump = factory.bump,
        constraint = factory.creator == authority.key()
    )]
    pub factory: Account<'info, Factory>,
    
    #[account(mut)]
    pub authority: Signer<'info>,
    
    pub system_program: Program<'info, System>,
}

pub fn update_farmer_rewards(
    ctx: Context<UpdateFarmerRewards>,
    factory_id: u64,
    farmer: Pubkey,
    new_pending_rewards: u64,
) -> Result<()> {
    let farmer_reward = &mut ctx.accounts.farmer_reward;
    
    if farmer_reward.farmer == Pubkey::default() {
        // Initialize new farmer reward account
        farmer_reward.farmer = farmer;
        farmer_reward.factory_id = factory_id;
        farmer_reward.total_earned = 0;
        farmer_reward.total_withdrawn = 0;
        farmer_reward.bump = *ctx.bumps.get("farmer_reward").unwrap();
    }
    
    farmer_reward.pending_rewards = new_pending_rewards;
    farmer_reward.total_earned = farmer_reward.total_withdrawn + new_pending_rewards;
    farmer_reward.last_updated = Clock::get()?.unix_timestamp;
    
    Ok(())
}

4. Withdraw Rewards (Farmer Initiated)

#[derive(Accounts)]
#[instruction(factory_id: u64)]
pub struct WithdrawRewards<'info> {
    #[account(
        mut,
        seeds = [b"farmer_reward", factory_id.to_le_bytes().as_ref(), farmer.key().as_ref()],
        bump = farmer_reward.bump,
    )]
    pub farmer_reward: Account<'info, FarmerReward>,
    
    #[account(
        mut,
        seeds = [b"factory", factory_id.to_le_bytes().as_ref()],
        bump = factory.bump,
    )]
    pub factory: Account<'info, Factory>,
    
    #[account(
        mut,
        seeds = [b"vault", factory_id.to_le_bytes().as_ref(), vault.token_mint.as_ref()],
        bump = vault.bump,
    )]
    pub vault: Account<'info, RewardVault>,
    
    #[account(
        mut,
        token::mint = vault.token_mint,
        token::authority = vault_authority,
    )]
    pub vault_token_account: Account<'info, TokenAccount>,
    
    #[account(
        mut,
        token::mint = vault.token_mint,
        token::authority = farmer,
    )]
    pub farmer_token_account: Account<'info, TokenAccount>,
    
    #[account(
        mut,
        token::mint = vault.token_mint,
    )]
    pub treasury_token_account: Account<'info, TokenAccount>,
    
    #[account(
        seeds = [b"vault_authority", factory_id.to_le_bytes().as_ref()],
        bump
    )]
    pub vault_authority: UncheckedAccount<'info>,
    
    #[account(
        seeds = [b"platform"],
        bump = platform_authority.bump,
    )]
    pub platform_authority: Account<'info, PlatformAuthority>,
    
    #[account(mut)]
    pub farmer: Signer<'info>,
    
    pub token_program: Program<'info, Token>,
}

pub fn withdraw_rewards(
    ctx: Context<WithdrawRewards>,
    factory_id: u64,
) -> Result<()> {
    let platform_authority = &ctx.accounts.platform_authority;
    require!(!platform_authority.is_paused, ErrorCode::SystemPaused);
    
    let farmer_reward = &mut ctx.accounts.farmer_reward;
    let pending_amount = farmer_reward.pending_rewards;
    require!(pending_amount > 0, ErrorCode::NoRewardsAvailable);
    
    // Calculate platform fee (10% = 1000 basis points)
    let platform_fee = (pending_amount as u128)
        .checked_mul(platform_authority.platform_fee_rate as u128)
        .unwrap()
        .checked_div(10_000)
        .unwrap() as u64;
    
    let farmer_amount = pending_amount.checked_sub(platform_fee).unwrap();
    
    // Transfer to farmer
    let vault_authority_seeds = &[
        b"vault_authority",
        factory_id.to_le_bytes().as_ref(),
        &[*ctx.bumps.get("vault_authority").unwrap()],
    ];
    let signer = &[&vault_authority_seeds[..]];
    
    token::transfer(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            token::Transfer {
                from: ctx.accounts.vault_token_account.to_account_info(),
                to: ctx.accounts.farmer_token_account.to_account_info(),
                authority: ctx.accounts.vault_authority.to_account_info(),
            },
            signer,
        ),
        farmer_amount,
    )?;
    
    // Transfer platform fee
    if platform_fee > 0 {
        token::transfer(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                token::Transfer {
                    from: ctx.accounts.vault_token_account.to_account_info(),
                    to: ctx.accounts.treasury_token_account.to_account_info(),
                    authority: ctx.accounts.vault_authority.to_account_info(),
                },
                signer,
            ),
            platform_fee,
        )?;
    }
    
    // Update farmer reward state
    farmer_reward.total_withdrawn += pending_amount;
    farmer_reward.pending_rewards = 0;
    
    // Update factory state
    let factory = &mut ctx.accounts.factory;
    factory.total_rewards_withdrawn += pending_amount;
    
    // Update vault state
    let vault = &mut ctx.accounts.vault;
    vault.total_withdrawn += pending_amount;
    
    emit!(RewardWithdrawnEvent {
        farmer: ctx.accounts.farmer.key(),
        factory_id,
        amount: farmer_amount,
        platform_fee,
        timestamp: Clock::get()?.unix_timestamp,
    });
    
    Ok(())
}

5. Batch Withdraw

pub fn batch_withdraw_rewards(
    ctx: Context<BatchWithdrawRewards>,
    factory_ids: Vec<u64>,
) -> Result<()> {
    // Implementation for batch withdrawals across multiple factories
    // This would iterate through factory_ids and perform withdrawals
    // Requires remaining accounts pattern for dynamic account handling
    
    for factory_id in factory_ids {
        // Perform individual withdrawal logic
        // Update cumulative amounts
    }
    
    Ok(())
}

πŸ” Security Features

Access Control

#[error_code]
pub enum ErrorCode {
    #[msg("Unauthorized access")]
    Unauthorized,
    #[msg("System is paused")]
    SystemPaused,
    #[msg("No rewards available")]
    NoRewardsAvailable,
    #[msg("Fee rate too high")]
    FeeTooHigh,
    #[msg("Insufficient vault balance")]
    InsufficientBalance,
    #[msg("Invalid factory ID")]
    InvalidFactory,
}

// Emergency pause functionality
pub fn pause_system(ctx: Context<PauseSystem>) -> Result<()> {
    let platform_authority = &mut ctx.accounts.platform_authority;
    require!(
        platform_authority.authority == ctx.accounts.authority.key(),
        ErrorCode::Unauthorized
    );
    platform_authority.is_paused = true;
    Ok(())
}

Event Logging

#[event]
pub struct RewardWithdrawnEvent {
    pub farmer: Pubkey,
    pub factory_id: u64,
    pub amount: u64,
    pub platform_fee: u64,
    pub timestamp: i64,
}

#[event]
pub struct RewardsDepositedEvent {
    pub factory_id: u64,
    pub amount: u64,
    pub depositor: Pubkey,
    pub timestamp: i64,
}

πŸ—„οΈ Database Schema Updates

New Tables

-- Track on-chain transactions
CREATE TABLE reward_transactions (
    id BIGSERIAL PRIMARY KEY,
    farmer_address VARCHAR(44) NOT NULL,
    factory_id BIGINT NOT NULL,
    transaction_hash VARCHAR(88) NOT NULL,
    amount BIGINT NOT NULL,
    platform_fee BIGINT NOT NULL,
    token_mint VARCHAR(44) NOT NULL,
    block_time TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Track pending rewards to be written to chain
CREATE TABLE pending_rewards (
    id BIGSERIAL PRIMARY KEY,
    farmer_address VARCHAR(44) NOT NULL,
    factory_id BIGINT NOT NULL,
    pending_amount BIGINT NOT NULL,
    last_updated TIMESTAMP DEFAULT NOW(),
    synced_to_chain BOOLEAN DEFAULT FALSE,
    UNIQUE(farmer_address, factory_id)
);

-- Factory contract mapping
CREATE TABLE factory_contracts (
    factory_id BIGINT PRIMARY KEY,
    contract_address VARCHAR(44) NOT NULL,
    reward_token_mint VARCHAR(44) NOT NULL,
    vault_address VARCHAR(44) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

πŸš€ Backend Integration

Reward Calculation Service

interface RewardCalculationService {
  calculatePendingRewards(farmerId: string, factoryId: number): Promise<number>;
  updateOnChainRewards(farmerId: string, factoryId: number, amount: number): Promise<string>;
  syncChainState(): Promise<void>;
}

class SolanaRewardService implements RewardCalculationService {
  private connection: Connection;
  private program: Program;
  
  async updateOnChainRewards(
    farmerId: string, 
    factoryId: number, 
    amount: number
  ): Promise<string> {
    const tx = await this.program.methods
      .updateFarmerRewards(
        new BN(factoryId),
        new PublicKey(farmerId),
        new BN(amount)
      )
      .accounts({
        farmerReward: this.getFarmerRewardPDA(factoryId, farmerId),
        factory: this.getFactoryPDA(factoryId),
        authority: this.authorityKeypair.publicKey,
      })
      .signers([this.authorityKeypair])
      .rpc();
      
    return tx;
  }
}

Event Listener Service

class ChainEventListener {
  async startListening() {
    this.program.addEventListener('RewardWithdrawnEvent', (event) => {
      this.handleWithdrawalEvent(event);
    });
    
    this.program.addEventListener('RewardsDepositedEvent', (event) => {
      this.handleDepositEvent(event);
    });
  }
  
  private async handleWithdrawalEvent(event: RewardWithdrawnEvent) {
    // Update database with withdrawal
    await this.db.query(`
      INSERT INTO reward_transactions 
      (farmer_address, factory_id, amount, platform_fee, transaction_hash)
      VALUES ($1, $2, $3, $4, $5)
    `, [
      event.farmer.toString(),
      event.factoryId.toNumber(),
      event.amount.toNumber(),
      event.platformFee.toNumber(),
      event.transactionHash
    ]);
  }
}

🎨 Frontend Integration

Withdrawal Component

const WithdrawRewards: React.FC = () => {
  const wallet = useWallet();
  const { connection } = useConnection();
  const program = useProgram();
  
  const [pendingRewards, setPendingRewards] = useState<Record<number, number>>({});
  const [isWithdrawing, setIsWithdrawing] = useState(false);
  
  const withdrawFromFactory = async (factoryId: number) => {
    if (!wallet.publicKey) return;
    
    setIsWithdrawing(true);
    try {
      const tx = await program.methods
        .withdrawRewards(new BN(factoryId))
        .accounts({
          farmerReward: getFarmerRewardPDA(factoryId, wallet.publicKey),
          factory: getFactoryPDA(factoryId),
          farmer: wallet.publicKey,
          // ... other accounts
        })
        .rpc();
        
      await connection.confirmTransaction(tx);
      toast.success('Rewards withdrawn successfully!');
      
      // Refresh balances
      await loadPendingRewards();
    } catch (error) {
      toast.error('Failed to withdraw rewards');
    } finally {
      setIsWithdrawing(false);
    }
  };
  
  const batchWithdraw = async (factoryIds: number[]) => {
    // Implementation for batch withdrawals
  };
  
  return (
    <div className="reward-withdrawal">
      {Object.entries(pendingRewards).map(([factoryId, amount]) => (
        <div key={factoryId} className="factory-reward">
          <span>Factory #{factoryId}: {amount} tokens</span>
          <button 
            onClick={() => withdrawFromFactory(Number(factoryId))}
            disabled={isWithdrawing || amount === 0}
          >
            Withdraw
          </button>
        </div>
      ))}
      
      <button 
        onClick={() => batchWithdraw(Object.keys(pendingRewards).map(Number))}
        disabled={isWithdrawing}
      >
        Withdraw All
      </button>
    </div>
  );
};

πŸ“‹ Implementation Phases

Phase 1: Core Contract Development

  • Implement basic contract structure
  • Core withdrawal functionality
  • Platform fee collection
  • Unit tests

Phase 2: Multi-Token & Batch Support

  • Add support for multiple SPL tokens
  • Implement batch withdrawal functionality
  • Integration tests

Phase 3: Backend Integration

  • Update reward calculation service
  • Implement event listeners
  • Database schema migration
  • API endpoint updates

Phase 4: Frontend Integration

  • Update withdrawal UI
  • Add batch withdrawal interface
  • Real-time balance updates
  • Transaction history

Phase 5: Security & Audit

  • Security audit
  • Penetration testing
  • Gas optimization
  • Mainnet deployment preparation

πŸ§ͺ Testing Strategy

Smart Contract Tests

#[cfg(test)]
mod tests {
    use super::*;
    
    #[tokio::test]
    async fn test_withdraw_rewards_success() {
        // Test successful withdrawal with correct fee calculation
    }
    
    #[tokio::test]
    async fn test_withdraw_no_rewards() {
        // Test withdrawal when no rewards available
    }
    
    #[tokio::test]
    async fn test_batch_withdrawal() {
        // Test batch withdrawal across multiple factories
    }
    
    #[tokio::test]
    async fn test_platform_fee_calculation() {
        // Test accurate platform fee calculation
    }
}

Integration Tests

  • Devnet deployment testing
  • End-to-end reward flow testing
  • Gas cost optimization testing
  • Error handling validation

πŸ” Monitoring & Analytics

Key Metrics to Track

  • Total rewards distributed
  • Platform fees collected
  • Average withdrawal amounts
  • Gas costs per transaction
  • Failed transaction rates
  • User adoption metrics

Alerting System

  • Smart contract errors
  • Unusual withdrawal patterns
  • Low vault balances
  • High gas costs
  • System pause events

This comprehensive implementation provides a secure, scalable, and transparent reward distribution system that addresses all the current limitations while introducing automated platform fee collection and improved user experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment