github.com/status-im/status-go@v1.1.0/services/wallet/token/token.go (about)

     1  package token
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"math/big"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/ethereum/go-ethereum/common/hexutil"
    18  	"github.com/ethereum/go-ethereum/event"
    19  	"github.com/ethereum/go-ethereum/log"
    20  	"github.com/status-im/status-go/contracts"
    21  	"github.com/status-im/status-go/contracts/community-tokens/assets"
    22  	eth_node_types "github.com/status-im/status-go/eth-node/types"
    23  	"github.com/status-im/status-go/multiaccounts/accounts"
    24  	"github.com/status-im/status-go/params"
    25  	"github.com/status-im/status-go/protocol/communities/token"
    26  	"github.com/status-im/status-go/rpc"
    27  	"github.com/status-im/status-go/rpc/network"
    28  	"github.com/status-im/status-go/server"
    29  	"github.com/status-im/status-go/services/accounts/accountsevent"
    30  	"github.com/status-im/status-go/services/communitytokens/communitytokensdatabase"
    31  	"github.com/status-im/status-go/services/utils"
    32  	"github.com/status-im/status-go/services/wallet/bigint"
    33  	"github.com/status-im/status-go/services/wallet/community"
    34  	"github.com/status-im/status-go/services/wallet/token/balancefetcher"
    35  	"github.com/status-im/status-go/services/wallet/walletevent"
    36  )
    37  
    38  const (
    39  	EventCommunityTokenReceived walletevent.EventType = "wallet-community-token-received"
    40  )
    41  
    42  type Token struct {
    43  	Address common.Address `json:"address"`
    44  	Name    string         `json:"name"`
    45  	Symbol  string         `json:"symbol"`
    46  	// Decimals defines how divisible the token is. For example, 0 would be
    47  	// indivisible, whereas 18 would allow very small amounts of the token
    48  	// to be traded.
    49  	Decimals uint   `json:"decimals"`
    50  	ChainID  uint64 `json:"chainId"`
    51  	// PegSymbol indicates that the token is pegged to some fiat currency, using the
    52  	// ISO 4217 alphabetic code. For example, an empty string means it is not
    53  	// pegged, while "USD" means it's pegged to the United States Dollar.
    54  	PegSymbol string `json:"pegSymbol"`
    55  	Image     string `json:"image,omitempty"`
    56  
    57  	CommunityData *community.Data `json:"community_data,omitempty"`
    58  	Verified      bool            `json:"verified"`
    59  	TokenListID   string          `json:"tokenListId"`
    60  }
    61  
    62  type ReceivedToken struct {
    63  	Token
    64  	Amount  float64     `json:"amount"`
    65  	TxHash  common.Hash `json:"txHash"`
    66  	IsFirst bool        `json:"isFirst"`
    67  }
    68  
    69  func (t *Token) IsNative() bool {
    70  	return strings.EqualFold(t.Symbol, "ETH")
    71  }
    72  
    73  type List struct {
    74  	Name    string   `json:"name"`
    75  	Tokens  []*Token `json:"tokens"`
    76  	Source  string   `json:"source"`
    77  	Version string   `json:"version"`
    78  }
    79  
    80  type ListWrapper struct {
    81  	UpdatedAt int64   `json:"updatedAt"`
    82  	Data      []*List `json:"data"`
    83  }
    84  
    85  type addressTokenMap = map[common.Address]*Token
    86  type storeMap = map[uint64]addressTokenMap
    87  
    88  type ManagerInterface interface {
    89  	balancefetcher.BalanceFetcher
    90  	LookupTokenIdentity(chainID uint64, address common.Address, native bool) *Token
    91  	LookupToken(chainID *uint64, tokenSymbol string) (token *Token, isNative bool)
    92  	GetTokenHistoricalBalance(account common.Address, chainID uint64, symbol string, timestamp int64) (*big.Int, error)
    93  	GetTokensByChainIDs(chainIDs []uint64) ([]*Token, error)
    94  }
    95  
    96  // Manager is used for accessing token store. It changes the token store based on overridden tokens
    97  type Manager struct {
    98  	balancefetcher.BalanceFetcher
    99  	db                   *sql.DB
   100  	RPCClient            rpc.ClientInterface
   101  	ContractMaker        *contracts.ContractMaker
   102  	networkManager       network.ManagerInterface
   103  	stores               []store // Set on init, not changed afterwards
   104  	communityTokensDB    *communitytokensdatabase.Database
   105  	communityManager     *community.Manager
   106  	mediaServer          *server.MediaServer
   107  	walletFeed           *event.Feed
   108  	accountFeed          *event.Feed
   109  	accountWatcher       *accountsevent.Watcher
   110  	accountsDB           *accounts.Database
   111  	tokenBalancesStorage TokenBalancesStorage
   112  
   113  	tokens []*Token
   114  
   115  	tokenLock sync.RWMutex
   116  }
   117  
   118  func mergeTokens(sliceLists [][]*Token) []*Token {
   119  	allKeys := make(map[string]bool)
   120  	res := []*Token{}
   121  	for _, list := range sliceLists {
   122  		for _, token := range list {
   123  			key := strconv.FormatUint(token.ChainID, 10) + token.Address.String()
   124  			if _, value := allKeys[key]; !value {
   125  				allKeys[key] = true
   126  				res = append(res, token)
   127  			}
   128  		}
   129  	}
   130  	return res
   131  }
   132  
   133  func prepareTokens(networkManager network.ManagerInterface, stores []store) []*Token {
   134  	tokens := make([]*Token, 0)
   135  
   136  	networks, err := networkManager.GetAll()
   137  	if err != nil {
   138  		return nil
   139  	}
   140  
   141  	for _, store := range stores {
   142  		validTokens := make([]*Token, 0)
   143  		for _, token := range store.GetTokens() {
   144  			token.Verified = true
   145  
   146  			for _, network := range networks {
   147  				if network.ChainID == token.ChainID {
   148  					validTokens = append(validTokens, token)
   149  					break
   150  				}
   151  			}
   152  		}
   153  
   154  		tokens = mergeTokens([][]*Token{tokens, validTokens})
   155  	}
   156  	return tokens
   157  }
   158  
   159  func NewTokenManager(
   160  	db *sql.DB,
   161  	RPCClient rpc.ClientInterface,
   162  	communityManager *community.Manager,
   163  	networkManager network.ManagerInterface,
   164  	appDB *sql.DB,
   165  	mediaServer *server.MediaServer,
   166  	walletFeed *event.Feed,
   167  	accountFeed *event.Feed,
   168  	accountsDB *accounts.Database,
   169  	tokenBalancesStorage TokenBalancesStorage,
   170  ) *Manager {
   171  	maker, _ := contracts.NewContractMaker(RPCClient)
   172  	stores := []store{newUniswapStore(), newDefaultStore()}
   173  	tokens := prepareTokens(networkManager, stores)
   174  
   175  	return &Manager{
   176  		BalanceFetcher:       balancefetcher.NewDefaultBalanceFetcher(maker),
   177  		db:                   db,
   178  		RPCClient:            RPCClient,
   179  		ContractMaker:        maker,
   180  		networkManager:       networkManager,
   181  		communityManager:     communityManager,
   182  		stores:               stores,
   183  		communityTokensDB:    communitytokensdatabase.NewCommunityTokensDatabase(appDB),
   184  		tokens:               tokens,
   185  		mediaServer:          mediaServer,
   186  		walletFeed:           walletFeed,
   187  		accountFeed:          accountFeed,
   188  		accountsDB:           accountsDB,
   189  		tokenBalancesStorage: tokenBalancesStorage,
   190  	}
   191  }
   192  
   193  func (tm *Manager) Start() {
   194  	tm.startAccountsWatcher()
   195  }
   196  
   197  func (tm *Manager) startAccountsWatcher() {
   198  	if tm.accountWatcher != nil {
   199  		return
   200  	}
   201  
   202  	tm.accountWatcher = accountsevent.NewWatcher(tm.accountsDB, tm.accountFeed, tm.onAccountsChange)
   203  	tm.accountWatcher.Start()
   204  }
   205  
   206  func (tm *Manager) Stop() {
   207  	tm.stopAccountsWatcher()
   208  }
   209  
   210  func (tm *Manager) stopAccountsWatcher() {
   211  	if tm.accountWatcher != nil {
   212  		tm.accountWatcher.Stop()
   213  		tm.accountWatcher = nil
   214  	}
   215  }
   216  
   217  // overrideTokensInPlace overrides tokens in the store with the ones from the networks
   218  // BEWARE: overridden tokens will have their original address removed and replaced by the one in networks
   219  func overrideTokensInPlace(networks []params.Network, tokens []*Token) {
   220  	for _, network := range networks {
   221  		if len(network.TokenOverrides) == 0 {
   222  			continue
   223  		}
   224  
   225  		for _, overrideToken := range network.TokenOverrides {
   226  			for _, token := range tokens {
   227  				if token.Symbol == overrideToken.Symbol {
   228  					token.Address = overrideToken.Address
   229  				}
   230  			}
   231  		}
   232  	}
   233  }
   234  
   235  func (tm *Manager) getTokens() []*Token {
   236  	tm.tokenLock.RLock()
   237  	defer tm.tokenLock.RUnlock()
   238  
   239  	return tm.tokens
   240  }
   241  
   242  func (tm *Manager) SetTokens(tokens []*Token) {
   243  	tm.tokenLock.Lock()
   244  	defer tm.tokenLock.Unlock()
   245  	tm.tokens = tokens
   246  }
   247  
   248  func (tm *Manager) FindToken(network *params.Network, tokenSymbol string) *Token {
   249  	if tokenSymbol == network.NativeCurrencySymbol {
   250  		return tm.ToToken(network)
   251  	}
   252  
   253  	return tm.GetToken(network.ChainID, tokenSymbol)
   254  }
   255  
   256  func (tm *Manager) LookupToken(chainID *uint64, tokenSymbol string) (token *Token, isNative bool) {
   257  	if chainID == nil {
   258  		networks, err := tm.networkManager.Get(false)
   259  		if err != nil {
   260  			return nil, false
   261  		}
   262  
   263  		for _, network := range networks {
   264  			if tokenSymbol == network.NativeCurrencySymbol {
   265  				return tm.ToToken(network), true
   266  			}
   267  			token := tm.GetToken(network.ChainID, tokenSymbol)
   268  			if token != nil {
   269  				return token, false
   270  			}
   271  		}
   272  	} else {
   273  		network := tm.networkManager.Find(*chainID)
   274  		if network != nil && tokenSymbol == network.NativeCurrencySymbol {
   275  			return tm.ToToken(network), true
   276  		}
   277  		return tm.GetToken(*chainID, tokenSymbol), false
   278  	}
   279  	return nil, false
   280  }
   281  
   282  // GetToken returns token by chainID and tokenSymbol. Use ToToken for native token
   283  func (tm *Manager) GetToken(chainID uint64, tokenSymbol string) *Token {
   284  	allTokens, err := tm.GetTokens(chainID)
   285  	if err != nil {
   286  		return nil
   287  	}
   288  	for _, token := range allTokens {
   289  		if token.Symbol == tokenSymbol {
   290  			return token
   291  		}
   292  	}
   293  	return nil
   294  }
   295  
   296  func (tm *Manager) LookupTokenIdentity(chainID uint64, address common.Address, native bool) *Token {
   297  	network := tm.networkManager.Find(chainID)
   298  	if native {
   299  		return tm.ToToken(network)
   300  	}
   301  
   302  	return tm.FindTokenByAddress(chainID, address)
   303  }
   304  
   305  func (tm *Manager) FindTokenByAddress(chainID uint64, address common.Address) *Token {
   306  	allTokens, err := tm.GetTokens(chainID)
   307  	if err != nil {
   308  		return nil
   309  	}
   310  	for _, token := range allTokens {
   311  		if token.Address == address {
   312  			return token
   313  		}
   314  	}
   315  
   316  	return nil
   317  }
   318  
   319  func (tm *Manager) FindOrCreateTokenByAddress(ctx context.Context, chainID uint64, address common.Address) *Token {
   320  	// If token comes datasource, simply returns it
   321  	for _, token := range tm.getTokens() {
   322  		if token.ChainID != chainID {
   323  			continue
   324  		}
   325  		if token.Address == address {
   326  			return token
   327  		}
   328  	}
   329  
   330  	// Create custom token if not known or try to link with a community
   331  	customTokens, err := tm.GetCustoms(false)
   332  	if err != nil {
   333  		return nil
   334  	}
   335  
   336  	for _, token := range customTokens {
   337  		if token.Address == address {
   338  			tm.discoverTokenCommunityID(ctx, token, address)
   339  			return token
   340  		}
   341  	}
   342  
   343  	token, err := tm.DiscoverToken(ctx, chainID, address)
   344  	if err != nil {
   345  		return nil
   346  	}
   347  
   348  	err = tm.UpsertCustom(*token)
   349  	if err != nil {
   350  		return nil
   351  	}
   352  
   353  	tm.discoverTokenCommunityID(ctx, token, address)
   354  	return token
   355  }
   356  
   357  func (tm *Manager) MarkAsPreviouslyOwnedToken(token *Token, owner common.Address) (bool, error) {
   358  	log.Info("Marking token as previously owned", "token", token, "owner", owner)
   359  	if token == nil {
   360  		return false, errors.New("token is nil")
   361  	}
   362  	if (owner == common.Address{}) {
   363  		return false, errors.New("owner is nil")
   364  	}
   365  
   366  	tokens, err := tm.tokenBalancesStorage.GetTokens()
   367  	if err != nil {
   368  		return false, err
   369  	}
   370  
   371  	if tokens[owner] == nil {
   372  		tokens[owner] = make([]StorageToken, 0)
   373  	} else {
   374  		for _, t := range tokens[owner] {
   375  			if t.Address == token.Address && t.ChainID == token.ChainID && t.Symbol == token.Symbol {
   376  				log.Info("Token already marked as previously owned", "token", token, "owner", owner)
   377  				return false, nil
   378  			}
   379  		}
   380  	}
   381  
   382  	// append token to the list of tokens
   383  	tokens[owner] = append(tokens[owner], StorageToken{
   384  		Token: *token,
   385  		BalancesPerChain: map[uint64]ChainBalance{
   386  			token.ChainID: {
   387  				RawBalance: "0",
   388  				Balance:    &big.Float{},
   389  				Address:    token.Address,
   390  				ChainID:    token.ChainID,
   391  			},
   392  		},
   393  	})
   394  
   395  	// save the updated list of tokens
   396  	err = tm.tokenBalancesStorage.SaveTokens(tokens)
   397  	if err != nil {
   398  		return false, err
   399  	}
   400  
   401  	return true, nil
   402  }
   403  
   404  func (tm *Manager) discoverTokenCommunityID(ctx context.Context, token *Token, address common.Address) {
   405  	if token == nil || token.CommunityData != nil {
   406  		// Token is invalid or is alrady discovered. Nothing to do here.
   407  		return
   408  	}
   409  	backend, err := tm.RPCClient.EthClient(token.ChainID)
   410  	if err != nil {
   411  		return
   412  	}
   413  	caller, err := assets.NewAssetsCaller(address, backend)
   414  	if err != nil {
   415  		return
   416  	}
   417  	uri, err := caller.BaseTokenURI(&bind.CallOpts{
   418  		Context: ctx,
   419  	})
   420  	if err != nil {
   421  		return
   422  	}
   423  
   424  	update, err := tm.db.Prepare("UPDATE tokens SET community_id=? WHERE network_id=? AND address=?")
   425  	if err != nil {
   426  		log.Error("Cannot prepare token update query", err)
   427  		return
   428  	}
   429  
   430  	if uri == "" {
   431  		// Update token community ID to prevent further checks
   432  		_, err := update.Exec("", token.ChainID, token.Address)
   433  		if err != nil {
   434  			log.Error("Cannot update community id", err)
   435  		}
   436  		return
   437  	}
   438  
   439  	uri = strings.TrimSuffix(uri, "/")
   440  	communityIDHex, err := utils.DeserializePublicKey(uri)
   441  	if err != nil {
   442  		return
   443  	}
   444  	communityID := eth_node_types.EncodeHex(communityIDHex)
   445  
   446  	token.CommunityData = &community.Data{
   447  		ID: communityID,
   448  	}
   449  
   450  	_, err = update.Exec(communityID, token.ChainID, token.Address)
   451  	if err != nil {
   452  		log.Error("Cannot update community id", err)
   453  	}
   454  }
   455  
   456  func (tm *Manager) FindSNT(chainID uint64) *Token {
   457  	tokens, err := tm.GetTokens(chainID)
   458  	if err != nil {
   459  		return nil
   460  	}
   461  
   462  	for _, token := range tokens {
   463  		if token.Symbol == "SNT" || token.Symbol == "STT" {
   464  			return token
   465  		}
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  func (tm *Manager) getNativeTokens() ([]*Token, error) {
   472  	tokens := make([]*Token, 0)
   473  	networks, err := tm.networkManager.Get(false)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  
   478  	for _, network := range networks {
   479  		tokens = append(tokens, tm.ToToken(network))
   480  	}
   481  
   482  	return tokens, nil
   483  }
   484  
   485  func (tm *Manager) GetAllTokens() ([]*Token, error) {
   486  	allTokens, err := tm.GetCustoms(true)
   487  	if err != nil {
   488  		log.Error("can't fetch custom tokens", "error", err)
   489  	}
   490  
   491  	allTokens = append(tm.getTokens(), allTokens...)
   492  
   493  	overrideTokensInPlace(tm.networkManager.GetConfiguredNetworks(), allTokens)
   494  
   495  	native, err := tm.getNativeTokens()
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  
   500  	allTokens = append(allTokens, native...)
   501  
   502  	return allTokens, nil
   503  }
   504  
   505  func (tm *Manager) GetTokens(chainID uint64) ([]*Token, error) {
   506  	tokens, err := tm.GetAllTokens()
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	res := make([]*Token, 0)
   512  
   513  	for _, token := range tokens {
   514  		if token.ChainID == chainID {
   515  			res = append(res, token)
   516  		}
   517  	}
   518  
   519  	return res, nil
   520  }
   521  
   522  func (tm *Manager) GetTokensByChainIDs(chainIDs []uint64) ([]*Token, error) {
   523  	tokens, err := tm.GetAllTokens()
   524  	if err != nil {
   525  		return nil, err
   526  	}
   527  
   528  	res := make([]*Token, 0)
   529  
   530  	for _, token := range tokens {
   531  		for _, chainID := range chainIDs {
   532  			if token.ChainID == chainID {
   533  				res = append(res, token)
   534  			}
   535  		}
   536  	}
   537  
   538  	return res, nil
   539  }
   540  
   541  func (tm *Manager) GetList() *ListWrapper {
   542  	data := make([]*List, 0)
   543  	nativeTokens, err := tm.getNativeTokens()
   544  	if err == nil {
   545  		data = append(data, &List{
   546  			Name:    "native",
   547  			Tokens:  nativeTokens,
   548  			Source:  "native",
   549  			Version: "1.0.0",
   550  		})
   551  	}
   552  
   553  	customTokens, err := tm.GetCustoms(true)
   554  	if err == nil && len(customTokens) > 0 {
   555  		data = append(data, &List{
   556  			Name:    "custom",
   557  			Tokens:  customTokens,
   558  			Source:  "custom",
   559  			Version: "1.0.0",
   560  		})
   561  	}
   562  
   563  	updatedAt := time.Now().Unix()
   564  	for _, store := range tm.stores {
   565  		updatedAt = store.GetUpdatedAt()
   566  		data = append(data, &List{
   567  			Name:    store.GetName(),
   568  			Tokens:  store.GetTokens(),
   569  			Source:  store.GetSource(),
   570  			Version: store.GetVersion(),
   571  		})
   572  	}
   573  	return &ListWrapper{
   574  		Data:      data,
   575  		UpdatedAt: updatedAt,
   576  	}
   577  }
   578  
   579  func (tm *Manager) DiscoverToken(ctx context.Context, chainID uint64, address common.Address) (*Token, error) {
   580  	caller, err := tm.ContractMaker.NewERC20(chainID, address)
   581  	if err != nil {
   582  		return nil, err
   583  	}
   584  
   585  	name, err := caller.Name(&bind.CallOpts{
   586  		Context: ctx,
   587  	})
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  
   592  	symbol, err := caller.Symbol(&bind.CallOpts{
   593  		Context: ctx,
   594  	})
   595  	if err != nil {
   596  		return nil, err
   597  	}
   598  
   599  	decimal, err := caller.Decimals(&bind.CallOpts{
   600  		Context: ctx,
   601  	})
   602  	if err != nil {
   603  		return nil, err
   604  	}
   605  
   606  	return &Token{
   607  		Address:  address,
   608  		Name:     name,
   609  		Symbol:   symbol,
   610  		Decimals: uint(decimal),
   611  		ChainID:  chainID,
   612  	}, nil
   613  }
   614  
   615  func (tm *Manager) getTokensFromDB(query string, args ...any) ([]*Token, error) {
   616  	communityTokens := []*token.CommunityToken{}
   617  	if tm.communityTokensDB != nil {
   618  		// Error is skipped because it's only returning optional metadata
   619  		communityTokens, _ = tm.communityTokensDB.GetCommunityERC20Metadata()
   620  	}
   621  
   622  	rows, err := tm.db.Query(query, args...)
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  	defer rows.Close()
   627  
   628  	var rst []*Token
   629  	for rows.Next() {
   630  		token := &Token{}
   631  		var communityIDDB sql.NullString
   632  		err := rows.Scan(&token.Address, &token.Name, &token.Symbol, &token.Decimals, &token.ChainID, &communityIDDB)
   633  		if err != nil {
   634  			return nil, err
   635  		}
   636  
   637  		if communityIDDB.Valid {
   638  			communityID := communityIDDB.String
   639  			for _, communityToken := range communityTokens {
   640  				if communityToken.CommunityID != communityID || uint64(communityToken.ChainID) != token.ChainID || communityToken.Symbol != token.Symbol {
   641  					continue
   642  				}
   643  				token.Image = tm.mediaServer.MakeCommunityTokenImagesURL(communityID, token.ChainID, token.Symbol)
   644  				break
   645  			}
   646  
   647  			token.CommunityData = &community.Data{
   648  				ID: communityID,
   649  			}
   650  		}
   651  
   652  		_ = tm.fillCommunityData(token)
   653  
   654  		rst = append(rst, token)
   655  	}
   656  
   657  	return rst, nil
   658  }
   659  
   660  func (tm *Manager) GetCustoms(onlyCommunityCustoms bool) ([]*Token, error) {
   661  	if onlyCommunityCustoms {
   662  		return tm.getTokensFromDB("SELECT address, name, symbol, decimals, network_id, community_id FROM tokens WHERE community_id IS NOT NULL AND community_id != ''")
   663  	}
   664  	return tm.getTokensFromDB("SELECT address, name, symbol, decimals, network_id, community_id FROM tokens")
   665  }
   666  
   667  func (tm *Manager) ToToken(network *params.Network) *Token {
   668  	return &Token{
   669  		Address:  common.HexToAddress("0x"),
   670  		Name:     network.NativeCurrencyName,
   671  		Symbol:   network.NativeCurrencySymbol,
   672  		Decimals: uint(network.NativeCurrencyDecimals),
   673  		ChainID:  network.ChainID,
   674  		Verified: true,
   675  	}
   676  }
   677  
   678  func (tm *Manager) UpsertCustom(token Token) error {
   679  	insert, err := tm.db.Prepare("INSERT OR REPLACE INTO TOKENS (network_id, address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)")
   680  	if err != nil {
   681  		return err
   682  	}
   683  	_, err = insert.Exec(token.ChainID, token.Address, token.Name, token.Symbol, token.Decimals)
   684  	return err
   685  }
   686  
   687  func (tm *Manager) DeleteCustom(chainID uint64, address common.Address) error {
   688  	_, err := tm.db.Exec(`DELETE FROM TOKENS WHERE address = ? and network_id = ?`, address, chainID)
   689  	return err
   690  }
   691  
   692  func (tm *Manager) SignalCommunityTokenReceived(address common.Address, txHash common.Hash, value *big.Int, t *Token, isFirst bool) {
   693  	if tm.walletFeed == nil || t == nil || t.CommunityData == nil {
   694  		return
   695  	}
   696  
   697  	if len(t.CommunityData.Name) == 0 {
   698  		_ = tm.fillCommunityData(t)
   699  	}
   700  	if len(t.CommunityData.Name) == 0 && tm.communityManager != nil {
   701  		communityData, _ := tm.communityManager.FetchCommunityMetadata(t.CommunityData.ID)
   702  		if communityData != nil {
   703  			t.CommunityData.Name = communityData.CommunityName
   704  			t.CommunityData.Color = communityData.CommunityColor
   705  			t.CommunityData.Image = tm.communityManager.GetCommunityImageURL(t.CommunityData.ID)
   706  		}
   707  	}
   708  
   709  	floatAmount, _ := new(big.Float).Quo(new(big.Float).SetInt(value), new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(t.Decimals)), nil))).Float64()
   710  	t.Image = tm.mediaServer.MakeCommunityTokenImagesURL(t.CommunityData.ID, t.ChainID, t.Symbol)
   711  
   712  	receivedToken := ReceivedToken{
   713  		Token:   *t,
   714  		Amount:  floatAmount,
   715  		TxHash:  txHash,
   716  		IsFirst: isFirst,
   717  	}
   718  
   719  	encodedMessage, err := json.Marshal(receivedToken)
   720  	if err != nil {
   721  		return
   722  	}
   723  
   724  	tm.walletFeed.Send(walletevent.Event{
   725  		Type:    EventCommunityTokenReceived,
   726  		ChainID: t.ChainID,
   727  		Accounts: []common.Address{
   728  			address,
   729  		},
   730  		Message: string(encodedMessage),
   731  	})
   732  }
   733  
   734  func (tm *Manager) fillCommunityData(token *Token) error {
   735  	if token == nil || token.CommunityData == nil || tm.communityManager == nil {
   736  		return nil
   737  	}
   738  
   739  	communityInfo, _, err := tm.communityManager.GetCommunityInfo(token.CommunityData.ID)
   740  	if err != nil {
   741  		return err
   742  	}
   743  	if err == nil && communityInfo != nil {
   744  		// Fetched data from cache. Cache is refreshed during every wallet token list call.
   745  		token.CommunityData.Name = communityInfo.CommunityName
   746  		token.CommunityData.Color = communityInfo.CommunityColor
   747  		token.CommunityData.Image = communityInfo.CommunityImage
   748  	}
   749  	return nil
   750  }
   751  
   752  func (tm *Manager) GetTokenHistoricalBalance(account common.Address, chainID uint64, symbol string, timestamp int64) (*big.Int, error) {
   753  	var balance big.Int
   754  	err := tm.db.QueryRow("SELECT balance FROM balance_history WHERE currency = ? AND chain_id = ? AND address = ? AND timestamp < ? order by timestamp DESC LIMIT 1", symbol, chainID, account, timestamp).Scan((*bigint.SQLBigIntBytes)(&balance))
   755  	if err == sql.ErrNoRows {
   756  		return nil, nil
   757  	} else if err != nil {
   758  		return nil, err
   759  	}
   760  	return &balance, nil
   761  }
   762  
   763  func (tm *Manager) GetPreviouslyOwnedTokens() (map[common.Address][]Token, error) {
   764  	storageTokens, err := tm.tokenBalancesStorage.GetTokens()
   765  	if err != nil {
   766  		return nil, err
   767  	}
   768  
   769  	tokens := make(map[common.Address][]Token)
   770  	for account, storageToken := range storageTokens {
   771  		for _, token := range storageToken {
   772  			tokens[account] = append(tokens[account], token.Token)
   773  		}
   774  	}
   775  
   776  	return tokens, nil
   777  }
   778  
   779  func (tm *Manager) removeTokenBalances(account common.Address) error {
   780  	_, err := tm.db.Exec("DELETE FROM token_balances WHERE user_address = ?", account.String())
   781  	return err
   782  }
   783  
   784  func (tm *Manager) onAccountsChange(changedAddresses []common.Address, eventType accountsevent.EventType, currentAddresses []common.Address) {
   785  	if eventType == accountsevent.EventTypeRemoved {
   786  		for _, account := range changedAddresses {
   787  			err := tm.removeTokenBalances(account)
   788  			if err != nil {
   789  				log.Error("token.Manager: can't remove token balances", "error", err)
   790  			}
   791  		}
   792  	}
   793  }
   794  
   795  func (tm *Manager) GetCachedBalancesByChain(accounts, tokenAddresses []common.Address, chainIDs []uint64) (map[uint64]map[common.Address]map[common.Address]*hexutil.Big, error) {
   796  	accountStrings := make([]string, len(accounts))
   797  	for i, account := range accounts {
   798  		accountStrings[i] = fmt.Sprintf("'%s'", account.Hex())
   799  	}
   800  
   801  	tokenAddressStrings := make([]string, len(tokenAddresses))
   802  	for i, tokenAddress := range tokenAddresses {
   803  		tokenAddressStrings[i] = fmt.Sprintf("'%s'", tokenAddress.Hex())
   804  	}
   805  
   806  	chainIDStrings := make([]string, len(chainIDs))
   807  	for i, chainID := range chainIDs {
   808  		chainIDStrings[i] = fmt.Sprintf("%d", chainID)
   809  	}
   810  
   811  	query := `SELECT chain_id, user_address, token_address, raw_balance
   812  			  	FROM token_balances
   813  				WHERE user_address IN (` + strings.Join(accountStrings, ",") + `)
   814  					AND token_address IN (` + strings.Join(tokenAddressStrings, ",") + `)
   815  					AND chain_id IN (` + strings.Join(chainIDStrings, ",") + `)`
   816  
   817  	rows, err := tm.db.Query(query)
   818  	if err != nil {
   819  		return nil, err
   820  	}
   821  	defer rows.Close()
   822  
   823  	ret := make(map[uint64]map[common.Address]map[common.Address]*hexutil.Big)
   824  
   825  	for rows.Next() {
   826  		var chainID uint64
   827  		var userAddressStr, tokenAddressStr string
   828  		var rawBalance string
   829  
   830  		err := rows.Scan(&chainID, &userAddressStr, &tokenAddressStr, &rawBalance)
   831  		if err != nil {
   832  			return nil, err
   833  		}
   834  
   835  		num := new(hexutil.Big)
   836  		_, ok := num.ToInt().SetString(rawBalance, 10)
   837  		if !ok {
   838  			return ret, nil
   839  		}
   840  
   841  		if ret[chainID] == nil {
   842  			ret[chainID] = make(map[common.Address]map[common.Address]*hexutil.Big)
   843  		}
   844  
   845  		if ret[chainID][common.HexToAddress(userAddressStr)] == nil {
   846  			ret[chainID][common.HexToAddress(userAddressStr)] = make(map[common.Address]*hexutil.Big)
   847  		}
   848  
   849  		ret[chainID][common.HexToAddress(userAddressStr)][common.HexToAddress(tokenAddressStr)] = num
   850  	}
   851  
   852  	return ret, nil
   853  }