[skip ci] Merge branch 'main' of https://github.com/cake-tech/cake_wallet into mweb

This commit is contained in:
Matthew Fosse 2024-06-25 22:41:09 -07:00
commit 1430c4487f
31 changed files with 295 additions and 187 deletions

View file

@ -2,9 +2,9 @@ import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:unorm_dart/unorm_dart.dart' as unorm;
import 'package:cryptography/cryptography.dart' as cryptography;
import 'package:cw_core/sec_random_native.dart';
import 'package:cw_core/utils/text_normalizer.dart';
const segwit = '100';
final wordlist = englishWordlist;
@ -137,121 +137,6 @@ bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
}
}
final COMBININGCODEPOINTS = combiningcodepoints();
List<int> combiningcodepoints() {
final source = '300:34e|350:36f|483:487|591:5bd|5bf|5c1|5c2|5c4|5c5|5c7|610:61a|64b:65f|670|' +
'6d6:6dc|6df:6e4|6e7|6e8|6ea:6ed|711|730:74a|7eb:7f3|816:819|81b:823|825:827|' +
'829:82d|859:85b|8d4:8e1|8e3:8ff|93c|94d|951:954|9bc|9cd|a3c|a4d|abc|acd|b3c|' +
'b4d|bcd|c4d|c55|c56|cbc|ccd|d4d|dca|e38:e3a|e48:e4b|eb8|eb9|ec8:ecb|f18|f19|' +
'f35|f37|f39|f71|f72|f74|f7a:f7d|f80|f82:f84|f86|f87|fc6|1037|1039|103a|108d|' +
'135d:135f|1714|1734|17d2|17dd|18a9|1939:193b|1a17|1a18|1a60|1a75:1a7c|1a7f|' +
'1ab0:1abd|1b34|1b44|1b6b:1b73|1baa|1bab|1be6|1bf2|1bf3|1c37|1cd0:1cd2|' +
'1cd4:1ce0|1ce2:1ce8|1ced|1cf4|1cf8|1cf9|1dc0:1df5|1dfb:1dff|20d0:20dc|20e1|' +
'20e5:20f0|2cef:2cf1|2d7f|2de0:2dff|302a:302f|3099|309a|a66f|a674:a67d|a69e|' +
'a69f|a6f0|a6f1|a806|a8c4|a8e0:a8f1|a92b:a92d|a953|a9b3|a9c0|aab0|aab2:aab4|' +
'aab7|aab8|aabe|aabf|aac1|aaf6|abed|fb1e|fe20:fe2f|101fd|102e0|10376:1037a|' +
'10a0d|10a0f|10a38:10a3a|10a3f|10ae5|10ae6|11046|1107f|110b9|110ba|11100:11102|' +
'11133|11134|11173|111c0|111ca|11235|11236|112e9|112ea|1133c|1134d|11366:1136c|' +
'11370:11374|11442|11446|114c2|114c3|115bf|115c0|1163f|116b6|116b7|1172b|11c3f|' +
'16af0:16af4|16b30:16b36|1bc9e|1d165:1d169|1d16d:1d172|1d17b:1d182|1d185:1d18b|' +
'1d1aa:1d1ad|1d242:1d244|1e000:1e006|1e008:1e018|1e01b:1e021|1e023|1e024|' +
'1e026:1e02a|1e8d0:1e8d6|1e944:1e94a';
return source.split('|').map((e) {
if (e.contains(':')) {
return e.split(':').map((hex) => int.parse(hex, radix: 16));
}
return int.parse(e, radix: 16);
}).fold(<int>[], (List<int> acc, element) {
if (element is List) {
for (var i = element[0] as int; i <= (element[1] as int); i++) {}
} else if (element is int) {
acc.add(element);
}
return acc;
}).toList();
}
String removeCombiningCharacters(String source) {
return source
.split('')
.where((char) => !COMBININGCODEPOINTS.contains(char.codeUnits.first))
.join('');
}
bool isCJK(String char) {
final n = char.codeUnitAt(0);
for (var x in CJKINTERVALS) {
final imin = x[0] as num;
final imax = x[1] as num;
if (n >= imin && n <= imax) return true;
}
return false;
}
String removeCJKSpaces(String source) {
final splitted = source.split('');
final filtered = <String>[];
for (var i = 0; i < splitted.length; i++) {
final char = splitted[i];
final isSpace = char.trim() == '';
final prevIsCJK = i != 0 && isCJK(splitted[i - 1]);
final nextIsCJK = i != splitted.length - 1 && isCJK(splitted[i + 1]);
if (!(isSpace && prevIsCJK && nextIsCJK)) {
filtered.add(char);
}
}
return filtered.join('');
}
String normalizeText(String source) {
final res =
removeCombiningCharacters(unorm.nfkd(source).toLowerCase()).trim().split('/\s+/').join(' ');
return removeCJKSpaces(res);
}
const CJKINTERVALS = [
[0x4e00, 0x9fff, 'CJK Unified Ideographs'],
[0x3400, 0x4dbf, 'CJK Unified Ideographs Extension A'],
[0x20000, 0x2a6df, 'CJK Unified Ideographs Extension B'],
[0x2a700, 0x2b73f, 'CJK Unified Ideographs Extension C'],
[0x2b740, 0x2b81f, 'CJK Unified Ideographs Extension D'],
[0xf900, 0xfaff, 'CJK Compatibility Ideographs'],
[0x2f800, 0x2fa1d, 'CJK Compatibility Ideographs Supplement'],
[0x3190, 0x319f, 'Kanbun'],
[0x2e80, 0x2eff, 'CJK Radicals Supplement'],
[0x2f00, 0x2fdf, 'CJK Radicals'],
[0x31c0, 0x31ef, 'CJK Strokes'],
[0x2ff0, 0x2fff, 'Ideographic Description Characters'],
[0xe0100, 0xe01ef, 'Variation Selectors Supplement'],
[0x3100, 0x312f, 'Bopomofo'],
[0x31a0, 0x31bf, 'Bopomofo Extended'],
[0xff00, 0xffef, 'Halfwidth and Fullwidth Forms'],
[0x3040, 0x309f, 'Hiragana'],
[0x30a0, 0x30ff, 'Katakana'],
[0x31f0, 0x31ff, 'Katakana Phonetic Extensions'],
[0x1b000, 0x1b0ff, 'Kana Supplement'],
[0xac00, 0xd7af, 'Hangul Syllables'],
[0x1100, 0x11ff, 'Hangul Jamo'],
[0xa960, 0xa97f, 'Hangul Jamo Extended A'],
[0xd7b0, 0xd7ff, 'Hangul Jamo Extended B'],
[0x3130, 0x318f, 'Hangul Compatibility Jamo'],
[0xa4d0, 0xa4ff, 'Lisu'],
[0x16f00, 0x16f9f, 'Miao'],
[0xa000, 0xa48f, 'Yi Syllables'],
[0xa490, 0xa4cf, 'Yi Radicals'],
];
final englishWordlist = <String>[
'abandon',
'ability',
@ -2301,4 +2186,4 @@ final englishWordlist = <String>[
'zero',
'zone',
'zoo'
];
];

View file

@ -894,7 +894,7 @@ packages:
source: hosted
version: "1.3.2"
unorm_dart:
dependency: "direct main"
dependency: transitive
description:
name: unorm_dart
sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b"

View file

@ -28,7 +28,6 @@ dependencies:
url: https://github.com/cake-tech/bitbox-flutter.git
ref: Add-Support-For-OP-Return-data
rxdart: ^0.27.5
unorm_dart: ^0.2.0
cryptography: ^2.0.5
bitcoin_base:
git:

View file

@ -16,6 +16,7 @@ abstract class TransactionInfo extends Object with Keyable {
void changeFiatAmount(String amount);
String? to;
String? from;
String? evmSignatureName;
List<String>? inputAddresses;
List<String>? outputAddresses;

View file

@ -0,0 +1,117 @@
import 'package:unorm_dart/unorm_dart.dart' as unorm;
const CJKINTERVALS = [
[0x4e00, 0x9fff, 'CJK Unified Ideographs'],
[0x3400, 0x4dbf, 'CJK Unified Ideographs Extension A'],
[0x20000, 0x2a6df, 'CJK Unified Ideographs Extension B'],
[0x2a700, 0x2b73f, 'CJK Unified Ideographs Extension C'],
[0x2b740, 0x2b81f, 'CJK Unified Ideographs Extension D'],
[0xf900, 0xfaff, 'CJK Compatibility Ideographs'],
[0x2f800, 0x2fa1d, 'CJK Compatibility Ideographs Supplement'],
[0x3190, 0x319f, 'Kanbun'],
[0x2e80, 0x2eff, 'CJK Radicals Supplement'],
[0x2f00, 0x2fdf, 'CJK Radicals'],
[0x31c0, 0x31ef, 'CJK Strokes'],
[0x2ff0, 0x2fff, 'Ideographic Description Characters'],
[0xe0100, 0xe01ef, 'Variation Selectors Supplement'],
[0x3100, 0x312f, 'Bopomofo'],
[0x31a0, 0x31bf, 'Bopomofo Extended'],
[0xff00, 0xffef, 'Halfwidth and Fullwidth Forms'],
[0x3040, 0x309f, 'Hiragana'],
[0x30a0, 0x30ff, 'Katakana'],
[0x31f0, 0x31ff, 'Katakana Phonetic Extensions'],
[0x1b000, 0x1b0ff, 'Kana Supplement'],
[0xac00, 0xd7af, 'Hangul Syllables'],
[0x1100, 0x11ff, 'Hangul Jamo'],
[0xa960, 0xa97f, 'Hangul Jamo Extended A'],
[0xd7b0, 0xd7ff, 'Hangul Jamo Extended B'],
[0x3130, 0x318f, 'Hangul Compatibility Jamo'],
[0xa4d0, 0xa4ff, 'Lisu'],
[0x16f00, 0x16f9f, 'Miao'],
[0xa000, 0xa48f, 'Yi Syllables'],
[0xa490, 0xa4cf, 'Yi Radicals'],
];
final COMBININGCODEPOINTS = combiningcodepoints();
List<int> combiningcodepoints() {
final source = '300:34e|350:36f|483:487|591:5bd|5bf|5c1|5c2|5c4|5c5|5c7|610:61a|64b:65f|670|' +
'6d6:6dc|6df:6e4|6e7|6e8|6ea:6ed|711|730:74a|7eb:7f3|816:819|81b:823|825:827|' +
'829:82d|859:85b|8d4:8e1|8e3:8ff|93c|94d|951:954|9bc|9cd|a3c|a4d|abc|acd|b3c|' +
'b4d|bcd|c4d|c55|c56|cbc|ccd|d4d|dca|e38:e3a|e48:e4b|eb8|eb9|ec8:ecb|f18|f19|' +
'f35|f37|f39|f71|f72|f74|f7a:f7d|f80|f82:f84|f86|f87|fc6|1037|1039|103a|108d|' +
'135d:135f|1714|1734|17d2|17dd|18a9|1939:193b|1a17|1a18|1a60|1a75:1a7c|1a7f|' +
'1ab0:1abd|1b34|1b44|1b6b:1b73|1baa|1bab|1be6|1bf2|1bf3|1c37|1cd0:1cd2|' +
'1cd4:1ce0|1ce2:1ce8|1ced|1cf4|1cf8|1cf9|1dc0:1df5|1dfb:1dff|20d0:20dc|20e1|' +
'20e5:20f0|2cef:2cf1|2d7f|2de0:2dff|302a:302f|3099|309a|a66f|a674:a67d|a69e|' +
'a69f|a6f0|a6f1|a806|a8c4|a8e0:a8f1|a92b:a92d|a953|a9b3|a9c0|aab0|aab2:aab4|' +
'aab7|aab8|aabe|aabf|aac1|aaf6|abed|fb1e|fe20:fe2f|101fd|102e0|10376:1037a|' +
'10a0d|10a0f|10a38:10a3a|10a3f|10ae5|10ae6|11046|1107f|110b9|110ba|11100:11102|' +
'11133|11134|11173|111c0|111ca|11235|11236|112e9|112ea|1133c|1134d|11366:1136c|' +
'11370:11374|11442|11446|114c2|114c3|115bf|115c0|1163f|116b6|116b7|1172b|11c3f|' +
'16af0:16af4|16b30:16b36|1bc9e|1d165:1d169|1d16d:1d172|1d17b:1d182|1d185:1d18b|' +
'1d1aa:1d1ad|1d242:1d244|1e000:1e006|1e008:1e018|1e01b:1e021|1e023|1e024|' +
'1e026:1e02a|1e8d0:1e8d6|1e944:1e94a';
return source.split('|').map((e) {
if (e.contains(':')) {
return e.split(':').map((hex) => int.parse(hex, radix: 16));
}
return int.parse(e, radix: 16);
}).fold(<int>[], (List<int> acc, element) {
if (element is List) {
for (var i = element[0] as int; i <= (element[1] as int); i++) {}
} else if (element is int) {
acc.add(element);
}
return acc;
}).toList();
}
String _removeCombiningCharacters(String source) {
return source
.split('')
.where((char) => !COMBININGCODEPOINTS.contains(char.codeUnits.first))
.join('');
}
String _removeCJKSpaces(String source) {
final splitted = source.split('');
final filtered = <String>[];
for (var i = 0; i < splitted.length; i++) {
final char = splitted[i];
final isSpace = char.trim() == '';
final prevIsCJK = i != 0 && _isCJK(splitted[i - 1]);
final nextIsCJK = i != splitted.length - 1 && _isCJK(splitted[i + 1]);
if (!(isSpace && prevIsCJK && nextIsCJK)) {
filtered.add(char);
}
}
return filtered.join('');
}
bool _isCJK(String char) {
final n = char.codeUnitAt(0);
for (var x in CJKINTERVALS) {
final imin = x[0] as num;
final imax = x[1] as num;
if (n >= imin && n <= imax) return true;
}
return false;
}
/// This method normalize text which transforms Unicode text into an equivalent decomposed form, allowing for easier sorting and searching of text.
String normalizeText(String source) {
final res =
_removeCombiningCharacters(unorm.nfkd(source).toLowerCase()).trim().split('/\s+/').join(' ');
return _removeCJKSpaces(res);
}

View file

@ -656,6 +656,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
unorm_dart:
dependency: "direct main"
description:
name: unorm_dart
sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
vector_math:
dependency: transitive
description:

View file

@ -20,6 +20,7 @@ dependencies:
intl: ^0.18.0
encrypt: ^5.0.1
socks5_proxy: ^1.0.4
unorm_dart: ^0.3.0
# tor:
# git:
# url: https://github.com/cake-tech/tor.git

View file

@ -29,6 +29,11 @@ class EthereumClient extends EVMChainClient {
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (jsonResponse['result'] is String) {
log(jsonResponse['result']);
return [];
}
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))

View file

@ -14,6 +14,7 @@ class EthereumTransactionInfo extends EVMChainTransactionInfo {
required super.confirmations,
required super.to,
required super.from,
super.evmSignatureName,
super.exponent,
});
@ -31,6 +32,7 @@ class EthereumTransactionInfo extends EVMChainTransactionInfo {
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'],
from: data['from'],
evmSignatureName: data['evmSignatureName'],
);
}

View file

@ -94,6 +94,7 @@ class EthereumWallet extends EVMChainWallet {
tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
to: transactionModel.to,
from: transactionModel.from,
evmSignatureName: transactionModel.evmSignatureName,
);
return model;
}

View file

@ -20,6 +20,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
required this.confirmations,
required this.to,
required this.from,
this.evmSignatureName,
}) : amount = ethAmount.toInt(),
fee = ethFee.toInt();
@ -38,6 +39,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
String? _fiatAmount;
final String? to;
final String? from;
final String? evmSignatureName;
//! Getter to be overridden in child classes
String get feeCurrency;
@ -73,5 +75,6 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
'tokenSymbol': tokenSymbol,
'to': to,
'from': from,
'evmSignatureName': evmSignatureName,
};
}

View file

@ -12,6 +12,8 @@ class EVMChainTransactionModel {
final String? tokenSymbol;
final int? tokenDecimal;
final bool isError;
final String input;
String? evmSignatureName;
EVMChainTransactionModel({
required this.date,
@ -27,6 +29,8 @@ class EVMChainTransactionModel {
required this.tokenSymbol,
required this.tokenDecimal,
required this.isError,
required this.input,
this.evmSignatureName,
});
factory EVMChainTransactionModel.fromJson(Map<String, dynamic> json, String defaultSymbol) =>
@ -44,5 +48,7 @@ class EVMChainTransactionModel {
tokenSymbol: json["tokenSymbol"] ?? defaultSymbol,
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
isError: json["isError"] == "1",
input: json["input"] ?? "",
evmSignatureName: json["evmSignatureName"],
);
}

View file

@ -39,6 +39,20 @@ import 'evm_erc20_balance.dart';
part 'evm_chain_wallet.g.dart';
const Map<String, String> methodSignatureToType = {
'0x095ea7b3': 'approval',
'0xa9059cbb': 'transfer',
'0x23b872dd': 'transferFrom',
'0x574da717': 'transferOut',
'0x2e1a7d4d': 'withdraw',
'0x7ff36ab5': 'swapExactETHForTokens',
'0x40c10f19': 'mint',
'0x44bc937b': 'depositWithExpiry',
'0xd0e30db0': 'deposit',
'0xe8e33700': 'addLiquidity',
'0xd505accf': 'permit',
};
abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
abstract class EVMChainWalletBase
@ -235,7 +249,8 @@ abstract class EVMChainWalletBase
String? hexOpReturnMemo;
if (opReturnMemo != null) {
hexOpReturnMemo = '0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
hexOpReturnMemo =
'0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
}
final CryptoCurrency transactionCurrency =
@ -337,11 +352,21 @@ abstract class EVMChainWalletBase
@override
Future<Map<String, EVMChainTransactionInfo>> fetchTransactions() async {
final List<EVMChainTransactionModel> transactions = [];
final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
final address = _evmChainPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address);
final externalTransactions = await _client.fetchTransactions(address);
final internalTransactions = await _client.fetchInternalTransactions(address);
final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
for (var transaction in externalTransactions) {
final evmSignatureName = analyzeTransaction(transaction.input);
if (evmSignatureName != 'depositWithExpiry' && evmSignatureName != 'transfer') {
transaction.evmSignatureName = evmSignatureName;
transactions.add(transaction);
}
}
for (var token in balance.keys) {
if (token is Erc20Token) {
@ -369,6 +394,17 @@ abstract class EVMChainWalletBase
return result;
}
String? analyzeTransaction(String? transactionInput) {
if (transactionInput == '0x' || transactionInput == null || transactionInput.isEmpty) {
return 'simpleTransfer';
}
final methodSignature =
transactionInput.length >= 10 ? transactionInput.substring(0, 10) : null;
return methodSignatureToType[methodSignature];
}
@override
Object get keys => throw UnimplementedError("keys");
@ -482,11 +518,11 @@ abstract class EVMChainWalletBase
await token.delete();
balance.remove(token);
await _removeTokenTransactionsInHistory(token);
await removeTokenTransactionsInHistory(token);
_updateBalance();
}
Future<void> _removeTokenTransactionsInHistory(Erc20Token token) async {
Future<void> removeTokenTransactionsInHistory(Erc20Token token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}

View file

@ -19,7 +19,7 @@ dependencies:
flutter_mobx: ^2.0.6+1
intl: ^0.18.0
encrypt: ^5.0.1
polyseed: ^0.0.2
polyseed: ^0.0.5
cw_core:
path: ../cw_core

View file

@ -456,7 +456,7 @@ class SolanaWalletClient {
funder: ownerKeypair,
);
} catch (e) {
throw Exception('Insufficient lamports balance to complete this transaction');
throw Exception('Insufficient SOL balance to complete this transaction');
}
// Input by the user

View file

@ -1,15 +1,15 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/utils/language_list.dart';
import 'package:cw_core/wallet_type.dart';
class SeedValidator extends Validator<MnemonicItem> {
SeedValidator({required this.type, required this.language})
@ -41,13 +41,16 @@ class SeedValidator extends Validator<MnemonicItem> {
return polygon!.getPolygonWordList(language);
case WalletType.solana:
return solana!.getSolanaWordList(language);
case WalletType.tron:
case WalletType.tron:
return tron!.getTronWordList(language);
default:
return [];
}
}
static bool needsNormalization(String language) =>
["POLYSEED_French", "POLYSEED_Spanish"].contains(language);
static List<String> getBitcoinWordList(String language) {
assert(language.toLowerCase() == LanguageList.english.toLowerCase());
return bitcoin!.getWordList();

View file

@ -201,7 +201,7 @@ class AddressResolver {
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
if (txtRecord != null) {
final record = await OpenaliasRecord.fetchAddressAndName(
formattedName: formattedName, ticker: ticker, txtRecord: txtRecord);
formattedName: formattedName, ticker: ticker.toLowerCase(), txtRecord: txtRecord);
return ParsedAddress.fetchOpenAliasAddress(record: record, name: text);
}
}

View file

@ -77,6 +77,7 @@ class PreferencesKey {
static const moneroSeedType = 'monero_seed_type';
static const clearnetDonationLink = 'clearnet_donation_link';
static const onionDonationLink = 'onion_donation_link';
static const donationLinkWalletName = 'donation_link_wallet_name';
static const lastSeenAppVersion = 'last_seen_app_version';
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
static const isNewInstall = 'is_new_install';

View file

@ -137,6 +137,10 @@ class CWEthereum extends Ethereum {
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final ethereumWallet = wallet as EthereumWallet;

View file

@ -135,6 +135,10 @@ class CWPolygon extends Polygon {
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async =>
await (wallet as PolygonWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final polygonWallet = wallet as PolygonWallet;

View file

@ -3,7 +3,6 @@ import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cw_core/receive_page_option.dart';
@ -14,7 +13,6 @@ import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_type.dart';
@ -171,8 +169,7 @@ class AddressPage extends BasePage {
textSize: 14,
height: 50,
);
}
else {
} else {
return const SizedBox();
}
}),
@ -204,8 +201,12 @@ class AddressPage extends BasePage {
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
final donationWalletName =
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
if (clearnetUrl != null && onionUrl != null) {
if (clearnetUrl != null &&
onionUrl != null &&
addressListViewModel.wallet.name == donationWalletName) {
Navigator.pushNamed(
context,
Routes.anonPayReceivePage,

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.d
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -87,6 +88,10 @@ class TransactionsPage extends StatelessWidget {
}
final transaction = item.transaction;
final transactionType = dashboardViewModel.type == WalletType.ethereum &&
transaction.evmSignatureName == 'approval'
? ' (${transaction.evmSignatureName})'
: '';
return Observer(
builder: (_) => TransactionRow(
@ -100,7 +105,8 @@ class TransactionsPage extends StatelessWidget {
? ''
: item.formattedFiatAmount,
isPending: transaction.isPending,
title: item.formattedTitle + item.formattedStatus,
title: item.formattedTitle +
item.formattedStatus + ' $transactionType',
),
);
}

View file

@ -60,8 +60,7 @@ class AnonPayInvoicePage extends BasePage {
@override
Widget middle(BuildContext context) => PresentReceiveOptionPicker(
receiveOptionViewModel: receiveOptionViewModel,
color: titleColor(context));
receiveOptionViewModel: receiveOptionViewModel, color: titleColor(context));
@override
Widget trailing(BuildContext context) => TrailButton(
@ -87,30 +86,36 @@ class AnonPayInvoicePage extends BasePage {
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: responsiveLayoutUtil.shouldRenderMobileUI ? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientTopPanelColor,
Theme.of(context).extension<ExchangePageTheme>()!.secondGradientTopPanelColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _amountFocusNode,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
) : null,
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
decoration: responsiveLayoutUtil.shouldRenderMobileUI
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context)
.extension<ExchangePageTheme>()!
.firstGradientTopPanelColor,
Theme.of(context)
.extension<ExchangePageTheme>()!
.secondGradientTopPanelColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
)
: null,
child: Observer(builder: (_) {
return Padding(
padding: EdgeInsets.fromLTRB(24, 120, 24, 0),
@ -143,9 +148,11 @@ class AnonPayInvoicePage extends BasePage {
: S.of(context).anonpay_description("a donation link", "donate"),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).extension<ExchangePageTheme>()!.receiveAmountColor,
fontWeight: FontWeight.w500,
fontSize: 12),
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.receiveAmountColor,
fontWeight: FontWeight.w500,
fontSize: 12),
),
),
),
@ -172,7 +179,7 @@ class AnonPayInvoicePage extends BasePage {
anonInvoicePageViewModel.generateDonationLink();
}
},
color: Theme.of(context).primaryColor,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: anonInvoicePageViewModel.state is IsExecutingState,
),
@ -199,8 +206,12 @@ class AnonPayInvoicePage extends BasePage {
final sharedPreferences = getIt.get<SharedPreferences>();
final clearnetUrl = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
final onionUrl = sharedPreferences.getString(PreferencesKey.onionDonationLink);
final donationWalletName =
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
if (clearnetUrl != null && onionUrl != null) {
if (clearnetUrl != null &&
onionUrl != null &&
anonInvoicePageViewModel.currentWalletName == donationWalletName) {
Navigator.pushReplacementNamed(context, Routes.anonPayReceivePage,
arguments: AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl,

View file

@ -1,11 +1,11 @@
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
class SeedWidget extends StatefulWidget {
SeedWidget({
@ -23,7 +23,6 @@ class SeedWidget extends StatefulWidget {
}
class SeedWidgetState extends State<SeedWidget> {
SeedWidgetState(String language, this.type)
: controller = TextEditingController(),
focusNode = FocusNode(),
@ -46,6 +45,7 @@ class SeedWidgetState extends State<SeedWidget> {
final FocusNode focusNode;
final WalletType type;
List<String> words;
bool normalizeSeed = false;
bool _showPlaceholder;
String get text => controller.text;
@ -60,6 +60,7 @@ class SeedWidgetState extends State<SeedWidget> {
void changeSeedLanguage(String language) {
setState(() {
words = SeedValidator.getWordList(type: type, language: language);
normalizeSeed = SeedValidator.needsNormalization(language);
});
}
@ -97,6 +98,7 @@ class SeedWidgetState extends State<SeedWidget> {
focusNode: focusNode,
controller: controller,
words: words,
normalizeSeed: normalizeSeed,
textStyle: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
backgroundColor: Colors.transparent,

View file

@ -1,6 +1,6 @@
import 'package:cw_core/utils/text_normalizer.dart';
import 'package:flutter/material.dart';
extension Compare<T> on Comparable<T> {
bool operator <=(T other) => compareTo(other) <= 0;
bool operator >=(T other) => compareTo(other) >= 0;
@ -39,6 +39,7 @@ class ValidatableAnnotatedEditableText extends EditableText {
required this.validStyle,
required this.invalidStyle,
required this.words,
this.normalizeSeed = false,
TextStyle textStyle = const TextStyle(
color: Colors.black,
backgroundColor: Colors.transparent,
@ -74,6 +75,7 @@ class ValidatableAnnotatedEditableText extends EditableText {
showSelectionHandles: true,
showCursor: true);
final bool normalizeSeed;
final List<String> words;
final TextStyle validStyle;
final TextStyle invalidStyle;
@ -137,7 +139,8 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
return result;
}
bool validate(String source) => widget.words.indexOf(source) >= 0;
bool validate(String source) =>
widget.words.indexOf(widget.normalizeSeed ? normalizeText(source) : source) >= 0;
List<TextRange> range(String pattern, String source) {
final result = <TextRange>[];
@ -173,4 +176,4 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
return TextSpan(style: widget.style, text: text);
}
}
}

View file

@ -150,6 +150,7 @@ abstract class AnonInvoicePageViewModelBase with Store {
await sharedPreferences.setString(PreferencesKey.clearnetDonationLink, result.clearnetUrl);
await sharedPreferences.setString(PreferencesKey.onionDonationLink, result.onionUrl);
await sharedPreferences.setString(PreferencesKey.donationLinkWalletName, _wallet.name);
state = ExecutedSuccessfullyState(payload: result);
}
@ -163,10 +164,13 @@ abstract class AnonInvoicePageViewModelBase with Store {
maximum = limit.max != null ? limit.max! / 4 : null;
}
@computed
String get currentWalletName => _wallet.name;
@action
void reset() {
selectedCurrency = walletTypeToCryptoCurrency(_wallet.type);
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
receipientEmail = '';
receipientName = '';
description = '';
@ -177,7 +181,10 @@ abstract class AnonInvoicePageViewModelBase with Store {
Future<void> _getPreviousDonationLink() async {
if (pageOption == ReceivePageOption.anonPayDonationLink) {
final donationLink = sharedPreferences.getString(PreferencesKey.clearnetDonationLink);
if (donationLink != null) {
final donationLinkWalletName =
sharedPreferences.getString(PreferencesKey.donationLinkWalletName);
if (donationLink != null && currentWalletName == donationLinkWalletName) {
final url = Uri.parse(donationLink);
url.queryParameters.forEach((key, value) {
if (key == 'name') receipientName = value;

View file

@ -39,7 +39,7 @@ abstract class ContactListViewModelBase with Store {
));
}
});
} else if (info.addresses?.isNotEmpty == true) {
} else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
info.addresses!.forEach((address, label) {
if (label.isEmpty) {
return;

View file

@ -144,10 +144,12 @@ abstract class HomeSettingsViewModelBase with Store {
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
ethereum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) ethereum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
polygon!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) polygon!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {

View file

@ -60,8 +60,6 @@ dependencies:
git:
url: https://github.com/cake-tech/flutter_file_picker.git
ref: master
unorm_dart: ^0.2.0
# check unorm_dart for usage and for replace
permission_handler: ^10.0.0
device_display_brightness:
git:
@ -100,7 +98,7 @@ dependencies:
# ref: main
socks5_proxy: ^1.0.4
flutter_svg: ^2.0.9
polyseed: ^0.0.4
polyseed: ^0.0.5
nostr_tools: ^1.0.9
solana: ^0.30.1
bitcoin_base:

View file

@ -266,7 +266,7 @@
"errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction",
"estimated": "Estimé",
"estimated_new_fee": "De nouveaux frais estimés",
"etherscan_history": "Historique d'Etherscan",
"etherscan_history": "Historique Etherscan",
"event": "Événement",
"events": "Événements",
"exchange": "Échanger",
@ -404,7 +404,7 @@
"node_test": "Tester",
"nodes": "Nœuds",
"nodes_list_reset_to_default_message": "Êtes vous certain de vouloir revenir aux réglages par défaut ?",
"none_of_selected_providers_can_exchange": "Aucun des prestataires sélectionnés ne peut effectuer cet échange",
"none_of_selected_providers_can_exchange": "Aucun des fournisseurs sélectionnés ne peut effectuer cet échange",
"noNFTYet": "Pas encore de NFT",
"normal": "Normal",
"note_optional": "Note (optionnelle)",
@ -794,7 +794,7 @@
"unconfirmed": "Solde non confirmé",
"understand": "J'ai compris",
"unmatched_currencies": "La devise de votre portefeuille (wallet) actuel ne correspond pas à celle du QR code scanné",
"unspent_change": "Changement",
"unspent_change": "Monnaie",
"unspent_coins_details_title": "Détails des pièces (coins) non dépensées",
"unspent_coins_title": "Pièces (coins) non dépensées",
"unsupported_asset": "Nous ne prenons pas en charge cette action pour cet élément. Veuillez créer ou passer à un portefeuille d'un type d'actif pris en charge.",
@ -873,4 +873,4 @@
"you_will_get": "Convertir vers",
"you_will_send": "Convertir depuis",
"yy": "AA"
}
}

View file

@ -667,6 +667,7 @@ abstract class Ethereum {
List<Erc20Token> getERC20Currencies(WalletBase wallet);
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
@ -770,6 +771,7 @@ abstract class Polygon {
List<Erc20Token> getERC20Currencies(WalletBase wallet);
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);