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 onchain. This package augments the core SDK classes with static fetch methods that retrieve live data from the blockchain.

Installation

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

yarn add @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 { MarketParams, Market, AccrualPosition, MarketId } from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/MarketParams";
import "@morpho-org/blue-sdk-viem/lib/augment/Market";
import "@morpho-org/blue-sdk-viem/lib/augment/Position";
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.

Class Augmentation

The blue-sdk-viem package uses an opt-in augmentation pattern. You can import augmentations granularly or all at once:

Granular Augmentation (Recommended)

import "@morpho-org/blue-sdk-viem/lib/augment/AccrualPosition";
import "@morpho-org/blue-sdk-viem/lib/augment/Holding";
import "@morpho-org/blue-sdk-viem/lib/augment/Market";
import "@morpho-org/blue-sdk-viem/lib/augment/MarketParams";
import "@morpho-org/blue-sdk-viem/lib/augment/Position";
import "@morpho-org/blue-sdk-viem/lib/augment/Token";
import "@morpho-org/blue-sdk-viem/lib/augment/VaultConfig";
import "@morpho-org/blue-sdk-viem/lib/augment/Vault";
import "@morpho-org/blue-sdk-viem/lib/augment/VaultUser";
import "@morpho-org/blue-sdk-viem/lib/augment/VaultMarketAllocation";
import "@morpho-org/blue-sdk-viem/lib/augment/VaultMarketConfig";
import "@morpho-org/blue-sdk-viem/lib/augment/VaultMarketPublicAllocatorConfig";

Full Augmentation

// Import all augmentations at once
import "@morpho-org/blue-sdk-viem/lib/augment";

Basic Usage

Fetching Market Configuration

To fetch the configuration of a specific market from the blockchain:

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

Fetching Market Data

To fetch live data for a specific market:

import { Time } from "@morpho-org/morpho-ts";
 
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);
  console.log("Borrow APY:", market.borrowApy);
  
  // Accrue interest to the latest timestamp
  const accruedMarket = market.accrueInterest(Time.timestamp());
  
  // Convert supply shares to assets
  const shares = 1_000000000000000000n; // 1 share
  const assets = accruedMarket.toSupplyAssets(shares);
  console.log("Supply Assets for 1 Share:", assets);
}

Fetching Position Data

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

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("Supply Assets:", position.supplyAssets);
  console.log("Collateral:", position.collateral);
  console.log("Is Position Healthy:", position.isHealthy);
  console.log("Max Borrowable Assets:", position.maxBorrowableAssets);
  
  // Accrue interest on the position
  const accruedPosition = position.accrueInterest(Time.timestamp());
  console.log("Accrued Borrow Assets:", accruedPosition.borrowAssets);
}

Fetching Historical Data

You can fetch market or position data at a specific block:

async function fetchHistoricalData() {
  const blockNumber = 18000000n; // Example block number
  
  // Fetch market data at a specific block
  const historicalMarket = await Market.fetch(marketId, client, {
    blockNumber,
  });
  
  console.log("Historical Market Utilization:", historicalMarket.utilization);
}

Advanced Usage

Error Handling

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

async function main() {
  try {
    await fetchMarketConfig();
    await fetchMarketData();
    await fetchUserPosition();
  } catch (error) {
    console.error("An error occurred:", error);
  }
}
 
main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

Multi-Chain Support

To work with multiple chains, create separate clients:

import { base } from "viem/chains";
 
const baseClient = createClient({
  chain: base,
  transport: http(process.env.RPC_URL_BASE),
});
 
// Fetch from Base chain
const baseMarket = await Market.fetch(marketId, baseClient);

Complex Scenarios

  • Fetch data from multiple markets simultaneously
  • Compare positions across different markets
  • Track historical APY changes over time
  • Monitor vault allocations and rebalancing

Conclusion

This documentation provides a comprehensive overview of how to use the blue-sdk-viem package to interact with Morpho markets and positions onchain. For more advanced usage and examples, visit the GitHub repository.

Examples

Market Metrics and Position

import {
  AccrualPosition,
  Market,
  MarketParams,
  MarketId,
} from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/MarketParams";
import "@morpho-org/blue-sdk-viem/lib/augment/Market";
import "@morpho-org/blue-sdk-viem/lib/augment/Position";
import { createClient, http } from "viem";
import { mainnet } from "viem/chains";
import "dotenv/config";
import { Time } from "@morpho-org/morpho-ts";
 
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 MarketParams.fetch(marketId, client);
  console.log("Collateral Token:", config.collateralToken);
  console.log("Loan Token:", config.loanToken);
  console.log("LLTV:", config.lltv);
  console.log("Liquidation Incentive Factor:", config.liquidationIncentiveFactor);
}
 
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);
  console.log("Borrow APY:", market.borrowApy);
  
  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("Supply Assets:", position.supplyAssets);
  console.log("Collateral:", position.collateral);
  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() {
  try {
    await fetchMarketConfig();
    await fetchMarketData();
    await fetchUserPosition();
  } catch (error) {
    console.error("An error occurred:", error);
  }
}
 
main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

Historical APY Calculation (1D, 7D, 30D)

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}`);
 
  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);
 
  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
  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 vaultApyData = await fetchHistoricalVaultData(
        test.vaultAddress,
        test.chainId
      );
      console.log("Vault APY Data:", {
        "1D APY": `${vaultApyData.oneDayAPY}%`,
        "7D APY": `${vaultApyData.sevenDayAPY}%`,
        "30D APY": `${vaultApyData.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);