Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Claim Rewards

This tutorial shows you how to implement reward claiming for Merkl programs in your application.

Overview

Claiming rewards involves:

  1. Fetching claim data (proof + transaction data) from the appropriate API
  2. Constructing or using pre-formatted transaction calldata
  3. Submitting the claim transaction onchain
  4. Handling the response and updating your UI

Prerequisites

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