Claiming rewards
This guide shows how to claim Flare rewards directly via smart contracts, using the published per-epoch Merkle distributions.
You will:
- read the claimable reward epoch range onchain,
- fetch the distribution tuples JSON offchain,
- extract your Merkle proof + claim tuple,
- submit
RewardManager.claim(...), - claim staking rewards
ValidatorRewardManager.claim(...).
Prerequisites
- Wallet + gas: A wallet that can sign transactions on the target network, funded with enough native token to pay gas.
- RPC access: An RPC endpoint for the chosen network (you can use any RPC listed on the Network page).
- Contracts: Addresses can be found in the Flare Contract Registry:
- Recipient address: The address that will receive the rewards (
recipient). - Reward distribution data access: You must be able to fetch per-epoch
reward-distribution-data-tuples.jsonfrom the official distribution locations on GitHub or GitLab. - Wrap option:
wrapRewardsistrueunless explicitly set tofalse, i.e. on Flare Mainnet you will receiveWFLR. - Beneficiary address (depends on claim type):
Claim type claimTypebeneficiarymust beAccount that must authorize executors DIRECT0signing policy address signing policy address FEE1identity address identity address
Recommended: Configure a claim executor and claim recipient
For improved security, set up a claim executor (an account authorized to claim on your behalf) and an allowed claim recipient (an address permitted to receive rewards).
- Create and fund a new account with sufficient native tokens
- Authorize this account as a claim executor, which can be done using the
setClaimExecutorsmethod.- For DIRECT (0) claims: Use your signing policy account to authorize the executor.
- For FEE (1) claims: Use your identity account to authorize the executor.
- Setup a Claim Recipient account, this can be done through the
setAllowedClaimRecipientmethod.
import { Contract } from "ethers";
const claimSetupManager = new Contract(
process.env.CLAIM_SETUP_MANAGER_ADDRESS!, // from contract registry
CLAIM_SETUP_MANAGER_ABI,
beneficiarySigner, // IMPORTANT: signer must be the signing policy address (`DIRECT`) or identity address (`FEE`)
);
// 1) Authorize the executor that will submit RewardManager.claim(...)
await (await claimSetupManager.setClaimExecutors([executorAddress])).wait();
// 2) Allowlist a recipient address that is permitted to receive rewards
await (
await claimSetupManager.setAllowedClaimRecipients([recipientAddress])
).wait();
Step-by-step
1. Connect to the contracts
Instantiate contract clients (ABI + address) for:
Example using ethers v6:
import { JsonRpcProvider, Wallet, Contract } from "ethers";
// 1) Provider + signer
const provider = new JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.CLAIM_EXECUTOR_PRIVATE_KEY!, provider);
// 2) Contract instances (ABI omitted here; use your generated ABI or interface)
const flareSystemsManager = new Contract(
process.env.FLARE_SYSTEMS_MANAGER_ADDRESS!,
FLARE_SYSTEMS_MANAGER_ABI,
provider,
);
const rewardManager = new Contract(
process.env.REWARD_MANAGER_ADDRESS!,
REWARD_MANAGER_ABI,
provider,
);
2. Read the claimable epoch range
Call:
startRewardEpochId = RewardManager.getNextClaimableRewardEpochId(beneficiary)[_, endRewardEpochId] = RewardManager.getRewardEpochIdsWithClaimableRewards()
If endRewardEpochId < startRewardEpochId, there is nothing to claim.
Example:
const start = await rewardManager.getNextClaimableRewardEpochId(beneficiary);
const [, end] = await rewardManager.getRewardEpochIdsWithClaimableRewards();
if (end < start) {
console.log("Nothing claimable for this beneficiary.");
}
3. Keep only signed epochs
For each epochId in [startRewardEpochId … endRewardEpochId], call:
rewardsHash = FlareSystemsManager.rewardsHash(epochId)
Only proceed if rewardsHash is not 0x000...000 (bytes32(0)).
Example:
const ZERO_BYTES32 =
"0x0000000000000000000000000000000000000000000000000000000000000000";
const signedEpochs: bigint[] = [];
for (let epochId = start; epochId <= end; epochId++) {
const rewardsHash = await flareSystemsManager.rewardsHash(epochId);
if (rewardsHash && rewardsHash !== ZERO_BYTES32) signedEpochs.push(epochId);
}
4. Fetch reward distribution tuples
All rewards scripts are publicly available, and you are encouraged to calculate/verify the rewards data yourself. The data for the rewards is published on flare-foundation/fsp-rewards and the instructions for how to calculate them are here.
For each signed epoch, fetch reward-distribution-data-tuples.json.
You fetch one JSON file per epoch, using the epoch id in the path.
If the JSON cannot be fetched or validated, you cannot build a valid Merkle proof for that epoch.
- Flare Mainnet
- Flare Testnet Coston2
- Songbird Canary-Network
- Songbird Testnet Coston
Fetch reward-distribution-data-tuples.json from flare-foundation/fsp-rewards on GitHub.
EPOCH_ID=123
BENEFICIARY="0xYourBeneficiaryAddressHere"
CLAIM_TYPE=0
curl -fsSL \
"https://raw.githubusercontent.com/flare-foundation/fsp-rewards/refs/heads/main/flare/${EPOCH_ID}/reward-distribution-data-tuples.json" \
| jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r '
.rewardClaims[]
| select(.[1][1] | ascii_downcase == ($b | ascii_downcase))
| select(.[1][3] == $ct)
'
Fetch reward-distribution-data-tuples.json from timivesel/ftsov2-testnet-rewards on GitLab.
EPOCH_ID=123
BENEFICIARY="0xYourBeneficiaryAddressHere"
CLAIM_TYPE=0
curl -fsSL \
"https://gitlab.com/timivesel/ftsov2-testnet-rewards/-/raw/main/rewards-data/coston2/${EPOCH_ID}/reward-distribution-data-tuples.json" \
| jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r '
.rewardClaims[]
| select(.[1][1] | ascii_downcase == ($b | ascii_downcase))
| select(.[1][3] == $ct)
'
Fetch reward-distribution-data-tuples.json from flare-foundation/fsp-rewards on GitHub.
EPOCH_ID=123
BENEFICIARY="0xYourBeneficiaryAddressHere"
CLAIM_TYPE=0
curl -fsSL \
"https://raw.githubusercontent.com/flare-foundation/fsp-rewards/refs/heads/main/songbird/${EPOCH_ID}/reward-distribution-data-tuples.json" \
| jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r '
.rewardClaims[]
| select(.[1][1] | ascii_downcase == ($b | ascii_downcase))
| select(.[1][3] == $ct)
'
Fetch reward-distribution-data-tuples.json from timivesel/ftsov2-testnet-rewards on GitLab.
EPOCH_ID=123
BENEFICIARY="0xYourBeneficiaryAddressHere"
CLAIM_TYPE=0
curl -fsSL \
"https://gitlab.com/timivesel/ftsov2-testnet-rewards/-/raw/main/rewards-data/coston/${EPOCH_ID}/reward-distribution-data-tuples.json" \
| jq --arg b "${BENEFICIARY}" --argjson ct "${CLAIM_TYPE}" -r '
.rewardClaims[]
| select(.[1][1] | ascii_downcase == ($b | ascii_downcase))
| select(.[1][3] == $ct)
'
5. Extract your Merkle proof + claim tuple
In the fetched JSON, locate an entry in rewardClaims where:
addressmatches yourbeneficiary(case-insensitive match offchain, exact value used onchain)claimTypeequals your targetclaimType(0for DIRECT,1for FEE)
Each matching entry provides:
merkleProof: array of hex strings- tuple
[id, address, sum, claimType]
You do not submit the JSON onchain.
You only extract your merkleProof and tuple from rewardClaims to build the claims[] argument passed to RewardManager.claim(...).
Build a RewardClaimWithProof struct from the JSON:
{
merkleProof: string[],
body: {
rewardEpochId: bigint, // BigInt(id)
beneficiary: string, // address
amount: bigint, // BigInt(sum)
claimType: bigint // BigInt(claimType)
}
}
Important: rewardEpochId is taken from the tuple's id.
Example:
type ClaimType = 0 | 1; // DIRECT=0, FEE=1
type RewardClaimTuple = [number, string, string, number]; // [id, address, sum, claimType]
type RewardClaimEntry = [string[], RewardClaimTuple]; // [merkleProof[], tuple]
function extractClaim(
rewardClaims: RewardClaimEntry[],
beneficiary: string,
claimType: ClaimType,
) {
const entry = rewardClaims.find(([, tuple]) => {
const [, address, , ct] = tuple;
return (
address.toLowerCase() === beneficiary.toLowerCase() && ct === claimType
);
});
if (!entry) return null;
const [merkleProof, [id, address, sum, ct]] = entry;
return {
merkleProof,
body: {
rewardEpochId: BigInt(id),
beneficiary: address,
amount: BigInt(sum),
claimType: BigInt(ct),
},
};
}
6. Submit the claim transaction
Call:
RewardManager.claim(beneficiary, recipient, lastEpochIdToClaim, wrapRewards, claims)
Where:
beneficiary: signing policy address (DIRECT) or identity address (FEE)recipient: the address that should receive rewardsclaims: array of the structs from Step 5lastEpochIdToClaim: set to the maximumbody.rewardEpochIdincluded inclaimswrapRewards: boolean (trueunless explicitly set tofalse)
Example:
if (claims.length === 0) throw new Error("No claims to submit.");
const lastEpochIdToClaim = claims
.map((c) => c.body.rewardEpochId)
.reduce((max, v) => (v > max ? v : max));
const tx = await rewardManager
.connect(signer)
.claim(beneficiary, recipient, lastEpochIdToClaim, wrapRewards, claims);
console.log("Submitted:", tx.hash);
await tx.wait();
console.log("Confirmed:", tx.hash);
7. Claim staking rewards
You can also claim staking rewards via the Flare Portal or using the flare-tx-sdk.
Instantiate contract clients (ABI + address) for:
import { JsonRpcProvider, Wallet, Contract } from "ethers";
// 1) Provider + signer
const provider = new JsonRpcProvider(process.env.RPC_URL);
// Signer must be a self-bond address or an address used for delegating stake.
const stakingSigner = new Wallet(process.env.STAKING_PRIVATE_KEY!, provider);
// 2) Contract instances (ABI omitted here; use your generated ABI or interface)
const validatorRewardManager = new Contract(
process.env.VALIDATOR_REWARD_MANAGER_ADDRESS!,
VALIDATOR_REWARD_MANAGER_ABI,
provider,
);
Call:
ValidatorRewardManager.claim(rewardOwner, recipient, rewardAmount, wrapRewards)
Where:
rewardOwner: self-bond address or address used for delegating stakerecipient: the address that should receive rewardsrewardAmount: amount of rewards to be claimedwrapRewards: boolean (trueunless explicitly set tofalse)
Example:
const state = await validatorRewardManager.getStateOfRewards(rewardOwner);
let rewardAmount = state[0] - state[1];
const tx = await validatorRewardManager
.connect(stakingSigner)
.claim(rewardOwner, recipient, rewardAmount, wrapRewards);
console.log("Submitted:", tx.hash);
await tx.wait();
console.log("Confirmed:", tx.hash);
Troubleshooting
T0. Authorization failure: executor not allowed or recipient not allowlisted
- If the signer sending
RewardManager.claim(...)is not the beneficiary, ensure it is configured as a claim executor viasetClaimExecutors(...). - If the transaction reverts when using a
recipientaddress, ensure the recipient is allowlisted viasetAllowedClaimRecipients(...). - Ensure you set these using the correct controlling account:
- DIRECT: signing policy account
- FEE: identity account
T1. No matching tuple found for my address
- Double-check:
- Beneficiary address selection (DIRECT uses signing policy address; FEE uses identity address).
claimTypevalue (DIRECT=0, FEE=1).
T2. Claim transaction reverts
- Ensure the Merkle proof and tuple fields (
rewardEpochId,beneficiary,amount,claimType) are copied exactly from the JSON. - Ensure
lastEpochIdToClaimis>=the maximumrewardEpochIdincluded in yourclaimsarray. - Ensure the epoch is signed (non-zero
rewardsHash) and within the claimable range you read onchain.
If a revert reason indicates additional authorization rules (e.g., the caller must be a specific executor), those rules are enforced by the contract and must be satisfied as written in the revert reason; they are not inferable from the claim flow alone.