aftermath-ts-sdk
Version:
Aftermath TypeScript SDK
443 lines (442 loc) • 22.9 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");
/**
* 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;