Skip to main content

Morpho SDK Viem Tutorial

Introduction

The blue-sdk-viem package extends the functionality of blue-sdk by providing Viem-based fetchers for interacting with Morpho markets and positions. This documentation will guide you through the setup and usage of it.

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 { AccrualPosition, MarketConfig, MarketId } from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/MarketConfig";
import { createClient, http } from "viem";
import { mainnet } from "viem/chains";
import "dotenv/config";
// Set up the client
const client = createClient({
chain: mainnet,
transport: http(process.env.RPC_URL_MAINNET),
});

Make sure to set up your .env file with the RPC_URL_MAINNET variable pointing to your Ethereum node URL.

Basic Usage

Fetching Market Configuration

To fetch the configuration of a specific market:

const marketId =
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc" as MarketId;
async function fetchMarketConfig() {
console.log("Fetching market config...");
const config = await MarketConfig.fetch(marketId, client);
console.log("Collateral Token:", config.collateralToken);
}

Fetching Market Data

To fetch data for a specific market:

import { Time } from "@morpho-org/morpho-ts";
import { Market } from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/Market";
async function fetchMarketData() {
console.log("Fetching market data...");
const market = await Market.fetch(marketId, client);
console.log("Market Utilization:", market.utilization);
console.log("Market Liquidity:", market.liquidity);
console.log("Market APY at Target:", market.apyAtTarget);
}

Accruing Interest and Converting Shares

To accrue interest on the market and convert supply shares to assets:

const accruedMarket = market.accrueInterest(Time.timestamp());
const shares = ethers.parseUnits("1", 18);
console.log(
"Supply Assets for 1 Share:",
ethers.formatUnits(accruedMarket.toSupplyAssets(shares), 18)
);

Fetching Position Data

To fetch data for a user's position in a specific market:

import "@morpho-org/blue-sdk-viem/lib/augment/Position";

async function fetchUserPosition() {
console.log("Fetching user position...");
const userAddress = "0x7f65e7326F22963e2039734dDfF61958D5d284Ca";
const position = await AccrualPosition.fetch(userAddress, marketId, client);
console.log("Borrow Assets:", position.borrowAssets);
console.log("Is Position Healthy:", position.isHealthy);
console.log("Max Borrowable Assets:", position.maxBorrowableAssets);
}

Accruing Interest on a Position

To accrue interest on a user's position:

const accruedPosition = position.accrueInterest(Time.timestamp());
console.log("Accrued Borrow Assets:", accruedPosition.borrowAssets);

Advanced Usage

Error Handling

Always wrap your main function in a try-catch block to handle any errors:

async function main() {
// ... your code here ...
}

main().catch((error) => {
console.error("An error occurred:", error);
});

Complex Scenarios

  • Explore more complex scenarios, such as multi-market interactions.

Conclusion

This documentation provides a basic overview of how to use the blue-sdk-viem package to interact with Morpho markets and positions. For more advanced usage, feel free to jump on the github repository.

Examples

Market Metrics and Position

import {
AccrualPosition,
Market,
MarketConfig,
MarketId,
} from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/MarketConfig";
import { createClient, http } from "viem";
import { mainnet } from "viem/chains";
import "dotenv/config";
import { Time } from "@morpho-org/morpho-ts";
import "@morpho-org/blue-sdk-viem/lib/augment/Market";
import "@morpho-org/blue-sdk-viem/lib/augment/Position";
const client = createClient({
chain: mainnet,
transport: http(process.env.RPC_URL_MAINNET),
});
const marketId: MarketId =
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc" as MarketId;
async function fetchMarketConfig() {
console.log("Fetching market config...");
const config = await MarketConfig.fetch(marketId, client);
console.log("Collateral Token:", config.collateralToken);
}
async function fetchMarketData() {
console.log("Fetching market data...");
const market = await Market.fetch(marketId, client);
console.log("Market Utilization:", market.utilization);
console.log("Market Liquidity:", market.liquidity);
console.log("Market APY at Target:", market.apyAtTarget);
const accruedMarket = market.accrueInterest(Time.timestamp());
const assets = accruedMarket.toSupplyAssets(BigInt(1e18));
console.log("Supply Assets for 1 Share:", assets);
}
async function fetchUserPosition() {
console.log("Fetching user position...");
const userAddress = "0x7f65e7326F22963e2039734dDfF61958D5d284Ca";
const position = await AccrualPosition.fetch(userAddress, marketId, client);
console.log("Borrow Assets:", position.borrowAssets);
console.log("Is Position Healthy:", position.isHealthy);
console.log("Max Borrowable Assets:", position.maxBorrowableAssets);
const accruedPosition = position.accrueInterest(Time.timestamp());
console.log("Accrued Borrow Assets:", accruedPosition.borrowAssets);
}
async function main() {
await fetchMarketConfig();
await fetchMarketData();
await fetchUserPosition();
}
main().catch((error) => {
console.error("An error occurred:", error);
});

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

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);