Erc20 token transactions are displaying incorrectly (#1493)

* evm signature name * hide depositWithExpiry and transfer transactions * Update contact_list_view_model.dart * remove erc20 token history when disabled

Serhii committed Jun 20, 2024 at 23:13 UTC 1690f6af1edc26c025cf07e5f21eb5af1725de99
13 files changed +79 -7
cw_core/lib/transaction_info.dart
+1
@@ -16,6 +16,7 @@ abstract class TransactionInfo extends Object with Keyable {
16 void changeFiatAmount(String amount);
17 String? to;
18 String? from;
19 + String? evmSignatureName;
20 List<String>? inputAddresses;
21 List<String>? outputAddresses;
22
cw_ethereum/lib/ethereum_client.dart
+5
@@ -29,6 +29,11 @@ class EthereumClient extends EVMChainClient {
29
30 final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
31
32 + if (jsonResponse['result'] is String) {
33 + log(jsonResponse['result']);
34 + return [];
35 + }
36 +
37 if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
38 return (jsonResponse['result'] as List)
39 .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
cw_ethereum/lib/ethereum_transaction_info.dart
+2
@@ -14,6 +14,7 @@ class EthereumTransactionInfo extends EVMChainTransactionInfo {
14 required super.confirmations,
15 required super.to,
16 required super.from,
17 + super.evmSignatureName,
18 super.exponent,
19 });
20
@@ -31,6 +32,7 @@ class EthereumTransactionInfo extends EVMChainTransactionInfo {
32 tokenSymbol: data['tokenSymbol'] as String,
33 to: data['to'],
34 from: data['from'],
35 + evmSignatureName: data['evmSignatureName'],
36 );
37 }
38
cw_ethereum/lib/ethereum_wallet.dart
+1
@@ -94,6 +94,7 @@ class EthereumWallet extends EVMChainWallet {
94 tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
95 to: transactionModel.to,
96 from: transactionModel.from,
97 + evmSignatureName: transactionModel.evmSignatureName,
98 );
99 return model;
100 }
cw_evm/lib/evm_chain_transaction_info.dart
+3
@@ -20,6 +20,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
20 required this.confirmations,
21 required this.to,
22 required this.from,
23 + this.evmSignatureName,
24 }) : amount = ethAmount.toInt(),
25 fee = ethFee.toInt();
26
@@ -38,6 +39,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
39 String? _fiatAmount;
40 final String? to;
41 final String? from;
42 + final String? evmSignatureName;
43
44 //! Getter to be overridden in child classes
45 String get feeCurrency;
@@ -73,5 +75,6 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
75 'tokenSymbol': tokenSymbol,
76 'to': to,
77 'from': from,
78 + 'evmSignatureName': evmSignatureName,
79 };
80 }
cw_evm/lib/evm_chain_transaction_model.dart
+6
@@ -12,6 +12,8 @@ class EVMChainTransactionModel {
12 final String? tokenSymbol;
13 final int? tokenDecimal;
14 final bool isError;
15 + final String input;
16 + String? evmSignatureName;
17
18 EVMChainTransactionModel({
19 required this.date,
@@ -27,6 +29,8 @@ class EVMChainTransactionModel {
29 required this.tokenSymbol,
30 required this.tokenDecimal,
31 required this.isError,
32 + required this.input,
33 + this.evmSignatureName,
34 });
35
36 factory EVMChainTransactionModel.fromJson(Map<String, dynamic> json, String defaultSymbol) =>
@@ -44,5 +48,7 @@ class EVMChainTransactionModel {
48 tokenSymbol: json["tokenSymbol"] ?? defaultSymbol,
49 tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
50 isError: json["isError"] == "1",
51 + input: json["input"] ?? "",
52 + evmSignatureName: json["evmSignatureName"],
53 );
54 }
cw_evm/lib/evm_chain_wallet.dart
+41 -5
@@ -39,6 +39,20 @@ import 'evm_erc20_balance.dart';
39
40 part 'evm_chain_wallet.g.dart';
41
42 +const Map<String, String> methodSignatureToType = {
43 + '0x095ea7b3': 'approval',
44 + '0xa9059cbb': 'transfer',
45 + '0x23b872dd': 'transferFrom',
46 + '0x574da717': 'transferOut',
47 + '0x2e1a7d4d': 'withdraw',
48 + '0x7ff36ab5': 'swapExactETHForTokens',
49 + '0x40c10f19': 'mint',
50 + '0x44bc937b': 'depositWithExpiry',
51 + '0xd0e30db0': 'deposit',
52 + '0xe8e33700': 'addLiquidity',
53 + '0xd505accf': 'permit',
54 +};
55 +
56 abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
57
58 abstract class EVMChainWalletBase
@@ -235,7 +249,8 @@ abstract class EVMChainWalletBase
249
250 String? hexOpReturnMemo;
251 if (opReturnMemo != null) {
238 - hexOpReturnMemo = '0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
252 + hexOpReturnMemo =
253 + '0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
254 }
255
256 final CryptoCurrency transactionCurrency =
@@ -337,11 +352,21 @@ abstract class EVMChainWalletBase
352
353 @override
354 Future<Map<String, EVMChainTransactionInfo>> fetchTransactions() async {
355 + final List<EVMChainTransactionModel> transactions = [];
356 + final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
357 +
358 final address = _evmChainPrivateKey.address.hex;
341 - final transactions = await _client.fetchTransactions(address);
359 + final externalTransactions = await _client.fetchTransactions(address);
360 final internalTransactions = await _client.fetchInternalTransactions(address);
361
344 - final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
362 + for (var transaction in externalTransactions) {
363 + final evmSignatureName = analyzeTransaction(transaction.input);
364 +
365 + if (evmSignatureName != 'depositWithExpiry' && evmSignatureName != 'transfer') {
366 + transaction.evmSignatureName = evmSignatureName;
367 + transactions.add(transaction);
368 + }
369 + }
370
371 for (var token in balance.keys) {
372 if (token is Erc20Token) {
@@ -369,6 +394,17 @@ abstract class EVMChainWalletBase
394 return result;
395 }
396
397 + String? analyzeTransaction(String? transactionInput) {
398 + if (transactionInput == '0x' || transactionInput == null || transactionInput.isEmpty) {
399 + return 'simpleTransfer';
400 + }
401 +
402 + final methodSignature =
403 + transactionInput.length >= 10 ? transactionInput.substring(0, 10) : null;
404 +
405 + return methodSignatureToType[methodSignature];
406 + }
407 +
408 @override
409 Object get keys => throw UnimplementedError("keys");
410
@@ -482,11 +518,11 @@ abstract class EVMChainWalletBase
518 await token.delete();
519
520 balance.remove(token);
485 - await _removeTokenTransactionsInHistory(token);
521 + await removeTokenTransactionsInHistory(token);
522 _updateBalance();
523 }
524
489 - Future<void> _removeTokenTransactionsInHistory(Erc20Token token) async {
525 + Future<void> removeTokenTransactionsInHistory(Erc20Token token) async {
526 transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
527 await transactionHistory.save();
528 }
lib/ethereum/cw_ethereum.dart
+4
@@ -137,6 +137,10 @@ class CWEthereum extends Ethereum {
137 Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
138 await (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
139
140 + @override
141 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async =>
142 + await (wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
143 +
144 @override
145 Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
146 final ethereumWallet = wallet as EthereumWallet;
lib/polygon/cw_polygon.dart
+4
@@ -135,6 +135,10 @@ class CWPolygon extends Polygon {
135 Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
136 await (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
137
138 + @override
139 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async =>
140 + await (wallet as PolygonWallet).removeTokenTransactionsInHistory(token as Erc20Token);
141 +
142 @override
143 Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
144 final polygonWallet = wallet as PolygonWallet;
lib/src/screens/dashboard/pages/transactions_page.dart
+7 -1
@@ -8,6 +8,7 @@ import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.d
8 import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
9 import 'package:cw_core/crypto_currency.dart';
10 import 'package:cw_core/sync_status.dart';
11 +import 'package:cw_core/wallet_type.dart';
12 import 'package:flutter/material.dart';
13 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
14 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -87,6 +88,10 @@ class TransactionsPage extends StatelessWidget {
88 }
89
90 final transaction = item.transaction;
91 + final transactionType = dashboardViewModel.type == WalletType.ethereum &&
92 + transaction.evmSignatureName == 'approval'
93 + ? ' (${transaction.evmSignatureName})'
94 + : '';
95
96 return Observer(
97 builder: (_) => TransactionRow(
@@ -100,7 +105,8 @@ class TransactionsPage extends StatelessWidget {
105 ? ''
106 : item.formattedFiatAmount,
107 isPending: transaction.isPending,
103 - title: item.formattedTitle + item.formattedStatus,
108 + title: item.formattedTitle +
109 + item.formattedStatus + ' $transactionType',
110 ),
111 );
112 }
lib/view_model/contact_list/contact_list_view_model.dart
+1 -1
@@ -39,7 +39,7 @@ abstract class ContactListViewModelBase with Store {
39 ));
40 }
41 });
42 - } else if (info.addresses?.isNotEmpty == true) {
42 + } else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
43 info.addresses!.forEach((address, label) {
44 if (label.isEmpty) {
45 return;
lib/view_model/dashboard/home_settings_view_model.dart
+2
@@ -144,10 +144,12 @@ abstract class HomeSettingsViewModelBase with Store {
144
145 if (_balanceViewModel.wallet.type == WalletType.ethereum) {
146 ethereum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
147 + if (!value) ethereum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
148 }
149
150 if (_balanceViewModel.wallet.type == WalletType.polygon) {
151 polygon!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
152 + if (!value) polygon!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
153 }
154
155 if (_balanceViewModel.wallet.type == WalletType.solana) {
tool/configure.dart
+2
@@ -667,6 +667,7 @@ abstract class Ethereum {
667 List<Erc20Token> getERC20Currencies(WalletBase wallet);
668 Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
669 Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
670 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
671 Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
672
673 CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
@@ -770,6 +771,7 @@ abstract class Polygon {
771 List<Erc20Token> getERC20Currencies(WalletBase wallet);
772 Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
773 Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
774 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
775 Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
776
777 CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);