Overview

Build leaderboard systems that drive engagement through:

  • Real-time user rankings via loyalty points
  • Paginated leaderboard data with cursor-based pagination
  • Sprint-specific leaderboards for competitions
  • Space-level ranking analytics

Basic Leaderboard Query

Get Leaderboard Rankings

query GetLeaderboard($spaceId: Int!, $cursorAfter: String, $sprintId: Int) {
  space(id: $spaceId) {
    id
    name
    loyaltyPointsRanks(
      cursorAfter: $cursorAfter
      sprintId: $sprintId
    ) {
      totalCount
      pageInfo {
        hasNextPage
        endCursor
      }
      list {
        rank
        points
        address {
          username
          avatar
          address
        }
      }
    }
  }
}

Variables:

{
  "spaceId": 40,
  "cursorAfter": null,
  "sprintId": null
}

Response:

{
  "data": {
    "space": {
      "id": "40",
      "name": "BNB Chain",
      "loyaltyPointsRanks": {
        "totalCount": 1500,
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "cursor_end"
        },
        "list": [
          {
            "rank": 1,
            "points": 5000,
            "address": {
              "username": "TopPlayer",
              "avatar": "https://example.com/avatar.jpg",
              "address": "0x1234567890123456789012345678901234567890"
            }
          }
        ]
      }
    }
  }
}

Sprint-Specific Leaderboards

Get Sprint Rankings

When you have a sprint ID, get sprint-specific rankings:

query GetSprintLeaderboard($spaceId: Int!, $sprintId: Int!, $cursorAfter: String) {
  space(id: $spaceId) {
    loyaltyPointsRanks(
      sprintId: $sprintId
      cursorAfter: $cursorAfter
    ) {
      totalCount
      list {
        rank
        points
        address {
          username
          address
        }
      }
    }
  }
}

Sprint Management: Sprint information must come from external sources as sprint management is not available through this API.

Pagination Patterns

Cursor-Based Navigation

# Get first page
curl -X POST https://graphigo-business.prd.galaxy.eco/query \
  -H "Content-Type: application/json" \
  -H "access-token: YOUR_ACCESS_TOKEN" \
  -d '{
    "query": "query GetLeaderboard($spaceId: Int!) { space(id: $spaceId) { loyaltyPointsRanks(cursorAfter: null) { totalCount pageInfo { hasNextPage endCursor } list { rank points address { username } } } } }",
    "variables": { "spaceId": 40 }
  }'

Loading Next Page

  1. Use pageInfo.endCursor from previous response
  2. Set as cursorAfter parameter for next request
  3. Continue until pageInfo.hasNextPage is false

User Position Lookup

No Direct User Lookup: To find a specific user’s rank, you must paginate through the leaderboard.

Search Strategy

Since there’s no direct user lookup, implement efficient search:

  1. Limited Search: Search first 10-20 pages for UI responsiveness
  2. Background Search: Comprehensive search for analytics
  3. Caching: Store user positions to reduce future searches

Basic Implementation

async function findUserPosition(spaceId, userAddress, accessToken) {
  let cursorAfter = null;
  let pageCount = 0;
  const maxPages = 20; // UI-friendly limit
  
  while (pageCount < maxPages) {
    const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'access-token': accessToken
      },
      body: JSON.stringify({
        query: `query GetLeaderboard($spaceId: Int!, $cursorAfter: String) {
          space(id: $spaceId) {
            loyaltyPointsRanks(cursorAfter: $cursorAfter) {
              pageInfo { hasNextPage endCursor }
              list { rank points address { address username } }
            }
          }
        }`,
        variables: { spaceId, cursorAfter }
      })
    });
    
    const data = await response.json();
    const ranks = data.data.space.loyaltyPointsRanks;
    
    // Search current page
    const userRank = ranks.list.find(item => 
      item.address.address.toLowerCase() === userAddress.toLowerCase()
    );
    
    if (userRank) {
      return {
        found: true,
        rank: userRank.rank,
        points: userRank.points,
        username: userRank.address.username,
        searchPages: pageCount + 1
      };
    }
    
    if (!ranks.pageInfo.hasNextPage) break;
    
    cursorAfter = ranks.pageInfo.endCursor;
    pageCount++;
    
    // Rate limiting
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  
  return { found: false, searchPages: pageCount };
}

Common Integration Patterns

Top N Rankings

// Get top 10 users
async function getTopRankings(spaceId, count = 10, accessToken) {
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': accessToken
    },
    body: JSON.stringify({
      query: `query GetTopRankings($spaceId: Int!) {
        space(id: $spaceId) {
          name
          loyaltyPointsRanks(cursorAfter: null) {
            totalCount
            list { rank points address { username avatar } }
          }
        }
      }`,
      variables: { spaceId }
    })
  });
  
  const data = await response.json();
  const leaderboard = data.data.space.loyaltyPointsRanks;
  
  return {
    spaceName: data.data.space.name,
    totalParticipants: leaderboard.totalCount,
    topUsers: leaderboard.list.slice(0, count)
  };
}

Sprint vs Overall Comparison

async function compareRankings(spaceId, sprintId, accessToken) {
  const [overall, sprint] = await Promise.all([
    getTopRankings(spaceId, 10, accessToken),
    getSprintRankings(spaceId, sprintId, 10, accessToken)
  ]);
  
  return {
    overall: overall.topUsers,
    sprint: sprint.topUsers,
    comparison: {
      overallParticipants: overall.totalParticipants,
      sprintParticipants: sprint.totalParticipants,
      participationRate: sprint.totalParticipants / overall.totalParticipants
    }
  };
}

async function getSprintRankings(spaceId, sprintId, count, accessToken) {
  // Similar to getTopRankings but with sprintId parameter
  // Implementation details follow the same pattern
}

Best Practices

  1. Pagination: Use cursor-based pagination (cursorAfter/cursorBefore)
  2. Rate Limiting: Add 200-500ms delays between pagination requests
  3. Caching: Cache leaderboard data for 30-60 seconds
  4. Search Limits: Limit user searches to reasonable page counts (20-50 pages)
  5. Error Handling: Handle cases where users are not found in rankings
  6. Sprint Context: Use sprintId for time-limited competitions

Pagination Parameters

  • cursorAfter: Get items after this cursor
  • cursorBefore: Get items before this cursor
  • sprintId: Optional sprint ID for sprint-specific rankings

Deprecated (Avoid)

  • first: Use cursor-based pagination instead
  • after: Use cursor-based pagination instead

Limitations & Workarounds

Current Limitations

  1. No Direct User Lookup: Must search through pages
  2. No Sprint Management: Sprint IDs from external sources
  3. Search Performance: Finding users requires multiple API calls
  1. Efficient Search: Limit search scope for UI responsiveness
  2. Caching Strategy: Cache user positions for short periods
  3. User Communication: Set expectations about search time
  4. Fallback UI: Show leaderboard context even when user isn’t found

Usage Examples

Basic Leaderboard Display

// Get and display top 10 rankings
const topRankings = await getTopRankings(40, 10, accessToken);

console.log(`${topRankings.spaceName} Leaderboard`);
console.log(`Total participants: ${topRankings.totalParticipants}`);
console.log('\nTop 10:');
topRankings.topUsers.forEach(user => {
  console.log(`${user.rank}. ${user.address.username} - ${user.points} points`);
});

User Position Check

// Find specific user's position
const userAddress = '0x1234567890123456789012345678901234567890';
const position = await findUserPosition(40, userAddress, accessToken);

if (position.found) {
  console.log(`User rank: #${position.rank}`);
  console.log(`User points: ${position.points}`);
  console.log(`Search completed in ${position.searchPages} pages`);
} else {
  console.log(`User not found in top ${position.searchPages * 50} participants`);
}

Next Steps