|
| 1 | +const sdk = require('@defillama/sdk') |
| 2 | +const axios = require('axios') |
| 3 | +const utils = require('../utils') |
| 4 | + |
| 5 | +const abi = { |
| 6 | + convertToAssets: { "inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function" }, |
| 7 | + totalAssets: { "inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"" ,"type":"uint256"}],"stateMutability":"view","type":"function" }, |
| 8 | +} |
| 9 | + |
| 10 | +const DAY = 86_400 |
| 11 | +const SCALE = BigInt(1e18) |
| 12 | +const SHARES = BigInt(1e18) |
| 13 | + |
| 14 | +async function getBlockAtTimestamp(chain, ts) { |
| 15 | + const { data } = await axios.get(`https://coins.llama.fi/block/${chain}/${ts}`) |
| 16 | + return { block: data.height || data.number, ts: data.timestamp || ts } |
| 17 | +} |
| 18 | + |
| 19 | +async function readShareToAssetRatio(chain, blockNumber, vault) { |
| 20 | + const { output } = await sdk.api.abi.call({ |
| 21 | + target: vault, |
| 22 | + abi: abi.convertToAssets, |
| 23 | + params: [SHARES.toString()], |
| 24 | + chain, |
| 25 | + block: blockNumber, |
| 26 | + }) |
| 27 | + return BigInt(output) |
| 28 | +} |
| 29 | + |
| 30 | +function ratioToDaily(rNow, rPrev, secondsBigOrNum) { |
| 31 | + if (rPrev === 0n || rNow === 0n) return 0 |
| 32 | + const qFP = (rNow * SCALE) / rPrev |
| 33 | + const q = Number(qFP) / Number(SCALE) |
| 34 | + if (!(q > 0) || !isFinite(q)) return 0 |
| 35 | + const seconds = Number(secondsBigOrNum) || 1 |
| 36 | + const exp = DAY / seconds |
| 37 | + return Math.pow(q, exp) - 1 |
| 38 | +} |
| 39 | + |
| 40 | +async function computeApyBase(chain, vault) { |
| 41 | + const nowTs = Math.floor(Date.now() / 1e3) |
| 42 | + const WEEK = 7 * DAY |
| 43 | + |
| 44 | + const [{ block: bNow, ts: tNow }, { block: bPast, ts: tPast }] = await Promise.all([ |
| 45 | + getBlockAtTimestamp(chain, nowTs), |
| 46 | + getBlockAtTimestamp(chain, nowTs - WEEK), |
| 47 | + ]) |
| 48 | + |
| 49 | + const [rNow, rPast] = await Promise.all([ |
| 50 | + readShareToAssetRatio(chain, bNow, vault), |
| 51 | + readShareToAssetRatio(chain, bPast, vault) |
| 52 | + ]) |
| 53 | + if (rNow === 0n || rPast === 0n || rNow === rPast) return 0 |
| 54 | + |
| 55 | + const daily = ratioToDaily(rNow, rPast, Math.max(1, tNow - tPast)) |
| 56 | + if (!daily) return 0 |
| 57 | + |
| 58 | + const aprBase = daily * 365 * 100 |
| 59 | + return utils.aprToApy(aprBase, 365) // number |
| 60 | +} |
| 61 | + |
| 62 | +async function getPrice(chain, token, tokenSubstitute) { |
| 63 | + async function _getPrice(chain, tokenAddress) { |
| 64 | + const priceKey = `${chain}:${tokenAddress}` |
| 65 | + const { data } = await axios.get(`https://coins.llama.fi/prices/current/${priceKey}`) |
| 66 | + price = data.coins?.[priceKey]?.price ?? 0 |
| 67 | + return price |
| 68 | + } |
| 69 | + const price = await _getPrice(chain, token) |
| 70 | + if (price) { |
| 71 | + return price |
| 72 | + } else if (tokenSubstitute) { |
| 73 | + return await _getPrice(chain, tokenSubstitute) |
| 74 | + } |
| 75 | + return 1 |
| 76 | +} |
| 77 | + |
| 78 | +async function getData(chain, vault, underlying, underlyingSubstitute = undefined) { |
| 79 | + const { output: totalAssetsBn } = await sdk.api.abi.call({ |
| 80 | + target: vault, |
| 81 | + abi: abi.totalAssets, |
| 82 | + chain, |
| 83 | + }) |
| 84 | + |
| 85 | + const price = await getPrice(chain, underlying, underlyingSubstitute) |
| 86 | + const tvlUsd = (Number(totalAssetsBn) / 1e18) * price |
| 87 | + const apyBase = await computeApyBase(chain, vault) |
| 88 | + |
| 89 | + return { tvlUsd, apyBase } |
| 90 | +} |
| 91 | + |
| 92 | +module.exports = { |
| 93 | + getData, |
| 94 | +} |
0 commit comments