useRewards
The four Flare reward kinds tracked as four independent claims, each with its own operation, its own confirmation and its own reason for being empty.
import { useRewards } from '@flarekit-dev/react'
Flare pays four different rewards, and they are not variants of one thing. An
FTSO delegation reward needs a Merkle proof and expires; an rNat claim belongs to
a project and its withdrawal burns half of what is still locked; FlareDrop
concluded on 2026-01-30; the staking reward never expires at all. useRewards
keeps them apart — three independent operations plus one shared claimable
position — so a settled FTSO claim can never move the rNat one, and an empty read
always says which emptiness it is.
Each kind decides "did this land" differently, so each kind's reconcile closure
belongs to the host. The claimable position is polled separately, because a
no-entitlement, proof-unavailable or concluded read has no operation to
advance.
Live#
The readout below is the hook's actual return value, running against the mock
rewards adapter on this render. Every empty value in it is a real observation
from the live Coston2 keyless read pass of 2026-08-12: the FTSO list is empty
because the account earned nothing, rnat reports no project because
getBalancesOf reverted with "no RNat account", flaredrop is concluded because
getClaimableMonths reverted with "already finished", and the staking reward is
a genuine on-chain (0, 0). None of those is a zero typed into a fixture.
buildPlan refuses with not-verified, which is also true rather than staged:
no reward claim has ever settled live on this deployment, so rewardsVerified
is still false and the kit will not emit a signable claim. fetchProof asks
the unofficial mirror for the one epoch the live pass captured and gets
proof-unavailable back — that epoch's proof belongs to a different
beneficiary, and this account has no claim in it.
// reads on mountRead from the running hook against the mock kit, on this render.
import { createMockRewardsAdapter } from '@flarekit-dev/core'
import { useRewards } from '@flarekit-dev/react'
import { ClaimCard } from '@flarekit-dev/react-ui'
const adapter = createMockRewardsAdapter() // live: makeRewardsAdapter(client, rewardsFor('coston2'), fetch)
const reconcile = (owner) => adapter.read(owner) // keyless: the read holds no key
export function Rewards({ account }) {
const { reads, ftso, rnat, flaredrop, buildPlan, submit } = useRewards({
account,
adapter,
reconcile,
})
const planResult = buildPlan({ kind: 'ftso-delegation', recipient: account, wrap: false })
// The three kinds are tracked independently — ftso settling never moves rnat.
return (
<ClaimCard
kind="ftso-delegation"
reads={reads}
operation={ftso.operation}
planResult={planResult}
onSubmit={() => planResult?.kind === 'plan' && submit(planResult.plan)}
/>
)
}Usage#
import { makeRewardsAdapter, type ClaimIntent } from '@flarekit-dev/core'
import { rewardsFor } from '@flarekit-dev/contracts'
import { useRewards } from '@flarekit-dev/react'
import { ClaimCard } from '@flarekit-dev/react-ui'
const adapter = makeRewardsAdapter(publicClient, rewardsFor('coston2'), fetch)
const reconcile = (owner: `0x${string}`) => adapter.read(owner)
function Rewards({ account }: { account: `0x${string}` }) {
const { reads, ftso, rnat, flaredrop, buildPlan, submit } = useRewards({
account,
adapter,
reconcile,
onSubmit: sendWithWallet,
// Each kind's own confirmation check, owned by the host.
ftso: { operation: ftsoOp, reconcile: confirmFtsoClaim },
})
const intent: ClaimIntent = { kind: 'ftso-delegation', recipient: account, wrap: false }
const planResult = buildPlan(intent)
return (
<ClaimCard
kind="ftso-delegation"
reads={reads}
operation={ftso.operation}
planResult={planResult}
onSubmit={() => planResult?.kind === 'plan' && submit(planResult.plan)}
/>
)
}reconcile is an effect dependency, so hold it steady — a closure rebuilt on
every render restarts the poll on every render. adapter only shapes the next
buildPlan, so its identity does not gate the poll.
Parameters#
| Prop | Type | Default | Description |
|---|---|---|---|
| accountrequired | 0x${string} | undefined | — | The account read and planned for. Without it the hook does not poll. |
| adapterrequired | RewardsAdapter | undefined | — | The reads, proof-fetch and call-builder seam. Carries the deployment, whose rewardsVerified flag is the first gate buildPlan runs. |
| reconcile | (account: 0x${string}) => Promise<RewardsReads> | — | Re-reads the claimable position across all four kinds. Read-only and keyless. |
| onSubmit | (plan: RewardsClaimPlan) => Promise<RewardsOperation> | — | Executes the plan through the host’s own wallet. Absent means submit is a no-op returning undefined. |
| pollMs | number | 15_000 | Poll cadence in milliseconds, shared by the position read and all three kind polls. The host owns the clock. |
| ftso | { operation?: RewardsOperation; reconcile?: (op) => Promise<RewardsOperation> } | — | The FTSO claim’s own operation and its own confirmation closure. |
| rnat | { operation?: RewardsOperation; reconcile?: (op) => Promise<RewardsOperation> } | — | The rNat claim’s own operation and confirmation — a decreased locked balance, not a claimed reward. |
| flaredrop | { operation?: RewardsOperation; reconcile?: (op) => Promise<RewardsOperation> } | — | The FlareDrop claim’s own operation and confirmation — a month leaving claimableMonths. |
Return type#
| Prop | Type | Default | Description |
|---|---|---|---|
| reads | RewardsReads | undefined | — | The claimable position: currentRewardEpoch, claimableEpochs, expireNextEpoch, ftso, rnat, flaredrop and staking. undefined until the first read lands. |
| error | SerializedError | undefined | — | The last failed position reading or wallet submit. It never fabricates an empty read, and a later successful poll clears it — a submit failure is visible for at most one poll interval. |
| ftso | ClaimKindResult | — | The FTSO claim: its operation, isSettled, and its own read error. |
| rnat | ClaimKindResult | — | The rNat claim, tracked independently of the other three. |
| flaredrop | ClaimKindResult | — | The FlareDrop claim, tracked independently of the other three. |
| buildPlan | (intent: ClaimIntent) => ClaimPlanResult | undefined | — | Pure and synchronous once a read has landed; undefined before that. |
| submit | (plan: RewardsClaimPlan) => Promise<RewardsOperation | undefined> | — | Forwards the plan to onSubmit. Returns undefined when no onSubmit was given or the wallet threw. |
| fetchProof | (epoch: number) => Promise<FtsoProofResult> | — | Asks the configured mirror for one epoch’s Merkle proof. Returns { status: available, amount, claimType, proof } or the declared { status: proof-unavailable } — never an amount conjured from an absent proof. |
States#
- the four kinds —
ftso-delegation,rnat,flaredropandstakingare distinct and never collapsed into a generic "claim". Each carries its own facts and its own reason for refusing. not-verified— the deployment has no live claim behind it yet, so no signable plan is produced for any kind. This gate runs before every other check.no-entitlement— a real read that found nothing owed: an empty FTSO list, an account with no rNat project, or a staking reward whosetotalequals itsclaimed.proof-unavailable— an FTSO epoch is entitled but its Merkle proof is not available from the mirror. The claim is refused rather than submitted without a proof, and the epoch is named.concluded— FlareDrop ended on 2026-01-30. It is a finished programme, not a temporary emptiness, and it reads differently fromno-entitlement.- expiry — FTSO rewards expire.
reads.expireNextEpochis the next epoch to fall out ofreads.claimableEpochs; on the read behind the demo above, 28 epochs were claimable and epoch 5902 was next to expire. The staking reward is the one kind that does not expire —expiresisfalseon it, always. - in flight — each kind's operation reaches
succeededonly when that kind's ownreconcileasserts a confirmation. A read error on one kind never moves another.
Mock to live#
The hook takes the adapter as a parameter, so the swap is the adapter alone:
// Mock: the observed Coston2 keyless read pass, no network.
const adapter = createMockRewardsAdapter()
// Live: the real adapter over your own viem client and fetch.
const adapter = makeRewardsAdapter(publicClient, rewardsFor('coston2'), fetch)Both drive the same buildRewardsClaimPlan, reconcileClaim and fetchFtsoProof.
The mock reproduces the two observed contract reverts as real thrown errors, so
the adapter's own revert handling runs rather than being bypassed, and it
refuses accounts and networks the live pass never drove.
What it will not do#
It will not collapse the kinds. There is no combined "claim everything" call here, because the four claims settle on different contracts, confirm in different ways, and fail for different reasons.
It will not claim an FTSO reward without a proof, and it will not present the
mirror as official. The proof source is an unofficial mirror
(official: false), and a surface rendering these values is expected to say so.
It will not hide the rNat burn. withdrawAll destroys 50% of the still-locked
balance; that is disclosed as part of the plan, before anything is signed.
It will not sign, and it will not mark a claim succeeded on submission. Only
the host's per-kind confirmation does that.