/**
* @author nirholas (Nich)
* @website x.com/nichxbt
* @github github.com/nirholas
* @license MIT
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"
import { ethers } from "ethers"
import { Logger } from "../utils/logger.js"
// Yearn V3 Registry
const YEARN_REGISTRY = "0x50c1a2eA0a861A967D9d0FFE2AE4012c2E053804"
export function registerYearnTools(server: McpServer) {
// Tool 1: Get vault info
server.tool(
"yearn_get_vault_info",
"Get detailed information about a Yearn vault",
{
vaultAddress: z.string().describe("The Yearn vault address"),
rpcUrl: z.string().optional().describe("Custom RPC URL")
},
async (params) => {
try {
const provider = new ethers.JsonRpcProvider(params.rpcUrl || "https://eth.llamarpc.com")
const vaultAbi = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function totalAssets() view returns (uint256)",
"function pricePerShare() view returns (uint256)",
"function token() view returns (address)"
]
const vault = new ethers.Contract(params.vaultAddress, vaultAbi, provider)
const [name, symbol, decimals, totalAssets, pricePerShare, token] = await Promise.all([
vault.name(),
vault.symbol(),
vault.decimals(),
vault.totalAssets(),
vault.pricePerShare(),
vault.token()
])
return {
content: [{
type: "text",
text: JSON.stringify({
vaultAddress: params.vaultAddress,
name,
symbol,
decimals: Number(decimals),
underlyingToken: token,
totalAssets: ethers.formatUnits(totalAssets, decimals),
pricePerShare: ethers.formatUnits(pricePerShare, decimals)
}, null, 2)
}]
}
} catch (error: any) {
Logger.error("Error getting vault info:", error)
throw new Error(`Failed to get vault info: ${error.message}`)
}
}
)
// Tool 2: Get top vaults
server.tool(
"yearn_get_top_vaults",
"Get list of top Yearn vaults by TVL",
{
limit: z.number().optional().describe("Number of vaults to return")
},
async (params) => {
try {
const limit = params.limit || 10
const topVaults = [
{ name: "yvDAI", token: "DAI", apy: "~5%", tvl: "$100M+", address: "0xdA816459F1AB5631232FE5e97a05BBBb94970c95" },
{ name: "yvUSDC", token: "USDC", apy: "~4%", tvl: "$80M+", address: "0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE" },
{ name: "yvWETH", token: "WETH", apy: "~3%", tvl: "$60M+", address: "0xa258C4606Ca8206D8aA700cE2143D7db854D168c" },
{ name: "yvUSDT", token: "USDT", apy: "~4%", tvl: "$50M+", address: "0x7Da96a3891Add058AdA2E826306D812C638D87a7" },
{ name: "yvWBTC", token: "WBTC", apy: "~2%", tvl: "$40M+", address: "0xA696a63cc78DfFa1a63E9E50587C197387FF6C7E" },
{ name: "yvCurve-stETH", token: "stETH/ETH LP", apy: "~6%", tvl: "$35M+", address: "0x5B8C556B8b2a78696F0B9B830B3d67623122E270" },
{ name: "yvCurve-3pool", token: "3CRV", apy: "~5%", tvl: "$30M+", address: "0x84E13785B5a27879921D6F685f041421C7F482dA" },
{ name: "yvFRAX", token: "FRAX", apy: "~5%", tvl: "$25M+", address: "0xB4AdA607B9d6b2c9Ee07A275e9616B84AC560139" },
{ name: "yvLUSD", token: "LUSD", apy: "~4%", tvl: "$20M+", address: "0x378cb52b00F9D0921cb46dFc099CFf73b42419dC" },
{ name: "yvSUSD", token: "sUSD", apy: "~4%", tvl: "$15M+", address: "0xa5cA62D95D24A4a350983D5B8ac4EB8638887396" }
]
return {
content: [{
type: "text",
text: JSON.stringify({
vaults: topVaults.slice(0, limit),
totalVaults: topVaults.length,
note: "APYs are estimates and vary based on market conditions"
}, null, 2)
}]
}
} catch (error: any) {
Logger.error("Error getting top vaults:", error)
throw new Error(`Failed to get top vaults: ${error.message}`)
}
}
)
// Tool 3: Get user vault position
server.tool(
"yearn_get_user_position",
"Get user's position in a Yearn vault",
{
userAddress: z.string().describe("The user's wallet address"),
vaultAddress: z.string().describe("The Yearn vault address"),
rpcUrl: z.string().optional().describe("Custom RPC URL")
},
async (params) => {
try {
const provider = new ethers.JsonRpcProvider(params.rpcUrl || "https://eth.llamarpc.com")
const vaultAbi = [
"function balanceOf(address account) view returns (uint256)",
"function pricePerShare() view returns (uint256)",
"function decimals() view returns (uint8)"
]
const vault = new ethers.Contract(params.vaultAddress, vaultAbi, provider)
const [balance, pricePerShare, decimals] = await Promise.all([
vault.balanceOf(params.userAddress),
vault.pricePerShare(),
vault.decimals()
])
const underlyingValue = (balance * pricePerShare) / (10n ** BigInt(decimals))
return {
content: [{
type: "text",
text: JSON.stringify({
userAddress: params.userAddress,
vaultAddress: params.vaultAddress,
vaultShares: ethers.formatUnits(balance, decimals),
underlyingValue: ethers.formatUnits(underlyingValue, decimals),
pricePerShare: ethers.formatUnits(pricePerShare, decimals)
}, null, 2)
}]
}
} catch (error: any) {
Logger.error("Error getting user position:", error)
throw new Error(`Failed to get user position: ${error.message}`)
}
}
)
// Tool 4: Get vault strategies
server.tool(
"yearn_get_vault_strategies",
"Get strategies used by a Yearn vault",
{
vaultAddress: z.string().describe("The Yearn vault address")
},
async (params) => {
try {
return {
content: [{
type: "text",
text: JSON.stringify({
vaultAddress: params.vaultAddress,
note: "Vault strategies can be fetched from the vault's withdrawal queue",
commonStrategies: [
"Compound/Aave lending",
"Curve LP farming",
"Convex boosted farming",
"Yearn Juiced vaults",
"Single-sided staking"
],
strategyInfo: "Each strategy has allocation limits and performance fees"
}, null, 2)
}]
}
} catch (error: any) {
Logger.error("Error getting strategies:", error)
throw new Error(`Failed to get strategies: ${error.message}`)
}
}
)
Logger.info("✅ Registered Yearn Finance tools")
}