Skip to main content
A rewards panel needs two numbers from two different endpoints, and mixing them up is the most expensive mistake in this integration. This guide covers which is which, every state you need to render, and the formatting trap that makes valid rewards look like zero.

Two Numbers, Two Endpoints

Only claims.get() may drive a claim button. accumulatedRewards tracks live accrual and always runs ahead of what a published merkle root actually covers. A button wired to it over-promises, and the transaction reverts with InvalidProof().
The two numbers differing is normal, not a bug. See How Claiming Works. Show the earned figure as the one that moves; show claimable on the button.

Read Lifetime Earnings

Only campaigns the user has already entered appear here. To surface campaigns they could earn from but have not joined, call campaigns.list with userAddress instead.

Read the Claimable Amount

A 404 here is usually a normal state, not an error. It means either the address has no position in that campaign, or no published root covers its rewards yet. Catch TbiNotFoundError and render an accruing state; surfacing it as a failure produces support tickets for a system working correctly.There is one exception. The endpoint also returns 404 once the claim window has closed, including for users who never claimed. Do not show an accruing state there. Gate the copy on the campaign schedule, not on the 404 alone.

Check Many Campaigns at Once

When you are rendering a list, one request per campaign is wasteful. claims.statuses batches up to 100 campaign IDs:
Campaigns without a published root report claimable: 0n rather than erroring, so there is no 404 to handle on this path.

UI States

Five states cover everything a user can be in:
Note what is not in that condition: campaign status. Ended campaigns stay claimable, and finalized means everything earned is claimable. Gate on the amount.

Formatting Amounts

The < 1 branch is load-bearing. A position opened in the last few hours can hold a reward of around 0.00003 tokens, and a fixed four-decimal format renders that as 0.0000, a valid claimable reward that reads as empty. Switch to significant digits below 1, and keep raw units available in your debug tooling.
Every amount is a bigint, so JSON.stringify throws on these objects. See Data Conventions for the logging and persistence serializers.

Keep Exploring

Claim Rewards

Turn a claimable amount into a confirmed transaction.

How Claiming Works

Why earned and claimable move on different clocks.