2022-05-10 07:48:05 +00:00
package wallet
import (
"context"
2022-10-27 07:38:05 +00:00
"math"
2022-05-10 07:48:05 +00:00
"math/big"
2024-02-02 10:42:56 +00:00
"sync"
2022-11-29 13:43:18 +00:00
"time"
2022-05-10 07:48:05 +00:00
2024-06-06 19:57:29 +00:00
"golang.org/x/exp/maps"
2022-05-10 07:48:05 +00:00
"github.com/ethereum/go-ethereum/common"
2024-02-15 11:01:49 +00:00
"github.com/ethereum/go-ethereum/common/hexutil"
2022-12-01 09:19:32 +00:00
"github.com/ethereum/go-ethereum/event"
2023-03-20 13:44:23 +00:00
"github.com/ethereum/go-ethereum/log"
2024-06-06 19:57:29 +00:00
"github.com/status-im/status-go/rpc/chain"
2022-10-27 07:38:05 +00:00
"github.com/status-im/status-go/services/wallet/async"
2023-02-21 09:05:16 +00:00
"github.com/status-im/status-go/services/wallet/market"
2023-01-25 18:28:51 +00:00
"github.com/status-im/status-go/services/wallet/thirdparty"
2022-09-13 07:10:59 +00:00
"github.com/status-im/status-go/services/wallet/token"
2024-03-15 12:37:00 +00:00
"github.com/status-im/status-go/services/wallet/transfer"
2022-11-29 13:43:18 +00:00
"github.com/status-im/status-go/services/wallet/walletevent"
2022-05-10 07:48:05 +00:00
)
2022-11-29 13:43:18 +00:00
// WalletTickReload emitted every 15mn to reload the wallet balance and history
const EventWalletTickReload walletevent . EventType = "wallet-tick-reload"
2023-01-17 09:56:16 +00:00
const EventWalletTickCheckConnected walletevent . EventType = "wallet-tick-check-connected"
2022-11-29 13:43:18 +00:00
2023-11-27 21:28:16 +00:00
const (
walletTickReloadPeriod = 10 * time . Minute
activityReloadDelay = 30 // Wait this many seconds after activity is detected before triggering a wallet reload
activityReloadMarginSeconds = 30 // Trigger a wallet reload if activity is detected this many seconds before the last reload
)
2023-01-12 17:54:14 +00:00
func getFixedCurrencies ( ) [ ] string {
2023-01-19 14:49:48 +00:00
return [ ] string { "USD" }
2023-01-12 17:54:14 +00:00
}
2023-04-06 14:59:17 +00:00
func belongsToMandatoryTokens ( symbol string ) bool {
var mandatoryTokens = [ ] string { "ETH" , "DAI" , "SNT" , "STT" }
for _ , t := range mandatoryTokens {
if t == symbol {
return true
}
}
return false
}
2024-06-06 19:57:29 +00:00
func NewReader ( tokenManager token . ManagerInterface , marketManager * market . Manager , persistence token . TokenBalancesStorage , walletFeed * event . Feed ) * Reader {
2023-04-25 12:00:17 +00:00
return & Reader {
2024-04-11 15:33:07 +00:00
tokenManager : tokenManager ,
marketManager : marketManager ,
persistence : persistence ,
walletFeed : walletFeed ,
refreshBalanceCache : true ,
2023-11-27 21:28:16 +00:00
}
2022-05-10 07:48:05 +00:00
}
type Reader struct {
2024-06-06 19:57:29 +00:00
tokenManager token . ManagerInterface
2023-11-27 21:28:16 +00:00
marketManager * market . Manager
2024-06-06 19:57:29 +00:00
persistence token . TokenBalancesStorage
2023-11-27 21:28:16 +00:00
walletFeed * event . Feed
cancel context . CancelFunc
walletEventsWatcher * walletevent . Watcher
2024-04-11 15:33:07 +00:00
lastWalletTokenUpdateTimestamp sync . Map
2023-11-27 21:28:16 +00:00
reloadDelayTimer * time . Timer
2024-02-02 10:42:56 +00:00
refreshBalanceCache bool
rw sync . RWMutex
2022-05-10 07:48:05 +00:00
}
2023-08-31 07:47:24 +00:00
func splitVerifiedTokens ( tokens [ ] * token . Token ) ( [ ] * token . Token , [ ] * token . Token ) {
verified := make ( [ ] * token . Token , 0 )
unverified := make ( [ ] * token . Token , 0 )
for _ , t := range tokens {
if t . Verified {
verified = append ( verified , t )
} else {
unverified = append ( unverified , t )
}
}
return verified , unverified
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
func getTokenBySymbols ( tokens [ ] * token . Token ) map [ string ] [ ] * token . Token {
res := make ( map [ string ] [ ] * token . Token )
2022-05-10 07:48:05 +00:00
2022-10-27 07:38:05 +00:00
for _ , t := range tokens {
if _ , ok := res [ t . Symbol ] ; ! ok {
res [ t . Symbol ] = make ( [ ] * token . Token , 0 )
2022-05-10 07:48:05 +00:00
}
2022-10-27 07:38:05 +00:00
res [ t . Symbol ] = append ( res [ t . Symbol ] , t )
}
return res
}
func getTokenAddresses ( tokens [ ] * token . Token ) [ ] common . Address {
set := make ( map [ common . Address ] bool )
for _ , token := range tokens {
set [ token . Address ] = true
}
res := make ( [ ] common . Address , 0 )
for address := range set {
res = append ( res , address )
}
return res
2022-05-10 07:48:05 +00:00
}
2022-12-01 09:19:32 +00:00
func ( r * Reader ) Start ( ) error {
ctx , cancel := context . WithCancel ( context . Background ( ) )
r . cancel = cancel
2023-11-27 21:28:16 +00:00
r . startWalletEventsWatcher ( )
2022-12-01 09:19:32 +00:00
go func ( ) {
2023-11-27 21:28:16 +00:00
ticker := time . NewTicker ( walletTickReloadPeriod )
2022-12-01 09:19:32 +00:00
defer ticker . Stop ( )
for {
select {
case <- ctx . Done ( ) :
return
case <- ticker . C :
2023-11-27 21:28:16 +00:00
r . triggerWalletReload ( )
2022-12-01 09:19:32 +00:00
}
}
} ( )
2022-11-29 13:43:18 +00:00
return nil
2022-05-10 07:48:05 +00:00
}
2022-12-01 09:19:32 +00:00
func ( r * Reader ) Stop ( ) {
if r . cancel != nil {
r . cancel ( )
}
2023-11-27 21:28:16 +00:00
r . stopWalletEventsWatcher ( )
r . cancelDelayedWalletReload ( )
2024-04-11 15:33:07 +00:00
r . lastWalletTokenUpdateTimestamp = sync . Map { }
2023-11-27 21:28:16 +00:00
}
2024-07-03 00:42:41 +00:00
func ( r * Reader ) Restart ( ) error {
r . Stop ( )
return r . Start ( )
}
2023-11-27 21:28:16 +00:00
func ( r * Reader ) triggerWalletReload ( ) {
r . cancelDelayedWalletReload ( )
r . walletFeed . Send ( walletevent . Event {
Type : EventWalletTickReload ,
} )
}
func ( r * Reader ) triggerDelayedWalletReload ( ) {
r . cancelDelayedWalletReload ( )
r . reloadDelayTimer = time . AfterFunc ( time . Duration ( activityReloadDelay ) * time . Second , r . triggerWalletReload )
}
func ( r * Reader ) cancelDelayedWalletReload ( ) {
if r . reloadDelayTimer != nil {
r . reloadDelayTimer . Stop ( )
r . reloadDelayTimer = nil
}
}
func ( r * Reader ) startWalletEventsWatcher ( ) {
if r . walletEventsWatcher != nil {
return
}
// Respond to ETH/Token transfers
walletEventCb := func ( event walletevent . Event ) {
if event . Type != transfer . EventInternalETHTransferDetected &&
event . Type != transfer . EventInternalERC20TransferDetected {
return
}
2024-04-11 15:33:07 +00:00
for _ , address := range event . Accounts {
timestamp , ok := r . lastWalletTokenUpdateTimestamp . Load ( address )
2024-04-25 12:15:07 +00:00
timecheck := int64 ( 0 )
if ok {
timecheck = timestamp . ( int64 ) - activityReloadMarginSeconds
}
2024-02-02 10:42:56 +00:00
2024-04-11 15:33:07 +00:00
if ! ok || event . At > timecheck {
r . triggerDelayedWalletReload ( )
r . invalidateBalanceCache ( )
break
}
2024-02-02 10:42:56 +00:00
}
2023-11-27 21:28:16 +00:00
}
r . walletEventsWatcher = walletevent . NewWatcher ( r . walletFeed , walletEventCb )
r . walletEventsWatcher . Start ( )
}
func ( r * Reader ) stopWalletEventsWatcher ( ) {
if r . walletEventsWatcher != nil {
r . walletEventsWatcher . Stop ( )
r . walletEventsWatcher = nil
}
2022-12-01 09:19:32 +00:00
}
2024-04-11 15:33:07 +00:00
func ( r * Reader ) tokensCachedForAddresses ( addresses [ ] common . Address ) bool {
2024-06-06 19:57:29 +00:00
cachedTokens , err := r . getCachedWalletTokensWithoutMarketData ( )
if err != nil {
return false
}
2024-04-11 15:33:07 +00:00
2024-06-06 19:57:29 +00:00
for _ , address := range addresses {
2024-04-11 15:33:07 +00:00
_ , ok := cachedTokens [ address ]
if ! ok {
return false
}
}
return true
}
func ( r * Reader ) isCacheTimestampValidForAddress ( address common . Address ) bool {
_ , ok := r . lastWalletTokenUpdateTimestamp . Load ( address )
return ok
}
func ( r * Reader ) areCacheTimestampsValid ( addresses [ ] common . Address ) bool {
for _ , address := range addresses {
if ! r . isCacheTimestampValidForAddress ( address ) {
return false
}
}
return true
}
func ( r * Reader ) isBalanceCacheValid ( addresses [ ] common . Address ) bool {
2024-02-02 10:42:56 +00:00
r . rw . RLock ( )
defer r . rw . RUnlock ( )
2024-04-11 15:33:07 +00:00
return ! r . refreshBalanceCache && r . tokensCachedForAddresses ( addresses ) && r . areCacheTimestampsValid ( addresses )
2024-02-02 10:42:56 +00:00
}
func ( r * Reader ) balanceRefreshed ( ) {
r . rw . Lock ( )
defer r . rw . Unlock ( )
r . refreshBalanceCache = false
}
func ( r * Reader ) invalidateBalanceCache ( ) {
r . rw . Lock ( )
defer r . rw . Unlock ( )
r . refreshBalanceCache = true
}
2024-06-06 19:57:29 +00:00
func ( r * Reader ) FetchOrGetCachedWalletBalances ( ctx context . Context , clients map [ uint64 ] chain . ClientInterface , addresses [ ] common . Address ) ( map [ common . Address ] [ ] token . StorageToken , error ) {
needFetch := ! r . isBalanceCacheValid ( addresses ) || r . isBalanceUpdateNeededAnyway ( clients , addresses )
if needFetch {
2024-08-01 11:54:29 +00:00
fetchedBalances , err := r . FetchBalances ( ctx , clients , addresses )
if err == nil {
return fetchedBalances , nil
}
2024-02-02 10:42:56 +00:00
}
2024-06-06 19:57:29 +00:00
return r . GetCachedBalances ( clients , addresses )
2024-02-02 10:42:56 +00:00
}
2024-06-06 19:57:29 +00:00
func ( r * Reader ) isBalanceUpdateNeededAnyway ( clients map [ uint64 ] chain . ClientInterface , addresses [ ] common . Address ) bool {
cachedTokens , err := r . getCachedWalletTokensWithoutMarketData ( )
2024-01-03 13:54:49 +00:00
if err != nil {
2024-06-06 19:57:29 +00:00
return true
2024-01-03 13:54:49 +00:00
}
2024-06-06 19:57:29 +00:00
chainIDs := maps . Keys ( clients )
updateAnyway := false
for _ , address := range addresses {
if res , ok := cachedTokens [ address ] ; ! ok || len ( res ) == 0 {
updateAnyway = true
break
2024-01-03 13:54:49 +00:00
}
2024-06-06 19:57:29 +00:00
networkFound := map [ uint64 ] bool { }
for _ , token := range cachedTokens [ address ] {
for _ , chain := range chainIDs {
if _ , ok := token . BalancesPerChain [ chain ] ; ok {
networkFound [ chain ] = true
}
}
}
2024-01-03 13:54:49 +00:00
2024-06-06 19:57:29 +00:00
for _ , chain := range chainIDs {
if ! networkFound [ chain ] {
updateAnyway = true
return updateAnyway
}
}
2024-01-03 13:54:49 +00:00
}
2024-06-06 19:57:29 +00:00
return updateAnyway
}
2024-01-03 13:54:49 +00:00
2024-06-06 19:57:29 +00:00
func tokensToBalancesPerChain ( cachedTokens map [ common . Address ] [ ] token . StorageToken ) map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big {
cachedBalancesPerChain := map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big { }
for address , tokens := range cachedTokens {
for _ , token := range tokens {
for _ , balance := range token . BalancesPerChain {
if _ , ok := cachedBalancesPerChain [ balance . ChainID ] ; ! ok {
cachedBalancesPerChain [ balance . ChainID ] = map [ common . Address ] map [ common . Address ] * hexutil . Big { }
}
if _ , ok := cachedBalancesPerChain [ balance . ChainID ] [ address ] ; ! ok {
cachedBalancesPerChain [ balance . ChainID ] [ address ] = map [ common . Address ] * hexutil . Big { }
}
bigBalance , _ := new ( big . Int ) . SetString ( balance . RawBalance , 10 )
cachedBalancesPerChain [ balance . ChainID ] [ address ] [ balance . Address ] = ( * hexutil . Big ) ( bigBalance )
}
}
2024-01-03 13:54:49 +00:00
}
2024-06-06 19:57:29 +00:00
return cachedBalancesPerChain
}
2024-01-03 13:54:49 +00:00
2024-06-06 19:57:29 +00:00
func ( r * Reader ) fetchBalances ( ctx context . Context , clients map [ uint64 ] chain . ClientInterface , addresses [ ] common . Address , tokenAddresses [ ] common . Address ) ( map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big , error ) {
latestBalances , err := r . tokenManager . GetBalancesByChain ( ctx , clients , addresses , tokenAddresses )
2024-01-03 13:54:49 +00:00
if err != nil {
2024-06-06 19:57:29 +00:00
log . Error ( "tokenManager.GetBalancesByChain error" , "err" , err )
2024-01-03 13:54:49 +00:00
return nil , err
}
2024-06-06 19:57:29 +00:00
return latestBalances , nil
}
2024-03-15 12:37:00 +00:00
2024-06-06 19:57:29 +00:00
func toChainBalance (
balances map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big ,
tok * token . Token ,
address common . Address ,
decimals uint ,
cachedTokens map [ common . Address ] [ ] token . StorageToken ,
hasError bool ,
isMandatoryToken bool ,
) * token . ChainBalance {
hexBalance := & big . Int { }
if balances != nil {
hexBalance = balances [ tok . ChainID ] [ address ] [ tok . Address ] . ToInt ( )
}
balance := big . NewFloat ( 0.0 )
if hexBalance != nil {
balance = new ( big . Float ) . Quo (
new ( big . Float ) . SetInt ( hexBalance ) ,
big . NewFloat ( math . Pow ( 10 , float64 ( decimals ) ) ) ,
)
}
isVisible := balance . Cmp ( big . NewFloat ( 0.0 ) ) > 0 || isCachedToken ( cachedTokens , address , tok . Symbol , tok . ChainID )
if ! isVisible && ! isMandatoryToken {
return nil
2024-03-11 13:48:40 +00:00
}
2024-02-15 11:01:49 +00:00
2024-06-06 19:57:29 +00:00
return & token . ChainBalance {
RawBalance : hexBalance . String ( ) ,
Balance : balance ,
Balance1DayAgo : "0" ,
Address : tok . Address ,
ChainID : tok . ChainID ,
HasError : hasError ,
2024-02-15 11:01:49 +00:00
}
2024-06-06 19:57:29 +00:00
}
2024-02-15 11:01:49 +00:00
2024-06-06 19:57:29 +00:00
func ( r * Reader ) getBalance1DayAgo ( balance * token . ChainBalance , dayAgoTimestamp int64 , symbol string , address common . Address ) ( * big . Int , error ) {
balance1DayAgo , err := r . tokenManager . GetTokenHistoricalBalance ( address , balance . ChainID , symbol , dayAgoTimestamp )
if err != nil {
log . Error ( "tokenManager.GetTokenHistoricalBalance error" , "err" , err )
return nil , err
2024-01-03 13:54:49 +00:00
}
2024-06-06 19:57:29 +00:00
return balance1DayAgo , nil
}
func ( r * Reader ) balancesToTokensByAddress ( connectedPerChain map [ uint64 ] bool , addresses [ ] common . Address , allTokens [ ] * token . Token , balances map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big , cachedTokens map [ common . Address ] [ ] token . StorageToken ) map [ common . Address ] [ ] token . StorageToken {
verifiedTokens , unverifiedTokens := splitVerifiedTokens ( allTokens )
result := make ( map [ common . Address ] [ ] token . StorageToken )
2024-03-08 12:52:39 +00:00
dayAgoTimestamp := time . Now ( ) . Add ( - 24 * time . Hour ) . Unix ( )
2024-01-03 13:54:49 +00:00
for _ , address := range addresses {
for _ , tokenList := range [ ] [ ] * token . Token { verifiedTokens , unverifiedTokens } {
for symbol , tokens := range getTokenBySymbols ( tokenList ) {
2024-06-06 19:57:29 +00:00
balancesPerChain := r . createBalancePerChainPerSymbol ( address , balances , tokens , cachedTokens , connectedPerChain , dayAgoTimestamp )
if balancesPerChain == nil {
2024-01-03 13:54:49 +00:00
continue
}
2024-06-06 19:57:29 +00:00
walletToken := token . StorageToken {
Token : token . Token {
Name : tokens [ 0 ] . Name ,
Symbol : symbol ,
Decimals : tokens [ 0 ] . Decimals ,
PegSymbol : token . GetTokenPegSymbol ( symbol ) ,
Verified : tokens [ 0 ] . Verified ,
CommunityData : tokens [ 0 ] . CommunityData ,
Image : tokens [ 0 ] . Image ,
} ,
2024-01-03 13:54:49 +00:00
BalancesPerChain : balancesPerChain ,
}
result [ address ] = append ( result [ address ] , walletToken )
}
}
}
2024-06-06 19:57:29 +00:00
return result
2024-01-03 13:54:49 +00:00
}
2024-06-06 19:57:29 +00:00
// For tokens with single symbol, create a chain balance for each chain
func ( r * Reader ) createBalancePerChainPerSymbol (
address common . Address ,
balances map [ uint64 ] map [ common . Address ] map [ common . Address ] * hexutil . Big ,
tokens [ ] * token . Token ,
cachedTokens map [ common . Address ] [ ] token . StorageToken ,
clientConnectionPerChain map [ uint64 ] bool ,
dayAgoTimestamp int64 ,
) map [ uint64 ] token . ChainBalance {
var balancesPerChain map [ uint64 ] token . ChainBalance
decimals := tokens [ 0 ] . Decimals
isMandatoryToken := belongsToMandatoryTokens ( tokens [ 0 ] . Symbol ) // we expect all tokens in the list to have the same symbol
for _ , tok := range tokens {
hasError := false
if connected , ok := clientConnectionPerChain [ tok . ChainID ] ; ok {
hasError = ! connected
}
2024-02-15 11:01:49 +00:00
2024-06-06 19:57:29 +00:00
// TODO: Avoid passing the entire balances map to toChainBalance. Iterate over the balances map once and pass the balance per address per token to toChainBalance
balance := toChainBalance ( balances , tok , address , decimals , cachedTokens , hasError , isMandatoryToken )
if balance != nil {
balance1DayAgo , _ := r . getBalance1DayAgo ( balance , dayAgoTimestamp , tok . Symbol , address ) // Ignore error
if balance1DayAgo != nil {
balance . Balance1DayAgo = balance1DayAgo . String ( )
}
2023-05-22 08:55:33 +00:00
2024-06-06 19:57:29 +00:00
if balancesPerChain == nil {
balancesPerChain = make ( map [ uint64 ] token . ChainBalance )
}
balancesPerChain [ tok . ChainID ] = * balance
2023-05-22 08:55:33 +00:00
}
}
2022-05-10 07:48:05 +00:00
2024-06-06 19:57:29 +00:00
return balancesPerChain
}
func ( r * Reader ) GetWalletToken ( ctx context . Context , clients map [ uint64 ] chain . ClientInterface , addresses [ ] common . Address , currency string ) ( map [ common . Address ] [ ] token . StorageToken , error ) {
cachedTokens , err := r . getCachedWalletTokensWithoutMarketData ( )
2023-12-21 14:12:50 +00:00
if err != nil {
return nil , err
}
2024-06-06 19:57:29 +00:00
chainIDs := maps . Keys ( clients )
2022-05-10 07:48:05 +00:00
2023-01-12 17:54:14 +00:00
currencies := make ( [ ] string , 0 )
currencies = append ( currencies , currency )
currencies = append ( currencies , getFixedCurrencies ( ) ... )
2023-10-17 15:05:05 +00:00
allTokens , err := r . tokenManager . GetTokensByChainIDs ( chainIDs )
2022-05-10 07:48:05 +00:00
if err != nil {
return nil , err
}
2022-10-27 07:38:05 +00:00
tokenAddresses := getTokenAddresses ( allTokens )
2023-09-12 07:45:24 +00:00
balances , err := r . tokenManager . GetBalancesByChain ( ctx , clients , addresses , tokenAddresses )
if err != nil {
log . Info ( "tokenManager.GetBalancesByChain error" , "err" , err )
return nil , err
2022-10-27 07:38:05 +00:00
}
2023-09-12 07:45:24 +00:00
2023-08-31 07:47:24 +00:00
verifiedTokens , unverifiedTokens := splitVerifiedTokens ( allTokens )
2023-09-12 07:45:24 +00:00
tokenSymbols := make ( [ ] string , 0 )
2024-06-06 19:57:29 +00:00
result := make ( map [ common . Address ] [ ] token . StorageToken )
2023-08-31 07:47:24 +00:00
2022-12-02 10:55:44 +00:00
for _ , address := range addresses {
2023-08-31 07:47:24 +00:00
for _ , tokenList := range [ ] [ ] * token . Token { verifiedTokens , unverifiedTokens } {
for symbol , tokens := range getTokenBySymbols ( tokenList ) {
2024-06-06 19:57:29 +00:00
balancesPerChain := make ( map [ uint64 ] token . ChainBalance )
2023-08-31 07:47:24 +00:00
decimals := tokens [ 0 ] . Decimals
2023-12-21 14:12:50 +00:00
isVisible := false
2024-06-06 19:57:29 +00:00
for _ , tok := range tokens {
hexBalance := balances [ tok . ChainID ] [ address ] [ tok . Address ]
2023-08-31 07:47:24 +00:00
balance := big . NewFloat ( 0.0 )
if hexBalance != nil {
balance = new ( big . Float ) . Quo (
new ( big . Float ) . SetInt ( hexBalance . ToInt ( ) ) ,
big . NewFloat ( math . Pow ( 10 , float64 ( decimals ) ) ) ,
)
}
hasError := false
2024-06-06 19:57:29 +00:00
if client , ok := clients [ tok . ChainID ] ; ok {
2024-05-20 11:21:21 +00:00
hasError = err != nil || ! client . IsConnected ( )
2023-08-31 07:47:24 +00:00
}
2023-12-21 14:12:50 +00:00
if ! isVisible {
2024-06-06 19:57:29 +00:00
isVisible = balance . Cmp ( big . NewFloat ( 0.0 ) ) > 0 || isCachedToken ( cachedTokens , address , tok . Symbol , tok . ChainID )
2023-08-31 07:47:24 +00:00
}
2024-06-06 19:57:29 +00:00
balancesPerChain [ tok . ChainID ] = token . ChainBalance {
2023-09-20 10:48:08 +00:00
RawBalance : hexBalance . ToInt ( ) . String ( ) ,
Balance : balance ,
2024-06-06 19:57:29 +00:00
Address : tok . Address ,
ChainID : tok . ChainID ,
2023-09-20 10:48:08 +00:00
HasError : hasError ,
2023-08-31 07:47:24 +00:00
}
2023-04-06 14:59:17 +00:00
}
2023-08-31 07:47:24 +00:00
2023-12-21 14:12:50 +00:00
if ! isVisible && ! belongsToMandatoryTokens ( symbol ) {
2023-08-31 07:47:24 +00:00
continue
2022-10-27 07:38:05 +00:00
}
2024-06-06 19:57:29 +00:00
walletToken := token . StorageToken {
Token : token . Token {
Name : tokens [ 0 ] . Name ,
Symbol : symbol ,
Decimals : decimals ,
PegSymbol : token . GetTokenPegSymbol ( symbol ) ,
Verified : tokens [ 0 ] . Verified ,
CommunityData : tokens [ 0 ] . CommunityData ,
Image : tokens [ 0 ] . Image ,
} ,
2023-09-12 07:45:24 +00:00
BalancesPerChain : balancesPerChain ,
2023-01-12 17:54:14 +00:00
}
2023-09-12 07:45:24 +00:00
tokenSymbols = append ( tokenSymbols , symbol )
2023-08-31 07:47:24 +00:00
result [ address ] = append ( result [ address ] , walletToken )
2022-10-27 07:38:05 +00:00
}
2022-05-10 07:48:05 +00:00
}
}
2023-04-25 12:00:17 +00:00
2023-09-12 07:45:24 +00:00
var (
group = async . NewAtomicGroup ( ctx )
prices = map [ string ] map [ string ] float64 { }
tokenDetails = map [ string ] thirdparty . TokenDetails { }
tokenMarketValues = map [ string ] thirdparty . TokenMarketValues { }
)
group . Add ( func ( parent context . Context ) error {
prices , err = r . marketManager . FetchPrices ( tokenSymbols , currencies )
if err != nil {
log . Info ( "marketManager.FetchPrices err" , err )
}
return nil
} )
group . Add ( func ( parent context . Context ) error {
tokenDetails , err = r . marketManager . FetchTokenDetails ( tokenSymbols )
if err != nil {
log . Info ( "marketManager.FetchTokenDetails err" , err )
}
return nil
} )
group . Add ( func ( parent context . Context ) error {
tokenMarketValues , err = r . marketManager . FetchTokenMarketValues ( tokenSymbols , currency )
if err != nil {
log . Info ( "marketManager.FetchTokenMarketValues err" , err )
}
return nil
} )
select {
case <- group . WaitAsync ( ) :
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
}
err = group . Error ( )
if err != nil {
return nil , err
}
for address , tokens := range result {
2024-06-06 19:57:29 +00:00
for index , tok := range tokens {
marketValuesPerCurrency := make ( map [ string ] token . TokenMarketValues )
2023-09-12 07:45:24 +00:00
for _ , currency := range currencies {
2024-06-06 19:57:29 +00:00
if _ , ok := tokenMarketValues [ tok . Symbol ] ; ! ok {
2023-09-12 07:45:24 +00:00
continue
}
2024-06-06 19:57:29 +00:00
marketValuesPerCurrency [ currency ] = token . TokenMarketValues {
MarketCap : tokenMarketValues [ tok . Symbol ] . MKTCAP ,
HighDay : tokenMarketValues [ tok . Symbol ] . HIGHDAY ,
LowDay : tokenMarketValues [ tok . Symbol ] . LOWDAY ,
ChangePctHour : tokenMarketValues [ tok . Symbol ] . CHANGEPCTHOUR ,
ChangePctDay : tokenMarketValues [ tok . Symbol ] . CHANGEPCTDAY ,
ChangePct24hour : tokenMarketValues [ tok . Symbol ] . CHANGEPCT24HOUR ,
Change24hour : tokenMarketValues [ tok . Symbol ] . CHANGE24HOUR ,
Price : prices [ tok . Symbol ] [ currency ] ,
2023-09-12 07:45:24 +00:00
HasError : ! r . marketManager . IsConnected ,
}
}
2024-06-06 19:57:29 +00:00
if _ , ok := tokenDetails [ tok . Symbol ] ; ! ok {
2023-09-12 07:45:24 +00:00
continue
}
2024-06-06 19:57:29 +00:00
result [ address ] [ index ] . Description = tokenDetails [ tok . Symbol ] . Description
result [ address ] [ index ] . AssetWebsiteURL = tokenDetails [ tok . Symbol ] . AssetWebsiteURL
result [ address ] [ index ] . BuiltOn = tokenDetails [ tok . Symbol ] . BuiltOn
2023-09-12 07:45:24 +00:00
result [ address ] [ index ] . MarketValuesPerCurrency = marketValuesPerCurrency
}
}
2024-04-11 15:33:07 +00:00
r . updateTokenUpdateTimestamp ( addresses )
2023-11-27 21:28:16 +00:00
2023-04-25 12:00:17 +00:00
return result , r . persistence . SaveTokens ( result )
}
2024-06-06 19:57:29 +00:00
func isCachedToken ( cachedTokens map [ common . Address ] [ ] token . StorageToken , address common . Address , symbol string , chainID uint64 ) bool {
2023-12-21 14:12:50 +00:00
if tokens , ok := cachedTokens [ address ] ; ok {
for _ , t := range tokens {
if t . Symbol != symbol {
continue
}
_ , ok := t . BalancesPerChain [ chainID ]
if ok {
return true
}
}
}
return false
}
2024-06-06 19:57:29 +00:00
// getCachedWalletTokensWithoutMarketData returns the latest fetched balances, minus
2023-04-25 12:00:17 +00:00
// price information
2024-06-06 19:57:29 +00:00
func ( r * Reader ) getCachedWalletTokensWithoutMarketData ( ) ( map [ common . Address ] [ ] token . StorageToken , error ) {
2023-04-25 12:00:17 +00:00
return r . persistence . GetTokens ( )
2022-05-10 07:48:05 +00:00
}
2024-04-11 15:33:07 +00:00
func ( r * Reader ) updateTokenUpdateTimestamp ( addresses [ ] common . Address ) {
for _ , address := range addresses {
r . lastWalletTokenUpdateTimestamp . Store ( address , time . Now ( ) . Unix ( ) )
}
}
2024-06-06 19:57:29 +00:00
func ( r * Reader ) FetchBalances ( ctx context . Context , clients map [ uint64 ] chain . ClientInterface , addresses [ ] common . Address ) ( map [ common . Address ] [ ] token . StorageToken , error ) {
cachedTokens , err := r . getCachedWalletTokensWithoutMarketData ( )
if err != nil {
return nil , err
}
chainIDs := maps . Keys ( clients )
allTokens , err := r . tokenManager . GetTokensByChainIDs ( chainIDs )
if err != nil {
return nil , err
}
connectedPerChain := map [ uint64 ] bool { }
for chainID , client := range clients {
connectedPerChain [ chainID ] = client . IsConnected ( )
}
tokenAddresses := getTokenAddresses ( allTokens )
balances , err := r . fetchBalances ( ctx , clients , addresses , tokenAddresses )
if err != nil {
log . Error ( "failed to update balances" , "err" , err )
return nil , err
}
tokens := r . balancesToTokensByAddress ( connectedPerChain , addresses , allTokens , balances , cachedTokens )
err = r . persistence . SaveTokens ( tokens )
if err != nil {
log . Error ( "failed to save tokens" , "err" , err ) // Do not return error, as it is not critical
}
r . updateTokenUpdateTimestamp ( addresses )
r . balanceRefreshed ( )
return tokens , err
}
func ( r * Reader ) GetCachedBalances ( clients map [ uint64 ] chain . ClientInterface , addresses [ ] common . Address ) ( map [ common . Address ] [ ] token . StorageToken , error ) {
cachedTokens , err := r . getCachedWalletTokensWithoutMarketData ( )
if err != nil {
return nil , err
}
chainIDs := maps . Keys ( clients )
allTokens , err := r . tokenManager . GetTokensByChainIDs ( chainIDs )
if err != nil {
return nil , err
}
connectedPerChain := map [ uint64 ] bool { }
for chainID , client := range clients {
connectedPerChain [ chainID ] = client . IsConnected ( )
}
balances := tokensToBalancesPerChain ( cachedTokens )
return r . balancesToTokensByAddress ( connectedPerChain , addresses , allTokens , balances , cachedTokens ) , nil
}