client-aftermath-ts-sdk
Version:
Client Aftermath TypeScript SDK
380 lines (379 loc) • 25.2 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");
class FarmsStakedPosition extends caller_1.Caller {
// =========================================================================
// Constructor
// =========================================================================
constructor(stakedPosition, trueLastHarvestRewardsTimestamp = undefined, config, Provider) {
super(config, "farms");
this.stakedPosition = stakedPosition;
this.Provider = Provider;
// =========================================================================
// Public
// =========================================================================
// =========================================================================
// Getters
// =========================================================================
this.isLocked = (inputs) => {
return !this.isUnlocked(inputs);
};
this.isLockDuration = () => {
return this.stakedPosition.lockDurationMs > 0;
};
this.unlockTimestamp = () => {
return (this.stakedPosition.lockStartTimestamp +
this.stakedPosition.lockDurationMs);
};
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 })) })), {});
};
this.rewardCoinTypes = () => {
return this.stakedPosition.rewardCoins.map((coin) => coin.coinType);
};
this.nonZeroRewardCoinTypes = (inputs) => {
return Object.entries(this.rewardCoinsToClaimableBalance(inputs))
.filter(([, val]) => val > BigInt(0))
.map(([key]) => key);
};
this.rewardCoin = (inputs) => {
const foundCoin = this.stakedPosition.rewardCoins.find((coin) => coin.coinType === inputs.coinType);
if (!foundCoin)
throw new Error("Invalid coin type");
return foundCoin;
};
this.hasClaimableRewards = (inputs) => {
const { stakingPool } = inputs;
return (utils_1.Helpers.sumBigInt(this.rewardCoinTypes().map((coinType) => this.rewardsEarned({
coinType,
stakingPool,
}))) > BigInt(0));
};
// =========================================================================
// Calculations
// =========================================================================
this.rewardsEarned = (inputs) => {
if (inputs.stakingPool.rewardCoin(inputs).actualRewards === BigInt(0) ||
inputs.stakingPool.rewardCoin(inputs).actualRewards <
inputs.stakingPool.rewardCoin(inputs).emissionRate)
return BigInt(0);
this.updatePosition(inputs);
const rewardCoin = this.rewardCoin(inputs);
const totalRewards = rewardCoin.multiplierRewardsAccumulated +
rewardCoin.baseRewardsAccumulated;
return totalRewards < farms_1.Farms.constants.minRewardsToClaim ||
totalRewards > inputs.stakingPool.rewardCoin(inputs).actualRewards
? BigInt(0)
: totalRewards;
};
// Updates the amount of rewards that can be harvested from `self` + the position's
// debt. If the position's lock duration has elapsed, it will be unlocked.
this.updatePosition = (inputs) => {
const stakingPool = new farmsStakingPool_1.FarmsStakingPool(utils_1.Helpers.deepCopy(inputs.stakingPool.stakingPool), this.config);
// If the position's `lock_multiplier` is larger than the `Vault`'s `max_lock_multiplier`;
// We want to only cap position's who have a `lock_duration_ms` or `lock_multiplier` that exceeds
// the vault's limits.
if (this.stakedPosition.lockDurationMs <=
stakingPool.stakingPool.maxLockDurationMs &&
this.stakedPosition.lockMultiplier <=
stakingPool.stakingPool.maxLockMultiplier) {
}
else {
// i. Reset the `Vault`'s `total_staked_amount_with_multiplier` that applies to this position.
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) => (Object.assign(Object.assign({}, rewardCoin), { multiplierRewardsDebt: (() => {
let currentDebtPerShare = stakingPool.rewardCoin({
coinType: rewardCoin.coinType,
}).rewardsAccumulatedPerShare;
return ((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();
// i. Increase the vault's `rewardsAccumulatedPerShare` values.
stakingPool.emitRewards();
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];
// NOTE: `total_rewards_from_time_t0` contains the amount of rewards a position would receive
// -- from time t0 to the current time -- given the vault's current state when, in reality,
// we need to calculate just the rewards that have accumulated since the last time the
// position's `rewards_accumulated` was updated [labeled time th-1].
//
let [totalBaseRewardsFromTimeT0, totalMultiplierRewardsFromTimeT0] = this.calcTotalRewardsFromTimeT0({
rewardsAccumulatedPerShare: rewardCoin.rewardsAccumulatedPerShare,
multiplierRewardsDebt: stakedPositionRewardCoin.multiplierRewardsDebt,
emissionEndTimestamp: stakingPool.stakingPool.emissionEndTimestamp,
});
// NOTE: Every time a position's `rewards_accumulated` is updated, a snapshot of the total
// rewards received from time t0 is taken and stored as the position's `debt` field. Here,
// the debt is subtracted from `total_rewards_from_time_t0` in order to calculate the amount
// of rewards that have been accumulated since the last time `rewards_accumulated` was
// updated.
//
// `pending_rewards_at_time_th_minus_1` is added to the resulting value to account for all
// rewards that had accumulated up until the last time `update_position` was updated.
//
// iia. Allocate rewards to this position that have been emitted since the last time this
// function was called.
this.stakedPosition.rewardCoins[rewardCoinIndex].baseRewardsAccumulated =
totalBaseRewardsFromTimeT0 -
stakedPositionRewardCoin.baseRewardsDebt +
stakedPositionRewardCoin.baseRewardsAccumulated;
this.stakedPosition.rewardCoins[rewardCoinIndex].multiplierRewardsAccumulated =
totalMultiplierRewardsFromTimeT0 -
stakedPositionRewardCoin.multiplierRewardsDebt +
stakedPositionRewardCoin.multiplierRewardsAccumulated;
// iib. Set the position's current debt to the total rewards attributed to this position given
// the vault's current state. This allows the next `update_position` call to disregard
// all rewards accumulated up until this point -- preventing double emission cases.
this.stakedPosition.rewardCoins[rewardCoinIndex].baseRewardsDebt =
totalBaseRewardsFromTimeT0;
this.stakedPosition.rewardCoins[rewardCoinIndex].multiplierRewardsDebt = totalMultiplierRewardsFromTimeT0;
}
// iii. Remove the position's lock multiplier + bonus staked amount if the position is no
// longer locked.
if (this.unlockTimestamp() < currentTimestamp) {
this.unlock();
}
this.stakedPosition.lastHarvestRewardsTimestamp = currentTimestamp;
};
// Removes a positions `lock_duration_ms` and `lock_multiplier`. Updates the vault's
// `staked_amount_with_multiplier` to account for the lost `lock_multiplier`.
this.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_1.FixedUtils.fixedOneB;
};
this.isUnlocked = (inputs) => {
const { stakingPool } = inputs;
// let emitted_rewards = stakingPool.calc_emitted_rewards();
// let total_rewards = stakingPool.stakingPool.rewardCoins.map(
// (coin) => coin.rewards
// );
// let length = emitted_rewards.length;
// let index = 0;
// let no_rewards_are_remaining = true;
// while (index < length && no_rewards_are_remaining) {
// let emitted = emitted_rewards[index];
// let total = total_rewards[index];
// if (emitted < total) no_rewards_are_remaining = false;
// index = index + 1;
// }
return (this.unlockTimestamp() <= (0, dayjs_1.default)().valueOf() ||
stakingPool.stakingPool.emissionEndTimestamp <= (0, dayjs_1.default)().valueOf() ||
// no_rewards_are_remaining
stakingPool.stakingPool.isUnlocked);
};
// =========================================================================
// Private Helpers
// =========================================================================
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;
}
// public calcTotalApr = (inputs: {
// rewardsUsd: number;
// stakeUsd: number;
// }): Apr => {
// const { rewardsUsd, stakeUsd } = inputs;
// dayjs.extend(duration);
// const oneYearMs = dayjs.duration(1, "year").asMilliseconds();
// const timeSinceLastHarvestMs =
// dayjs().valueOf() - this.trueLastHarvestRewardsTimestamp;
// const rewardsUsdOneYear =
// timeSinceLastHarvestMs > 0
// ? rewardsUsd * (oneYearMs / timeSinceLastHarvestMs)
// : 0;
// const apr = stakeUsd > 0 ? rewardsUsdOneYear / stakeUsd : 0;
// return apr < 0 ? 0 : isNaN(apr) ? 0 : apr;
// };
// =========================================================================
// Transactions
// =========================================================================
// =========================================================================
// Staking Transactions
// =========================================================================
getDepositPrincipalTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildDepositPrincipalTx(Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }));
});
}
getUnstakeTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildUnstakeTx(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) }));
});
}
// =========================================================================
// Locking Transactions
// =========================================================================
getLockTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().buildLockTx(Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }));
});
}
getRenewLockTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().buildRenewLockTx(Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }));
});
}
getUnlockTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().buildUnlockTx(Object.assign(Object.assign({}, inputs), { stakedPositionId: this.stakedPosition.objectId, stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId }));
});
}
// =========================================================================
// Reward Harvesting Transactions
// =========================================================================
getHarvestRewardsTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildHarvestRewardsTx(Object.assign(Object.assign({}, inputs), { stakedPositionIds: [this.stakedPosition.objectId], stakeCoinType: this.stakedPosition.stakeCoinType, stakingPoolId: this.stakedPosition.stakingPoolObjectId, rewardCoinTypes: this.nonZeroRewardCoinTypes(inputs) }));
});
}
// =========================================================================
// Private
// =========================================================================
// =========================================================================
// Calculations
// =========================================================================
// Calculates a position's accrued rewards [from time t0] given a vault's
// `rewardsAccumulatedPerShare`. If the position is beyond its lock duration, we need to only
// apply the lock multiplier rewards to the time spent locked.
calcTotalRewardsFromTimeT0(inputs) {
const { rewardsAccumulatedPerShare, multiplierRewardsDebt, emissionEndTimestamp, } = inputs;
const currentTimestamp = (0, dayjs_1.default)().valueOf();
const lastRewardTimestamp = this.stakedPosition.lastHarvestRewardsTimestamp;
const lockEndTimestamp = this.unlockTimestamp();
const principalStakedAmount = this.stakedPosition.stakedAmount;
// Base [e.g. principal] staked amount receives full (unaltered) rewards.
const rewardsAttributedToPrincipal = (principalStakedAmount * rewardsAccumulatedPerShare) /
fixedUtils_1.FixedUtils.fixedOneB;
const totalRewardsAttributedToLockMultiplier = (this.stakedPosition.stakedAmountWithMultiplier *
rewardsAccumulatedPerShare) /
fixedUtils_1.FixedUtils.fixedOneB;
// The position should only receive multiplied rewards for the time that was spent locked since
// the last harvest. This case occurs when the user calls `pending_rewards` after the position's
// lock duration has expired.
const rewardsAttributedToLockMultiplier = (() => {
return currentTimestamp <= lockEndTimestamp
? //*********************************************************************************************//
// v //
// |-------------------------------------------+-------------------------------------------| //
// last_reward_timestamp_ms current_timestamp_ms lock_end_timestamp_ms //
//*********************************************************************************************//
totalRewardsAttributedToLockMultiplier
: lockEndTimestamp <= lastRewardTimestamp
? //*********************************************************************************************//
// emission_end_timestamp_ms lock_end_timestamp_ms v //
// |----------------------------+-----------------------------+----------------------------| //
// last_reward_timestamp_ms current_timestamp_ms //
//*********************************************************************************************//
//
// NOTE: if the lock period was longer than the rewards emission, the position receives the full
// multiplier rewards
multiplierRewardsDebt
: emissionEndTimestamp <= lockEndTimestamp
? totalRewardsAttributedToLockMultiplier
: (() => {
//*********************************************************************************************//
// lock_end_timestamp_ms emission_end_timestamp_ms v //
// |----------------------------+-----------------------------+----------------------------| //
// last_reward_timestamp_ms current_timestamp_ms //
//*********************************************************************************************//
//
// NOTE: there is no enforced ordering of `emission_end_timestamp_ms` and `current_timestamp_ms`
// by the time we get to this branch.
// Multiplier staked amount receives (altered) rewards dependent on the total time the
// position was locked since the last harvest.
const timeSpentLockedSinceLastHarvestMs = lockEndTimestamp - lastRewardTimestamp;
const timeSinceLastHarvestMs = currentTimestamp - lastRewardTimestamp;
// ********************************************************************************************//
// / timeSpentLockedSinceLastHarvestMs \ //
// | ----------------------------------------- | x totalRewardsAttributedToLockMultiplier //
// \ timeSinceLastHarvest / //
// ********************************************************************************************//
// Only disperse the multiplied rewards that were received while this position was locked.
//
// We are assigning this value to the total_multiplier_rewards later and this number should
// never decrease, so we use max() here.
const possibleMultiplierRewardsDebt = (totalRewardsAttributedToLockMultiplier *
BigInt(Math.floor(timeSpentLockedSinceLastHarvestMs))) /
BigInt(Math.floor(timeSinceLastHarvestMs));
return possibleMultiplierRewardsDebt >
multiplierRewardsDebt
? possibleMultiplierRewardsDebt
: multiplierRewardsDebt;
})();
})();
return [
rewardsAttributedToPrincipal,
rewardsAttributedToLockMultiplier,
];
}
}
exports.FarmsStakedPosition = FarmsStakedPosition;