UNPKG

@kamino-finance/farms-sdk

Version:
937 lines 69.5 kB
"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(