Skip to main content
The rewards view users see on Rabbithole, inside your app. It lists every campaign the connected wallet has a position in, shows lifetime earnings next to the claimable amount, and puts a Claim Button on each row. This page assumes the client and helpers from Project Setup.

Two Numbers per Row

The two differ by design, and the gap is normal. Show earned as the number that moves, and claimable as the number the button acts on. The full explanation is in How Claiming Works.

The Component

components/rewards-panel.tsx
Only campaigns the wallet has already entered appear. To show campaigns the user could join but has not, render the Opportunities List alongside.

Why the Extra Lookup

rewards.forUser returns amounts and the reward token, but not the target being rewarded, so the row cannot label itself. The useCampaign hook fetches that once per campaign and caches it for the session. If you already know your campaign IDs, replace the lookup with your own map from ID to pool name.

States to Render

The panel above collapses these into “Accruing” and an amount. A production panel usually spells them out:
Do not gate the button on campaign status. Accrual stops when a campaign ends, but published rewards stay claimable, and finalized means everything earned is claimable. Gate on the amount.
The claim window comes from the campaign object fetched by useCampaign. See Claim Windows.

Totals Across Campaigns

Campaigns pay in different tokens, so there is no single “total claimable” to sum. Group by rewardToken.address if you want per-token totals, and use the Claim All button to settle every campaign on one chain in a single transaction.

Keep Exploring

Claim Button

The button each row renders.

Show User Rewards

The endpoints behind the panel and the formatting trap.

How Claiming Works

Why earned and claimable move on different clocks.