aftermath-ts-sdk
Version:
Aftermath TypeScript SDK
437 lines (436 loc) • 24.8 kB
JavaScript
"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;