diff --git a/examples/stake-tokens/components/react/all-validators.tsx b/examples/stake-tokens/components/react/all-validators.tsx index 9c0ca7dd3..0f0a7d2c1 100644 --- a/examples/stake-tokens/components/react/all-validators.tsx +++ b/examples/stake-tokens/components/react/all-validators.tsx @@ -24,6 +24,7 @@ import { Text, Image, useColorMode, + Center, } from '@chakra-ui/react'; import { DelegateWarning, @@ -46,6 +47,34 @@ import type { import { TransactionResult } from '../types'; import { ChainName } from '@cosmos-kit/core'; +export const Thumbnail = ({ + identity, + name, + thumbnailUrl, +}: { + identity: string | undefined; + name: string | undefined; + thumbnailUrl: string; +}) => { + return ( + <> + {identity && thumbnailUrl ? ( + {name} + ) : ( +
+ {name && name.slice(0, 1).toUpperCase()} +
+ )} + + ); +}; + const AllValidators = ({ validators, balance, @@ -53,6 +82,7 @@ const AllValidators = ({ updateData, unbondingDays, chainName, + thumbnails, }: { validators: Validator[]; balance: number; @@ -60,6 +90,9 @@ const AllValidators = ({ updateData: () => void; unbondingDays: number; chainName: ChainName; + thumbnails: { + [key: string]: string; + }; }) => { const { isOpen, onOpen, onClose } = useDisclosure(); const { getSigningStargateClient, address } = useChain(chainName); @@ -161,9 +194,20 @@ const AllValidators = ({ {index + 1} - {/* {validator.description.moniker} */} + {validator?.description?.moniker} - {/* {validator.voting} */} - 10,000,000  + {Math.floor(exponentiate(validator.tokens, -exp))} +   - {/* {validator.commission} */} - 5% + + {validator.commission?.commissionRates?.rate && + exponentiate( + validator.commission.commissionRates.rate, + -16 + ).toFixed(0)} + % + {/* {validator.apr} */} diff --git a/examples/stake-tokens/components/react/delegate-modal.tsx b/examples/stake-tokens/components/react/delegate-modal.tsx index ed5310d5d..ff5709bdc 100644 --- a/examples/stake-tokens/components/react/delegate-modal.tsx +++ b/examples/stake-tokens/components/react/delegate-modal.tsx @@ -22,6 +22,7 @@ import { UnorderedList, useColorModeValue, useToast, + Center, } from '@chakra-ui/react'; import { TransactionResult } from '../types'; @@ -33,11 +34,17 @@ export const ValidatorInfo = ({ }: { imgUrl: string; name: string; - commission: number; + commission: number | string; apr: number; }) => ( - {name} + {imgUrl ? ( + {name} + ) : ( +
+ {name.slice(0, 1).toUpperCase()} +
+ )} {name} diff --git a/examples/stake-tokens/components/react/my-validators.tsx b/examples/stake-tokens/components/react/my-validators.tsx index b7517bd42..184f3e98c 100644 --- a/examples/stake-tokens/components/react/my-validators.tsx +++ b/examples/stake-tokens/components/react/my-validators.tsx @@ -47,6 +47,7 @@ import type { } from 'interchain/types/codegen/cosmos/staking/v1beta1/staking'; import type { DelegationDelegatorReward as Reward } from 'interchain/types/codegen/cosmos/distribution/v1beta1/distribution'; import { ChainName } from '@cosmos-kit/core'; +import { Thumbnail } from './all-validators'; const MyValidators = ({ validators, @@ -57,6 +58,7 @@ const MyValidators = ({ updateData, unbondingDays, chainName, + thumbnails, }: { validators: Validator[]; allValidator: Validator[]; @@ -66,6 +68,9 @@ const MyValidators = ({ updateData: () => void; unbondingDays: number; chainName: ChainName; + thumbnails: { + [key: string]: string; + }; }) => { const { getSigningStargateClient, address } = useChain(chainName); @@ -152,9 +157,11 @@ const MyValidators = ({ return { details: validator?.description?.details, name: validator?.description?.moniker, + identity: validator?.description?.identity, address: validator.operatorAddress, staked: exponentiate(delegation.balance!.amount, -exp), reward: Number(exponentiate(rewardAmount, -exp).toFixed(6)), + commission: validator?.commission?.commissionRates?.rate, }; }); @@ -379,9 +386,17 @@ const MyValidators = ({ @@ -427,9 +442,17 @@ const MyValidators = ({ @@ -474,9 +497,17 @@ const MyValidators = ({ @@ -548,23 +579,31 @@ const MyValidators = ({ overflowX="hidden" > {index + 1} - {/* {validator.description.moniker} */} + {validator?.description?.moniker}
- {/* {validator.voting} */} - 10,000,000  + {Math.floor(exponentiate(validator.tokens, -exp))} +   - {/* {validator.commission} */} - 5% + + {validator.commission?.commissionRates?.rate && + exponentiate( + validator.commission.commissionRates.rate, + -16 + ).toFixed(0)} + % + {/* {validator.apr} */} @@ -645,13 +684,13 @@ const MyValidators = ({ overflowX="hidden" > {index + 1} - {/* {validator.name} */} + {validator.name} diff --git a/examples/stake-tokens/components/react/staking.tsx b/examples/stake-tokens/components/react/staking.tsx index b4ab65995..f17ca7fcf 100644 --- a/examples/stake-tokens/components/react/staking.tsx +++ b/examples/stake-tokens/components/react/staking.tsx @@ -29,6 +29,15 @@ export const getExponent = (chainName: string) => { )?.exponent as number; }; +const splitIntoChunks = (arr: any[], chunkSize: number) => { + const res = []; + for (let i = 0; i < arr.length; i += chunkSize) { + const chunk = arr.slice(i, i + chunkSize); + res.push(chunk); + } + return res; +}; + interface StakingTokens { balance: number; rewards: Reward[]; @@ -38,6 +47,9 @@ interface StakingTokens { myValidators: Validator[]; allValidators: Validator[]; unbondingDays: number; + thumbnails: { + [key: string]: string; + }; } export const StakingSection = ({ chainName }: { chainName: ChainName }) => { @@ -52,6 +64,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { myValidators: [], allValidators: [], unbondingDays: 0, + thumbnails: {}, }); const coin = getCoin(chainName); @@ -68,6 +81,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { myValidators: [], allValidators: [], unbondingDays: 0, + thumbnails: {}, }); return; } @@ -115,19 +129,22 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { const totalReward = Number(exponentiate(reward, -exp).toFixed(6)); // ALL VALIDATORS - const { validators: allValidators } = - await client.cosmos.staking.v1beta1.validators({ - status: cosmos.staking.v1beta1.bondStatusToJSON( - cosmos.staking.v1beta1.BondStatus.BOND_STATUS_BONDED - ), - pagination: { - key: new Uint8Array(), - offset: Long.fromNumber(0), - limit: Long.fromNumber(200), - countTotal: false, - reverse: false, - }, - }); + const { validators } = await client.cosmos.staking.v1beta1.validators({ + status: cosmos.staking.v1beta1.bondStatusToJSON( + cosmos.staking.v1beta1.BondStatus.BOND_STATUS_BONDED + ), + pagination: { + key: new Uint8Array(), + offset: Long.fromNumber(0), + limit: Long.fromNumber(200), + countTotal: false, + reverse: false, + }, + }); + + const allValidators = validators.sort((a, b) => + new BigNumber(b.tokens).minus(new BigNumber(a.tokens)).toNumber() + ); // DELEGATIONS const { delegationResponses: delegations } = @@ -145,6 +162,49 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { ? Number((params?.unbondingTime?.seconds.low / 86400).toFixed(0)) : 0; + // THUMBNAILS + const validatorThumbnails = localStorage.getItem( + `${chainName}-validator-thumbnails` + ); + + let thumbnails = {}; + + if (validatorThumbnails) { + thumbnails = JSON.parse(validatorThumbnails); + } else { + const identities = allValidators.map( + (validator) => validator.description!.identity + ); + + const chunkedIdentities = splitIntoChunks(identities, 30); + + let responses: any[] = []; + + for (const chunk of chunkedIdentities) { + const thumbnailRequests = chunk.map((identity) => { + const url = `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`; + return fetch(url).then((response) => response.json()); + }); + responses = [...responses, await Promise.all(thumbnailRequests)]; + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + const thumbnailUrls = responses + .flat() + .map((value) => value.them?.[0]?.pictures?.primary.url); + + thumbnails = thumbnailUrls.reduce( + (prev, cur, idx) => + identities[idx] && cur ? { ...prev, [identities[idx]]: cur } : prev, + {} + ); + + localStorage.setItem( + `${chainName}-validator-thumbnails`, + JSON.stringify(thumbnails) + ); + } + setData({ rewards, totalReward, @@ -154,9 +214,10 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { myValidators, allValidators, unbondingDays, + thumbnails, }); setIsLoading(false); - }, [address, coin, exp, getRpcEndpoint]); + }, [address, chainName, coin.base, exp, getRpcEndpoint]); useEffect(() => { getData(); @@ -193,6 +254,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { updateData={getData} unbondingDays={data.unbondingDays} chainName={chainName} + thumbnails={data.thumbnails} /> )} @@ -205,6 +267,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => { updateData={getData} unbondingDays={data.unbondingDays} chainName={chainName} + thumbnails={data.thumbnails} /> )} diff --git a/examples/stake-tokens/components/types.tsx b/examples/stake-tokens/components/types.tsx index f95b0397e..913fd9910 100644 --- a/examples/stake-tokens/components/types.tsx +++ b/examples/stake-tokens/components/types.tsx @@ -96,4 +96,6 @@ export interface MyValidator { address: string; staked: number; reward: number; + identity: string | undefined; + commission: string | undefined; }