FarFISH Logo
FarFISHDocs

Leaderboard API

The FarFISH leaderboard tracks user achievements and displays top performers. Leaderboard score multiplied by 20 equals total FRH earned.

Base URL

text
https://app.farfish.xyz/api/leaderboard

Scoring Formula: Score × 20 = Total FRH Rewards

Each referral adds +1 score (+20 FRH). Additional score sources include staking milestones and special events.


GET /api/leaderboard

Get top 100 users ranked by total rewards earned.

Request

Endpoint: GET /api/leaderboard

Parameters: None

Example:

text
GET https://app.farfish.xyz/api/leaderboard

Response

Success (200):

json
[
  {
    "rank": 1,
    "wallet": "0x1234567890abcdef1234567890abcdef12345678",
    "referrals_count": 150,
    "rewards": 3000
  },
  {
    "rank": 2,
    "wallet": "0xabcdef1234567890abcdef1234567890abcdef12",
    "referrals_count": 120,
    "rewards": 2400
  },
  ...
]

Response Schema:

typescript
Array<{
  rank: number;           // 1-based rank position
  wallet: string;         // Full wallet address (lowercase)
  referrals_count: number; // Total successful referrals
  rewards: number;        // Total FRH earned (score × 20)
}>

Data Source

Backend Operations:

text
1. Query leaderboard for all users, sorted by score descending
2. GET refcount:{wallet}                   // Get referral count for each user

Leaderboard Sorted Set Structure:

  • Key: leaderboard
  • Member: Wallet address (e.g., 0x1234...5678)
  • Score: Reward multiplier (e.g., 150 = 3000 FRH)

Sorting Logic

Users are sorted by score descending (highest rewards first):

text
Score 150 → Rank 1 (3000 FRH)
Score 120 → Rank 2 (2400 FRH)
Score 75  → Rank 3 (1500 FRH)
...

Empty Response

If no users exist (fresh database):

json
[]

cURL Example

bash
curl https://app.farfish.xyz/api/leaderboard

JavaScript Example

typescript
async function getTopUsers() {
  const response = await fetch('https://app.farfish.xyz/api/leaderboard');
  const leaderboard = await response.json();
  
  leaderboard.forEach(user => {
    console.log(`#${user.rank}: ${user.wallet}`);
    console.log(`  Referrals: ${user.referrals_count}`);
    console.log(`  Rewards: ${user.rewards} FRH`);
  });
  
  return leaderboard;
}

React Hook Example

typescript
import { useQuery } from '@tanstack/react-query';

function useLeaderboard() {
  return useQuery({
    queryKey: ['leaderboard'],
    queryFn: async () => {
      const response = await fetch('/api/leaderboard');
      if (!response.ok) throw new Error('Failed to fetch leaderboard');
      return response.json();
    },
    staleTime: 60_000, // Cache for 1 minute
  });
}

// Usage
function LeaderboardPage() {
  const { data: leaderboard, isLoading } = useLeaderboard();
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <table>
      <thead>
        <tr>
          <th>Rank</th>
          <th>Wallet</th>
          <th>Referrals</th>
          <th>Rewards</th>
        </tr>
      </thead>
      <tbody>
        {leaderboard.map(user => (
          <tr key={user.wallet}>
            <td>#{user.rank}</td>
            <td>{user.wallet.slice(0, 6)}...{user.wallet.slice(-4)}</td>
            <td>{user.referrals_count}</td>
            <td>{user.rewards} FRH</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

GET /api/leaderboard/user

Get specific user's rank and statistics.

Request

Endpoint: GET /api/leaderboard/user?wallet={address}

Query Parameters:

  • wallet (required) - Ethereum wallet address

Example:

text
GET /api/leaderboard/user?wallet=0x742d35cc6634c0532925a3b844bc9e7595f0beb9

Response

Success (200):

json
{
  "rank": 42,
  "wallet": "0x742d35cc6634c0532925a3b844bc9e7595f0beb9",
  "referrals_count": 15,
  "rewards": 300
}

Not Ranked (200):

json
{
  "rank": 0,
  "wallet": "0x742d35cc6634c0532925a3b844bc9e7595f0beb9",
  "referrals_count": 0,
  "rewards": 0
}

Response Schema:

typescript
{
  rank: number;            // 1-based rank (0 if not ranked)
  wallet: string;          // Queried wallet address
  referrals_count: number; // Total referrals made
  rewards: number;         // Total FRH earned (score × 20)
}

Auto-Initialization Behavior

If wallet is not found in leaderboard, the API automatically creates an entry with score 0:

Backend Operations:

text
1. Check if wallet exists in leaderboard
2. Add wallet with score 0 if missing
3. Get rank position (0-indexed from backend)

This ensures every wallet query returns valid data even on first connection.

Rank Calculation

Backend returns 0-based index, converted to 1-based rank:

text
Backend index: 0 → Rank: 1 (1st place)
Backend index: 1 → Rank: 2 (2nd place)
Backend index: 41 → Rank: 42

If not found, rank is set to 0 (unranked).

cURL Example

bash
curl "https://app.farfish.xyz/api/leaderboard/user?wallet=0x742d35cc6634c0532925a3b844bc9e7595f0beb9"

JavaScript Example

typescript
async function getUserRank(wallet: string) {
  const response = await fetch(
    `https://app.farfish.xyz/api/leaderboard/user?wallet=${wallet}`
  );
  const data = await response.json();
  
  if (data.rank === 0) {
    console.log('Not yet ranked');
  } else {
    console.log(`Rank #${data.rank}`);
    console.log(`${data.referrals_count} referrals`);
    console.log(`${data.rewards} FRH earned`);
  }
  
  return data;
}

React Hook Example

typescript
// Get wallet address from your Web3 connection library
const { address } = useWalletConnection();
import { useQuery } from '@tanstack/react-query';

function useUserRank() {
  const { address } = useAccount();
  
  return useQuery({
    queryKey: ['leaderboard', 'user', address],
    queryFn: async () => {
      if (!address) return null;
      const response = await fetch(`/api/leaderboard/user?wallet=${address}`);
      if (!response.ok) throw new Error('Failed to fetch user rank');
      return response.json();
    },
    enabled: !!address, // Only run when wallet connected
    staleTime: 30_000,  // Cache for 30 seconds
  });
}

// Usage
function UserProfile() {
  const { data: userRank, isLoading } = useUserRank();
  
  if (isLoading) return <div>Loading...</div>;
  if (!userRank) return <div>Connect wallet to view rank</div>;
  
  return (
    <div>
      <h2>Your Rank: #{userRank.rank || 'Unranked'}</h2>
      <p>Referrals: {userRank.referrals_count}</p>
      <p>Total Rewards: {userRank.rewards} FRH</p>
    </div>
  );
}

Score Calculation

How Scores Increase

ActionScore ChangeFRH Reward
Refer new user (as referrer)+1+20 FRH
Get referred (as referee)+1+20 FRH
Staking milestone (future)+X+20X FRH
Special events (future)+Y+20Y FRH

Current Implementation

Referral Recording (both parties rewarded):

typescript
// API: POST /api/referral/record
await backend.increment('leaderboard', referrerWallet); // Referrer +1
await backend.increment('leaderboard', refereeWallet);  // Referee +1

Result:

  • Referrer score increases by 1 (effective +20 FRH)
  • Referee score increases by 1 (effective +20 FRH)
  • Both users' ranks update automatically

Score Formula

text
Total FRH = Leaderboard Score × 20

Examples:
Score 1   → 20 FRH
Score 10  → 200 FRH
Score 100 → 2000 FRH
Score 500 → 10,000 FRH

Display vs Distribution: Leaderboard shows total theoretical rewards. Actual token distribution may occur periodically (e.g., daily, weekly) or on-demand via claim mechanism. Check smart contracts for token claim status.


Leaderboard Stats Example

Display global leaderboard statistics:

typescript
async function getLeaderboardStats() {
  const leaderboard = await fetch('/api/leaderboard').then(r => r.json());
  
  if (leaderboard.length === 0) {
    return { totalUsers: 0, totalRewards: 0, topScore: 0 };
  }
  
  const totalUsers = leaderboard.length;
  const totalRewards = leaderboard.reduce((sum, user) => sum + user.rewards, 0);
  const topScore = leaderboard[0].rewards;
  
  console.log(`Total Users: ${totalUsers}`);
  console.log(`Total Rewards Distributed: ${totalRewards} FRH`);
  console.log(`Top Score: ${topScore} FRH`);
  
  return { totalUsers, totalRewards, topScore };
}

Pagination (Future Enhancement)

Current implementation returns all users (no pagination). For production with 10,000+ users, pagination should be added.

Recommended Implementation:

typescript
// Future API
GET /api/leaderboard?page=1&limit=100

// Response
{
  "data": [...100 users...],
  "pagination": {
    "page": 1,
    "limit": 100,
    "total": 5000,
    "totalPages": 50
  }
}

Backend Operation:

typescript
const start = (page - 1) * limit;
const stop = start + limit - 1;
await backend.getLeaderboardRange(start, stop);

Real-Time Updates

Leaderboard updates immediately when:

  • User records a referral (both parties' scores +1)
  • User gets referred (score +1)
  • Any action that modifies leaderboard scores

No caching delay - Backend updates are atomic and instantly queryable.

Polling Strategy

typescript
// Poll leaderboard every 30 seconds
useQuery({
  queryKey: ['leaderboard'],
  queryFn: fetchLeaderboard,
  refetchInterval: 30_000, // 30 seconds
});

WebSocket Alternative (Future)

For real-time rank updates without polling:

typescript
// Future enhancement
const ws = new WebSocket('wss://app.farfish.xyz/leaderboard');
ws.onmessage = (event) => {
  const update = JSON.parse(event.data);
  // { wallet: "0x...", newRank: 42, rewards: 840 }
};

Error Handling

ErrorHTTP StatusMeaningAction
Empty array []200No users in leaderboardNormal (fresh database)
rank: 0200User not ranked yetNormal (new user)
Invalid wallet400Malformed addressValidate format client-side
Server error500Backend connection issueRetry with exponential backoff

Last updated: October 12, 2026