βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β 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]
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
}
#[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
}
#[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
}
#[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
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
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(())
}
#[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]
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,
}
-- 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()
);
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;
}
}
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
]);
}
}
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>
);
};
- Implement basic contract structure
- Core withdrawal functionality
- Platform fee collection
- Unit tests
- Add support for multiple SPL tokens
- Implement batch withdrawal functionality
- Integration tests
- Update reward calculation service
- Implement event listeners
- Database schema migration
- API endpoint updates
- Update withdrawal UI
- Add batch withdrawal interface
- Real-time balance updates
- Transaction history
- Security audit
- Penetration testing
- Gas optimization
- Mainnet deployment preparation
#[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
}
}
- Devnet deployment testing
- End-to-end reward flow testing
- Gas cost optimization testing
- Error handling validation
- Total rewards distributed
- Platform fees collected
- Average withdrawal amounts
- Gas costs per transaction
- Failed transaction rates
- User adoption metrics
- 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.