User rewards
Display claimable rewards for users, build claim transactions, and surface earning history
Merkl exposes a family of /v4/users/{address}/rewards/* endpoints to read a user's reward state, build claim transactions, and surface where rewards were earned. This page is organized by use case — pick the endpoint that matches what you need to render.
Concepts: amount, pending, claimed, proofs
Every reward endpoint speaks the same vocabulary. These are the fields you will see on tokens:
amount— Cumulative reward credited to the user in the live Merkle tree (includes anything already claimed).claimed— Cumulative amount the user has already pulled onchain.pending— Earned but not yet in any Merkle root. Updates more frequently (~every 2 hours) thanamount. Surface it separately — it is not claimable.proofs— Merkle proofs required by theDistributorcontract to claim the currentamount. Pass them as-is.
The claimable amount is amount - claimed.
Whenever a new Merkle root is pushed onchain, pending resets to zero because those rewards are now folded into amount. Never sum amount + pending and display it as claimable.
Cache bypass: reloadChainId
After a user claims, Merkl needs to index the claim transaction onchain before claimed reflects the new onchain balance. Indexing typically takes up to ~5 minutes, so during that window the cached response keeps showing the old claimed value.
Pass reloadChainId={chainId} to force a fresh read for that chain and reconcile against the onchain state immediately. Supported on /rewards/summary, /rewards/breakdowns, and the legacy /v4/users/{address}/rewards. Use it only when needed — every call with reloadChainId bypasses the cache and is more expensive on the server.
GET https://api.merkl.xyz/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/summary?reloadChainId=324
Pick the right endpoint
| Use case | Endpoint | Notes |
|---|---|---|
| Build a claim transaction | GET /v4/users/{address}/rewards/summary | Recommended, fast |
| Show USD totals | GET /v4/users/{address}/rewards/stats | Lightweight USD totals |
| Show full reward breakdowns (small users) | GET /v4/users/{address}/rewards/breakdowns | Capped at ~1000 breakdowns |
| Show full reward breakdowns (any user, paginated) | GET /v4/leaves/{recipient}/breakdowns | Paginated, scales further |
| List opportunities a user earned on (per chain) | GET /v4/users/{address}/rewards/chains/{chainId}/breakdowns | Per-opportunity, single chain |
| List opportunities a user is currently earning on | GET /v4/users/{address}/rewards/active-opportunities | LIVE only, APR desc |
| Legacy combined endpoint | GET /v4/users/{address}/rewards | Backwards compatibility only |
Build claim transactions — /rewards/summary
This is the endpoint you want for any "Claim" UI. It returns amount, claimed, pending, and proofs per token, grouped by chain, and is the cheapest and fastest server-side path because it skips the breakdown traversal.
GET https://api.merkl.xyz/v4/users/{address}/rewards/summary
Supports filtering by chain, token, and protocol — plus reloadChainId for cache bypass. See the API reference for the full schema.
Example: /v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/summary
USD totals — /rewards/stats
A one-shot endpoint for headline numbers in dashboards or notification copy.
GET https://api.merkl.xyz/v4/users/{address}/rewards/stats
Returns totalEarnedUSD, pendingUSD, claimableUSD. Supports filtering by chain, token, and protocol. See the API reference for the full schema.
Example: /v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/stats
Reward breakdowns — where each reward came from
Two endpoints serve breakdowns, depending on how much data you expect.
Small users — /rewards/breakdowns
Returns the same amount / claimed / pending / proofs shape as /rewards/summary, plus a breakdowns array on every token detailing which campaign each fraction of the reward came from.
GET https://api.merkl.xyz/v4/users/{address}/rewards/breakdowns
Supports filtering by chain, token, and protocol — plus reloadChainId for cache bypass. See the API reference for the full schema.
This endpoint is capped at ~1000 breakdowns per response. If a user has more than that on the queried scope, the breakdowns field comes back empty. For those users, paginate through /v4/leaves/{recipient}/breakdowns instead.
Example: /v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/breakdowns?chainIds=324
Any user, paginated — /v4/leaves/{recipient}/breakdowns
The scalable path. Returns one page of leaf-level breakdowns at a time for a (recipient, tokenAddress, distributionChainId) triple, with pending amounts reconciled from temp leaves.
GET https://api.merkl.xyz/v4/leaves/{recipient}/breakdowns
Requires a tokenAddress and a distributionChainId. Supports additional filtering by campaign, opportunity, and protocol, plus pagination via page / items. See the API reference for the full schema.
Example: first page of breakdowns on Arbitrum for a specific token — /v4/leaves/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/breakdowns?tokenAddress={token}&distributionChainId=42161&page=0&items=50.
Per-chain opportunity breakdown — /rewards/chains/{chainId}/breakdowns
Lists every opportunity a user has rewards on for a single chain. One row per (opportunity, token) with claimed, pending, and a server-computed claimable. Useful when expanding a chain row in a dashboard.
GET https://api.merkl.xyz/v4/users/{address}/rewards/chains/{chainId}/breakdowns
See the API reference for the full schema.
Example: /v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/chains/42161/breakdowns
Active opportunities — /rewards/active-opportunities
Returns the opportunities where the user is currently earning — only those with LIVE campaigns and a non-zero balance (settled or pending). Aggregated per opportunity, sorted by APR desc.
GET https://api.merkl.xyz/v4/users/{address}/rewards/active-opportunities
See the API reference for the full schema.
Example: /v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards/active-opportunities
Legacy endpoint — /v4/users/{address}/rewards
Still supported for backwards compatibility. Returns rewards aggregated by chain with breakdowns embedded. Filters are limited to chain only — the newer endpoints add token and protocol filtering. New integrations should use /rewards/summary for claiming and /rewards/breakdowns or /v4/leaves/{recipient}/breakdowns for breakdown details. See the API reference for the full schema.
GET https://api.merkl.xyz/v4/users/{address}/rewards?chainId={chain_id}
Examples:
- Single chain — zkSync:
/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards?chainId=324 - Multiple chains — zkSync, Ethereum, Arbitrum:
/v4/users/0x4F2BF7469Bc38d1aE779b1F4affC588f35E60973/rewards?chainId=324,1,42161
Historical rewards
For use cases that need the rewards earned by an address between two timestamps (accounting, tax reporting, retroactive analytics, etc.), dedicated API routes are available on demand. These routes are computationally heavy, so they are not publicly exposed to every integrator. Reach out to the Merkl team to request access and describe your use case.
To pull the full history of onchain claim transactions for a user, the GET /v4/claims endpoint is publicly available and easy to query. It supports filters like recipient, chainId, campaignId, token, and root — combine them to scope the history exactly to what you need. See the full filter list and response schema in the API reference.
Building claim transactions
Rewards are claimed through the Distributor contract. Find contract addresses for each chain on the Chains & Contracts page.
If the token or amount doesn't match the proof when calling the Distributor contract, the transaction will revert.
Rewards on Merkl are claimable per token. Users can claim rewards for a single token or all tokens at once.
Distributor contract interface
The full source is available in the merkl-contracts repository. The functions and events most relevant to integrators are:
interface IDistributor {
/// @notice Cumulative claim record stored per (user, token).
struct Claim {
uint208 amount;
uint48 timestamp;
bytes32 merkleRoot;
}
/*//////////////////////////////////// CLAIM ////////////////////////////////////*/
/// @notice Claim rewards for a batch of (user, token) pairs.
/// @dev `amounts` are cumulative — pass the `amount` returned by the API as-is.
/// Tokens are sent to `users[i]` (or to a recipient previously set via
/// `setClaimRecipient`). Reverts with `InvalidProof` if a leaf does not
/// match the active Merkle root.
function claim(
address[] calldata users,
address[] calldata tokens,
uint256[] calldata amounts,
bytes32[][] calldata proofs
) external;
/// @notice Claim with explicit recipients and optional `onClaim` callback data.
/// @dev Only `msg.sender` claiming for itself can override the recipient.
function claimWithRecipient(
address[] calldata users,
address[] calldata tokens,
uint256[] calldata amounts,
bytes32[][] calldata proofs,
address[] calldata recipients,
bytes[] memory datas
) external;
/*//////////////////////////////////// USER ADMIN ////////////////////////////////////*/
/// @notice Authorize / deauthorize an operator to claim on behalf of `user`.
/// Pass `address(0)` to allow anyone to claim for the user.
function toggleOperator(address user, address operator) external;
/// @notice Set a default recipient for `user`'s claims of `token`.
/// `token = address(0)` sets the default for all tokens.
function setClaimRecipient(address recipient, address token) external;
/*//////////////////////////////////// VIEWS ////////////////////////////////////*/
/// @notice Returns the Merkle root currently used to verify proofs.
/// Equals `lastTree.merkleRoot` while a dispute window is open.
function getMerkleRoot() external view returns (bytes32);
/// @notice Cumulative amount already claimed by `user` for `token`.
function claimed(address user, address token)
external
view
returns (uint208 amount, uint48 timestamp, bytes32 merkleRoot);
/// @notice Operator authorizations (1 = authorized, 0 = not authorized).
function operators(address user, address operator) external view returns (uint256);
/// @notice Per-(user, token) custom recipient set via `setClaimRecipient`.
function claimRecipient(address user, address token) external view returns (address);
/*//////////////////////////////////// EVENTS ////////////////////////////////////*/
event Claimed(address indexed user, address indexed token, uint256 amount);
event ClaimRecipientUpdated(address indexed user, address indexed token, address indexed recipient);
event OperatorToggled(address indexed user, address indexed operator, bool isWhitelisted);
event TreeUpdated(bytes32 merkleRoot, bytes32 ipfsHash, uint48 endOfDisputePeriod);
}
The full ABI (including governance functions, dispute flow, and errors) is available as a JSON file: Download the Distributor ABI.
Example claiming script
Here is a script to claim all token rewards for a user on a chain. It uses /rewards/summary — the recommended endpoint — so the call stays lightweight even for power users. The Distributor address is the same on most EVM chains — see the Chains & Contracts page for per-chain values.
import type { JsonRpcSigner } from '@ethersproject/providers'
import { MerklApi } from '@merkl/api'
import { Distributor__factory } from '@sdk' // Or load the ABI from /images/distributor-abi.json
const DISTRIBUTOR_ADDRESS = '0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae'
export const claim = async (chainId: number, signer: JsonRpcSigner) => {
const { status, data } = await MerklApi('https://api.merkl.xyz')
.v4.users({ address: signer._address })
.rewards.summary.get({ query: { chainIds: [String(chainId)] } })
if (status !== 200) throw 'Failed to fetch rewards'
const users = []
const tokens = []
const amounts = []
const proofs = []
for (const rewards of data) {
if (rewards.chain.id !== chainId) continue
for (const reward of rewards.rewards) {
users.push(signer._address)
tokens.push(reward.token.address)
amounts.push(reward.amount)
proofs.push(reward.proofs)
}
}
if (tokens.length === 0) throw 'No tokens to claim'
const contract = Distributor__factory.connect(DISTRIBUTOR_ADDRESS, signer)
await (await contract.claim(users, tokens, amounts, proofs)).wait()
}