@kamino-finance/farms-sdk
Version:
937 lines • 69.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.calcAvgBoost = exports.Farms = exports.SIZE_GLOBAL_CONFIG = exports.SIZE_FARM_STATE = void 0;
exports.getCurrentTimeUnit = getCurrentTimeUnit;
exports.calculatePendingWithdrawalCooldownStatus = calculatePendingWithdrawalCooldownStatus;
exports.getCurrentRps = getCurrentRps;
const accounts_1 = require("@kamino-finance/scope-sdk/dist/@codegen/scope/accounts");
const kit_1 = require("@solana/kit");
const decimal_js_1 = __importDefault(require("decimal.js"));
const accounts_2 = require("./@codegen/farms/accounts");
const programs_1 = require("./@codegen/farms/programs");
const index_1 = require("./@codegen/farms/types/index");
const consts_1 = require("./consts");
const utils_1 = require("./utils");
const arrayUtils_1 = require("./utils/arrayUtils");
const batch_1 = require("./utils/batch");
const farmOperations = __importStar(require("./utils/operations"));
const token_1 = require("./utils/token");
const token_2 = require("@solana-program/token");
const exponential_backoff_1 = require("exponential-backoff");
const fzstd_1 = require("fzstd");
const bn_js_1 = __importDefault(require("bn.js"));
const utils_2 = require("./utils");
const consts_2 = require("./utils/consts");
const farms_1 = require("./utils/farms");
const option_1 = require("./utils/option");
const accountSizes_1 = require("./accountSizes");
Object.defineProperty(exports, "SIZE_FARM_STATE", { enumerable: true, get: function () { return accountSizes_1.SIZE_FARM_STATE; } });
Object.defineProperty(exports, "SIZE_GLOBAL_CONFIG", { enumerable: true, get: function () { return accountSizes_1.SIZE_GLOBAL_CONFIG; } });
const ZERO_BN = 0n;
const base64Encoder = (0, kit_1.getBase64Encoder)();
const SOLANA_API_RETRY = {
maxDelay: 10 * 1000,
numOfAttempts: 3,
retry: (e, attemptNumber) => {
// silent retry
return true;
},
};
class Farms {
_connection;
_farmsProgramId;
constructor(connection, farmsProgramId = programs_1.FARMS_PROGRAM_ADDRESS) {
this._connection = connection;
this._farmsProgramId = farmsProgramId;
}
getConnection() {
return this._connection;
}
getProgramID() {
return this._farmsProgramId;
}
async getAllUserStatesForUser(user) {
let filters = [];
filters.push({
memcmp: {
bytes: user.toString(),
offset: 48n,
encoding: "base58",
},
});
filters.push({ dataSize: BigInt((0, accounts_2.getUserStateSize)()) });
const decoder = (0, accounts_2.getUserStateDecoder)();
return (await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters,
encoding: "base64",
})
.send()).map((x) => {
const userAndKey = {
userState: decoder.decode(base64Encoder.encode(x.account.data[0])),
key: x.pubkey,
};
return userAndKey;
});
}
async getUserStatesForUserAndFarm(user, farm) {
let filters = [];
filters.push({
memcmp: {
bytes: user.toString(),
offset: 48n,
encoding: "base58",
},
});
filters.push({
memcmp: {
bytes: farm.toString(),
offset: 16n,
encoding: "base58",
},
});
filters.push({ dataSize: BigInt((0, accounts_2.getUserStateSize)()) });
const decoder = (0, accounts_2.getUserStateDecoder)();
return (await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters,
encoding: "base64",
})
.send()).map((x) => {
const userAndKey = {
userState: decoder.decode(base64Encoder.encode(x.account.data[0])),
key: x.pubkey,
};
return userAndKey;
});
}
async getAllUserStates() {
const decoder = (0, accounts_2.getUserStateDecoder)();
return (await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters: [{ dataSize: BigInt((0, accounts_2.getUserStateSize)()) }],
encoding: "base64",
})
.send()).map((x) => {
const userAndKey = {
userState: decoder.decode(base64Encoder.encode(x.account.data[0])),
key: x.pubkey,
};
return userAndKey;
});
}
async getAllUserStatesWithFilter(isFarmDelegated) {
const decoder = (0, accounts_2.getUserStateDecoder)();
return (await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters: [
{ dataSize: BigInt((0, accounts_2.getUserStateSize)()) },
{
memcmp: {
offset: 80n,
bytes: (isFarmDelegated ? "2" : "1"),
encoding: "base58",
},
},
],
encoding: "base64",
})
.send()).map((x) => {
const userAndKey = {
userState: decoder.decode(base64Encoder.encode(x.account.data[0])),
key: x.pubkey,
};
return userAndKey;
});
}
/**
* Get all farms user states from an async generator filled with batches of max 100 user states each
* @example
* const userStateGenerator = farms.batchGetAllUserStates();
* for await (const userStates of userStateGenerator) {
* console.log('got a batch of user states:', userStates.length);
* }
* @param isFarmDelegated - Optional filter to get only user states for farms that are delegated or not
*/
async *batchGetAllUserStates(isFarmDelegated) {
// Get all farms first and then get user states for each farm
let farms = await this.getAllFarmStates();
if (isFarmDelegated !== undefined) {
farms = farms.filter((farm) => Boolean(farm.farmState.isFarmDelegated) === isFarmDelegated);
}
for (const farm of farms) {
const farmUserStates = await (0, exponential_backoff_1.backOff)(() => this.getAllUserStatesForFarm(farm.key), SOLANA_API_RETRY);
if (farmUserStates.length > 0) {
// Process in smaller batches to avoid memory issues
for (const batch of (0, arrayUtils_1.chunks)(farmUserStates, 100)) {
yield batch;
}
}
}
}
async getAllUserStatesForFarm(farm) {
const decoder = (0, accounts_2.getUserStateDecoder)();
return ((await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters: [
{ dataSize: BigInt((0, accounts_2.getUserStateSize)()) },
{
memcmp: {
offset: 8n + 8n,
bytes: farm.toString(),
encoding: "base58",
},
},
],
encoding: "base64+zstd",
})
.send())
// TODO: type properly when base64+zstd encoding type is available in @solana/kit
.map((x) => {
const compressedData = new Uint8Array(base64Encoder.encode(x.account.data[0]));
const decompressedData = (0, fzstd_1.decompress)(compressedData);
const userAndKey = {
userState: decoder.decode(decompressedData),
key: x.pubkey,
};
return userAndKey;
}));
}
async getFarmsForMint(mint) {
let filters = [];
filters.push({
memcmp: {
bytes: mint.toString(),
offset: 72n,
encoding: "base58",
},
});
filters.push({ dataSize: BigInt((0, accounts_2.getFarmStateSize)()) });
const decoder = (0, accounts_2.getFarmStateDecoder)();
return (await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters,
encoding: "base64",
})
.send()).map((x) => {
const farmAndKey = {
farmState: decoder.decode(base64Encoder.encode(x.account.data[0])),
key: x.pubkey,
};
return farmAndKey;
});
}
async getAllFarmStates() {
const decoder = (0, accounts_2.getFarmStateDecoder)();
return (await this._connection
.getProgramAccounts(this._farmsProgramId, {
filters: [{ dataSize: BigInt((0, accounts_2.getFarmStateSize)()) }],
encoding: "base64",
})
.send())
.map((x) => {
try {
const farmAndKey = {
farmState: decoder.decode(base64Encoder.encode(x.account.data[0])),
key: x.pubkey,
};
return farmAndKey;
}
catch (err) {
return null;
}
})
.filter((x) => x !== null);
}
/**
* Get all farm configs and states categorized by type where possible (otehrwise standalone)
* @param markets - Pre-fetched market data -> fetch via KaminoMarket.load() from klend-sdk
* @param strategies - Pre-fetched strategy data -> fetch via Kamino.getAllStrategiesWithFilters() from kliquidity-sdk
* @param vaults - Pre-fetched vault data -> fetch via KaminoManager.getAllVaults() from klend-sdk
* @param logger - Optional logger for debugging
*/
async getAllConfigsAndStates({ markets, strategies, vaults, logger = farms_1.noOpLogger, }) {
const allFarms = await this.getAllFarmStates();
return (0, farms_1.getAllFarmConfigsAndStates)({
allFarms,
markets,
strategies,
vaults,
logger,
});
}
async getAllFarmStatesByPubkeys(keys) {
const farmAndKeys = [];
const farmStates = await (0, batch_1.batchFetch)(keys, async (chunk) => await this.fetchMultipleFarmStatesWithCheckedSize(chunk));
farmStates.forEach((farmState, index) => {
if (farmState) {
farmAndKeys.push({ farmState: farmState, key: keys[index] });
}
});
return farmAndKeys;
}
async getStakedAmountForFarm(farm) {
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, farm);
if (!farmAccount.exists) {
throw Error("No Farm found");
}
const farmState = farmAccount.data;
return (0, utils_1.lamportsToCollDecimal)(new decimal_js_1.default((0, utils_1.scaleDownWads)(farmState.totalActiveStakeScaled)), Number(farmState.token.decimals));
}
async getStakedAmountForMintForFarm(_mint, farm) {
return this.getStakedAmountForFarm(farm);
}
async getStakedAmountForMint(mint) {
const farms = await this.getFarmsForMint(mint);
let totalStaked = new decimal_js_1.default(0);
for (let index = 0; index < farms.length; index++) {
totalStaked = totalStaked.add((0, utils_1.lamportsToCollDecimal)(new decimal_js_1.default(farms[index].farmState.totalStakedAmount.toString()), Number(farms[index].farmState.token.decimals)));
}
return totalStaked;
}
async getLockupDurationAndExpiry(farm, user, timestampNow) {
let userStateAddress = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farm, user);
let userStateAccount = await (0, accounts_2.fetchMaybeUserState)(this._connection, userStateAddress);
let farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, farm);
if (!farmAccount.exists) {
throw new Error("Error fetching farm state");
}
const farmState = farmAccount.data;
let lockingMode = Number(farmState.lockingMode);
let lockingDuration = Number(farmState.lockingDuration);
let penalty = Number(farmState.lockingEarlyWithdrawalPenaltyBps);
if (penalty !== 0 && penalty !== 10000) {
throw "Early withdrawal penalty is not supported yet";
}
if (penalty > 10000) {
throw "Early withdrawal penalty is too high";
}
let lockingStart = 0;
if (lockingMode == index_1.LockingMode.None) {
return {
farmLockupOriginalDuration: 0,
farmLockupExpiry: 0,
lockupRemainingDuration: 0,
};
}
if (lockingMode == index_1.LockingMode.WithExpiry) {
// Locking starts globally for the entire farm
lockingStart = Number(farmState.lockingStartTimestamp);
}
if (lockingMode == index_1.LockingMode.Continuous) {
// Locking starts for each user individually at each stake
// if the user has a state, else now
if (!userStateAccount.exists) {
lockingStart = timestampNow;
}
else {
const userState = userStateAccount.data;
lockingStart = Number(userState.lastStakeTs);
}
}
const timestampBeginning = lockingStart;
const timestampMaturity = lockingStart + lockingDuration;
if (timestampNow >= timestampMaturity) {
// Time has passed, no remaining
return {
farmLockupOriginalDuration: Number(farmState.lockingDuration),
farmLockupExpiry: timestampMaturity,
lockupRemainingDuration: 0,
};
}
if (timestampNow < timestampBeginning) {
// Time has not started, no remaining
return {
farmLockupOriginalDuration: Number(farmState.lockingDuration),
farmLockupExpiry: timestampMaturity,
lockupRemainingDuration: 0,
};
}
const timeRemaining = timestampMaturity - timestampNow;
const remainingLockedDurationSeconds = Math.max(timeRemaining, 0);
return {
farmLockupOriginalDuration: Number(farmState.lockingDuration),
farmLockupExpiry: timestampMaturity,
lockupRemainingDuration: remainingLockedDurationSeconds,
};
}
async getUserStateKeysForDelegatedFarm(user, farm, delegatees) {
if (delegatees) {
return this.getUserStateKeysForDelegatedFarmDeterministic(user, farm, delegatees);
}
const userStates = await this.getUserStatesForUserAndFarm(user, farm);
const userStateKeysForFarm = [];
for (let index = 0; index < userStates.length; index++) {
if (userStates[index].userState.farmState === farm) {
userStateKeysForFarm.push(userStates[index]);
}
}
if (userStateKeysForFarm.length === 0) {
throw Error("No user state found for user " + user + " for farm " + farm);
}
else {
return userStateKeysForFarm;
}
}
async getUserStateKeysForDelegatedFarmDeterministic(user, farm, delegatees) {
const userStateKeysForFarm = [];
const userStateAddresses = await Promise.all(delegatees.map(async (delegate) => {
return await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farm, delegate);
}));
const userStateAccounts = await (0, accounts_2.fetchAllMaybeUserState)(this._connection, userStateAddresses);
userStateAccounts.forEach((account, index) => {
if (account.exists && account.data.farmState === farm) {
userStateKeysForFarm.push({
key: userStateAddresses[index],
userState: account.data,
});
}
});
if (userStateKeysForFarm.length === 0) {
throw Error("No user state found for user " + user + " for farm " + farm);
}
else {
return userStateKeysForFarm;
}
}
async getOraclePrices(farmState) {
let oraclePrices = null;
if (farmState.scopePrices !== utils_1.DEFAULT_PUBLIC_KEY) {
oraclePrices = await accounts_1.OraclePrices.fetch(this._connection, farmState.scopePrices);
if (!oraclePrices) {
throw new Error("Error fetching oracle prces");
}
}
return oraclePrices;
}
filterFarmsForStrategies(farmStates, strategiesToInclude) {
if (strategiesToInclude) {
return farmStates.filter((farmState) => strategiesToInclude.has(farmState.farmState.strategyId));
}
return farmStates;
}
filterFarmsForVaults(farmStates, vaultsToInclude) {
if (vaultsToInclude) {
return farmStates.filter((farmState) => vaultsToInclude.has(farmState.farmState.vaultId));
}
return farmStates;
}
async getFarmStatesFromUserStates(userStates, strategiesToInclude, vaultsToInclude) {
const farmPks = new Set();
for (let i = 0; i < userStates.length; i++) {
farmPks.add(userStates[i].userState.farmState);
}
const farmStates = await (0, batch_1.batchFetch)(Array.from(farmPks), async (chunk) => await this.getAllFarmStatesByPubkeys(chunk));
if (!farmStates) {
throw new Error("Error fetching farms");
}
let farmStatesFiltered = this.filterFarmsForStrategies(farmStates, strategiesToInclude);
farmStatesFiltered = this.filterFarmsForVaults(farmStatesFiltered, vaultsToInclude);
return farmStatesFiltered;
}
getUserPendingRewards(userState, farmState, timestamp, oraclePrices) {
// calculate userState pending rewards
const userPendingRewardAmounts = [];
let hasReward = false;
for (let indexReward = 0; indexReward < farmState.rewardInfos.length; indexReward++) {
userPendingRewardAmounts[indexReward] = (0, utils_1.calculatePendingRewards)(farmState, userState, indexReward, timestamp, oraclePrices);
if (userPendingRewardAmounts[indexReward].gt(0)) {
hasReward = true;
}
}
return { userPendingRewardAmounts, hasReward };
}
async getAllFarmsForUser(user, timestamp, strategiesToInclude, vaultsToInclude) {
const userStates = await this.getAllUserStatesForUser(user);
const farmStatesFiltered = await this.getFarmStatesFromUserStates(userStates, strategiesToInclude, vaultsToInclude);
if (farmStatesFiltered.length === 0) {
// Return empty if no serializable farm states found
return new Map();
}
const userFarms = new Map();
for (let userState of userStates) {
let farmState = farmStatesFiltered.find((farmState) => farmState.key === userState.userState.farmState);
if (!farmState) {
// Skip farms that are not serializable anymore
continue;
}
let oraclePrices = await this.getOraclePrices(farmState.farmState);
const { userPendingRewardAmounts, hasReward } = this.getUserPendingRewards(userState.userState, farmState.farmState, timestamp, oraclePrices);
// add new userFarm state if non empty (has rewards or stake) and not already present
if (!userFarms.has(userState.userState.farmState)) {
const userFarm = {
userStateAddress: userState.key,
farm: userState.userState.farmState,
strategyId: farmState.farmState.strategyId,
delegateAuthority: farmState.farmState.delegateAuthority,
stakedToken: farmState.farmState.token.mint,
userState: userState.userState,
activeStakeByDelegatee: new Map(),
pendingDepositStakeByDelegatee: new Map(),
pendingWithdrawalUnstakeByDelegatee: new Map(),
pendingRewards: new Array(farmState.farmState.rewardInfos.length)
.fill(undefined)
.map(function (value, index) {
return {
rewardTokenMint: utils_1.DEFAULT_PUBLIC_KEY,
rewardTokenProgramId: farmState.farmState.rewardInfos[index].token.tokenProgram,
rewardType: farmState?.farmState.rewardInfos[index].rewardType || 0,
cumulatedPendingRewards: new decimal_js_1.default(0),
pendingRewardsByDelegatee: new Map(),
};
}),
};
if (new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.activeStakeScaled)).gt(0) ||
hasReward) {
// active stake by delegatee
userFarm.activeStakeByDelegatee.set(userState.userState.delegatee, (0, utils_1.lamportsToCollDecimal)(new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.activeStakeScaled)), Number(farmState.farmState.token.decimals)));
// pendingDepositStake by delegatee
userFarm.pendingDepositStakeByDelegatee.set(userState.userState.delegatee, new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.pendingDepositStakeScaled)));
// pendingWithdrawalUnstake by delegatee
userFarm.pendingWithdrawalUnstakeByDelegatee.set(userState.userState.delegatee, new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.pendingWithdrawalUnstakeScaled)));
// cumulating rewards
for (let indexReward = 0; indexReward < farmState.farmState.rewardInfos.length; indexReward++) {
userFarm.pendingRewards[indexReward].rewardTokenMint =
farmState.farmState.rewardInfos[indexReward].token.mint;
userFarm.pendingRewards[indexReward].cumulatedPendingRewards =
userFarm.pendingRewards[indexReward].cumulatedPendingRewards.add(userPendingRewardAmounts[indexReward]);
userFarm.pendingRewards[indexReward].pendingRewardsByDelegatee.set(userState.userState.delegatee, userPendingRewardAmounts[indexReward]);
}
// set updated userFarm
userFarms.set(userState.userState.farmState, userFarm);
}
else {
// skip as we are not accounting for empty userFarms
continue;
}
}
}
return userFarms;
}
async getRewardsAPYForStrategy(strategy) {
const farmIncentives = await (0, utils_2.getRewardsApyForStrategy)(this.getConnection(), strategy);
return farmIncentives;
}
async getAllFarmsForUserMultiState(user, timestamp, strategiesToInclude, vaultsToInclude) {
const userStates = await this.getAllUserStatesForUser(user);
const farmStatesFiltered = await this.getFarmStatesFromUserStates(userStates, strategiesToInclude, vaultsToInclude);
if (farmStatesFiltered.length === 0) {
// Return empty if no serializable farm states found
return new Map();
}
const userFarmsByFarm = new Map();
for (let userState of userStates) {
let farmState = farmStatesFiltered.find((farmState) => farmState.key === userState.userState.farmState);
if (!farmState) {
// Skip farms that are not serializable anymore
continue;
}
let oraclePrices = await this.getOraclePrices(farmState.farmState);
const { userPendingRewardAmounts, hasReward } = this.getUserPendingRewards(userState.userState, farmState.farmState, timestamp, oraclePrices);
// Only add if there's a reward or active stake
if (!new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.activeStakeScaled)).gt(0) &&
!hasReward) {
continue;
}
// Create a new UserFarm instance
const userFarm = {
userStateAddress: userState.key,
farm: userState.userState.farmState,
strategyId: farmState.farmState.strategyId,
delegateAuthority: farmState.farmState.delegateAuthority,
stakedToken: farmState.farmState.token.mint,
userState: userState.userState,
activeStakeByDelegatee: new Map(),
pendingDepositStakeByDelegatee: new Map(),
pendingWithdrawalUnstakeByDelegatee: new Map(),
pendingRewards: new Array(farmState.farmState.rewardInfos.length)
.fill(undefined)
.map(function (value, index) {
return {
rewardTokenMint: farmState.farmState.rewardInfos[index].token.mint,
rewardTokenProgramId: farmState.farmState.rewardInfos[index].token.tokenProgram,
rewardType: farmState?.farmState.rewardInfos[index].rewardType || 0,
cumulatedPendingRewards: new decimal_js_1.default(0),
pendingRewardsByDelegatee: new Map(),
};
}),
};
// active stake by delegatee
userFarm.activeStakeByDelegatee.set(userState.userState.delegatee, (0, utils_1.lamportsToCollDecimal)(new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.activeStakeScaled)), Number(farmState.farmState.token.decimals)));
// pendingDepositStake by delegatee
userFarm.pendingDepositStakeByDelegatee.set(userState.userState.delegatee, new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.pendingDepositStakeScaled)));
// pendingWithdrawalUnstake by delegatee
userFarm.pendingWithdrawalUnstakeByDelegatee.set(userState.userState.delegatee, new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.pendingWithdrawalUnstakeScaled)));
// cumulating rewards
for (let indexReward = 0; indexReward < farmState.farmState.rewardInfos.length; indexReward++) {
userFarm.pendingRewards[indexReward].rewardTokenMint =
farmState.farmState.rewardInfos[indexReward].token.mint;
userFarm.pendingRewards[indexReward].cumulatedPendingRewards =
userPendingRewardAmounts[indexReward];
userFarm.pendingRewards[indexReward].pendingRewardsByDelegatee.set(userState.userState.delegatee, userPendingRewardAmounts[indexReward]);
}
// Add the userFarm to the array for the corresponding farm
if (!userFarmsByFarm.has(userState.userState.farmState)) {
userFarmsByFarm.set(userState.userState.farmState, [userFarm]);
}
else {
userFarmsByFarm.get(userState.userState.farmState).push(userFarm);
}
}
return userFarmsByFarm;
}
async getUserStateKeyForUndelegatedFarm(user, farmAddress) {
const userStateAddress = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farmAddress, user);
const userStateAccount = await (0, accounts_2.fetchMaybeUserState)(this._connection, userStateAddress);
if (!userStateAccount.exists) {
throw new Error(`User state not found ${userStateAddress.toString()}`);
}
return { key: userStateAddress, userState: userStateAccount.data };
}
async getCurrentTimeUnitForFarm(farmState) {
// Use a finalized slot so cooldown status is conservative; processed slots can also lack block time.
const slot = await this._connection
.getSlot({ commitment: "finalized" })
.send();
if (farmState.timeUnit === index_1.TimeUnit.Slots) {
return new bn_js_1.default(slot.toString());
}
if (farmState.timeUnit === index_1.TimeUnit.Seconds) {
const timestamp = await this._connection.getBlockTime(slot).send();
if (timestamp === null) {
throw new Error(`Could not resolve block time for slot ${slot.toString()}`);
}
return new bn_js_1.default(timestamp.toString());
}
throw new Error(`Unsupported farm time unit ${farmState.timeUnit}`);
}
async getPendingWithdrawalCooldownStatus(userStateAddress) {
const userStateAccount = await (0, accounts_2.fetchMaybeUserState)(this._connection, userStateAddress);
if (!userStateAccount.exists) {
throw new Error(`User state not found ${userStateAddress.toString()}`);
}
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, userStateAccount.data.farmState);
if (!farmAccount.exists) {
throw new Error(`Farm state not found ${userStateAccount.data.farmState.toString()}`);
}
const currentTimeUnit = await this.getCurrentTimeUnitForFarm(farmAccount.data);
return calculatePendingWithdrawalCooldownStatus(farmAccount.data, userStateAccount.data, currentTimeUnit, userStateAddress);
}
async getPendingWithdrawalCooldownStatusForUser(user, farm) {
const farmAddress = typeof farm === "string" ? farm : farm.key;
const userStateAddress = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farmAddress, user);
if (typeof farm === "string") {
return this.getPendingWithdrawalCooldownStatus(userStateAddress);
}
const userStateAccount = await (0, accounts_2.fetchMaybeUserState)(this._connection, userStateAddress);
if (!userStateAccount.exists) {
throw new Error(`User state not found ${userStateAddress.toString()}`);
}
if (userStateAccount.data.farmState !== farmAddress) {
throw new Error(`User state ${userStateAddress.toString()} belongs to farm ${userStateAccount.data.farmState.toString()}, not ${farmAddress.toString()}`);
}
if (farm.farmState.token.mint !== utils_1.DEFAULT_PUBLIC_KEY) {
const expectedFarmVault = await (0, utils_1.getFarmVaultPDA)(this._farmsProgramId, farmAddress, farm.farmState.token.mint);
if (farm.farmState.farmVault !== expectedFarmVault) {
throw new Error(`Farm state data does not match farm address ${farmAddress.toString()}`);
}
}
const currentTimeUnit = await this.getCurrentTimeUnitForFarm(farm.farmState);
return calculatePendingWithdrawalCooldownStatus(farm.farmState, userStateAccount.data, currentTimeUnit, userStateAddress);
}
async getPendingWithdrawalCooldownStatusForWalletAndFarm(wallet, farm) {
return this.getPendingWithdrawalCooldownStatusForUser(wallet, farm);
}
async getUserTokensInUndelegatedFarm(user, farm, tokenDecimals) {
const userState = await this.getUserStateKeyForUndelegatedFarm(user, farm);
return (0, utils_1.lamportsToCollDecimal)(new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.userState.activeStakeScaled)), tokenDecimals);
}
async getUserForUndelegatedFarm(user, farmAddress, timestamp) {
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, farmAddress);
if (!farmAccount.exists) {
throw new Error(`Farm not found ${farmAddress.toString()}`);
}
const farmState = farmAccount.data;
const userStateAddress = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farmAddress, user);
const userStateAccount = await (0, accounts_2.fetchMaybeUserState)(this._connection, userStateAddress);
if (!userStateAccount.exists) {
throw new Error(`User state not found ${userStateAddress.toString()}`);
}
const userState = userStateAccount.data;
const userFarm = {
userStateAddress: userStateAddress,
farm: farmAddress,
userState,
strategyId: farmState.strategyId,
delegateAuthority: farmState.delegateAuthority,
stakedToken: farmState.token.mint,
activeStakeByDelegatee: new Map(),
pendingDepositStakeByDelegatee: new Map(),
pendingWithdrawalUnstakeByDelegatee: new Map(),
pendingRewards: new Array(farmState.rewardInfos.length)
.fill(undefined)
.map(function (value, index) {
return {
rewardTokenMint: utils_1.DEFAULT_PUBLIC_KEY,
rewardTokenProgramId: farmState?.rewardInfos[index].token.tokenProgram,
rewardType: farmState?.rewardInfos[index].rewardType || 0,
cumulatedPendingRewards: new decimal_js_1.default(0),
pendingRewardsByDelegatee: new Map(),
};
}),
};
// active stake
userFarm.activeStakeByDelegatee.set(user, (0, utils_1.lamportsToCollDecimal)(new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.activeStakeScaled)), Number(farmState.token.decimals)));
// pendingDepositStake
userFarm.pendingDepositStakeByDelegatee.set(user, new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.pendingDepositStakeScaled)));
// pendingWithdrawalUnstake
userFarm.pendingWithdrawalUnstakeByDelegatee.set(user, new decimal_js_1.default((0, utils_1.scaleDownWads)(userState.pendingWithdrawalUnstakeScaled)));
// get oraclePrices
let oraclePrices = null;
if (farmState.scopePrices !== utils_1.DEFAULT_PUBLIC_KEY) {
oraclePrices = await accounts_1.OraclePrices.fetch(this._connection, farmState.scopePrices);
if (!oraclePrices) {
throw new Error("Error fetching oracle prices");
}
}
const userPendingRewardAmounts = [];
for (let indexReward = 0; indexReward < farmState.rewardInfos.length; indexReward++) {
// calculate pending rewards
userPendingRewardAmounts[indexReward] = (0, utils_1.calculatePendingRewards)(farmState, userState, indexReward, timestamp, oraclePrices);
userFarm.pendingRewards[indexReward].rewardTokenMint =
farmState.rewardInfos[indexReward].token.mint;
userFarm.pendingRewards[indexReward].cumulatedPendingRewards =
userPendingRewardAmounts[indexReward];
userFarm.pendingRewards[indexReward].pendingRewardsByDelegatee.set(user, userPendingRewardAmounts[indexReward]);
}
return userFarm;
}
async createNewUserIx(authority, farm, user = authority.address, delegatee = user) {
const userState = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farm, user);
const ix = farmOperations.initializeUser(farm, user, userState, authority, delegatee);
return ix;
}
async stakeIx(user, farm, amountLamports, stakeTokenMint, scopePrices) {
const farmVault = await (0, utils_1.getFarmVaultPDA)(this._farmsProgramId, farm, stakeTokenMint);
const userStatePk = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farm, user.address);
const userTokenAta = await (0, token_1.getAssociatedTokenAddress)(user.address, stakeTokenMint, token_2.TOKEN_PROGRAM_ADDRESS);
const ix = farmOperations.stake(user, userStatePk, userTokenAta, farm, farmVault, stakeTokenMint, scopePrices, (0, utils_1.decimalToBN)(amountLamports));
return ix;
}
async unstakeIx(user, farm, amountLamports, scopePrices) {
const userStatePk = await (0, utils_1.getUserStatePDA)(this._farmsProgramId, farm, user.address);
const ix = farmOperations.unstake(user, userStatePk, farm, scopePrices, (0, utils_1.decimalToBN)(amountLamports));
return ix;
}
async withdrawUnstakedDepositIx(user, userState, farmState, stakeTokenMint) {
const userTokenAta = await (0, token_1.getAssociatedTokenAddress)(user.address, stakeTokenMint, token_2.TOKEN_PROGRAM_ADDRESS);
const farmVault = await (0, utils_1.getFarmVaultPDA)(this._farmsProgramId, farmState, stakeTokenMint);
const farmVaultsAuthority = await (0, utils_1.getFarmAuthorityPDA)(this._farmsProgramId, farmState);
return farmOperations.withdrawUnstakedDeposit(user, userState, farmState, userTokenAta, farmVault, farmVaultsAuthority);
}
async claimForUserForFarmRewardIx(user, farm, rewardMint, isDelegated, rewardIndex = -1, delegatees) {
const ixns = [];
const ataIxns = [];
const userStatesAndKeys = isDelegated
? await this.getUserStateKeysForDelegatedFarm(user.address, farm, delegatees)
: [await this.getUserStateKeyForUndelegatedFarm(user.address, farm)];
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, farm);
if (!farmAccount.exists) {
throw new Error(`Farm not found ${farm.toString()}`);
}
const farmState = farmAccount.data;
const treasuryVault = await (0, utils_1.getTreasuryVaultPDA)(this._farmsProgramId, farmState.globalConfig, rewardMint);
// find rewardIndex if not defined
if (rewardIndex === -1) {
rewardIndex = farmState.rewardInfos.findIndex((r) => r.token.mint === rewardMint);
}
const rewardsTokenProgram = farmState.rewardInfos[rewardIndex].token.tokenProgram;
const userRewardAta = await (0, token_1.getAssociatedTokenAddress)(user.address, rewardMint, rewardsTokenProgram);
const ataExists = await (0, utils_1.checkIfAccountExists)(this._connection, userRewardAta);
if (!ataExists) {
const [, ix] = await (0, token_1.createAssociatedTokenAccountIdempotentInstruction)(user, rewardMint, rewardsTokenProgram, user.address, userRewardAta);
ataIxns.push([rewardMint, ix]);
}
for (let userStateIndex = 0; userStateIndex < userStatesAndKeys.length; userStateIndex++) {
const ix = farmOperations.harvestReward(user, userStatesAndKeys[userStateIndex].key, userRewardAta, farmState.globalConfig, treasuryVault, farm, rewardMint, farmState.rewardInfos[rewardIndex].rewardsVault, farmState.farmVaultsAuthority, (0, option_1.getScopePricesFromFarm)(farmState), rewardsTokenProgram, rewardIndex);
ixns.push(ix);
}
return [ataIxns, ixns];
}
async claimForUserForFarmAllRewardsIx(payer, user, farm, isDelegated, delegatees) {
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, farm);
const userStatesAndKeys = isDelegated
? await this.getUserStateKeysForDelegatedFarm(user, farm, delegatees)
: [await this.getUserStateKeyForUndelegatedFarm(user, farm)];
const ixs = new Array();
if (!farmAccount.exists) {
throw new Error(`Farm not found ${farm.toString()}`);
}
const farmState = farmAccount.data;
const timestampSeconds = Date.now() / 1000;
for (let userStateIndex = 0; userStateIndex < userStatesAndKeys.length; userStateIndex++) {
for (let rewardIndex = 0; rewardIndex < Number(farmState.numRewardTokens); rewardIndex++) {
const rewardMint = farmState.rewardInfos[rewardIndex].token.mint;
const rewardTokenProgram = farmState.rewardInfos[rewardIndex].token.tokenProgram;
const rewardMinClaimDurationSeconds = Number(farmState.rewardInfos[rewardIndex].minClaimDurationSeconds);
const lastClaimTsSeconds = Number(userStatesAndKeys[userStateIndex].userState.lastClaimTs[rewardIndex]);
if (timestampSeconds - lastClaimTsSeconds <
rewardMinClaimDurationSeconds) {
continue;
}
const userRewardAta = await (0, token_1.getAssociatedTokenAddress)(user, rewardMint, rewardTokenProgram);
const treasuryVault = await (0, utils_1.getTreasuryVaultPDA)(this._farmsProgramId, farmState.globalConfig, rewardMint);
const ataExists = await (0, utils_1.checkIfAccountExists)(this._connection, userRewardAta);
if (!ataExists && payer.address === user) {
const [, ix] = await (0, token_1.createAssociatedTokenAccountIdempotentInstruction)(payer, rewardMint, rewardTokenProgram, user, userRewardAta);
ixs.push(ix);
}
ixs.push(farmOperations.harvestReward(payer, userStatesAndKeys[userStateIndex].key, userRewardAta, farmState.globalConfig, treasuryVault, farm, rewardMint, farmState.rewardInfos[rewardIndex].rewardsVault, farmState.farmVaultsAuthority, (0, option_1.getScopePricesFromFarm)(farmState), rewardTokenProgram, rewardIndex));
}
}
return ixs;
}
async transferOwnershipIx(user, userState, newUser) {
const userStateAccount = await (0, accounts_2.fetchMaybeUserState)(this._connection, userState);
if (!userStateAccount.exists) {
throw new Error(`User state not found ${userState.toString()}`);
}
const userStateData = userStateAccount.data;
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, userStateData.farmState);
if (!farmAccount.exists) {
throw new Error(`Farm state not found ${userStateData.farmState.toString()}`);
}
const farmState = farmAccount.data;
this.validateFarmStateForTransferOwnership(farmState);
const newOwnerUserState = await (0, utils_1.getUserStatePDA)(programs_1.FARMS_PROGRAM_ADDRESS, userStateData.farmState, newUser);
return farmOperations.transferOwnership(user, userState, newUser, userStateData.farmState, newOwnerUserState, (0, option_1.getScopePricesFromFarm)(farmState));
}
validateFarmStateForTransferOwnership(farmState) {
if (Number(farmState.lockingMode) !== index_1.LockingMode.None) {
throw new Error("Transfer ownership is not allowed for farms with a locking mode");
}
if (farmState.isFarmDelegated) {
throw new Error("Transfer ownership is not allowed for delegated farms");
}
if (farmState.withdrawalCooldownPeriod > 0) {
throw new Error("Transfer ownership is not allowed for farms with a withdrawal cooldown period");
}
}
async transferOwnershipAllUserStatesIx(user, newUser) {
const userStates = await this.getAllUserStatesForUser(user.address);
const farms = await this.getFarmStatesFromUserStates(userStates);
const ixs = new Array();
for (let index = 0; index < userStates.length; index++) {
const farmAddress = userStates[index].userState.farmState;
const farmState = farms.find((farm) => farm.key === farmAddress);
if (!farmState) {
throw new Error(`Farm state not found for user state ${userStates[index].key}`);
}
this.validateFarmStateForTransferOwnership(farmState.farmState);
const newOwnerUserState = await (0, utils_1.getUserStatePDA)(programs_1.FARMS_PROGRAM_ADDRESS, farmAddress, newUser);
ixs[index] = farmOperations.transferOwnership(user, userStates[index].key, newUser, farmAddress, newOwnerUserState, (0, option_1.getScopePricesFromFarm)(farmState.farmState));
}
return ixs;
}
async createFarmIxs(admin, farm, globalConfig, stakeTokenMint) {
const farmVault = await (0, utils_1.getFarmVaultPDA)(this._farmsProgramId, farm.address, stakeTokenMint);
const farmVaultAuthority = await (0, utils_1.getFarmAuthorityPDA)(this._farmsProgramId, farm.address);
let ixs = [];
ixs.push(await (0, utils_1.createKeypairRentExemptIx)(this.getConnection(), admin, farm, accountSizes_1.SIZE_FARM_STATE, this._farmsProgramId));
ixs.push(farmOperations.initializeFarm(globalConfig, admin, farm.address, farmVault, farmVaultAuthority, stakeTokenMint));
return ixs;
}
async createFarmDelegatedIx(admin, farm, globalConfig, farmDelegate) {
const farmVaultAuthority = await (0, utils_1.getFarmAuthorityPDA)(this._farmsProgramId, farm.address);
let ixs = [];
ixs.push(await (0, utils_1.createKeypairRentExemptIx)(this.getConnection(), admin, farm, accountSizes_1.SIZE_FARM_STATE, this._farmsProgramId));
ixs.push(farmOperations.initializeFarmDelegated(globalConfig, admin, farm.address, farmVaultAuthority, farmDelegate));
return ixs;
}
async addRewardToFarmIx(admin, globalConfig, farm, mint, tokenProgram) {
const globalConfigAccount = await (0, accounts_2.fetchMaybeGlobalConfig)(this._connection, globalConfig);
if (!globalConfigAccount.exists) {
throw new Error("Could not fetch global config");
}
const globalConfigState = globalConfigAccount.data;
const treasuryVault = await (0, utils_1.getTreasuryVaultPDA)(this._farmsProgramId, globalConfig, mint);
let farmVaultAuthority = await (0, utils_1.getFarmAuthorityPDA)(this._farmsProgramId, farm);
const rewardVault = await (0, utils_1.getRewardVaultPDA)(this._farmsProgramId, farm, mint);
const ix = farmOperations.initializeReward(globalConfig, globalConfigState.treasuryVaultsAuthority, treasuryVault, admin, farm, rewardVault, farmVaultAuthority, mint, tokenProgram);
return ix;
}
async addRewardAmountToFarmIx(payer, farm, mint, amount, rewardIndexOverride = -1, decimalsOverride = -1, tokenProgramOverride = token_2.TOKEN_PROGRAM_ADDRESS, scopePricesOverride = (0, kit_1.none)()) {
let decimals = decimalsOverride;
let rewardIndex = rewardIndexOverride;
let scopePrices = scopePricesOverride;
let tokenProgram = tokenProgramOverride;
if (rewardIndex == -1) {
const farmAccount = await (0, accounts_2.fetchMaybeFarmState)(this._connection, farm);
if (!farmAccount.exists) {
throw new Error(`Could not fetch farm state ${farm}`);
}
const farmState = farmAccount.data;
scopePrices = (0, option_1.getScopePricesFromFarm)(farmState);
for (let i = 0; farmState.rewardInfos.length; i++) {
if (farmState.rewardInfos[i].token.mint === mint) {
if (farmState.rewardInfos[i].token.tokenProgram !== utils_1.DEFAULT_PUBLIC_KEY) {
tokenProgram = farmState.rewardInfos[i].token.tokenProgram;
}
rewardIndex = i;
decimals = Number(farmState.rewardInfos[i].token.decimals);
break;
}
}
}
if (decimals == -1) {
throw new Error(`Could not find reward token ${mint}`);
}
let amountLamports = BigInt((0, utils_1.collToLamportsDecimal)(amount, decimals).floor().toFixed());
const payerRewardAta = await (0, token_1.getAssociatedTokenAddress)(payer.address, mint, tokenProgram);
let rewardVault = await (0, utils_1.getRewardVaultPDA)(this._farmsProgramId, farm, mint);
let farmVaultsAuthority = await (0, utils_1.getFarmAuthorityPDA)(this._farmsProgramId, farm);
const ix = farmOperations.addReward(payer, farm, rewardVault, farmVaultsAuthority, payerRewardAta, mint, scopePrices, rewardIndex, tokenProgram, amountLamports);
return ix;
}
async rewardUserOnceIx(delegateAuthority, farmState, userState, rewardMint, amountLamports, expectedRewardsIssuedCumulative, userStateId) {
const rewardIndex = farmState.farmState.rewardInfos.findIndex((r) => r.token.mint === rewardMint);
const ix = farmOperations.rewardUserOnce(delegateAuthority, farmState.key, userState, rewardIndex, amountLamports, expectedRewardsIssuedCumulative, userStateId);
return ix;
}
async withdrawRewardAmountFromFarmIx(