Skip to content

Commit a77fec2

Browse files
authored
Merge pull request #72 from cosmology-tech/fix/staking-example
fix staking-tokens example
2 parents 5d917c6 + 50260d1 commit a77fec2

File tree

4 files changed

+141
-69
lines changed

4 files changed

+141
-69
lines changed

examples/stake-tokens/components/react/all-validators.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export const Thumbnail = ({
6767
mr={2}
6868
/>
6969
) : (
70-
<Center boxSize="30px" bgColor="gray.400" borderRadius="full" mr={2}>
71-
{name && name.slice(0, 1).toUpperCase()}
70+
<Center boxSize="30px" bgColor="gray.200" borderRadius="full" mr={2}>
71+
{name && name.trim().slice(0, 1).toUpperCase()}
7272
</Center>
7373
)}
7474
</>
@@ -195,8 +195,8 @@ const AllValidators = ({
195195
<ModalBody>
196196
<ValidatorInfo
197197
imgUrl={
198-
currentValidator?.description?.identity
199-
? thumbnails[currentValidator.description.identity]
198+
currentValidator
199+
? thumbnails[currentValidator?.operatorAddress]
200200
: ''
201201
}
202202
name={currentValidator?.description?.moniker || ''}
@@ -270,17 +270,15 @@ const AllValidators = ({
270270
<Thumbnail
271271
identity={validator.description?.identity}
272272
name={validator.description?.moniker}
273-
thumbnailUrl={
274-
validator.description?.identity
275-
? thumbnails[validator.description.identity]
276-
: ''
277-
}
273+
thumbnailUrl={thumbnails[validator.operatorAddress]}
278274
/>
279275
<Text>{validator?.description?.moniker}</Text>
280276
</Box>
281277
</Td>
282278
<Td>
283-
{Math.floor(exponentiate(validator.tokens, -exp))}
279+
{Math.floor(
280+
exponentiate(validator.tokens, -exp)
281+
).toLocaleString()}
284282
&nbsp;
285283
<Token color="blackAlpha.800" token={coin.symbol} />
286284
</Td>

examples/stake-tokens/components/react/my-validators.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,7 @@ const MyValidators = ({
387387
<ModalBody>
388388
<ValidatorInfo
389389
imgUrl={
390-
currentValidator?.identity
391-
? thumbnails[currentValidator.identity]
392-
: ''
390+
currentValidator ? thumbnails[currentValidator.address] : ''
393391
}
394392
name={currentValidator?.name || ''}
395393
commission={
@@ -443,9 +441,7 @@ const MyValidators = ({
443441
<ModalBody>
444442
<ValidatorInfo
445443
imgUrl={
446-
currentValidator?.identity
447-
? thumbnails[currentValidator.identity]
448-
: ''
444+
currentValidator ? thumbnails[currentValidator.address] : ''
449445
}
450446
name={currentValidator?.name || ''}
451447
commission={
@@ -498,9 +494,7 @@ const MyValidators = ({
498494
<ModalBody>
499495
<ValidatorInfo
500496
imgUrl={
501-
currentValidator?.identity
502-
? thumbnails[currentValidator.identity]
503-
: ''
497+
currentValidator ? thumbnails[currentValidator.address] : ''
504498
}
505499
name={currentValidator?.name || ''}
506500
commission={
@@ -582,17 +576,15 @@ const MyValidators = ({
582576
<Thumbnail
583577
identity={validator.description?.identity}
584578
name={validator.description?.moniker}
585-
thumbnailUrl={
586-
validator.description?.identity
587-
? thumbnails[validator.description.identity]
588-
: ''
589-
}
579+
thumbnailUrl={thumbnails[validator.operatorAddress]}
590580
/>
591581
<Text>{validator?.description?.moniker}</Text>
592582
</Box>
593583
</Td>
594584
<Td>
595-
{Math.floor(exponentiate(validator.tokens, -exp))}
585+
{Math.floor(
586+
exponentiate(validator.tokens, -exp)
587+
).toLocaleString()}
596588
&nbsp;
597589
<Token color="blackAlpha.800" token={coin.symbol} />
598590
</Td>
@@ -687,9 +679,7 @@ const MyValidators = ({
687679
<Thumbnail
688680
identity={validator.identity}
689681
name={validator.name}
690-
thumbnailUrl={
691-
validator.identity ? thumbnails[validator.identity] : ''
692-
}
682+
thumbnailUrl={thumbnails[validator.address]}
693683
/>
694684
<Text>{validator.name}</Text>
695685
</Box>

examples/stake-tokens/components/react/staking.tsx

Lines changed: 121 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback, useEffect, useState } from 'react';
22
import { useChain } from '@cosmos-kit/react';
3-
import { Box, Skeleton } from '@chakra-ui/react';
3+
import { Box, SkeletonText } from '@chakra-ui/react';
44
import { cosmos } from 'interchain';
55
import BigNumber from 'bignumber.js';
66
import { decodeCosmosSdkDecFromProto } from '@cosmjs/stargate';
@@ -16,6 +16,7 @@ import AllValidators from './all-validators';
1616
import { getCoin } from '../../config';
1717
import router from 'next/router';
1818
import { ChainName } from '@cosmos-kit/core';
19+
import { ImageSource } from '../types';
1920

2021
export const exponentiate = (num: number | string, exp: number) => {
2122
return new BigNumber(num)
@@ -38,6 +39,109 @@ const splitIntoChunks = (arr: any[], chunkSize: number) => {
3839
return res;
3940
};
4041

42+
const convertChainName = (chainName: string) => {
43+
if (chainName.endsWith('testnet')) {
44+
return chainName.replace('testnet', '-testnet');
45+
}
46+
47+
switch (chainName) {
48+
case 'cosmoshub':
49+
return 'cosmos';
50+
case 'assetmantle':
51+
return 'asset-mantle';
52+
case 'cryptoorgchain':
53+
return 'crypto-org';
54+
case 'dig':
55+
return 'dig-chain';
56+
case 'gravitybridge':
57+
return 'gravity-bridge';
58+
case 'kichain':
59+
return 'ki-chain';
60+
case 'oraichain':
61+
return 'orai-chain';
62+
case 'terra':
63+
return 'terra-classic';
64+
default:
65+
return chainName;
66+
}
67+
};
68+
69+
const isUrlValid = async (url: string) => {
70+
const res = await fetch(url, { method: 'HEAD' });
71+
const contentType = res?.headers?.get('Content-Type') || '';
72+
return contentType.startsWith('image');
73+
};
74+
75+
const getCosmostationUrl = (chainName: string, validatorAddr: string) => {
76+
const cosmostationChainName = convertChainName(chainName);
77+
return `https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/${cosmostationChainName}/moniker/${validatorAddr}.png`;
78+
};
79+
80+
const addImageSource = async (
81+
validator: Validator,
82+
chainName: string
83+
): Promise<Validator & ImageSource> => {
84+
const url = getCosmostationUrl(chainName, validator.operatorAddress);
85+
const isValid = await isUrlValid(url);
86+
return { ...validator, imageSource: isValid ? 'cosmostation' : 'keybase' };
87+
};
88+
89+
const getKeybaseUrl = (identity: string) => {
90+
return `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`;
91+
};
92+
93+
const getImgUrls = async (validators: Validator[], chainName: string) => {
94+
const validatorsWithImgSource = await Promise.all(
95+
validators.map((validator) => addImageSource(validator, chainName))
96+
);
97+
98+
// cosmostation urls
99+
const cosmostationUrls = validatorsWithImgSource
100+
.filter((validator) => validator.imageSource === 'cosmostation')
101+
.map(({ operatorAddress }) => {
102+
return {
103+
address: operatorAddress,
104+
url: getCosmostationUrl(chainName, operatorAddress),
105+
};
106+
});
107+
108+
// keybase urls
109+
const keybaseIdentities = validatorsWithImgSource
110+
.filter((validator) => validator.imageSource === 'keybase')
111+
.map((validator) => ({
112+
address: validator.operatorAddress,
113+
identity: validator.description?.identity || '',
114+
}));
115+
116+
const chunkedIdentities = splitIntoChunks(keybaseIdentities, 20);
117+
118+
let responses: any[] = [];
119+
120+
for (const chunk of chunkedIdentities) {
121+
const thumbnailRequests = chunk.map(({ address, identity }) => {
122+
if (!identity) return { address, url: '' };
123+
124+
return fetch(getKeybaseUrl(identity))
125+
.then((response) => response.json())
126+
.then((res) => ({
127+
address,
128+
url: res.them?.[0]?.pictures?.primary.url || '',
129+
}));
130+
});
131+
responses = [...responses, await Promise.all(thumbnailRequests)];
132+
await new Promise((resolve) => setTimeout(resolve, 500));
133+
}
134+
135+
const keybaseUrls = responses.flat();
136+
137+
const allUrls = [...cosmostationUrls, ...keybaseUrls].reduce(
138+
(prev, cur) => ({ ...prev, [cur.address]: cur.url }),
139+
{}
140+
);
141+
142+
return allUrls;
143+
};
144+
41145
interface StakingTokens {
42146
balance: number;
43147
rewards: Reward[];
@@ -91,7 +195,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
91195
let rpcEndpoint = await getRpcEndpoint();
92196

93197
if (!rpcEndpoint) {
94-
console.log('no rpc endpoint — using a fallback');
198+
console.log('no rpc endpoint — using a fallback');
95199
rpcEndpoint = `https://rpc.cosmos.directory/${chainName}`;
96200
}
97201

@@ -163,42 +267,16 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
163267
: 0;
164268

165269
// THUMBNAILS
270+
let thumbnails = {};
271+
166272
const validatorThumbnails = localStorage.getItem(
167273
`${chainName}-validator-thumbnails`
168274
);
169275

170-
let thumbnails = {};
171-
172276
if (validatorThumbnails) {
173277
thumbnails = JSON.parse(validatorThumbnails);
174278
} else {
175-
const identities = allValidators.map(
176-
(validator) => validator.description!.identity
177-
);
178-
179-
const chunkedIdentities = splitIntoChunks(identities, 30);
180-
181-
let responses: any[] = [];
182-
183-
for (const chunk of chunkedIdentities) {
184-
const thumbnailRequests = chunk.map((identity) => {
185-
const url = `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`;
186-
return fetch(url).then((response) => response.json());
187-
});
188-
responses = [...responses, await Promise.all(thumbnailRequests)];
189-
await new Promise((resolve) => setTimeout(resolve, 1000));
190-
}
191-
192-
const thumbnailUrls = responses
193-
.flat()
194-
.map((value) => value.them?.[0]?.pictures?.primary.url);
195-
196-
thumbnails = thumbnailUrls.reduce(
197-
(prev, cur, idx) =>
198-
identities[idx] && cur ? { ...prev, [identities[idx]]: cur } : prev,
199-
{}
200-
);
201-
279+
thumbnails = await getImgUrls(validators, chainName);
202280
localStorage.setItem(
203281
`${chainName}-validator-thumbnails`,
204282
JSON.stringify(thumbnails)
@@ -233,7 +311,13 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
233311

234312
return (
235313
<Box my={14}>
236-
<Skeleton isLoaded={!isLoading}>
314+
<SkeletonText
315+
isLoaded={!isLoading}
316+
mt="0"
317+
noOfLines={4}
318+
spacing="4"
319+
skeletonHeight="4"
320+
>
237321
<Stats
238322
balance={data.balance}
239323
rewards={data.rewards}
@@ -242,9 +326,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
242326
updateData={getData}
243327
chainName={chainName}
244328
/>
245-
</Skeleton>
246-
{data.myValidators.length > 0 && (
247-
<Skeleton isLoaded={!isLoading}>
329+
{data.myValidators.length > 0 && (
248330
<MyValidators
249331
validators={data.myValidators}
250332
allValidator={data.allValidators}
@@ -256,10 +338,8 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
256338
chainName={chainName}
257339
thumbnails={data.thumbnails}
258340
/>
259-
</Skeleton>
260-
)}
261-
{data.allValidators.length > 0 && (
262-
<Skeleton isLoaded={!isLoading}>
341+
)}
342+
{data.allValidators.length > 0 && (
263343
<AllValidators
264344
balance={data.balance}
265345
validators={data.allValidators}
@@ -269,8 +349,8 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
269349
chainName={chainName}
270350
thumbnails={data.thumbnails}
271351
/>
272-
</Skeleton>
273-
)}
352+
)}
353+
</SkeletonText>
274354
</Box>
275355
);
276356
};

examples/stake-tokens/components/types.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,7 @@ export interface MyValidator {
9999
identity: string | undefined;
100100
commission: string | undefined;
101101
}
102+
103+
export type ImageSource = {
104+
imageSource: 'cosmostation' | 'keybase';
105+
};

0 commit comments

Comments
 (0)