Skip to content

Commit

Permalink
Stake graph update (#190)
Browse files Browse the repository at this point in the history
* changed stake page graph data to display amounts and values from delegations on chain filter

* added back button to some staking pages
  • Loading branch information
JoshuaBrigati authored Jan 12, 2023
1 parent 36be7bd commit 1b4a328
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 29 deletions.
11 changes: 11 additions & 0 deletions src/data/queries/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ export function getChainNamefromID(
)
}

export function getChainIdFromAddress(
address: string,
chains: Record<string, InterchainNetwork>
) {
return (
Object.values(chains)
.find(({ prefix }) => address.includes(prefix))
?.chainID.toLowerCase() ?? ""
)
}

export function useIBCChannels() {
const networks = useNetwork()

Expand Down
128 changes: 128 additions & 0 deletions src/data/queries/staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() || {}
Expand Down Expand Up @@ -361,6 +363,132 @@ export const useCalcInterchainDelegationsTotal = (
return { currencyTotal, graphData: { all: allData, ...tableDataByChain } }
}

export const useCalcDelegationsByValidator = (
delegationsQueryResults: UseQueryResult<{
delegation: Delegation[]
chainID: string
}>[],
interchainValidators: UseQueryResult<Validator[], unknown>[]
) => {
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
Expand Down
18 changes: 16 additions & 2 deletions src/pages/stake/StakedDonut.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -57,14 +68,17 @@

.circle {
height: 15px;
width: 15px;
min-width: 15px;
border-radius: 50%;
}

.denom {
text-transform: uppercase;
font-weight: 500;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.percent {
Expand Down
75 changes: 50 additions & 25 deletions src/pages/stake/StakedDonut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ul className={styles.legend}>
{payload.map((entry: any, index: any) => (
<li key={`item-${index}`} className={styles.detailLine}>
<div
className={styles.circle}
style={{ backgroundColor: entry.color }}
></div>
<img
className={styles.icon}
src={entry.payload.icon}
alt={entry.value}
/>
<p className={styles.denom}>{entry.value}</p>
<p className={styles.percent}>
{Math.round(entry.payload.percent * 100)}%
</p>
</li>
))}
{payload.map((entry: any, index: any) => {
const percentage = Math.round(entry.payload.percent * 100)
return (
<li key={`item-${index}`} className={styles.detailLine}>
<div
className={styles.circle}
style={{ backgroundColor: entry.color }}
></div>
{chainSelected ? (
<>
{entry.payload.identity !== "Other" && (
<ProfileIcon src={entry.payload.identity} size={22} />
)}
<p className={styles.denom}>{entry.payload.moniker}</p>
<p className={styles.percent}>
{percentage < 1 ? `< ${percentage}` : percentage}%
</p>
</>
) : (
<>
<img
className={styles.icon}
src={entry.payload.icon}
alt={entry.value}
/>
<p className={styles.denom}>{entry.value}</p>
<p className={styles.percent}>
{Math.round(entry.payload.percent * 100)}%
</p>
</>
)}
</li>
)
})}
</ul>
)
}
Expand All @@ -62,7 +85,7 @@ const StakedDonut = () => {

return (
<div className={styles.tooltip}>
<h6>{payload[0]?.name}</h6>
<h6>{payload[0]?.payload.name || payload[0]?.payload.moniker}</h6>
<div className={styles.infoLine}>
<p>Balance: </p>
<p>{payload[0]?.payload.amount}</p>
Expand Down Expand Up @@ -93,10 +116,12 @@ const StakedDonut = () => {
layout="vertical"
verticalAlign="middle"
align="right"
content={<RenderLegend />}
content={<RenderLegend chainSelected={!!chain} />}
className="legend"
/>
<Tooltip content={<RenderTooltip />} />
<Tooltip
content={<RenderTooltip chainSelected={!!chain} />}
/>
<Pie
data={graphData[chain || "all"]}
cx={125}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/stake/ValidatorDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const ValidatorDetails = () => {
}

return (
<Page {...state} title={t("Validator details")}>
<Page {...state} title={t("Validator details")} backButton>
{render()}
</Page>
)
Expand Down
4 changes: 4 additions & 0 deletions src/styles/_customize.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@
.recharts-tooltip-wrapper:focus-visible {
outline: none;
}

.recharts-legend-wrapper {
max-width: 45%;
}
2 changes: 1 addition & 1 deletion src/txs/stake/StakeTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const StakeTx = () => {
}

return (
<Page {...state} title={t("Delegate")}>
<Page {...state} title={t("Delegate")} backButton>
<Auto
columns={[
<Tabs
Expand Down

0 comments on commit 1b4a328

Please sign in to comment.