diff --git a/src/data/queries/chains.ts b/src/data/queries/chains.ts index c536bbcaf..40f45f0cc 100644 --- a/src/data/queries/chains.ts +++ b/src/data/queries/chains.ts @@ -52,6 +52,17 @@ export function getChainNamefromID( ) } +export function getChainIdFromAddress( + address: string, + chains: Record +) { + return ( + Object.values(chains) + .find(({ prefix }) => address.includes(prefix)) + ?.chainID.toLowerCase() ?? "" + ) +} + export function useIBCChannels() { const networks = useNetwork() diff --git a/src/data/queries/staking.ts b/src/data/queries/staking.ts index c0bda7dfa..6b162bb31 100644 --- a/src/data/queries/staking.ts +++ b/src/data/queries/staking.ts @@ -21,6 +21,8 @@ import { useMemoizedPrices } from "data/queries/coingecko" import { useNativeDenoms } from "data/token" import shuffle from "utils/shuffle" import { getIsBonded } from "pages/stake/ValidatorsList" +import { getChainIdFromAddress } from "./chains" +import { useNetwork } from "data/wallet" export const useInterchainValidators = () => { const addresses = useInterchainAddresses() || {} @@ -361,6 +363,132 @@ export const useCalcInterchainDelegationsTotal = ( return { currencyTotal, graphData: { all: allData, ...tableDataByChain } } } +export const useCalcDelegationsByValidator = ( + delegationsQueryResults: UseQueryResult<{ + delegation: Delegation[] + chainID: string + }>[], + interchainValidators: UseQueryResult[] +) => { + const { data: prices } = useMemoizedPrices() + const readNativeDenom = useNativeDenoms() + const networks = useNetwork() + + if (!delegationsQueryResults.length) + return { currencyTotal: 0, tableData: {} } + + const delegationsPriceByDemon = {} as any + const delegationsAmountsByDemon = {} as any + const validatorByChain = {} as any + const allValidatorByChain = {} as any + let currencyTotal = 0 + + delegationsQueryResults.forEach((result) => { + if (result.status === "success") { + currencyTotal += result.data?.delegation?.length + ? BigNumber.sum( + ...result.data.delegation.map(({ balance, validator_address }) => { + const amount = BigNumber.sum( + delegationsAmountsByDemon[balance.denom] || 0, + balance.amount.toNumber() + ).toNumber() + + const { token, decimals } = readNativeDenom(balance.denom) + const currecyPrice: any = + (amount * (prices?.[token]?.price || 0)) / 10 ** decimals + + delegationsPriceByDemon[balance.denom] = currecyPrice + delegationsAmountsByDemon[balance.denom] = amount + + if (!validatorByChain[result.data.chainID]) { + validatorByChain[result.data.chainID] = {} + validatorByChain[result.data.chainID][validator_address] = { + value: 0, + amount: 0, + } + } + + const delegatorCurrecyPrice: any = + (balance.amount.toNumber() * (prices?.[token]?.price || 0)) / + 10 ** decimals + + validatorByChain[result.data.chainID][validator_address] = { + value: delegatorCurrecyPrice, + amount: balance.amount.toNumber(), + } + + return currecyPrice + }) + ).toNumber() + : 0 + } + }) + + interchainValidators.map((response) => { + if (response.status === "success") { + const addressChainId = + getChainIdFromAddress(response.data[0]?.operator_address, networks) || + "" + allValidatorByChain[addressChainId] = [...response.data] + } + }) + + const tableDataByChain = {} as any + + Object.keys(validatorByChain).forEach((chainName) => { + const delegationsDataComplete = Object.keys( + validatorByChain[chainName] + ).map((validator) => { + if (!allValidatorByChain[chainName]) { + return {} + } + const { description } = getFindValidator(allValidatorByChain[chainName])( + validator + ) + return { + address: validator, + moniker: description.moniker, + identity: description.identity, + value: validatorByChain[chainName][validator].value, + amount: readAmount(validatorByChain[chainName][validator].amount, {}), + } + }) + + const sortedValis = delegationsDataComplete.sort( + (a, b) => b.value - a.value + ) + if (sortedValis.length <= 4) { + tableDataByChain[chainName] = sortedValis + } else { + const top4 = sortedValis.slice(0, 4) + const other = { + address: "Other", + moniker: "Other", + identity: "Other", + value: sortedValis.slice(4).reduce((acc, vali) => acc + vali.value, 0), + amount: sortedValis + .slice(4) + .reduce((acc, vali) => acc + Number(vali.amount), 0) + .toString(), + } + + tableDataByChain[chainName] = [...top4, other] + } + }) + + const allData = Object.keys(delegationsPriceByDemon).map((demonName) => { + const { symbol, icon } = readNativeDenom(demonName) + return { + name: symbol, + value: delegationsPriceByDemon[demonName], + amount: readAmount(delegationsAmountsByDemon[demonName], {}), + icon, + } + }) + + return { currencyTotal, graphData: { all: allData, ...tableDataByChain } } +} + /* Quick stake helpers */ export const getPriorityVals = (validators: Validator[]) => { const MAX_COMMISSION = 0.05 diff --git a/src/pages/stake/StakedDonut.module.scss b/src/pages/stake/StakedDonut.module.scss index 1206829ec..a97d436c0 100644 --- a/src/pages/stake/StakedDonut.module.scss +++ b/src/pages/stake/StakedDonut.module.scss @@ -36,10 +36,21 @@ .rechartsWrapper { outline: none; } + @media (min-width: 710px) and (max-width: 991px) { + max-width: 600px; + } + + @media (min-width: 1150px) { + max-width: 600px; + } + + @media (min-width: 1340px) { + max-width: 600px; + } } .legend { - margin-right: 50px; + margin-right: 15px; display: flex; flex-direction: column; gap: 16px; @@ -57,7 +68,7 @@ .circle { height: 15px; - width: 15px; + min-width: 15px; border-radius: 50%; } @@ -65,6 +76,9 @@ text-transform: uppercase; font-weight: 500; color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .percent { diff --git a/src/pages/stake/StakedDonut.tsx b/src/pages/stake/StakedDonut.tsx index c03e4fd0b..0927317c1 100644 --- a/src/pages/stake/StakedDonut.tsx +++ b/src/pages/stake/StakedDonut.tsx @@ -14,45 +14,68 @@ import { } from "recharts" import { useInterchainDelegations, - useCalcInterchainDelegationsTotal, + useInterchainValidators, + useCalcDelegationsByValidator, } from "data/queries/staking" import PaymentsOutlinedIcon from "@mui/icons-material/PaymentsOutlined" import { useThemeState } from "data/settings/Theme" +import ProfileIcon from "./components/ProfileIcon" const StakedDonut = () => { const { t } = useTranslation() const [current] = useThemeState() const interchainDelegations = useInterchainDelegations() - const state = combineState(...interchainDelegations) + const interchainValidators = useInterchainValidators() + const state = combineState(...interchainDelegations, ...interchainValidators) - const { graphData } = useCalcInterchainDelegationsTotal(interchainDelegations) + const { graphData } = useCalcDelegationsByValidator( + interchainDelegations, + interchainValidators + ) - const defaultColors = ["#4672ED", "#7893F5", "#FF7940", "#FF9F40", "#F4BE37"] + const defaultColors = ["#7893F5", "#7c1ae5", "#FF7940", "#FF9F40", "#acacac"] const COLORS = current?.donutColors || defaultColors const RenderLegend = (props: any) => { - const { payload } = props + const { payload, chainSelected } = props return ( ) } @@ -62,7 +85,7 @@ const StakedDonut = () => { return (
-
{payload[0]?.name}
+
{payload[0]?.payload.name || payload[0]?.payload.moniker}

Balance:

{payload[0]?.payload.amount}

@@ -93,10 +116,12 @@ const StakedDonut = () => { layout="vertical" verticalAlign="middle" align="right" - content={} + content={} className="legend" /> - } /> + } + /> { } return ( - + {render()} ) diff --git a/src/styles/_customize.scss b/src/styles/_customize.scss index ee952a7d9..6aa1915b2 100644 --- a/src/styles/_customize.scss +++ b/src/styles/_customize.scss @@ -59,3 +59,7 @@ .recharts-tooltip-wrapper:focus-visible { outline: none; } + +.recharts-legend-wrapper { + max-width: 45%; +} diff --git a/src/txs/stake/StakeTx.tsx b/src/txs/stake/StakeTx.tsx index 57afc1c35..eeb92a897 100644 --- a/src/txs/stake/StakeTx.tsx +++ b/src/txs/stake/StakeTx.tsx @@ -58,7 +58,7 @@ const StakeTx = () => { } return ( - +