Claim Rewards
Implement Merkl reward claims in an application by fetching claim data, submitting the claim, and handling legacy reward flows separately.
This tutorial shows you how to implement reward claiming for Merkl programs in your application.
Overview
Claiming rewards involves:
- Fetching claim data (proof + transaction data) from the appropriate API
- Constructing or using pre-formatted transaction calldata
- Submitting the claim transaction onchain
- Handling the response and updating your UI
Prerequisites
- Understanding of Distribution Systems
- Familiarity with Fetching Rewards Data
- A web3 library (viem, ethers, web3.js)
- User's wallet connected to your app
Claiming via Merkl
Merkl is the primary distribution system for current reward programs.
Step 1: Fetch Claim Data
Endpoint:
GET https://api.merkl.xyz/v4/users/{address}/rewards?chainId={chainId}The {address} must be lowercase, and chainId is required. The same endpoint returns everything you need to claim: the token, the cumulative amount, the already-claimed amount, and the merkle proofs.
Example:
async function fetchMerklRewards(
userAddress: string,
chainId: number
): Promise<MerklReward[]> {
const response = await fetch(
`https://api.merkl.xyz/v4/users/${userAddress.toLowerCase()}/rewards?chainId=${chainId}`
);
if (!response.ok) {
throw new Error(`Merkl API error: ${response.status}`);
}
// The endpoint returns an array of per-chain objects; flatten to a flat list of rewards.
const data: MerklChainRewards[] = await response.json();
return data.flatMap((entry) => entry.rewards);
}Response Structure:
[
{
"chain": { "id": 8453, "name": "Base" },
"rewards": [
{
"token": { "address": "0xTOKEN", "symbol": "MORPHO", "decimals": 18 },
"amount": "2000000000000000000",
"claimed": "1000000000000000000",
"pending": "0",
"proofs": ["0xproof1a", "0xproof1b"]
}
]
}
]amount is the cumulative total earned to date and claimed is the cumulative amount already claimed onchain — both are passed as-is. The distributor transfers the difference (amount - claimed), so you do not subtract them yourself. pending is reward that is not yet in a live merkle root and cannot be claimed.
Step 2: Execute the Claim
Using viem:
import { createWalletClient, custom } from "viem";
import { mainnet } from "viem/chains";
// Same Distributor address across all supported chains
const MERKL_DISTRIBUTOR = "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae";
async function claimMerklRewards(
userAddress: string,
chainId: number
) {
// 1. Fetch the user's rewards
const rewards = await fetchMerklRewards(userAddress, chainId);
// Keep only genuinely-claimable rewards — entries with no proofs or nothing
// left to claim make the whole `claim` tx revert with `InvalidProof`.
const claimable = rewards.filter(
(r) => r.proofs.length > 0 && BigInt(r.amount) > BigInt(r.claimed)
);
// Build the parallel, index-aligned arrays the contract expects
const users = claimable.map(() => userAddress);
const tokens = claimable.map((r) => r.token.address);
const amounts = claimable.map((r) => BigInt(r.amount)); // cumulative; pass as-is
const proofs = claimable.map((r) => r.proofs);
// 2. Setup wallet client
// The claim must be sent from the user's own wallet — a third-party sender
// reverts with `NotWhitelisted` unless it has been approved as an operator.
const walletClient = createWalletClient({
account: userAddress,
chain: mainnet,
transport: custom(window.ethereum),
});
// 3. Send claim transaction
const hash = await walletClient.writeContract({
address: MERKL_DISTRIBUTOR,
abi: MERKL_ABI,
functionName: "claim",
args: [users, tokens, amounts, proofs],
});
// 4. Wait for confirmation
const receipt = await walletClient.waitForTransactionReceipt({ hash });
return receipt;
}
// Merkl Distributor ABI (claim function)
const MERKL_ABI = [
{
inputs: [
{ name: "users", type: "address[]" },
{ name: "tokens", type: "address[]" },
{ name: "amounts", type: "uint256[]" },
{ name: "proofs", type: "bytes32[][]" },
],
name: "claim",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
] as const;Step 3: Handle the Result
try {
const receipt = await claimMerklRewards(userAddress, 1);
if (receipt.status === "success") {
console.log("✅ Merkl rewards claimed successfully!");
// Update your UI to reflect claimed rewards
} else {
console.error("❌ Claim transaction failed");
}
} catch (error) {
console.error("Error claiming Merkl rewards:", error);
// Show error message to user
}For more details on Merkl claiming, see the official Merkl documentation.
Legacy Rewards (Pre-Merkl)
Resources
- Merkl Claim Docs: docs.merkl.xyz/integrate-merkl/app#claiming-user-rewards
- Legacy Rewards Lookup: rewards-legacy.morpho.org
Create a Rewards Program
Create a Morpho Vault or market reward program with Merkl, then check which campaign types the Morpho App displays.
Integrate Rewards Display
Build an interface for Morpho reward APY and claimable balances, including program details, live updates, empty states, and accessibility.