Skip to content

Claim Rewards

This tutorial shows you how to implement reward claiming for both Merkl (current programs) and Morpho URD (legacy programs) in your application. Supporting both systems ensures users can access all their available rewards.

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

The process is similar for both Merkl and URD, but they use different APIs and smart contracts.

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/claim?user={address}&chainId={chainId}
Example:
async function fetchMerklClaimData(
  userAddress: string,
  chainId: number
): Promise<MerklClaimData> {
  const response = await fetch(
    `https://api.merkl.xyz/v4/claim?user=${userAddress}&chainId=${chainId}`
  );
 
  if (!response.ok) {
    throw new Error(`Merkl API error: ${response.status}`);
  }
 
  return response.json();
}
Response Structure:
{
  "claim": {
    "user": "0x...",
    "tokens": ["0xTOKEN1", "0xTOKEN2"],
    "amounts": ["1000000000000000000", "2000000000000000000"],
    "proofs": [
      ["0xproof1a", "0xproof1b"],
      ["0xproof2a", "0xproof2b"]
    ]
  }
}

Step 2: Execute the Claim

Using viem:

import { createWalletClient, custom } from "viem";
import { mainnet } from "viem/chains";
 
const MERKL_DISTRIBUTOR = "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae"; // Mainnet
 
async function claimMerklRewards(
  userAddress: string,
  chainId: number
) {
  // 1. Fetch claim data
  const claimData = await fetchMerklClaimData(userAddress, chainId);
 
  // 2. Setup wallet client
  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: [
      claimData.claim.user,
      claimData.claim.tokens,
      claimData.claim.amounts,
      claimData.claim.proofs,
    ],
  });
 
  // 4. Wait for confirmation
  const receipt = await walletClient.waitForTransactionReceipt({ hash });
 
  return receipt;
}
 
// Merkl Distributor ABI (claim function)
const MERKL_ABI = [
  {
    inputs: [
      { name: "user", 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.

Claiming via Morpho URD - Legacy distributions

The URD handles legacy reward programs. While new programs use Merkl, many users still have unclaimed URD rewards.

Step 1: Fetch Distribution Data

Endpoint:
GET https://rewards.morpho.org/v1/users/{address}/distributions
Example:
interface URDDistribution {
  user: string;
  distributor: string;  // URD contract address
  chain_id: number;
  asset: {
    address: string;
    symbol: string;
  };
  claimable: string;  // Cumulative amount
  proof: string[];
  tx_data: string;  // Pre-formatted calldata
}
 
async function fetchURDDistributions(
  userAddress: string
): Promise<URDDistribution[]> {
  const response = await fetch(
    `https://rewards.morpho.org/v1/users/${userAddress}/distributions`
  );
 
  if (!response.ok) {
    throw new Error(`Rewards API error: ${response.status}`);
  }
 
  return response.json();
}
Response Structure:
[
  {
    "user": "0x...",
    "distributor": "0x330eefa8a787552DC5cAd3C3cA644844B1E61Ddb",
    "chain_id": 1,
    "asset": {
      "address": "0x58D97B57BB95320F9a05dC918Aef65434969c2B2",
      "symbol": "MORPHO"
    },
    "claimable": "115631364898103632676",
    "proof": ["0x1a2b...", "0x3c4d..."],
    "tx_data": "0x4e71d92d..."
  }
]

Step 2: Check Already-Claimed Amount

Before claiming, verify how much the user has already claimed to avoid unnecessary transactions:

import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
 
const URD_ABI = [
  {
    inputs: [
      { name: "account", type: "address" },
      { name: "reward", type: "address" },
    ],
    name: "claimed",
    outputs: [{ name: "", type: "uint256" }],
    stateMutability: "view",
    type: "function",
  },
] as const;
 
async function getClaimedAmount(
  distributorAddress: string,
  userAddress: string,
  rewardTokenAddress: string
): Promise<bigint> {
  const publicClient = createPublicClient({
    chain: mainnet,
    transport: http(),
  });
 
  const claimed = await publicClient.readContract({
    address: distributorAddress as `0x${string}`,
    abi: URD_ABI,
    functionName: "claimed",
    args: [userAddress as `0x${string}`, rewardTokenAddress as `0x${string}`],
  });
 
  return claimed;
}

Step 3: Execute the Claim

Using Pre-formatted tx_data

The API provides ready-to-use transaction calldata:

async function claimURDRewards(distribution: URDDistribution) {
  // 1. Check if there's anything to claim
  const alreadyClaimed = await getClaimedAmount(
    distribution.distributor,
    distribution.user,
    distribution.asset.address
  );
 
  const claimable = BigInt(distribution.claimable);
 
  if (alreadyClaimed >= claimable) {
    console.log("Nothing to claim (already claimed)");
    return null;
  }
 
  // 2. Setup wallet client
  const walletClient = createWalletClient({
    account: distribution.user as `0x${string}`,
    chain: mainnet,
    transport: custom(window.ethereum),
  });
 
  // 3. Send transaction with pre-formatted data
  const hash = await walletClient.sendTransaction({
    to: distribution.distributor as `0x${string}`,
    data: distribution.tx_data as `0x${string}`,
  });
 
  // 4. Wait for confirmation
  const receipt = await walletClient.waitForTransactionReceipt({ hash });
 
  return receipt;
}

Resources