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:
| API | Use Case | Rewards Type | Data Provided |
|---|---|---|---|
| Morpho API | Vault/Market reward rates | Merkl only | APRs, reward token info, integrated with vault/market data |
| Merkl API | User claimable rewards | Merkl only | User balances, claim data, real-time rewards |
| Legacy Rewards | User claimable rewards | URD only | Deprecated. Use rewards-legacy.morpho.org |
- Use Morpho API for displaying reward APRs on vaults/markets
- Use Merkl API for user-specific Merkl rewards
- 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
}
}
}
}{
"address": "0xb576765fB15505433aF24FEe2c0325895C559FB2",
"chainId": 1
}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.
- Use
vaultV2ByAddressinstead ofvaultByAddress - 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:
- 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, 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}%`);- Vault rewards are in two separate API locations:
state.rewardsandstate.allocation[].market.state.rewards - Manual weighted average calculation is required for market rewards
- Use
supplyAssetsUsdfrom allocations to calculate weights - Convenience field
netApyincludes 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;
}Legacy Rewards (Pre-Merkl)
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] = 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:
| API | Rate Limit | Notes |
|---|---|---|
| Morpho API | 5k / 5 min | Use responsibly |
| Merkl API | 10 request / second | Cache responses |
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
- Legacy Rewards Lookup: rewards-legacy.morpho.org
- Example Code: morpho-org/merkl-morpho-recipe
