๐งช How to get a staking pool APY โ
๐ Full API Setup Example (JS) โ
js
import { ethers } from "ethers";
import { BigNumber } from "bignumber.js";
// ABI for the Staking Pool contract - contains all necessary function signatures
// prettier-ignore
const STAKING_POOL_ABI = [{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"FeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Reward","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstaked","type":"event"},{"inputs":[],"name":"BLOCKS_PER_DAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLOCKS_PER_SECOND","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_POOL_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_LOCK_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POOL_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RATIO_PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNBONDING_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"active","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimIfRequiredAndUnstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimUnstakes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"currentStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAvailableSelfStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDailyVestingRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVestingSchedule","outputs":[{"internalType":"uint256","name":"lockPeriodDays","type":"uint256"},{"internalType":"uint256","name":"vestingDurationDays","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"increaseSelfStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialLockPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialSelfStakeAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract PoolOwnership","name":"_ownershipNFT","type":"address"},{"internalType":"contract MATToken","name":"_token","type":"address"},{"internalType":"uint256","name":"_selfStake","type":"uint256"},{"internalType":"uint256","name":"_lockPeriodInDays","type":"uint256"},{"internalType":"uint256","name":"_vestingDurationInDays","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastFeeChange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"moveStakeToSelfStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ownershipNFT","outputs":[{"internalType":"contract PoolOwnership","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"pendingUnstakes","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"block","type":"uint256"}],"internalType":"struct StakingPool.Unstake[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolCreation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ratio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"replenish","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"safeClaimUnstakes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"setFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract MATToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vestingDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
/**
* Array of staking pool contract addresses on the Matchain network
* These are the validator pools that participate in the staking mechanism
*/
const stakingPoolAddresses = [
"0xf9Ff433f14f237CE6643257ca4Cd5AEc355eaEE7",
"0x256b02b26C67029B2881ab5d493cf8E5068D5Fcc",
"0xa3BE9646116F834D0f9F1a035fdAd6862b5f0A2A",
"0xfa38f584f561642Ff0d1AB67b5A175798668A047",
"0x443D8318224C59c31987EA310033D0DC47A10d88",
"0xc021A95E3Ad7D4e06745c4A12438A960c33859D7",
"0xfF5Eb1345aa5d4C948C227742FCd32157539E480",
"0x0B0381a2C4C3a537C614Afe71334c9F387718922",
"0x9c4C30d5cD29c6D24CF1A712D93B756cBf5071bA",
"0x8Aa82b86056af2DD5cdAAB45EEFE500c84d0af82",
"0x4a47d64B074D730868623d89ec633d16adf53212",
"0x739110f0233B8598957Af1321920787c13142910",
"0xC3258AF62e46B502700c34ED7C3128F99f0fC532",
"0xd899cd3cAFB1c9F6d585C36649592956D3dF5Ef9",
"0xdB604bE88482cE061FBc2595E619f967dE1A7502",
"0x96b5F62604729ED5b7219f1b3f15A8359E0a86Fb",
"0xeEF37E218087Bca2f4c3c6C4c50f3D3B51B2Eb4d",
"0xb4175a66e28F2C348959872B28AF0d12F891201F",
"0xDD1904dcf7e0b6DFf60BD9729F626B076999E18f",
"0x53F91B6aE02c8CDbC22565F7F7A158aDD6F1de6E",
"0x0De1a88dcF7Fd12C54d9aC0807c990F26BD1C121",
];
// JSON-RPC provider for connecting to the Matchain network
const provider = new ethers.JsonRpcProvider("https://rpc.matchain.io");
/**
* Interface representing the essential information of a staking pool
*/
interface PoolInfo {
stakingPool: string; // Contract address of the staking pool
active: boolean; // Whether the pool is currently active
commission: bigint; // Commission fee percentage (in basis points)
totalStake: bigint; // Total amount of tokens staked in this pool
}
/**
* Parameters required for APY calculation
*/
interface ICalculateAPYParams {
totalStakedAmount: BigNumber; // Total staked amount across all pools
fee: BigNumber; // Pool commission fee as a percentage
}
// Maximum number of pools to consider for APY calculation (top pools by stake)
const POOLS_LIMIT = 21;
// Mathematical constants for APY calculation
const ONE = new BigNumber(1);
const ZERO = new BigNumber(0);
const HUNDRED = new BigNumber(100);
// Time constants for APY calculation
const avgSecondsInYear = new BigNumber(31556736); // Average seconds in a year (365.25 days)
const tokensYear = avgSecondsInYear.div(3).div(2); // Token emission rate calculation
/**
* Calculates the Annual Percentage Yield (APY) for a staking pool
* Formula: (tokensPerYear * (1 - fee/100) / totalStakedAmount) * 100
*
* @param totalStakedAmount - Total amount of tokens staked across all pools
* @param fee - Commission fee percentage of the pool
* @returns APY as a percentage
*/
const calculateAPY = ({ totalStakedAmount, fee }: ICalculateAPYParams): BigNumber => {
return tokensYear.multipliedBy(ONE.minus(fee.div(HUNDRED)).div(totalStakedAmount).multipliedBy(HUNDRED));
};
/**
* Fetches pool information from a staking pool contract
*
* @param stakingPool - Contract address of the staking pool
* @returns Promise resolving to PoolInfo object
*/
const getPool = async (stakingPool: string): Promise<PoolInfo> => {
const pool = new ethers.Contract(stakingPool, STAKING_POOL_ABI, provider);
// Fetch pool data in parallel for better performance
const [active, commission, totalStake] = await Promise.all([
pool.active(),
pool.fee(),
pool.totalStake()
]);
return {
stakingPool,
active,
commission,
totalStake,
};
};
/**
* Main function to calculate APY for all staking pools
*
* Process:
* 1. Fetch information for all staking pools
* 2. Sort pools by total stake (descending)
* 3. Take top POOLS_LIMIT pools
* 4. Calculate total staked amount across selected pools
* 5. Calculate APY for each pool based on their commission and total stake
*
* @returns Promise resolving to an object mapping pool addresses to their APY values
*/
const getStakingPoolApys = async (): Promise<{ [stakingPool: string]: BigNumber }> => {
// Fetch pool information for all staking pools
const pools: PoolInfo[] = [];
for (const poolAddress of stakingPoolAddresses) {
pools.push(await getPool(poolAddress));
}
// Sort pools by total stake (highest first) and limit to top pools
const sortedStakingPoolInfos = pools
.sort((a, b) => (b.totalStake >= a.totalStake ? 1 : -1))
.slice(0, POOLS_LIMIT);
// Calculate total staked amount across all selected pools
const totalStakedAmount = sortedStakingPoolInfos
.map((v) => v.totalStake)
.reduce((acc, v) => acc + v, 0n);
// Calculate APY for each pool
const stakingPoolAPYs: { [stakingPool: string]: BigNumber } = {};
sortedStakingPoolInfos.forEach((v) => {
stakingPoolAPYs[v.stakingPool] = calculateAPY({
totalStakedAmount: new BigNumber(ethers.formatEther(totalStakedAmount)),
fee: new BigNumber(ethers.formatUnits(v.commission, 2)),
});
});
console.log(stakingPoolAPYs);
return stakingPoolAPYs;
};
// Execute the APY calculation
getStakingPoolApys();
// Example output format:
// For example: 19.798509935889813888 => APY: 19.798509935889813888 %
// {
// '0x96b5F62604729ED5b7219f1b3f15A8359E0a86Fb': 19.798509935889813888,
// '0xf9Ff433f14f237CE6643257ca4Cd5AEc355eaEE7': 19.798509935889813888,
// '0x0De1a88dcF7Fd12C54d9aC0807c990F26BD1C121': 19.798509935889813888,
// '0x8Aa82b86056af2DD5cdAAB45EEFE500c84d0af82': 19.798509935889813888,
// '0x256b02b26C67029B2881ab5d493cf8E5068D5Fcc': 19.798509935889813888,
// '0xa3BE9646116F834D0f9F1a035fdAd6862b5f0A2A': 19.798509935889813888,
// '0xc021A95E3Ad7D4e06745c4A12438A960c33859D7': 19.798509935889813888,
// '0xC3258AF62e46B502700c34ED7C3128F99f0fC532': 19.798509935889813888,
// '0x443D8318224C59c31987EA310033D0DC47A10d88': 19.798509935889813888,
// '0x53F91B6aE02c8CDbC22565F7F7A158aDD6F1de6E': 19.798509935889813888,
// '0x0B0381a2C4C3a537C614Afe71334c9F387718922': 19.798509935889813888,
// '0xfF5Eb1345aa5d4C948C227742FCd32157539E480': 19.798509935889813888,
// '0x9c4C30d5cD29c6D24CF1A712D93B756cBf5071bA': 19.798509935889813888,
// '0xDD1904dcf7e0b6DFf60BD9729F626B076999E18f': 19.798509935889813888,
// '0xb4175a66e28F2C348959872B28AF0d12F891201F': 19.798509935889813888,
// '0xfa38f584f561642Ff0d1AB67b5A175798668A047': 19.798509935889813888,
// '0xeEF37E218087Bca2f4c3c6C4c50f3D3B51B2Eb4d': 19.798509935889813888,
// '0xdB604bE88482cE061FBc2595E619f967dE1A7502': 19.798509935889813888,
// '0xd899cd3cAFB1c9F6d585C36649592956D3dF5Ef9': 19.798509935889813888,
// '0x4a47d64B074D730868623d89ec633d16adf53212': 19.798509935889813888,
// '0x739110f0233B8598957Af1321920787c13142910': 19.798509935889813888
// }