import VaultImpl from "@meteora-ag/vault-sdk"; import { Mint, unpackMint } from "@solana/spl-token"; import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed"); const keeperBaseUrl = "https://merv2-api.meteora.ag"; const lendingProgramIdsToName = new Map([ ["MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA", "MarginFi"], ["So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo", "Solend"], ["KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD", "Kamino"], ]); async function getVaultApy(tokenMint: PublicKey): Promise { const response = await fetch( `${keeperBaseUrl}/vault_state/${tokenMint.toBase58()}` ); return response .json() .then((data) => Math.ceil(Number(data.closest_apy) * 100) / 100); } async function showVaultDetails() { const mintAddresses = [ new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), new PublicKey("So11111111111111111111111111111111111111112"), ]; const mintAccounts = await connection.getMultipleAccountsInfo(mintAddresses); const mintMap = new Map(); for (let i = 0; i < mintAddresses.length; i++) { const mintAddress = mintAddresses[i]; const mintAccount = mintAccounts[i]; const mintState = unpackMint(mintAddress, mintAccount); mintMap.set(mintAddress.toBase58(), mintState); } const vaults = await VaultImpl.createMultiple(connection, mintAddresses); for (const vault of vaults) { const mintState = mintMap.get(vault.vaultState.tokenMint.toBase58()); console.log(`Vault for mint ${vault.vaultState.tokenMint.toBase58()}`); console.log(`Vault address: ${vault.vaultPda.toBase58()}`); // Total amount in vault const totalAmount = Number(vault.vaultState.totalAmount.toString()); const vaultTotalUiAmount = totalAmount / 10 ** mintState.decimals; console.log(`Total amount in vault: ${vaultTotalUiAmount}`); const lpMintSupply = Number(vault.tokenLpMint.supply.toString()); const virtualPrice = totalAmount / lpMintSupply; // Vault LP to token amount exchange rate console.log(`Virtual Price: ${virtualPrice}`); // This is the current max withdrawable amount from the vault after deduct locked to drip profits + token allocated to strategies const withdrawableAmount = await vault.getWithdrawableAmount(); const withdrawableUiAmount = withdrawableAmount / 10 ** mintState.decimals; console.log(`Withdrawable amount: ${withdrawableUiAmount}`); // The only way to get the current APY is to fetch it from the keeper API const apy = await getVaultApy(vault.vaultState.tokenMint); console.log(`APY: ${apy}%`); // Lending that the vault deposited to. This retrieve the info from onchain. User can get it from API https://merv2-api.meteora.ag/vault_state as well. const strategyAddresses = vault.vaultState.strategies.filter( (s) => !s.equals(PublicKey.default) ); const strategyAccounts = await connection.getMultipleAccountsInfo( strategyAddresses ); const strategyReserveLiquidityMap = new Map(); for (const strategy of strategyAccounts) { // TODO: Fix the SDK const reserveBytes = strategy.data.slice(8, 8 + 32); const reserveAddress = new PublicKey(reserveBytes); const liquidity = strategy.data.readBigInt64LE(8 + 32 + 32 + 1); const liquidityAmount = Number(liquidity.toString()) / 10 ** mintState.decimals; strategyReserveLiquidityMap.set( reserveAddress.toBase58(), liquidityAmount ); } const reserveAddresses = Array.from(strategyReserveLiquidityMap.keys()); const reserveAccounts = await connection.getMultipleAccountsInfo( reserveAddresses.map((addr) => new PublicKey(addr)) ); for (let i = 0; i < reserveAddresses.length; i++) { const reserveAddress = reserveAddresses[i]; const reserveAccount = reserveAccounts[i]; console.log(`Reserve: ${reserveAddress}`); console.log( `Liquidity: ${strategyReserveLiquidityMap.get(reserveAddress)}` ); console.log( `Lending Program: ${ lendingProgramIdsToName.get(reserveAccount.owner.toBase58()) || "Unknown" }` ); } console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); } } showVaultDetails().catch((error) => { console.error("Error fetching vault details:", error); });