UNPKG

aftermath-ts-sdk

Version:
443 lines (442 loc) 22.9 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.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"); /** * The `FarmsStakingPool` class represents a staking pool (also referred * to as a "vault" in some contexts). It allows reading details about * emission schedules, reward tokens, stake coin type, and lock durations, * as well as constructing transactions to stake, harvest, and mutate the * pool parameters if the user has the correct admin privileges. */ class FarmsStakingPool extends caller_1.Caller { /** * Creates a `FarmsStakingPool` instance based on on-chain pool data. * * @param stakingPool - The on-chain data object describing the pool. * @param config - An optional `CallerConfig` for network settings. * @param Provider - An optional `AftermathApi` for transaction building. */ constructor(stakingPool, config, Provider) { super(config, "farms"); this.stakingPool = stakingPool; this.Provider = Provider; // ========================================================================= // Getters // ========================================================================= /** * Retrieves the version of this staking pool (1 or 2). */ this.version = () => { return this.stakingPool.version; }; /** * Lists all reward coin types offered by this staking pool. * * @returns An array of `CoinType` strings. */ this.rewardCoinTypes = () => { return this.stakingPool.rewardCoins.map((coin) => coin.coinType); }; /** * Lists all reward coin types for which this pool currently has a non-zero actual reward balance. * * @returns An array of `CoinType` strings that have > 0 actual rewards. */ this.nonZeroRewardCoinTypes = () => { return this.stakingPool.rewardCoins .filter((coin) => coin.emissionRate <= coin.actualRewards && coin.actualRewards > BigInt(0)) .map((coin) => coin.coinType); }; /** * Retrieves the on-chain record for a specific reward coin type in this pool. * * @param inputs - Contains the `coinType` to look up. * @throws If the specified coinType is not found in `rewardCoins`. * @returns A `FarmsStakingPoolRewardCoin` object. */ this.rewardCoin = (inputs) => { const foundCoin = this.stakingPool.rewardCoins.find((coin) => coin.coinType === inputs.coinType); if (!foundCoin) throw new Error("Invalid coin type"); return foundCoin; }; /** * Computes the maximum lock duration (in ms) that remains valid in this staking pool, * factoring in the current time and the pool's emission end. * * @returns The maximum possible lock duration in milliseconds, or 0 if the pool is effectively closed. */ this.maxLockDurationMs = () => { return Math.max(Math.min(this.stakingPool.maxLockDurationMs, this.stakingPool.emissionEndTimestamp - (0, dayjs_1.default)().valueOf()), 0); }; // ========================================================================= // Calculations // ========================================================================= /** * Calculates and applies newly emitted rewards for each reward coin in this pool, * updating the `rewardsAccumulatedPerShare`. This simulates the on-chain * `emitRewards` logic. * * @example * ```typescript * someFarmsPool.emitRewards(); * // The pool's rewardsAccumulatedPerShare fields are now updated. * ``` */ this.emitRewards = () => { const currentTimestamp = (0, dayjs_1.default)().valueOf(); // If no staked amount, no distribution 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; } }; /** * Computes an approximate APR for a specific reward coin, based on the current * emission rate, coin price, pool TVL, and the lock multiplier range. This assumes * maximum lock multiplier for the final APR result. * * @param inputs - Includes the `coinType`, its `price` and `decimals`, plus the total `tvlUsd` in the pool. * @returns A numeric APR (0.05 = 5%). */ this.calcApr = (inputs) => { const { coinType, price, decimals, tvlUsd } = inputs; if (price <= 0 || tvlUsd <= 0) return 0; const rewardCoin = this.rewardCoin({ coinType }); const currentTimestamp = (0, dayjs_1.default)().valueOf(); // If the current emission rate is below the actual supply, or if the pool hasn't started or is ended, yield 0 if (rewardCoin.emissionRate > rewardCoin.actualRewards) return 0; if (rewardCoin.emissionStartTimestamp > currentTimestamp || currentTimestamp > this.stakingPool.emissionEndTimestamp) { return 0; } const emissionRateTokens = rewardCoin.emissionRate; const emissionRateUsd = coin_1.Coin.balanceWithDecimals(emissionRateTokens, decimals) * price; dayjs_1.default.extend(duration_1.default); const oneYearMs = dayjs_1.default.duration(1, "year").asMilliseconds(); const rewardsUsdOneYear = emissionRateUsd * (oneYearMs / rewardCoin.emissionSchedulesMs); // The final APR is normalized by total staked value and the maximum lock multiplier const apr = rewardsUsdOneYear / tvlUsd / utils_1.Casting.bigIntToFixedNumber(this.stakingPool.maxLockMultiplier); return apr < 0 ? 0 : isNaN(apr) ? 0 : apr; }; /** * Computes the total APR contributed by all reward coin types in this pool, summing * up the individual APR for each coin type. This also assumes max lock multiplier. * * @param inputs - Contains price data (`coinsToPrice`), decimal data (`coinsToDecimals`), and the total TVL in USD. * @returns The sum of all coin APRs (0.10 = 10%). */ 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); }; /** * Given a lock duration in ms, calculates the lock multiplier to be used by staked positions. * This function clamps the input duration between the pool's `minLockDurationMs` and * `maxLockDurationMs`. * * @param inputs - An object containing the `lockDurationMs` for which to calculate a multiplier. * @returns A `FarmsMultiplier` (bigint) representing the scaled factor (1.0 = 1e9 if using fixedOneB). */ 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 // ========================================================================= /** * Provides access to the farm-specific provider methods for building transactions. */ 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; } // ========================================================================= // Public // ========================================================================= // ========================================================================= // Stats // ========================================================================= /** * Fetches the total value locked (TVL) for this staking pool alone. * * @returns A `number` representing this pool's TVL in USD (or another currency). * * @example * ```typescript * const poolTvl = await someFarmsPool.getTVL(); * console.log(poolTvl); * ``` */ getTVL() { return __awaiter(this, void 0, void 0, function* () { return new farms_1.Farms(this.config, this.Provider).getTVL({ farmIds: [this.stakingPool.objectId], }); }); } /** * Fetches the total value locked (TVL) of the reward coins in this specific staking pool. * * @returns A `number` representing this pool's reward TVL. * * @example * ```typescript * const rewardTvl = await someFarmsPool.getRewardsTVL(); * console.log(rewardTvl); * ``` */ getRewardsTVL() { return __awaiter(this, void 0, void 0, function* () { return new farms_1.Farms(this.config, this.Provider).getRewardsTVL({ farmIds: [this.stakingPool.objectId], }); }); } // ========================================================================= // Transactions // ========================================================================= // ========================================================================= // Staking Transactions // ========================================================================= /** * Builds a transaction to stake tokens into this pool, optionally locking them. * * @param inputs - Contains `stakeAmount`, `lockDurationMs`, `walletAddress`, and optional sponsorship. * @returns A transaction object (or bytes) that can be signed and executed to create a staked position. */ getStakeTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }); return this.version() === 1 ? this.useProvider().fetchBuildStakeTxV1(args) : this.useProvider().fetchBuildStakeTxV2(Object.assign({}, args)); }); } // ========================================================================= // Reward Harvesting Transactions // ========================================================================= /** * Builds a transaction to harvest rewards from multiple staked positions in this pool. * * @param inputs - Contains `stakedPositionIds`, the `walletAddress`, and optionally any others. * @returns A transaction that can be signed and executed to claim rewards from multiple positions. */ getHarvestRewardsTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId, rewardCoinTypes: this.nonZeroRewardCoinTypes() }); return this.version() === 1 ? this.useProvider().buildHarvestRewardsTxV1(args) : this.useProvider().buildHarvestRewardsTxV2(args); }); } // ========================================================================= // Mutation/Creation Transactions (Owner Only) // ========================================================================= /** * Builds a transaction to increase the emission rate (or schedule) for specific reward coins. * * @param inputs - Contains the `ownerCapId` that authorizes changes, plus an array of `rewards` with new emission details. * @returns A transaction to be signed and executed by the owner cap holder. */ getIncreaseRewardsEmissionsTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }); return this.version() === 1 ? this.useProvider().buildIncreaseStakingPoolRewardsEmissionsTxV1(args) : this.useProvider().buildIncreaseStakingPoolRewardsEmissionsTxV2(args); }); } /** * Builds a transaction to update the pool's minimum stake amount, only authorized by the `ownerCapId`. * * @param inputs - Contains the new `minStakeAmount`, the `ownerCapId`, and the calling `walletAddress`. * @returns A transaction that can be signed and executed to change the minimum stake requirement. */ getUpdateMinStakeAmountTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }); return this.version() === 1 ? this.useProvider().buildSetStakingPoolMinStakeAmountTxV1(args) : this.useProvider().buildSetStakingPoolMinStakeAmountTxV2(args); }); } /** * Builds a transaction granting a one-time admin cap to another address, allowing them to perform specific * one-time administrative actions (like initializing a reward). * * @param inputs - Body containing the `ownerCapId`, the `recipientAddress`, and the `rewardCoinType`. * @returns A transaction to be executed by the current pool owner. */ getGrantOneTimeAdminCapTransaction(inputs) { return this.version() === 1 ? this.useProvider().buildGrantOneTimeAdminCapTxV1(inputs) : this.useProvider().buildGrantOneTimeAdminCapTxV2(inputs); } // ========================================================================= // Mutation Transactions (Owner/Admin Only) // ========================================================================= /** * Builds a transaction to initialize a new reward coin in this pool, specifying the amount, emission rate, * and schedule parameters. This can be done by either the `ownerCapId` or a `oneTimeAdminCapId`. * * @param inputs - Contains emission info (rate, schedule) and which cap is used (`ownerCapId` or `oneTimeAdminCapId`). * @returns A transaction object for the reward initialization. */ getInitializeRewardTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }); return this.version() === 1 ? this.useProvider().fetchBuildInitializeStakingPoolRewardTxV1(args) : this.useProvider().fetchBuildInitializeStakingPoolRewardTxV2(args); }); } /** * Builds a transaction to add more reward coins (top-up) to an existing reward * coin configuration, either as the owner or via a one-time admin cap. * * @param inputs - Contains an array of reward objects, each specifying amount and coin type. * @returns A transaction that can be signed and executed to increase the reward distribution pool. */ getTopUpRewardsTransaction(inputs) { return __awaiter(this, void 0, void 0, function* () { const args = Object.assign(Object.assign({}, inputs), { stakeCoinType: this.stakingPool.stakeCoinType, stakingPoolId: this.stakingPool.objectId }); return this.version() === 1 ? this.useProvider().fetchBuildTopUpStakingPoolRewardsTxV1(args) : this.useProvider().fetchBuildTopUpStakingPoolRewardsTxV2(args); }); } // ========================================================================= // Private // ========================================================================= // ========================================================================= // Calculations // ========================================================================= /** * Updates `rewardsAccumulatedPerShare` by distributing `rewardsToEmit` among * the total staked amount with multiplier. This mimics on-chain distribution logic. * * @param inputs - Contains the `rewardsToEmit` and which `rewardCoinIndex` to update. */ increaseRewardsAccumulatedPerShare(inputs) { const { rewardsToEmit, rewardCoinIndex } = inputs; const stakedWithMultiplier = this.stakingPool.stakedAmountWithMultiplier; if (stakedWithMultiplier === BigInt(0)) return; // Distribute proportionally const newRewardsAccumulatedPerShare = (rewardsToEmit * BigInt(1000000000000000000)) / stakedWithMultiplier; if (newRewardsAccumulatedPerShare === BigInt(0)) return; this.stakingPool.rewardCoins[rewardCoinIndex].rewardsAccumulatedPerShare += newRewardsAccumulatedPerShare; } /** * Computes how many rewards to emit based on the time since `lastRewardTimestamp` and * the pool's emission schedule, clamped by the total `rewardsRemaining`. */ 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; // IMPORTANT: Cap the amount of rewards to emit by the amount of remaining rewards. return rewardsRemaining < rewardsToEmit ? rewardsRemaining : rewardsToEmit; } /** * Calculates how many tokens were emitted between two timestamps (Tm and Tn) for a given reward coin, * based on the discrete `emissionRate` and `emissionSchedulesMs`. * * @param inputs - Contains `timestampTm`, `timestampTn`, and the relevant `rewardCoin`. * @returns The total number of tokens emitted in that time window. */ calcRewardsEmittedFromTimeTmToTn(inputs) { const { timestampTm, timestampTn, rewardCoin } = inputs; const numberOfEmissionsFromTimeTmToTn = rewardCoin.emissionSchedulesMs === 0 ? 0 : (timestampTn - timestampTm) / rewardCoin.emissionSchedulesMs; return (BigInt(Math.floor(numberOfEmissionsFromTimeTmToTn)) * rewardCoin.emissionRate); } } exports.FarmsStakingPool = FarmsStakingPool;