Building Landing Pages
Display leaderboards, offer info, and claim status for your incentive programs
How leaderboards, claims, epochs, and results connect, and how to build a landing page that displays incentive data and enables users to claim rewards.
Domain Model
Project
└── Recurring Incentive (leaderboard | rebate | raffle | direct)
└── Epoch (evaluation period)
├── Status lifecycle: UPCOMING → EVALUATING → CLAIMING → COMPLETED (or FAILED as terminal)
├── Query → Results (ranked wallets + metric values)
└── Offer (on-chain, per-epoch reward distribution)Key concepts:
- A project has one or more recurring incentives.
- Each incentive runs in epochs — time-bounded evaluation periods.
- Epoch status lifecycle: UPCOMING → EVALUATING → CLAIMING → COMPLETED (or FAILED as terminal).
- During EVALUATING, the query runs and produces results — ranked wallets with metric values (this is the leaderboard data).
- During CLAIMING, eligible users can claim rewards via an API call with their wallet pubkey.
- Each epoch has an associated offer that handles on-chain reward distribution. The offer is distinct from the top-level recurring incentive that owns the epochs.
- Use
get_recurring_incentiveto inspect epoch details: status, eval/claim windows, offer IDs, query IDs.
Displaying Results & Scores
Via MCP Tools (authenticated, for builders)
| Tool | Mode | Use Case |
|---|---|---|
get_epoch_leaderboard | preview | Leaderboard scores/rankings — ranked wallets and metric values from the query |
get_epoch_leaderboard | recipients | Allocations and payouts — wallet addresses, amounts, and distribution status (DISTRIBUTED/PENDING/FAILED) |
get_epoch_leaderboard | download | Full CSV download/export URL (signed S3, expires 5 min) for the complete result set |
Via REST API (public, for frontends)
Latest evaluation results (live scores):
GET /project/:projectId/recurring-offer/:id/latest-eval-results?limit=10&offset=0- Fully public, no auth required.
- Returns ranked wallets with metric values from the most recent evaluation.
- Use this for live leaderboard displays.
Recipients data (allocations and payout status) requires project authentication and should be fetched from
your backend using the MCP get_epoch_leaderboard tool with mode: "recipients".
Epoch-Level Data (for dashboards & analytics)
Each recurring incentive runs in epochs — time-bounded evaluation periods. Each epoch carries its own configuration, which can change between epochs. Use get_recurring_incentive to access the configs array.
Distribution config (per epoch)
| Field | Description |
|---|---|
distributionType | FORMULA (leaderboard, rebate, direct) or RAFFLE (raffle). Legacy values DIRECT, MANUAL, CSV may appear on older projects but are not written by the MCP. |
emissionType | TOKENS or SOL. POINTS exists in the DB enum for non-MCP paths but create_recurring_incentive rejects it. |
tokenAddress | Token mint address (when emissionType is TOKENS) |
tokenDecimals | Token decimal places (when emissionType is TOKENS) |
totalFundAmount | Total reward budget for the epoch |
customFormula | Payout formula (for FORMULA distribution) |
prizeBuckets | Array of {amount, count} prize tiers (when distributionType is RAFFLE) |
selectionLogic | WEIGHTED_BY_METRIC or EQUAL_CHANCES (when distributionType is RAFFLE) |
maxPerParticipant | Per-user reward cap (if set) |
claimWindowDuration | Claim period duration in seconds |
distributionMethod | AIRDROP or CLAIM |
MCP input vs. persisted config names: create_recurring_incentive accepts raffleBuckets and
raffleWeighting. Once persisted on the epoch config, they appear as prizeBuckets and selectionLogic.
If you're reading analytics or epoch configs, expect the persisted names.
Offer metadata (per epoch)
| Field | Description |
|---|---|
title | Incentive display title |
description | Optional description |
image | Optional image URL |
Epoch timing
| Field | Description |
|---|---|
evalStart / evalEnd | Evaluation window — when user activity is tracked |
claimStart / claimEnd | Claim window — when users can claim rewards |
status | UPCOMING, EVALUATING, CLAIMING, COMPLETED, or FAILED |
Historical data across epochs
Distribution configs and metadata can change epoch-to-epoch (e.g., raising the reward pool, adjusting the formula). This means builders can:
- Show reward history across epochs (how the incentive evolved).
- Display per-epoch analytics (participants, total distributed, rewards per wallet).
- Compare configs between epochs to highlight changes.
- Build dashboards showing incentive performance over time.
Use get_epoch_leaderboard with a specific epochConfigId to fetch results or recipients for any historical epoch, not just the latest.
Displaying Offer Info
Via REST API (for frontends)
GET /claim/details/byOffer?projectId=X&offerStatus=ACTIVE- Works without auth for non-gated offers.
- Add
&wallet=<userPubkey>to also get:isEligible— whether the wallet qualifies.eligibleAmounts— reward amounts the wallet can claim.
Displaying Claim Status (Per User)
GET /claim/details/byAllocation?projectId=X&wallet=<userPubkey>- Works without auth for non-gated offers.
- Returns per-allocation details:
| Field | Description |
|---|---|
offerId | The offer this allocation belongs to |
amount | Reward amount |
token | Reward token address |
availableAt | When the claim becomes available |
crank | Nested object { id, status, signature } (or null if not yet queued). status follows the lifecycle below; signature is the Solana transaction signature, populated when status is DONE. |
Crank status lifecycle (crank.status):
STAGED → STARTED → PENDING → DONE (or FAILED, or INVALID)- STAGED — Claim queued for processing.
- STARTED — Processing has begun.
- PENDING — Transaction submitted, waiting for confirmation.
- DONE — Complete;
crank.signatureis the Solana transaction signature — link to Solana explorer. - FAILED — Processing failed; may be retried.
- INVALID — Allocation is permanently ineligible and will not be retried.
Inspect the live API response for the current crank-status values rather than hard-coding them — the exact status set may evolve.
Poll this endpoint for real-time progress updates on the user's claims.
Enabling Claims
Claims are fully server-side — no transaction signing is required from the user's wallet.
Connect wallet
Use a wallet adapter (e.g. @solana/wallet-adapter-react) to get the user's pubkey.
Trigger claims
GET /claim?projectId=X&wallet=<userPubkey>This queues all eligible claims for the wallet. Multiple claims may be available if the user qualifies for more than one allocation.
Poll for status
GET /claim/details/byAllocation?projectId=X&wallet=<userPubkey>Watch crank.status progress from STAGED through DONE.
Auth Considerations
| Endpoint Category | Auth Required | Safe For |
|---|---|---|
Latest eval results (/project/:projectId/recurring-offer/:id/latest-eval-results) | None | Client-side, fully public |
Claim trigger (/claim) | Optional API key | Client-side for non-gated |
Claim status (/claim/details/byAllocation) | Optional API key | Client-side for non-gated |
Offer details (/claim/details/byOffer) | Optional API key | Client-side for non-gated |
Incentive results (get_epoch_leaderboard) | Project auth | Backend only |
Recipients / payouts (get_epoch_leaderboard recipients mode) | Project auth | Backend only |
Epoch data (get_recurring_incentive) | Project auth | Backend only |
| Query downloads | Project auth | Backend only |
Rule of thumb: If it is user-facing display data (eval results, stats, claim status), it is public. If it is project management data (epoch configs, recipient allocations, raw query downloads), it requires project authentication and should only be called from your backend.
Recommended Architecture
┌─────────────────────────────────────────────────┐
│ FRONTEND │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Wallet │ │ Public API Calls │ │
│ │ Connect │ │ │ │
│ │ (get pubkey) │ │ • GET /project/:pid/ │ │
│ │ │ │ recurring-offer/:id/ │ │
│ │ │ │ latest-eval-results │ │
│ └──────┬───────┘ │ │ │
│ │ │ │ │
│ │ │ • GET /claim │ │
│ │ │ • GET /claim/details/... │ │
│ │ │ • GET /claim/details/ │ │
│ │ │ byOffer │ │
│ │ └─────────────────────────┘ │
│ │ │
│ └─── pubkey used for claim + status ──► │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ BACKEND │
│ │
│ ┌─────────────────────────────────────────────┐│
│ │ Authenticated Calls (project auth / API key) ││
│ │ ││
│ │ • get_recurring_incentive (epoch details) ││
│ │ • get_epoch_leaderboard (eval results, CSV) ││
│ │ • Gated offer details ││
│ └─────────────────────────────────────────────┘│
└─────────────────────────────────────────────────┘Frontend handles wallet connection and all public endpoints — eval results display, offer info, claim triggering, and claim status polling. No secrets needed.
Backend handles authenticated calls for epoch management, raw evaluation results, and any API-key-gated offer queries. Keep project auth tokens and API keys server-side only.