Referral System

Overview

The Med.Fun referral system allows users to earn passive income by inviting friends to the platform. Users receive commission on their referrals' trading volume, with rates increasing based on tier progression.

How It Works

  1. Generate Code: User generates a unique 6-character referral code (or custom code)

  2. Share Link: User shares referral link: https://med.fun/ref/{CODE}

  3. Friend Signs Up: New user creates account using the referral link

  4. Track Activity: System tracks referred user's trading volume

  5. Earn Commission: Referrer earns percentage of trading fees

  6. Claim Earnings: Accumulated earnings can be claimed (min $1.00)

Database Schema

referral_codes

Stores user referral codes and tracking data.

Column
Type
Nullable
Default
Description

id

uuid

No

gen_random_uuid()

Primary key

code

text

No

-

Referral code (6-9 chars)

user_wallet

text

No

-

Owner's wallet address

is_custom

boolean

No

false

Whether code is custom

total_referrals

integer

No

0

Count of referrals

total_volume

numeric

No

0

Total trading volume

total_earnings

numeric

No

0

Total commissions earned

created_at

timestamptz

No

now()

Creation timestamp

updated_at

timestamptz

No

now()

Last update timestamp

Constraints:

  • Unique index on code (case-insensitive)

  • Unique index on user_wallet

  • Check: length(code) >= 1 AND length(code) <= 9

RLS Policies:

  • Users can view their own codes

  • Users can create codes for themselves

  • Users can update their own codes

user_referrals

Tracks individual referred users and their activity.

Column
Type
Nullable
Default
Description

id

uuid

No

gen_random_uuid()

Primary key

referrer_wallet

text

No

-

Referrer's wallet

referred_wallet

text

No

-

Referred user's wallet

referral_code

text

No

-

Code used

status

text

No

'active'

Status (active/inactive)

total_volume

numeric

No

0

Referred user's volume

total_earnings

numeric

No

0

Earnings from this user

created_at

timestamptz

No

now()

Referral timestamp

last_trade_at

timestamptz

Yes

null

Last trade timestamp

Constraints:

  • Unique constraint on referred_wallet

  • Foreign key to referral_codes(code)

RLS Policies:

  • Users can view their own referrals

  • System can insert new referrals

referral_earnings

Records individual commission payments.

Column
Type
Nullable
Default
Description

id

uuid

No

gen_random_uuid()

Primary key

referrer_wallet

text

No

-

Earner's wallet

referred_wallet

text

No

-

Source user's wallet

amount

numeric

No

-

Commission amount

trade_volume

numeric

No

-

Trade that generated it

commission_rate

numeric

No

-

Rate applied

status

text

No

'pending'

pending/claimed

created_at

timestamptz

No

now()

Earned timestamp

claimed_at

timestamptz

Yes

null

Claimed timestamp

RLS Policies:

  • Users can view their own earnings

  • Users can update their own earnings (claim)

referral_tiers

Defines tier structure and benefits.

Column
Type
Nullable
Default
Description

id

uuid

No

gen_random_uuid()

Primary key

tier_name

text

No

-

Tier name

tier_level

integer

No

-

Tier number (1-4)

min_referrals

integer

No

0

Min referrals needed

min_volume

numeric

No

0

Min volume needed

commission_rate

numeric

No

-

Commission %

benefits

jsonb

No

{}

Tier benefits

color

text

No

-

UI color

created_at

timestamptz

No

now()

Creation timestamp

Default Tiers:

Tier
Level
Min Referrals
Min Volume
Rate
Benefits

Bronze

1

0

$0

10%

Base commission

Silver

2

5

$1,000

12%

Priority support, Custom codes

Gold

3

25

$10,000

15%

Custom badge, Early access

Platinum

4

100

$100,000

20%

VIP support, Account manager

referral_settings

User preferences for referral notifications.

Column
Type
Nullable
Default
Description

id

uuid

No

gen_random_uuid()

Primary key

user_wallet

text

No

-

User's wallet

email_notifications

boolean

No

true

Email notifications

push_notifications

boolean

No

true

Push notifications

auto_claim_threshold

numeric

Yes

null

Auto-claim amount

created_at

timestamptz

No

now()

Creation timestamp

updated_at

timestamptz

No

now()

Last update timestamp

Referral Code System

Auto-Generated Codes

  • Format: 6 uppercase alphanumeric characters

  • Example: ABC123, XYZ789

  • Generation: Random using Math.random() and character set

  • Validation: Must be unique across all users

Custom Codes

  • Format: 1-9 alphanumeric characters

  • Example: CRYPTO, MOON, TRADER

  • Validation:

    • Must be 1-9 characters

    • Alphanumeric only (A-Z, 0-9)

    • Case-insensitive uniqueness check

    • Cannot contain special characters

  • Availability: Check via database before creation

https://med.fun/ref/{CODE}

Commission Structure

Base Commission

  • Default Rate: 10% of referred user's trading fees

  • Calculation: tradingFee * commissionRate

  • Tracking: Real-time per trade

  • Accumulation: Added to pending earnings

Tier-Based Boosts

Commission rates increase with tier:

Bronze (Tier 1):   10% commission
Silver (Tier 2):   12% commission (+20% boost)
Gold (Tier 3):     15% commission (+50% boost)
Platinum (Tier 4): 20% commission (+100% boost)

Example Calculation

Referred user makes trade: $100
Trading fee (1%): $1.00
Referrer tier: Gold (15%)
Commission: $1.00 * 0.15 = $0.15

Tier Progression

Tier Requirements

Bronze (Starting Tier)

  • Min Referrals: 0

  • Min Volume: $0

  • Benefits: Base 10% commission

Silver Tier

  • Min Referrals: 5 active referrals

  • Min Volume: $1,000 total

  • Benefits: 12% commission, Priority support, Custom referral codes

Gold Tier

  • Min Referrals: 25 active referrals

  • Min Volume: $10,000 total

  • Benefits: 15% commission, Custom badge, Early access to features

Platinum Tier

  • Min Referrals: 100 active referrals

  • Min Volume: $100,000 total

  • Benefits: 20% commission, VIP support, Dedicated account manager

Tier Calculation Logic

// Find highest tier user qualifies for
const currentTier = tiers
  .filter(tier => 
    totalReferrals >= tier.min_referrals && 
    totalVolume >= tier.min_volume
  )
  .sort((a, b) => b.tier_level - a.tier_level)[0];

Progress Tracking

// Calculate progress to next tier
const nextTier = tiers.find(t => t.tier_level === currentTier.tier_level + 1);
if (nextTier) {
  const referralProgress = (totalReferrals / nextTier.min_referrals) * 100;
  const volumeProgress = (totalVolume / nextTier.min_volume) * 100;
  const overallProgress = Math.min(referralProgress, volumeProgress);
}

Claiming Earnings

Claim Requirements

  • Minimum Amount: $1.00 USD

  • Status: Must be 'pending'

  • User Action: Manual claim via dashboard

Claim Process

async function claimEarnings() {
  // 1. Check minimum threshold
  if (pendingEarnings < 1.00) {
    return error("Minimum $1.00 required");
  }
  
  // 2. Update earnings status
  await supabase
    .from('referral_earnings')
    .update({ 
      status: 'claimed',
      claimed_at: new Date()
    })
    .eq('referrer_wallet', userWallet)
    .eq('status', 'pending');
    
  // 3. Refresh data
  await fetchReferralData();
}

Transaction Flow

  1. User clicks "Claim Earnings" button

  2. Frontend validates minimum amount

  3. Backend updates earnings records

  4. Status changes: pendingclaimed

  5. claimed_at timestamp recorded

  6. UI updates with new balances

Real-time Updates

Supabase Subscriptions

Referral Updates:

const referralsChannel = supabase
  .channel('user_referrals_changes')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'user_referrals',
      filter: `referrer_wallet=eq.${userWallet}`
    },
    () => fetchReferralData()
  )
  .subscribe();

Earnings Updates:

const earningsChannel = supabase
  .channel('referral_earnings_changes')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'referral_earnings',
      filter: `referrer_wallet=eq.${userWallet}`
    },
    () => fetchReferralData()
  )
  .subscribe();

Frontend Components

useReferrals Hook

Location: /src/hooks/useReferrals.tsx

Purpose: Manages all referral data and operations

State Management:

const {
  referralCode,          // User's code
  referralLink,          // Full referral URL
  totalReferrals,        // Count of referrals
  activeReferrals,       // Active referrals count
  totalVolume,           // Cumulative volume
  totalEarnings,         // Total earned
  pendingEarnings,       // Unclaimed amount
  claimedEarnings,       // Claimed amount
  userReferrals,         // Array of referrals
  referralEarnings,      // Earnings history
  currentTier,           // Current tier object
  nextTier,              // Next tier object
  tierProgress,          // Progress percentage
  loading,               // Loading state
  claiming,              // Claiming state
  generateReferralCode,  // Function
  claimEarnings,         // Function
  copyReferralLink,      // Function
  refreshData            // Function
} = useReferrals(userWallet);

Key Functions:

generateReferralCode:

async function generateReferralCode(customCode?: string) {
  if (customCode) {
    // Validate custom code
    if (!/^[A-Z0-9]{1,9}$/.test(customCode.toUpperCase())) {
      throw new Error("Invalid format");
    }
    
    // Check availability
    const exists = await checkCodeExists(customCode);
    if (exists) {
      throw new Error("Code already taken");
    }
  }
  
  const code = customCode || generateRandomCode();
  
  await supabase
    .from('referral_codes')
    .insert({
      code: code.toUpperCase(),
      user_wallet: userWallet,
      is_custom: !!customCode
    });
}

claimEarnings:

async function claimEarnings() {
  if (pendingEarnings < 1) {
    throw new Error("Minimum $1.00 required");
  }
  
  await supabase
    .from('referral_earnings')
    .update({
      status: 'claimed',
      claimed_at: new Date().toISOString()
    })
    .eq('referrer_wallet', userWallet)
    .eq('status', 'pending');
    
  toast.success("Earnings claimed successfully!");
}

copyReferralLink:

function copyReferralLink() {
  const link = `${window.location.origin}/ref/${referralCode}`;
  navigator.clipboard.writeText(link);
  toast.success("Referral link copied!");
}

ReferralDashboard Component

Location: /src/components/ReferralDashboard.tsx

Features:

  • Tabbed interface (Overview, Referrals, Settings)

  • Referral code display and generation

  • Link sharing (copy + native share API)

  • Earnings summary with claim button

  • Referral list with status and earnings

  • Tier progress visualization

  • Mobile-responsive (Sheet on mobile, Dialog on desktop)

UI Structure:

<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="referrals">Referrals</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  
  <TabsContent value="overview">
    {/* Code, earnings, stats, tier progress */}
  </TabsContent>
  
  <TabsContent value="referrals">
    {/* Referral list with details */}
  </TabsContent>
  
  <TabsContent value="settings">
    {/* Tier benefits and settings */}
  </TabsContent>
</Tabs>

ReferralStatsCard Component

Location: /src/components/ReferralStatsCard.tsx

Purpose: Summary card showing key metrics

Displayed Data:

  • Active referrals count

  • Total earnings

  • Pending earnings

  • "View Details" button

Integration Points

User Signup Flow

// When new user signs up with referral code
async function handleSignupWithReferral(wallet: string, code: string) {
  // 1. Verify referral code exists
  const { data: referralCode } = await supabase
    .from('referral_codes')
    .select('*')
    .eq('code', code.toUpperCase())
    .single();
    
  if (!referralCode) return;
  
  // 2. Create referral relationship
  await supabase
    .from('user_referrals')
    .insert({
      referrer_wallet: referralCode.user_wallet,
      referred_wallet: wallet,
      referral_code: code.toUpperCase(),
      status: 'active'
    });
    
  // 3. Update referral code stats
  await supabase
    .from('referral_codes')
    .update({
      total_referrals: referralCode.total_referrals + 1
    })
    .eq('id', referralCode.id);
}

Trade Commission Tracking

// When referred user makes a trade
async function recordTradeCommission(
  referredWallet: string,
  tradeVolume: number,
  tradeFee: number
) {
  // 1. Get referral relationship
  const { data: referral } = await supabase
    .from('user_referrals')
    .select('*, referral_codes!inner(*)')
    .eq('referred_wallet', referredWallet)
    .eq('status', 'active')
    .single();
    
  if (!referral) return;
  
  // 2. Get referrer's tier
  const tier = await getCurrentTier(referral.referrer_wallet);
  
  // 3. Calculate commission
  const commission = tradeFee * (tier.commission_rate / 100);
  
  // 4. Record earning
  await supabase
    .from('referral_earnings')
    .insert({
      referrer_wallet: referral.referrer_wallet,
      referred_wallet: referredWallet,
      amount: commission,
      trade_volume: tradeVolume,
      commission_rate: tier.commission_rate,
      status: 'pending'
    });
    
  // 5. Update stats
  await updateReferralStats(referral.referrer_wallet, tradeVolume, commission);
}

User Flows

Flow 1: Generate and Share Code

1. User connects wallet
2. User clicks "Generate Referral Code"
3. System generates 6-char code (or accepts custom)
4. Code saved to database
5. Referral link displayed
6. User clicks "Copy Link"
7. Link copied to clipboard
8. User shares link externally

Flow 2: Earn Commission

1. Friend clicks referral link
2. Friend creates account
3. Referral relationship created in database
4. Friend makes first trade
5. Trade fee calculated (1%)
6. Commission calculated based on tier (10-20%)
7. Earning recorded as 'pending'
8. Referrer sees new earning in dashboard
9. Real-time update via Supabase subscription

Flow 3: Claim Earnings

1. User opens referral dashboard
2. Pending earnings displayed
3. User clicks "Claim Earnings"
4. System checks minimum ($1.00)
5. Status updated to 'claimed'
6. claimed_at timestamp recorded
7. UI updates balances
8. Success toast displayed

Flow 4: Tier Progression

1. User accumulates referrals and volume
2. System checks tier requirements
3. Requirements met for next tier
4. Tier automatically upgraded
5. Commission rate increased
6. New benefits unlocked
7. Progress bar updates
8. Notification displayed

API Reference

Database Functions

(None currently - all logic in application layer)

RLS Helper Functions

-- Check if user owns referral code
CREATE FUNCTION user_owns_referral_code(code_id uuid)
RETURNS boolean AS $$
  SELECT EXISTS (
    SELECT 1 FROM referral_codes
    WHERE id = code_id
    AND user_wallet = (current_setting('request.jwt.claims')::json->>'sub')
  );
$$ LANGUAGE sql SECURITY DEFINER;

Best Practices

For Developers

1. Always validate referral codes:

if (!/^[A-Z0-9]{1,9}$/.test(code)) {
  throw new Error("Invalid code format");
}

2. Use transactions for multi-step operations:

// When creating referral relationship
await supabase.rpc('create_referral', {
  p_referrer: referrerWallet,
  p_referred: referredWallet,
  p_code: code
});

3. Handle race conditions:

// Use database constraints and ON CONFLICT
INSERT INTO user_referrals (...)
ON CONFLICT (referred_wallet) DO NOTHING;

4. Monitor real-time subscriptions:

// Clean up subscriptions on unmount
useEffect(() => {
  return () => {
    supabase.removeChannel(channel);
  };
}, []);

For Product

1. Minimum payout prevents micro-transactions

  • $1.00 minimum balances gas costs and prevents spam

2. Tier system encourages growth

  • Progressive rewards incentivize user acquisition

3. Real-time updates build trust

  • Instant feedback on earnings creates positive UX

4. Custom codes enable branding

  • Personal codes improve conversion rates

Troubleshooting

Code Already Exists

Error: "Referral code already taken" Solution: Try a different custom code or use auto-generated code

Earnings Not Appearing

Check:

  1. Is referred user's account active?

  2. Has referred user completed trades?

  3. Check database for pending earnings

  4. Verify real-time subscription is active

Cannot Claim Earnings

Check:

  1. Is pending balance >= $1.00?

  2. Are earnings in 'pending' status?

  3. Check wallet connection

  4. Verify RLS policies allow update

Tier Not Updating

Check:

  1. Verify referral count meets minimum

  2. Verify volume meets minimum

  3. Check tier calculation logic

  4. Refresh dashboard data

Performance Considerations

Database Indexes

CREATE INDEX idx_referral_codes_wallet ON referral_codes(user_wallet);
CREATE INDEX idx_user_referrals_referrer ON user_referrals(referrer_wallet);
CREATE INDEX idx_referral_earnings_referrer ON referral_earnings(referrer_wallet);
CREATE INDEX idx_referral_earnings_status ON referral_earnings(status);

Query Optimization

  • Use select('*') sparingly, fetch only needed columns

  • Implement pagination for large referral lists

  • Cache tier data to reduce database calls

  • Use database views for complex aggregations

Future Enhancements

Potential Features

  • Auto-Claim: Automatic claiming at threshold

  • Leaderboards: Top referrers displayed publicly

  • Bonus Rewards: Limited-time commission boosts

  • Team Referrals: Multi-level commission structure

  • Analytics: Detailed referral performance metrics

  • Email Notifications: Alerts for new referrals and earnings

Technical Improvements

  • Edge function for commission calculation

  • Webhooks for real-time trade notifications

  • GraphQL API for complex queries

  • Caching layer for frequently accessed data

Last updated

Was this helpful?