diff --git a/src/hooks/useREG.ts b/src/hooks/useREG.ts index 0a83161..78ae35d 100644 --- a/src/hooks/useREG.ts +++ b/src/hooks/useREG.ts @@ -12,7 +12,10 @@ import { selectUserAddressList, selectUserIncludesEth, } from 'src/store/features/settings/settingsSelector' -import { REGRealtoken } from 'src/store/features/wallets/walletsSelector' +import { + REGRealtoken, + updateBalanceValues, +} from 'src/store/features/wallets/walletsSelector' import { APIRealTokenProductType } from 'src/types/APIRealToken' import { Currency } from 'src/types/Currencies' import { ERC20ABI } from 'src/utils/blockchain/abi/ERC20ABI' @@ -64,12 +67,11 @@ const getREG = async ( ERC20ABI, GnosisRpcProvider, ) - const availableBalance = await getAddressesBalances( + const balances = await getAddressesBalances( REG_ContractAddress, addressList, providers, ) - const regVaultAbiGetUserGlobalStateOnly = getRegVaultAbiGetUserGlobalStateOnly() @@ -77,7 +79,7 @@ const getREG = async ( [ // First provider [ - // First vault + // First vault: Gnosis [ REG_Vault_Gnosis_ContractAddress, // Contract address regVaultAbiGetUserGlobalStateOnly, // Contract ABI @@ -102,7 +104,10 @@ const getREG = async ( providers, ) - const totalAmount = availableBalance + lockedBalance + // Add locked balance to balance.balance.gnosis.amount + balances.balance.gnosis.amount += lockedBalance + // Add locked balance to total amount + const totalAmount = balances?.totalAmount + lockedBalance const contractRegTotalSupply = await RegContract_Gnosis.totalSupply() const totalTokens = Number(contractRegTotalSupply) / 10 ** REGtokenDecimals const amount = totalAmount / 10 ** REGtokenDecimals @@ -143,6 +148,8 @@ const getREG = async ( : DEFAULT_REG_PRICE / userRate const value = tokenPrice * amount const totalInvestment = totalTokens * tokenPrice + // Update all balance values with token price + updateBalanceValues(balances.balance, tokenPrice) return { id: `${REG_asset_ID}`, @@ -159,6 +166,7 @@ const getREG = async ( value, totalInvestment, unitPriceCost: tokenPrice, + balance: balances.balance, } } diff --git a/src/hooks/useREGVotingPower.ts b/src/hooks/useREGVotingPower.ts index 5af3af1..ef8580f 100644 --- a/src/hooks/useREGVotingPower.ts +++ b/src/hooks/useREGVotingPower.ts @@ -26,21 +26,20 @@ const getRegVotingPower = async ( ERC20ABI, GnosisRpcProvider, ) - const totalAmount = await getAddressesBalances( + const balances = await getAddressesBalances( RegVotingPower_Gnosis_ContractAddress, addressList, providers, ) - const contractRegVotePowerTotalSupply = await RegVotingPowerContract.totalSupply() const totalTokens = Number(contractRegVotePowerTotalSupply) / 10 ** REGVotingPowertokenDecimals - const amount = totalAmount / 10 ** REGVotingPowertokenDecimals + const amount = balances?.totalAmount / 10 ** REGVotingPowertokenDecimals const tokenPrice = DEFAULT_REGVotingPower_PRICE const value = tokenPrice * amount const totalInvestment = tokenPrice * totalTokens - + // Reg voting power does not have (yet) a unit price cost return { id: `${REGVotingPower_asset_ID}`, fullName: 'REG Voting Power Registry', @@ -56,6 +55,7 @@ const getRegVotingPower = async ( value, totalInvestment, unitPriceCost: tokenPrice, + balance: balances.balance, } } diff --git a/src/hooks/useRWA.ts b/src/hooks/useRWA.ts index 88cf860..839be89 100644 --- a/src/hooks/useRWA.ts +++ b/src/hooks/useRWA.ts @@ -11,7 +11,10 @@ import { import { selectUserAddressList, // selectUserIncludesEth, } from 'src/store/features/settings/settingsSelector' -import { RWARealtoken } from 'src/store/features/wallets/walletsSelector' +import { + RWARealtoken, + updateBalanceValues, +} from 'src/store/features/wallets/walletsSelector' import { APIRealTokenProductType } from 'src/types/APIRealToken' import { Currency } from 'src/types/Currencies' import { ERC20ABI } from 'src/utils/blockchain/abi/ERC20ABI' @@ -49,14 +52,14 @@ const getRWA = async ( ERC20ABI, GnosisRpcProvider, ) - const totalAmount = await getAddressesBalances( + const balances = await getAddressesBalances( RWA_ContractAddress, addressList, providers, ) const RwaContractTotalSupply = await contractRwa_Gnosis.totalSupply() const totalTokens = Number(RwaContractTotalSupply) / 10 ** RWAtokenDecimals - const amount = totalAmount / 10 ** RWAtokenDecimals + const amount = balances?.totalAmount / 10 ** RWAtokenDecimals // RWA token prices in USDC and WXDAI from LPs const rwaPriceUsdc = await getUniV2AssetPrice( @@ -92,6 +95,8 @@ const getRWA = async ( const tokenPrice = (assetAveragePriceUSD ?? DEFAULT_RWA_PRICE) / userRate const value = tokenPrice * amount const totalInvestment = totalTokens * tokenPrice + // Update all balance values with token price + updateBalanceValues(balances.balance, tokenPrice) return { id: `${RWA_asset_ID}`, @@ -113,6 +118,7 @@ const getRWA = async ( timezone_type: 3, timezone: 'UTC', }, + balance: balances.balance, } } diff --git a/src/repositories/RpcProvider.ts b/src/repositories/RpcProvider.ts index ded4082..6ce9193 100644 --- a/src/repositories/RpcProvider.ts +++ b/src/repositories/RpcProvider.ts @@ -239,3 +239,16 @@ export async function getTransactionReceipt( return receipt } + +/** + * Get chain ID + * @param provider (Ethers) RPC provider + * @returns Chain ID as number | undefined + */ +export const getChainId = ( + provider: JsonRpcProvider | undefined, +): number | undefined => { + return provider?._network?.chainId + ? Number(provider?._network?.chainId) + : undefined +} diff --git a/src/store/features/wallets/walletsSelector.ts b/src/store/features/wallets/walletsSelector.ts index a35ad13..7d2c319 100644 --- a/src/store/features/wallets/walletsSelector.ts +++ b/src/store/features/wallets/walletsSelector.ts @@ -5,7 +5,6 @@ import _max from 'lodash/max' import _sumBy from 'lodash/sumBy' import moment from 'moment' -import { AssetProductType } from 'src/components/assetsView' import { WalletBalances, WalletType } from 'src/repositories' import { UserRealTokenTransfer } from 'src/repositories/transfers/transfers.type' import { RootState } from 'src/store/store' @@ -20,9 +19,23 @@ import { } from 'src/types/RentCalculation' import { computeUCP } from 'src/utils/transfer/computeUCP' +import { + CHAIN_ID_ETHEREUM, + CHAIN_ID_GNOSIS_XDAI, + CHAIN_NAME_ETHEREUM, + CHAIN_NAME_GNOSIS_XDAI, +} from '../../../utils/blockchain/consts/otherTokens' import { selectRealtokens } from '../realtokens/realtokensSelector' import { selectUserRentCalculation } from '../settings/settingsSelector' +export type BalanceByWalletType = Record< + WalletType, + { + amount: number + value: number + } +> + export interface UserRealtoken extends RealToken { id: string isWhitelisted: boolean @@ -33,13 +46,7 @@ export interface UserRealtoken extends RealToken { priceCost?: number unrealizedCapitalGain?: number lastChanges: string - balance: Record< - WalletType, - { - amount: number - value: number - } - > + balance: BalanceByWalletType } export interface OtherRealtoken { @@ -55,6 +62,23 @@ export interface OtherRealtoken { imageLink: string[] isRmmAvailable: boolean unitPriceCost: number + balance: BalanceByWalletType +} + +/** + * Updates all balance values with token price + * @param balance + * @param tokenPrice + */ +export const updateBalanceValues = ( + balance: BalanceByWalletType, + tokenPrice: number, +) => { + // Loop on each record + Object.keys(balance).forEach((key) => { + balance[key as WalletType].value = + balance[key as WalletType].amount * tokenPrice + }) } // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -248,3 +272,12 @@ export const selectRmmDetails = createSelector( return _mapValues(rmmDetails, (value) => value / rates.XDAI) }, ) + +export const getWalletChainName = (chainId: number) => { + switch (chainId) { + case CHAIN_ID_ETHEREUM: + return CHAIN_NAME_ETHEREUM + case CHAIN_ID_GNOSIS_XDAI: + return CHAIN_NAME_GNOSIS_XDAI + } +} diff --git a/src/utils/blockchain/consts/otherTokens.ts b/src/utils/blockchain/consts/otherTokens.ts index 992a99e..00ce91e 100644 --- a/src/utils/blockchain/consts/otherTokens.ts +++ b/src/utils/blockchain/consts/otherTokens.ts @@ -33,6 +33,12 @@ const WXDAItokenDecimals = 18 const HoneySwapFactory_Address = '0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7' +const CHAIN_ID_GNOSIS_XDAI = 100 +const CHAIN_ID_ETHEREUM = 1 + +const CHAIN_NAME_GNOSIS_XDAI = 'gnosis' +const CHAIN_NAME_ETHEREUM = 'ethereum' + export { RWA_ContractAddress, REG_ContractAddress, @@ -54,4 +60,8 @@ export { RWA_asset_ID, REG_asset_ID, REGVotingPower_asset_ID, + CHAIN_ID_GNOSIS_XDAI, + CHAIN_ID_ETHEREUM, + CHAIN_NAME_GNOSIS_XDAI, + CHAIN_NAME_ETHEREUM, } diff --git a/src/utils/blockchain/erc20Infos.ts b/src/utils/blockchain/erc20Infos.ts index 4072610..5590306 100644 --- a/src/utils/blockchain/erc20Infos.ts +++ b/src/utils/blockchain/erc20Infos.ts @@ -1,28 +1,56 @@ import { Contract, JsonRpcProvider } from 'ethers' +import { getChainId } from 'src/repositories/RpcProvider' +import { BalanceByWalletType } from 'src/store/features/wallets/walletsSelector' +import { getWalletChainName } from 'src/store/features/wallets/walletsSelector' import { getErc20AbiBalanceOfOnly } from 'src/utils/blockchain/ERC20' import { batchCallOneContractOneFunctionMultipleParams } from './contract' +export interface Balances { + totalAmount: number + balance: BalanceByWalletType +} + const getAddressesBalances = async ( contractAddress: string, addressList: string[], providers: JsonRpcProvider[], consoleWarnOnError = false, -) => { - let totalAmount = 0 +): Promise => { + const balances: Balances = { + totalAmount: 0, + balance: { + gnosis: { + amount: 0, + value: 0, + }, + ethereum: { + amount: 0, + value: 0, + }, + rmm: { + amount: 0, + value: 0, + }, + levinSwap: { + amount: 0, + value: 0, + }, + }, + } try { if (!contractAddress) { consoleWarnOnError && console.error('Invalid contract address') - return totalAmount + return balances } if (!addressList?.length) { consoleWarnOnError && console.error('Invalid address list') - return totalAmount + return balances } if (!providers?.length) { consoleWarnOnError && console.error('Invalid providers') - return totalAmount + return balances } const erc20AbiBalanceOfOnly = getErc20AbiBalanceOfOnly() if (!erc20AbiBalanceOfOnly) { @@ -41,22 +69,47 @@ const getAddressesBalances = async ( ) return balances }) - const balancesArray = await Promise.all(balancesPromises.flat()) - const balances = balancesArray.flat() - // Sum all valid balances - balances.forEach((balance: object | null | undefined) => { - try { - if (balance) { - totalAmount += Number(balance) - } - } catch (error) {} + // warn but don't stop + balancesArray?.length !== providers.length && + consoleWarnOnError && + console.warn( + `Invalid balances array length (${balancesArray?.length}) (inconsistent with providers length (${providers.length}))`, + ) + providers.forEach((provider: JsonRpcProvider, providerIdx) => { + const chainId = getChainId(provider) + const wt = chainId ? getWalletChainName(chainId) : null + if (wt) { + balances.balance[wt].value = 0 + // warn but don't stop + balancesArray[providerIdx]?.length !== addressList.length && + consoleWarnOnError && + console.warn( + 'Invalid balances array (inconsistent addressList length)', + ) + ;(balancesArray[providerIdx] as unknown as bigint[])?.forEach( + (addressBalanceBI: bigint, addressIdx) => { + try { + const addressBalance = Number(addressBalanceBI) + balances.balance[wt].amount += addressBalance + } catch (error) { + // warn but don't stop + consoleWarnOnError && + console.warn( + `Invalid balance conversion for address ${addressList[addressIdx]} on chain ${chainId}`, + error, + ) + } + }, + ) + balances.totalAmount += balances.balance[wt].amount + } }) - return totalAmount + return balances } catch (error) { console.warn('Failed to get balances', error) } - return totalAmount + return balances } export { getAddressesBalances }