feat: add token list

This commit is contained in:
Anthony Laibe 2023-10-17 17:05:05 +02:00
parent ecbacb0a7f
commit 5381ec4a76
14 changed files with 2147 additions and 2476 deletions

View File

@ -131,26 +131,6 @@ func (api *API) FetchDecodedTxData(ctx context.Context, data string) (*thirdpart
return api.s.decoder.Decode(data)
}
// @deprecated
// GetTokensBalances return mapping of token balances for every account.
func (api *API) GetTokensBalances(ctx context.Context, accounts, addresses []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
chainClients, err := api.s.rpcClient.EthClients([]uint64{api.s.rpcClient.UpstreamChainID})
if err != nil {
return nil, err
}
return api.s.tokenManager.GetBalances(ctx, chainClients, accounts, addresses)
}
// @deprecated
func (api *API) GetTokensBalancesForChainIDs(ctx context.Context, chainIDs []uint64, accounts, addresses []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
log.Debug("wallet.api.GetTokensBalances", "accounts", accounts, "addresses", addresses)
clients, err := api.s.rpcClient.EthClients(chainIDs)
if err != nil {
return nil, err
}
return api.s.tokenManager.GetBalances(ctx, clients, accounts, addresses)
}
// GetBalanceHistory retrieves token balance history for token identity on multiple chains
func (api *API) GetBalanceHistory(ctx context.Context, chainIDs []uint64, address common.Address, tokenSymbol string, currencySymbol string, timeInterval history.TimeInterval) ([]*history.ValuePoint, error) {
log.Debug("wallet.api.GetBalanceHistory", "chainIDs", chainIDs, "address", address, "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "timeInterval", timeInterval)
@ -182,16 +162,25 @@ func (api *API) GetBalanceHistoryRange(ctx context.Context, chainIDs []uint64, a
return api.s.history.GetBalanceHistory(ctx, chainIDs, address, tokenSymbol, currencySymbol, fromTimestamp)
}
func (api *API) GetTokenList(ctx context.Context) ([]*token.List, error) {
log.Debug("call to get token list")
rst := api.s.tokenManager.GetList()
log.Debug("result from token list", "len", len(rst))
return rst, nil
}
// @deprecated
func (api *API) GetTokens(ctx context.Context, chainID uint64) ([]*token.Token, error) {
log.Debug("call to get tokens")
rst, err := api.s.tokenManager.GetTokens(chainID, true)
rst, err := api.s.tokenManager.GetTokens(chainID)
log.Debug("result from token store", "len", len(rst))
return rst, err
}
// @deprecated
func (api *API) GetCustomTokens(ctx context.Context) ([]*token.Token, error) {
log.Debug("call to get custom tokens")
rst, err := api.s.tokenManager.GetCustoms()
rst, err := api.s.tokenManager.GetCustoms(true)
log.Debug("result from database for custom tokens", "len", len(rst))
return rst, err
}
@ -202,24 +191,6 @@ func (api *API) DiscoverToken(ctx context.Context, chainID uint64, address commo
return token, err
}
// @deprecated
func (api *API) GetVisibleTokens(chainIDs []uint64) (map[uint64][]*token.Token, error) {
log.Debug("call to get visible tokens")
rst, err := api.s.tokenManager.GetVisible(chainIDs)
log.Debug("result from database for visible tokens", "len", len(rst))
return rst, err
}
// @deprecated
func (api *API) ToggleVisibleToken(ctx context.Context, chainID uint64, address common.Address) (bool, error) {
log.Debug("call to toggle visible tokens")
err := api.s.tokenManager.Toggle(chainID, address)
if err != nil {
return false, err
}
return true, nil
}
func (api *API) AddCustomToken(ctx context.Context, token token.Token) error {
log.Debug("call to create or edit custom token")
if token.ChainID == 0 {

View File

@ -94,7 +94,7 @@ func (s *Service) getAllFiatCurrencyFormats() (FormatPerSymbol, error) {
}
func (s *Service) fetchAllTokenCurrencyFormats() (FormatPerSymbol, error) {
tokens, err := s.tokenManager.GetAllTokensAndNativeCurrencies()
tokens, err := s.tokenManager.GetAllTokens()
if err != nil {
return nil, err
}

View File

@ -6,17 +6,26 @@ import (
"github.com/stretchr/testify/require"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
)
func TestKeycardPairingsFile(t *testing.T) {
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
accountsDb, err := accounts.NewDB(appDB)
require.NoError(t, err)
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
service := NewService(db, nil, &rpc.Client{}, nil, nil, nil, nil, &params.NodeConfig{}, nil, nil, nil, nil)
service := NewService(db, accountsDb, &rpc.Client{NetworkManager: network.NewManager(db)}, nil, nil, nil, nil, &params.NodeConfig{}, nil, nil, nil, nil)
data, err := service.KeycardPairings().GetPairingsJSONFileContent()
require.NoError(t, err)

View File

@ -36,7 +36,7 @@ func (p *Persistence) SaveTokens(tokens map[common.Address][]Token) (err error)
if b.HasError || b.Balance.Cmp(big.NewFloat(0)) == 0 {
continue
}
_, err = tx.Exec(`INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_color,token_decimals,token_description,token_url,balance,raw_balance,chain_id) VALUES (?,?,?,?,?,?,?,?,?,?,?)`, address.Hex(), t.Name, t.Symbol, b.Address.Hex(), t.Color, t.Decimals, t.Description, t.AssetWebsiteURL, b.Balance.String(), b.RawBalance, chainID)
_, err = tx.Exec(`INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_decimals,token_description,token_url,balance,raw_balance,chain_id) VALUES (?,?,?,?,?,?,?,?,?,?)`, address.Hex(), t.Name, t.Symbol, b.Address.Hex(), t.Decimals, t.Description, t.AssetWebsiteURL, b.Balance.String(), b.RawBalance, chainID)
if err != nil {
return err
}
@ -49,7 +49,7 @@ func (p *Persistence) SaveTokens(tokens map[common.Address][]Token) (err error)
}
func (p *Persistence) GetTokens() (map[common.Address][]Token, error) {
rows, err := p.db.Query(`SELECT user_address, token_name, token_symbol, token_address, token_color, token_decimals, token_description, token_url, balance, raw_balance, chain_id FROM token_balances `)
rows, err := p.db.Query(`SELECT user_address, token_name, token_symbol, token_address, token_decimals, token_description, token_url, balance, raw_balance, chain_id FROM token_balances `)
if err != nil {
return nil, err
}
@ -63,7 +63,7 @@ func (p *Persistence) GetTokens() (map[common.Address][]Token, error) {
token := Token{}
var chainID uint64
err := rows.Scan(&addressStr, &token.Name, &token.Symbol, &tokenAddress, &token.Color, &token.Decimals, &token.Description, &token.AssetWebsiteURL, &balance, &rawBalance, &chainID)
err := rows.Scan(&addressStr, &token.Name, &token.Symbol, &tokenAddress, &token.Decimals, &token.Description, &token.AssetWebsiteURL, &balance, &rawBalance, &chainID)
if err != nil {
return nil, err
}

View File

@ -34,7 +34,6 @@ func TestSaveTokens(t *testing.T) {
token1 := Token{
Name: "token-1",
Symbol: "TT1",
Color: "color-1",
Decimals: 10,
BalancesPerChain: make(map[uint64]ChainBalance),
Description: "description-1",
@ -58,7 +57,6 @@ func TestSaveTokens(t *testing.T) {
token2 := Token{
Name: "token-2",
Symbol: "TT2",
Color: "color-2",
Decimals: 11,
BalancesPerChain: make(map[uint64]ChainBalance),
Description: "description-2",
@ -75,7 +73,6 @@ func TestSaveTokens(t *testing.T) {
token3 := Token{
Name: "token-3",
Symbol: "TT3",
Color: "color-3",
Decimals: 11,
BalancesPerChain: make(map[uint64]ChainBalance),
Description: "description-3",
@ -118,7 +115,6 @@ func TestSaveTokens(t *testing.T) {
require.Equal(t, actualToken1.Name, token1.Name)
require.Equal(t, actualToken1.Symbol, token1.Symbol)
require.Equal(t, actualToken1.Color, token1.Color)
require.Equal(t, actualToken1.Decimals, token1.Decimals)
require.Equal(t, actualToken1.Description, token1.Description)
require.Equal(t, actualToken1.AssetWebsiteURL, token1.AssetWebsiteURL)
@ -137,7 +133,6 @@ func TestSaveTokens(t *testing.T) {
require.Equal(t, actualToken2.Name, token2.Name)
require.Equal(t, actualToken2.Symbol, token2.Symbol)
require.Equal(t, actualToken2.Color, token2.Color)
require.Equal(t, actualToken2.Decimals, token2.Decimals)
require.Equal(t, actualToken2.Description, token2.Description)
require.Equal(t, actualToken2.AssetWebsiteURL, token2.AssetWebsiteURL)
@ -150,7 +145,6 @@ func TestSaveTokens(t *testing.T) {
require.Equal(t, actualToken3.Name, token3.Name)
require.Equal(t, actualToken3.Symbol, token3.Symbol)
require.Equal(t, actualToken3.Color, token3.Color)
require.Equal(t, actualToken3.Decimals, token3.Decimals)
require.Equal(t, actualToken3.Description, token3.Description)
require.Equal(t, actualToken3.AssetWebsiteURL, token3.AssetWebsiteURL)

View File

@ -82,7 +82,6 @@ type ChainBalance struct {
type Token struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Color string `json:"color"`
Decimals uint `json:"decimals"`
BalancesPerChain map[uint64]ChainBalance `json:"balancesPerChain"`
Description string `json:"description"`
@ -192,7 +191,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
}
currencies = append(currencies, currency)
currencies = append(currencies, getFixedCurrencies()...)
allTokens, err := r.tokenManager.GetTokensByChainIDs(chainIDs, true)
allTokens, err := r.tokenManager.GetTokensByChainIDs(chainIDs)
if err != nil {
return nil, err
@ -263,7 +262,6 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address)
walletToken := Token{
Name: tokens[0].Name,
Color: tokens[0].Color,
Symbol: symbol,
BalancesPerChain: balancesPerChain,
Decimals: decimals,

View File

@ -31,7 +31,6 @@ var uniswapTokens = []*Token{
Address: common.HexToAddress("{{ $token.Address }}"),
Name: "{{ $token.Name }}",
Symbol: "{{ $token.Symbol }}",
Color: "{{ $token.Color }}",
Decimals: {{ $token.Decimals }},
ChainID: {{ $token.ChainID }},
PegSymbol: "{{ $token.PegSymbol }}",

View File

@ -3,7 +3,6 @@ package token
import (
"context"
"database/sql"
"errors"
"math/big"
"strconv"
"strings"
@ -34,7 +33,6 @@ type Token struct {
Address common.Address `json:"address"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Color string `json:"color"`
// Decimals defines how divisible the token is. For example, 0 would be
// indivisible, whereas 18 would allow very small amounts of the token
// to be traded.
@ -45,14 +43,26 @@ type Token struct {
// pegged, while "USD" means it's pegged to the United States Dollar.
PegSymbol string `json:"pegSymbol"`
Verified bool `json:"verified"`
CommunityID *string `json:"communityId,omitempty"`
Verified bool `json:"verified"`
TokenListID string `json:"tokenListId"`
}
func (t *Token) IsNative() bool {
return t.Address == nativeChainAddress
}
type List struct {
Name string `json:"name"`
Tokens []*Token `json:"tokens"`
UpdatedAt int64 `json:"updatedAt"`
Source string `json:"source"`
Version string `json:"version"`
}
type addressTokenMap = map[common.Address]*Token
type storeMap = map[uint64]addressTokenMap
type ManagerInterface interface {
LookupTokenIdentity(chainID uint64, address common.Address, native bool) *Token
LookupToken(chainID *uint64, tokenSymbol string) (token *Token, isNative bool)
@ -66,30 +76,63 @@ type Manager struct {
networkManager *network.Manager
stores []store // Set on init, not changed afterwards
// member variables below are protected by mutex
tokenList []*Token
tokenMap storeMap
areTokensFetched bool
tokens []*Token
tokenLock sync.RWMutex
}
func mergeTokens(sliceLists [][]*Token) []*Token {
allKeys := make(map[string]bool)
res := []*Token{}
for _, list := range sliceLists {
for _, token := range list {
key := strconv.FormatUint(token.ChainID, 10) + token.Address.String()
if _, value := allKeys[key]; !value {
allKeys[key] = true
res = append(res, token)
}
}
}
return res
}
func NewTokenManager(
db *sql.DB,
RPCClient *rpc.Client,
networkManager *network.Manager,
) *Manager {
maker, _ := contracts.NewContractMaker(RPCClient)
// Order of stores is important when merging token lists. The former prevale
stores := []store{newUniswapStore(), newDefaultStore()}
tokens := make([]*Token, 0)
networks, err := networkManager.GetAll()
if err != nil {
return nil
}
for _, store := range stores {
validTokens := make([]*Token, 0)
for _, token := range store.GetTokens() {
token.Verified = true
for _, network := range networks {
if network.ChainID == token.ChainID {
validTokens = append(validTokens, token)
break
}
}
}
tokens = mergeTokens([][]*Token{tokens, validTokens})
}
return &Manager{
db: db,
RPCClient: RPCClient,
contractMaker: maker,
networkManager: networkManager,
stores: []store{newUniswapStore(), newDefaultStore()},
tokenList: nil,
tokenMap: nil,
areTokensFetched: false,
db: db,
RPCClient: RPCClient,
contractMaker: maker,
networkManager: networkManager,
stores: stores,
tokens: tokens,
}
}
@ -111,103 +154,17 @@ func overrideTokensInPlace(networks []params.Network, tokens []*Token) {
}
}
func mergeTokenLists(sliceLists [][]*Token) []*Token {
allKeys := make(map[string]bool)
res := []*Token{}
for _, list := range sliceLists {
for _, token := range list {
key := strconv.FormatUint(token.ChainID, 10) + token.Address.String()
if _, value := allKeys[key]; !value {
allKeys[key] = true
res = append(res, token)
}
}
}
return res
}
func (tm *Manager) inStore(address common.Address, chainID uint64) bool {
if address == nativeChainAddress {
return true
}
if !tm.areTokensFetched {
tm.fetchTokens()
}
tokensMap, ok := tm.getAddressTokenMap(chainID)
if !ok {
return false
}
_, ok = tokensMap[address]
return ok
}
func (tm *Manager) getTokenList() []*Token {
func (tm *Manager) getTokens() []*Token {
tm.tokenLock.RLock()
defer tm.tokenLock.RUnlock()
return tm.tokenList
}
func (tm *Manager) getAddressTokenMap(chainID uint64) (addressTokenMap, bool) {
tm.tokenLock.RLock()
defer tm.tokenLock.RUnlock()
tokenMap, chainPresent := tm.tokenMap[chainID]
return tokenMap, chainPresent
return tm.tokens
}
func (tm *Manager) SetTokens(tokens []*Token) {
tm.tokenLock.Lock()
defer tm.tokenLock.Unlock()
tm.tokenList = tokens
tm.tokenMap = toTokenMap(tokens)
tm.areTokensFetched = true
}
func (tm *Manager) fetchTokens() {
tokenList := make([]*Token, 0)
networks, err := tm.networkManager.GetAll()
if err != nil {
return
}
for _, store := range tm.stores {
tokens := store.GetTokens()
validTokens := make([]*Token, 0)
for _, token := range tokens {
token.Verified = true
for _, network := range networks {
if network.ChainID == token.ChainID {
validTokens = append(validTokens, token)
break
}
}
}
tokenList = mergeTokenLists([][]*Token{tokenList, validTokens})
}
tm.SetTokens(tokenList)
}
func (tm *Manager) getFullTokenList(chainID uint64) []*Token {
tokens, err := tm.GetTokens(chainID, false)
if err != nil {
return nil
}
customTokens, err := tm.GetCustomsByChainID(chainID, false)
if err != nil {
return nil
}
return append(tokens, customTokens...)
tm.tokens = tokens
}
func (tm *Manager) FindToken(network *params.Network, tokenSymbol string) *Token {
@ -220,7 +177,7 @@ func (tm *Manager) FindToken(network *params.Network, tokenSymbol string) *Token
func (tm *Manager) LookupToken(chainID *uint64, tokenSymbol string) (token *Token, isNative bool) {
if chainID == nil {
networks, err := tm.networkManager.Get(true)
networks, err := tm.networkManager.Get(false)
if err != nil {
return nil, false
}
@ -246,7 +203,10 @@ func (tm *Manager) LookupToken(chainID *uint64, tokenSymbol string) (token *Toke
// GetToken returns token by chainID and tokenSymbol. Use ToToken for native token
func (tm *Manager) GetToken(chainID uint64, tokenSymbol string) *Token {
allTokens := tm.getFullTokenList(chainID)
allTokens, err := tm.GetTokens(chainID)
if err != nil {
return nil
}
for _, token := range allTokens {
if token.Symbol == tokenSymbol {
return token
@ -265,7 +225,10 @@ func (tm *Manager) LookupTokenIdentity(chainID uint64, address common.Address, n
}
func (tm *Manager) FindTokenByAddress(chainID uint64, address common.Address) *Token {
allTokens := tm.getFullTokenList(chainID)
allTokens, err := tm.GetTokens(chainID)
if err != nil {
return nil
}
for _, token := range allTokens {
if token.Address == address {
return token
@ -276,8 +239,23 @@ func (tm *Manager) FindTokenByAddress(chainID uint64, address common.Address) *T
}
func (tm *Manager) FindOrCreateTokenByAddress(ctx context.Context, chainID uint64, address common.Address) *Token {
allTokens := tm.getFullTokenList(chainID)
for _, token := range allTokens {
// If token comes datasource, simply returns it
for _, token := range tm.getTokens() {
if token.ChainID != chainID {
continue
}
if token.Address == address {
return token
}
}
// Create custom token if not known or try to link with a community
customTokens, err := tm.GetCustoms(false)
if err != nil {
return nil
}
for _, token := range customTokens {
if token.Address == address {
tm.discoverTokenCommunityID(context.Background(), token, address)
return token
@ -347,7 +325,7 @@ func (tm *Manager) discoverTokenCommunityID(ctx context.Context, token *Token, a
}
func (tm *Manager) FindSNT(chainID uint64) *Token {
tokens, err := tm.GetTokens(chainID, false)
tokens, err := tm.GetTokens(chainID)
if err != nil {
return nil
}
@ -361,83 +339,110 @@ func (tm *Manager) FindSNT(chainID uint64) *Token {
return nil
}
func (tm *Manager) GetAllTokensAndNativeCurrencies() ([]*Token, error) {
allTokens, err := tm.GetAllTokens()
if err != nil {
return nil, err
}
func (tm *Manager) getNativeTokens() ([]*Token, error) {
tokens := make([]*Token, 0)
networks, err := tm.networkManager.Get(false)
if err != nil {
return nil, err
}
for _, network := range networks {
allTokens = append(allTokens, tm.ToToken(network))
tokens = append(tokens, tm.ToToken(network))
}
return allTokens, nil
return tokens, nil
}
func (tm *Manager) GetAllTokens() ([]*Token, error) {
if !tm.areTokensFetched {
tm.fetchTokens()
}
tokens, err := tm.GetCustoms()
allTokens, err := tm.GetCustoms(true)
if err != nil {
log.Error("can't fetch custom tokens", "error", err)
}
tokens = append(tm.getTokenList(), tokens...)
allTokens = append(tm.getTokens(), allTokens...)
overrideTokensInPlace(tm.networkManager.GetConfiguredNetworks(), tokens)
overrideTokensInPlace(tm.networkManager.GetConfiguredNetworks(), allTokens)
return tokens, nil
native, err := tm.getNativeTokens()
if err != nil {
return nil, err
}
allTokens = append(allTokens, native...)
return allTokens, nil
}
func (tm *Manager) GetTokensByChainIDs(chainIDs []uint64, onlyCommunityCustoms bool) ([]*Token, error) {
tokens := make([]*Token, 0)
for _, chainID := range chainIDs {
t, err := tm.GetTokens(chainID, onlyCommunityCustoms)
if err != nil {
return nil, err
func (tm *Manager) GetTokens(chainID uint64) ([]*Token, error) {
tokens, err := tm.GetAllTokens()
if err != nil {
return nil, err
}
res := make([]*Token, 0)
for _, token := range tokens {
if token.ChainID == chainID {
res = append(res, token)
}
tokens = append(tokens, t...)
}
return tokens, nil
}
func (tm *Manager) GetDefaultTokens(chainID uint64) ([]*Token, error) {
if !tm.areTokensFetched {
tm.fetchTokens()
}
tokensMap, ok := tm.getAddressTokenMap(chainID)
if !ok {
return nil, errors.New("no tokens for this network")
}
res := make([]*Token, 0, len(tokensMap))
for _, token := range tokensMap {
res = append(res, token)
}
return res, nil
}
func (tm *Manager) GetTokens(chainID uint64, onlyCommunityCustoms bool) ([]*Token, error) {
res, err := tm.GetDefaultTokens(chainID)
func (tm *Manager) GetTokensByChainIDs(chainIDs []uint64) ([]*Token, error) {
tokens, err := tm.GetAllTokens()
if err != nil {
return nil, err
}
tokens, err := tm.GetCustomsByChainID(chainID, onlyCommunityCustoms)
if err != nil {
return nil, err
res := make([]*Token, 0)
for _, token := range tokens {
for _, chainID := range chainIDs {
if token.ChainID == chainID {
res = append(res, token)
}
}
}
return append(res, tokens...), nil
return res, nil
}
func (tm *Manager) GetList() []*List {
res := make([]*List, 0)
nativeTokens, err := tm.getNativeTokens()
if err == nil {
res = append(res, &List{
Name: "native",
Tokens: nativeTokens,
UpdatedAt: time.Now().Unix(),
Source: "native",
Version: "1.0.0",
})
}
customTokens, err := tm.GetCustoms(true)
if err == nil && len(customTokens) > 0 {
res = append(res, &List{
Name: "custom",
Tokens: customTokens,
UpdatedAt: time.Now().Unix(),
Source: "custom",
Version: "1.0.0",
})
}
for _, store := range tm.stores {
res = append(res, &List{
Name: store.GetName(),
Tokens: store.GetTokens(),
UpdatedAt: store.GetUpdatedAt(),
Source: store.GetSource(),
Version: store.GetVersion(),
})
}
return res
}
func (tm *Manager) DiscoverToken(ctx context.Context, chainID uint64, address common.Address) (*Token, error) {
@ -476,7 +481,7 @@ func (tm *Manager) DiscoverToken(ctx context.Context, chainID uint64, address co
}, nil
}
func (tm *Manager) getTokens(query string, args ...any) ([]*Token, error) {
func (tm *Manager) getTokensFromDB(query string, args ...any) ([]*Token, error) {
rows, err := tm.db.Query(query, args...)
if err != nil {
return nil, err
@ -487,7 +492,7 @@ func (tm *Manager) getTokens(query string, args ...any) ([]*Token, error) {
for rows.Next() {
token := &Token{}
var communityIDDB sql.NullString
err := rows.Scan(&token.Address, &token.Name, &token.Symbol, &token.Decimals, &token.Color, &token.ChainID, &communityIDDB)
err := rows.Scan(&token.Address, &token.Name, &token.Symbol, &token.Decimals, &token.ChainID, &communityIDDB)
if err != nil {
return nil, err
}
@ -502,46 +507,11 @@ func (tm *Manager) getTokens(query string, args ...any) ([]*Token, error) {
return rst, nil
}
func (tm *Manager) GetCustoms() ([]*Token, error) {
return tm.getTokens("SELECT address, name, symbol, decimals, color, network_id, community_id FROM tokens")
}
func (tm *Manager) GetCustomsByChainID(chainID uint64, onlyCommunityCustoms bool) ([]*Token, error) {
func (tm *Manager) GetCustoms(onlyCommunityCustoms bool) ([]*Token, error) {
if onlyCommunityCustoms {
return tm.getTokens("SELECT address, name, symbol, decimals, color, network_id, community_id FROM tokens WHERE network_id=? AND community_id IS NOT NULL AND community_id != ''", chainID)
return tm.getTokensFromDB("SELECT address, name, symbol, decimals, network_id, community_id FROM tokens WHERE community_id IS NOT NULL AND community_id != ''")
}
return tm.getTokens("SELECT address, name, symbol, decimals, color, network_id, community_id FROM tokens WHERE network_id=?", chainID)
}
func (tm *Manager) IsTokenVisible(chainID uint64, address common.Address) (bool, error) {
rows, err := tm.db.Query("SELECT chain_id, address FROM visible_tokens WHERE chain_id = ? AND address = ?", chainID, address)
if err != nil {
return false, err
}
defer rows.Close()
return rows.Next(), nil
}
func (tm *Manager) Toggle(chainID uint64, address common.Address) error {
isVisible, err := tm.IsTokenVisible(chainID, address)
if err != nil {
return err
}
if isVisible {
_, err = tm.db.Exec(`DELETE FROM visible_tokens WHERE address = ? and chain_id = ?`, address, chainID)
return err
}
insert, err := tm.db.Prepare("INSERT OR REPLACE INTO visible_tokens (chain_id, address) VALUES (?, ?)")
if err != nil {
return err
}
defer insert.Close()
_, err = insert.Exec(chainID, address)
return err
return tm.getTokensFromDB("SELECT address, name, symbol, decimals, network_id, community_id FROM tokens")
}
func (tm *Manager) ToToken(network *params.Network) *Token {
@ -555,79 +525,12 @@ func (tm *Manager) ToToken(network *params.Network) *Token {
}
}
func (tm *Manager) GetVisible(chainIDs []uint64) (map[uint64][]*Token, error) {
customTokens, err := tm.GetCustoms()
if err != nil {
return nil, err
}
rst := make(map[uint64][]*Token)
for _, chainID := range chainIDs {
network := tm.networkManager.Find(chainID)
if network == nil {
continue
}
rst[chainID] = make([]*Token, 0)
rst[chainID] = append(rst[chainID], tm.ToToken(network))
}
rows, err := tm.db.Query("SELECT chain_id, address FROM visible_tokens")
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
address := common.HexToAddress("0x")
chainID := uint64(0)
err := rows.Scan(&chainID, &address)
if err != nil {
return nil, err
}
found := false
tokens, err := tm.GetTokens(chainID, false)
if err != nil {
continue
}
for _, token := range tokens {
if token.Address == address {
rst[chainID] = append(rst[chainID], token)
found = true
break
}
}
if found {
continue
}
for _, token := range customTokens {
if token.Address == address {
rst[chainID] = append(rst[chainID], token)
break
}
}
}
for _, chainID := range chainIDs {
if len(rst[chainID]) == 1 {
token := tm.FindSNT(chainID)
if token != nil {
rst[chainID] = append(rst[chainID], token)
}
}
}
return rst, nil
}
func (tm *Manager) UpsertCustom(token Token) error {
insert, err := tm.db.Prepare("INSERT OR REPLACE INTO TOKENS (network_id, address, name, symbol, decimals, color) VALUES (?, ?, ?, ?, ?, ?)")
insert, err := tm.db.Prepare("INSERT OR REPLACE INTO TOKENS (network_id, address, name, symbol, decimals) VALUES (?, ?, ?, ?, ?)")
if err != nil {
return err
}
_, err = insert.Exec(token.ChainID, token.Address, token.Name, token.Symbol, token.Decimals, token.Color)
_, err = insert.Exec(token.ChainID, token.Address, token.Name, token.Symbol, token.Decimals)
return err
}
@ -680,136 +583,6 @@ func (tm *Manager) GetBalance(ctx context.Context, client chain.ClientInterface,
return tm.GetTokenBalance(ctx, client, account, token)
}
func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]chain.ClientInterface, accounts, tokens []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
var (
group = async.NewAtomicGroup(parent)
mu sync.Mutex
response = map[common.Address]map[common.Address]*hexutil.Big{}
)
updateBalance := func(account common.Address, token common.Address, balance *big.Int) {
mu.Lock()
if _, ok := response[account]; !ok {
response[account] = map[common.Address]*hexutil.Big{}
}
if _, ok := response[account][token]; !ok {
zeroHex := hexutil.Big(*big.NewInt(0))
response[account][token] = &zeroHex
}
sum := big.NewInt(0).Add(response[account][token].ToInt(), balance)
sumHex := hexutil.Big(*sum)
response[account][token] = &sumHex
mu.Unlock()
}
for clientIdx := range clients {
client := clients[clientIdx]
ethScanContract, _, err := tm.contractMaker.NewEthScan(client.NetworkID())
if err == nil {
fetchChainBalance := false
var tokenChunks [][]common.Address
chunkSize := 500
for i := 0; i < len(tokens); i += chunkSize {
end := i + chunkSize
if end > len(tokens) {
end = len(tokens)
}
tokenChunks = append(tokenChunks, tokens[i:end])
}
for _, token := range tokens {
if token == nativeChainAddress {
fetchChainBalance = true
}
}
if fetchChainBalance {
group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel()
res, err := ethScanContract.EtherBalances(&bind.CallOpts{
Context: ctx,
}, accounts)
if err != nil {
log.Error("can't fetch chain balance 2", err)
return nil
}
for idx, account := range accounts {
balance := new(big.Int)
balance.SetBytes(res[idx].Data)
updateBalance(account, common.HexToAddress("0x"), balance)
}
return nil
})
}
for accountIdx := range accounts {
account := accounts[accountIdx]
for idx := range tokenChunks {
chunk := tokenChunks[idx]
group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel()
res, err := ethScanContract.TokensBalance(&bind.CallOpts{
Context: ctx,
}, account, chunk)
if err != nil {
log.Error("can't fetch erc20 token balance 3", "account", account, "error", err)
return nil
}
for idx, token := range chunk {
if !res[idx].Success {
continue
}
balance := new(big.Int)
balance.SetBytes(res[idx].Data)
updateBalance(account, token, balance)
}
return nil
})
}
}
} else {
for tokenIdx := range tokens {
for accountIdx := range accounts {
// Below, we set account, token and client from idx on purpose to avoid override
account := accounts[accountIdx]
token := tokens[tokenIdx]
client := clients[clientIdx]
if !tm.inStore(token, client.NetworkID()) {
continue
}
group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel()
balance, err := tm.GetBalance(ctx, client, account, token)
if err != nil {
log.Error("can't fetch erc20 token balance 4", "account", account, "token", token, "error", err)
return nil
}
updateBalance(account, token, balance)
return nil
})
}
}
}
}
select {
case <-group.WaitAsync():
case <-parent.Done():
return nil, parent.Err()
}
return response, group.Error()
}
func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64]chain.ClientInterface, accounts, tokens []common.Address) (map[uint64]map[common.Address]map[common.Address]*hexutil.Big, error) {
return tm.GetBalancesAtByChain(parent, clients, accounts, tokens, nil)
}

View File

@ -1,7 +1,6 @@
package token
import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
@ -18,14 +17,11 @@ func setupTestTokenDB(t *testing.T) (*Manager, func()) {
require.NoError(t, err)
return &Manager{
db: db,
RPCClient: nil,
contractMaker: nil,
networkManager: nil,
stores: nil,
tokenList: nil,
tokenMap: nil,
areTokensFetched: false,
db: db,
RPCClient: nil,
contractMaker: nil,
networkManager: nil,
stores: nil,
}, func() {
require.NoError(t, db.Close())
}
@ -35,7 +31,7 @@ func TestCustoms(t *testing.T) {
manager, stop := setupTestTokenDB(t)
defer stop()
rst, err := manager.GetCustoms()
rst, err := manager.GetCustoms(false)
require.NoError(t, err)
require.Nil(t, rst)
@ -44,14 +40,13 @@ func TestCustoms(t *testing.T) {
Name: "Zilliqa",
Symbol: "ZIL",
Decimals: 12,
Color: "#fa6565",
ChainID: 777,
}
err = manager.UpsertCustom(token)
require.NoError(t, err)
rst, err = manager.GetCustoms()
rst, err = manager.GetCustoms(false)
require.NoError(t, err)
require.Equal(t, 1, len(rst))
require.Equal(t, token, *rst[0])
@ -59,11 +54,27 @@ func TestCustoms(t *testing.T) {
err = manager.DeleteCustom(777, token.Address)
require.NoError(t, err)
rst, err = manager.GetCustoms()
rst, err = manager.GetCustoms(false)
require.NoError(t, err)
require.Equal(t, 0, len(rst))
}
func toTokenMap(tokens []*Token) storeMap {
tokenMap := storeMap{}
for _, token := range tokens {
addTokMap := tokenMap[token.ChainID]
if addTokMap == nil {
addTokMap = make(addressTokenMap)
}
addTokMap[token.Address] = token
tokenMap[token.ChainID] = addTokMap
}
return tokenMap
}
func TestTokenOverride(t *testing.T) {
networks := []params.Network{
{
@ -113,7 +124,7 @@ func TestTokenOverride(t *testing.T) {
tokenList,
}
overrideTokensInPlace(networks, testStore.tokenList)
overrideTokensInPlace(networks, tokenList)
tokens := testStore.GetTokens()
tokenMap := toTokenMap(tokens)
_, found := tokenMap[1][common.Address{1}]
@ -125,62 +136,3 @@ func TestTokenOverride(t *testing.T) {
require.Equal(t, common.Address{33}, tokenMap[2][common.Address{33}].Address)
require.Equal(t, common.Address{4}, tokenMap[2][common.Address{4}].Address)
}
func TestMergeTokenLists(t *testing.T) {
tokenList1 := []*Token{
&Token{
Address: common.Address{1},
Symbol: "SNT",
ChainID: 1,
},
}
tokenList1Copy := []*Token{
&Token{
Address: common.Address{1},
Symbol: "SNT",
ChainID: 1,
},
}
tokenList2 := []*Token{
&Token{
Address: common.Address{3},
Symbol: "STT",
ChainID: 2,
},
&Token{
Address: common.Address{4},
Symbol: "TTT",
ChainID: 2,
},
}
tokenList1Plus2 := []*Token{
&Token{
Address: common.Address{1},
Symbol: "SNT",
ChainID: 1,
},
&Token{
Address: common.Address{3},
Symbol: "STT",
ChainID: 2,
},
&Token{
Address: common.Address{4},
Symbol: "TTT",
ChainID: 2,
},
}
tokenListEmpty := []*Token{}
mergedList := mergeTokenLists([][]*Token{tokenListEmpty, tokenListEmpty})
require.Equal(t, 0, len(mergedList))
mergedList = mergeTokenLists([][]*Token{tokenListEmpty, tokenList1})
require.True(t, reflect.DeepEqual(mergedList, tokenList1))
mergedList = mergeTokenLists([][]*Token{tokenList1, tokenList1Copy})
require.True(t, reflect.DeepEqual(mergedList, tokenList1))
mergedList = mergeTokenLists([][]*Token{tokenList1, tokenList2})
require.True(t, reflect.DeepEqual(mergedList, tokenList1Plus2))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,26 @@ func newUniswapStore() *uniswapStore {
return &uniswapStore{}
}
func (ts *uniswapStore) GetTokens() []*Token {
func (s *uniswapStore) GetTokens() []*Token {
for _, token := range uniswapTokens {
token.TokenListID = "uniswap"
}
return uniswapTokens
}
func (s *uniswapStore) GetName() string {
return "Uniswap Labs Default Token List"
}
func (s *uniswapStore) GetVersion() string {
return "11.8.0"
}
func (s *uniswapStore) GetUpdatedAt() int64 {
return 1697613003
}
func (s *uniswapStore) GetSource() string {
return "https://gateway.ipfs.io/ipns/tokens.uniswap.org"
}

View File

@ -164,7 +164,7 @@ func (c *findBlocksCommand) ERC20ScanByBalance(parent context.Context, fromBlock
func (c *findBlocksCommand) checkERC20Tail(parent context.Context) ([]*DBHeader, error) {
log.Debug("checkERC20Tail", "account", c.account, "to block", c.startBlockNumber, "from", c.resFromBlock.Number)
tokens, err := c.tokenManager.GetTokens(c.chainClient.NetworkID(), false)
tokens, err := c.tokenManager.GetTokens(c.chainClient.NetworkID())
if err != nil {
return nil, err
}

View File

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/contracts/ethscan"
"github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/rpc/chain"
@ -867,6 +868,9 @@ func TestFindBlocksCommand(t *testing.T) {
ctx := context.Background()
group := async.NewGroup(ctx)
appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
tm := &TransactionManager{db, nil, nil, nil, nil, nil, nil, nil, nil, nil}
@ -892,7 +896,7 @@ func TestFindBlocksCommand(t *testing.T) {
}
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(db))
tokenManager := token.NewTokenManager(db, client, network.NewManager(appdb))
tokenManager.SetTokens([]*token.Token{
{
Address: tokenTXXAddress,
@ -990,6 +994,9 @@ func (m *MockChainClient) AbstractEthClient(chainID walletcommon.ChainID) (chain
}
func TestFetchTransfersForLoadedBlocks(t *testing.T) {
appdb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
tm := &TransactionManager{db, nil, nil, nil, nil, nil, nil, nil, nil, nil}
@ -1008,7 +1015,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) {
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(db))
tokenManager := token.NewTokenManager(db, client, network.NewManager(appdb))
tokenManager.SetTokens([]*token.Token{
{