11import { useState , useEffect } from 'react'
22import api from '../services/api'
33import { countries } from '../utils/countries'
4- import { formatTokenUsdEstimate } from '../utils/tokenUsd'
54
65interface NetworkInfo {
76 name ?: string ;
@@ -35,6 +34,14 @@ interface MultiNetworkStatus {
3534 totalReserve ?: number ;
3635}
3736
37+ interface TokenPriceQuote {
38+ symbol : string ;
39+ coinId : string ;
40+ usd : number ;
41+ lastUpdatedAt : string | null ;
42+ source : 'coingecko' ;
43+ }
44+
3845type ExplorerLinkConfig = {
3946 label : string ;
4047 buildUrl : ( address : string ) => string ;
@@ -107,10 +114,17 @@ function MultiNetworkGasBalances() {
107114 const [ loadingNetworks , setLoadingNetworks ] = useState < Record < string , boolean > > ( { } )
108115 const [ error , setError ] = useState < string | null > ( null )
109116 const [ copiedAddressKey , setCopiedAddressKey ] = useState < string | null > ( null )
117+ const [ tokenQuotes , setTokenQuotes ] = useState < Record < string , TokenPriceQuote > > ( { } )
110118
111119 const [ scope , setScope ] = useState < 'GLOBAL' | 'COUNTRY' > ( 'GLOBAL' ) ;
112120 const [ selectedCountry , setSelectedCountry ] = useState < string > ( 'DE' ) ; // Default to Germany or commonly used
113121
122+ const usdFormatter = new Intl . NumberFormat ( 'en-US' , {
123+ style : 'currency' ,
124+ currency : 'USD' ,
125+ maximumFractionDigits : 2
126+ } )
127+
114128 const shortenAddress = ( value : string , startLength = 6 , endLength = 4 ) => {
115129 if ( ! value || value === 'N/A' ) {
116130 return value
@@ -151,6 +165,7 @@ function MultiNetworkGasBalances() {
151165 setError ( null )
152166 setNetworkStatus ( null )
153167 setLoadingNetworks ( { } )
168+ setTokenQuotes ( { } )
154169
155170 const params = new URLSearchParams ( )
156171 if ( currentScope === 'COUNTRY' ) {
@@ -178,7 +193,7 @@ function MultiNetworkGasBalances() {
178193 networkName : net . networkName ,
179194 adapterType : net . adapterType ,
180195 walletAddress : net . walletAddress ,
181- tokenSymbol : '...' ,
196+ tokenSymbol : net . tokenSymbol || '...' ,
182197 totalReserve : 0 ,
183198 walletBalance : 0 ,
184199 availableForDistribution : 0 ,
@@ -199,6 +214,31 @@ function MultiNetworkGasBalances() {
199214 setLoadingNetworks ( initialLoading )
200215 setLoading ( false )
201216
217+ const tokenSymbols = Array . from (
218+ new Set (
219+ networks
220+ . map ( ( net : any ) => typeof net . tokenSymbol === 'string' ? net . tokenSymbol . trim ( ) . toUpperCase ( ) : '' )
221+ . filter ( ( symbol : string ) => symbol . length > 0 )
222+ )
223+ )
224+
225+ if ( tokenSymbols . length > 0 ) {
226+ try {
227+ const priceResponse = await api . get ( '/api/global/token-prices' , {
228+ params : {
229+ symbols : tokenSymbols . join ( ',' )
230+ }
231+ } )
232+
233+ const quotes = priceResponse . data ?. data ?. quotes
234+ if ( quotes && typeof quotes === 'object' ) {
235+ setTokenQuotes ( quotes )
236+ }
237+ } catch ( priceError ) {
238+ console . error ( 'Failed to fetch live token prices:' , priceError )
239+ }
240+ }
241+
202242 // 2. Fetch each network's status individualy in parallel (backend will coalesce and cache)
203243 enabledNetworks . forEach ( async ( networkId : string ) => {
204244 try {
@@ -250,6 +290,31 @@ function MultiNetworkGasBalances() {
250290 }
251291 }
252292
293+ const formatUsdAmount = ( tokenSymbol : string , amount ?: number ) => {
294+ if ( ! Number . isFinite ( amount ) ) {
295+ return null
296+ }
297+
298+ const quote = tokenQuotes [ tokenSymbol . trim ( ) . toUpperCase ( ) ]
299+ if ( ! quote || ! Number . isFinite ( quote . usd ) ) {
300+ return null
301+ }
302+
303+ return usdFormatter . format ( ( amount as number ) * quote . usd )
304+ }
305+
306+ const latestQuoteTime = Object . values ( tokenQuotes ) . reduce < string | null > ( ( latest , quote ) => {
307+ if ( ! quote . lastUpdatedAt ) {
308+ return latest
309+ }
310+
311+ if ( ! latest || new Date ( quote . lastUpdatedAt ) . getTime ( ) > new Date ( latest ) . getTime ( ) ) {
312+ return quote . lastUpdatedAt
313+ }
314+
315+ return latest
316+ } , null )
317+
253318 useEffect ( ( ) => {
254319 fetchMultiNetworkStatus ( scope , selectedCountry )
255320 } , [ scope , selectedCountry ] )
@@ -356,9 +421,11 @@ function MultiNetworkGasBalances() {
356421 < p style = { { margin : '0.5rem 0 0 0' , color : '#0c4a6e' , fontSize : '0.9rem' } } >
357422 { networkStatus . totalNetworks } networks enabled: { networkStatus . enabledNetworks . join ( ', ' ) }
358423 </ p >
359- < p style = { { margin : '0.5rem 0 0 0' , color : '#075985' , fontSize : '0.8rem' } } >
360- USD values are reference estimates from a static price table, not live market quotes.
361- </ p >
424+ { latestQuoteTime && (
425+ < p style = { { margin : '0.5rem 0 0 0' , color : '#075985' , fontSize : '0.8rem' } } >
426+ USD quotes from CoinGecko. Last update: { new Date ( latestQuoteTime ) . toLocaleString ( ) }
427+ </ p >
428+ ) }
362429 </ div >
363430
364431 { /* Network Details */ }
@@ -399,10 +466,14 @@ function MultiNetworkGasBalances() {
399466 const balanceDisplay = balanceFormatted === 'N/A'
400467 ? ( isNetworkLoading ? 'Loading...' : 'N/A' )
401468 : `${ balanceFormatted } ${ tokenSymbol } `
402- const balanceUsdEstimate = formatTokenUsdEstimate ( tokenSymbol , fallbackWalletBalance )
469+ const balanceUsdEstimate = formatUsdAmount ( tokenSymbol , fallbackWalletBalance )
403470 const gasPriceDisplay = gasPriceFormatted === 'N/A'
404471 ? ( isNetworkLoading ? 'Loading...' : 'N/A' )
405472 : `${ gasPriceFormatted } ${ tokenSymbol } `
473+ const gasPriceNumeric = Number . parseFloat ( gasPriceFormatted )
474+ const gasPriceUsdEstimate = Number . isFinite ( gasPriceNumeric )
475+ ? formatUsdAmount ( tokenSymbol , gasPriceNumeric )
476+ : null
406477
407478 return (
408479 < div key = { networkName } style = { {
@@ -450,6 +521,9 @@ function MultiNetworkGasBalances() {
450521 </ p >
451522 < p style = { { margin : '0.25rem 0' , color : '#888' } } >
452523 < strong > Gas Price:</ strong > { gasPriceDisplay }
524+ { gasPriceUsdEstimate && (
525+ < span style = { { color : '#cbd5e1' } } > ({ gasPriceUsdEstimate } )</ span >
526+ ) }
453527 </ p >
454528 < p style = { { margin : '0.25rem 0' , color : '#888' } } >
455529 < strong > Address:</ strong > { " " }
0 commit comments