Skip to content

Commit

Permalink
Add factor-v2 (#1196)
Browse files Browse the repository at this point in the history
* add factor-v2

* fix mux

* add glp, mux, vela

* boilerplate(factor-v2): add strategy adapter

* chore(factor-v2): separate utils

* fix(factor-v2): circular import of utils

* feat(factor-v2): glp strategy adapter

* feat(factor-v2): lodestar strategy-adapter

* feat(factor-v2): mux strategy-adapter

* feat(factor-v2): vela strategy-adapter:wq

* feat(factor-v2): add sjoe strategy-adapter

* fix(factor-v2): sjoe apr * 100

* feat(factor-v2): silo strategy-adapter

* chore(factor-v2): refactor

* feat(factor-v2): add pendle strategy-adapter

* feat(factor-v2): add penpie strategy-adapter

* feat(factor-v2): add olive strategy-adapter

* feat(factor-v2): add redacted strategy-adapter

* feat(factor-v2): add tender strategy-adapter

* chore(factor-v2): disable redacted

* feat(factor-v2): add getDefiLLamaPools

* feat(factor-v2): glp adapter use defillama api

* feat(factor-v2): add pendle adapter

* feat(factor-v2): add tender adapter

* feat(factor-v2): add olive adapter

* feat(factor-v2): add vela adapter
  • Loading branch information
dimasriat authored Feb 22, 2024
1 parent 759bccf commit 8dcb071
Show file tree
Hide file tree
Showing 16 changed files with 726 additions and 0 deletions.
39 changes: 39 additions & 0 deletions src/adaptors/factor-v2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const vaults = require('./vaults');
const { getTvl, getApr } = require('./shared');

async function getSingleYieldVaultAPY() {
const poolData = await Promise.all(
vaults.map(async (vault) => {
const project = 'factor-v2';
const chain = 'arbitrum';
const pool = `${vault.poolAddress}-${chain}`.toLowerCase();
const url = `https://app.factor.fi/vault/${vault.poolAddress}`;
const symbol = vault.symbol;

const [tvlUsd, apyBase] = await Promise.all([
getTvl(vault.poolAddress, vault.underlyingToken, vault.strategy),
getApr(vault.poolAddress, vault.underlyingToken, vault.strategy),
]);

const data = {
pool,
chain,
project,
symbol,
tvlUsd,
apyBase,
underlyingTokens: [vault.underlyingToken],
url,
};

return data;
})
);

return poolData;
}

module.exports = {
timetravel: false,
apy: getSingleYieldVaultAPY,
};
101 changes: 101 additions & 0 deletions src/adaptors/factor-v2/shared.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const sdk = require('@defillama/sdk3');
const utils = require('../utils');

const {
getMuxLpApr,
getGlpApr,
getVlpApr,
getLodestarApr,
getLodestarTokenPriceInUSD,
getPendleApr,
getSJoeApr,
getSiloApr,
getTenderApr,
getOliveApr,
getPxGMXApr,
getPenpieApr
} = require('./strategy-adapter');
const { getCoinDataFromDefillamaAPI } = require('./strategy-adapter/utils');

async function getApr(poolAddress, underlyingTokenAddress, strategy) {
let apr = 0;
switch (strategy) {
case 'GLPStrategy':
apr = await getGlpApr();
break;
case 'MuxStrategy':
apr = await getMuxLpApr();
break;
case 'VelaStrategy':
apr = await getVlpApr();
break;
case 'LodestarStrategy':
apr = await getLodestarApr(underlyingTokenAddress);
break;
case 'PenpieStrategy':
apr = await getPenpieApr(underlyingTokenAddress);
break;
case 'PendleStrategy':
apr = await getPendleApr(underlyingTokenAddress);
break;
case 'TraderJoeStrategy':
apr = await getSJoeApr(underlyingTokenAddress);
break;
case 'SiloStrategy':
apr = await getSiloApr(underlyingTokenAddress);
break;
case 'TenderStrategy':
apr = await getTenderApr(underlyingTokenAddress);
break;
case 'OliveStrategy':
apr = await getOliveApr();
break;
case 'RedactedStrategy':
apr = await getPxGMXApr();
break;
default:
apr = 0;
}

const harvestCountPerDay = 3;
const apyBase = utils.aprToApy(apr, harvestCountPerDay * 365);

return apyBase;
}

async function getTvl(poolAddress, underlyingTokenAddress, strategy) {
let underlyingTokenPrice = 0;

if (strategy == 'LodestarStrategy') {
underlyingTokenPrice = await getLodestarTokenPriceInUSD(
underlyingTokenAddress
);
} else if (strategy == "RedactedStrategy") {
const gmxCoin = await getCoinDataFromDefillamaAPI('arbitrum','0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a')
underlyingTokenPrice = gmxCoin.price;
} else {
underlyingTokenPrice = (
await utils.getPrices([underlyingTokenAddress], 'arbitrum')
).pricesByAddress[underlyingTokenAddress.toLowerCase()];
}

const [{ output: assetBalance }, { output: assetDecimals }] =
await Promise.all([
sdk.api.abi.call({
target: poolAddress,
abi: 'uint256:assetBalance',
chain: 'arbitrum',
}),
sdk.api.abi.call({
target: underlyingTokenAddress,
abi: 'erc20:decimals',
chain: 'arbitrum',
}),
]);

const tvlUsd = (assetBalance / 10 ** assetDecimals) * underlyingTokenPrice;

return tvlUsd;
}

module.exports = { getTvl, getApr };
16 changes: 16 additions & 0 deletions src/adaptors/factor-v2/strategy-adapter/glp-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { getDefiLLamaPools } = require('./utils');

/*//////////////////////////////////////////////////////////////////////////////
GLP APR
//////////////////////////////////////////////////////////////////////////////*/

async function getGlpApr() {
const pool = await getDefiLLamaPools(
'825688c0-c694-4a6b-8497-177e425b7348'
);
const apr = pool.apyBase + pool.apyReward;

return apr;
}

module.exports = { getGlpApr };
13 changes: 13 additions & 0 deletions src/adaptors/factor-v2/strategy-adapter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
...require('./lodestar-adapter'),
...require('./mux-adapter'),
...require('./glp-adapter'),
...require('./vela-adapter'),
...require('./pendle-adapter'),
...require('./sjoe-adapter'),
...require('./silo-adapter'),
...require('./tender-adapter'),
...require('./olive-adapter'),
...require('./redacted-adapter'),
...require('./penpie-adapter'),
};
59 changes: 59 additions & 0 deletions src/adaptors/factor-v2/strategy-adapter/lodestar-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const sdk = require('@defillama/sdk3');
const utils = require('../../utils');
const { makeReadable } = require('./utils');

async function getSupplyRatePerBlock(assetAddress) {
const { output } = await sdk.api.abi.call({
target: assetAddress,
abi: 'uint256:supplyRatePerBlock',
chain: 'arbitrum',
});
return {
supplyRatePerBlock: makeReadable(output),
};
}

async function getLodestarApr(assetAddress) {
const { supplyRatePerBlock } = await getSupplyRatePerBlock(assetAddress);
const blocksPerYear = 7200 * 365;
const apr = (1 + supplyRatePerBlock) ** blocksPerYear - 1;
return apr * 100;
}

async function getExchangeRateStored(assetAddress) {
const { output } = await sdk.api.abi.call({
target: assetAddress,
abi: 'uint256:exchangeRateStored',
chain: 'arbitrum',
});
return {
exchangeRateStored: makeReadable(output, 16),
};
}

async function getLodestarUnderlyingTokenPriceInUSD(lToken) {
const { output: underlyingTokenAddress } = await sdk.api.abi.call({
target: lToken,
abi: 'address:underlying',
chain: 'arbitrum',
});
const underlyingLodestarTokenPriceInUSD = (
await utils.getPrices([underlyingTokenAddress], 'arbitrum')
).pricesByAddress[underlyingTokenAddress.toLowerCase()];

return { underlyingLodestarTokenPriceInUSD };
}

async function getLodestarTokenPriceInUSD(lToken) {
const [{ exchangeRateStored }, { underlyingLodestarTokenPriceInUSD }] =
await Promise.all([
getExchangeRateStored(lToken),
getLodestarUnderlyingTokenPriceInUSD(lToken),
]);

const lTokenPriceInUSD =
underlyingLodestarTokenPriceInUSD * exchangeRateStored;
return lTokenPriceInUSD;
}

module.exports = { getLodestarApr, getLodestarTokenPriceInUSD };
108 changes: 108 additions & 0 deletions src/adaptors/factor-v2/strategy-adapter/mux-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const sdk = require('@defillama/sdk3');
const utils = require('../../utils');
const { makeReadable } = require('./utils');

/*//////////////////////////////////////////////////////////////////////////////
Mux Reward Router
//////////////////////////////////////////////////////////////////////////////*/

async function getDataFromRewardRouter() {
const REWARD_ROUTER_ADDRESS = '0xaf9C4F6A0ceB02d4217Ff73f3C95BbC8c7320ceE';

const rewardRouterABIs = [
'uint256:feeRewardRate',
'uint256:muxRewardRate',
'uint256:poolOwnedRate',
'uint256:votingEscrowedRate',
];

const [feeRewardRate, muxRewardRate, poolOwnedRate, votingEscrowedRate] =
await Promise.all(
rewardRouterABIs.map(async (abi) => {
const { output } = await sdk.api.abi.call({
target: REWARD_ROUTER_ADDRESS,
abi: abi,
chain: 'arbitrum',
});
return output;
})
);
return {
feeRewardRate: makeReadable(feeRewardRate),
muxRewardRate: makeReadable(muxRewardRate),
poolOwnedRate: makeReadable(poolOwnedRate),
votingEscrowedRate: makeReadable(votingEscrowedRate),
};
}

/*//////////////////////////////////////////////////////////////////////////////
Mux Pool Owned Liquidity
//////////////////////////////////////////////////////////////////////////////*/

async function getDataFromPOL() {
const MLP_ADDRESS = '0x7CbaF5a14D953fF896E5B3312031515c858737C8';
const POL_ADDRESS = '0x18891480b9dd2aC5eF03220C45713d780b5CFdeF';

const { output: mlpPolBalance } = await sdk.api.abi.call({
target: MLP_ADDRESS,
abi: 'erc20:balanceOf',
params: [POL_ADDRESS],
chain: 'arbitrum',
});

return {
mlpPolBalance: makeReadable(mlpPolBalance),
};
}

/*//////////////////////////////////////////////////////////////////////////////
Token Prices
//////////////////////////////////////////////////////////////////////////////*/

async function getTokenPrices() {
const tokenAddresses = [
'0x7CbaF5a14D953fF896E5B3312031515c858737C8', // MuxLP
'0x4e352cf164e64adcbad318c3a1e222e9eba4ce42', // MCB
'0x82af49447d8a07e3bd95bd0d56f35241523fbab1', // WETH
];

const tokenPrices = await utils.getPrices(tokenAddresses, 'arbitrum');

const mlpPrice = tokenPrices.pricesByAddress[tokenAddresses[0].toLowerCase()];
const mcbPrice = tokenPrices.pricesByAddress[tokenAddresses[1].toLowerCase()];
const ethPrice = tokenPrices.pricesByAddress[tokenAddresses[2].toLowerCase()];

return { mlpPrice, mcbPrice, ethPrice };
}

/*//////////////////////////////////////////////////////////////////////////////
Mux LP APR
//////////////////////////////////////////////////////////////////////////////*/

async function getMuxLpApr() {
const [
{ mlpPolBalance },
{ feeRewardRate, muxRewardRate, poolOwnedRate, votingEscrowedRate },
{ mlpPrice, mcbPrice, ethPrice },
] = await Promise.all([
getDataFromPOL(),
getDataFromRewardRouter(),
getTokenPrices(),
]);

const mlpCirculatingSupply = mlpPolBalance / poolOwnedRate;

const muxAPR =
(muxRewardRate * mcbPrice * 86400 * 365 * (1 - votingEscrowedRate)) /
(mlpCirculatingSupply * mlpPrice);

const ethAPR =
(feeRewardRate * ethPrice * 86400 * 365 * 0.7) /
(mlpCirculatingSupply * mlpPrice);

const totalAPR = (muxAPR + ethAPR) * 100;

return totalAPR;
}

module.exports = { getMuxLpApr };
22 changes: 22 additions & 0 deletions src/adaptors/factor-v2/strategy-adapter/olive-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { getDefiLLamaPools } = require('./utils');

async function getOliveApr() {
const pool = await getDefiLLamaPools(
'79587734-a461-4f4c-b9e2-c85c70484cf8'
);

const beefyApr =
pool.apyBase + (isNaN(pool.apyReward) ? 0 : pool.apyReward);

const ampFactor = 1.33;
const weeksPerYear = 365 / 7;
const oliveBoost = 0.05;
const apr =
(1 + (beefyApr * ampFactor) / weeksPerYear) ** weeksPerYear -
1 +
oliveBoost;

return apr * 100;
}

module.exports = { getOliveApr };
23 changes: 23 additions & 0 deletions src/adaptors/factor-v2/strategy-adapter/pendle-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { getDefiLLamaPools } = require('./utils');

async function getPendleApr(poolAddress) {
const poolAddressToIdMap = {
// wstETH
'0x08a152834de126d2ef83d612ff36e4523fd0017f':
'f05aa688-f627-4438-89c6-2fef135510c7',

// rETH
'0x14fbc760efaf36781cb0eb3cb255ad976117b9bd':
'35fe5f76-3b7d-42c8-9e54-3da70fbcb3a9',
};

const pool = await getDefiLLamaPools(
poolAddressToIdMap[poolAddress.toLowerCase()]
);

const apr = pool.apyBase + pool.apyReward;

return apr;
}

module.exports = { getPendleApr };
Loading

0 comments on commit 8dcb071

Please sign in to comment.