Skip to content

Commit

Permalink
feat: rewardApy & rewardTokens prop
Browse files Browse the repository at this point in the history
  • Loading branch information
murka committed Dec 30, 2024
1 parent 5edd5fd commit 41c3ed5
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 108 deletions.
111 changes: 3 additions & 108 deletions src/adaptors/evaa-protocol/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { Address, Cell, Slice, Dictionary, beginCell } = require("@ton/core");
const { signVerify } = require('@ton/crypto');
const crypto = require("crypto");
const getPrices = require('./getPrices');
const { getDistributions, calculateRewardApy } = require('./rewardApy');

function sha256Hash(input) {
const hash = crypto.createHash("sha256");
Expand Down Expand Up @@ -307,112 +308,6 @@ function calculateCurrentRates(assetConfig, assetData) {
};
}

function isLeapYear(year) {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

async function getDistributions(endpoint = 'evaa.space') {
try {
let result = await fetch(`https://${endpoint}/query/distributions/list`, {
headers: { accept: 'application/json' },
});
let resData = await result.json();
return resData;
} catch (error) {
console.error(error);
return undefined;
}
}

function calculateRewardApy(distributionsResp, pool, data, prices) {
try {
if (
!distributionsResp?.distributions ||
distributionsResp.distributions.length === 0
) {
console.log('Invalid distributions data:', distributionsResp);
return undefined;
}

const currentCampaign = distributionsResp?.distributions.find(
(campaign) => campaign.started && !campaign.expired
);

if (!currentCampaign) return 0;

const currentSeasons = currentCampaign?.seasons
?.map((season) => {
if (!season.started || season.expired) {
return;
}

const rewardingAssetId = BigInt(season?.rewarding_asset_id ?? 0);
const rewardsAssetId = BigInt(season?.rewards_asset_id ?? 0);

const rewardingAssetData = data.assetsData.get(rewardingAssetId);
const rewardsAssetData = data.assetsData.get(rewardsAssetId);
const rewardPool = season?.pool;
if (!rewardingAssetData || !rewardsAssetData || rewardPool !== pool) {
return;
}

const rewardType = season?.reward_type;

const rewardingAssetConfig = data.assetsConfig.get(rewardingAssetId);
const rewardsAssetConfig = data.assetsConfig.get(rewardsAssetId);

const rewardingScaleFactor =
10 ** Number(rewardingAssetConfig.decimals);
const rewardsScaleFactor = 10 ** Number(rewardsAssetConfig.decimals);

const rewardingPriceData = prices.dict.get(rewardingAssetId);
const rewardsPriceData = prices.dict.get(rewardsAssetId);

const rewardingPrice =
Number(rewardingPriceData) / Number(priceScaleFactor);
const rewardsPrice =
Number(rewardsPriceData) / Number(priceScaleFactor);

const totalAmount =
rewardType === 'borrow'
? rewardingAssetData.totalBorrow
: rewardingAssetData.totalSupply;
const rate = rewardingScaleFactor / Number(totalAmount);
const rewardForUnit =
rate *
(((Number(season?.rewards_amount) / rewardsScaleFactor) *
(rewardsPrice ?? 0)) /
rewardingPrice) *
rewardingScaleFactor;
const seasonStart = new Date(season?.campaign_start ?? 0);
const seasonEnd = new Date(season?.campaign_end ?? 0);
const totalSecsInCurrentSeason =
(Number(seasonEnd) - Number(seasonStart)) / 1000;
const isLeap = isLeapYear(new Date().getFullYear());
const totalSecsInYear = (isLeap ? 366 : 365) * 24 * 60 * 60;
const apy =
((rewardForUnit * totalSecsInYear) /
totalSecsInCurrentSeason /
rewardingScaleFactor) *
100;

return {
apy,
rewardType,
rewardingAssetId,
rewardsAssetId,
pool: rewardPool,
};
})
?.filter(Boolean);

return currentSeasons || [];
} catch (error) {
console.error(error);
return undefined;
}
}

// ignore pools with TVL below the threshold
const MIN_TVL_USD = 100000;

Expand Down Expand Up @@ -481,7 +376,7 @@ const getApy = async () => {
const apyRewardData = rewardApys.find(
(rewardApy) =>
rewardApy.rewardingAssetId == assetId &&
rewardApy.rewardType == 'supply'
rewardApy.rewardType.toLowerCase() === 'supply'
);

const apyReward = apyRewardData ? apyRewardData.apy : undefined;
Expand All @@ -490,7 +385,7 @@ const getApy = async () => {
const apyRewardBorrowData = rewardApys.find(
(rewardApy) =>
rewardApy.rewardingAssetId == assetId &&
rewardApy.rewardType == 'borrow'
rewardApy.rewardType.toLowerCase() === 'borrow'
);

const apyRewardBorrow = apyRewardBorrowData
Expand Down
177 changes: 177 additions & 0 deletions src/adaptors/evaa-protocol/rewardApy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
const fetch = require('node-fetch');

function isLeapYear(year) {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}

async function getDistributions(endpoint = 'evaa.space') {
try {
let result = await fetch(`https://${endpoint}/query/distributions/list`, {
headers: { accept: 'application/json' },
});
let resData = await result.json();
return resData;
} catch (error) {
console.error(error);
return undefined;
}
}
const isLeap = isLeapYear(new Date().getFullYear());
const totalSecsInYear = (isLeap ? 366 : 365) * 24 * 60 * 60;

function calcApy(
rewardAmount,
totalAmount,
rewardingAssetPrice,
rewardsAssetPrice,
rewardingScaleFactor,
rewardsScaleFactor,
totalSecsInCurrentSeason
) {
const rate = rewardingScaleFactor / Number(totalAmount);
const rewardForUnit =
rate *
(((Number(rewardAmount) / rewardsScaleFactor) * (rewardsAssetPrice ?? 0)) /
(rewardingAssetPrice || 1)) *
rewardingScaleFactor;

return (
((rewardForUnit * totalSecsInYear) /
(totalSecsInCurrentSeason * rewardingScaleFactor)) *
100
);
}

function calculateRewardApy(distributionsResp, pool, data, prices) {
try {
if (
!distributionsResp?.distributions ||
distributionsResp.distributions.length === 0
) {
console.log('Invalid distributions data:', distributionsResp);
return [];
}

const currentCampaign = distributionsResp?.distributions.find(
(campaign) => campaign.started && !campaign.expired
);

if (!currentCampaign) {
return [];
}

const seasonsApy = currentCampaign.seasons
?.filter((season) => season.started && !season.expired)
?.filter((season) => season.pool === pool)
?.map((season) => {
const rewardingAssetId = BigInt(season?.rewarding_asset_id ?? 0);
const rewardsAssetId = BigInt(season?.rewards_asset_id ?? 0);

const rewardingAssetData = data.assetsData.get(rewardingAssetId);
const rewardsAssetData = data.assetsData.get(rewardsAssetId);

if (!rewardingAssetData || !rewardsAssetData) {
return undefined;
}

const rewardType =
season?.reward_type?.toLowerCase() === 'borrow' ? 'Borrow' : 'Supply';

let rewardAmount = Number(season?.rewards_amount) || 0;

if (rewardType === 'Borrow' && season?.borrow_budget) {
rewardAmount = season.borrow_budget;
} else if (rewardType === 'Supply' && season?.supply_budget) {
rewardAmount = season.supply_budget;
}

const totalAmountSupply =
rewardingAssetData.totalSupply?.original ??
rewardingAssetData.totalSupply;
const totalAmountBorrow =
rewardingAssetData.totalBorrow?.original ??
rewardingAssetData.totalBorrow;

const totalAmount =
rewardType === 'Borrow' ? totalAmountBorrow : totalAmountSupply;

if (!totalAmount || totalAmount === '0') {
return [];
}

const rewardingAssetConfig = data.assetsConfig.get(rewardingAssetId);
const rewardsAssetConfig = data.assetsConfig.get(rewardsAssetId);

const rewardingScaleFactor =
10 ** Number(rewardingAssetConfig?.decimals ?? 0);
const rewardsScaleFactor =
10 ** Number(rewardsAssetConfig?.decimals ?? 0);

const rewardingPriceData = prices.dict.get(rewardingAssetId);
const rewardsPriceData = prices.dict.get(rewardsAssetId);

const rewardingAssetPrice = Number(rewardingPriceData);
const rewardsAssetPrice = Number(rewardsPriceData);

const seasonStart = new Date(season?.campaign_start ?? 0);
const seasonEnd = new Date(season?.campaign_end ?? 0);
const totalSecsInCurrentSeason = (seasonEnd - seasonStart) / 1000;

if (totalSecsInCurrentSeason <= 0) {
return undefined;
}

const baseApy = calcApy(
rewardAmount,
totalAmount,
rewardingAssetPrice,
rewardsAssetPrice,
rewardingScaleFactor,
rewardsScaleFactor,
totalSecsInCurrentSeason
);

const result = [
{
apy: baseApy,
rewardType,
rewardingAssetId,
rewardsAssetId,
},
];

if (
rewardType === 'Borrow' &&
season?.supply_budget &&
season.supply_budget > 0
) {
const supplyApy = calcApy(
season.supply_budget,
totalAmountSupply,
rewardingAssetPrice,
rewardsAssetPrice,
rewardingScaleFactor,
rewardsScaleFactor,
totalSecsInCurrentSeason
);
result.push({
apy: supplyApy,
rewardType: 'Supply',
rewardingAssetId,
rewardsAssetId,
});
}

return result;
});
return seasonsApy.flat();
} catch (error) {
console.error(error);
return undefined;
}
}

module.exports = {
getDistributions,
calculateRewardApy,
};

0 comments on commit 41c3ed5

Please sign in to comment.