diff --git a/examples/stake-tokens/components/react/all-validators.tsx b/examples/stake-tokens/components/react/all-validators.tsx
index 0f0a7d2c1..2207e865b 100644
--- a/examples/stake-tokens/components/react/all-validators.tsx
+++ b/examples/stake-tokens/components/react/all-validators.tsx
@@ -67,8 +67,8 @@ export const Thumbnail = ({
mr={2}
/>
) : (
-
- {name && name.slice(0, 1).toUpperCase()}
+
+ {name && name.trim().slice(0, 1).toUpperCase()}
)}
>
@@ -195,8 +195,8 @@ const AllValidators = ({
{validator?.description?.moniker}
- {Math.floor(exponentiate(validator.tokens, -exp))}
+ {Math.floor(
+ exponentiate(validator.tokens, -exp)
+ ).toLocaleString()}
|
diff --git a/examples/stake-tokens/components/react/my-validators.tsx b/examples/stake-tokens/components/react/my-validators.tsx
index 184f3e98c..d957fab88 100644
--- a/examples/stake-tokens/components/react/my-validators.tsx
+++ b/examples/stake-tokens/components/react/my-validators.tsx
@@ -387,9 +387,7 @@ const MyValidators = ({
{validator?.description?.moniker}
- {Math.floor(exponentiate(validator.tokens, -exp))}
+ {Math.floor(
+ exponentiate(validator.tokens, -exp)
+ ).toLocaleString()}
|
@@ -687,9 +679,7 @@ const MyValidators = ({
{validator.name}
diff --git a/examples/stake-tokens/components/react/staking.tsx b/examples/stake-tokens/components/react/staking.tsx
index f17ca7fcf..3e35b568b 100644
--- a/examples/stake-tokens/components/react/staking.tsx
+++ b/examples/stake-tokens/components/react/staking.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useChain } from '@cosmos-kit/react';
-import { Box, Skeleton } from '@chakra-ui/react';
+import { Box, SkeletonText } from '@chakra-ui/react';
import { cosmos } from 'interchain';
import BigNumber from 'bignumber.js';
import { decodeCosmosSdkDecFromProto } from '@cosmjs/stargate';
@@ -16,6 +16,7 @@ import AllValidators from './all-validators';
import { getCoin } from '../../config';
import router from 'next/router';
import { ChainName } from '@cosmos-kit/core';
+import { ImageSource } from '../types';
export const exponentiate = (num: number | string, exp: number) => {
return new BigNumber(num)
@@ -38,6 +39,109 @@ const splitIntoChunks = (arr: any[], chunkSize: number) => {
return res;
};
+const convertChainName = (chainName: string) => {
+ if (chainName.endsWith('testnet')) {
+ return chainName.replace('testnet', '-testnet');
+ }
+
+ switch (chainName) {
+ case 'cosmoshub':
+ return 'cosmos';
+ case 'assetmantle':
+ return 'asset-mantle';
+ case 'cryptoorgchain':
+ return 'crypto-org';
+ case 'dig':
+ return 'dig-chain';
+ case 'gravitybridge':
+ return 'gravity-bridge';
+ case 'kichain':
+ return 'ki-chain';
+ case 'oraichain':
+ return 'orai-chain';
+ case 'terra':
+ return 'terra-classic';
+ default:
+ return chainName;
+ }
+};
+
+const isUrlValid = async (url: string) => {
+ const res = await fetch(url, { method: 'HEAD' });
+ const contentType = res?.headers?.get('Content-Type') || '';
+ return contentType.startsWith('image');
+};
+
+const getCosmostationUrl = (chainName: string, validatorAddr: string) => {
+ const cosmostationChainName = convertChainName(chainName);
+ return `https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/${cosmostationChainName}/moniker/${validatorAddr}.png`;
+};
+
+const addImageSource = async (
+ validator: Validator,
+ chainName: string
+): Promise => {
+ const url = getCosmostationUrl(chainName, validator.operatorAddress);
+ const isValid = await isUrlValid(url);
+ return { ...validator, imageSource: isValid ? 'cosmostation' : 'keybase' };
+};
+
+const getKeybaseUrl = (identity: string) => {
+ return `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`;
+};
+
+const getImgUrls = async (validators: Validator[], chainName: string) => {
+ const validatorsWithImgSource = await Promise.all(
+ validators.map((validator) => addImageSource(validator, chainName))
+ );
+
+ // cosmostation urls
+ const cosmostationUrls = validatorsWithImgSource
+ .filter((validator) => validator.imageSource === 'cosmostation')
+ .map(({ operatorAddress }) => {
+ return {
+ address: operatorAddress,
+ url: getCosmostationUrl(chainName, operatorAddress),
+ };
+ });
+
+ // keybase urls
+ const keybaseIdentities = validatorsWithImgSource
+ .filter((validator) => validator.imageSource === 'keybase')
+ .map((validator) => ({
+ address: validator.operatorAddress,
+ identity: validator.description?.identity || '',
+ }));
+
+ const chunkedIdentities = splitIntoChunks(keybaseIdentities, 20);
+
+ let responses: any[] = [];
+
+ for (const chunk of chunkedIdentities) {
+ const thumbnailRequests = chunk.map(({ address, identity }) => {
+ if (!identity) return { address, url: '' };
+
+ return fetch(getKeybaseUrl(identity))
+ .then((response) => response.json())
+ .then((res) => ({
+ address,
+ url: res.them?.[0]?.pictures?.primary.url || '',
+ }));
+ });
+ responses = [...responses, await Promise.all(thumbnailRequests)];
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ }
+
+ const keybaseUrls = responses.flat();
+
+ const allUrls = [...cosmostationUrls, ...keybaseUrls].reduce(
+ (prev, cur) => ({ ...prev, [cur.address]: cur.url }),
+ {}
+ );
+
+ return allUrls;
+};
+
interface StakingTokens {
balance: number;
rewards: Reward[];
@@ -91,7 +195,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
let rpcEndpoint = await getRpcEndpoint();
if (!rpcEndpoint) {
- console.log('no rpc endpoint — using a fallback');
+ console.log('no rpc endpoint — using a fallback');
rpcEndpoint = `https://rpc.cosmos.directory/${chainName}`;
}
@@ -163,42 +267,16 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
: 0;
// THUMBNAILS
+ let 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,
- {}
- );
-
+ thumbnails = await getImgUrls(validators, chainName);
localStorage.setItem(
`${chainName}-validator-thumbnails`,
JSON.stringify(thumbnails)
@@ -233,7 +311,13 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
return (
-
+
{
updateData={getData}
chainName={chainName}
/>
-
- {data.myValidators.length > 0 && (
-
+ {data.myValidators.length > 0 && (
{
chainName={chainName}
thumbnails={data.thumbnails}
/>
-
- )}
- {data.allValidators.length > 0 && (
-
+ )}
+ {data.allValidators.length > 0 && (
{
chainName={chainName}
thumbnails={data.thumbnails}
/>
-
- )}
+ )}
+
);
};
diff --git a/examples/stake-tokens/components/types.tsx b/examples/stake-tokens/components/types.tsx
index 913fd9910..cfc751d4c 100644
--- a/examples/stake-tokens/components/types.tsx
+++ b/examples/stake-tokens/components/types.tsx
@@ -99,3 +99,7 @@ export interface MyValidator {
identity: string | undefined;
commission: string | undefined;
}
+
+export type ImageSource = {
+ imageSource: 'cosmostation' | 'keybase';
+};