diff --git a/src/adaptors/primex-finance/abi.js b/src/adaptors/primex-finance/abi.js new file mode 100644 index 0000000000..a3e7d4e5c0 --- /dev/null +++ b/src/adaptors/primex-finance/abi.js @@ -0,0 +1,537 @@ +const abi = { + getAllBucketsFactory: { + "inputs": [ + { + "internalType": "address", + "name": "_bucketFactory", + "type": "address" + }, + { + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "internalType": "address", + "name": "_positionManager", + "type": "address" + }, + { + "internalType": "bool", + "name": "_showDeprecated", + "type": "bool" + } + ], + "name": "getAllBucketsFactory", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "bucketAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "decimals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.TokenMetadata", + "name": "asset", + "type": "tuple" + }, + { + "internalType": "uint128", + "name": "bar", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "lar", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "supply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "demand", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "availableLiquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "utilizationRatio", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "decimals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.TokenMetadata", + "name": "asset", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isSupported", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pairPriceDrop", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLeverage", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.BucketTokenMetadata", + "name": "properties", + "type": "tuple" + } + ], + "internalType": "struct IPrimexLens.SupportedAsset[]", + "name": "supportedAssets", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "decimals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.TokenMetadata", + "name": "pToken", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "decimals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.TokenMetadata", + "name": "debtToken", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "feeBuffer", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawalFeeRate", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "contract ILiquidityMiningRewardDistributor", + "name": "liquidityMiningRewardDistributor", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBucketLaunched", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "accumulatingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadlineTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stabilizationDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stabilizationEndTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountPerUser", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxStabilizationEndTimestamp", + "type": "uint256" + } + ], + "internalType": "struct IBucketStorage.LiquidityMiningParams", + "name": "miningParams", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "amountInMining", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentPercent", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "minReward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxReward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "extraReward", + "type": "uint256" + } + ], + "internalType": "struct ILiquidityMiningRewardDistributor.RewardsInPMX", + "name": "rewardsInPMX", + "type": "tuple" + } + ], + "internalType": "struct IPrimexLens.LenderInfo", + "name": "lenderInfo", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "pmxAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawnRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalPoints", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.LiquidityMiningBucketInfo", + "name": "lmBucketInfo", + "type": "tuple" + }, + { + "internalType": "uint128", + "name": "estimatedBar", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "estimatedLar", + "type": "uint128" + }, + { + "internalType": "bool", + "name": "isDeprecated", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isDelisted", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "urOptimal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "k0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "k1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "b0", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "b1", + "type": "int256" + } + ], + "internalType": "struct IInterestRateStrategy.BarCalculationParams", + "name": "barCalcParams", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "maxTotalDeposit", + "type": "uint256" + } + ], + "internalType": "struct IPrimexLens.BucketMetaData[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + activityRewardDistributorBuckets: { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "buckets", + "outputs": [ + { + "internalType": "uint256", + "name": "rewardIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastUpdatedTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardPerToken", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "scaledTotalSupply", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isFinished", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "fixedReward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastUpdatedRewardTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardPerDay", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalReward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + getChainlinkLatestRoundData: { + "inputs": [ + { + "internalType": "address[]", + "name": "_feeds", + "type": "address[]" + } + ], + "name": "getChainlinkLatestRoundData", + "outputs": [ + { + "components": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "internalType": "struct IPrimexLens.RoundData[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + } +} + +module.exports = { abi } \ No newline at end of file diff --git a/src/adaptors/primex-finance/index.js b/src/adaptors/primex-finance/index.js new file mode 100644 index 0000000000..51473d178b --- /dev/null +++ b/src/adaptors/primex-finance/index.js @@ -0,0 +1,106 @@ +const sdk = require('@defillama/sdk'); +const superagent = require('superagent'); +const { abi } = require('./abi'); +const { APYREWARD_BY_SYMBOL, CHAIN_IDS, DEAD_ADDRESS, ROLES, SECONDS_PER_YEAR, APY_REWARD_BONUS, config } = require('./utils') + +const getPoolUrl = (address, chain) => `https://app.primex.finance/#/bucket-details/${address}?network=${CHAIN_IDS[chain]}` + +const formatPool = async (bucket, config, EPMXPrice) => { + const { bucketAddress, asset, supportedAssets, supply, demand, bar, lar, estimatedBar, estimatedLar, miningParams, name } = bucket; + const { chain, activityRewardDistributor, EPMX } = config + + const [rewardPerTokenLender, rewardPerTokenTrader] = (await Promise.all( + Object.values(ROLES).map((r) => { + return ( + sdk.api.abi.call({ + abi: abi.activityRewardDistributorBuckets, + target: activityRewardDistributor, + chain: chain.toLowerCase(), + params: [bucketAddress, r] + }) + ) + }) + )).map(v => v.output.isFinished ? 0 : v.output.rewardPerToken) + + const symbol = asset.symbol + const underlyingTokens = [asset.tokenAddress] + + const priceKeys = underlyingTokens.map((t) => `${chain.toLowerCase()}:${t}`).join(',') + const prices = ( + await superagent.get(`https://coins.llama.fi/prices/current/${priceKeys}`) + ).body.coins; + + const assetPrice = prices[`${chain.toLowerCase()}:${asset.tokenAddress}`] + const totalSupplyUsd = (supply / 10 ** assetPrice.decimals) * assetPrice.price + const totalBorrowUsd = (demand / 10 ** assetPrice.decimals) * assetPrice.price + const tvlUsd = totalSupplyUsd - totalBorrowUsd + + const isMiningPhase = !miningParams.isBucketLaunched && miningParams.deadlineTimestamp * 1000 > Date.now() + + const apyBaseCalculated = (Math.pow(1 + (lar / 10 ** 27) / SECONDS_PER_YEAR, SECONDS_PER_YEAR) - 1) * 100 + const apyBase = isMiningPhase ? 0 : apyBaseCalculated + + const apyBaseBorrowCalculated = (Math.pow(1 + (bar / 10 ** 27) / SECONDS_PER_YEAR, SECONDS_PER_YEAR) - 1) * 100 + const apyBaseBorrow = isMiningPhase ? 0 : apyBaseBorrowCalculated + + const apyRewardCalculated = rewardPerTokenLender * 10 ** asset.decimals / 10 ** 18 * SECONDS_PER_YEAR * EPMXPrice / assetPrice / 10 ** 18 * 100; + const apyReward = isMiningPhase ? APYREWARD_BY_SYMBOL[symbol] + APY_REWARD_BONUS : apyRewardCalculated + + const apyRewardBorrowCalculated = rewardPerTokenTrader * 10 ** asset.decimals / 10 ** 18 * SECONDS_PER_YEAR * EPMXPrice / assetPrice / 10 ** 18 * 100; + const apyRewardBorrow = isMiningPhase ? 0 : apyRewardBorrowCalculated + + return { + pool: `${bucketAddress}-${chain}`.toLowerCase(), + chain, + project: 'primex-finance', + symbol, + tvlUsd, + apyBase, + apyReward, + rewardTokens: [EPMX], + underlyingTokens, + url: getPoolUrl(bucketAddress, chain), + apyBaseBorrow, + apyRewardBorrow, + totalSupplyUsd, + totalBorrowUsd, + } +} + +const getPools = async (config) => { + const { chain, lensAddress, bucketsFactory, positionManager, EPMX, EPMXPriceFeed, EPMXPriceFeedDecimals } = config; + + const buckets = ( + await sdk.api.abi.call({ + abi: abi.getAllBucketsFactory, + target: lensAddress, + chain: chain.toLowerCase(), + params: [bucketsFactory, DEAD_ADDRESS, positionManager, false] + })).output + + const EPMXPrice = (await sdk.api.abi.call({ + abi: abi.getChainlinkLatestRoundData, + target: lensAddress, + chain: chain.toLowerCase(), + params: [[EPMXPriceFeed]] + })).output[0].answer / 10 ** EPMXPriceFeedDecimals + + return await Promise.all( + buckets + .filter(({ miningParams }) => { + const isMiningFailed = !miningParams.isBucketLaunched && miningParams.deadlineTimestamp * 1000 <= Date.now() + + return !isMiningFailed + }) + .map((b) => formatPool(b, config, EPMXPrice)) + ) +} + +const getApy = async () => { + return (await Promise.all(config.map((c) => getPools(c)))).flat() +}; + +module.exports = { + timetravel: false, + apy: getApy, +}; diff --git a/src/adaptors/primex-finance/utils.js b/src/adaptors/primex-finance/utils.js new file mode 100644 index 0000000000..b88c4a0d36 --- /dev/null +++ b/src/adaptors/primex-finance/utils.js @@ -0,0 +1,38 @@ +const DEAD_ADDRESS = '0x000000000000000000000000000000000000dEaD' +const SECONDS_PER_YEAR = 31536000; + +const ROLES = { + LENDER: 0, + TRADER: 1 +} + +const CHAIN_IDS = { + Polygon: 137, +}; + +const APYREWARD_BY_SYMBOL = { + ['WETH']: 18, + ['WBTC']: 1, + ['USDC']: 31, + ['USDT']: 20, + ['WMATIC']: 21, +} + +const APY_REWARD_BONUS = 7; + +const config = [ + { + chain: 'Polygon', + lensAddress: '0xA37a23C5Eb527985caae2a710a0F0De7C49ACb9d', + bucketsFactory: '0x7E6915D307F434E4171cCee90e180f5021c60089', + positionManager: '0x02bcaA4633E466d151b34112608f60A82a4F6035', + activityRewardDistributor: '0x156e2fC8e1906507412BEeEB6640Bf999a1Ea76b', + EPMX: "0xDc6D1bd104E1efa4A1bf0BBCf6E0BD093614E31A", + EPMXPriceFeed: "0x103A9FF33c709405DF58f8f209C53f6B5c5eA2BE", + EPMXPriceFeedDecimals: 8, + }, +] + +const getPoolUrl = (address, chain) => `https://app.primex.finance/#/bucket-details/${address}?network=${CHAIN_IDS[chain]}` + +module.exports = { DEAD_ADDRESS, SECONDS_PER_YEAR, ROLES, CHAIN_IDS, APYREWARD_BY_SYMBOL, APY_REWARD_BONUS, config } \ No newline at end of file