UNPKG

@hubbleprotocol/farms-sdk

Version:
321 lines (295 loc) 10.3 kB
import { getFarmsProgramId, lamportsToCollDecimal } from "../utils"; import { initializeClient } from "./utils"; import { Farms } from "../Farms"; import * as fs from "fs"; import { PublicKey } from "@solana/web3.js"; import { getMarketsFromApi, KaminoMarket, PublicKeySet, U64_MAX, } from "@kamino-finance/klend-sdk"; import { FarmState } from "../rpc_client/accounts"; import Decimal from "decimal.js"; import { RewardType } from "../rpc_client/types"; import { Kamino } from "@kamino-finance/kliquidity-sdk"; export async function downloadAllFarmConfigs(targetPath: string) { const admin = process.env.ADMIN!; const rpc = process.env.RPC!; const klendProgramId = new PublicKey(process.env.KLEND_PROGRAM_ID!); const lendingMarkets = await getMarketsFromApiData(klendProgramId); const env = initializeClient(rpc, admin, getFarmsProgramId(rpc), false); const farmClient = new Farms(env.provider.connection); const allFarms = await farmClient.getAllFarmStates(); const fetchedFarmsForStratsAndReserves = new PublicKeySet<PublicKey>([]); // Download for lending based on API markets - all reserve farms for (const market of lendingMarkets) { const kaminoMarket = await KaminoMarket.load( env.provider.connection, market.key, 450, klendProgramId, ); if (!kaminoMarket) { throw new Error("Kamino market not found"); } fs.mkdirSync(`${targetPath}/lending/${market.marketName}`, { recursive: true, }); for (const reserve of kaminoMarket.reserves.values()) { if (!reserve.state.farmCollateral.equals(PublicKey.default)) { fetchedFarmsForStratsAndReserves.add(reserve.state.farmCollateral); const farmStateCollateral = await FarmState.fetch( env.provider.connection, reserve.state.farmCollateral, ); if (farmStateCollateral) { const farmConfig = getFarmConfigType( reserve.state.farmCollateral, farmStateCollateral, { type: "reserve", reserve: reserve.address, market: market.key, strategy: undefined, }, ); fs.writeFileSync( `${targetPath}/lending/${market.marketName}/${ reserve!.symbol }-Collateral.json`, JSON.stringify(farmConfig, null, 2), ); } else { console.log( "Could not fetch farm", reserve.state.farmCollateral.toBase58(), ); } } if (!reserve.state.farmDebt.equals(PublicKey.default)) { fetchedFarmsForStratsAndReserves.add(reserve.state.farmDebt); const farmStateDebt = await FarmState.fetch( env.provider.connection, reserve.state.farmDebt, ); if (farmStateDebt) { const farmConfig = getFarmConfigType( reserve.state.farmDebt, farmStateDebt, { type: "reserve", reserve: reserve.address, market: market.key, strategy: undefined, }, ); fs.writeFileSync( `${targetPath}/lending/${market.marketName}/${ reserve!.symbol }-Debt.json`, JSON.stringify(farmConfig, null, 2), ); } else { console.log( "Could not fetch farm", reserve.state.farmDebt.toBase58(), ); } } } } // Download for yvaults all strategy farms const kamino = new Kamino("mainnet-beta", env.provider.connection); let strategies = await kamino.getAllStrategiesWithFilters({ strategyCreationStatus: "LIVE", }); fs.mkdirSync(`${targetPath}/yvaults/`, { recursive: true }); for (const strategy of strategies) { const farmAddress = strategy!.strategy.farm; if (!farmAddress.equals(PublicKey.default)) { fetchedFarmsForStratsAndReserves.add(farmAddress); const farmState = await FarmState.fetch( env.provider.connection, farmAddress, ); if (farmState) { const farmConfig = getFarmConfigType(farmAddress, farmState, { type: "strategy", reserve: undefined, market: undefined, strategy: strategy.address, }); // in case strategy is not set on farm side, we override value so we set on next upsert farmConfig.strategyId = strategy.address; fs.writeFileSync( `${targetPath}/yvaults/${strategy.address.toBase58()}.json`, JSON.stringify(farmConfig, null, 2), ); } else { console.log("Could not fetch farm", farmAddress.toBase58()); } } } // Download all standalone farms fs.mkdirSync(`${targetPath}/standaloneFarms/`, { recursive: true }); for (const farmAndKey of allFarms) { // skip farms already downloaded as part of reserves or strategies if (fetchedFarmsForStratsAndReserves.contains(farmAndKey.key)) { continue; } const farmConfig = getFarmConfigType(farmAndKey.key, farmAndKey.farmState, { type: "standalone", reserve: undefined, market: undefined, strategy: undefined, }); fs.writeFileSync( `${targetPath}/standaloneFarms/${farmAndKey.key.toBase58()}.json`, JSON.stringify(farmConfig, null, 2), ); } } export async function getMarketsFromApiData( programId: PublicKey, ): Promise<{ marketName: string; key: PublicKey }[]> { const markets: { marketName: string; key: PublicKey }[] = []; await getMarketsFromApi(programId, "API").then(function (response) { for (const marketData of response) { markets.push({ marketName: marketData.description.replace(" ", "-"), key: new PublicKey(marketData.lendingMarket), }); } }); return markets; } export type FarmConfig = { farmMetadata: FarmMetadata; farmPubkey: PublicKey; stakingTokenMint: PublicKey; withdrawAuthority: PublicKey; globalConfig: PublicKey; strategyId: PublicKey; depositCapAmount: number; rewards: Array< | { rewardTokenMint: PublicKey; rewardType: string; rewardPerSecondDecimals: number; minClaimDurationSeconds: number; rewardCurve: Array< | { startTs: number; rps: number; } | undefined >; rewardAvailable: number; rewardToTopUp: number; rewardToTopUpDurationDays: number; } | undefined >; pendingFarmAdmin: PublicKey; scopePrices: PublicKey; scopePriceOracleId: string; scopeOracleMaxAge: number; lockingMode: number; lockingStart: number; lockingDuration: number; lockingEarlyWithdrawalPenaltyBps: number; depositWarmupPeriod: number; withdrawCooldownPeriod: number; slashedAmountSpillAddress: PublicKey; }; export type FarmMetadata = { type: string; // strategy or reserve reserve: PublicKey | undefined; market: PublicKey | undefined; strategy: PublicKey | undefined; }; function getRewardType(rewardTypeNumber: number): string { switch (rewardTypeNumber) { case RewardType.Proportional.discriminator: return RewardType.Proportional.kind; case RewardType.Constant.discriminator: return RewardType.Constant.kind; default: throw new Error(`Invalid reward type: ${rewardTypeNumber}`); } } export function getFarmConfigType( farmKey: PublicKey, farmState: FarmState, farmMetadata: FarmMetadata, ): FarmConfig { return { farmMetadata, farmPubkey: farmKey, stakingTokenMint: farmState.token.mint, withdrawAuthority: farmState.withdrawAuthority, globalConfig: farmState.globalConfig, strategyId: farmState.strategyId, // reserve farm depositCapAmount: new Decimal( farmState.depositCapAmount.toString(), ).toNumber(), rewards: farmState.rewardInfos .map((rewardInfo) => { if (!rewardInfo.token.mint.equals(PublicKey.default)) { return { rewardTokenMint: rewardInfo.token.mint, rewardType: getRewardType(rewardInfo.rewardType), rewardPerSecondDecimals: rewardInfo.rewardsPerSecondDecimals, minClaimDurationSeconds: new Decimal( rewardInfo.minClaimDurationSeconds.toString(), ).toNumber(), rewardCurve: rewardInfo.rewardScheduleCurve.points .map((point) => { if ( new Decimal(point.rewardPerTimeUnit.toString()).toNumber() !== 0 || point.tsStart.toString() !== U64_MAX ) { return { startTs: new Decimal(point.tsStart.toString()).toNumber(), rps: new Decimal( point.rewardPerTimeUnit.toString(), ).toNumber(), }; } }) .filter((point) => point !== undefined), rewardAvailable: lamportsToCollDecimal( new Decimal(rewardInfo.rewardsAvailable.toString()), rewardInfo.token.decimals.toNumber(), ) .floor() .toNumber(), rewardToTopUp: 0, rewardToTopUpDurationDays: 0, }; } }) .filter((rewardInfoConfig) => rewardInfoConfig !== undefined), pendingFarmAdmin: farmState.pendingFarmAdmin, scopePrices: farmState.scopePrices, scopePriceOracleId: farmState.scopeOraclePriceId.toString(), scopeOracleMaxAge: new Decimal( farmState.scopeOracleMaxAge.toString(), ).toNumber(), lockingMode: new Decimal(farmState.lockingMode.toString()).toNumber(), lockingStart: new Decimal( farmState.lockingStartTimestamp.toString(), ).toNumber(), lockingDuration: new Decimal( farmState.lockingDuration.toString(), ).toNumber(), lockingEarlyWithdrawalPenaltyBps: new Decimal( farmState.lockingEarlyWithdrawalPenaltyBps.toString(), ).toNumber(), depositWarmupPeriod: farmState.depositWarmupPeriod, withdrawCooldownPeriod: farmState.withdrawalCooldownPeriod, slashedAmountSpillAddress: farmState.slashedAmountSpillAddress, }; }