Leaderboard API
The FarFISH leaderboard tracks user achievements and displays top performers. Leaderboard score multiplied by 20 equals total FRH earned.
Base URL
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:
GET https://app.farfish.xyz/api/leaderboard
Response
Success (200):
[
{
"rank": 1,
"wallet": "0x1234567890abcdef1234567890abcdef12345678",
"referrals_count": 150,
"rewards": 3000
},
{
"rank": 2,
"wallet": "0xabcdef1234567890abcdef1234567890abcdef12",
"referrals_count": 120,
"rewards": 2400
},
...
]
Response Schema:
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:
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):
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):
[]
cURL Example
curl https://app.farfish.xyz/api/leaderboard
JavaScript Example
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
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:
GET /api/leaderboard/user?wallet=0x742d35cc6634c0532925a3b844bc9e7595f0beb9
Response
Success (200):
{
"rank": 42,
"wallet": "0x742d35cc6634c0532925a3b844bc9e7595f0beb9",
"referrals_count": 15,
"rewards": 300
}
Not Ranked (200):
{
"rank": 0,
"wallet": "0x742d35cc6634c0532925a3b844bc9e7595f0beb9",
"referrals_count": 0,
"rewards": 0
}
Response Schema:
{
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:
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:
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
curl "https://app.farfish.xyz/api/leaderboard/user?wallet=0x742d35cc6634c0532925a3b844bc9e7595f0beb9"
JavaScript Example
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
// 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
| Action | Score Change | FRH 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):
// 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
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:
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:
// Future API
GET /api/leaderboard?page=1&limit=100
// Response
{
"data": [...100 users...],
"pagination": {
"page": 1,
"limit": 100,
"total": 5000,
"totalPages": 50
}
}
Backend Operation:
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
// 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:
// 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
| Error | HTTP Status | Meaning | Action |
|---|---|---|---|
Empty array [] | 200 | No users in leaderboard | Normal (fresh database) |
rank: 0 | 200 | User not ranked yet | Normal (new user) |
| Invalid wallet | 400 | Malformed address | Validate format client-side |
| Server error | 500 | Backend connection issue | Retry with exponential backoff |
Related Documentation
- Referral API - How referrals increase scores
- User API - Initialize user profiles
- Tokenomics - FRH reward economics
- Leaderboard Guide - User-facing leaderboard behavior