Rewards System
Overview
The Med.Fun rewards system provides cashback on trading fees through a tier-based multiplier system. Users earn cashback on every trade, with higher tiers receiving increased multipliers based on their trading volume.
How It Works
Trade Tokens: Execute buy or sell trades on the platform
Earn Cashback: Receive percentage of trading fees back
Tier Multipliers: Cashback multiplied by your current tier (1x to 3x)
Track Volume: Trading volume determines your tier
Accumulate: Cashback accumulates as "pending"
Claim: Withdraw accumulated cashback (minimum $1.00)
Database Schema
reward_tiers
reward_tiersDefines tier structure and multipliers.
id
uuid
No
gen_random_uuid()
Primary key
tier_name
text
No
-
Tier display name
tier_level
integer
No
-
Tier number (1-5)
min_volume
numeric
No
0
Min volume required
max_volume
numeric
Yes
null
Max volume (null = unlimited)
cashback_multiplier
numeric
No
1.0
Multiplier for cashback
tier_color
text
No
-
UI color code
benefits
jsonb
No
{}
Additional tier benefits
created_at
timestamptz
No
now()
Creation timestamp
Default Tiers:
Intern
1
$0
$1,000
1.0x
Gray
Resident
2
$1,000
$10,000
1.5x
Blue
Chief Surgeon
3
$10,000
$50,000
2.0x
Purple
Surgical Trader
4
$50,000
$250,000
2.5x
Gold
Doctor of Degeneracy
5
$250,000
∞
3.0x
Rainbow
Constraints:
Unique on
tier_levelCheck:
cashback_multiplier > 0
RLS Policies:
Anyone can view tiers
System can manage tiers
user_trading_stats
user_trading_statsTracks user trading activity and current tier.
id
uuid
No
gen_random_uuid()
Primary key
user_wallet
text
No
-
User's wallet address
total_volume
numeric
No
0
Lifetime trading volume
total_trades
integer
No
0
Total trade count
current_tier_id
uuid
Yes
null
Current tier ID
lifetime_cashback
numeric
No
0
Total cashback earned
created_at
timestamptz
No
now()
Stats creation
updated_at
timestamptz
No
now()
Last update
Constraints:
Unique index on
user_walletForeign key to
reward_tiers(id)Check:
total_volume >= 0Check:
total_trades >= 0
RLS Policies:
Users can view their own stats
Users can create their own stats
Users can update their own stats
System can manage all stats
cashback_earnings
cashback_earningsRecords individual cashback transactions.
id
uuid
No
gen_random_uuid()
Primary key
user_wallet
text
No
-
Earner's wallet
trade_id
uuid
Yes
null
Related trade ID
trade_volume
numeric
No
-
Trade amount
cashback_amount
numeric
No
-
Cashback earned
multiplier
numeric
No
-
Tier multiplier applied
status
text
No
'pending'
pending/claimed
created_at
timestamptz
No
now()
Earned timestamp
claimed_at
timestamptz
Yes
null
Claimed timestamp
Constraints:
Foreign key to
user_trading_stats(user_wallet)Check:
cashback_amount >= 0Check:
multiplier > 0
RLS Policies:
Users can view their own earnings
Users can update their own earnings (claim)
System can insert earnings
Reward Tiers
Tier Definitions
Tier 1: Intern
Min Volume: $0
Max Volume: $999.99
Multiplier: 1.0x (base rate)
Color:
#6B7280(Gray)Benefits:
Access to basic cashback
Transaction history
Standard support
Tier 2: Resident
Min Volume: $1,000
Max Volume: $9,999.99
Multiplier: 1.5x (+50% boost)
Color:
#3B82F6(Blue)Benefits:
All Tier 1 benefits
Priority transaction processing
Email support
Tier 3: Chief Surgeon
Min Volume: $10,000
Max Volume: $49,999.99
Multiplier: 2.0x (+100% boost)
Color:
#8B5CF6(Purple)Benefits:
All Tier 2 benefits
Advanced analytics
Custom dashboard
Priority support
Tier 4: Surgical Trader
Min Volume: $50,000
Max Volume: $249,999.99
Multiplier: 2.5x (+150% boost)
Color:
#F59E0B(Gold)Benefits:
All Tier 3 benefits
API access
Custom alerts
VIP support channel
Tier 5: Doctor of Degeneracy
Min Volume: $250,000+
Max Volume: Unlimited
Multiplier: 3.0x (+200% boost)
Color:
linear-gradient(rainbow)(Rainbow)Benefits:
All Tier 4 benefits
Dedicated account manager
Custom fee structures
Early access to features
Exclusive community access
Tier Progression
Automatic Tier Assignment:
function determineTier(totalVolume: number, tiers: RewardTier[]): RewardTier {
// Sort tiers by min_volume descending
const sortedTiers = [...tiers].sort((a, b) => b.min_volume - a.min_volume);
// Find first tier where user qualifies
for (const tier of sortedTiers) {
if (totalVolume >= tier.min_volume) {
if (tier.max_volume === null || totalVolume <= tier.max_volume) {
return tier;
}
}
}
// Default to Tier 1
return tiers.find(t => t.tier_level === 1)!;
}Progress Calculation:
function calculateTierProgress(
currentVolume: number,
currentTier: RewardTier,
nextTier: RewardTier | null
): number {
if (!nextTier) return 100; // Max tier reached
const tierVolumeRange = nextTier.min_volume - currentTier.min_volume;
const volumeInTier = currentVolume - currentTier.min_volume;
return (volumeInTier / tierVolumeRange) * 100;
}Cashback Calculation
Base Cashback Formula
// Trading fee is typically 1% of trade volume
const tradingFee = tradeVolume * 0.01;
// Base cashback (before multiplier)
const baseCashback = tradingFee * BASE_CASHBACK_RATE; // e.g., 10% of fee
// Apply tier multiplier
const finalCashback = baseCashback * tierMultiplier;Example Calculations
Example 1: Intern (1.0x multiplier)
Trade Volume: $1,000
Trading Fee (1%): $10.00
Base Cashback (10%): $1.00
Tier Multiplier: 1.0x
Final Cashback: $1.00Example 2: Chief Surgeon (2.0x multiplier)
Trade Volume: $5,000
Trading Fee (1%): $50.00
Base Cashback (10%): $5.00
Tier Multiplier: 2.0x
Final Cashback: $10.00Example 3: Doctor of Degeneracy (3.0x multiplier)
Trade Volume: $10,000
Trading Fee (1%): $100.00
Base Cashback (10%): $10.00
Tier Multiplier: 3.0x
Final Cashback: $30.00Cashback Tracking
async function recordTradeCashback(
userWallet: string,
tradeId: string,
tradeVolume: number,
tradeFee: number
) {
// 1. Get user's current tier
const { data: stats } = await supabase
.from('user_trading_stats')
.select('*, reward_tiers!inner(*)')
.eq('user_wallet', userWallet)
.single();
if (!stats) return;
// 2. Calculate cashback
const baseCashback = tradeFee * 0.10; // 10% of fee
const cashbackAmount = baseCashback * stats.reward_tiers.cashback_multiplier;
// 3. Record earning
await supabase
.from('cashback_earnings')
.insert({
user_wallet: userWallet,
trade_id: tradeId,
trade_volume: tradeVolume,
cashback_amount: cashbackAmount,
multiplier: stats.reward_tiers.cashback_multiplier,
status: 'pending'
});
// 4. Update stats
await supabase
.from('user_trading_stats')
.update({
total_volume: stats.total_volume + tradeVolume,
total_trades: stats.total_trades + 1,
lifetime_cashback: stats.lifetime_cashback + cashbackAmount
})
.eq('user_wallet', userWallet);
}Trading Stats Tracking
Stats Initialization
When a user makes their first trade:
async function initializeTradingStats(userWallet: string) {
// Get Tier 1 (Intern)
const { data: tier1 } = await supabase
.from('reward_tiers')
.select('*')
.eq('tier_level', 1)
.single();
// Create stats record
await supabase
.from('user_trading_stats')
.insert({
user_wallet: userWallet,
total_volume: 0,
total_trades: 0,
current_tier_id: tier1.id,
lifetime_cashback: 0
});
}Stats Updates
After each trade:
async function updateTradingStats(
userWallet: string,
tradeVolume: number,
cashbackEarned: number
) {
// 1. Update volume and trade count
const { data: updatedStats } = await supabase
.from('user_trading_stats')
.update({
total_volume: stats.total_volume + tradeVolume,
total_trades: stats.total_trades + 1,
lifetime_cashback: stats.lifetime_cashback + cashbackEarned,
updated_at: new Date().toISOString()
})
.eq('user_wallet', userWallet)
.select()
.single();
// 2. Check for tier upgrade
await checkTierUpgrade(userWallet, updatedStats.total_volume);
}Tier Upgrade Check
async function checkTierUpgrade(
userWallet: string,
totalVolume: number
) {
// 1. Get all tiers
const { data: tiers } = await supabase
.from('reward_tiers')
.select('*')
.order('min_volume', { ascending: false });
// 2. Determine appropriate tier
const newTier = tiers.find(tier =>
totalVolume >= tier.min_volume &&
(tier.max_volume === null || totalVolume <= tier.max_volume)
);
if (!newTier) return;
// 3. Get current tier
const { data: currentStats } = await supabase
.from('user_trading_stats')
.select('current_tier_id')
.eq('user_wallet', userWallet)
.single();
// 4. Update if tier changed
if (currentStats.current_tier_id !== newTier.id) {
await supabase
.from('user_trading_stats')
.update({ current_tier_id: newTier.id })
.eq('user_wallet', userWallet);
// 5. Notify user
toast.success(`🎉 Tier upgraded to ${newTier.tier_name}!`);
}
}Claiming Cashback
Claim Requirements
Minimum Amount: $1.00 USD
Status: Must be 'pending'
User Action: Manual claim via dashboard
Claim Process
async function claimCashback(userWallet: string) {
// 1. Check pending balance
const { data: earnings } = await supabase
.from('cashback_earnings')
.select('cashback_amount')
.eq('user_wallet', userWallet)
.eq('status', 'pending');
const pendingAmount = earnings?.reduce(
(sum, e) => sum + parseFloat(e.cashback_amount),
0
) || 0;
if (pendingAmount < 1.00) {
throw new Error('Minimum $1.00 required to claim');
}
// 2. Update earnings status
const { error } = await supabase
.from('cashback_earnings')
.update({
status: 'claimed',
claimed_at: new Date().toISOString()
})
.eq('user_wallet', userWallet)
.eq('status', 'pending');
if (error) throw error;
// 3. Process withdrawal (implementation specific)
await processWithdrawal(userWallet, pendingAmount);
// 4. Show success
toast.success(`Successfully claimed $${pendingAmount.toFixed(2)}!`);
}Claim History
Users can view claimed cashback history:
const { data: claimedEarnings } = await supabase
.from('cashback_earnings')
.select('*')
.eq('user_wallet', userWallet)
.eq('status', 'claimed')
.order('claimed_at', { ascending: false });Real-time Updates
Supabase Subscriptions
Trading Stats Updates:
const statsChannel = supabase
.channel('user_trading_stats_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'user_trading_stats',
filter: `user_wallet=eq.${userWallet}`
},
() => fetchRewardsData()
)
.subscribe();Cashback Earnings Updates:
const earningsChannel = supabase
.channel('cashback_earnings_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'cashback_earnings',
filter: `user_wallet=eq.${userWallet}`
},
() => fetchRewardsData()
)
.subscribe();Frontend Components
useRewards Hook
useRewards HookLocation: /src/hooks/useRewards.tsx
Purpose: Manages all rewards-related data and operations
State Management:
const {
tiers, // All reward tiers
userStats, // User's trading stats
cashbackEarnings, // Earnings history
totalCashback, // Lifetime cashback
pendingCashback, // Unclaimed amount
claimedCashback, // Claimed amount
currentTier, // Current tier object
nextTier, // Next tier object
tierProgress, // Progress percentage
volumeToNextTier, // Volume needed
loading, // Loading state
claiming, // Claiming state
claimCashback, // Function
refreshData // Function
} = useRewards(userWallet);Key Functions:
fetchRewardsData:
async function fetchRewardsData() {
if (!userWallet) {
setLoading(false);
return;
}
try {
// 1. Fetch all tiers
const { data: tiersData } = await supabase
.from('reward_tiers')
.select('*')
.order('tier_level');
setTiers(tiersData || []);
// 2. Fetch or create user stats
let { data: statsData } = await supabase
.from('user_trading_stats')
.select('*, reward_tiers!inner(*)')
.eq('user_wallet', userWallet)
.single();
if (!statsData) {
// Create initial stats
const tier1 = tiersData?.find(t => t.tier_level === 1);
const { data: newStats } = await supabase
.from('user_trading_stats')
.insert({
user_wallet: userWallet,
total_volume: 0,
total_trades: 0,
current_tier_id: tier1?.id,
lifetime_cashback: 0
})
.select('*, reward_tiers!inner(*)')
.single();
statsData = newStats;
}
setUserStats(statsData);
setCurrentTier(statsData.reward_tiers);
// 3. Calculate next tier and progress
const nextTierData = tiersData?.find(
t => t.tier_level === statsData.reward_tiers.tier_level + 1
);
setNextTier(nextTierData || null);
if (nextTierData) {
const progress =
((statsData.total_volume - statsData.reward_tiers.min_volume) /
(nextTierData.min_volume - statsData.reward_tiers.min_volume)) * 100;
setTierProgress(Math.min(100, Math.max(0, progress)));
setVolumeToNextTier(nextTierData.min_volume - statsData.total_volume);
}
// 4. Fetch cashback earnings
const { data: earningsData } = await supabase
.from('cashback_earnings')
.select('*')
.eq('user_wallet', userWallet)
.order('created_at', { ascending: false });
setCashbackEarnings(earningsData || []);
// 5. Calculate totals
const total = earningsData?.reduce(
(sum, e) => sum + parseFloat(e.cashback_amount),
0
) || 0;
const pending = earningsData
?.filter(e => e.status === 'pending')
.reduce((sum, e) => sum + parseFloat(e.cashback_amount), 0) || 0;
const claimed = earningsData
?.filter(e => e.status === 'claimed')
.reduce((sum, e) => sum + parseFloat(e.cashback_amount), 0) || 0;
setTotalCashback(total);
setPendingCashback(pending);
setClaimedCashback(claimed);
} catch (error) {
console.error('Error fetching rewards data:', error);
} finally {
setLoading(false);
}
}TradingRewardsDashboard Component
TradingRewardsDashboard ComponentLocation: /src/components/TradingRewardsDashboard.tsx
Features:
Tabbed interface (Overview, History, Tiers)
Current tier display with color coding
Cashback summary (pending/claimed)
Trading stats (volume, trades)
Tier progress bar
Claim button
Earnings history list
All tiers overview
Mobile-responsive
UI Structure:
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="history">History</TabsTrigger>
<TabsTrigger value="tiers">Tiers</TabsTrigger>
</TabsList>
<TabsContent value="overview">
{/* Tier badge, cashback amounts, stats, progress */}
</TabsContent>
<TabsContent value="history">
{/* Cashback earnings list */}
</TabsContent>
<TabsContent value="tiers">
{/* All tiers with requirements and benefits */}
</TabsContent>
</Tabs>RewardsCard Component
RewardsCard ComponentLocation: /src/components/RewardsCard.tsx
Purpose: Summary card for quick access
Displayed Data:
Current tier name and icon
Pending cashback amount
Total earned amount
"View Details" button
Integration Points
Record Trade Cashback
// After trade execution
async function handleTradeComplete(trade: TradeData) {
// 1. Calculate fee
const tradeFee = trade.volume * 0.01; // 1%
// 2. Get user tier
const { data: stats } = await supabase
.from('user_trading_stats')
.select('*, reward_tiers!inner(*)')
.eq('user_wallet', trade.userWallet)
.single();
if (!stats) {
await initializeTradingStats(trade.userWallet);
return;
}
// 3. Calculate cashback
const baseCashback = tradeFee * 0.10;
const cashback = baseCashback * stats.reward_tiers.cashback_multiplier;
// 4. Record earning
await supabase
.from('cashback_earnings')
.insert({
user_wallet: trade.userWallet,
trade_id: trade.id,
trade_volume: trade.volume,
cashback_amount: cashback,
multiplier: stats.reward_tiers.cashback_multiplier,
status: 'pending'
});
// 5. Update stats
await updateTradingStats(
trade.userWallet,
trade.volume,
cashback
);
// 6. Check tier upgrade
await checkTierUpgrade(
trade.userWallet,
stats.total_volume + trade.volume
);
}User Flows
Flow 1: First Trade & Cashback
1. User connects wallet
2. User makes first trade ($100)
3. System creates trading_stats record (Tier 1)
4. Trading fee calculated: $1.00
5. Base cashback: $0.10 (10% of fee)
6. Tier multiplier applied: 1.0x
7. Final cashback: $0.10
8. Cashback recorded as 'pending'
9. Stats updated: volume = $100, trades = 1Flow 2: Tier Upgrade
1. User has $9,500 volume (Tier 1: Intern)
2. User makes trade for $1,000
3. New total volume: $10,500
4. System checks tier eligibility
5. Qualifies for Tier 3: Chief Surgeon ($10k+)
6. Tier upgraded in database
7. Multiplier increases: 1.0x → 2.0x
8. Toast: "🎉 Tier upgraded to Chief Surgeon!"
9. Future cashback uses 2.0x multiplierFlow 3: Claim Cashback
1. User accumulates $5.50 pending cashback
2. User opens rewards dashboard
3. Sees "$5.50 Available to Claim"
4. Clicks "Claim Cashback" button
5. System validates minimum ($1.00) ✓
6. Status updated: pending → claimed
7. claimed_at timestamp recorded
8. Withdrawal processed
9. Balance updates in UI
10. Toast: "Successfully claimed $5.50!"Best Practices
For Developers
1. Always use transactions for multi-step operations:
// Use Supabase RPC for atomic operations
await supabase.rpc('record_trade_with_cashback', {
p_user: userWallet,
p_volume: tradeVolume,
p_fee: tradeFee
});2. Handle tier upgrades gracefully:
// Check tier after every stats update
if (newVolume >= nextTier.min_volume) {
await upgradeTier(userWallet, nextTier.id);
}3. Validate cashback calculations:
// Never allow negative cashback
if (cashbackAmount < 0) {
throw new Error("Invalid cashback calculation");
}
// Cap at reasonable maximum
const MAX_CASHBACK = tradeFee * 0.50; // 50% of fee max
cashbackAmount = Math.min(cashbackAmount, MAX_CASHBACK);For Product
1. Tier thresholds encourage growth
Each tier ~10x previous tier volume
Creates clear progression goals
Rewards high-volume traders
2. Minimum claim prevents micro-transactions
$1.00 minimum balances network costs
Encourages accumulation
Reduces support burden
3. Real-time updates build engagement
Instant feedback on earnings
Progress bars show advancement
Tier upgrades feel rewarding
Troubleshooting
Cashback Not Recording
Check:
Does user_trading_stats record exist?
Is current_tier_id set correctly?
Verify cashback calculation logic
Check database triggers/functions
Tier Not Upgrading
Check:
Is total_volume updating correctly?
Verify tier min_volume thresholds
Check tier upgrade logic
Confirm current_tier_id updates
Cannot Claim Cashback
Check:
Is pending balance >= $1.00?
Are earnings in 'pending' status?
Verify wallet connection
Check RLS policies
Performance Considerations
Database Indexes
CREATE INDEX idx_trading_stats_wallet ON user_trading_stats(user_wallet);
CREATE INDEX idx_cashback_earnings_wallet ON cashback_earnings(user_wallet);
CREATE INDEX idx_cashback_earnings_status ON cashback_earnings(status);
CREATE INDEX idx_reward_tiers_level ON reward_tiers(tier_level);Caching Strategy
Cache tier definitions (rarely change)
Fetch user stats on demand
Use real-time for automatic updates
Aggregate earnings in application layer
Future Enhancements
Potential Features
Bonus Multipliers: Limited-time cashback boosts
VIP Tiers: Exclusive invite-only tiers
Cashback Tokens: Earn native platform tokens
Leaderboards: Top earners by tier
Referral Bonuses: Extra cashback for referrals
Auto-Claim: Automatic claiming at threshold
Technical Improvements
Database function for atomic cashback recording
Scheduled job for batch tier upgrades
Analytics dashboard for cashback trends
A/B testing framework for multipliers
Last updated
Was this helpful?