Skip to content

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:

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