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