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:
API | Use Case | Rewards Type | Data Provided |
---|---|---|---|
Morpho API | Vault/Market reward rates | Both Merkl & Old URD | APRs, reward token info, integrated with vault/market data |
Merkl API | User claimable rewards | Merkl only | User balances, claim data, real-time rewards |
Rewards API | Historical rewards | URD only | Legacy programs, distributions, claim data |
- Use Morpho API for displaying reward APRs on vaults/markets (easiest integration)
- Use Merkl API for user-specific Merkl rewards
- 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:
- Vault-level rewards: Direct campaigns targeting the vault
- 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 }
}
}
}
}
}
}
}
}
{
"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}%`);
- Vault rewards are in two separate API locations:
state.rewards
andstate.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
}
}
}
}
}
}
supplyApr
: Reward APR for suppliersborrowApr
: 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}
curl "https://api.merkl.xyz/v4/userRewards?user=0x1234...5678"
{
"1": { // Chain ID
"0xVAULT_OR_MARKET_ADDRESS": {
"claimable": {
"0xREWARD_TOKEN_ADDRESS": {
"accumulated": "1234567890",
"unclaimed": "1234567890",
"symbol": "MORPHO",
"decimals": 18
}
}
}
}
}
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
curl "https://rewards.morpho.org/v1/users/0x0ec553110e53122d1226646670a8475D4C8E6F04/rewards"
[
{
"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"
}
}
]
claimable_now
: Available to claim right nowclaimable_next
: Will become claimable in next epochclaimed
: Already claimed by usertotal
: Sum of all the above
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
- 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:
API | Rate Limit | Notes |
---|---|---|
Morpho API | 2k / 5 min | Use responsibly |
Merkl API | Unknown, likely generous | Cache responses |
Rewards API | 850 / min | Avoid excessive polling |
Best Practice: Cache aggressively and fetch only when needed.
Next Steps
- Display rewards: Follow the Integrate Display tutorial
- Enable claiming: See the Claim Rewards tutorial
- Full example: Check out the Complete Integration Guide
Resources
- Morpho API Playground: api.morpho.org/graphql
- Merkl Docs: docs.merkl.xyz/integrate-merkl/app
- Rewards API Docs: rewards.morpho.org/docs
- Example Code: morpho-org/merkl-morpho-recipe