Add Single Finance (#621)

* Add Single Finance

* update token symbol and add ltv

* update syntax
This commit is contained in:
Single Finance Lab
2023-03-06 15:00:40 +08:00
committed by GitHub
parent e87196bd2f
commit 6849629116
2 changed files with 519 additions and 0 deletions

View File

@@ -0,0 +1,219 @@
module.exports = {
VaultABI: [
{
inputs: [],
name: 'totalToken',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'decimals',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'totalSupply',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'vaultDebtVal',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'decimals',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'config',
outputs: [
{
internalType: 'contract IVaultConfig',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
],
Erc20ABI: [
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
name: 'balance',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
],
ConfigurableInterestVaultConfigABI: [
{
inputs: [
{
internalType: 'uint256',
name: 'debt',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'floating',
type: 'uint256',
},
],
name: 'getInterestRate',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getReservePoolBps',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
],
BigBangABI: [
{
inputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
name: 'poolInfo',
outputs: [
{
internalType: 'uint256',
name: 'allocPoint',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lastRewardBlock',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'accRewardPerShare',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'totalAllocPoint',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'singlePerBlock',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
],
};

View File

@@ -0,0 +1,300 @@
const sdk = require('@defillama/sdk');
const utils = require('../utils');
const superagent = require('superagent');
const {
VaultABI,
ConfigurableInterestVaultConfigABI,
Erc20ABI,
BigBangABI,
} = require('./abis');
const poolApiEndpoint = 'https://api.singlefinance.io/api/vaults';
const singleToken = {
cronos: '0x0804702a4E749d39A35FDe73d1DF0B1f1D6b8347'.toLowerCase(),
fantom: '0x8cc97b50fe87f31770bcdcd6bc8603bc1558380b'.toLowerCase(),
};
const singleVaultAddress = {
cronos: '0x3710000815c45d715af84f35919a6f2a901b7548'.toLowerCase(),
fantom: '0xE87158503f831244E67af248E02bb1cc1CEfA841'.toLowerCase(),
};
const bigBang = {
cronos: '0x1Ae8a7C582C3f9F9117d5Bc2863F2eD16cBd29cb'.toLowerCase(),
fantom: '0x7C770a787B430582AbccE88886e9E4E24A457A61'.toLowerCase(),
};
const blocksPerYear = (secondsPerBlock) =>
(60 / secondsPerBlock) * 60 * 24 * 365;
const blocksPerYears = {
cronos: blocksPerYear(5.8),
fantom: blocksPerYear(5.8),
};
const availablePools = {
cronos: ['CRO', 'USDC', 'VVS', 'MMF', 'USDT', 'VERSA', 'ARGO', 'bCRO'],
fantom: ['FTM', 'USDC', 'fUSDT'],
// cronos: ['CRO',],
// fantom:[],
};
const getApyBase = (
borrowingInterest,
totalBorrow,
totalSupply,
lendingPerformanceFee
) => {
return (
borrowingInterest *
(totalBorrow / totalSupply) *
(1 - lendingPerformanceFee) *
100
);
};
const getApyReward = (
stakedTvl,
allocPoint,
totalAllocPoint,
singlePerBlock,
singlePrice,
blocksPerYear
) => {
return (
(((allocPoint / totalAllocPoint) * singlePerBlock * singlePrice) /
stakedTvl) *
blocksPerYear *
100
);
};
const getPrices = async (addresses) => {
const prices = (
await superagent.post('https://coins.llama.fi/prices').send({
coins: addresses,
})
).body.coins;
const priceItems = Object.entries(prices).reduce(
(acc, [name, price]) => ({
...acc,
[name.split(':')[1]]: price.price,
}),
{}
);
return priceItems;
};
const secondToYearlyInterestRate = (rate) =>
(Number(rate) * (60 * 60 * 24 * 365)) / 1000000000000000000;
const getPriceInUsd = (item, decimal, price) => {
return (Number(item) / 10 ** Number(decimal)) * price;
};
const multiCallOutput = async (abi, calls, chain) => {
const res = await sdk.api.abi.multiCall({
abi: abi,
calls: calls,
chain: chain,
});
return res.output.map(({ output }) => output);
};
const getApy = async (chain) => {
const allPools = await utils.getData(
`${poolApiEndpoint}?chainid=${
chain === 'fantom' ? '250' : chain === 'cronos' ? '25' : undefined
}`
);
const pools = allPools.data.filter((pool) => {
const symbol = pool.token.symbol;
return availablePools[chain].includes(symbol);
});
const singlePrice = await getPrices([chain + ':' + singleToken[chain]]);
const totalSupply = await multiCallOutput(
VaultABI.find(({ name }) => name === 'totalToken'),
pools.map((pool) => ({ target: pool.address })),
chain
);
const totalSupplyToken = await multiCallOutput(
VaultABI.find(({ name }) => name === 'totalSupply'),
pools.map((pool) => ({ target: pool.address })),
chain
);
const totalBorrowed = await multiCallOutput(
VaultABI.find(({ name }) => name === 'vaultDebtVal'),
pools.map((pool) => ({ target: pool.address })),
chain
);
const decimals = await multiCallOutput(
VaultABI.find(({ name }) => name === 'decimals'),
pools.map((pool) => ({ target: pool.address })),
chain
);
const configs = await multiCallOutput(
VaultABI.find(({ name }) => name === 'config'),
pools.map((pool) => ({ target: pool.address })),
chain
);
const totalToken = await multiCallOutput(
Erc20ABI.find(({ name }) => name === 'balanceOf'),
pools.map((pool) => ({
target: pool.token.id,
params: [pool.address],
})),
chain
);
const interestRate = await multiCallOutput(
ConfigurableInterestVaultConfigABI.find(
({ name }) => name === 'getInterestRate'
),
configs.map((config, i) => {
return {
target: config,
params: [totalBorrowed[i].toString(), totalToken[i].toString()],
};
}),
chain
);
const totalIbStaked = await multiCallOutput(
VaultABI.find(({ name }) => name === 'balanceOf'),
configs.map((config, i) => {
return {
target: pools[i].address,
params: bigBang[chain],
};
}),
chain
);
const lendingPerformanceFeeBps = await multiCallOutput(
ConfigurableInterestVaultConfigABI.find(
({ name }) => name === 'getReservePoolBps'
),
configs.map((config, i) => {
return {
target: config,
};
}),
chain
);
const allocPoint = await multiCallOutput(
BigBangABI.find(({ name }) => name === 'poolInfo'),
pools.map((pool) => ({
params: [pool.fairLaunchPoolId],
target: bigBang[chain],
})),
chain
);
const totalAllocPoint = await multiCallOutput(
BigBangABI.find(({ name }) => name === 'totalAllocPoint'),
[{ target: bigBang[chain] }],
chain
);
const singlePerBlock = await multiCallOutput(
BigBangABI.find(({ name }) => name === 'singlePerBlock'),
[{ target: bigBang[chain] }],
chain
);
const singleVaultDeimals = await multiCallOutput(
VaultABI.find(({ name }) => name === 'decimals'),
[{ target: singleVaultAddress[chain] }],
chain
);
const prices = await getPrices(
pools.map((pool) => {
return chain + ':' + pool.token.id;
})
);
const res = pools.map((pool, idx) => {
const tokenAddress = pool.token.id.toLowerCase();
const totalSupplyUsd = getPriceInUsd(
totalSupply[idx],
decimals[idx],
prices[tokenAddress]
);
const totalBorrowUsd = getPriceInUsd(
totalBorrowed[idx],
decimals[idx],
prices[tokenAddress]
);
const lendingPerformanceFee = Number(lendingPerformanceFeeBps[idx]) / 10000;
const apyBase = getApyBase(
secondToYearlyInterestRate(interestRate[idx]),
totalBorrowed[idx],
totalSupply[idx],
lendingPerformanceFee
);
let conversionRate = 1;
if (totalSupplyToken[idx] && Number(totalSupplyToken[idx]) > 0) {
conversionRate = totalSupply[idx] / totalSupplyToken[idx];
}
const stakedTvl =
(Number(totalIbStaked[idx]) / Math.pow(10, decimals[idx])) *
prices[tokenAddress] *
conversionRate;
const apyReward = getApyReward(
stakedTvl,
allocPoint[idx].allocPoint,
totalAllocPoint[0],
singlePerBlock[0] / Math.pow(10, singleVaultDeimals[0]),
singlePrice[singleToken[chain]],
blocksPerYears[chain]
);
return {
pool: pool.address,
chain: utils.formatChain(chain),
project: 'single-finance',
symbol: pool.token.symbol,
tvlUsd: totalSupplyUsd - totalBorrowUsd,
apyBaseBorrow: secondToYearlyInterestRate(interestRate[idx]) * 100,
totalSupplyUsd,
totalBorrowUsd,
apyBase,
apyReward: chain === 'cronos' ? apyReward : 0,
underlyingTokens: [pool.token.id],
rewardTokens: chain === 'cronos' ? [singleToken[chain]] : [],
ltv: 0,
};
});
return res;
};
const apy = async () => {
const cronosPools = await getApy('cronos');
const fantomPools = await getApy('fantom');
return [...cronosPools, ...fantomPools];
};
module.exports = {
timetravel: false,
apy: apy,
url: 'https://app.singlefinance.io/lend',
};