Skip to main content

Examples

Display 1D, 7D or 30D Borrow APY in SDK view (Market, And Vault)

Installation

To use blue-sdk-viem, you need to install it along with its peer dependencies:

npm install @morpho-org/blue-sdk @morpho-org/blue-sdk-viem @morpho-org/morpho-ts viem dotenv

Setup

First, import the necessary modules and set up your environment:

import { http, Address, PublicClient, createPublicClient } from "viem";
import "dotenv/config";
import {
getChainAddresses,
Market,
MarketId,
Vault,
} from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment";
import { base, mainnet } from "viem/chains";

Make sure to set up your .env file with the RPC_URL_MAINNET and RPC_URL_BASE variables pointing to your Ethereum and Base node URLs.

Execution

Execute file with ts.node.

Expected output

Output of the example below should be similar to this:

Fetching APY data for Base...
Block numbers being queried: {
oneDayAgo: '23595355',
sevenDaysAgo: '23301355',
thirtyDaysAgo: '22174355',
current: '23644355'
}
Vault APY Data: { '1D APY': '7.11%', '7D APY': '8.01%', '30D APY': '5.69%' }
Market APY Data: { '1D APY': '10.78%', '7D APY': '8.25%', '30D APY': '3.33%' }

Full code example

import { http, Address, PublicClient, createPublicClient } from "viem";
import "dotenv/config";
import {
getChainAddresses,
Market,
MarketId,
Vault,
} from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment";
import { base, mainnet } from "viem/chains";

interface APYData {
oneDayAPY: number;
sevenDayAPY: number;
thirtyDayAPY: number;
}

// Chain-specific blocks per day
const BLOCKS_PER_DAY = {
MAINNET: BigInt(7000), // Ethereum mainnet: ~7000 blocks per day
BASE: BigInt(7000 * 7), // Base: ~7x faster than mainnet
};

async function initializeClientAndLoader(chainId: number) {
const rpcUrl =
chainId === 1
? process.env.RPC_URL_MAINNET
: chainId === 8453
? process.env.RPC_URL_BASE
: undefined;

if (!rpcUrl)
throw new Error(`No RPC URL configured for chain ID: ${chainId}`);

// Create a public client with the necessary actions
const client = createPublicClient({
chain: chainId === 1 ? mainnet : chainId === 8453 ? base : mainnet,
transport: http(rpcUrl),
});

const config = getChainAddresses(chainId);
if (!config) throw new Error(`Unsupported chain ID: ${chainId}`);
return { client, config };
}

async function getBlockNumberFromDaysAgo(
client: PublicClient,
daysAgo: number,
chainId: number
): Promise<bigint> {
const currentBlock = await client.getBlockNumber();
const blocksPerDay =
chainId === 1 ? BLOCKS_PER_DAY.MAINNET : BLOCKS_PER_DAY.BASE;
const blockDelta = blocksPerDay * BigInt(daysAgo);

// Ensure we don't try to fetch a negative block number
const targetBlock =
currentBlock > blockDelta ? currentBlock - blockDelta : BigInt(0);
return targetBlock;
}

async function calculateAPY(
currentValue: bigint,
previousValue: bigint,
daysPeriod: number
): Promise<number> {
if (previousValue === BigInt(0)) return 0;

const percentageChange =
(Number(currentValue - previousValue) / Number(previousValue)) * 100;
// Convert to annual rate
const annualRate =
(Math.pow(1 + percentageChange / 100, 365 / daysPeriod) - 1) * 100;
return Number(annualRate.toFixed(2));
}

async function fetchHistoricalVaultData(
vaultAddress: Address,
chainId: number
): Promise<APYData> {
const { client } = await initializeClientAndLoader(chainId);

// Get current vault state
const currentVault = await Vault.fetch(vaultAddress, client);
const currentAssets = currentVault.toAssets(BigInt(1e18));

// Get historical block numbers with chain-specific calculations
const oneDayAgoBlock = await getBlockNumberFromDaysAgo(
client as PublicClient,
1,
chainId
);
const sevenDaysAgoBlock = await getBlockNumberFromDaysAgo(
client as PublicClient,
7,
chainId
);
const thirtyDaysAgoBlock = await getBlockNumberFromDaysAgo(
client as PublicClient,
30,
chainId
);

console.log("Block numbers being queried:", {
oneDayAgo: oneDayAgoBlock.toString(),
sevenDaysAgo: sevenDaysAgoBlock.toString(),
thirtyDaysAgo: thirtyDaysAgoBlock.toString(),
current: (await client.getBlockNumber()).toString(),
});

// Fetch historical vault states
const oneDayAgoVault = await Vault.fetch(vaultAddress, client, {
blockNumber: oneDayAgoBlock,
});
const sevenDaysAgoVault = await Vault.fetch(vaultAddress, client, {
blockNumber: sevenDaysAgoBlock,
});
const thirtyDaysAgoVault = await Vault.fetch(vaultAddress, client, {
blockNumber: thirtyDaysAgoBlock,
});

// Calculate historical assets
const oneDayAgoAssets = oneDayAgoVault.toAssets(BigInt(1e18));
const sevenDaysAgoAssets = sevenDaysAgoVault.toAssets(BigInt(1e18));
const thirtyDaysAgoAssets = thirtyDaysAgoVault.toAssets(BigInt(1e18));

// Calculate APYs
const oneDayAPY = await calculateAPY(currentAssets, oneDayAgoAssets, 1);
const sevenDayAPY = await calculateAPY(currentAssets, sevenDaysAgoAssets, 7);
const thirtyDayAPY = await calculateAPY(
currentAssets,
thirtyDaysAgoAssets,
30
);

return {
oneDayAPY,
sevenDayAPY,
thirtyDayAPY,
};
}

async function fetchHistoricalMarketData(
marketId: MarketId,
chainId: number
): Promise<APYData> {
const { client } = await initializeClientAndLoader(chainId);

// Get current market state
const currentMarket = await Market.fetch(marketId, client);
const currentAsset = currentMarket.totalBorrowAssets;
const currentShare = currentMarket.totalBorrowShares;
const currentRatio = currentShare / (currentAsset + 1n);

// Get historical block numbers
const oneDayAgoBlock = await getBlockNumberFromDaysAgo(
client as PublicClient,
1,
chainId
);
const sevenDaysAgoBlock = await getBlockNumberFromDaysAgo(
client as PublicClient,
7,
chainId
);
const thirtyDaysAgoBlock = await getBlockNumberFromDaysAgo(
client as PublicClient,
30,
chainId
);

// Fetch historical market states
const oneDayAgoMarket = await Market.fetch(marketId, client, {
blockNumber: oneDayAgoBlock,
});
const sevenDaysAgoMarket = await Market.fetch(marketId, client, {
blockNumber: sevenDaysAgoBlock,
});
const thirtyDaysAgoMarket = await Market.fetch(marketId, client, {
blockNumber: thirtyDaysAgoBlock,
});

// Calculate historical ratios
const oneDayAgoRatio =
oneDayAgoMarket.totalBorrowShares /
(oneDayAgoMarket.totalBorrowAssets + 1n);
const sevenDaysAgoRatio =
sevenDaysAgoMarket.totalBorrowShares /
(sevenDaysAgoMarket.totalBorrowAssets + 1n);
const thirtyDaysAgoRatio =
thirtyDaysAgoMarket.totalBorrowShares /
(thirtyDaysAgoMarket.totalBorrowAssets + 1n);

// Calculate APYs and make them positive
const oneDayAPY = Math.abs(
await calculateAPY(currentRatio, oneDayAgoRatio, 1)
);
const sevenDayAPY = Math.abs(
await calculateAPY(currentRatio, sevenDaysAgoRatio, 7)
);
const thirtyDayAPY = Math.abs(
await calculateAPY(currentRatio, thirtyDaysAgoRatio, 30)
);

return {
oneDayAPY,
sevenDayAPY,
thirtyDayAPY,
};
}

// Example usage
async function main() {
const testCases = [
{
chain: "Base",
chainId: 8453,
vaultAddress: "0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca" as Address,
marketId:
"0x1c21c59df9db44bf6f645d854ee710a8ca17b479451447e9f56758aee10a2fad" as MarketId,
},
];

for (const test of testCases) {
try {
console.log(`\nFetching APY data for ${test.chain}...`);
const apyData = await fetchHistoricalVaultData(
test.vaultAddress,
test.chainId
);
console.log("Vault APY Data:", {
"1D APY": `${apyData.oneDayAPY}%`,
"7D APY": `${apyData.sevenDayAPY}%`,
"30D APY": `${apyData.thirtyDayAPY}%`,
});

const marketApyData = await fetchHistoricalMarketData(
test.marketId,
test.chainId
);
console.log("Market APY Data:", {
"1D APY": `${marketApyData.oneDayAPY}%`,
"7D APY": `${marketApyData.sevenDayAPY}%`,
"30D APY": `${marketApyData.thirtyDayAPY}%`,
});
} catch (error) {
console.error(`Error fetching data for ${test.chain}:`, error);
}
}
}

main().catch(console.error);