diff --git a/services/wallet/reader.go b/services/wallet/reader.go index 499266443..ac90adaa5 100644 --- a/services/wallet/reader.go +++ b/services/wallet/reader.go @@ -90,6 +90,22 @@ type Token struct { BuiltOn string `json:"builtOn"` MarketValuesPerCurrency map[string]TokenMarketValues `json:"marketValuesPerCurrency"` PegSymbol string `json:"pegSymbol"` + Verified bool `json:"verified"` +} + +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 } func getTokenBySymbols(tokens []*token.Token) map[string][]*token.Token { @@ -184,8 +200,8 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) } currencies = append(currencies, currency) currencies = append(currencies, getFixedCurrencies()...) - allTokens, err := r.tokenManager.GetTokensByChainIDs(chainIDs) + if err != nil { return nil, err } @@ -251,68 +267,73 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) } err = group.Error() result := make(map[common.Address][]Token) + verifiedTokens, unverifiedTokens := splitVerifiedTokens(allTokens) + for _, address := range addresses { - for symbol, tokens := range getTokenBySymbols(allTokens) { - balancesPerChain := make(map[uint64]ChainBalance) - decimals := tokens[0].Decimals - anyPositiveBalance := false - for _, token := range tokens { - hexBalance := balances[token.ChainID][address][token.Address] - 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))), - ) + for _, tokenList := range [][]*token.Token{verifiedTokens, unverifiedTokens} { + for symbol, tokens := range getTokenBySymbols(tokenList) { + balancesPerChain := make(map[uint64]ChainBalance) + decimals := tokens[0].Decimals + anyPositiveBalance := false + for _, token := range tokens { + hexBalance := balances[token.ChainID][address][token.Address] + 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 + if client, ok := clients[token.ChainID]; ok { + hasError = err != nil || !client.IsConnected + } + if !anyPositiveBalance { + anyPositiveBalance = balance.Cmp(big.NewFloat(0.0)) > 0 + } + balancesPerChain[token.ChainID] = ChainBalance{ + Balance: balance, + Address: token.Address, + ChainID: token.ChainID, + HasError: hasError, + } } - hasError := false - if client, ok := clients[token.ChainID]; ok { - hasError = err != nil || !client.IsConnected - } - if !anyPositiveBalance { - anyPositiveBalance = balance.Cmp(big.NewFloat(0.0)) > 0 - } - balancesPerChain[token.ChainID] = ChainBalance{ - Balance: balance, - Address: token.Address, - ChainID: token.ChainID, - HasError: hasError, - } - } - if !anyPositiveBalance && !belongsToMandatoryTokens(symbol) { - continue - } - - marketValuesPerCurrency := make(map[string]TokenMarketValues) - for _, currency := range currencies { - marketValuesPerCurrency[currency] = TokenMarketValues{ - MarketCap: tokenMarketValues[symbol].MKTCAP, - HighDay: tokenMarketValues[symbol].HIGHDAY, - LowDay: tokenMarketValues[symbol].LOWDAY, - ChangePctHour: tokenMarketValues[symbol].CHANGEPCTHOUR, - ChangePctDay: tokenMarketValues[symbol].CHANGEPCTDAY, - ChangePct24hour: tokenMarketValues[symbol].CHANGEPCT24HOUR, - Change24hour: tokenMarketValues[symbol].CHANGE24HOUR, - Price: prices[symbol][currency], - HasError: !r.marketManager.IsConnected, + if !anyPositiveBalance && !belongsToMandatoryTokens(symbol) { + continue } - } - walletToken := Token{ - Name: tokens[0].Name, - Color: tokens[0].Color, - Symbol: symbol, - BalancesPerChain: balancesPerChain, - Decimals: decimals, - Description: tokenDetails[symbol].Description, - AssetWebsiteURL: tokenDetails[symbol].AssetWebsiteURL, - BuiltOn: tokenDetails[symbol].BuiltOn, - MarketValuesPerCurrency: marketValuesPerCurrency, - PegSymbol: token.GetTokenPegSymbol(symbol), - } + marketValuesPerCurrency := make(map[string]TokenMarketValues) + for _, currency := range currencies { + marketValuesPerCurrency[currency] = TokenMarketValues{ + MarketCap: tokenMarketValues[symbol].MKTCAP, + HighDay: tokenMarketValues[symbol].HIGHDAY, + LowDay: tokenMarketValues[symbol].LOWDAY, + ChangePctHour: tokenMarketValues[symbol].CHANGEPCTHOUR, + ChangePctDay: tokenMarketValues[symbol].CHANGEPCTDAY, + ChangePct24hour: tokenMarketValues[symbol].CHANGEPCT24HOUR, + Change24hour: tokenMarketValues[symbol].CHANGE24HOUR, + Price: prices[symbol][currency], + HasError: !r.marketManager.IsConnected, + } + } - result[address] = append(result[address], walletToken) + walletToken := Token{ + Name: tokens[0].Name, + Color: tokens[0].Color, + Symbol: symbol, + BalancesPerChain: balancesPerChain, + Decimals: decimals, + Description: tokenDetails[symbol].Description, + AssetWebsiteURL: tokenDetails[symbol].AssetWebsiteURL, + BuiltOn: tokenDetails[symbol].BuiltOn, + MarketValuesPerCurrency: marketValuesPerCurrency, + PegSymbol: token.GetTokenPegSymbol(symbol), + Verified: tokens[0].Verified, + } + + result[address] = append(result[address], walletToken) + } } } diff --git a/services/wallet/token/token.go b/services/wallet/token/token.go index 1629d1d2b..a1fc706ad 100644 --- a/services/wallet/token/token.go +++ b/services/wallet/token/token.go @@ -39,6 +39,8 @@ type Token struct { // ISO 4217 alphabetic code. For example, an empty string means it is not // pegged, while "USD" means it's pegged to the United States Dollar. PegSymbol string `json:"pegSymbol"` + + Verified bool `json:"verified"` } func (t *Token) IsNative() bool { @@ -54,6 +56,7 @@ type ManagerInterface interface { type Manager struct { db *sql.DB RPCClient *rpc.Client + contractMaker *contracts.ContractMaker networkManager *network.Manager stores []store tokenList []*Token @@ -66,9 +69,18 @@ func NewTokenManager( RPCClient *rpc.Client, networkManager *network.Manager, ) *Manager { + maker, _ := contracts.NewContractMaker(RPCClient) // Order of stores is important when merging token lists. The former prevale - tokenManager := &Manager{db, RPCClient, networkManager, []store{newUniswapStore(), newDefaultStore()}, nil, nil, false} - return tokenManager + return &Manager{ + db, + RPCClient, + maker, + networkManager, + []store{newUniswapStore(), newDefaultStore()}, + nil, + nil, + false, + } } // overrideTokensInPlace overrides tokens in the store with the ones from the networks @@ -135,6 +147,8 @@ func (tm *Manager) fetchTokens() { 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) @@ -224,9 +238,31 @@ func (tm *Manager) FindTokenByAddress(chainID uint64, address common.Address) *T return token } } + return nil } +func (tm *Manager) FindOrCreateTokenByAddress(ctx context.Context, chainID uint64, address common.Address) *Token { + allTokens := tm.getFullTokenList(chainID) + for _, token := range allTokens { + if token.Address == address { + return token + } + } + + token, err := tm.DiscoverToken(ctx, chainID, address) + if err != nil { + return nil + } + + err = tm.UpsertCustom(*token) + if err != nil { + return nil + } + + return token +} + func (tm *Manager) FindSNT(chainID uint64) *Token { tokens, err := tm.GetTokens(chainID) if err != nil { @@ -248,7 +284,7 @@ func (tm *Manager) GetAllTokensAndNativeCurrencies() ([]*Token, error) { return nil, err } - networks, err := tm.RPCClient.NetworkManager.Get(false) + networks, err := tm.networkManager.Get(false) if err != nil { return nil, err } @@ -314,11 +350,7 @@ func (tm *Manager) GetTokens(chainID uint64) ([]*Token, error) { } func (tm *Manager) DiscoverToken(ctx context.Context, chainID uint64, address common.Address) (*Token, error) { - backend, err := tm.RPCClient.EthClient(chainID) - if err != nil { - return nil, err - } - caller, err := ierc20.NewIERC20Caller(address, backend) + caller, err := tm.contractMaker.NewERC20(chainID, address) if err != nil { return nil, err } @@ -349,6 +381,7 @@ func (tm *Manager) DiscoverToken(ctx context.Context, chainID uint64, address co Name: name, Symbol: symbol, Decimals: uint(decimal), + ChainID: chainID, }, nil } @@ -432,6 +465,7 @@ func (tm *Manager) ToToken(network *params.Network) *Token { Symbol: network.NativeCurrencySymbol, Decimals: uint(network.NativeCurrencyDecimals), ChainID: network.ChainID, + Verified: true, } } @@ -573,14 +607,11 @@ func (tm *Manager) GetBalances(parent context.Context, clients map[uint64]*chain response[account][token] = &sumHex mu.Unlock() } - contractMaker, err := contracts.NewContractMaker(tm.RPCClient) - if err != nil { - return nil, err - } + for clientIdx := range clients { client := clients[clientIdx] - ethScanContract, err := contractMaker.NewEthScan(client.ChainID) + ethScanContract, err := tm.contractMaker.NewEthScan(client.ChainID) if err == nil { fetchChainBalance := false @@ -711,13 +742,9 @@ func (tm *Manager) GetBalancesByChain(parent context.Context, clients map[uint64 mu.Unlock() } - contractMaker, err := contracts.NewContractMaker(tm.RPCClient) - if err != nil { - return nil, err - } for clientIdx := range clients { client := clients[clientIdx] - ethScanContract, err := contractMaker.NewEthScan(client.ChainID) + ethScanContract, err := tm.contractMaker.NewEthScan(client.ChainID) if err != nil { log.Error("error scanning contract", "err", err) return nil, err diff --git a/services/wallet/token/token_test.go b/services/wallet/token/token_test.go index ddc0c4705..88e89165a 100644 --- a/services/wallet/token/token_test.go +++ b/services/wallet/token/token_test.go @@ -16,7 +16,7 @@ import ( func setupTestTokenDB(t *testing.T) (*Manager, func()) { db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) require.NoError(t, err) - return &Manager{db, nil, nil, nil, nil, nil, false}, func() { + return &Manager{db, nil, nil, nil, nil, nil, nil, false}, func() { require.NoError(t, db.Close()) } } diff --git a/services/wallet/transfer/commands.go b/services/wallet/transfer/commands.go index 02f95213e..5b0bfcbed 100644 --- a/services/wallet/transfer/commands.go +++ b/services/wallet/transfer/commands.go @@ -404,6 +404,8 @@ func (c *transfersCommand) Run(ctx context.Context) (err error) { return err } + c.processUnknownErc20CommunityTransactions(ctx, allTransfers) + err = c.processMultiTransactions(ctx, allTransfers) if err != nil { log.Error("processMultiTransactions error", "error", err) @@ -548,6 +550,15 @@ func (c *transfersCommand) checkAndProcessBridgeMultiTx(ctx context.Context, tx return false, nil } +func (c *transfersCommand) processUnknownErc20CommunityTransactions(ctx context.Context, allTransfers []Transfer) { + for _, tx := range allTransfers { + if tx.Type == w_common.Erc20Transfer { + // Find token in db or if this is a community token, find its metadata + _ = c.tokenManager.FindOrCreateTokenByAddress(ctx, tx.NetworkID, *tx.Transaction.To()) + } + } +} + func (c *transfersCommand) processMultiTransactions(ctx context.Context, allTransfers []Transfer) error { txByTxHash := subTransactionListToTransactionsByTxHash(allTransfers) diff --git a/services/wallet/transfer/downloader.go b/services/wallet/transfer/downloader.go index 7860e04f7..1457ee351 100644 --- a/services/wallet/transfer/downloader.go +++ b/services/wallet/transfer/downloader.go @@ -317,7 +317,6 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(tx *types.Transaction } rst := make([]Transfer, 0, len(receipt.Logs)) - for _, log := range receipt.Logs { eventType := w_common.GetEventType(log) // Only add ERC20/ERC721 transfers from/to the given account @@ -341,7 +340,6 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(tx *types.Transaction case w_common.HopBridgeWithdrawalBondedEventType, w_common.HopBridgeTransferSentEventType: mustAppend = true } - if mustAppend { transfer := Transfer{ Type: w_common.EventTypeToSubtransactionType(eventType),