client-aftermath-ts-sdk
Version:
Client Aftermath TypeScript SDK
306 lines (305 loc) • 16.3 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.FarmsStakingPool = void 0;
const caller_1 = require("../../general/utils/caller");
const utils_1 = require("../../general/utils");
const dayjs_1 = __importDefault(require("dayjs"));
const duration_1 = __importDefault(require("dayjs/plugin/duration"));
const fixedUtils_1 = require("../../general/utils/fixedUtils");
const coin_1 = require("../coin/coin");
const farms_1 = require("./farms");
class FarmsStakingPool extends caller_1.Caller {
// =========================================================================
// Constructor
// =========================================================================
constructor(stakingPool, config, Provider) {
super(config, "farms");
this.stakingPool = stakingPool;
this.Provider = Provider;
// =========================================================================
// Getters
// =========================================================================
this.rewardCoinTypes = () => {
return this.stakingPool.rewardCoins.map((coin) => coin.coinType);
};
this.nonZeroRewardCoinTypes = () => {
return this.stakingPool.rewardCoins
.filter((coin) => coin.emissionRate <= coin.actualRewards &&
coin.actualRewards > BigInt(0))
.map((coin) => coin.coinType);
};
this.rewardCoin = (inputs) => {
const foundCoin = this.stakingPool.rewardCoins.find((coin) => coin.coinType === inputs.coinType);
if (!foundCoin)
throw new Error("Invalid coin type");
return foundCoin;
};
this.maxLockDurationMs = () => {
return Math.max(Math.min(this.stakingPool.maxLockDurationMs, this.stakingPool.emissionEndTimestamp - (0, dayjs_1.default)().valueOf()), 0);
};
// =========================================================================
// Calculations
// =========================================================================
// Calculates the amount of rewards that have emitted since the last time this function has been
// called. Updates `rewards_accumulated_per_share`.
this.emitRewards = () => {
const currentTimestamp = (0, dayjs_1.default)().valueOf();
// ia. Check that the vault has deposits.
if (this.stakingPool.stakedAmount === BigInt(0))
return;
const rewardCoins = utils_1.Helpers.deepCopy(this.stakingPool.rewardCoins);
for (const [rewardCoinIndex, rewardCoin] of rewardCoins.entries()) {
// ib. Check that enough time has passed since the last emission.
if (currentTimestamp <
rewardCoin.lastRewardTimestamp + rewardCoin.emissionSchedulesMs)
continue;
// iia. Calculate how many rewards have to be emitted.
const rewardsToEmit = this.calcRewardsToEmit({ rewardCoin });
if (rewardsToEmit === BigInt(0))
continue;
// iii. Increase the amount of rewards emitted per share.
this.increaseRewardsAccumulatedPerShare({
rewardsToEmit,
rewardCoinIndex,
});
const numberOfEmissions = (currentTimestamp - rewardCoin.lastRewardTimestamp) /
rewardCoin.emissionSchedulesMs;
// IMPORTANT: only increase by multiples of `emission_schedule_ms`.
//
// iv. Update reward's `last_reward_timestamp`.
this.stakingPool.rewardCoins[rewardCoinIndex].lastRewardTimestamp =
rewardCoin.lastRewardTimestamp +
numberOfEmissions * rewardCoin.emissionSchedulesMs;
}
};
this.calcApr = (inputs) => {
const { coinType, price, decimals, tvlUsd } = inputs;
if (price <= 0 || tvlUsd <= 0)
return 0;
// this.emitRewards();
const rewardCoin = this.rewardCoin({ coinType });
const currentTimestamp = (0, dayjs_1.default)().valueOf();
if (rewardCoin.emissionRate > rewardCoin.actualRewards)
return 0;
if (rewardCoin.emissionStartTimestamp > currentTimestamp ||
currentTimestamp > this.stakingPool.emissionEndTimestamp)
return 0;
const emissionRateUsd = coin_1.Coin.balanceWithDecimals(rewardCoin.emissionRate, decimals) * price;
dayjs_1.default.extend(duration_1.default);
const oneYearMs = dayjs_1.default.duration(1, "year").asMilliseconds();
const rewardsUsdOneYear = emissionRateUsd * (oneYearMs / rewardCoin.emissionSchedulesMs);
const apr = rewardsUsdOneYear /
tvlUsd /
utils_1.Casting.bigIntToFixedNumber(this.stakingPool.maxLockMultiplier);
return apr < 0 ? 0 : isNaN(apr) ? 0 : apr;
};
this.calcTotalApr = (inputs) => {
const { coinsToPrice, coinsToDecimals, tvlUsd } = inputs;
const aprs = this.rewardCoinTypes().map((coinType) => this.calcApr({
coinType,
price: coinsToPrice[coinType],
decimals: coinsToDecimals[coinType],
tvlUsd,
}));
return utils_1.Helpers.sum(aprs);
};
this.calcMultiplier = (inputs) => {
const lockDurationMs = inputs.lockDurationMs > this.stakingPool.maxLockDurationMs
? this.stakingPool.maxLockDurationMs
: inputs.lockDurationMs < this.stakingPool.minLockDurationMs
? this.stakingPool.minLockDurationMs
: inputs.lockDurationMs;
const totalPossibleLockDurationMs = this.stakingPool.maxLockDurationMs -
this.stakingPool.minLockDurationMs;
const newMultiplier = 1 +
((lockDurationMs - this.stakingPool.minLockDurationMs) /
(totalPossibleLockDurationMs <= 0
? 1
: totalPossibleLockDurationMs)) *
(utils_1.Casting.bigIntToFixedNumber(this.stakingPool.maxLockMultiplier) -
1);
const multiplier = utils_1.Casting.numberToFixedBigInt(newMultiplier);
return multiplier < fixedUtils_1.FixedUtils.fixedOneB
? fixedUtils_1.FixedUtils.fixedOneB
: utils_1.Helpers.minBigInt(multiplier, this.stakingPool.maxLockMultiplier);
};
// =========================================================================
// 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.stakingPool = stakingPool;
// this.emitRewards();
}
// =========================================================================
// Public
// =========================================================================
// =========================================================================
// Stats
// =========================================================================
getTVL() {
return __awaiter(this, void 0, void 0, function* () {
return new farms_1.Farms(this.config, this.Provider).getTVL({
farmIds: [this.stakingPool.objectId],
});
});
}
getRewardsTVL() {
return __awaiter(this, void 0, void 0, function* () {
return new farms_1.Farms(this.config, this.Provider).getRewardsTVL({
farmIds: [this.stakingPool.objectId],
});
});
}
// public calc_emitted_rewards(): bigint[] {
// let emitted_rewards: bigint[] = [];
// let length = this.stakingPool.rewardCoins.length;
// let index = 0;
// while (index < length) {
// let emission_start_timestamp_ms =
// this.stakingPool.rewardCoins[index].emissionStartTimestamp;
// let last_reward_timestamp_ms =
// this.stakingPool.rewardCoins[index].lastRewardTimestamp;
// emitted_rewards.push(
// this.calcRewardsEmittedFromTimeTmToTn({
// timestampTm: emission_start_timestamp_ms,
// timestampTn: last_reward_timestamp_ms,
// rewardCoin: this.stakingPool.rewardCoins[index],
// })
// );
// index = index + 1;
// }
// return emitted_rewards;
// }
// public calc_emitted_rewards_for(inputs: {
// rewardCoinIndex: number;
// }): bigint {
// let reward_index = inputs.rewardCoinIndex;
// let emission_start_timestamp_ms =
// this.stakingPool.rewardCoins[reward_index].emissionStartTimestamp;
// let last_reward_timestamp_ms =
// this.stakingPool.rewardCoins[reward_index].lastRewardTimestamp;
// return this.calcRewardsEmittedFromTimeTmToTn({
// timestampTm: emission_start_timestamp_ms,
// timestampTn: last_reward_timestamp_ms,
// rewardCoin: this.stakingPool.rewardCoins[reward_index],
// });
// }
// =========================================================================
// Transactions
// =========================================================================
// =========================================================================
// Staking Transactions
// =========================================================================
getStakeTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildStakeTx(Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }));
});
}
// =========================================================================
// Reward Harvesting Transactions
// =========================================================================
getHarvestRewardsTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildHarvestRewardsTx(Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId, rewardCoinTypes: this.nonZeroRewardCoinTypes() }));
});
}
// =========================================================================
// Mutation/Creation Transactions (Owner Only)
// =========================================================================
getIncreaseRewardsEmissionsTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchIncreaseStakingPoolRewardsEmissionsTx(Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }));
});
}
getUpdateMinStakeAmountTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().buildSetStakingPoolMinStakeAmountTx(Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }));
});
}
getGrantOneTimeAdminCapTransaction(inputs) {
return this.useProvider().buildGrantOneTimeAdminCapTx(inputs);
}
// =========================================================================
// Mutation Transactions (Owner/Admin Only)
// =========================================================================
getInitializeRewardTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildInitializeStakingPoolRewardTx(Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }));
});
}
getTopUpRewardsTransaction(inputs) {
return __awaiter(this, void 0, void 0, function* () {
return this.useProvider().fetchBuildTopUpStakingPoolRewardsTx(Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }));
});
}
// =========================================================================
// Private
// =========================================================================
// =========================================================================
// Calculations
// =========================================================================
increaseRewardsAccumulatedPerShare(inputs) {
const { rewardsToEmit, rewardCoinIndex } = inputs;
// i. Calculate the pro-rata amount of rewards allocated to each staked amount.
const newRewardsAccumulatedPerShare = (rewardsToEmit * BigInt(1000000000000000000)) /
this.stakingPool.stakedAmountWithMultiplier;
if (newRewardsAccumulatedPerShare === BigInt(0))
return;
// ii. Increase the amount of rewards emitted per share.
this.stakingPool.rewardCoins[rewardCoinIndex].rewardsAccumulatedPerShare += newRewardsAccumulatedPerShare;
}
rewardsRemaining(inputs) {
const rewardCoin = this.stakingPool.rewardCoins[inputs.rewardCoinIndex];
const currentTimestamp = (0, dayjs_1.default)().valueOf();
const numberOfEmissions = BigInt(Math.floor((currentTimestamp - rewardCoin.emissionStartTimestamp) /
rewardCoin.emissionSchedulesMs));
const emittedRewards = rewardCoin.emissionRate * numberOfEmissions;
const totalRewards = rewardCoin.rewards;
if (totalRewards <= emittedRewards)
return BigInt(0);
return totalRewards - emittedRewards;
}
calcRewardsToEmit(inputs) {
const { rewardCoin } = inputs;
const currentTimestamp = (0, dayjs_1.default)().valueOf();
// Calculate the number of rewards that have been emitted since the last time this reward was emitted.
const rewardsToEmit = this.calcRewardsEmittedFromTimeTmToTn({
timestampTm: rewardCoin.lastRewardTimestamp,
timestampTn: currentTimestamp,
rewardCoin,
});
const rewardsRemaining = rewardCoin.rewardsRemaining;
// IMPORTANT: Cap the amount of rewards to emit by the amount of remaining rewards.
//
return rewardsRemaining < rewardsToEmit
? rewardsRemaining
: rewardsToEmit;
}
calcRewardsEmittedFromTimeTmToTn(inputs) {
const { timestampTm, timestampTn, rewardCoin } = inputs;
const numberOfEmissionsFromTimeTmToTn = (timestampTn - timestampTm) /
// -----------------------------------------------
rewardCoin.emissionSchedulesMs;
return (BigInt(Math.floor(numberOfEmissionsFromTimeTmToTn)) *
rewardCoin.emissionRate);
}
}
exports.FarmsStakingPool = FarmsStakingPool;