UNPKG

aftermath-ts-sdk

Version:
437 lines (436 loc) 24.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FarmsStakedPosition = void 0; const caller_1 = require("../../general/utils/caller"); const farmsStakingPool_1 = require("./farmsStakingPool"); const fixedUtils_1 = require("../../general/utils/fixedUtils"); const utils_1 = require("../../general/utils"); const dayjs_1 = __importDefault(require("dayjs")); const farms_1 = require("./farms"); /** * The `FarmsStakedPosition` class represents a user's individual staked position * in a particular staking pool. It provides methods to query position details, * calculate potential rewards, lock/unlock stake, and build transactions * for depositing, unstaking, or harvesting rewards. */ class FarmsStakedPosition extends caller_1.Caller { // ========================================================================= // Constructor // ========================================================================= /** * Creates a `FarmsStakedPosition` instance for a user's staked position in a farm. * * @param stakedPosition - The on-chain data object representing the user's staked position. * @param trueLastHarvestRewardsTimestamp - Optionally overrides the last harvest time from the on-chain data. * @param config - Optional configuration for the underlying `Caller`. * @param Provider - Optional `AftermathApi` instance for transaction building. */ constructor(stakedPosition, trueLastHarvestRewardsTimestamp = undefined, config, Provider) { super(config, "farms"); this.stakedPosition = stakedPosition; this.Provider = Provider; // ========================================================================= // Public // ========================================================================= // ========================================================================= // Getters // ========================================================================= /** * Returns the version of the farm system that this position belongs to (1 or 2). */ this.version = () => { return this.stakedPosition.version; }; /** * Checks whether the position is still locked, based on the current time and the lock parameters. * * @param inputs - Contains a `FarmsStakingPool` instance to check for system constraints. * @returns `true` if the position is locked; otherwise, `false`. */ this.isLocked = (inputs) => { return !this.isUnlocked(inputs); }; /** * Checks whether the position has a non-zero lock duration. * * @returns `true` if the position was created with a lock duration > 0. */ this.isLockDuration = () => { return this.stakedPosition.lockDurationMs > 0; }; /** * Computes the timestamp (in ms) at which this position's lock will end. * * @returns The unlock timestamp (lock start + lock duration). */ this.unlockTimestamp = () => { return (this.stakedPosition.lockStartTimestamp + this.stakedPosition.lockDurationMs); }; /** * Computes the user's accrued rewards for each reward coin in this position, * returned as a `CoinsToBalance` object keyed by coin type. * * @param inputs - Contains a reference to the `FarmsStakingPool`. * @returns A mapping from `coinType` to the amount of earned rewards. */ this.rewardCoinsToClaimableBalance = (inputs) => { return this.stakedPosition.rewardCoins.reduce((acc, coin) => (Object.assign(Object.assign({}, acc), { [coin.coinType]: this.rewardsEarned(Object.assign(Object.assign({}, inputs), { coinType: coin.coinType })) })), {}); }; /** * Lists all reward coin types associated with this position. * * @returns An array of `CoinType` strings representing the reward coins. */ this.rewardCoinTypes = () => { return this.stakedPosition.rewardCoins.map((coin) => coin.coinType); }; /** * Returns only the reward coin types that currently have a non-zero claimable balance. * * @param inputs - Contains a reference to the `FarmsStakingPool`. * @returns An array of `CoinType` strings that have pending rewards > 0. */ this.nonZeroRewardCoinTypes = (inputs) => { return Object.entries(this.rewardCoinsToClaimableBalance(inputs)) .filter(([, val]) => val > BigInt(0)) .map(([key]) => key); }; /** * Retrieves the reward coin record for a specific coin type in this position. * * @param inputs - Must contain a `coinType` string to look up. * @throws If the coin type is not found in this position. * @returns The reward coin object from the position. */ this.rewardCoin = (inputs) => { const foundCoin = this.stakedPosition.rewardCoins.find((coin) => coin.coinType === inputs.coinType); if (!foundCoin) throw new Error("Invalid coin type"); return foundCoin; }; /** * Checks if this position has any claimable rewards across all reward coin types. * * @param inputs - Contains a reference to the `FarmsStakingPool`. * @returns `true` if there are unclaimed rewards; otherwise, `false`. */ this.hasClaimableRewards = (inputs) => { const { stakingPool } = inputs; return (utils_1.Helpers.sumBigInt(this.rewardCoinTypes().map((coinType) => this.rewardsEarned({ coinType, stakingPool, }))) > BigInt(0)); }; // ========================================================================= // Calculations // ========================================================================= /** * Calculates the current amount of earned rewards for a specific coin type, * factoring in any emission constraints and the pool's actual reward availability. * * @param inputs - Contains the `coinType` to check and a reference to the `FarmsStakingPool`. * @returns The total `BigInt` amount of rewards earned for the specified coin type. */ this.rewardsEarned = (inputs) => { if (inputs.stakingPool.rewardCoin(inputs).actualRewards === BigInt(0)) return BigInt(0); // this.updatePosition(inputs); const rewardCoin = this.rewardCoin(inputs); const totalRewards = rewardCoin.multiplierRewardsAccumulated + rewardCoin.baseRewardsAccumulated; // If below the minimum threshold to claim, show 0. If the total rewards // exceed what's actually in the pool, we clamp it to 0 or a logic fallback. if (totalRewards < farms_1.Farms.constants.minRewardsToClaim) { return BigInt(0); } // Additional clamp to handle overshoot beyond actual pool reserves return totalRewards > inputs.stakingPool.rewardCoin(inputs).actualRewards ? BigInt(0) : totalRewards; }; /** * Updates the position's reward calculations based on the pool's current * emission state, effectively "syncing" the on-chain logic into this local * representation. Also checks if the lock duration has elapsed. * * @param inputs - Contains a reference to the `FarmsStakingPool`. * @remarks This method is typically called before computing `rewardsEarned()`. */ this.updatePosition = (inputs) => { const stakingPool = new farmsStakingPool_1.FarmsStakingPool(utils_1.Helpers.deepCopy(inputs.stakingPool.stakingPool), this.config); // If the lock multiplier is valid, proceed. If not, adjust the staked position // to the pool's maximum allowed lock multiplier or duration. if (this.stakedPosition.lockDurationMs <= stakingPool.stakingPool.maxLockDurationMs && this.stakedPosition.lockMultiplier <= stakingPool.stakingPool.maxLockMultiplier) { // Lock multiplier is valid; do nothing special } else { // The position's lock duration or multiplier exceeds the pool's max allowed -> clamp stakingPool.stakingPool.stakedAmountWithMultiplier -= this.stakedPosition.stakedAmountWithMultiplier; // ii. Update the `lock_duration` and `lock_multiplier` related fields. this.stakedPosition.lockDurationMs = stakingPool.stakingPool.maxLockDurationMs; this.stakedPosition.lockMultiplier = stakingPool.stakingPool.maxLockMultiplier; this.stakedPosition.stakedAmountWithMultiplier = (this.stakedPosition.stakedAmount * (this.stakedPosition.lockMultiplier - fixedUtils_1.FixedUtils.fixedOneB)) / fixedUtils_1.FixedUtils.fixedOneB; this.stakedPosition.rewardCoins = [ ...this.stakedPosition.rewardCoins.map((rewardCoin) => { const currentDebtPerShare = stakingPool.rewardCoin({ coinType: rewardCoin.coinType, }).rewardsAccumulatedPerShare; return Object.assign(Object.assign({}, rewardCoin), { multiplierRewardsDebt: (this.stakedPosition.stakedAmountWithMultiplier * currentDebtPerShare) / fixedUtils_1.FixedUtils.fixedOneB }); }), ]; // iii. Increase the `Vault`'s `total_staked_amount_with_multiplier` to account for the // positions new lock multiplier. stakingPool.stakingPool.stakedAmountWithMultiplier += this.stakedPosition.stakedAmountWithMultiplier; } const currentTimestamp = (0, dayjs_1.default)().valueOf(); // Accumulate any newly emitted rewards in the pool’s state stakingPool.emitRewards(); // Update position’s base + multiplier rewards using the updated pool info for (const [rewardCoinIndex, rewardCoin,] of stakingPool.stakingPool.rewardCoins.entries()) { //******************************************************************************************// // debt (i.e. total_rewards_from_time_t0_to_th-1) // // .--- pending_rewards_at_time_th_minus_1 ---| // // |------------------------------------------+-------------------------------------------| // // t0 th-1 now // // '----------------------------- total_rewards_from_time_t0 -----------------------------' // //******************************************************************************************// // NOTE: new reward types might have been added to the vault since this position last called // `update_ pending_rewards`, so we need to be cautious when borrowing from `rewards_debt` // and `rewards_accumulated`. // if (rewardCoinIndex >= this.stakedPosition.rewardCoins.length) { this.stakedPosition.rewardCoins.push({ coinType: rewardCoin.coinType, baseRewardsAccumulated: BigInt(0), baseRewardsDebt: BigInt(0), multiplierRewardsAccumulated: BigInt(0), multiplierRewardsDebt: BigInt(0), }); } const stakedPositionRewardCoin = this.stakedPosition.rewardCoins[rewardCoinIndex]; const [totalBaseRewardsFromTimeT0, totalMultiplierRewardsFromTimeT0,] = this.calcTotalRewardsFromTimeT0({ rewardsAccumulatedPerShare: rewardCoin.rewardsAccumulatedPerShare, multiplierRewardsDebt: stakedPositionRewardCoin.multiplierRewardsDebt, emissionEndTimestamp: stakingPool.stakingPool.emissionEndTimestamp, }); // Add newly accrued rewards since the last update this.stakedPosition.rewardCoins[rewardCoinIndex].baseRewardsAccumulated = totalBaseRewardsFromTimeT0 - stakedPositionRewardCoin.baseRewardsDebt + stakedPositionRewardCoin.baseRewardsAccumulated; this.stakedPosition.rewardCoins[rewardCoinIndex].multiplierRewardsAccumulated = totalMultiplierRewardsFromTimeT0 - stakedPositionRewardCoin.multiplierRewardsDebt + stakedPositionRewardCoin.multiplierRewardsAccumulated; // Update debts to the new total from time t0 this.stakedPosition.rewardCoins[rewardCoinIndex].baseRewardsDebt = totalBaseRewardsFromTimeT0; this.stakedPosition.rewardCoins[rewardCoinIndex].multiplierRewardsDebt = totalMultiplierRewardsFromTimeT0; } // Check if this position’s lock has expired // if (this.unlockTimestamp() < currentTimestamp) { // this.unlock(); // } this.stakedPosition.lastHarvestRewardsTimestamp = currentTimestamp; }; // /** // * Removes the lock multiplier from this position if the current time is beyond the lock duration, // * reverting `lockMultiplier` to 1.0 (fixedOneB). // */ // private unlock = () => { // // ia. Remove position's `multiplier_staked_amount` from the pool. // // afterburner_vault::decrease_stake_with_multiplier(vault, self.multiplier_staked_amount); // this.stakedPosition.stakedAmountWithMultiplier = BigInt(0); // // ib. Reset position's lock parameters. // this.stakedPosition.lockDurationMs = 0; // this.stakedPosition.lockMultiplier = FixedUtils.fixedOneB; // }; /** * Determines if this position is unlocked based on the lock end timestamp, the emission end timestamp, * or a forced unlock condition in the pool. */ this.isUnlocked = (inputs) => { const { stakingPool } = inputs; const currentTime = (0, dayjs_1.default)().valueOf(); // If lock has expired, the emission has ended, or the pool is forcibly unlocked, then it is unlocked return (this.unlockTimestamp() <= currentTime || stakingPool.stakingPool.emissionEndTimestamp <= currentTime || stakingPool.stakingPool.isUnlocked); }; /** * Provides access to the `Farms` provider in the `AftermathApi`. */ this.useProvider = () => { var _a; const provider = (_a = this.Provider) === null || _a === void 0 ? void 0 : _a.Farms(); if (!provider) throw new Error("missing AftermathApi Provider"); return provider; }; this.stakedPosition = stakedPosition; this.trueLastHarvestRewardsTimestamp = trueLastHarvestRewardsTimestamp !== null && trueLastHarvestRewardsTimestamp !== void 0 ? trueLastHarvestRewardsTimestamp : stakedPosition.lastHarvestRewardsTimestamp; } // ========================================================================= // Transactions // ========================================================================= // ========================================================================= // Staking Transactions // ========================================================================= /** * Builds a transaction to deposit additional principal into this staked position. * * @param inputs - Contains `depositAmount`, the `walletAddress` performing the deposit, and optional sponsorship. * @returns A transaction object (or bytes) that can be signed and executed to increase stake. */ getDepositPrincipalTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }); return this.version() === 1 ? this.useProvider().fetchBuildDepositPrincipalTxV1(args) : this.useProvider().fetchBuildDepositPrincipalTxV2(args); }); } /** * Builds a transaction to unstake this entire position, optionally claiming SUI as afSUI. * * @param inputs - Contains `walletAddress`, the `FarmsStakingPool` reference, and optional `claimSuiAsAfSui`. * @returns A transaction that can be signed and executed to fully withdraw principal and possibly rewards. */ getUnstakeTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId, withdrawAmount: this.stakedPosition.stakedAmount, rewardCoinTypes: this.nonZeroRewardCoinTypes(inputs) }); return this.version() === 1 ? this.useProvider().buildUnstakeTxV1(args) : this.useProvider().buildUnstakeTxV2(args); }); } // ========================================================================= // Locking Transactions // ========================================================================= /** * Builds a transaction to lock this position for a specified duration, increasing its lock multiplier (if any). * * @param inputs - Contains the `lockDurationMs` and the `walletAddress`. * @returns A transaction that can be signed and executed to lock the position. */ getLockTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }); return this.version() === 1 ? this.useProvider().buildLockTxV1(args) : this.useProvider().buildLockTxV2(args); }); } /** * Builds a transaction to re-lock this position (renew lock duration) at the current multiplier. * * @param inputs - Contains the `walletAddress`. * @returns A transaction that can be signed and executed to extend or refresh the lock. */ getRenewLockTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }); return this.version() === 1 ? this.useProvider().buildRenewLockTxV1(args) : this.useProvider().buildRenewLockTxV2(args); }); } /** * Builds a transaction to unlock this position, removing any lock-based multiplier. * * @param inputs - Contains the `walletAddress`. * @returns A transaction that can be signed and executed to unlock the position immediately. */ getUnlockTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }); return this.version() === 1 ? this.useProvider().buildUnlockTxV1(args) : this.useProvider().buildUnlockTxV2(args); }); } // ========================================================================= // Reward Harvesting Transactions // ========================================================================= /** * Builds a transaction to harvest (claim) the rewards from this position, * optionally receiving SUI as afSUI. * * @param inputs - Includes the `walletAddress`, the `FarmsStakingPool`, and optional `claimSuiAsAfSui`. * @returns A transaction that can be signed and executed to claim accrued rewards. */ getHarvestRewardsTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakedPositionIds: [this.stakedPosition.objectId], stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId, rewardCoinTypes: this.nonZeroRewardCoinTypes(inputs) }); return this.version() === 1 ? this.useProvider().buildHarvestRewardsTxV1(args) : this.useProvider().buildHarvestRewardsTxV2(args); }); } // ========================================================================= // Private // ========================================================================= // ========================================================================= // Calculations // ========================================================================= /** * Calculates the total base + multiplier rewards from time t0 for this position, * ensuring that multiplier rewards only apply during the locked period. * * @param inputs - Contains updated `rewardsAccumulatedPerShare`, the position’s `multiplierRewardsDebt`, and the pool’s `emissionEndTimestamp`. * @returns A tuple `[baseRewards, multiplierRewards]`. */ calcTotalRewardsFromTimeT0(inputs) { const { rewardsAccumulatedPerShare, multiplierRewardsDebt, emissionEndTimestamp, } = inputs; const lastRewardTimestamp = this.stakedPosition.lastHarvestRewardsTimestamp; const lockEndTimestamp = this.unlockTimestamp(); const principalStakedAmount = this.stakedPosition.stakedAmount; const baseRewards = (principalStakedAmount * rewardsAccumulatedPerShare) / fixedUtils_1.FixedUtils.fixedOneB; // const totalMultiplierRewards = // (this.stakedPosition.stakedAmountWithMultiplier * // rewardsAccumulatedPerShare) / // FixedUtils.fixedOneB; const multiplierEndTimestamp = Math.min(lockEndTimestamp, emissionEndTimestamp); const multiplierRewards = (() => { if (lastRewardTimestamp <= multiplierEndTimestamp) { return ((rewardsAccumulatedPerShare * this.stakedPosition.stakedAmountWithMultiplier) / fixedUtils_1.FixedUtils.fixedOneB); } else { return multiplierRewardsDebt; } })(); return [baseRewards, multiplierRewards]; } } exports.FarmsStakedPosition = FarmsStakedPosition;