From 313375e215cc37f1870185bf88b61d0eed6b9f27 Mon Sep 17 00:00:00 2001 From: Cuteivist Date: Thu, 21 Dec 2023 15:12:50 +0100 Subject: [PATCH] feat: Save previously owned tokens (#4482) --- services/wallet/reader.go | 28 +++++++++++++--- services/wallet/token/token.go | 17 ++++++++++ services/wallet/token/token_test.go | 49 ++++++++++++++++++++++++++++ services/wallet/transfer/commands.go | 21 +++++++++++- 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/services/wallet/reader.go b/services/wallet/reader.go index fd2d49361..2233504b8 100644 --- a/services/wallet/reader.go +++ b/services/wallet/reader.go @@ -250,6 +250,11 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) availableNetworks = append(availableNetworks, network) } + cachedTokens, err := r.GetCachedWalletTokensWithoutMarketData() + if err != nil { + return nil, err + } + chainIDs := make([]uint64, 0) for _, network := range availableNetworks { chainIDs = append(chainIDs, network.ChainID) @@ -296,7 +301,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) for symbol, tokens := range getTokenBySymbols(tokenList) { balancesPerChain := make(map[uint64]ChainBalance) decimals := tokens[0].Decimals - anyPositiveBalance := false + isVisible := false for _, token := range tokens { hexBalance := balances[token.ChainID][address][token.Address] balance := big.NewFloat(0.0) @@ -310,8 +315,8 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) if client, ok := clients[token.ChainID]; ok { hasError = err != nil || !client.GetIsConnected() } - if !anyPositiveBalance { - anyPositiveBalance = balance.Cmp(big.NewFloat(0.0)) > 0 + if !isVisible { + isVisible = balance.Cmp(big.NewFloat(0.0)) > 0 || r.isCachedToken(cachedTokens, address, token.Symbol, token.ChainID) } balancesPerChain[token.ChainID] = ChainBalance{ RawBalance: hexBalance.ToInt().String(), @@ -322,7 +327,7 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) } } - if !anyPositiveBalance && !belongsToMandatoryTokens(symbol) { + if !isVisible && !belongsToMandatoryTokens(symbol) { continue } @@ -420,6 +425,21 @@ func (r *Reader) GetWalletToken(ctx context.Context, addresses []common.Address) return result, r.persistence.SaveTokens(result) } +func (r *Reader) isCachedToken(cachedTokens map[common.Address][]Token, address common.Address, symbol string, chainID uint64) bool { + 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 +} + // GetCachedWalletTokensWithoutMarketData returns the latest fetched balances, minus // price information func (r *Reader) GetCachedWalletTokensWithoutMarketData() (map[common.Address][]Token, error) { diff --git a/services/wallet/token/token.go b/services/wallet/token/token.go index 7c15a3ecb..ebc1d445b 100644 --- a/services/wallet/token/token.go +++ b/services/wallet/token/token.go @@ -3,6 +3,7 @@ package token import ( "context" "database/sql" + "errors" "math/big" "strconv" "strings" @@ -293,6 +294,22 @@ func (tm *Manager) FindOrCreateTokenByAddress(ctx context.Context, chainID uint6 return token } +func (tm *Manager) MarkAsPreviouslyOwnedToken(token *Token, owner common.Address) error { + if token == nil { + return errors.New("token is nil") + } + if (owner == common.Address{}) { + return errors.New("owner is nil") + } + count := 0 + err := tm.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM token_balances WHERE user_address = ? AND token_address = ? AND chain_id = ?)`, owner.Hex(), token.Address.Hex(), token.ChainID).Scan(&count) + if err != nil || count > 0 { + return err + } + _, err = tm.db.Exec(`INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_decimals,chain_id,token_decimals,raw_balance,balance) VALUES (?,?,?,?,?,?,?,?,?)`, owner.Hex(), token.Name, token.Symbol, token.Address.Hex(), token.Decimals, token.ChainID, 0, "0", "0") + return err +} + func (tm *Manager) discoverTokenCommunityID(ctx context.Context, token *Token, address common.Address) { if token == nil || token.CommunityData != nil { // Token is invalid or is alrady discovered. Nothing to do here. diff --git a/services/wallet/token/token_test.go b/services/wallet/token/token_test.go index d457c4cc9..70f348242 100644 --- a/services/wallet/token/token_test.go +++ b/services/wallet/token/token_test.go @@ -193,3 +193,52 @@ 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 TestMarkAsPreviouslyOwnedToken(t *testing.T) { + manager, stop := setupTestTokenDB(t) + defer stop() + + owner := common.HexToAddress("0x1234567890abcdef") + token := &Token{ + Address: common.HexToAddress("0xabcdef1234567890"), + Name: "TestToken", + Symbol: "TT", + Decimals: 18, + ChainID: 1, + } + + err := manager.MarkAsPreviouslyOwnedToken(nil, owner) + require.Error(t, err) + + err = manager.MarkAsPreviouslyOwnedToken(token, common.Address{}) + require.Error(t, err) + + err = manager.MarkAsPreviouslyOwnedToken(token, owner) + require.NoError(t, err) + + // Verify that the token balance was inserted correctly + var count int + err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) + + token.Name = "123" + + err = manager.MarkAsPreviouslyOwnedToken(token, owner) + require.NoError(t, err) + + // Not updated because already exists + err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) + + token.ChainID = 2 + + err = manager.MarkAsPreviouslyOwnedToken(token, owner) + require.NoError(t, err) + + // Same token on different chains counts as different token + err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count) + require.NoError(t, err) + require.Equal(t, 2, count) +} diff --git a/services/wallet/transfer/commands.go b/services/wallet/transfer/commands.go index 4d742edbe..cc847e68b 100644 --- a/services/wallet/transfer/commands.go +++ b/services/wallet/transfer/commands.go @@ -354,6 +354,20 @@ func setMultiTxID(tx Transaction, multiTxID MultiTransactionIDType) { } } +func (c *transfersCommand) markMultiTxTokensAsPreviouslyOwned(ctx context.Context, multiTransaction *MultiTransaction, ownerAddress common.Address) { + if multiTransaction == nil { + return + } + if len(multiTransaction.ToAsset) > 0 && multiTransaction.ToNetworkID > 0 { + token := c.tokenManager.GetToken(multiTransaction.ToNetworkID, multiTransaction.ToAsset) + _ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, ownerAddress) + } + if len(multiTransaction.FromAsset) > 0 && multiTransaction.FromNetworkID > 0 { + token := c.tokenManager.GetToken(multiTransaction.FromNetworkID, multiTransaction.FromAsset) + _ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, ownerAddress) + } +} + func (c *transfersCommand) checkAndProcessSwapMultiTx(ctx context.Context, tx Transaction) (bool, error) { for _, subTx := range tx { switch subTx.Type { @@ -370,6 +384,7 @@ func (c *transfersCommand) checkAndProcessSwapMultiTx(ctx context.Context, tx Tr return false, err } setMultiTxID(tx, id) + c.markMultiTxTokensAsPreviouslyOwned(ctx, multiTransaction, subTx.Address) return true, nil } } @@ -390,6 +405,7 @@ func (c *transfersCommand) checkAndProcessBridgeMultiTx(ctx context.Context, tx if multiTransaction != nil { setMultiTxID(tx, MultiTransactionIDType(multiTransaction.ID)) + c.markMultiTxTokensAsPreviouslyOwned(ctx, multiTransaction, subTx.Address) return true, nil } } @@ -403,7 +419,10 @@ func (c *transfersCommand) processUnknownErc20CommunityTransactions(ctx context. // To can be nil in case of erc20 contract creation if tx.Type == w_common.Erc20Transfer && tx.Transaction.To() != nil { // Find token in db or if this is a community token, find its metadata - _ = c.tokenManager.FindOrCreateTokenByAddress(ctx, tx.NetworkID, *tx.Transaction.To()) + token := c.tokenManager.FindOrCreateTokenByAddress(ctx, tx.NetworkID, *tx.Transaction.To()) + if token != nil && (token.Verified || token.CommunityData != nil) { + _ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, tx.Address) + } } } }