From 86bf2275f8522d2870759fd36459e94f87a6920a Mon Sep 17 00:00:00 2001 From: William Poulin Date: Mon, 20 Jun 2022 15:54:32 -0400 Subject: [PATCH] fix: refactored solace's helpers to be injectable + enabled solace TVL --- .../solace/ethereum/helpers/getBondBalance.ts | 47 -------- .../ethereum/helpers/getPolicyBalance.ts | 30 ----- .../solace/ethereum/helpers/getScpBalance.ts | 13 -- .../ethereum/helpers/getXSLockerBalance.ts | 55 --------- .../ethereum/helpers/getXSolaceV1Balance.ts | 13 -- .../solace/ethereum/solace.balance-fetcher.ts | 92 +++++++++++--- .../solace.bonds.contract-position-fetcher.ts | 87 +++++++------ ...lace.policies.contract-position-fetcher.ts | 6 +- .../ethereum/solace.scp.token-fetcher.ts | 22 ++-- .../solace/ethereum/solace.tvl-fetcher.ts | 114 ------------------ ...lace.xslocker.contract-position-fetcher.ts | 6 +- .../solace.xsolacev1.token-fetcher.ts | 15 ++- .../solace/helpers/SolaceBondBalanceHelper.ts | 51 ++++++++ .../helpers/SolacePolicyBalanceHelper.ts | 35 ++++++ .../solace/helpers/SolaceXSBalanceHelper.ts | 59 +++++++++ .../solace/polygon/helpers/getBondBalance.ts | 49 -------- .../polygon/helpers/getPolicyBalance.ts | 30 ----- .../polygon/helpers/getXSLockerBalance.ts | 55 --------- .../solace/polygon/solace.balance-fetcher.ts | 59 ++++++--- .../solace.bonds.contract-position-fetcher.ts | 81 ++++++------- ...lace.policies.contract-position-fetcher.ts | 6 +- src/apps/solace/polygon/solace.tvl-fetcher.ts | 104 ---------------- src/apps/solace/solace.definition.ts | 3 +- src/apps/solace/solace.module.ts | 17 ++- 24 files changed, 381 insertions(+), 668 deletions(-) delete mode 100644 src/apps/solace/ethereum/helpers/getBondBalance.ts delete mode 100644 src/apps/solace/ethereum/helpers/getPolicyBalance.ts delete mode 100644 src/apps/solace/ethereum/helpers/getScpBalance.ts delete mode 100644 src/apps/solace/ethereum/helpers/getXSLockerBalance.ts delete mode 100644 src/apps/solace/ethereum/helpers/getXSolaceV1Balance.ts delete mode 100644 src/apps/solace/ethereum/solace.tvl-fetcher.ts create mode 100644 src/apps/solace/helpers/SolaceBondBalanceHelper.ts create mode 100644 src/apps/solace/helpers/SolacePolicyBalanceHelper.ts create mode 100644 src/apps/solace/helpers/SolaceXSBalanceHelper.ts delete mode 100644 src/apps/solace/polygon/helpers/getBondBalance.ts delete mode 100644 src/apps/solace/polygon/helpers/getPolicyBalance.ts delete mode 100644 src/apps/solace/polygon/helpers/getXSLockerBalance.ts delete mode 100644 src/apps/solace/polygon/solace.tvl-fetcher.ts diff --git a/src/apps/solace/ethereum/helpers/getBondBalance.ts b/src/apps/solace/ethereum/helpers/getBondBalance.ts deleted file mode 100644 index f0086ace2..000000000 --- a/src/apps/solace/ethereum/helpers/getBondBalance.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ethers } from 'ethers'; -import { range } from 'lodash'; - -import { drillBalance } from '~app-toolkit'; -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { MetaType } from '~position/position.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../../contracts'; -import { SOLACE_DEFINITION } from '../../solace.definition'; - -const BN = ethers.BigNumber; - -export default async function getBondBalance( - address: string, - appToolkit: IAppToolkit, - solaceContractFactory: SolaceContractFactory, -) { - const network = Network.ETHEREUM_MAINNET; - return appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.bonds.id, - network, - resolveBalances: async ({ address, contractPosition, multicall }) => { - // Resolve the staked token and reward token from the contract position object - const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; - const rewardToken = contractPosition.tokens.find(t => t.metaType === MetaType.CLAIMABLE)!; - const teller = solaceContractFactory.bondTellerErc20({ address: contractPosition.address, network }); - - const mct = multicall.wrap(teller); - const balance = await mct.balanceOf(address); - const indices = range(0, balance.toNumber()); - const tokenIDs = await Promise.all(indices.map((i: number) => mct.tokenOfOwnerByIndex(address, i))); - const bonds = await Promise.all(tokenIDs.map(id => mct.bonds(id))); - - let supplySum = BN.from(0); - let rewardSum = BN.from(0); - indices.forEach((i: number) => { - supplySum = supplySum.add(bonds[i].principalPaid); - rewardSum = rewardSum.add(bonds[i].payoutAmount.sub(bonds[i].payoutAlreadyClaimed)); - }); - - return [drillBalance(stakedToken, supplySum.toString()), drillBalance(rewardToken, rewardSum.toString())]; - }, - }); -} diff --git a/src/apps/solace/ethereum/helpers/getPolicyBalance.ts b/src/apps/solace/ethereum/helpers/getPolicyBalance.ts deleted file mode 100644 index fd507de5b..000000000 --- a/src/apps/solace/ethereum/helpers/getPolicyBalance.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { drillBalance } from '~app-toolkit'; -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { MetaType } from '~position/position.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../../contracts'; -import { SOLACE_DEFINITION } from '../../solace.definition'; - -export default async function getPolicyBalance( - address: string, - appToolkit: IAppToolkit, - solaceContractFactory: SolaceContractFactory, -) { - const network = Network.ETHEREUM_MAINNET; - return appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.policies.id, - network, - resolveBalances: async ({ address, contractPosition }) => { - // Resolve the staked token and reward token from the contract position object - const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; - - const product = solaceContractFactory.solaceCoverProduct({ address: contractPosition.address, network }); - const bal = await product.accountBalanceOf(address); - - return [drillBalance(stakedToken, bal.toString())]; - }, - }); -} diff --git a/src/apps/solace/ethereum/helpers/getScpBalance.ts b/src/apps/solace/ethereum/helpers/getScpBalance.ts deleted file mode 100644 index a41d69adf..000000000 --- a/src/apps/solace/ethereum/helpers/getScpBalance.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { Network } from '~types/network.interface'; - -import { SOLACE_DEFINITION } from '../../solace.definition'; - -export default async function getScpBalance(address: string, appToolkit: IAppToolkit) { - return appToolkit.helpers.tokenBalanceHelper.getTokenBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.scp.id, - network: Network.ETHEREUM_MAINNET, - }); -} diff --git a/src/apps/solace/ethereum/helpers/getXSLockerBalance.ts b/src/apps/solace/ethereum/helpers/getXSLockerBalance.ts deleted file mode 100644 index 8d8ce65bc..000000000 --- a/src/apps/solace/ethereum/helpers/getXSLockerBalance.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ethers } from 'ethers'; -import { range } from 'lodash'; - -import { drillBalance } from '~app-toolkit'; -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { MetaType } from '~position/position.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../../contracts'; -import { SOLACE_DEFINITION } from '../../solace.definition'; - -const BN = ethers.BigNumber; - -const XSLOCKER_ADDRESS = '0x501ace47c5b0c2099c4464f681c3fa2ecd3146c1'; -const STAKING_REWARDS_ADDRESS = '0x501ace3d42f9c8723b108d4fbe29989060a91411'; - -export default async function getXSLockerBalance( - address: string, - appToolkit: IAppToolkit, - solaceContractFactory: SolaceContractFactory, -) { - const network = Network.ETHEREUM_MAINNET; - return appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.xslocker.id, - network, - resolveBalances: async ({ address, contractPosition, multicall }) => { - // Resolve the staked token and reward token from the contract position object - const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; - const rewardToken = contractPosition.tokens.find(t => t.metaType === MetaType.CLAIMABLE)!; - - const xslocker = solaceContractFactory.xsLocker({ address: XSLOCKER_ADDRESS, network }); - const stakingRewards = solaceContractFactory.stakingRewards({ address: STAKING_REWARDS_ADDRESS, network }); - - const mcxsl = multicall.wrap(xslocker); - const mcsr = multicall.wrap(stakingRewards); - - const balance = await xslocker.balanceOf(address); - const indices = range(0, balance.toNumber()); - const tokenIDs = await Promise.all(indices.map((i: number) => mcxsl.tokenOfOwnerByIndex(address, i))); - const locks = await Promise.all(tokenIDs.map(id => mcxsl.locks(id))); - const rewards = await Promise.all(tokenIDs.map(id => mcsr.pendingRewardsOfLock(id))); - - let supplySum = BN.from(0); - let rewardSum = BN.from(0); - indices.forEach((i: number) => { - supplySum = supplySum.add(locks[i].amount); - rewardSum = rewardSum.add(rewards[i]); - }); - - return [drillBalance(stakedToken, supplySum.toString()), drillBalance(rewardToken, rewardSum.toString())]; - }, - }); -} diff --git a/src/apps/solace/ethereum/helpers/getXSolaceV1Balance.ts b/src/apps/solace/ethereum/helpers/getXSolaceV1Balance.ts deleted file mode 100644 index b620cdc0d..000000000 --- a/src/apps/solace/ethereum/helpers/getXSolaceV1Balance.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { Network } from '~types/network.interface'; - -import { SOLACE_DEFINITION } from '../../solace.definition'; - -export default async function getXSolaceV1Balance(address: string, appToolkit: IAppToolkit) { - return appToolkit.helpers.tokenBalanceHelper.getTokenBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.xsolacev1.id, - network: Network.ETHEREUM_MAINNET, - }); -} diff --git a/src/apps/solace/ethereum/solace.balance-fetcher.ts b/src/apps/solace/ethereum/solace.balance-fetcher.ts index 5fe3fe4f7..eed1302cf 100644 --- a/src/apps/solace/ethereum/solace.balance-fetcher.ts +++ b/src/apps/solace/ethereum/solace.balance-fetcher.ts @@ -6,38 +6,94 @@ import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation import { BalanceFetcher } from '~balance/balance-fetcher.interface'; import { Network } from '~types/network.interface'; -import { SolaceContractFactory } from '../contracts'; +import { SolaceBondBalanceHelper } from '../helpers/SolaceBondBalanceHelper'; +import { SolacePolicyBalanceHelper } from '../helpers/SolacePolicyBalanceHelper'; +import { SolaceXSBalanceHelper } from '../helpers/SolaceXSBalanceHelper'; import { SOLACE_DEFINITION } from '../solace.definition'; -import getBondBalance from './helpers/getBondBalance'; -import getPolicyBalance from './helpers/getPolicyBalance'; -import getScpBalance from './helpers/getScpBalance'; -import getXSLockerBalance from './helpers/getXSLockerBalance'; -import getXSolaceV1Balance from './helpers/getXSolaceV1Balance'; - const network = Network.ETHEREUM_MAINNET; @Register.BalanceFetcher(SOLACE_DEFINITION.id, network) export class EthereumSolaceBalanceFetcher implements BalanceFetcher { constructor( @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, - @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, + @Inject(SolaceBondBalanceHelper) + private readonly solaceBondBalanceHelper: SolaceBondBalanceHelper, + @Inject(SolacePolicyBalanceHelper) + private readonly solacePolicyBalanceHelper: SolacePolicyBalanceHelper, + @Inject(SolaceXSBalanceHelper) + private readonly solaceXSBalanceHelper: SolaceXSBalanceHelper, ) {} + async getScpBalance(address: string) { + return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({ + address, + appId: SOLACE_DEFINITION.id, + groupId: SOLACE_DEFINITION.groups.scp.id, + network, + }); + } + + async getXSolaceV1Balance(address: string) { + return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({ + address, + appId: SOLACE_DEFINITION.id, + groupId: SOLACE_DEFINITION.groups.xsolacev1.id, + network, + }); + } + + async getXSLockerBalance(address: string) { + return this.solaceXSBalanceHelper.getBalances({ + address, + network, + }); + } + + async getBondBalance(address: string) { + return this.solaceBondBalanceHelper.getBalances({ + address, + network, + }); + } + + async getPolicyBalance(address: string) { + return this.solacePolicyBalanceHelper.getBalances({ + address, + network, + }); + } + async getBalances(address: string) { const [scpBal, xsolaceBal, xslockerBal, bondBal, policyBal] = await Promise.all([ - getScpBalance(address, this.appToolkit), - getXSolaceV1Balance(address, this.appToolkit), - getXSLockerBalance(address, this.appToolkit, this.solaceContractFactory), - getBondBalance(address, this.appToolkit, this.solaceContractFactory), - getPolicyBalance(address, this.appToolkit, this.solaceContractFactory), + this.getScpBalance(address), + this.getXSolaceV1Balance(address), + this.getXSLockerBalance(address), + this.getBondBalance(address), + this.getPolicyBalance(address), ]); + return presentBalanceFetcherResponse([ - { label: 'SCP', assets: scpBal }, - { label: 'xSOLACEv1', assets: xsolaceBal }, - { label: 'xsLocker', assets: xslockerBal }, - { label: 'Bonds', assets: bondBal }, - { label: 'Policies', assets: policyBal }, + { + label: 'SCP', + assets: scpBal, + }, + { + label: 'xSOLACEv1', + assets: xsolaceBal, + }, + { + label: 'xsLocker', + assets: xslockerBal, + }, + { + label: 'Bonds', + assets: bondBal, + }, + { + label: 'Policies', + assets: policyBal, + }, ]); } } diff --git a/src/apps/solace/ethereum/solace.bonds.contract-position-fetcher.ts b/src/apps/solace/ethereum/solace.bonds.contract-position-fetcher.ts index e4bc3d356..1e211d7da 100644 --- a/src/apps/solace/ethereum/solace.bonds.contract-position-fetcher.ts +++ b/src/apps/solace/ethereum/solace.bonds.contract-position-fetcher.ts @@ -3,6 +3,7 @@ import { compact } from 'lodash'; import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; import { Register } from '~app-toolkit/decorators'; +import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present'; import { getImagesFromToken, getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present'; import { ContractType } from '~position/contract.interface'; import { PositionFetcher } from '~position/position-fetcher.interface'; @@ -10,57 +11,34 @@ import { ContractPosition } from '~position/position.interface'; import { claimable, supplied } from '~position/position.utils'; import { Network } from '~types/network.interface'; +import { SolaceContractFactory } from '../contracts'; import { SOLACE_DEFINITION } from '../solace.definition'; const appId = SOLACE_DEFINITION.id; const groupId = SOLACE_DEFINITION.groups.bonds.id; const network = Network.ETHEREUM_MAINNET; -const SOLACE_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; - -const BOND_TELLERS = [ - { - name: 'Solace DAI Bond', - address: '0x501ace677634fd09a876e88126076933b686967a', - deposit: '0x6b175474e89094c44da98b954eedeac495271d0f', - }, - { - name: 'Solace ETH Bond', - address: '0x501ace95141f3eb59970dd64af0405f6056fb5d8', - deposit: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - }, - { - name: 'Solace USDC Bond', - address: '0x501ace7e977e06a3cb55f9c28d5654c9d74d5ca9', - deposit: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - }, - { - name: 'Solace WBTC Bond', - address: '0x501acef0d0c73bd103337e6e9fd49d58c426dc27', - deposit: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', - }, - { - name: 'Solace USDT Bond', - address: '0x501ace5ceec693df03198755ee80d4ce0b5c55fe', - deposit: '0xdac17f958d2ee523a2206206994597c13d831ec7', - }, - { - name: 'Solace SCP Bond', - address: '0x501ace00fd8e5db7c3be5e6d254ba4995e1b45b7', - deposit: '0x501acee83a6f269b77c167c6701843d454e2efa0', - }, - { - name: 'Solace FRAX Bond', - address: '0x501acef4f8397413c33b13cb39670ad2f17bfe62', - deposit: '0x853d955acef822db058eb8505911ed77f175b99e', - }, +const SOLACE_TOKEN_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; + +const BOND_TELLER_ADDRESSES = [ + '0x501ace677634fd09a876e88126076933b686967a', // DAI Bond + '0x501ace95141f3eb59970dd64af0405f6056fb5d8', // ETH Bond + '0x501ace7e977e06a3cb55f9c28d5654c9d74d5ca9', // USDC Bond + '0x501acef0d0c73bd103337e6e9fd49d58c426dc27', // WBTC Bond + '0x501ace5ceec693df03198755ee80d4ce0b5c55fe', // USDT Bond + '0x501ace00fd8e5db7c3be5e6d254ba4995e1b45b7', // SCP Bond + '0x501acef4f8397413c33b13cb39670ad2f17bfe62', // FRAX Bond ]; -@Register.ContractPositionFetcher({ appId, groupId, network }) +@Register.ContractPositionFetcher({ appId, groupId, network, options: { includeInTvl: true } }) export class EthereumSolaceBondsContractPositionFetcher implements PositionFetcher { - constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} + constructor( + @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, + @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, + ) {} async getPositions() { + const multicall = this.appToolkit.getMulticall(network); const baseTokens = await this.appToolkit.getBaseTokenPrices(network); const appTokens = await this.appToolkit.getAppTokenPositions({ appId, @@ -69,25 +47,42 @@ export class EthereumSolaceBondsContractPositionFetcher implements PositionFetch }); const allTokens = [...appTokens, ...baseTokens]; - const solaceToken = baseTokens.find(t => t.address === SOLACE_ADDRESS)!; + const solaceToken = baseTokens.find(t => t.address === SOLACE_TOKEN_ADDRESS)!; const positions = await Promise.all( - BOND_TELLERS.map(async teller => { - const depositToken = allTokens.find(v => v.address === teller.deposit); + BOND_TELLER_ADDRESSES.map(async bondTellerAddress => { + const bondTellerContract = this.solaceContractFactory.bondTellerErc20({ address: bondTellerAddress, network }); + + const [underlyingAddressRaw, underWritingPoolAddress] = await Promise.all([ + multicall.wrap(bondTellerContract).principal(), + multicall.wrap(bondTellerContract).underwritingPool(), + ]); + + const underlyingAddress = underlyingAddressRaw.toLowerCase(); + + const depositToken = allTokens.find(v => v.address === underlyingAddress); if (!depositToken || !solaceToken) return null; const tokens = [supplied(depositToken), claimable(solaceToken)]; + const baseTokenContract = this.solaceContractFactory.erc20({ address: underlyingAddress, network }); + const balanceOfRaw = await multicall.wrap(baseTokenContract).balanceOf(underWritingPoolAddress); + const balanceOf = Number(balanceOfRaw) / 10 ** depositToken.decimals; + const liquidity = balanceOf * depositToken.price; + const position: ContractPosition = { type: ContractType.POSITION, appId, groupId, - address: teller.address, + address: bondTellerAddress, network, tokens, - dataProps: {}, + dataProps: { + liquidity, + }, displayProps: { - label: `${getLabelFromToken(depositToken)} Bond`, + label: `${getLabelFromToken(depositToken)}`, images: getImagesFromToken(depositToken), + statsItems: [{ label: 'Liquidity', value: buildDollarDisplayItem(liquidity) }], }, }; diff --git a/src/apps/solace/ethereum/solace.policies.contract-position-fetcher.ts b/src/apps/solace/ethereum/solace.policies.contract-position-fetcher.ts index 7f461d84c..80d96345c 100644 --- a/src/apps/solace/ethereum/solace.policies.contract-position-fetcher.ts +++ b/src/apps/solace/ethereum/solace.policies.contract-position-fetcher.ts @@ -9,7 +9,6 @@ import { ContractPosition } from '~position/position.interface'; import { supplied } from '~position/position.utils'; import { Network } from '~types/network.interface'; -import { SolaceContractFactory } from '../contracts'; import { SOLACE_DEFINITION } from '../solace.definition'; const appId = SOLACE_DEFINITION.id; @@ -22,10 +21,7 @@ const SOLACE_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; @Register.ContractPositionFetcher({ appId, groupId, network }) export class EthereumSolacePoliciesContractPositionFetcher implements PositionFetcher { - constructor( - @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, - @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, - ) {} + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} async getPositions() { const baseTokens = await this.appToolkit.getBaseTokenPrices(network); diff --git a/src/apps/solace/ethereum/solace.scp.token-fetcher.ts b/src/apps/solace/ethereum/solace.scp.token-fetcher.ts index 6c68dee00..5fc2b998b 100644 --- a/src/apps/solace/ethereum/solace.scp.token-fetcher.ts +++ b/src/apps/solace/ethereum/solace.scp.token-fetcher.ts @@ -17,12 +17,10 @@ const groupId = SOLACE_DEFINITION.groups.scp.id; const network = Network.ETHEREUM_MAINNET; const SCP_ADDRESS = '0x501acee83a6f269b77c167c6701843d454e2efa0'; -const symbol = 'SCP'; -const decimals = 18; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; const SOLACE_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; -@Register.TokenPositionFetcher({ appId, groupId, network }) +@Register.TokenPositionFetcher({ appId, groupId, network, options: { includeInTvl: true } }) export class EthereumSolaceScpTokenFetcher implements PositionFetcher { constructor( @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, @@ -34,13 +32,18 @@ export class EthereumSolaceScpTokenFetcher implements PositionFetcher t.address === ZERO_ADDRESS)!; - const scp = this.solaceContractFactory.scp({ address: SCP_ADDRESS, network }); - const mcscp = multicall.wrap(scp); - const [supplyRaw, pricePerShareRaw] = await Promise.all([mcscp.totalSupply(), mcscp.pricePerShare()]); + const scpTokenContract = this.solaceContractFactory.scp({ address: SCP_ADDRESS, network }); + const [symbol, decimals, supplyRaw, pricePerShareRaw] = await Promise.all([ + multicall.wrap(scpTokenContract).symbol(), + multicall.wrap(scpTokenContract).decimals(), + multicall.wrap(scpTokenContract).totalSupply(), + multicall.wrap(scpTokenContract).pricePerShare(), + ]); const supply = Number(supplyRaw) / 10 ** decimals; const pricePerShare = Number(pricePerShareRaw) / 10 ** decimals; const price = eth.price * pricePerShare; + const liquidity = supply * price; const tokens = [eth]; const token: AppTokenPosition = { @@ -55,11 +58,14 @@ export class EthereumSolaceScpTokenFetcher implements PositionFetcher { - if (token.address === ZERO_ADDRESS) { - const mcmc = multicall.wrap(multicall.contract); - return mcmc.getEthBalance(UWP_ADDRESS).then(v => Number(v) / 10 ** token.decimals); - } else { - const tokenContract = this.solaceContractFactory.erc20({ address: token.address, network }); - const mct = multicall.wrap(tokenContract); - return mct.balanceOf(UWP_ADDRESS).then(v => Number(v) / 10 ** token.decimals); - } - }), - ); - - let usd = 0; - await Promise.all( - indices.map(async (i: number) => { - const token = allTokens.find(v => v.address === TOKENS[i].address)!; - if (!token) return; - usd += balances[i] * token.price; - }), - ); - - return usd; - } -} diff --git a/src/apps/solace/ethereum/solace.xslocker.contract-position-fetcher.ts b/src/apps/solace/ethereum/solace.xslocker.contract-position-fetcher.ts index 1ca56bc96..9781dfca4 100644 --- a/src/apps/solace/ethereum/solace.xslocker.contract-position-fetcher.ts +++ b/src/apps/solace/ethereum/solace.xslocker.contract-position-fetcher.ts @@ -9,7 +9,6 @@ import { ContractPosition } from '~position/position.interface'; import { claimable, supplied } from '~position/position.utils'; import { Network } from '~types/network.interface'; -import { SolaceContractFactory } from '../contracts'; import { SOLACE_DEFINITION } from '../solace.definition'; const appId = SOLACE_DEFINITION.id; @@ -21,10 +20,7 @@ const XSLOCKER_ADDRESS = '0x501ace47c5b0c2099c4464f681c3fa2ecd3146c1'; @Register.ContractPositionFetcher({ appId, groupId, network }) export class EthereumSolaceXslockerContractPositionFetcher implements PositionFetcher { - constructor( - @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, - @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, - ) {} + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} async getPositions() { const baseTokens = await this.appToolkit.getBaseTokenPrices(network); diff --git a/src/apps/solace/ethereum/solace.xsolacev1.token-fetcher.ts b/src/apps/solace/ethereum/solace.xsolacev1.token-fetcher.ts index 609c8c600..e5846e683 100644 --- a/src/apps/solace/ethereum/solace.xsolacev1.token-fetcher.ts +++ b/src/apps/solace/ethereum/solace.xsolacev1.token-fetcher.ts @@ -2,6 +2,7 @@ import { Inject } from '@nestjs/common'; import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; import { Register } from '~app-toolkit/decorators'; +import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present'; import { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present'; import { ContractType } from '~position/contract.interface'; import { WithMetaType } from '~position/display.interface'; @@ -20,7 +21,6 @@ const network = Network.ETHEREUM_MAINNET; const SOLACE_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; const XSOLACE_V1_ADDRESS = '0x501ace5ac3af20f49d53242b6d208f3b91cfc411'; const symbol = 'xSOLACEv1'; -const decimals = 18; const ONE_ETHER = '1000000000000000000'; @Register.TokenPositionFetcher({ appId, groupId, network }) @@ -36,13 +36,17 @@ export class EthereumSolaceXsolacev1TokenFetcher implements PositionFetcher) => t.address === SOLACE_ADDRESS)!; const xsolacev1 = this.solaceContractFactory.xSolacev1({ address: XSOLACE_V1_ADDRESS, network }); - const mcxs = multicall.wrap(xsolacev1); - const [supplyRaw, pricePerShareRaw] = await Promise.all([mcxs.totalSupply(), mcxs.xSolaceToSolace(ONE_ETHER)]); + const [decimals, supplyRaw, pricePerShareRaw] = await Promise.all([ + multicall.wrap(xsolacev1).decimals(), + multicall.wrap(xsolacev1).totalSupply(), + multicall.wrap(xsolacev1).xSolaceToSolace(ONE_ETHER), + ]); const supply = Number(supplyRaw) / 10 ** decimals; const pricePerShare = Number(pricePerShareRaw) / 10 ** decimals; const price = solace.price * pricePerShare; const tokens = [solace]; + const liquidity = price * supply; const token: AppTokenPosition = { type: ContractType.APP_TOKEN, @@ -56,10 +60,13 @@ export class EthereumSolaceXsolacev1TokenFetcher implements PositionFetcher { + // Resolve the staked token and reward token from the contract position object + const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; + const rewardToken = contractPosition.tokens.find(t => t.metaType === MetaType.CLAIMABLE)!; + const teller = this.solaceContractFactory.bondTellerErc20({ address: contractPosition.address, network }); + + const mct = multicall.wrap(teller); + const balance = await mct.balanceOf(address); + const indices = range(0, balance.toNumber()); + const tokenIDs = await Promise.all(indices.map((i: number) => mct.tokenOfOwnerByIndex(address, i))); + const bonds = await Promise.all(tokenIDs.map(id => mct.bonds(id))); + + let supplySum = BN.from(0); + let rewardSum = BN.from(0); + indices.forEach((i: number) => { + supplySum = supplySum.add(bonds[i].principalPaid); + rewardSum = rewardSum.add(bonds[i].payoutAmount.sub(bonds[i].payoutAlreadyClaimed)); + }); + + return [drillBalance(stakedToken, supplySum.toString()), drillBalance(rewardToken, rewardSum.toString())]; + }, + }); + } +} diff --git a/src/apps/solace/helpers/SolacePolicyBalanceHelper.ts b/src/apps/solace/helpers/SolacePolicyBalanceHelper.ts new file mode 100644 index 000000000..e1a21268e --- /dev/null +++ b/src/apps/solace/helpers/SolacePolicyBalanceHelper.ts @@ -0,0 +1,35 @@ +import { Inject, Injectable } from '@nestjs/common'; + +import { drillBalance } from '~app-toolkit'; +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { MetaType } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import { SolaceContractFactory } from '../contracts'; +import { SOLACE_DEFINITION } from '../solace.definition'; + +@Injectable() +export class SolacePolicyBalanceHelper { + constructor( + @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, + @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, + ) {} + + getBalances({ address, network }: { address: string; network: Network }) { + return this.appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ + address, + appId: SOLACE_DEFINITION.id, + groupId: SOLACE_DEFINITION.groups.policies.id, + network, + resolveBalances: async ({ address, contractPosition }) => { + // Resolve the staked token and reward token from the contract position object + const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; + + const product = this.solaceContractFactory.solaceCoverProduct({ address: contractPosition.address, network }); + const bal = await product.accountBalanceOf(address); + + return [drillBalance(stakedToken, bal.toString())]; + }, + }); + } +} diff --git a/src/apps/solace/helpers/SolaceXSBalanceHelper.ts b/src/apps/solace/helpers/SolaceXSBalanceHelper.ts new file mode 100644 index 000000000..9dac3b4c4 --- /dev/null +++ b/src/apps/solace/helpers/SolaceXSBalanceHelper.ts @@ -0,0 +1,59 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ethers } from 'ethers'; +import { range } from 'lodash'; + +import { drillBalance } from '~app-toolkit'; +import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface'; +import { MetaType } from '~position/position.interface'; +import { Network } from '~types/network.interface'; + +import { SolaceContractFactory } from '../contracts'; +import { SOLACE_DEFINITION } from '../solace.definition'; + +const BN = ethers.BigNumber; + +const XSLOCKER_ADDRESS = '0x501ace47c5b0c2099c4464f681c3fa2ecd3146c1'; +const STAKING_REWARDS_ADDRESS = '0x501ace3d42f9c8723b108d4fbe29989060a91411'; + +@Injectable() +export class SolaceXSBalanceHelper { + constructor( + @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, + @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, + ) {} + + getBalances({ address, network }: { address: string; network: Network }) { + return this.appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ + address, + appId: SOLACE_DEFINITION.id, + groupId: SOLACE_DEFINITION.groups.xslocker.id, + network, + resolveBalances: async ({ address, contractPosition, multicall }) => { + // Resolve the staked token and reward token from the contract position object + const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; + const rewardToken = contractPosition.tokens.find(t => t.metaType === MetaType.CLAIMABLE)!; + + const xslocker = this.solaceContractFactory.xsLocker({ address: XSLOCKER_ADDRESS, network }); + const stakingRewards = this.solaceContractFactory.stakingRewards({ address: STAKING_REWARDS_ADDRESS, network }); + + const mcxsl = multicall.wrap(xslocker); + const mcsr = multicall.wrap(stakingRewards); + + const balance = await xslocker.balanceOf(address); + const indices = range(0, balance.toNumber()); + const tokenIDs = await Promise.all(indices.map((i: number) => mcxsl.tokenOfOwnerByIndex(address, i))); + const locks = await Promise.all(tokenIDs.map(id => mcxsl.locks(id))); + const rewards = await Promise.all(tokenIDs.map(id => mcsr.pendingRewardsOfLock(id))); + + let supplySum = BN.from(0); + let rewardSum = BN.from(0); + indices.forEach((i: number) => { + supplySum = supplySum.add(locks[i].amount); + rewardSum = rewardSum.add(rewards[i]); + }); + + return [drillBalance(stakedToken, supplySum.toString()), drillBalance(rewardToken, rewardSum.toString())]; + }, + }); + } +} diff --git a/src/apps/solace/polygon/helpers/getBondBalance.ts b/src/apps/solace/polygon/helpers/getBondBalance.ts deleted file mode 100644 index 538816372..000000000 --- a/src/apps/solace/polygon/helpers/getBondBalance.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ethers } from 'ethers'; -import { range } from 'lodash'; - -import { drillBalance } from '~app-toolkit'; -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { MetaType } from '~position/position.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../../contracts'; -import { SOLACE_DEFINITION } from '../../solace.definition'; - -const BN = ethers.BigNumber; - -export default async function getBondBalance( - address: string, - appToolkit: IAppToolkit, - solaceContractFactory: SolaceContractFactory, -) { - const network = Network.POLYGON_MAINNET; - return appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.bonds.id, - network, - resolveBalances: async ({ address, contractPosition, multicall }) => { - // Resolve the staked token and reward token from the contract position object - const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; - const rewardToken = contractPosition.tokens.find(t => t.metaType === MetaType.CLAIMABLE)!; - - const teller = solaceContractFactory.bondTellerErc20({ address: contractPosition.address, network }); - - const mct = multicall.wrap(teller); - - const balance = await mct.balanceOf(address); - const indices = range(0, balance.toNumber()); - const tokenIDs = await Promise.all(indices.map((i: number) => mct.tokenOfOwnerByIndex(address, i))); - const bonds = await Promise.all(tokenIDs.map(id => mct.bonds(id))); - - let supplySum = BN.from(0); - let rewardSum = BN.from(0); - indices.forEach((i: number) => { - supplySum = supplySum.add(bonds[i].principalPaid); - rewardSum = rewardSum.add(bonds[i].payoutAmount.sub(bonds[i].payoutAlreadyClaimed)); - }); - - return [drillBalance(stakedToken, supplySum.toString()), drillBalance(rewardToken, rewardSum.toString())]; - }, - }); -} diff --git a/src/apps/solace/polygon/helpers/getPolicyBalance.ts b/src/apps/solace/polygon/helpers/getPolicyBalance.ts deleted file mode 100644 index e742b2bf6..000000000 --- a/src/apps/solace/polygon/helpers/getPolicyBalance.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { drillBalance } from '~app-toolkit'; -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { MetaType } from '~position/position.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../../contracts'; -import { SOLACE_DEFINITION } from '../../solace.definition'; - -export default async function getPolicyBalance( - address: string, - appToolkit: IAppToolkit, - solaceContractFactory: SolaceContractFactory, -) { - const network = Network.POLYGON_MAINNET; - return appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.policies.id, - network, - resolveBalances: async ({ address, contractPosition }) => { - // Resolve the staked token and reward token from the contract position object - const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; - - const product = solaceContractFactory.solaceCoverProduct({ address: contractPosition.address, network }); - const bal = await product.accountBalanceOf(address); - - return [drillBalance(stakedToken, bal.toString())]; - }, - }); -} diff --git a/src/apps/solace/polygon/helpers/getXSLockerBalance.ts b/src/apps/solace/polygon/helpers/getXSLockerBalance.ts deleted file mode 100644 index 35333d366..000000000 --- a/src/apps/solace/polygon/helpers/getXSLockerBalance.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ethers } from 'ethers'; -import { range } from 'lodash'; - -import { drillBalance } from '~app-toolkit'; -import { IAppToolkit } from '~app-toolkit/app-toolkit.interface'; -import { MetaType } from '~position/position.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../../contracts'; -import { SOLACE_DEFINITION } from '../../solace.definition'; - -const BN = ethers.BigNumber; - -const XSLOCKER_ADDRESS = '0x501ace47c5b0c2099c4464f681c3fa2ecd3146c1'; -const STAKING_REWARDS_ADDRESS = '0x501ace3d42f9c8723b108d4fbe29989060a91411'; - -export default async function getXSLockerBalance( - address: string, - appToolkit: IAppToolkit, - solaceContractFactory: SolaceContractFactory, -) { - const network = Network.POLYGON_MAINNET; - return appToolkit.helpers.contractPositionBalanceHelper.getContractPositionBalances({ - address, - appId: SOLACE_DEFINITION.id, - groupId: SOLACE_DEFINITION.groups.xslocker.id, - network, - resolveBalances: async ({ address, contractPosition, multicall }) => { - // Resolve the staked token and reward token from the contract position object - const stakedToken = contractPosition.tokens.find(t => t.metaType === MetaType.SUPPLIED)!; - const rewardToken = contractPosition.tokens.find(t => t.metaType === MetaType.CLAIMABLE)!; - - const xslocker = solaceContractFactory.xsLocker({ address: XSLOCKER_ADDRESS, network }); - const stakingRewards = solaceContractFactory.stakingRewards({ address: STAKING_REWARDS_ADDRESS, network }); - - const mcxsl = multicall.wrap(xslocker); - const mcsr = multicall.wrap(stakingRewards); - - const balance = await xslocker.balanceOf(address); - const indices = range(0, balance.toNumber()); - const tokenIDs = await Promise.all(indices.map((i: number) => mcxsl.tokenOfOwnerByIndex(address, i))); - const locks = await Promise.all(tokenIDs.map(id => mcxsl.locks(id))); - const rewards = await Promise.all(tokenIDs.map(id => mcsr.pendingRewardsOfLock(id))); - - let supplySum = BN.from(0); - let rewardSum = BN.from(0); - indices.forEach((i: number) => { - supplySum = supplySum.add(locks[i].amount); - rewardSum = rewardSum.add(rewards[i]); - }); - - return [drillBalance(stakedToken, supplySum.toString()), drillBalance(rewardToken, rewardSum.toString())]; - }, - }); -} diff --git a/src/apps/solace/polygon/solace.balance-fetcher.ts b/src/apps/solace/polygon/solace.balance-fetcher.ts index 0ea05fbf8..669f319a1 100644 --- a/src/apps/solace/polygon/solace.balance-fetcher.ts +++ b/src/apps/solace/polygon/solace.balance-fetcher.ts @@ -1,38 +1,69 @@ import { Inject } from '@nestjs/common'; -import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; import { Register } from '~app-toolkit/decorators'; import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present'; import { BalanceFetcher } from '~balance/balance-fetcher.interface'; import { Network } from '~types/network.interface'; -import { SolaceContractFactory } from '../contracts'; +import { SolaceBondBalanceHelper } from '../helpers/SolaceBondBalanceHelper'; +import { SolacePolicyBalanceHelper } from '../helpers/SolacePolicyBalanceHelper'; +import { SolaceXSBalanceHelper } from '../helpers/SolaceXSBalanceHelper'; import { SOLACE_DEFINITION } from '../solace.definition'; -import getBondBalance from './helpers/getBondBalance'; -import getPolicyBalance from './helpers/getPolicyBalance'; -import getXSLockerBalance from './helpers/getXSLockerBalance'; - const network = Network.POLYGON_MAINNET; @Register.BalanceFetcher(SOLACE_DEFINITION.id, network) export class PolygonSolaceBalanceFetcher implements BalanceFetcher { constructor( - @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, - @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, + @Inject(SolaceBondBalanceHelper) + private readonly solaceBondBalanceHelper: SolaceBondBalanceHelper, + @Inject(SolacePolicyBalanceHelper) + private readonly solacePolicyBalanceHelper: SolacePolicyBalanceHelper, + @Inject(SolaceXSBalanceHelper) + private readonly solaceXSBalanceHelper: SolaceXSBalanceHelper, ) {} + async getXSLockerBalance(address: string) { + return this.solaceXSBalanceHelper.getBalances({ + address, + network, + }); + } + + async getBondBalance(address: string) { + return this.solaceBondBalanceHelper.getBalances({ + address, + network, + }); + } + + async getPolicyBalance(address: string) { + return this.solacePolicyBalanceHelper.getBalances({ + address, + network, + }); + } + async getBalances(address: string) { const [xslockerBal, bondBal, policyBal] = await Promise.all([ - getXSLockerBalance(address, this.appToolkit, this.solaceContractFactory), - getBondBalance(address, this.appToolkit, this.solaceContractFactory), - getPolicyBalance(address, this.appToolkit, this.solaceContractFactory), + this.getXSLockerBalance(address), + this.getBondBalance(address), + this.getPolicyBalance(address), ]); return presentBalanceFetcherResponse([ - { label: 'xsLocker', assets: xslockerBal }, - { label: 'Bonds', assets: bondBal }, - { label: 'Policies', assets: policyBal }, + { + label: 'xsLocker', + assets: xslockerBal, + }, + { + label: 'Bonds', + assets: bondBal, + }, + { + label: 'Policies', + assets: policyBal, + }, ]); } } diff --git a/src/apps/solace/polygon/solace.bonds.contract-position-fetcher.ts b/src/apps/solace/polygon/solace.bonds.contract-position-fetcher.ts index 373e1fe44..428ac3043 100644 --- a/src/apps/solace/polygon/solace.bonds.contract-position-fetcher.ts +++ b/src/apps/solace/polygon/solace.bonds.contract-position-fetcher.ts @@ -3,6 +3,7 @@ import { compact } from 'lodash'; import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; import { Register } from '~app-toolkit/decorators'; +import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present'; import { getImagesFromToken, getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present'; import { ContractType } from '~position/contract.interface'; import { PositionFetcher } from '~position/position-fetcher.interface'; @@ -19,45 +20,17 @@ const network = Network.POLYGON_MAINNET; export const SOLACE_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; -const BOND_TELLERS = [ - { - name: 'Solace DAI Bond', - address: '0x501ace677634fd09a876e88126076933b686967a', - deposit: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - }, - { - name: 'Solace ETH Bond', - address: '0x501ace367f1865dea154236d5a8016b80a49e8a9', - deposit: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', - }, - { - name: 'Solace USDC Bond', - address: '0x501ace7e977e06a3cb55f9c28d5654c9d74d5ca9', - deposit: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - }, - { - name: 'Solace WBTC Bond', - address: '0x501acef0d0c73bd103337e6e9fd49d58c426dc27', - deposit: '0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6', - }, - { - name: 'Solace USDT Bond', - address: '0x501ace5ceec693df03198755ee80d4ce0b5c55fe', - deposit: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', - }, - { - name: 'Solace FRAX Bond', - address: '0x501acef4f8397413c33b13cb39670ad2f17bfe62', - deposit: '0x45c32fa6df82ead1e2ef74d17b76547eddfaff89', - }, - { - name: 'Solace MATIC Bond', - address: '0x501ace133452d4df83ca68c684454fcba608b9dd', - deposit: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - }, +const BOND_TELLER_ADDRESSES = [ + '0x501ace677634fd09a876e88126076933b686967a', // DAI Bond + '0x501ace367f1865dea154236d5a8016b80a49e8a9', // ETH Bond + '0x501ace7e977e06a3cb55f9c28d5654c9d74d5ca9', // USDC Bond + '0x501acef0d0c73bd103337e6e9fd49d58c426dc27', // WBTC Bond + '0x501ace5ceec693df03198755ee80d4ce0b5c55fe', // USDT Bond + '0x501acef4f8397413c33b13cb39670ad2f17bfe62', // FRAX Bond + '0x501ace133452d4df83ca68c684454fcba608b9dd', // MATIC Bond ]; -@Register.ContractPositionFetcher({ appId, groupId, network }) +@Register.ContractPositionFetcher({ appId, groupId, network, options: { includeInTvl: true } }) export class PolygonSolaceBondsContractPositionFetcher implements PositionFetcher { constructor( @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, @@ -65,26 +38,44 @@ export class PolygonSolaceBondsContractPositionFetcher implements PositionFetche ) {} async getPositions() { + const multicall = this.appToolkit.getMulticall(network); const baseTokens = await this.appToolkit.getBaseTokenPrices(network); - const solace = baseTokens.find(t => t.address === SOLACE_ADDRESS)!; + const solaceToken = baseTokens.find(t => t.address === SOLACE_ADDRESS)!; const positions = await Promise.all( - BOND_TELLERS.map(async teller => { - const depositToken = baseTokens.find(v => v.address === teller.deposit)!; - if (!depositToken || !solace) return null; - const tokens = [supplied(depositToken), claimable(solace)]; + BOND_TELLER_ADDRESSES.map(async bondTellerAddress => { + const bondTellerContract = this.solaceContractFactory.bondTellerErc20({ address: bondTellerAddress, network }); + + const [underlyingAddressRaw, underWritingPoolAddress] = await Promise.all([ + multicall.wrap(bondTellerContract).principal(), + multicall.wrap(bondTellerContract).underwritingPool(), + ]); + + const underlyingAddress = underlyingAddressRaw.toLowerCase(); + + const depositToken = baseTokens.find(v => v.address === underlyingAddress); + if (!depositToken || !solaceToken) return null; + const tokens = [supplied(depositToken), claimable(solaceToken)]; + + const baseTokenContract = this.solaceContractFactory.erc20({ address: underlyingAddress, network }); + const balanceOfRaw = await multicall.wrap(baseTokenContract).balanceOf(underWritingPoolAddress); + const balanceOf = Number(balanceOfRaw) / 10 ** depositToken.decimals; + const liquidity = balanceOf * depositToken.price; const position: ContractPosition = { type: ContractType.POSITION, appId, groupId, - address: teller.address, + address: bondTellerAddress, network, tokens, - dataProps: {}, + dataProps: { + liquidity, + }, displayProps: { - label: `${getLabelFromToken(depositToken)} Bond`, + label: `${getLabelFromToken(depositToken)}`, images: getImagesFromToken(depositToken), + statsItems: [{ label: 'Liquidity', value: buildDollarDisplayItem(liquidity) }], }, }; diff --git a/src/apps/solace/polygon/solace.policies.contract-position-fetcher.ts b/src/apps/solace/polygon/solace.policies.contract-position-fetcher.ts index b5ebbfdfe..2c97ffcb9 100644 --- a/src/apps/solace/polygon/solace.policies.contract-position-fetcher.ts +++ b/src/apps/solace/polygon/solace.policies.contract-position-fetcher.ts @@ -9,7 +9,6 @@ import { ContractPosition } from '~position/position.interface'; import { supplied } from '~position/position.utils'; import { Network } from '~types/network.interface'; -import { SolaceContractFactory } from '../contracts'; import { SOLACE_DEFINITION } from '../solace.definition'; const appId = SOLACE_DEFINITION.id; @@ -22,10 +21,7 @@ const SOLACE_ADDRESS = '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40'; @Register.ContractPositionFetcher({ appId, groupId, network }) export class PolygonSolacePoliciesContractPositionFetcher implements PositionFetcher { - constructor( - @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, - @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, - ) {} + constructor(@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit) {} async getPositions() { const baseTokens = await this.appToolkit.getBaseTokenPrices(network); diff --git a/src/apps/solace/polygon/solace.tvl-fetcher.ts b/src/apps/solace/polygon/solace.tvl-fetcher.ts deleted file mode 100644 index 7b8201908..000000000 --- a/src/apps/solace/polygon/solace.tvl-fetcher.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { range } from 'lodash'; - -import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface'; -import { Register } from '~app-toolkit/decorators'; -import { TvlFetcher } from '~stats/tvl/tvl-fetcher.interface'; -import { Network } from '~types/network.interface'; - -import { SolaceContractFactory } from '../contracts'; -import { SOLACE_DEFINITION } from '../solace.definition'; - -const appId = SOLACE_DEFINITION.id; -const network = Network.POLYGON_MAINNET; - -// TODO: there is more to TVL than amount in the UWP -// since its the bulk we call it sufficient for now -const UWP_ADDRESS = '0xd1108a800363c262774b990e9df75a4287d5c075'; -const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; -const TOKENS = [ - { - symbol: 'SOLACE', - address: '0x501ace9c35e60f03a2af4d484f49f9b1efde9f40', - decimals: 18, - }, - { - symbol: 'FRAX', - address: '0x45c32fa6df82ead1e2ef74d17b76547eddfaff89', - decimals: 18, - }, - { - symbol: 'USDC', - address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - decimals: 6, - }, - { - symbol: 'USDT', - address: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', - decimals: 6, - }, - { - symbol: 'DAI', - address: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - decimals: 18, - }, - { - symbol: 'WETH', - address: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', - decimals: 18, - }, - { - symbol: 'WBTC', - address: '0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6', - decimals: 8, - }, - { - symbol: 'MATIC', - address: '0x0000000000000000000000000000000000000000', - decimals: 18, - }, - { - symbol: 'WMATIC', - address: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - decimals: 18, - }, - // TODO: LP -]; - -@Register.TvlFetcher({ appId, network }) -export class PolygonSolaceTvlFetcher implements TvlFetcher { - constructor( - @Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit, - @Inject(SolaceContractFactory) private readonly solaceContractFactory: SolaceContractFactory, - ) {} - - async getTvl() { - const baseTokens = await this.appToolkit.getBaseTokenPrices(network); - const multicall = this.appToolkit.getMulticall(network); - const indices = range(0, TOKENS.length); - - const balances = await Promise.all( - TOKENS.map(async token => { - if (token.address === ZERO_ADDRESS) { - const mcmc = multicall.wrap(multicall.contract); - return mcmc.getEthBalance(UWP_ADDRESS).then(v => Number(v) / 10 ** token.decimals); - } else { - const tokenContract = this.solaceContractFactory.erc20({ address: token.address, network }); - const mct = multicall.wrap(tokenContract); - return mct.balanceOf(UWP_ADDRESS).then(v => Number(v) / 10 ** token.decimals); - } - }), - ); - - let usd = 0; - await Promise.all( - indices.map(async (i: number) => { - const token = baseTokens.find(v => v.address === TOKENS[i].address)!; - if (!token) return; - usd += balances[i] * token.price; - }), - ); - - return usd; - } -} diff --git a/src/apps/solace/solace.definition.ts b/src/apps/solace/solace.definition.ts index 8ae7e7e75..c0104ed1d 100644 --- a/src/apps/solace/solace.definition.ts +++ b/src/apps/solace/solace.definition.ts @@ -39,6 +39,7 @@ export const SOLACE_DEFINITION = appDefinition({ id: 'policies', type: GroupType.POSITION, label: 'Policies', + isHiddenFromExplore: true, }, }, @@ -46,10 +47,8 @@ export const SOLACE_DEFINITION = appDefinition({ keywords: [], links: { - learn: '', github: 'https://github.com/solace-fi', twitter: 'https://twitter.com/SolaceFi/', - telegram: '', discord: 'https://discord.com/invite/7v8qsyepfu/', medium: 'https://medium.com/solace-fi/', }, diff --git a/src/apps/solace/solace.module.ts b/src/apps/solace/solace.module.ts index 1268c4fc6..748a33cbd 100644 --- a/src/apps/solace/solace.module.ts +++ b/src/apps/solace/solace.module.ts @@ -6,33 +6,38 @@ import { EthereumSolaceBalanceFetcher } from './ethereum/solace.balance-fetcher' import { EthereumSolaceBondsContractPositionFetcher } from './ethereum/solace.bonds.contract-position-fetcher'; import { EthereumSolacePoliciesContractPositionFetcher } from './ethereum/solace.policies.contract-position-fetcher'; import { EthereumSolaceScpTokenFetcher } from './ethereum/solace.scp.token-fetcher'; -import { EthereumSolaceTvlFetcher } from './ethereum/solace.tvl-fetcher'; import { EthereumSolaceXslockerContractPositionFetcher } from './ethereum/solace.xslocker.contract-position-fetcher'; import { EthereumSolaceXsolacev1TokenFetcher } from './ethereum/solace.xsolacev1.token-fetcher'; +import { SolaceBondBalanceHelper } from './helpers/SolaceBondBalanceHelper'; +import { SolacePolicyBalanceHelper } from './helpers/SolacePolicyBalanceHelper'; +import { SolaceXSBalanceHelper } from './helpers/SolaceXSBalanceHelper'; import { PolygonSolaceBalanceFetcher } from './polygon/solace.balance-fetcher'; import { PolygonSolaceBondsContractPositionFetcher } from './polygon/solace.bonds.contract-position-fetcher'; import { PolygonSolacePoliciesContractPositionFetcher } from './polygon/solace.policies.contract-position-fetcher'; -import { PolygonSolaceTvlFetcher } from './polygon/solace.tvl-fetcher'; import { PolygonSolaceXslockerContractPositionFetcher } from './polygon/solace.xslocker.contract-position-fetcher'; import SOLACE_DEFINITION, { SolaceAppDefinition } from './solace.definition'; @Register.AppModule({ appId: SOLACE_DEFINITION.id, providers: [ + SolaceAppDefinition, + SolaceContractFactory, + // Helpers + SolaceBondBalanceHelper, + SolacePolicyBalanceHelper, + SolaceXSBalanceHelper, + // Ethereum EthereumSolaceBalanceFetcher, EthereumSolaceBondsContractPositionFetcher, EthereumSolacePoliciesContractPositionFetcher, EthereumSolaceScpTokenFetcher, - EthereumSolaceTvlFetcher, EthereumSolaceXslockerContractPositionFetcher, EthereumSolaceXsolacev1TokenFetcher, + // Polygon PolygonSolaceBalanceFetcher, PolygonSolaceBondsContractPositionFetcher, PolygonSolacePoliciesContractPositionFetcher, - PolygonSolaceTvlFetcher, PolygonSolaceXslockerContractPositionFetcher, - SolaceAppDefinition, - SolaceContractFactory, ], }) export class SolaceAppModule extends AbstractApp() {}