Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
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 V2 and V1 reward rates from Morpho API
  • How to query user-specific rewards from Merkl API
  • 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 two main APIs for rewards data:

APIUse CaseRewards TypeData Provided
Morpho APIVault/Market reward ratesMerkl onlyAPRs, reward token info, integrated with vault/market data
Merkl APIUser claimable rewardsMerkl onlyUser balances, claim data, real-time rewards
Legacy RewardsUser claimable rewardsURD onlyDeprecated. Use rewards-legacy.morpho.org
Recommended Strategy:
  1. Use Morpho API for displaying reward APRs on vaults/markets
  2. Use Merkl API for user-specific Merkl rewards
  3. For legacy URD rewards, direct users to rewards-legacy.morpho.org

Fetching Morpho Vault V2 Rewards

Using Morpho API

Morpho Vault V2 rewards are pre-aggregated by the API. Unlike Morpho Vault V1, you don't need to manually combine vault-level and market-level rewards. The rewards field on a Morpho Vault V2 already includes all reward sources.

query VaultV2Rewards($address: String!, $chainId: Int!) {
  vaultV2ByAddress(address: $address, chainId: $chainId) {
    address
    name
    symbol
    apy
    netApy  # Complete APY including all rewards
 
    # Pre-aggregated rewards from all sources
    rewards {
      supplyApr
      asset {
        address
        symbol
        priceUsd
      }
    }
 
    # Adapters show where the vault allocates
    adapters {
      items {
        type      # MorphoVaultV1Adapter, MorphoMarketV1AdapterV2, etc.
        address
        assetsUsd
      }
    }
  }
}
Variables example:
{
  "address": "0xb576765fB15505433aF24FEe2c0325895C559FB2",
  "chainId": 1
}
Returned fields:

Fields below are returned as decimals (e.g. 0.05 = 5%).

  • apy: Current APY of the vault (before fees), derived from liquidity adapter rates.
  • netApy: Current net APY of the vault (after fees, including rewards), derived from liquidity adapter rates.
  • rewards[].supplyApr: APR for each reward token.
Key Differences from Morpho Vault V1:
  • Use vaultV2ByAddress instead of vaultByAddress
  • APY and rewards fields are at the top level, not nested under state
  • No manual aggregation needed: the API pre-aggregates rewards from all adapters

Fetching Morpho Vault V1 Rewards

Using Morpho API

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 example:
{
  "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, reward) => sum + parseFloat(reward.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, reward) => marketSum + parseFloat(reward.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: ${parseFloat(vault.state.apy) * 100}%`);
console.log(`Vault Rewards APR: ${vaultRewardsApr * 100}%`);
console.log(`Market Rewards APR: ${marketRewardsApr * 100}%`);
console.log(`Total APY: ${(parseFloat(vault.state.apy) + totalRewardsApr) * 100}%`);

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 * 100}%`);
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;
}

Legacy Rewards (Pre-Merkl)

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] = await Promise.all([
  fetchVaultFromMorphoAPI(vaultAddress),
  fetchMerklRewards(userAddress),
]);

Error Handling

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

API Rate Limits

Be mindful of rate limits:

APIRate LimitNotes
Morpho API5k / 5 minUse responsibly
Merkl API10 request / secondCache responses

Best Practice: Cache aggressively and fetch only when needed.

Next Steps

Resources