@kamino-finance/farms-sdk
Version:
398 lines (377 loc) • 12.1 kB
text/typescript
import { Address } from "@solana/kit";
import Decimal from "decimal.js";
import { FarmAndKey } from "../models";
import { FarmState } from "../@codegen/farms/accounts";
import { RewardType } from "../@codegen/farms/types";
import { lamportsToCollDecimal } from "./utils";
import { U64_MAX } from "./consts";
import { DEFAULT_PUBLIC_KEY } from "./pubkey";
export interface IFarmResponse {
config: FarmConfig;
state: FarmState;
}
export interface ILogger {
log: (...args: any[]) => void;
debug: (...args: any[]) => void;
warn: (...args: any[]) => void;
error: (...args: any[]) => void;
}
export const noOpLogger: ILogger = {
log: () => {},
debug: () => {},
warn: () => {},
error: () => {},
};
export interface ReserveInfo {
address: Address;
symbol: string;
farmCollateral: Address;
farmDebt: Address;
}
export interface MarketWithReserves {
address: Address;
marketName: string;
reserves: ReserveInfo[];
}
export interface StrategyInfo {
address: Address;
farm: Address;
}
export interface VaultInfo {
address: Address;
vaultFarm: Address;
}
export function getAllFarmConfigsAndStates({
allFarms,
logger,
markets,
strategies,
vaults,
}: {
allFarms: FarmAndKey[];
logger: ILogger;
markets: MarketWithReserves[];
strategies: StrategyInfo[];
vaults: VaultInfo[];
}): {
collateralFarms: IFarmResponse[];
debtFarms: IFarmResponse[];
strategyFarms: IFarmResponse[];
earnVaultFarms: IFarmResponse[];
standaloneFarms: IFarmResponse[];
} {
const fetchedFarmsForStratsAndReserves = new Set<Address>([]);
const collateralFarms: IFarmResponse[] = [];
const debtFarms: IFarmResponse[] = [];
const strategyFarms: IFarmResponse[] = [];
const earnVaultFarms: IFarmResponse[] = [];
const standaloneFarms: IFarmResponse[] = [];
for (const market of markets) {
for (const reserve of market.reserves) {
if (reserve.farmCollateral !== DEFAULT_PUBLIC_KEY) {
fetchedFarmsForStratsAndReserves.add(reserve.farmCollateral);
const farmStateCollateral = allFarms.find(
(farm) => farm.key === reserve.farmCollateral,
)?.farmState;
if (farmStateCollateral) {
const farmConfig = getFarmConfigType(
reserve.farmCollateral,
farmStateCollateral,
{
type: "reserve",
reserve: reserve.address,
reserveSymbol: reserve.symbol,
market: market.address,
marketName: market.marketName,
strategy: undefined,
vault: undefined,
},
);
if (farmConfig.scopePrices !== DEFAULT_PUBLIC_KEY) {
logger.log(
`farmPk: ${farmConfig.farmPubkey} scopePrice: ${farmConfig.scopePrices}`,
);
}
collateralFarms.push({
config: farmConfig,
state: farmStateCollateral,
});
} else {
logger.log("Could not fetch farm", reserve.farmCollateral);
}
}
if (reserve.farmDebt !== DEFAULT_PUBLIC_KEY) {
fetchedFarmsForStratsAndReserves.add(reserve.farmDebt);
const farmStateDebt = allFarms.find(
(farm) => farm.key === reserve.farmDebt,
)?.farmState;
if (farmStateDebt) {
const farmConfig = getFarmConfigType(
reserve.farmDebt,
farmStateDebt,
{
type: "reserve",
reserve: reserve.address,
reserveSymbol: reserve.symbol,
market: market.address,
marketName: market.marketName,
strategy: undefined,
vault: undefined,
},
);
if (farmConfig.scopePrices !== DEFAULT_PUBLIC_KEY) {
logger.log(
`farmPk: ${farmConfig.farmPubkey} scopePrice: ${farmConfig.scopePrices}`,
);
}
debtFarms.push({
config: farmConfig,
state: farmStateDebt,
});
} else {
logger.log("Could not fetch farm", reserve.farmDebt);
}
}
}
}
for (const strategy of strategies) {
const farmAddress = strategy.farm;
if (farmAddress !== DEFAULT_PUBLIC_KEY) {
fetchedFarmsForStratsAndReserves.add(farmAddress);
const farmState = allFarms.find(
(farm) => farm.key === farmAddress,
)?.farmState;
if (farmState) {
const farmConfig = getFarmConfigType(farmAddress, farmState, {
type: "strategy",
reserve: undefined,
reserveSymbol: undefined,
market: undefined,
marketName: undefined,
strategy: strategy.address,
vault: undefined,
});
// in case strategy is not set on farm side, we override value so we set on next upsert
farmConfig.strategyId = strategy.address;
if (farmConfig.scopePrices !== DEFAULT_PUBLIC_KEY) {
logger.log(
`farmPk: ${farmConfig.farmPubkey} scopePrice: ${farmConfig.scopePrices}`,
);
}
strategyFarms.push({
config: farmConfig,
state: farmState,
});
} else {
logger.log("Could not fetch farm", farmAddress);
}
}
}
for (const vault of vaults) {
const farmAddress = vault.vaultFarm;
if (farmAddress !== DEFAULT_PUBLIC_KEY) {
fetchedFarmsForStratsAndReserves.add(farmAddress);
const farmState = allFarms.find(
(farm) => farm.key === farmAddress,
)?.farmState;
if (farmState) {
const farmConfig = getFarmConfigType(farmAddress, farmState, {
type: "earnVault",
reserve: undefined,
reserveSymbol: undefined,
market: undefined,
marketName: undefined,
strategy: undefined,
vault: vault.address,
});
// in case vaultId is not set on farm side, we override value so we set on next upsert
farmConfig.vaultId = vault.address;
if (farmConfig.scopePrices !== DEFAULT_PUBLIC_KEY) {
logger.log(
`farmPk: ${farmConfig.farmPubkey} scopePrice: ${farmConfig.scopePrices}`,
);
}
earnVaultFarms.push({
config: farmConfig,
state: farmState,
});
} else {
logger.log("Could not fetch farm", farmAddress);
}
}
}
for (const farmAndKey of allFarms) {
// skip farms already processed as part of reserves, strategies, or vaults
if (fetchedFarmsForStratsAndReserves.has(farmAndKey.key)) {
continue;
}
const farmConfig = getFarmConfigType(farmAndKey.key, farmAndKey.farmState, {
type: "standalone",
reserve: undefined,
reserveSymbol: undefined,
market: undefined,
marketName: undefined,
strategy: undefined,
vault: undefined,
});
if (farmConfig.scopePrices !== DEFAULT_PUBLIC_KEY) {
logger.log(
`farmPk: ${farmConfig.farmPubkey} scopePrice: ${farmConfig.scopePrices}`,
);
}
standaloneFarms.push({
config: farmConfig,
state: farmAndKey.farmState,
});
}
return {
collateralFarms,
debtFarms,
strategyFarms,
earnVaultFarms,
standaloneFarms,
};
}
export type FarmConfig = {
farmMetadata: FarmMetadata;
farmPubkey: Address;
stakingTokenMint: Address;
withdrawAuthority: Address;
globalConfig: Address;
strategyId: Address;
vaultId: Address;
depositCapAmount: number;
rewards: Array<
| {
rewardTokenMint: Address;
rewardType: string;
rewardPerSecondDecimals: number;
minClaimDurationSeconds: number;
rewardCurve: Array<
| {
startTs: number;
rps: number;
}
| undefined
>;
rewardAvailable: number;
rewardToTopUp: number;
rewardToTopUpDurationDays: number;
}
| undefined
>;
farmAdmin: Address;
delegateAuthority: Address;
pendingFarmAdmin: Address;
scopePrices: Address;
scopePriceOracleId: string;
isRewardUserOnceEnabled: number;
scopeOracleMaxAge: number;
lockingMode: number;
lockingStart: number;
lockingDuration: number;
lockingEarlyWithdrawalPenaltyBps: number;
depositWarmupPeriod: number;
withdrawCooldownPeriod: number;
slashedAmountSpillAddress: Address;
delegatedRpsAdmin: Address;
secondDelegatedAuthority: Address;
};
export type FarmMetadata = {
type: string; // strategy or reserve or earnVault
reserve: Address | undefined;
reserveSymbol: string | undefined;
market: Address | undefined;
marketName: string | undefined;
strategy: Address | undefined;
vault: Address | undefined;
};
function getRewardType(rewardType: RewardType): string {
const name = RewardType[rewardType];
if (name === undefined) {
throw new Error(`Invalid reward type: ${rewardType}`);
}
return name;
}
export function getFarmConfigType(
farmKey: Address,
farmState: FarmState,
farmMetadata: FarmMetadata,
): FarmConfig {
return {
farmMetadata,
farmPubkey: farmKey,
stakingTokenMint: farmState.token.mint,
withdrawAuthority: farmState.withdrawAuthority,
globalConfig: farmState.globalConfig,
strategyId: farmState.strategyId, // reserve farm
vaultId: farmState.vaultId,
depositCapAmount: new Decimal(
farmState.depositCapAmount.toString(),
).toNumber(),
rewards: farmState.rewardInfos
.map((rewardInfo) => {
if (rewardInfo.token.mint !== DEFAULT_PUBLIC_KEY) {
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(),
};
}
return undefined;
})
.filter((point) => point !== undefined),
rewardAvailable: lamportsToCollDecimal(
new Decimal(rewardInfo.rewardsAvailable.toString()),
Number(rewardInfo.token.decimals),
)
.floor()
.toNumber(),
rewardToTopUp: 0,
rewardToTopUpDurationDays: 0,
};
}
return undefined;
})
.filter((rewardInfoConfig) => rewardInfoConfig !== undefined),
farmAdmin: farmState.farmAdmin,
pendingFarmAdmin: farmState.pendingFarmAdmin,
delegateAuthority: farmState.delegateAuthority,
isRewardUserOnceEnabled: farmState.isRewardUserOnceEnabled,
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,
delegatedRpsAdmin: farmState.delegatedRpsAdmin,
secondDelegatedAuthority: farmState.secondDelegatedAuthority,
};
}