1
1
import { useCallback , useEffect , useState } from 'react' ;
2
2
import { useChain } from '@cosmos-kit/react' ;
3
- import { Box , Skeleton } from '@chakra-ui/react' ;
3
+ import { Box , SkeletonText } from '@chakra-ui/react' ;
4
4
import { cosmos } from 'interchain' ;
5
5
import BigNumber from 'bignumber.js' ;
6
6
import { decodeCosmosSdkDecFromProto } from '@cosmjs/stargate' ;
@@ -16,6 +16,7 @@ import AllValidators from './all-validators';
16
16
import { getCoin } from '../../config' ;
17
17
import router from 'next/router' ;
18
18
import { ChainName } from '@cosmos-kit/core' ;
19
+ import { ImageSource } from '../types' ;
19
20
20
21
export const exponentiate = ( num : number | string , exp : number ) => {
21
22
return new BigNumber ( num )
@@ -38,6 +39,109 @@ const splitIntoChunks = (arr: any[], chunkSize: number) => {
38
39
return res ;
39
40
} ;
40
41
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
+
41
145
interface StakingTokens {
42
146
balance : number ;
43
147
rewards : Reward [ ] ;
@@ -91,7 +195,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
91
195
let rpcEndpoint = await getRpcEndpoint ( ) ;
92
196
93
197
if ( ! rpcEndpoint ) {
94
- console . log ( 'no rpc endpoint — using a fallback' ) ;
198
+ console . log ( 'no rpc endpoint — using a fallback' ) ;
95
199
rpcEndpoint = `https://rpc.cosmos.directory/${ chainName } ` ;
96
200
}
97
201
@@ -163,42 +267,16 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
163
267
: 0 ;
164
268
165
269
// THUMBNAILS
270
+ let thumbnails = { } ;
271
+
166
272
const validatorThumbnails = localStorage . getItem (
167
273
`${ chainName } -validator-thumbnails`
168
274
) ;
169
275
170
- let thumbnails = { } ;
171
-
172
276
if ( validatorThumbnails ) {
173
277
thumbnails = JSON . parse ( validatorThumbnails ) ;
174
278
} 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 ) ;
202
280
localStorage . setItem (
203
281
`${ chainName } -validator-thumbnails` ,
204
282
JSON . stringify ( thumbnails )
@@ -233,7 +311,13 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
233
311
234
312
return (
235
313
< 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
+ >
237
321
< Stats
238
322
balance = { data . balance }
239
323
rewards = { data . rewards }
@@ -242,9 +326,7 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
242
326
updateData = { getData }
243
327
chainName = { chainName }
244
328
/>
245
- </ Skeleton >
246
- { data . myValidators . length > 0 && (
247
- < Skeleton isLoaded = { ! isLoading } >
329
+ { data . myValidators . length > 0 && (
248
330
< MyValidators
249
331
validators = { data . myValidators }
250
332
allValidator = { data . allValidators }
@@ -256,10 +338,8 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
256
338
chainName = { chainName }
257
339
thumbnails = { data . thumbnails }
258
340
/>
259
- </ Skeleton >
260
- ) }
261
- { data . allValidators . length > 0 && (
262
- < Skeleton isLoaded = { ! isLoading } >
341
+ ) }
342
+ { data . allValidators . length > 0 && (
263
343
< AllValidators
264
344
balance = { data . balance }
265
345
validators = { data . allValidators }
@@ -269,8 +349,8 @@ export const StakingSection = ({ chainName }: { chainName: ChainName }) => {
269
349
chainName = { chainName }
270
350
thumbnails = { data . thumbnails }
271
351
/>
272
- </ Skeleton >
273
- ) }
352
+ ) }
353
+ </ SkeletonText >
274
354
</ Box >
275
355
) ;
276
356
} ;
0 commit comments