mirror of
https://github.com/uniwhale-io/DefiLlama-yield-server.git
synced 2026-01-12 17:12:21 +08:00
Add Single Finance (#621)
* Add Single Finance * update token symbol and add ltv * update syntax
This commit is contained in:
committed by
GitHub
parent
e87196bd2f
commit
6849629116
219
src/adaptors/single-finance/abis.js
Normal file
219
src/adaptors/single-finance/abis.js
Normal 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',
|
||||
},
|
||||
],
|
||||
};
|
||||
300
src/adaptors/single-finance/index.js
Normal file
300
src/adaptors/single-finance/index.js
Normal 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',
|
||||
};
|
||||
Reference in New Issue
Block a user