diff --git a/src/adaptors/inverse-finance-firm/ERC4626.json b/src/adaptors/inverse-finance-firm/ERC4626.json new file mode 100644 index 0000000000..90dcd58ec3 --- /dev/null +++ b/src/adaptors/inverse-finance-firm/ERC4626.json @@ -0,0 +1,461 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [{ "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_from", "type": "address" }, + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [{ "name": "", "type": "uint8" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_owner", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "name": "balance", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [{ "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "name": "_owner", "type": "address" }, + { "name": "_spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { "payable": true, "stateMutability": "payable", "type": "fallback" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "owner", "type": "address" }, + { "indexed": true, "name": "spender", "type": "address" }, + { "indexed": false, "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "from", "type": "address" }, + { "indexed": true, "name": "to", "type": "address" }, + { "indexed": false, "name": "value", "type": "uint256" } + ], + "name": "Transfer", + "type": "event" + }, + { + "name": "asset", + "type": "function", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "assetTokenAddress", + "type": "address" + } + ] + }, + { + "name": "totalAssets", + "type": "function", + "stateMutability": "view", + "inputs": [], + "outputs": [ + { + "name": "totalManagedAssets", + "type": "uint256" + } + ] + }, + { + "name": "convertToShares", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256" + } + ] + }, + { + "name": "convertToAssets", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256" + } + ] + }, + { + "name": "maxDeposit", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "receiver", + "type": "address" + } + ], + "outputs": [ + { + "name": "maxAssets", + "type": "uint256" + } + ] + }, + { + "name": "previewDeposit", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256" + } + ] + }, + { + "name": "deposit", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "assets", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256" + } + ] + }, + { + "name": "maxMint", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "receiver", + "type": "address" + } + ], + "outputs": [ + { + "name": "maxShares", + "type": "uint256" + } + ] + }, + { + "name": "previewMint", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256" + } + ] + }, + { + "name": "mint", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "shares", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256" + } + ] + }, + { + "name": "maxWithdraw", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "outputs": [ + { + "name": "maxAssets", + "type": "uint256" + } + ] + }, + { + "name": "previewWithdraw", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "assets", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256" + } + ] + }, + { + "name": "withdraw", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "assets", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "owner", + "type": "address" + } + ], + "outputs": [ + { + "name": "shares", + "type": "uint256" + } + ] + }, + { + "name": "maxRedeem", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "outputs": [ + { + "name": "maxShares", + "type": "uint256" + } + ] + }, + { + "name": "previewRedeem", + "type": "function", + "stateMutability": "view", + "inputs": [ + { + "name": "shares", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256" + } + ] + }, + { + "name": "redeem", + "type": "function", + "stateMutability": "nonpayable", + "inputs": [ + { + "name": "shares", + "type": "uint256" + }, + { + "name": "receiver", + "type": "address" + }, + { + "name": "owner", + "type": "address" + } + ], + "outputs": [ + { + "name": "assets", + "type": "uint256" + } + ] + }, + { + "name": "Deposit", + "type": "event", + "inputs": [ + { + "name": "_caller", + "indexed": true, + "type": "address" + }, + { + "name": "owner", + "indexed": true, + "type": "address" + }, + { + "name": "assets", + "indexed": false, + "type": "uint256" + }, + { + "name": "shares", + "indexed": false, + "type": "uint256" + } + ] + }, + { + "name": "Withdraw", + "type": "event", + "inputs": [ + { + "name": "_caller", + "indexed": true, + "type": "address" + }, + { + "name": "receiver", + "indexed": true, + "type": "address" + }, + { + "name": "owner", + "indexed": true, + "type": "address" + }, + { + "name": "assets", + "indexed": false, + "type": "uint256" + }, + { + "name": "shares", + "indexed": false, + "type": "uint256" + } + ] + } + ] + \ No newline at end of file diff --git a/src/adaptors/inverse-finance-firm/abi.ts b/src/adaptors/inverse-finance-firm/abi.ts index d9dd4cbef6..fc3658a21c 100644 --- a/src/adaptors/inverse-finance-firm/abi.ts +++ b/src/adaptors/inverse-finance-firm/abi.ts @@ -75,5 +75,7 @@ module.exports = { ], "dbr": [ "event AddMarket(address indexed market)" - ] + ], + "weeklyRevenue": {"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"weeklyRevenue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, + "getMarketPrice": {"inputs":[{"internalType":"address","name":"market","type":"address"}],"name":"getMarketPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}, } \ No newline at end of file diff --git a/src/adaptors/inverse-finance-firm/index.js b/src/adaptors/inverse-finance-firm/index.js index 97b2d35ad3..5b30b15e71 100644 --- a/src/adaptors/inverse-finance-firm/index.js +++ b/src/adaptors/inverse-finance-firm/index.js @@ -5,6 +5,7 @@ const BigNumber = require('bignumber.js'); const utils = require('../utils'); const abi = require('./abi'); const path = require('path'); +const ERC4626abi = require('./ERC4626.json'); require('dotenv').config({ path: path.resolve(__dirname, '../../../config.env'), @@ -13,11 +14,27 @@ require('dotenv').config({ const firmStart = 16159015; const DBR = '0xAD038Eb671c44b853887A7E32528FaB35dC5D710'; const DOLA = '0x865377367054516e17014CcdED1e7d814EDC9ce4'; +const INV = '0x41D5D79431A913C4aE7d69a668ecdfE5fF9DFB68'; +const SINV_ADDRESS = '0x08d23468A467d2bb86FaE0e32F247A26C7E2e994'; +const SDOLA_ADDRESS = '0xb45ad160634c528Cc3D2926d9807104FA3157305'; +const TOKENS_VIEWER = '0x826bBeB1DBd9aA36CD44538CC45Dcf9E93BDA574'; +const FIRM_VIEWER = '0x545C963eB523199969cf0298395EeAdcE514b691'; +const ONE_DAY_MS = 86400000; +const WEEKS_PER_YEAR = 365 / 7; const provider = new ethers.providers.JsonRpcProvider( process.env.ALCHEMY_CONNECTION_ETHEREUM ); +const getWeekIndexUtc = (ts) => { + const d = ts ? new Date(ts) : new Date(); + const weekFloat = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), 0, 0, 0) / (ONE_DAY_MS * 7); + return Math.floor(weekFloat); +} + +const aprToApy = (apr, compoundingsPerYear) => + !compoundingsPerYear ? apr : (Math.pow(1 + (apr / 100) / compoundingsPerYear, compoundingsPerYear) - 1) * 100; + const l1TokenPrices = async (l1TokenAddrs) => { const l1TokenQuery = l1TokenAddrs.map((addr) => `ethereum:${addr}`).join(); const data = await utils.getData( @@ -62,6 +79,7 @@ const getFirmEscrowsWithMarket = async (markets, provider) => { return escrowsWithMarkets; }; + const main = async () => { const balances = {}; @@ -93,6 +111,8 @@ const main = async () => { ).output; const underlyings = allUnderlying.map((u) => u.output); + const addressesToPrice = underlyings.concat([DBR, DOLA, INV]); + const prices = await l1TokenPrices(addressesToPrice); const allDebt = ( await sdk.api.abi.multiCall({ @@ -105,6 +125,39 @@ const main = async () => { }) ).output; + const allSymbols = ( + await sdk.api.abi.multiCall({ + chain: 'ethereum', + calls: underlyings.map((m) => ({ + target: m, + params: [], + })), + abi: 'string:symbol', + }) + ).output; + + const allDecimals = ( + await sdk.api.abi.multiCall({ + chain: 'ethereum', + calls: underlyings.map((m) => ({ + target: m, + params: [], + })), + abi: 'uint:decimals', + }) + ).output; + + const allPrices = ( + await sdk.api.abi.multiCall({ + chain: 'ethereum', + calls: markets.map((m) => ({ + target: FIRM_VIEWER, + params: [m], + })), + abi: abi.getMarketPrice, + }) + ).output; + const allCfs = ( await sdk.api.abi.multiCall({ chain: 'ethereum', @@ -138,9 +191,6 @@ const main = async () => { }) ).output; - const addressesToPrice = underlyings.concat([DBR, DOLA]); - const prices = await l1TokenPrices(addressesToPrice); - allBalances.map((b, i) => { const market = escrowsWithMarkets[i].market; if (!balances[market]) { @@ -155,10 +205,12 @@ const main = async () => { const pools = markets.map((m, marketIndex) => { const underlying = allUnderlying.find((u) => u.input.target === m).output; - const decimals = prices[underlying].decimals; - const symbol = prices[underlying].symbol; + const decimals = Number(allDecimals[marketIndex].output); + const symbol = allSymbols[marketIndex].output; + // use oracle price as fallback for exotic collaterals such as pendle collaterals + const price = prices[underlying].price || (Number(allPrices[marketIndex].output)/1e18); const totalSupplyUsd = - (Number(balances[m]) / 10 ** decimals) * prices[underlying].price; + (Number(balances[m]) / 10 ** decimals) * price; const totalBorrowUsd = Number(allDebt[marketIndex].output) / 1e18; const debtCeilingUsd = (Number(allLiquidity[marketIndex].output) / 1e18) * prices[DOLA].price; @@ -172,7 +224,7 @@ const main = async () => { apyBase: 0, underlyingTokens: [underlying], poolMeta: 'Fixed Borrow Rate', - url: 'https://inverse.finance/firm/' + symbol, + url: 'https://inverse.finance/firm', apyBaseBorrow: currentFixedRate, debtCeilingUsd: debtCeilingUsd + totalBorrowUsd, totalSupplyUsd, @@ -182,6 +234,115 @@ const main = async () => { }; }); + const weekIndex = getWeekIndexUtc(); + // sDOLA + const sdolaData = ( + await Promise.all([ + sdk.api.abi.multiCall( + { + chain: 'ethereum', + calls: [ + { + target: SDOLA_ADDRESS, + params: [], + }, + ], + abi: 'uint:totalAssets', + } + ), + sdk.api.abi.multiCall( + { + chain: 'ethereum', + calls: [ + { + target: SDOLA_ADDRESS, + params: [weekIndex - 1], + }, + ], + abi: abi.weeklyRevenue, + } + ), + ]) + ); + + const sDolaTotalAssets = Number(sdolaData[0].output[0].output) / 1e18; + const sDolaPastWeekRevenue = Number(sdolaData[1].output[0].output) / 1e18; + const sDOLAapr = sDolaTotalAssets > 0 ? (sDolaPastWeekRevenue * WEEKS_PER_YEAR) / sDolaTotalAssets * 100 : 0; + + // add sDOLA + pools.push({ + pool: `sDOLA`, + chain: 'Ethereum', + project: 'inverse-finance-firm', + mintedCoin: 'sDOLA', + symbol: 'sDOLA', + tvlUsd: sDolaTotalAssets * prices[DOLA].price, + apyBase: aprToApy(sDOLAapr, WEEKS_PER_YEAR), + underlyingTokens: [DOLA], + poolMeta: 'Yield-Bearing stable', + url: 'https://inverse.finance/sDOLA', + }); + + // sINV + const sinvData = ( + await Promise.all([ + sdk.api.abi.multiCall( + { + chain: 'ethereum', + calls: [ + { + target: SINV_ADDRESS, + params: [], + }, + ], + abi: 'uint:totalAssets', + } + ), + sdk.api.abi.multiCall( + { + chain: 'ethereum', + calls: [ + { + target: SINV_ADDRESS, + params: [], + }, + ], + abi: 'uint:lastPeriodRevenue', + } + ), + sdk.api.abi.multiCall( + { + chain: 'ethereum', + calls: [ + { + target: TOKENS_VIEWER, + params: [], + }, + ], + abi: 'uint:getDbrApr', + } + ), + ]) + ); + + const sInvTotalAssets = Number(sinvData[0].output[0].output) / 1e18; + const sinvPastWeekRevenue = Number(sinvData[1].output[0].output) / 1e18; + const sINVapr = sDolaTotalAssets > 0 ? (sinvPastWeekRevenue * WEEKS_PER_YEAR) / sInvTotalAssets * 100 : 0; + const dbrApr = Number(sinvData[2].output[0].output) / 1e18; + + // add sINV + pools.push({ + pool: `sINV`, + chain: 'Ethereum', + project: 'inverse-finance-firm', + mintedCoin: 'sINV', + symbol: 'sINV', + tvlUsd: sInvTotalAssets * prices[INV].price, + apyBase: aprToApy(sINVapr, WEEKS_PER_YEAR), + underlyingTokens: [INV], + url: 'https://inverse.finance/sINV', + }); + return utils.removeDuplicates(pools.filter((p) => utils.keepFinite(p))); };