Skip to content

Fetch Rewards Data

This tutorial shows you how to fetch reward information for Morpho Vaults and Markets from both Merkl API and Morpho API. Understanding how to query both systems is essential for displaying complete reward information to your users.

What You'll Learn

  • How to fetch vault and market reward rates from Morpho API
  • How to query user-specific rewards from Merkl API
  • How to get historical reward data from Morpho Rewards API (URD)
  • How to combine data from multiple sources for a complete picture

Prerequisites

  • Basic understanding of Reward Programs
  • Familiarity with REST and GraphQL APIs
  • A tool for making API requests (curl, fetch, axios, or GraphQL client)

API Landscape Overview

There are three main APIs for rewards data:

APIUse CaseRewards TypeData Provided
Morpho APIVault/Market reward ratesBoth Merkl & Old URDAPRs, reward token info, integrated with vault/market data
Merkl APIUser claimable rewardsMerkl onlyUser balances, claim data, real-time rewards
Rewards APIHistorical rewardsURD onlyLegacy programs, distributions, claim data
Recommended Strategy:
  1. Use Morpho API for displaying reward APRs on vaults/markets (easiest integration)
  2. Use Merkl API for user-specific Merkl rewards
  3. Use Rewards API for user-specific URD (legacy) rewards

Fetching Vault Rewards

Using Morpho API (Recommended)

Vault rewards come from two sources that must be queried and aggregated manually:

  1. Vault-level rewards: Direct campaigns targeting the vault
  2. Market-level rewards: Forwarded from markets where the vault allocates

Query Complete Vault Rewards

query VaultRewards($address: String!, $chainId: Int!) {
  vaultByAddress(address: $address, chainId: $chainId) {
    address
    name
    symbol
    state {
      apy
      netApy  # Convenience field: complete APY including rewards
 
      # Vault-level rewards (direct)
      rewards {
        supplyApr
        yearlySupplyTokens
        asset {
          address
          symbol
          priceUsd
          chain { id }
        }
      }
 
      # Market-level rewards (forwarded from allocations)
      allocation {
        supplyAssetsUsd  # Required for weighted average
        market {
          uniqueKey
          loanAsset { symbol }
          collateralAsset { symbol }
          state {
            rewards {
              supplyApr
              asset {
                address
                symbol
                priceUsd
                chain { id }
              }
            }
          }
        }
      }
    }
  }
}
Variables:
{
  "address": "0x9aB2d181E4b87ba57D5eD564D3eF652C4E710707",
  "chainId": 8453
}

Calculate Total Rewards APR

Vault rewards require manual aggregation with weighted averaging:

const vault = response.data.vaultByAddress;
 
// 1. Sum vault-level rewards
const vaultRewardsApr = vault.state.rewards.reduce(
  (sum, r) => sum + parseFloat(r.supplyApr),
  0
);
 
// 2. Calculate weighted average of market-level rewards
const totalAllocatedUsd = vault.state.allocation.reduce(
  (sum, alloc) => sum + parseFloat(alloc.supplyAssetsUsd),
  0
);
 
const marketRewardsApr = vault.state.allocation.reduce((sum, alloc) => {
  const marketRewards = alloc.market.state.rewards.reduce(
    (marketSum, r) => marketSum + parseFloat(r.supplyApr),
    0
  );
  const weight = parseFloat(alloc.supplyAssetsUsd) / totalAllocatedUsd;
  return sum + (marketRewards * weight);
}, 0);
 
// 3. Total rewards APR
const totalRewardsApr = vaultRewardsApr + marketRewardsApr;
 
console.log(`Base APY: ${vault.state.apy}%`);
console.log(`Vault Rewards APR: ${vaultRewardsApr}%`);
console.log(`Market Rewards APR: ${marketRewardsApr}%`);
console.log(`Total APY: ${parseFloat(vault.state.apy) + totalRewardsApr}%`);

Alternative: Use Convenience Fields

For simple total APY display without breakdown:

const vault = response.data.vaultByAddress;
const netApy = parseFloat(vault.state.netApy);  // Pre-calculated complete APY
 
console.log(`Total APY: ${netApy}%`);
Important Notes:
  • Vault rewards are in two separate API locations: state.rewards and state.allocation[].market.state.rewards
  • Manual weighted average calculation is required for market rewards
  • Use supplyAssetsUsd from allocations to calculate weights
  • Convenience field netApy includes all components but doesn't provide breakdown

Fetching Market Rewards

Markets can have rewards for supply and borrow.

Using Morpho API

query MarketRewards($uniqueKey: String!) {
  markets(where: { uniqueKey: $uniqueKey }) {
    items {
      uniqueKey
      loanAsset {
        symbol
      }
      collateralAsset {
        symbol
      }
      state {
        borrowApy
        supplyApy
        rewards {
          supplyApr
          borrowApr
          asset {
            address
            symbol
            priceUsd
          }
        }
      }
    }
  }
}
Key Fields:
  • supplyApr: Reward APR for suppliers
  • borrowApr: Reward APR for borrowers (often negative, meaning borrowers earn rewards)

Fetching User-Specific Rewards

Merkl API (Current Programs)

To get a user's claimable Merkl rewards:

Endpoint:
GET https://api.merkl.xyz/v4/userRewards?user={address}
Example:
curl "https://api.merkl.xyz/v4/userRewards?user=0x1234...5678"
Response Structure:
{
  "1": {  // Chain ID
    "0xVAULT_OR_MARKET_ADDRESS": {
      "claimable": {
        "0xREWARD_TOKEN_ADDRESS": {
          "accumulated": "1234567890",
          "unclaimed": "1234567890",
          "symbol": "MORPHO",
          "decimals": 18
        }
      }
    }
  }
}
TypeScript Example:
async function fetchMerklRewards(userAddress: string) {
  const response = await fetch(
    `https://api.merkl.xyz/v4/userRewards?user=${userAddress}`
  );
  const data = await response.json();
 
  // Parse rewards by chain
  const chainId = 1; // Ethereum mainnet
  const rewards = data[chainId] || {};
 
  // Aggregate all claimable rewards
  const claimableByToken: Record<string, bigint> = {};
 
  for (const [campaignAddress, campaignData] of Object.entries(rewards)) {
    for (const [tokenAddress, tokenData] of Object.entries(
      campaignData.claimable || {}
    )) {
      const unclaimed = BigInt(tokenData.unclaimed || "0");
      claimableByToken[tokenAddress] =
        (claimableByToken[tokenAddress] || 0n) + unclaimed;
    }
  }
 
  return claimableByToken;
}

Morpho Rewards API (Legacy URD Programs)

To get a user's URD rewards:

Endpoint:
GET https://rewards.morpho.org/v1/users/{address}/rewards
Example:
curl "https://rewards.morpho.org/v1/users/0x0ec553110e53122d1226646670a8475D4C8E6F04/rewards"
Response Structure:
[
  {
    "user": "0x0ec553110e53122d1226646670a8475D4C8E6F04",
    "type": "uniform-reward",
    "asset": {
      "id": "0x58D97B57BB95320F9a05dC918Aef65434969c2B2-1",
      "address": "0x58D97B57BB95320F9a05dC918Aef65434969c2B2",
      "chain_id": 1
    },
    "program_id": "0x5068...",
    "amount": {
      "total": "115631364898103632676",
      "claimable_now": "22676259927164556632",
      "claimable_next": "1688912463745536463",
      "claimed": "91266192507193539581"
    }
  }
]
Key Fields:
  • claimable_now: Available to claim right now
  • claimable_next: Will become claimable in next epoch
  • claimed: Already claimed by user
  • total: Sum of all the above
TypeScript Example:
async function fetchURDRewards(userAddress: string) {
  const response = await fetch(
    `https://rewards.morpho.org/v1/users/${userAddress}/rewards`
  );
  const rewards = await response.json();
 
  // Aggregate claimable by token
  const claimableByToken: Record<string, bigint> = {};
 
  for (const reward of rewards) {
    const tokenAddress = reward.asset.address;
    const claimable = BigInt(reward.amount.claimable_now);
 
    claimableByToken[tokenAddress] =
      (claimableByToken[tokenAddress] || 0n) + claimable;
  }
 
  return claimableByToken;
}

Combining Data from Multiple Sources

To show a complete rewards picture, you'll need to combine data from Merkl and URD:

async function fetchAllUserRewards(userAddress: string) {
  // Fetch from both sources in parallel
  const [merklRewards, urdRewards] = await Promise.all([
    fetchMerklRewards(userAddress),
    fetchURDRewards(userAddress),
  ]);
 
  // Combine by token address
  const allRewards: Record<string, {
    merkl: bigint;
    urd: bigint;
    total: bigint;
  }> = {};
 
  // Add Merkl rewards
  for (const [token, amount] of Object.entries(merklRewards)) {
    allRewards[token] = allRewards[token] || { merkl: 0n, urd: 0n, total: 0n };
    allRewards[token].merkl = amount;
    allRewards[token].total += amount;
  }
 
  // Add URD rewards
  for (const [token, amount] of Object.entries(urdRewards)) {
    allRewards[token] = allRewards[token] || { merkl: 0n, urd: 0n, total: 0n };
    allRewards[token].urd = amount;
    allRewards[token].total += amount;
  }
 
  return allRewards;
}

Performance Considerations

Caching

Vault/Market Rewards (Morpho API):
  • Cache for 5-15 minutes
  • Rewards rates change infrequently
User Merkl Rewards:
  • Cache for 5-10 minutes
  • Updates every 8 hours, but users expect fresh data

Parallel Fetching

Always fetch from multiple APIs in parallel:

const [vaultData, merklRewards, urdRewards] = await Promise.all([
  fetchVaultFromMorphoAPI(vaultAddress),
  fetchMerklRewards(userAddress),
  fetchURDRewards(userAddress),
]);

Error Handling

async function fetchRewardsSafely(userAddress: string) {
  try {
    const merkl = await fetchMerklRewards(userAddress);
  } catch (error) {
    console.error("Merkl API error:", error);
    merkl = {}; // Fallback to empty
  }
 
  try {
    const urd = await fetchURDRewards(userAddress);
  } catch (error) {
    console.error("URD API error:", error);
    urd = {}; // Fallback to empty
  }
 
  return { merkl, urd };
}

API Rate Limits

Be mindful of rate limits:

APIRate LimitNotes
Morpho API2k / 5 minUse responsibly
Merkl APIUnknown, likely generousCache responses
Rewards API850 / minAvoid excessive polling

Best Practice: Cache aggressively and fetch only when needed.

Next Steps

Resources