From bad9b4c60890f950cd9ecaf917be14bbe633d731 Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:04:08 +0100 Subject: [PATCH 01/16] fix: Wallet type check when registering key service (#1111) * fix: Wallet type check when registering key service * fix: WalletConnect dependency registration --- lib/di.dart | 10 +++++++--- lib/src/screens/settings/connection_sync_page.dart | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index 96c7505ef..aec07ff3f 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -740,9 +740,13 @@ Future setup({ return PowNodeListViewModel(_powNodeSource, appStore); }); - getIt.registerFactory( - () => ConnectionSyncPage(getIt.get(), getIt.get()), - ); + getIt.registerFactory(() { + final wallet = getIt.get().wallet; + return ConnectionSyncPage( + getIt.get(), + wallet?.type == WalletType.ethereum ? getIt.get() : null, + ); + }); getIt.registerFactory( () => SecurityBackupPage(getIt.get(), getIt.get())); diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 468a237cb..573778ed2 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -23,7 +23,7 @@ class ConnectionSyncPage extends BasePage { @override String get title => S.current.connection_sync; - final Web3WalletService web3walletService; + final Web3WalletService? web3walletService; final DashboardViewModel dashboardViewModel; @override @@ -91,7 +91,7 @@ class ConnectionSyncPage extends BasePage { Navigator.of(context).push( MaterialPageRoute( builder: (context) { - return WalletConnectConnectionsView(web3walletService: web3walletService); + return WalletConnectConnectionsView(web3walletService: web3walletService!); }, ), ); From b41489321148465a6a506e2b71dcbde863ea1456 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 5 Oct 2023 15:18:35 +0300 Subject: [PATCH 02/16] CW-491-Send-to-Mastodon-username-addresses (#1107) * Send to Mastodon username addresses * Update mastodon_user.dart * Enhance Eth out of gas error condition Remove some code warnings [skip ci] --------- Co-authored-by: OmarHatem --- lib/di.dart | 24 +++---- lib/entities/parse_address_from_domain.dart | 35 +++++++++++ lib/entities/parsed_address.dart | 11 +++- lib/mastodon/mastodon_api.dart | 63 +++++++++++++++++++ lib/mastodon/mastodon_user.dart | 36 +++++++++++ .../widgets/extract_address_from_parsed.dart | 5 ++ lib/view_model/send/send_view_model.dart | 3 +- 7 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 lib/mastodon/mastodon_api.dart create mode 100644 lib/mastodon/mastodon_user.dart diff --git a/lib/di.dart b/lib/di.dart index aec07ff3f..b572c4b98 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -217,7 +217,6 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; -import 'package:cake_wallet/nano/nano.dart' as nanoNano; import 'core/totp_request_details.dart'; @@ -644,7 +643,7 @@ Future setup({ return MoneroAccountListViewModel(wallet); } throw Exception( - 'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel'); + 'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel'); }); getIt.registerFactory( @@ -929,7 +928,7 @@ Future setup({ wallet: wallet!); }); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final url = args.first as String; final buyViewModel = args[1] as BuyViewModel; @@ -958,7 +957,7 @@ Future setup({ getIt.registerFactory(() { final wallet = getIt.get().wallet; - return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource!); + return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource); }); getIt.registerFactory(() => @@ -969,7 +968,7 @@ Future setup({ (item, model) => UnspentCoinsDetailsViewModel(unspentCoinsItem: item, unspentCoinsListViewModel: model)); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final item = args.first as UnspentCoinsItem; final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel; @@ -1022,7 +1021,7 @@ Future setup({ getIt.registerFactory(() => IoniaLoginPage(getIt.get())); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final email = args.first as String; final isSignIn = args[1] as bool; @@ -1031,13 +1030,14 @@ Future setup({ getIt.registerFactory(() => IoniaWelcomePage()); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final merchant = args.first as IoniaMerchant; return IoniaBuyGiftCardPage(getIt.get(param1: merchant)); }); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>( + (List args, _) { final amount = args.first as double; final merchant = args.last as IoniaMerchant; return IoniaBuyGiftCardDetailPage( @@ -1050,7 +1050,7 @@ Future setup({ ioniaService: getIt.get(), giftCard: giftCard); }); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final amount = args[0] as double; final merchant = args[1] as IoniaMerchant; final tip = args[2] as IoniaTip; @@ -1063,7 +1063,7 @@ Future setup({ return IoniaGiftCardDetailPage(getIt.get(param1: giftCard)); }); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final giftCard = args.first as IoniaGiftCard; return IoniaMoreOptionsPage(giftCard); @@ -1073,13 +1073,13 @@ Future setup({ (IoniaGiftCard giftCard, _) => IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get())); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { final giftCard = args.first as IoniaGiftCard; return IoniaCustomRedeemPage(getIt.get(param1: giftCard)); }); - getIt.registerFactoryParam((List args, _) { + getIt.registerFactoryParam, void>((List args, _) { return IoniaCustomTipPage(getIt.get(param1: args)); }); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 515b9a2df..14bfe2f7f 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; +import 'package:cake_wallet/mastodon/mastodon_api.dart'; import 'package:cake_wallet/twitter/twitter_api.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; @@ -73,6 +74,40 @@ class AddressResolver { } } } + + if (text.startsWith('@') && text.contains('@', 1) && text.contains('.', 1)) { + final subText = text.substring(1); + final hostNameIndex = subText.indexOf('@'); + final hostName = subText.substring(hostNameIndex + 1); + final userName = subText.substring(0, hostNameIndex); + + final mastodonUser = + await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName); + + if (mastodonUser != null) { + String? addressFromBio = + extractAddressByType(raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker)); + + if (addressFromBio != null) { + return ParsedAddress.fetchMastodonAddress(address: addressFromBio, name: text); + } else { + final pinnedPosts = + await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName); + + if (pinnedPosts.isNotEmpty) { + final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n'); + String? addressFromPinnedPost = extractAddressByType( + raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker)); + + if (addressFromPinnedPost != null) { + return ParsedAddress.fetchMastodonAddress( + address: addressFromPinnedPost, name: text); + } + } + } + } + } + if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) { final bool isFioRegistered = await FioAddressProvider.checkAvail(text); if (isFioRegistered) { diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index b8c0a81d5..df20dd9ee 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -1,7 +1,8 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/yat_record.dart'; -enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, ens, contact } + +enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, ens, contact, mastodon } class ParsedAddress { ParsedAddress({ @@ -69,6 +70,14 @@ class ParsedAddress { ); } + factory ParsedAddress.fetchMastodonAddress({required String address, required String name}){ + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.mastodon + ); + } + factory ParsedAddress.fetchContactAddress({required String address, required String name}){ return ParsedAddress( addresses: [address], diff --git a/lib/mastodon/mastodon_api.dart b/lib/mastodon/mastodon_api.dart new file mode 100644 index 000000000..8326ce05d --- /dev/null +++ b/lib/mastodon/mastodon_api.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:cake_wallet/mastodon/mastodon_user.dart'; + +class MastodonAPI { + static const httpsScheme = 'https'; + static const userPath = '/api/v1/accounts/lookup'; + static const statusesPath = '/api/v1/accounts/:id/statuses'; + + static Future lookupUserByUserName( + {required String userName, required String apiHost}) async { + try { + final queryParams = {'acct': userName}; + + final uri = Uri( + scheme: httpsScheme, + host: apiHost, + path: userPath, + queryParameters: queryParams, + ); + + final response = await http.get(uri); + + if (response.statusCode != 200) return null; + + final Map responseJSON = json.decode(response.body) as Map; + + return MastodonUser.fromJson(responseJSON); + } catch (e) { + print('Error in lookupUserByUserName: $e'); + return null; + } + } + + static Future> getPinnedPosts({ + required String userId, + required String apiHost, + }) async { + try { + final queryParams = {'pinned': 'true'}; + + final uri = Uri( + scheme: httpsScheme, + host: apiHost, + path: statusesPath.replaceAll(':id', userId), + queryParameters: queryParams, + ); + + final response = await http.get(uri); + + if (response.statusCode != 200) { + throw Exception('Unexpected HTTP status: ${response.statusCode}'); + } + + final List responseJSON = json.decode(response.body) as List; + + return responseJSON.map((json) => PinnedPost.fromJson(json as Map)).toList(); + } catch (e) { + print('Error in getPinnedPosts: $e'); + throw e; + } + } +} diff --git a/lib/mastodon/mastodon_user.dart b/lib/mastodon/mastodon_user.dart new file mode 100644 index 000000000..f5a29f298 --- /dev/null +++ b/lib/mastodon/mastodon_user.dart @@ -0,0 +1,36 @@ +class MastodonUser { + String id; + String username; + String acct; + String note; + + MastodonUser({ + required this.id, + required this.username, + required this.acct, + required this.note, + }); + + factory MastodonUser.fromJson(Map json) { + return MastodonUser( + id: json['id'] as String, + username: json['username'] as String, + acct: json['acct'] as String, + note: json['note'] as String, + ); + } +} + +class PinnedPost { + final String id; + final String content; + + PinnedPost({required this.id, required this.content}); + + factory PinnedPost.fromJson(Map json) { + return PinnedPost( + id: json['id'] as String, + content: json['content'] as String, + ); + } +} diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index 2d0847158..73bff23c1 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -38,6 +38,11 @@ Future extractAddressFromParsed( content = S.of(context).extracted_address_content('${parsedAddress.name} (Twitter)'); address = parsedAddress.addresses.first; break; + case ParseFrom.mastodon: + title = S.of(context).address_detected; + content = S.of(context).extracted_address_content('${parsedAddress.name} (Mastodon)'); + address = parsedAddress.addresses.first; + break; case ParseFrom.yatRecord: if (parsedAddress.name.isEmpty) { title = S.of(context).yat_error; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index a3457d4af..faebed11f 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,4 +1,3 @@ -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; @@ -430,7 +429,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) { if (walletType == WalletType.ethereum || walletType == WalletType.haven) { - if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for gas')) { + if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for')) { return S.current.do_not_have_enough_gas_asset(currency.toString()); } } From cb21a28b225692648daa3cf0420caf1df855fe88 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Thu, 5 Oct 2023 09:42:35 -0400 Subject: [PATCH 03/16] hotfix nodes page loading in the wrong nodes (#1112) --- lib/entities/default_settings_migration.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index f9e386b49..690a5e9eb 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -161,7 +161,7 @@ Future defaultSettingsMigration( break; case 22: await addNanoNodeList(nodes: nodes); - await addNanoPowNodeList(nodes: nodes); + await addNanoPowNodeList(nodes: powNodes); await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); await changeNanoCurrentPowNodeToDefault( sharedPreferences: sharedPreferences, nodes: powNodes); From 5a20d342adbe771fd348e46b5c51726749124493 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Sun, 8 Oct 2023 12:23:46 +0100 Subject: [PATCH 04/16] Fr.fixes.v4.9.2 (#1116) * Fixed typo * Fixes of French localization --- res/values/strings_en.arb | 2 +- res/values/strings_fr.arb | 86 +++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 9ba95dd44..b981c0177 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -689,7 +689,7 @@ "support_title_guides": "Cake Wallet guides", "support_description_guides": "Documentation and support for common issues", "support_title_other_links": "Other support links", - "support_description_other_links": "Join our communities or reach us our our partners through other methods", + "support_description_other_links": "Join our communities or reach us or our partners through other methods", "choose_derivation": "Choose Wallet Derivation", "new_first_wallet_text": "Keep your crypto safe, piece of cake", "select_destination": "Please select destination for the backup file.", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 0b39ef4ff..f2e336040 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -21,7 +21,7 @@ "contact_name": "Nom de Contact", "reset": "Réinitialiser", "save": "Sauvegarder", - "address_remove_contact": "Supprimer contact", + "address_remove_contact": "Supprimer le contact", "address_remove_content": "Êtes vous certain de vouloir supprimer le contact sélectionné ?", "authenticated": "Authentifié", "authentication": "Authentification", @@ -67,8 +67,8 @@ "min_value": "Min: ${value} ${currency}", "max_value": "Max: ${value} ${currency}", "change_currency": "Changer de Devise", - "overwrite_amount": "Écraser montant", - "qr_payment_amount": "Ce QR code contient un montant de paiement. Voulez-vous écraser la valeur actuelle ?", + "overwrite_amount": "Remplacer le montant", + "qr_payment_amount": "Ce QR code contient un montant de paiement. Voulez-vous remplacer la valeur actuelle ?", "copy_id": "Copier l'ID", "exchange_result_write_down_trade_id": "Merci de copier ou d'écrire l'ID d'échange pour continuer.", "trade_id": "ID d'échange :", @@ -109,7 +109,7 @@ "nodes": "Nœuds", "node_reset_settings_title": "Réinitialisation des réglages", "nodes_list_reset_to_default_message": "Êtes vous certain de vouloir revenir aux réglages par défaut ?", - "change_current_node": "Êtes vous certain de vouloir changer le nœud actuel vers ${node} ?", + "change_current_node": "Êtes vous certain de vouloir changer le nœud actuel pour ${node} ?", "change": "Changer", "remove_node": "Supprimer le nœud", "remove_node_message": "Êtes vous certain de vouloir supprimer le nœud sélectionné ?", @@ -269,7 +269,7 @@ "error_text_address": "L'adresse du portefeuille (wallet) doit correspondre au type de\ncryptomonnaie", "error_text_node_address": "Merci d'entrer une adresse IPv4", "error_text_node_port": "Le port d'un nœud doit être un nombre compris entre 0 et 65535", - "error_text_node_proxy_address": "Veuillez saisir :, par exemple 127.0.0.1:9050", + "error_text_node_proxy_address": "Veuillez saisir :, par exemple 127.0.0.1:9050", "error_text_payment_id": "Un ID de paiement ne peut être constitué que de 16 à 64 caractères hexadécimaux", "error_text_xmr": "La valeur de XMR dépasse le solde disponible.\nLa partie décimale doit comporter au plus 12 chiffres", "error_text_fiat": "La valeur du montant ne peut dépasser le solde disponible.\nLa partie décimale doit comporter au plus 2 chiffres", @@ -350,7 +350,7 @@ "seed_alert_yes": "Oui, je suis sûr", "exchange_sync_alert_content": "Merci d'attendre que votre portefeuille (wallet) soit synchronisé", "pre_seed_title": "IMPORTANT", - "pre_seed_description": "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privé et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", + "pre_seed_description": "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privée et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de la stocker dans un lieu sûr en dehors de l'application Cake Wallet.", "pre_seed_button_text": "J'ai compris. Montrez moi ma phrase secrète (seed)", "xmr_to_error": "Erreur XMR.TO", "xmr_to_error_description": "Montant invalide. La partie décimale doit contenir au plus 8 chiffres", @@ -409,15 +409,15 @@ "unspent_coins_details_title": "Détails des pièces (coins) non dépensées", "freeze": "Geler", "frozen": "Gelées", - "coin_control": "Contrôle des pièces (optionnel)", + "coin_control": "Contrôle optionnel des pièces (coins)", "address_detected": "Adresse détectée", "address_from_domain": "Cette adresse est issue de ${domain} sur Unstoppable Domains", "add_receiver": "Ajouter un autre bénéficiaire (optionnel)", "manage_yats": "Gérer les Yats", "yat_alert_title": "Envoyez et recevez des cryptos plus facilement avec Yat", - "yat_alert_content": "Les utilisateurs de Cake Wallet peuvent maintenant envoyer et recevoir leurs monnaies favories avec un utilisateur unique en son genre basé sur les emojis.", - "get_your_yat": "Obtenez votre Yat", - "connect_an_existing_yat": "Connectez un Yat existant", + "yat_alert_content": "Les utilisateurs de Cake Wallet peuvent maintenant envoyer et recevoir leurs monnaies favorites avec un utilisateur unique en son genre basé sur les emojis.", + "get_your_yat": "Obtenir votre Yat", + "connect_an_existing_yat": "Connecter un Yat existant", "connect_yats": "Connecter Yats", "yat_address": "Adresse Yat", "yat": "Yat", @@ -427,13 +427,13 @@ "choose_address": "\n\nMerci de choisir l'adresse :", "yat_popup_title": "L'adresse de votre portefeuille (wallet) peut être emojifiée.", "yat_popup_content": "Vous pouvez à présent envoyer et recevoir des cryptos dans Cake Wallet à l'aide de votre Yat - un nom d'utilisateur court à base d'emoji. Gérér les Yats à tout moment depuis l'écran de paramétrage", - "second_intro_title": "Une adresse emoji pour les gouverner tous", + "second_intro_title": "Une adresse emoji pour les gouverner toutes", "second_intro_content": "Votre Yat est une seule et unique adresse emoji qui remplace toutes vos longues adresses hexadécimales pour toutes vos cryptomonnaies.", - "third_intro_title": "Yat joue bien avec les autres", + "third_intro_title": "Yat est universel", "third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !", "learn_more": "En savoir plus", "search": "Chercher", - "search_language": "Recherche une langue", + "search_language": "Rechercher une langue", "search_currency": "Rechercher une devise", "new_template": "Nouveau Modèle", "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", @@ -514,8 +514,8 @@ "active_cards": "Cartes actives", "delete_account": "Supprimer le compte", "cards": "Cartes", - "active": "Actif", - "redeemed": "racheté", + "active": "Actives, + "redeemed": "Converties", "gift_card_balance_note": "Les cartes-cadeaux avec un solde restant apparaîtront ici", "gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici", "logout": "Déconnexion", @@ -523,7 +523,7 @@ "percentageOf": "sur ${amount}", "is_percentage": "est", "search_category": "Catégorie de recherche", - "mark_as_redeemed": "Marquer comme échangé", + "mark_as_redeemed": "Marquer comme convertie", "more_options": "Plus d'options", "awaiting_payment_confirmation": "En attente de confirmation de paiement", "transaction_sent_notice": "Si l'écran ne continue pas après 1 minute, vérifiez un explorateur de blocs et votre e-mail.", @@ -544,7 +544,7 @@ "cake_pay_learn_more": "Achetez et utilisez instantanément des cartes-cadeaux dans l'application !\nBalayer de gauche à droite pour en savoir plus.", "automatic": "Automatique", "fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge avec les échanges sélectionnés", - "variable_pair_not_supported": "Cette paire de variables n'est pas prise en charge avec les échanges sélectionnés", + "variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés", "none_of_selected_providers_can_exchange": "Aucun des prestataires sélectionnés ne peut effectuer cet échange", "choose_one": "Choisissez-en un", "choose_from_available_options": "Choisissez parmi les options disponibles :", @@ -634,7 +634,7 @@ "setup_totp_recommended": "Configurer TOTP (recommandé)", "disable_buy": "Désactiver l'action d'achat", "disable_sell": "Désactiver l'action de vente", - "cake_2fa_preset": "Gâteau 2FA prédéfini", + "cake_2fa_preset": "Cake 2FA prédéfini", "monero_dark_theme": "Thème sombre Monero", "bitcoin_dark_theme": "Thème sombre Bitcoin", "bitcoin_light_theme": "Thème léger Bitcoin", @@ -644,33 +644,33 @@ "auto_generate_subaddresses": "Générer automatiquement des sous-adresses", "narrow": "Étroit", "normal": "Normal", - "aggressive": "Trop zélé", + "aggressive": "Agressif", "require_for_assessing_wallet": "Nécessaire pour accéder au portefeuille", - "require_for_sends_to_non_contacts": "Exiger pour les envois à des non-contacts", + "require_for_sends_to_non_contacts": "Exiger pour les envois hors contacts", "require_for_sends_to_contacts": "Exiger pour les envois aux contacts", - "require_for_sends_to_internal_wallets": "Exiger pour les envois vers des portefeuilles internes", - "require_for_exchanges_to_internal_wallets": "Exiger pour les échanges vers des portefeuilles internes", + "require_for_sends_to_internal_wallets": "Exiger pour les envois vers des portefeuilles (wallets) internes", + "require_for_exchanges_to_internal_wallets": "Exiger pour les échanges vers des portefeuilles (wallets) internes", "require_for_adding_contacts": "Requis pour ajouter des contacts", - "require_for_creating_new_wallets": "Nécessaire pour créer de nouveaux portefeuilles", + "require_for_creating_new_wallets": "Nécessaire pour créer de nouveaux portefeuilles (wallets)", "require_for_all_security_and_backup_settings": "Exiger pour tous les paramètres de sécurité et de sauvegarde", "available_balance_description": "Le solde disponible est le montant que vous pouvez dépenser immédiatement. Il est calculé en soustrayant le solde gelé du solde total.", - "syncing_wallet_alert_title": "Votre portefeuille est en cours de synchronisation", - "syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être complets tant qu'il n'y a pas « SYNCHRONISÉ » en haut. Cliquez/appuyez pour en savoir plus.", + "syncing_wallet_alert_title": "Votre portefeuille (wallet) est en cours de synchronisation", + "syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être à jour tant que la mention « SYNCHRONISÉ » n'apparaît en haut de l'écran. Cliquez/appuyez pour en savoir plus.", "home_screen_settings": "Paramètres de l'écran d'accueil", "sort_by": "Trier par", - "search_add_token": "Rechercher / Ajouter un jeton", - "edit_token": "Modifier le jeton", + "search_add_token": "Rechercher / Ajouter un token", + "edit_token": "Modifier le token", "warning": "Avertissement", - "add_token_warning": "Ne modifiez pas ou n'ajoutez pas de jetons comme indiqué par les escrocs.\nConfirmez toujours les adresses de jeton auprès de sources fiables !", - "add_token_disclaimer_check": "J'ai confirmé l'adresse et les informations du contrat de jeton en utilisant une source fiable. L'ajout d'informations malveillantes ou incorrectes peut entraîner une perte de fonds.", - "token_contract_address": "Adresse du contrat de jeton", - "token_name": "Nom du jeton, par exemple : Tether", - "token_symbol": "Symbole de jeton, par exemple : USDT", - "token_decimal": "Décimal de jeton", + "add_token_warning": "Ne modifiez pas ou n'ajoutez pas de tokens comme pourraient vous le suggérer des escrocs.\nConfirmez toujours les adresses de token auprès de sources fiables !", + "add_token_disclaimer_check": "J'ai confirmé l'adresse et les informations du contrat de token en utilisant une source fiable. L'ajout d'informations malveillantes ou incorrectes peut entraîner une perte de fonds.", + "token_contract_address": "Adresse du contrat de token", + "token_name": "Nom du token, par exemple : Tether", + "token_symbol": "Symbole de token, par exemple : USDT", + "token_decimal": "Décimales de token", "field_required": "Ce champ est obligatoire", "pin_at_top": "épingler ${token} en haut", "invalid_input": "Entrée invalide", - "fiat_balance": "Fiat Balance", + "fiat_balance": "Solde fiat", "gross_balance": "Solde brut", "alphabetical": "Alphabétique", "generate_name": "Générer un nom", @@ -685,19 +685,19 @@ "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.", "manage_pow_nodes": "Gérer les nœuds PoW", "support_title_live_chat": "Support en direct", - "support_description_live_chat": "GRATUIT ET RAPIDE! Des représentants de soutien formé sont disponibles pour aider", - "support_title_guides": "Guides de portefeuille à gâteau", - "support_description_guides": "Documentation et soutien aux problèmes communs", + "support_description_live_chat": "GRATUIT ET RAPIDE ! Des représentants de soutien formé sont disponibles pour aider", + "support_title_guides": "Guides de Cake Wallet", + "support_description_guides": "Documentation et support pour les problèmes communs", "support_title_other_links": "Autres liens d'assistance", - "support_description_other_links": "Rejoignez nos communautés ou contactez-nous nos partenaires à travers d'autres méthodes", - "choose_derivation": "Choisissez la dérivation du portefeuille", + "support_description_other_links": "Rejoignez nos communautés ou contactez-nous ou nos partenaires à travers d'autres méthodes", + "choose_derivation": "Choisissez le chemin de dérivation du portefeuille", "new_first_wallet_text": "Gardez facilement votre crypto-monnaie en sécurité", "select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.", "save_to_downloads": "Enregistrer dans les téléchargements", "select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.", - "onramper_option_description": "Achetez rapidement la crypto avec de nombreux méthodes de paiement. Disponible dans la plupart des pays. Les écarts et les frais varient.", + "onramper_option_description": "Achetez rapidement des cryptomonnaies avec de nombreuses méthodes de paiement. Disponible dans la plupart des pays. Les spreads et les frais peuvent varier.", "default_buy_provider": "Fournisseur d'achat par défaut", - "ask_each_time": "Demandez à chaque fois", + "ask_each_time": "Demander à chaque fois", "buy_provider_unavailable": "Fournisseur actuellement indisponible.", "signTransaction": "Signer une transaction", "errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification", @@ -709,9 +709,9 @@ "reject": "Rejeter", "approve": "Approuver", "expiresOn": "Expire le", - "walletConnect": "PortefeuilleConnect", + "walletConnect": "WalletConnect", "nullURIError": "L'URI est nul", - "connectWalletPrompt": "Connectez votre portefeuille avec WalletConnect pour effectuer des transactions", + "connectWalletPrompt": "Connectez votre portefeuille (wallet) avec WalletConnect pour effectuer des transactions", "newConnection": "Nouvelle connexion", "activeConnectionsPrompt": "Les connexions actives apparaîtront ici", "deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à", From e5408a388e31b6fa3c669c9516034d4ef2d2e38f Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 9 Oct 2023 18:18:59 +0300 Subject: [PATCH 05/16] v4.10.0 & v1.7.0 (#1113) * New versions * update the new version text after macos [skip ci] * add cake-wallet headers * add nano/banano to getAddressFromStringPattern * Revert "Exolix integration (#1080)" This reverts commit 9eb6867ab98dbb3a5fec64e0c940c716cf1fd43c. * fix: Bug in conditions check and clean up of repeated code in switch cases (#1117) * update build numbers [skip ci] --------- Co-authored-by: fosse Co-authored-by: Adegoke David <64401859+Blazebrain@users.noreply.github.com> --- .github/workflows/pr_test_build.yml | 1 - assets/images/exolix.png | Bin 1203 -> 0 bytes assets/text/Monerocom_Release_Notes.txt | 2 +- assets/text/Release_Notes.txt | 5 +- cw_nano/lib/nano_client.dart | 24 +- lib/core/address_validator.dart | 4 + lib/core/backup_service.dart | 3 +- lib/di.dart | 8 - lib/entities/cake_2fa_preset_options.dart | 3 + .../exchange_provider_description.dart | 7 +- .../exolix/exolix_exchange_provider.dart | 294 ------------------ lib/exchange/exolix/exolix_request.dart | 20 -- lib/exchange/trade_state.dart | 27 -- lib/nano/cw_nano.dart | 18 +- .../screens/dashboard/widgets/trade_row.dart | 3 - .../screens/nano/nano_change_rep_page.dart | 3 +- .../settings/connection_sync_page.dart | 2 +- lib/store/app_store.dart | 7 + lib/store/dashboard/trade_filter_store.dart | 18 +- .../dashboard/dashboard_view_model.dart | 5 - .../dashboard/transaction_list_item.dart | 5 +- .../exchange/exchange_trade_view_model.dart | 4 - .../exchange/exchange_view_model.dart | 14 - lib/view_model/send/send_view_model.dart | 12 +- lib/view_model/set_up_2fa_viewmodel.dart | 169 ++++------ lib/view_model/support_view_model.dart | 5 - lib/view_model/trade_details_view_model.dart | 10 - scripts/android/app_env.sh | 8 +- scripts/ios/app_env.sh | 8 +- scripts/macos/app_env.sh | 4 +- tool/configure.dart | 6 +- tool/utils/secret_key.dart | 1 - 32 files changed, 146 insertions(+), 554 deletions(-) delete mode 100644 assets/images/exolix.png delete mode 100644 lib/exchange/exolix/exolix_exchange_provider.dart delete mode 100644 lib/exchange/exolix/exolix_request.dart diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index af03c5e30..6b12911b6 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -128,7 +128,6 @@ jobs: echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart diff --git a/assets/images/exolix.png b/assets/images/exolix.png deleted file mode 100644 index 29e5f2db1d659dc3ca64858d800287c29b0a49fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1203 zcmV;k1WfyhP)Px(ZAnByRA@upT3t+3M-={MH7sigx|iVHa%-9*rD;eiDv(r5E2W5NYg6q{jN0l$ z+cY7PkiIm<#56^T51O>5sXnx%8dB;{OlVT-Pg|@HO{iU3kPSPJ+FaGHkbfx&^k z^U0W+*a2}bR6Yd3mjrNH!J)t?D0U3qQ4d+sXF8S$NDGx?AlPViQUkrCey{p204jnj zL?B02KBK@FA`^(p5}?87RU3KrI0+OuB{?t%z{Lw}Q!e25y4*nbs2PqafgF#gl+rnW znE_45_k=jgeEqloPK2G1Ku)f*jXLn%bQs5rNkZ7}8&I2#jWP5vwM_4iM4r#J+(13SXI#i^9@dtI5a2YU(uNmhna7;QxP2mD@jUbK>q zKq}(AsKv4JvG;_IfFvtIx1e2Ptm^x{YQ*lyACj#62;gn2pefa(IPSlrzNO2B?5;lB zx_!mZizPA!tl6s8U+PSlsD*by+X0qU5`u$vSFs=~3#Tf|?ZTk^%@542F2Ng5=2<3jsJjPET~|%@St>s0RWmgLcHJj-wyt&6a5@~OM#KvE zM#oTk>MXY&b0aeOlKO*Ni$icOA-MM18V*UlNPgSTLOL`=%ZVf_tpMH;(&P|cEz9_( zY#E2-gh<8N4%{4Occ{>4C2m>q8NFeJpeW#3RgC_QOA)oUmsX-^ezqx0bI%QYex=J) zE>=K$V<*f1i+62D{-#%Pvgmz;#>cg}C^KCF-w0xpk)nvSnD z%usM#BM}bb?u-mvtgeh}Ad4VR3;KO5kbMG_V#Bo-9d6(03;45kf8?{e!UEJi`^*#) zRll9X^#>0vuRc?>O`x)^3G)jVO)=uvjXN=PxhL}Zh3ZO385xoPTW;J!!@pN-8E>1w z6P3&H!q@fjjUd2@!Y#TgN$=}5TB>m`7({Wqb-$0E(DDuthXZeR9ZtA{!TZB#EBPQI zQzM01@^42+sqOSoJs97cZtz$#+g zV@bn%SiH5GTfV)b4#RzSx#a}}TZL1J%v?9to9bh|=%%LT743IaSe&ytDEO_oI%QkfGIfCwHv4qWNGF>{&^PuspT8b41Ke23l zSdm2~7M<)4-Gj!|jVMLQ-jB81#`d5T7eBTA_=E0AsS%i-n!jzn1^}#=pgjR*Pg#Yr zN&CXqr)3ePQ%{CSH#56U!b+I8_%}Z`CA*$vtl7DczP<+U={jod Rkx~Ev002ovPDHLkV1hCPI;#Kx diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 9393f7768..46a62494b 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,3 @@ -Fix 2FA code issue +Support getting Addresses from ENS and Mastodon Bug fixes Minor enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 1fd86c9ca..50c88833d 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,4 +1,5 @@ -Ethereum enhancements and bug fixes -Fix 2FA code issue +Add Nano wallet +Add WalletConnect to connect your ETH wallet with your favorite dApp +Support getting Addresses from ENS and Mastodon Bug fixes Minor enhancements \ No newline at end of file diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 29f47cc2d..2866c4653 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -13,6 +13,11 @@ class NanoClient { static const String DEFAULT_REPRESENTATIVE = "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579"; + static const Map CAKE_HEADERS = { + "Content-Type": "application/json", + "nano-app": "cake-wallet" + }; + Node? _node; Node? _powNode; @@ -37,7 +42,7 @@ class NanoClient { Future getBalance(String address) async { final response = await http.post( _node!.uri, - headers: {"Content-Type": "application/json"}, + headers: CAKE_HEADERS, body: jsonEncode( { "action": "account_balance", @@ -57,7 +62,7 @@ class NanoClient { try { final response = await http.post( _node!.uri, - headers: {"Content-Type": "application/json"}, + headers: CAKE_HEADERS, body: jsonEncode( { "action": "account_info", @@ -123,7 +128,7 @@ class NanoClient { Future requestWork(String hash) async { final response = await http.post( _powNode!.uri, - headers: {'Content-type': 'application/json'}, + headers: CAKE_HEADERS, body: json.encode( { "action": "work_generate", @@ -157,7 +162,6 @@ class NanoClient { } Future processBlock(Map block, String subtype) async { - final headers = {"Content-Type": "application/json"}; final processBody = jsonEncode({ "action": "process", "json_block": "true", @@ -167,7 +171,7 @@ class NanoClient { final processResponse = await http.post( _node!.uri, - headers: headers, + headers: CAKE_HEADERS, body: processBody, ); @@ -260,10 +264,6 @@ class NanoClient { }) async { bool openBlock = false; - final headers = { - "Content-Type": "application/json", - }; - // first check if the account is open: // get the account info (we need the frontier and representative): AccountInfoResponse? infoData = await getAccountInfo(destinationAddress); @@ -335,7 +335,7 @@ class NanoClient { }); final processResponse = await http.post( _node!.uri, - headers: headers, + headers: CAKE_HEADERS, body: processBody, ); @@ -351,7 +351,7 @@ class NanoClient { required String privateKey, }) async { final receivableResponse = await http.post(_node!.uri, - headers: {"Content-Type": "application/json"}, + headers: CAKE_HEADERS, body: jsonEncode({ "action": "receivable", "account": destinationAddress, @@ -401,7 +401,7 @@ class NanoClient { Future> fetchTransactions(String address) async { try { final response = await http.post(_node!.uri, - headers: {"Content-Type": "application/json"}, + headers: CAKE_HEADERS, body: jsonEncode({ "action": "account_history", "account": address, diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 5f5b004ba..d039a40a7 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -267,6 +267,10 @@ class AddressValidator extends TextValidator { '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)'; case CryptoCurrency.eth: return '0x[0-9a-zA-Z]{42}'; + case CryptoCurrency.nano: + return 'nano_[0-9a-zA-Z]{60}'; + case CryptoCurrency.banano: + return 'ban_[0-9a-zA-Z]{60}'; default: return null; } diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index 24b5558d5..ad4fe9623 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; @@ -308,7 +309,7 @@ class BackupService { if (currentPinLength != null) await _sharedPreferences.setInt(PreferencesKey.currentPinLength, currentPinLength); - if (currentTheme != null) + if (currentTheme != null && DeviceInfo.instance.isMobile) await _sharedPreferences.setInt(PreferencesKey.currentTheme, currentTheme); if (exchangeStatus != null) diff --git a/lib/di.dart b/lib/di.dart index b572c4b98..b871e6a6b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -417,10 +417,6 @@ Future setup({ } if (appStore.wallet != null) { authStore.allowed(); - - if (appStore.wallet!.type == WalletType.ethereum) { - getIt.get().init(); - } return; } @@ -441,10 +437,6 @@ Future setup({ } else { if (appStore.wallet != null) { authStore.allowed(); - - if (appStore.wallet!.type == WalletType.ethereum) { - getIt.get().init(); - } return; } diff --git a/lib/entities/cake_2fa_preset_options.dart b/lib/entities/cake_2fa_preset_options.dart index 2aa6c4215..417af2b98 100644 --- a/lib/entities/cake_2fa_preset_options.dart +++ b/lib/entities/cake_2fa_preset_options.dart @@ -6,6 +6,7 @@ class Cake2FAPresetsOptions extends EnumerableItem with Serializable { static const narrow = Cake2FAPresetsOptions(title: 'Narrow', raw: 0); static const normal = Cake2FAPresetsOptions(title: 'Normal', raw: 1); static const aggressive = Cake2FAPresetsOptions(title: 'Aggressive', raw: 2); + static const none = Cake2FAPresetsOptions(title: 'None', raw: 3); static Cake2FAPresetsOptions deserialize({required int raw}) { switch (raw) { @@ -15,6 +16,8 @@ class Cake2FAPresetsOptions extends EnumerableItem with Serializable { return Cake2FAPresetsOptions.normal; case 2: return Cake2FAPresetsOptions.aggressive; + case 3: + return Cake2FAPresetsOptions.none; default: throw Exception( 'Incorrect Cake 2FA Preset $raw for Cake2FAPresetOptions deserialize', diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index 151d018e0..e545f69ce 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -24,10 +24,7 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< static const trocador = ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); - static const exolix = - ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png'); - - static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); + static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: ''); static ExchangeProviderDescription deserialize({required int raw}) { switch (raw) { @@ -44,8 +41,6 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< case 5: return trocador; case 6: - return exolix; - case 7: return all; default: throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); diff --git a/lib/exchange/exolix/exolix_exchange_provider.dart b/lib/exchange/exolix/exolix_exchange_provider.dart deleted file mode 100644 index 0768f1160..000000000 --- a/lib/exchange/exolix/exolix_exchange_provider.dart +++ /dev/null @@ -1,294 +0,0 @@ -import 'dart:convert'; -import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; -import 'package:http/http.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cake_wallet/exchange/exchange_pair.dart'; -import 'package:cake_wallet/exchange/exchange_provider.dart'; -import 'package:cake_wallet/exchange/limits.dart'; -import 'package:cake_wallet/exchange/trade.dart'; -import 'package:cake_wallet/exchange/trade_request.dart'; -import 'package:cake_wallet/exchange/trade_state.dart'; -import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; -import 'package:cake_wallet/exchange/exchange_provider_description.dart'; - -class ExolixExchangeProvider extends ExchangeProvider { - ExolixExchangeProvider() : super(pairList: _supportedPairs()); - - static final apiKey = secrets.exolixApiKey; - static const apiBaseUrl = 'exolix.com'; - static const transactionsPath = '/api/v2/transactions'; - static const ratePath = '/api/v2/rate'; - - static const List _notSupported = [ - CryptoCurrency.usdt, - CryptoCurrency.xhv, - CryptoCurrency.btt, - CryptoCurrency.firo, - CryptoCurrency.zaddr, - CryptoCurrency.xvg, - CryptoCurrency.kmd, - CryptoCurrency.paxg, - CryptoCurrency.rune, - CryptoCurrency.scrt, - CryptoCurrency.btcln, - CryptoCurrency.cro, - CryptoCurrency.ftm, - CryptoCurrency.frax, - CryptoCurrency.gusd, - CryptoCurrency.gtc, - CryptoCurrency.weth, - ]; - - static List _supportedPairs() { - final supportedCurrencies = - CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList(); - - return supportedCurrencies - .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true))) - .expand((i) => i) - .toList(); - } - - @override - String get title => 'Exolix'; - - @override - bool get isAvailable => true; - - @override - bool get isEnabled => true; - - @override - bool get supportsFixedRate => true; - - @override - ExchangeProviderDescription get description => ExchangeProviderDescription.exolix; - - @override - Future checkIsAvailable() async => true; - - static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float'; - - @override - Future fetchLimits( - {required CryptoCurrency from, - required CryptoCurrency to, - required bool isFixedRateMode}) async { - final params = { - 'rateType': getRateType(isFixedRateMode), - 'amount': '1', - }; - if (isFixedRateMode) { - params['coinFrom'] = _normalizeCurrency(to); - params['coinTo'] = _normalizeCurrency(from); - params['networkFrom'] = _networkFor(to); - params['networkTo'] = _networkFor(from); - } else { - params['coinFrom'] = _normalizeCurrency(from); - params['coinTo'] = _normalizeCurrency(to); - params['networkFrom'] = _networkFor(from); - params['networkTo'] = _networkFor(to); - } - final uri = Uri.https(apiBaseUrl, ratePath, params); - final response = await get(uri); - - if (response.statusCode != 200) { - throw Exception('Unexpected http status: ${response.statusCode}'); - } - - final responseJSON = json.decode(response.body) as Map; - return Limits(min: responseJSON['minAmount'] as double?); - } - - @override - Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { - final _request = request as ExolixRequest; - - final headers = {'Content-Type': 'application/json'}; - final body = { - 'coinFrom': _normalizeCurrency(_request.from), - 'coinTo': _normalizeCurrency(_request.to), - 'networkFrom': _networkFor(_request.from), - 'networkTo': _networkFor(_request.to), - 'withdrawalAddress': _request.address, - 'refundAddress': _request.refundAddress, - 'rateType': getRateType(isFixedRateMode), - 'apiToken': apiKey, - }; - - if (isFixedRateMode) { - body['withdrawalAmount'] = _request.toAmount; - } else { - body['amount'] = _request.fromAmount; - } - - final uri = Uri.https(apiBaseUrl, transactionsPath); - final response = await post(uri, headers: headers, body: json.encode(body)); - - if (response.statusCode == 400) { - final responseJSON = json.decode(response.body) as Map; - final errors = responseJSON['errors'] as Map; - final errorMessage = errors.values.join(', '); - throw Exception(errorMessage); - } - - if (response.statusCode != 200 && response.statusCode != 201) { - throw Exception('Unexpected http status: ${response.statusCode}'); - } - - final responseJSON = json.decode(response.body) as Map; - final id = responseJSON['id'] as String; - final inputAddress = responseJSON['depositAddress'] as String; - final refundAddress = responseJSON['refundAddress'] as String?; - final extraId = responseJSON['depositExtraId'] as String?; - final payoutAddress = responseJSON['withdrawalAddress'] as String; - final amount = responseJSON['amount'].toString(); - - return Trade( - id: id, - from: _request.from, - to: _request.to, - provider: description, - inputAddress: inputAddress, - refundAddress: refundAddress, - extraId: extraId, - createdAt: DateTime.now(), - amount: amount, - state: TradeState.created, - payoutAddress: payoutAddress); - } - - @override - Future findTradeById({required String id}) async { - final findTradeByIdPath = transactionsPath + '/$id'; - final uri = Uri.https(apiBaseUrl, findTradeByIdPath); - final response = await get(uri); - - if (response.statusCode == 404) { - throw TradeNotFoundException(id, provider: description); - } - - if (response.statusCode == 400) { - final responseJSON = json.decode(response.body) as Map; - final errors = responseJSON['errors'] as Map; - final errorMessage = errors.values.join(', '); - - throw TradeNotFoundException(id, provider: description, description: errorMessage); - } - - if (response.statusCode != 200) { - throw Exception('Unexpected http status: ${response.statusCode}'); - } - - final responseJSON = json.decode(response.body) as Map; - final coinFrom = responseJSON['coinFrom']['coinCode'] as String; - final from = CryptoCurrency.fromString(coinFrom); - final coinTo = responseJSON['coinTo']['coinCode'] as String; - final to = CryptoCurrency.fromString(coinTo); - final inputAddress = responseJSON['depositAddress'] as String; - final amount = responseJSON['amount'].toString(); - final status = responseJSON['status'] as String; - final state = TradeState.deserialize(raw: _prepareStatus(status)); - final extraId = responseJSON['depositExtraId'] as String?; - final outputTransaction = responseJSON['hashOut']['hash'] as String?; - final payoutAddress = responseJSON['withdrawalAddress'] as String; - - return Trade( - id: id, - from: from, - to: to, - provider: description, - inputAddress: inputAddress, - amount: amount, - state: state, - extraId: extraId, - outputTransaction: outputTransaction, - payoutAddress: payoutAddress); - } - - @override - Future fetchRate( - {required CryptoCurrency from, - required CryptoCurrency to, - required double amount, - required bool isFixedRateMode, - required bool isReceiveAmount}) async { - try { - if (amount == 0) { - return 0.0; - } - - final params = { - 'coinFrom': _normalizeCurrency(from), - 'coinTo': _normalizeCurrency(to), - 'networkFrom': _networkFor(from), - 'networkTo': _networkFor(to), - 'rateType': getRateType(isFixedRateMode), - }; - - if (isReceiveAmount) { - params['withdrawalAmount'] = amount.toString(); - } else { - params['amount'] = amount.toString(); - } - - final uri = Uri.https(apiBaseUrl, ratePath, params); - final response = await get(uri); - final responseJSON = json.decode(response.body) as Map; - - if (response.statusCode != 200) { - final message = responseJSON['message'] as String?; - throw Exception(message); - } - - final rate = responseJSON['rate'] as double; - - return rate; - } catch (e) { - print(e.toString()); - return 0.0; - } - } - - String _prepareStatus(String status) { - switch (status) { - case 'deleted': - case 'error': - return 'overdue'; - default: - return status; - } - } - - String _networkFor(CryptoCurrency currency) { - switch (currency) { - case CryptoCurrency.arb: - return 'ARBITRUM'; - default: - return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title; - } - } - - String _normalizeCurrency(CryptoCurrency currency) { - switch (currency) { - case CryptoCurrency.nano: - return 'XNO'; - case CryptoCurrency.bttc: - return 'BTT'; - case CryptoCurrency.zec: - return 'ZEC'; - default: - return currency.title; - } - } - - String _normalizeTag(String tag) { - switch (tag) { - case 'POLY': - return 'Polygon'; - default: - return tag; - } - } -} diff --git a/lib/exchange/exolix/exolix_request.dart b/lib/exchange/exolix/exolix_request.dart deleted file mode 100644 index e97ffa386..000000000 --- a/lib/exchange/exolix/exolix_request.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cake_wallet/exchange/trade_request.dart'; - -class ExolixRequest extends TradeRequest { - ExolixRequest( - {required this.from, - required this.to, - required this.address, - required this.fromAmount, - required this.toAmount, - required this.refundAddress}); - - CryptoCurrency from; - CryptoCurrency to; - String address; - String fromAmount; - String toAmount; - String refundAddress; -} diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index ebf74ce7a..98737339c 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -35,15 +35,6 @@ class TradeState extends EnumerableItem with Serializable { static const completed = TradeState(raw: 'completed', title: 'Completed'); static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); - static const wait = TradeState(raw: 'wait', title: 'Waiting'); - static const overdue = TradeState(raw: 'overdue', title: 'Overdue'); - static const refund = TradeState(raw: 'refund', title: 'Refund'); - static const refunded = TradeState(raw: 'refunded', title: 'Refunded'); - static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation'); - static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed'); - static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); - static const sending = TradeState(raw: 'sending', title: 'Sending'); - static const success = TradeState(raw: 'success', title: 'Success'); static TradeState deserialize({required String raw}) { switch (raw) { case 'pending': @@ -86,24 +77,6 @@ class TradeState extends EnumerableItem with Serializable { return failed; case 'completed': return completed; - case 'wait': - return wait; - case 'overdue': - return overdue; - case 'refund': - return refund; - case 'refunded': - return refunded; - case 'confirmation': - return confirmation; - case 'confirmed': - return confirmed; - case 'exchanging': - return exchanging; - case 'sending': - return sending; - case 'success': - return success; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); } diff --git a/lib/nano/cw_nano.dart b/lib/nano/cw_nano.dart index 0c52a24d6..cd0f0ca8a 100644 --- a/lib/nano/cw_nano.dart +++ b/lib/nano/cw_nano.dart @@ -178,6 +178,11 @@ class CWNano extends Nano { BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) { return (transactionInfo as NanoTransactionInfo).amountRaw; } + + @override + String getRepresentative(Object wallet) { + return (wallet as NanoWallet).representative; + } } class CWNanoUtil extends NanoUtil { @@ -308,14 +313,19 @@ class CWNanoUtil extends NanoUtil { /// @param raw 100000000000000000000000000000 /// @return Decimal value 1.000000000000000000000000000000 /// - @override - Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) { + Decimal _getRawAsDecimal(String? raw, BigInt? rawPerCur) { rawPerCur ??= rawPerNano; final Decimal amount = Decimal.parse(raw.toString()); final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal(); return result; } + @override + String getRawAsDecimalString(String? raw, BigInt? rawPerCur) { + final Decimal result = _getRawAsDecimal(raw, rawPerCur); + return result.toString(); + } + @override String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) { Decimal bigger = input.shift(digits); @@ -332,7 +342,7 @@ class CWNanoUtil extends NanoUtil { @override String getRawAsUsableString(String? raw, BigInt rawPerCur) { final String res = - truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9); + truncateDecimal(_getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9); if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") { return "0"; @@ -361,7 +371,7 @@ class CWNanoUtil extends NanoUtil { @override String getRawAccuracy(String? raw, BigInt rawPerCur) { final String rawString = getRawAsUsableString(raw, rawPerCur); - final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString(); + final String rawDecimalString = _getRawAsDecimal(raw, rawPerCur).toString(); if (raw == null || raw.isEmpty || raw == "0") { return ""; diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index 7f570b98e..a42593f24 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -94,9 +94,6 @@ class TradeRow extends StatelessWidget { borderRadius: BorderRadius.circular(50), child: Image.asset('assets/images/trocador.png', width: 36, height: 36)); break; - case ExchangeProviderDescription.exolix: - image = Image.asset('assets/images/exolix.png', width: 36, height: 36); - break; default: image = null; } diff --git a/lib/src/screens/nano/nano_change_rep_page.dart b/lib/src/screens/nano/nano_change_rep_page.dart index b11b82257..bf541c39a 100644 --- a/lib/src/screens/nano/nano_change_rep_page.dart +++ b/lib/src/screens/nano/nano_change_rep_page.dart @@ -6,7 +6,6 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cw_nano/nano_wallet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -18,7 +17,7 @@ class NanoChangeRepPage extends BasePage { NanoChangeRepPage(WalletBase wallet) : _wallet = wallet, _addressController = TextEditingController() { - _addressController.text = (wallet as NanoWallet).representative; + _addressController.text = nano!.getRepresentative(wallet); } final TextEditingController _addressController; diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 573778ed2..98ca9342c 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -85,7 +85,7 @@ class ConnectionSyncPage extends BasePage { ); }, ), - if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[ + if (dashboardViewModel.wallet.type == WalletType.ethereum && DeviceInfo.instance.isMobile) ...[ WalletConnectTile( onTap: () async { Navigator.of(context).push( diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index 639efacb6..e814ff44b 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -1,5 +1,8 @@ +import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/wallet_base.dart'; @@ -40,5 +43,9 @@ abstract class AppStoreBase with Store { this.wallet?.close(); this.wallet = wallet; this.wallet!.setExceptionHandler(ExceptionHandler.onError); + + if (wallet.type == WalletType.ethereum) { + getIt.get().init(); + } } } diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index 799e8b951..c772a35d6 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -13,8 +13,7 @@ abstract class TradeFilterStoreBase with Store { displaySideShift = true, displayMorphToken = true, displaySimpleSwap = true, - displayTrocador = true, - displayExolix = true; + displayTrocador = true; @observable bool displayXMRTO; @@ -34,11 +33,8 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayTrocador; - @observable - bool displayExolix; - @computed - bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix; + bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador; @action void toggleDisplayExchange(ExchangeProviderDescription provider) { @@ -60,10 +56,7 @@ abstract class TradeFilterStoreBase with Store { break; case ExchangeProviderDescription.trocador: displayTrocador = !displayTrocador; - break; - case ExchangeProviderDescription.exolix: - displayExolix = !displayExolix; - break; + break; case ExchangeProviderDescription.all: if (displayAllTrades) { displayChangeNow = false; @@ -72,7 +65,6 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = false; displaySimpleSwap = false; displayTrocador = false; - displayExolix = false; } else { displayChangeNow = true; displaySideShift = true; @@ -80,7 +72,6 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = true; displaySimpleSwap = true; displayTrocador = true; - displayExolix = true; } break; } @@ -107,8 +98,7 @@ abstract class TradeFilterStoreBase with Store { ||(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) - ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) - ||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix)) + ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)) .toList() : _trades; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 03f0aa9a8..f6eb7f244 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -98,11 +98,6 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.trocador.title, onChanged: () => tradeFilterStore .toggleDisplayExchange(ExchangeProviderDescription.trocador)), - FilterItem( - value: () => tradeFilterStore.displayExolix, - caption: ExchangeProviderDescription.exolix.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.exolix)), ] }, subname = '', diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index e5fe354a0..e7e640afc 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -92,9 +92,8 @@ class TransactionListItem extends ActionListItem with Keyable { break; case WalletType.nano: amount = calculateFiatAmountRaw( - cryptoAmount: nanoUtil! - .getRawAsDecimal(nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano) - .toDouble(), + cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString( + nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), price: price); break; case WalletType.ethereum: diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 346844171..cfabd994f 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; @@ -54,9 +53,6 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; - case ExchangeProviderDescription.exolix: - _provider = ExolixExchangeProvider(); - break; } _updateItems(); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 47b408bc2..b438e9b74 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -6,8 +6,6 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; -import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; -import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -152,7 +150,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider(useTorOnly: _useTorOnly), - ExolixExchangeProvider(), ]; @observable @@ -549,17 +546,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with amount = isFixedRateMode ? receiveAmount : depositAmount; } - if (provider is ExolixExchangeProvider) { - request = ExolixRequest( - from: depositCurrency, - to: receiveCurrency, - fromAmount: depositAmount.replaceAll(',', '.'), - toAmount: receiveAmount.replaceAll(',', '.'), - refundAddress: depositAddress, - address: receiveAddress); - amount = isFixedRateMode ? receiveAmount : depositAmount; - } - amount = amount.replaceAll(',', '.'); if (limitsState is LimitsLoadedSuccessfully) { diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index faebed11f..2ba6dc784 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -278,7 +278,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor List conditionsList = []; for (var output in outputs) { - final show = checkThroughChecksToDisplayTOTP(output.address); + + final show = checkThroughChecksToDisplayTOTP(output.extractedAddress); conditionsList.add(show); } @@ -427,9 +428,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } } - String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) { + String translateErrorMessage( + String error, + WalletType walletType, + CryptoCurrency currency, + ) { if (walletType == WalletType.ethereum || walletType == WalletType.haven) { - if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for')) { + if (error.contains('gas required exceeds allowance') || + error.contains('insufficient funds for')) { return S.current.do_not_have_enough_gas_asset(currency.toString()); } } diff --git a/lib/view_model/set_up_2fa_viewmodel.dart b/lib/view_model/set_up_2fa_viewmodel.dart index eacd3128d..b8fc40f23 100644 --- a/lib/view_model/set_up_2fa_viewmodel.dart +++ b/lib/view_model/set_up_2fa_viewmodel.dart @@ -201,38 +201,15 @@ abstract class Setup2FAViewModelBase with Store { @observable ObservableList selected2FASettings; - //! The code here works, but can be improved - //! Still trying out various ways to improve it - @action - void selectCakePreset(Cake2FAPresetsOptions cake2FAPreset) { - // The tabs are ordered in the format [Narrow || Normal || Verbose] - // Where Narrow = 0, Normal = 1 and Verbose = 2 - switch (cake2FAPreset) { - case Cake2FAPresetsOptions.narrow: - activateCake2FANarrowPreset(); - break; - case Cake2FAPresetsOptions.normal: - activateCake2FANormalPreset(); - break; - case Cake2FAPresetsOptions.aggressive: - activateCake2FAAggressivePreset(); - break; - default: - activateCake2FANormalPreset(); - } - } - @action void checkIfTheCurrentSettingMatchesAnyOfThePresets() { final hasNormalPreset = checkIfTheNormalPresetIsPresent(); final hasNarrowPreset = checkIfTheNarrowPresetIsPresent(); final hasVerbosePreset = checkIfTheVerbosePresetIsPresent(); - if (hasNormalPreset || hasNarrowPreset || hasVerbosePreset) { - unhighlightTabs = false; - } else { - unhighlightTabs = true; - } + if (hasNormalPreset || hasNarrowPreset || hasVerbosePreset) return; + + noCake2FAPresetSelected(); } @action @@ -288,32 +265,8 @@ abstract class Setup2FAViewModelBase with Store { } @action - void activateCake2FANormalPreset() { - _settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.normal; - setAllControlsToFalse(); - switchShouldRequireTOTP2FAForSendsToNonContact(true); - switchShouldRequireTOTP2FAForSendsToContact(true); - switchShouldRequireTOTP2FAForSendsToInternalWallets(true); - switchShouldRequireTOTP2FAForExchangesToInternalWallets(true); - switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(true); - } - - @action - void activateCake2FANarrowPreset() { - _settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.narrow; - setAllControlsToFalse(); - switchShouldRequireTOTP2FAForSendsToNonContact(true); - switchShouldRequireTOTP2FAForAddingContacts(true); - switchShouldRequireTOTP2FAForCreatingNewWallet(true); - switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(true); - } - - @action - void activateCake2FAAggressivePreset() { - _settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.aggressive; - setAllControlsToFalse(); - switchShouldRequireTOTP2FAForAccessingWallet(true); - switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(true); + void noCake2FAPresetSelected() { + _settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.none; } @action @@ -330,90 +283,106 @@ abstract class Setup2FAViewModelBase with Store { unhighlightTabs = false; } + final Map> presetsMap = { + Cake2FAPresetsOptions.normal: [ + VerboseControlSettings.sendsToContacts, + VerboseControlSettings.sendsToNonContacts, + VerboseControlSettings.sendsToInternalWallets, + VerboseControlSettings.securityAndBackupSettings, + VerboseControlSettings.exchangesToInternalWallets + ], + Cake2FAPresetsOptions.narrow: [ + VerboseControlSettings.addingContacts, + VerboseControlSettings.sendsToNonContacts, + VerboseControlSettings.creatingNewWallets, + VerboseControlSettings.securityAndBackupSettings, + ], + Cake2FAPresetsOptions.aggressive: [ + VerboseControlSettings.accessWallet, + VerboseControlSettings.securityAndBackupSettings, + ], + Cake2FAPresetsOptions.none: [], + }; + @action - void switchShouldRequireTOTP2FAForAccessingWallet(bool value) { - _settingsStore.shouldRequireTOTP2FAForAccessingWallet = value; - if (value) { - selected2FASettings.add(VerboseControlSettings.accessWallet); - } else { - selected2FASettings.remove(VerboseControlSettings.accessWallet); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + void selectCakePreset(Cake2FAPresetsOptions preset) { + presetsMap[preset]?.forEach(toggleControl); + _settingsStore.selectedCake2FAPreset = preset; + } + + @action + void toggleControl(VerboseControlSettings control, [bool value = true]) { + final methodsMap = { + VerboseControlSettings.sendsToContacts: switchShouldRequireTOTP2FAForSendsToContact, + VerboseControlSettings.accessWallet: switchShouldRequireTOTP2FAForAccessingWallet, + VerboseControlSettings.addingContacts: switchShouldRequireTOTP2FAForAddingContacts, + VerboseControlSettings.creatingNewWallets: switchShouldRequireTOTP2FAForCreatingNewWallet, + VerboseControlSettings.sendsToNonContacts: switchShouldRequireTOTP2FAForSendsToNonContact, + VerboseControlSettings.sendsToInternalWallets: + switchShouldRequireTOTP2FAForSendsToInternalWallets, + VerboseControlSettings.securityAndBackupSettings: + switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings, + VerboseControlSettings.exchangesToInternalWallets: + switchShouldRequireTOTP2FAForExchangesToInternalWallets, + }; + + methodsMap[control]?.call(value); } @action void switchShouldRequireTOTP2FAForSendsToContact(bool value) { _settingsStore.shouldRequireTOTP2FAForSendsToContact = value; - if (value) { - selected2FASettings.add(VerboseControlSettings.sendsToContacts); - } else { - selected2FASettings.remove(VerboseControlSettings.sendsToContacts); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + updateSelectedSettings(VerboseControlSettings.sendsToContacts, value); + } + + @action + void switchShouldRequireTOTP2FAForAccessingWallet(bool value) { + _settingsStore.shouldRequireTOTP2FAForAccessingWallet = value; + updateSelectedSettings(VerboseControlSettings.accessWallet, value); } @action void switchShouldRequireTOTP2FAForSendsToNonContact(bool value) { _settingsStore.shouldRequireTOTP2FAForSendsToNonContact = value; - if (value) { - selected2FASettings.add(VerboseControlSettings.sendsToNonContacts); - } else { - selected2FASettings.remove(VerboseControlSettings.sendsToNonContacts); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + updateSelectedSettings(VerboseControlSettings.sendsToNonContacts, value); } @action void switchShouldRequireTOTP2FAForSendsToInternalWallets(bool value) { _settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets = value; - if (value) { - selected2FASettings.add(VerboseControlSettings.sendsToInternalWallets); - } else { - selected2FASettings.remove(VerboseControlSettings.sendsToInternalWallets); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + updateSelectedSettings(VerboseControlSettings.sendsToInternalWallets, value); } @action void switchShouldRequireTOTP2FAForExchangesToInternalWallets(bool value) { _settingsStore.shouldRequireTOTP2FAForExchangesToInternalWallets = value; - if (value) { - selected2FASettings.add(VerboseControlSettings.exchangesToInternalWallets); - } else { - selected2FASettings.remove(VerboseControlSettings.exchangesToInternalWallets); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + updateSelectedSettings(VerboseControlSettings.exchangesToInternalWallets, value); } @action void switchShouldRequireTOTP2FAForAddingContacts(bool value) { _settingsStore.shouldRequireTOTP2FAForAddingContacts = value; - if (value) - selected2FASettings.add(VerboseControlSettings.addingContacts); - else { - selected2FASettings.remove(VerboseControlSettings.addingContacts); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + updateSelectedSettings(VerboseControlSettings.addingContacts, value); } @action void switchShouldRequireTOTP2FAForCreatingNewWallet(bool value) { _settingsStore.shouldRequireTOTP2FAForCreatingNewWallets = value; - if (value) { - selected2FASettings.add(VerboseControlSettings.creatingNewWallets); - } else { - selected2FASettings.remove(VerboseControlSettings.creatingNewWallets); - } - checkIfTheCurrentSettingMatchesAnyOfThePresets(); + updateSelectedSettings(VerboseControlSettings.creatingNewWallets, value); } @action void switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(bool value) { _settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings = value; - if (value) - selected2FASettings.add(VerboseControlSettings.securityAndBackupSettings); - else { - selected2FASettings.remove(VerboseControlSettings.securityAndBackupSettings); + updateSelectedSettings(VerboseControlSettings.securityAndBackupSettings, value); + } + + @action + void updateSelectedSettings(VerboseControlSettings control, bool value) { + if (value) { + selected2FASettings.add(control); + } else { + selected2FASettings.remove(control); } checkIfTheCurrentSettingMatchesAnyOfThePresets(); } diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index ccef76154..d3b14c59b 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -53,11 +53,6 @@ abstract class SupportViewModelBase with Store { icon: 'assets/images/simpleSwap.png', linkTitle: 'support@simpleswap.io', link: 'mailto:support@simpleswap.io'), - LinkListItem( - title: 'Exolix', - icon: 'assets/images/exolix.png', - linkTitle: 'support@exolix.com', - link: 'mailto:support@exolix.com'), if (!isMoneroOnly) ... [ LinkListItem( title: 'Wyre', diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index 393629237..c0b1ac461 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; -import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -55,9 +54,6 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; - case ExchangeProviderDescription.exolix: - _provider = ExolixExchangeProvider(); - break; } _updateItems(); @@ -161,12 +157,6 @@ abstract class TradeDetailsViewModelBase with Store { items.add(StandartListItem( title: '${trade.providerName} ${S.current.password}', value: trade.password ?? '')); } - - if (trade.provider == ExchangeProviderDescription.exolix) { - final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}'; - items.add( - TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); - } } void _launchUrl(String url) { diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 92c8a0559..771c1f89a 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.2" -MONERO_COM_BUILD_NUMBER=58 +MONERO_COM_VERSION="1.7.0" +MONERO_COM_BUILD_NUMBER=61 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.9.2" -CAKEWALLET_BUILD_NUMBER=171 +CAKEWALLET_VERSION="4.10.0" +CAKEWALLET_BUILD_NUMBER=175 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 078688918..aa3116418 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.2" -MONERO_COM_BUILD_NUMBER=56 +MONERO_COM_VERSION="1.7.0" +MONERO_COM_BUILD_NUMBER=59 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.9.2" -CAKEWALLET_BUILD_NUMBER=185 +CAKEWALLET_VERSION="4.10.0" +CAKEWALLET_BUILD_NUMBER=189 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 5103d42b2..d649d752d 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.2.2" -CAKEWALLET_BUILD_NUMBER=33 +CAKEWALLET_VERSION="1.3.0" +CAKEWALLET_BUILD_NUMBER=36 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/tool/configure.dart b/tool/configure.dart index eff75eeae..f7b9dc126 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -639,6 +639,7 @@ abstract class Nano { Future changeRep(Object wallet, String address); Future updateTransactions(Object wallet); BigInt getTransactionAmountRaw(TransactionInfo transactionInfo); + String getRepresentative(Object wallet); } abstract class NanoAccountList { @@ -672,8 +673,7 @@ abstract class NanoUtil { BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); - Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur); - String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}); + String getRawAsDecimalString(String? raw, BigInt? rawPerCur); String getRawAsUsableString(String? raw, BigInt rawPerCur); String getRawAccuracy(String? raw, BigInt rawPerCur); String getAmountAsRaw(String amount, BigInt rawPerCur); @@ -693,7 +693,7 @@ abstract class NanoUtil { } """; - const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil = CWNanoUtil();\n'; + const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n'; const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n'; final output = '$nanoCommonHeaders\n' + diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index a8c6a6166..fc2cc7fa8 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -32,7 +32,6 @@ class SecretKey { SecretKey('fiatApiKey', () => ''), SecretKey('payfuraApiKey', () => ''), SecretKey('chatwootWebsiteToken', () => ''), - SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), SecretKey('walletConnectProjectId', () => ''), From dd81db74e8b7571d4163bc151767a428354f9821 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 9 Oct 2023 14:34:15 -0400 Subject: [PATCH 06/16] fix in strings_fr.arb (#1119) --- res/values/strings_fr.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f2e336040..f171cbb68 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -514,7 +514,7 @@ "active_cards": "Cartes actives", "delete_account": "Supprimer le compte", "cards": "Cartes", - "active": "Actives, + "active": "Actives", "redeemed": "Converties", "gift_card_balance_note": "Les cartes-cadeaux avec un solde restant apparaîtront ici", "gift_card_redeemed_note": "Les cartes-cadeaux que vous avez utilisées apparaîtront ici", From 9afb8c230f21eface442b9755119718bca472f16 Mon Sep 17 00:00:00 2001 From: JanekGOOGLE140 <109436710+JanekGOOGLE140@users.noreply.github.com> Date: Tue, 10 Oct 2023 23:59:10 +0200 Subject: [PATCH 07/16] Update of strings_pl.arb (#1121) * Update strings_pl.arb * Update strings_pl.arb --- res/values/strings_pl.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 6e8e863a7..f243a2343 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -292,7 +292,7 @@ "available_balance": "Dostępne środki", "hidden_balance": "Ukryte saldo", "sync_status_syncronizing": "SYNCHRONIZACJA", - "sync_status_syncronized": "SYNCHRONIZOWANIE", + "sync_status_syncronized": "ZSYNCHRONIZOWANO", "sync_status_not_connected": "NIE POŁĄCZONY", "sync_status_starting_sync": "ROZPOCZĘCIE SYNCHRONIZACJI", "sync_status_failed_connect": "POŁĄCZENIE NIEUDANE", From a035872fc99c3f2e8eac4438ef0628fb3644f995 Mon Sep 17 00:00:00 2001 From: Procyon Lotor <110021993+ProcyonLotor123@users.noreply.github.com> Date: Thu, 12 Oct 2023 03:20:19 +0300 Subject: [PATCH 08/16] Exolix integration: bugfix apiToken on fetchRates (#1120) * Add Exolix exchange integration * update tx payload * remove import * Improve mapping * Additional fixes * fix apiBaseUrl * Update trade_details_view_model.dart * Update exolix_exchange_provider.dart * Fix status URL * Fix fetch rates API error handling update limits API to use a valid amount and validate on success status code * bugfix added apiToken for fetchRate --------- Co-authored-by: Justin Ehrenhofer Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build.yml | 1 + assets/images/exolix.png | Bin 0 -> 1203 bytes .../exchange_provider_description.dart | 7 +- .../exolix/exolix_exchange_provider.dart | 295 ++++++++++++++++++ lib/exchange/exolix/exolix_request.dart | 20 ++ lib/exchange/trade_state.dart | 27 ++ .../screens/dashboard/widgets/trade_row.dart | 3 + lib/store/dashboard/trade_filter_store.dart | 18 +- .../dashboard/dashboard_view_model.dart | 5 + .../exchange/exchange_trade_view_model.dart | 4 + .../exchange/exchange_view_model.dart | 14 + lib/view_model/support_view_model.dart | 5 + lib/view_model/trade_details_view_model.dart | 10 + tool/utils/secret_key.dart | 1 + 14 files changed, 405 insertions(+), 5 deletions(-) create mode 100644 assets/images/exolix.png create mode 100644 lib/exchange/exolix/exolix_exchange_provider.dart create mode 100644 lib/exchange/exolix/exolix_request.dart diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 6b12911b6..af03c5e30 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -128,6 +128,7 @@ jobs: echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart diff --git a/assets/images/exolix.png b/assets/images/exolix.png new file mode 100644 index 0000000000000000000000000000000000000000..29e5f2db1d659dc3ca64858d800287c29b0a49fa GIT binary patch literal 1203 zcmV;k1WfyhP)Px(ZAnByRA@upT3t+3M-={MH7sigx|iVHa%-9*rD;eiDv(r5E2W5NYg6q{jN0l$ z+cY7PkiIm<#56^T51O>5sXnx%8dB;{OlVT-Pg|@HO{iU3kPSPJ+FaGHkbfx&^k z^U0W+*a2}bR6Yd3mjrNH!J)t?D0U3qQ4d+sXF8S$NDGx?AlPViQUkrCey{p204jnj zL?B02KBK@FA`^(p5}?87RU3KrI0+OuB{?t%z{Lw}Q!e25y4*nbs2PqafgF#gl+rnW znE_45_k=jgeEqloPK2G1Ku)f*jXLn%bQs5rNkZ7}8&I2#jWP5vwM_4iM4r#J+(13SXI#i^9@dtI5a2YU(uNmhna7;QxP2mD@jUbK>q zKq}(AsKv4JvG;_IfFvtIx1e2Ptm^x{YQ*lyACj#62;gn2pefa(IPSlrzNO2B?5;lB zx_!mZizPA!tl6s8U+PSlsD*by+X0qU5`u$vSFs=~3#Tf|?ZTk^%@542F2Ng5=2<3jsJjPET~|%@St>s0RWmgLcHJj-wyt&6a5@~OM#KvE zM#oTk>MXY&b0aeOlKO*Ni$icOA-MM18V*UlNPgSTLOL`=%ZVf_tpMH;(&P|cEz9_( zY#E2-gh<8N4%{4Occ{>4C2m>q8NFeJpeW#3RgC_QOA)oUmsX-^ezqx0bI%QYex=J) zE>=K$V<*f1i+62D{-#%Pvgmz;#>cg}C^KCF-w0xpk)nvSnD z%usM#BM}bb?u-mvtgeh}Ad4VR3;KO5kbMG_V#Bo-9d6(03;45kf8?{e!UEJi`^*#) zRll9X^#>0vuRc?>O`x)^3G)jVO)=uvjXN=PxhL}Zh3ZO385xoPTW;J!!@pN-8E>1w z6P3&H!q@fjjUd2@!Y#TgN$=}5TB>m`7({Wqb-$0E(DDuthXZeR9ZtA{!TZB#EBPQI zQzM01@^42+sqOSoJs97cZtz$#+g zV@bn%SiH5GTfV)b4#RzSx#a}}TZL1J%v?9to9bh|=%%LT743IaSe&ytDEO_oI%QkfGIfCwHv4qWNGF>{&^PuspT8b41Ke23l zSdm2~7M<)4-Gj!|jVMLQ-jB81#`d5T7eBTA_=E0AsS%i-n!jzn1^}#=pgjR*Pg#Yr zN&CXqr)3ePQ%{CSH#56U!b+I8_%}Z`CA*$vtl7DczP<+U={jod Rkx~Ev002ovPDHLkV1hCPI;#Kx literal 0 HcmV?d00001 diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index e545f69ce..151d018e0 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< static const trocador = ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); - static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: ''); + static const exolix = + ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png'); + + static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); static ExchangeProviderDescription deserialize({required int raw}) { switch (raw) { @@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< case 5: return trocador; case 6: + return exolix; + case 7: return all; default: throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); diff --git a/lib/exchange/exolix/exolix_exchange_provider.dart b/lib/exchange/exolix/exolix_exchange_provider.dart new file mode 100644 index 000000000..01b8ecc2b --- /dev/null +++ b/lib/exchange/exolix/exolix_exchange_provider.dart @@ -0,0 +1,295 @@ +import 'dart:convert'; +import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/exchange_pair.dart'; +import 'package:cake_wallet/exchange/exchange_provider.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; + +class ExolixExchangeProvider extends ExchangeProvider { + ExolixExchangeProvider() : super(pairList: _supportedPairs()); + + static final apiKey = secrets.exolixApiKey; + static const apiBaseUrl = 'exolix.com'; + static const transactionsPath = '/api/v2/transactions'; + static const ratePath = '/api/v2/rate'; + + static const List _notSupported = [ + CryptoCurrency.usdt, + CryptoCurrency.xhv, + CryptoCurrency.btt, + CryptoCurrency.firo, + CryptoCurrency.zaddr, + CryptoCurrency.xvg, + CryptoCurrency.kmd, + CryptoCurrency.paxg, + CryptoCurrency.rune, + CryptoCurrency.scrt, + CryptoCurrency.btcln, + CryptoCurrency.cro, + CryptoCurrency.ftm, + CryptoCurrency.frax, + CryptoCurrency.gusd, + CryptoCurrency.gtc, + CryptoCurrency.weth, + ]; + + static List _supportedPairs() { + final supportedCurrencies = + CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList(); + + return supportedCurrencies + .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true))) + .expand((i) => i) + .toList(); + } + + @override + String get title => 'Exolix'; + + @override + bool get isAvailable => true; + + @override + bool get isEnabled => true; + + @override + bool get supportsFixedRate => true; + + @override + ExchangeProviderDescription get description => ExchangeProviderDescription.exolix; + + @override + Future checkIsAvailable() async => true; + + static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float'; + + @override + Future fetchLimits( + {required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode}) async { + final params = { + 'rateType': getRateType(isFixedRateMode), + 'amount': '1', + }; + if (isFixedRateMode) { + params['coinFrom'] = _normalizeCurrency(to); + params['coinTo'] = _normalizeCurrency(from); + params['networkFrom'] = _networkFor(to); + params['networkTo'] = _networkFor(from); + } else { + params['coinFrom'] = _normalizeCurrency(from); + params['coinTo'] = _normalizeCurrency(to); + params['networkFrom'] = _networkFor(from); + params['networkTo'] = _networkFor(to); + } + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); + + if (response.statusCode != 200) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + return Limits(min: responseJSON['minAmount'] as double?); + } + + @override + Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + final _request = request as ExolixRequest; + + final headers = {'Content-Type': 'application/json'}; + final body = { + 'coinFrom': _normalizeCurrency(_request.from), + 'coinTo': _normalizeCurrency(_request.to), + 'networkFrom': _networkFor(_request.from), + 'networkTo': _networkFor(_request.to), + 'withdrawalAddress': _request.address, + 'refundAddress': _request.refundAddress, + 'rateType': getRateType(isFixedRateMode), + 'apiToken': apiKey, + }; + + if (isFixedRateMode) { + body['withdrawalAmount'] = _request.toAmount; + } else { + body['amount'] = _request.fromAmount; + } + + final uri = Uri.https(apiBaseUrl, transactionsPath); + final response = await post(uri, headers: headers, body: json.encode(body)); + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final errors = responseJSON['errors'] as Map; + final errorMessage = errors.values.join(', '); + throw Exception(errorMessage); + } + + if (response.statusCode != 200 && response.statusCode != 201) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + final id = responseJSON['id'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final refundAddress = responseJSON['refundAddress'] as String?; + final extraId = responseJSON['depositExtraId'] as String?; + final payoutAddress = responseJSON['withdrawalAddress'] as String; + final amount = responseJSON['amount'].toString(); + + return Trade( + id: id, + from: _request.from, + to: _request.to, + provider: description, + inputAddress: inputAddress, + refundAddress: refundAddress, + extraId: extraId, + createdAt: DateTime.now(), + amount: amount, + state: TradeState.created, + payoutAddress: payoutAddress); + } + + @override + Future findTradeById({required String id}) async { + final findTradeByIdPath = transactionsPath + '/$id'; + final uri = Uri.https(apiBaseUrl, findTradeByIdPath); + final response = await get(uri); + + if (response.statusCode == 404) { + throw TradeNotFoundException(id, provider: description); + } + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final errors = responseJSON['errors'] as Map; + final errorMessage = errors.values.join(', '); + + throw TradeNotFoundException(id, provider: description, description: errorMessage); + } + + if (response.statusCode != 200) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + final coinFrom = responseJSON['coinFrom']['coinCode'] as String; + final from = CryptoCurrency.fromString(coinFrom); + final coinTo = responseJSON['coinTo']['coinCode'] as String; + final to = CryptoCurrency.fromString(coinTo); + final inputAddress = responseJSON['depositAddress'] as String; + final amount = responseJSON['amount'].toString(); + final status = responseJSON['status'] as String; + final state = TradeState.deserialize(raw: _prepareStatus(status)); + final extraId = responseJSON['depositExtraId'] as String?; + final outputTransaction = responseJSON['hashOut']['hash'] as String?; + final payoutAddress = responseJSON['withdrawalAddress'] as String; + + return Trade( + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: amount, + state: state, + extraId: extraId, + outputTransaction: outputTransaction, + payoutAddress: payoutAddress); + } + + @override + Future fetchRate( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode, + required bool isReceiveAmount}) async { + try { + if (amount == 0) { + return 0.0; + } + + final params = { + 'coinFrom': _normalizeCurrency(from), + 'coinTo': _normalizeCurrency(to), + 'networkFrom': _networkFor(from), + 'networkTo': _networkFor(to), + 'rateType': getRateType(isFixedRateMode), + 'apiToken': apiKey, + }; + + if (isReceiveAmount) { + params['withdrawalAmount'] = amount.toString(); + } else { + params['amount'] = amount.toString(); + } + + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); + final responseJSON = json.decode(response.body) as Map; + + if (response.statusCode != 200) { + final message = responseJSON['message'] as String?; + throw Exception(message); + } + + final rate = responseJSON['rate'] as double; + + return rate; + } catch (e) { + print(e.toString()); + return 0.0; + } + } + + String _prepareStatus(String status) { + switch (status) { + case 'deleted': + case 'error': + return 'overdue'; + default: + return status; + } + } + + String _networkFor(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.arb: + return 'ARBITRUM'; + default: + return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title; + } + } + + String _normalizeCurrency(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.nano: + return 'XNO'; + case CryptoCurrency.bttc: + return 'BTT'; + case CryptoCurrency.zec: + return 'ZEC'; + default: + return currency.title; + } + } + + String _normalizeTag(String tag) { + switch (tag) { + case 'POLY': + return 'Polygon'; + default: + return tag; + } + } +} diff --git a/lib/exchange/exolix/exolix_request.dart b/lib/exchange/exolix/exolix_request.dart new file mode 100644 index 000000000..e97ffa386 --- /dev/null +++ b/lib/exchange/exolix/exolix_request.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; + +class ExolixRequest extends TradeRequest { + ExolixRequest( + {required this.from, + required this.to, + required this.address, + required this.fromAmount, + required this.toAmount, + required this.refundAddress}); + + CryptoCurrency from; + CryptoCurrency to; + String address; + String fromAmount; + String toAmount; + String refundAddress; +} diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index 98737339c..ebf74ce7a 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -35,6 +35,15 @@ class TradeState extends EnumerableItem with Serializable { static const completed = TradeState(raw: 'completed', title: 'Completed'); static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); + static const wait = TradeState(raw: 'wait', title: 'Waiting'); + static const overdue = TradeState(raw: 'overdue', title: 'Overdue'); + static const refund = TradeState(raw: 'refund', title: 'Refund'); + static const refunded = TradeState(raw: 'refunded', title: 'Refunded'); + static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation'); + static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed'); + static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); + static const sending = TradeState(raw: 'sending', title: 'Sending'); + static const success = TradeState(raw: 'success', title: 'Success'); static TradeState deserialize({required String raw}) { switch (raw) { case 'pending': @@ -77,6 +86,24 @@ class TradeState extends EnumerableItem with Serializable { return failed; case 'completed': return completed; + case 'wait': + return wait; + case 'overdue': + return overdue; + case 'refund': + return refund; + case 'refunded': + return refunded; + case 'confirmation': + return confirmation; + case 'confirmed': + return confirmed; + case 'exchanging': + return exchanging; + case 'sending': + return sending; + case 'success': + return success; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); } diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index a42593f24..7f570b98e 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget { borderRadius: BorderRadius.circular(50), child: Image.asset('assets/images/trocador.png', width: 36, height: 36)); break; + case ExchangeProviderDescription.exolix: + image = Image.asset('assets/images/exolix.png', width: 36, height: 36); + break; default: image = null; } diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index c772a35d6..799e8b951 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -13,7 +13,8 @@ abstract class TradeFilterStoreBase with Store { displaySideShift = true, displayMorphToken = true, displaySimpleSwap = true, - displayTrocador = true; + displayTrocador = true, + displayExolix = true; @observable bool displayXMRTO; @@ -33,8 +34,11 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayTrocador; + @observable + bool displayExolix; + @computed - bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador; + bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix; @action void toggleDisplayExchange(ExchangeProviderDescription provider) { @@ -56,7 +60,10 @@ abstract class TradeFilterStoreBase with Store { break; case ExchangeProviderDescription.trocador: displayTrocador = !displayTrocador; - break; + break; + case ExchangeProviderDescription.exolix: + displayExolix = !displayExolix; + break; case ExchangeProviderDescription.all: if (displayAllTrades) { displayChangeNow = false; @@ -65,6 +72,7 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = false; displaySimpleSwap = false; displayTrocador = false; + displayExolix = false; } else { displayChangeNow = true; displaySideShift = true; @@ -72,6 +80,7 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = true; displaySimpleSwap = true; displayTrocador = true; + displayExolix = true; } break; } @@ -98,7 +107,8 @@ abstract class TradeFilterStoreBase with Store { ||(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) - ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)) + ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) + ||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix)) .toList() : _trades; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index f6eb7f244..03f0aa9a8 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -98,6 +98,11 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.trocador.title, onChanged: () => tradeFilterStore .toggleDisplayExchange(ExchangeProviderDescription.trocador)), + FilterItem( + value: () => tradeFilterStore.displayExolix, + caption: ExchangeProviderDescription.exolix.title, + onChanged: () => tradeFilterStore + .toggleDisplayExchange(ExchangeProviderDescription.exolix)), ] }, subname = '', diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index cfabd994f..346844171 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; @@ -53,6 +54,9 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; + case ExchangeProviderDescription.exolix: + _provider = ExolixExchangeProvider(); + break; } _updateItems(); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index b438e9b74..47b408bc2 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -6,6 +6,8 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -150,6 +152,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider(useTorOnly: _useTorOnly), + ExolixExchangeProvider(), ]; @observable @@ -546,6 +549,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with amount = isFixedRateMode ? receiveAmount : depositAmount; } + if (provider is ExolixExchangeProvider) { + request = ExolixRequest( + from: depositCurrency, + to: receiveCurrency, + fromAmount: depositAmount.replaceAll(',', '.'), + toAmount: receiveAmount.replaceAll(',', '.'), + refundAddress: depositAddress, + address: receiveAddress); + amount = isFixedRateMode ? receiveAmount : depositAmount; + } + amount = amount.replaceAll(',', '.'); if (limitsState is LimitsLoadedSuccessfully) { diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index d3b14c59b..ccef76154 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -53,6 +53,11 @@ abstract class SupportViewModelBase with Store { icon: 'assets/images/simpleSwap.png', linkTitle: 'support@simpleswap.io', link: 'mailto:support@simpleswap.io'), + LinkListItem( + title: 'Exolix', + icon: 'assets/images/exolix.png', + linkTitle: 'support@exolix.com', + link: 'mailto:support@exolix.com'), if (!isMoneroOnly) ... [ LinkListItem( title: 'Wyre', diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index c0b1ac461..393629237 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -54,6 +55,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; + case ExchangeProviderDescription.exolix: + _provider = ExolixExchangeProvider(); + break; } _updateItems(); @@ -157,6 +161,12 @@ abstract class TradeDetailsViewModelBase with Store { items.add(StandartListItem( title: '${trade.providerName} ${S.current.password}', value: trade.password ?? '')); } + + if (trade.provider == ExchangeProviderDescription.exolix) { + final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}'; + items.add( + TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); + } } void _launchUrl(String url) { diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index fc2cc7fa8..a8c6a6166 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -32,6 +32,7 @@ class SecretKey { SecretKey('fiatApiKey', () => ''), SecretKey('payfuraApiKey', () => ''), SecretKey('chatwootWebsiteToken', () => ''), + SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), SecretKey('walletConnectProjectId', () => ''), From acb05178713e2d4b0bad2ac84f410fb69e2e5f67 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Wed, 11 Oct 2023 20:32:11 -0400 Subject: [PATCH 09/16] fix , bug and swapping for nano (#1123) --- cw_nano/lib/nano_wallet.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index da50f4ebb..bf7cc6bca 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -169,8 +169,8 @@ abstract class NanoWalletBase if (txOut.sendAll) { amt = balance[currency]?.currentBalance ?? BigInt.zero; } else { - amt = BigInt.tryParse( - NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ?? + amt = BigInt.tryParse(NanoUtil.getAmountAsRaw( + txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ?? BigInt.zero; } @@ -182,7 +182,9 @@ abstract class NanoWalletBase final block = await _client.constructSendBlock( amountRaw: amt.toString(), - destinationAddress: txOut.extractedAddress ?? txOut.address, + destinationAddress: credentials.outputs.first.isParsedAddress + ? credentials.outputs.first.extractedAddress! + : credentials.outputs.first.address, privateKey: _privateKey!, balanceAfterTx: runningBalance, previousHash: previousHash, From 66301ff2478c1879859b5a20d4deff0559f7af25 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 13 Oct 2023 01:50:16 +0300 Subject: [PATCH 10/16] CW-432-Add-Bitcoin-Cash-BCH (#1041) * initial commit * creating and restoring a wallet * [skip ci] add transaction priority * fix send and unspent screen * fix transaction priority type * replace Unspend with BitcoinUnspent * add transaction creation * fix transaction details screen * minor fix * fix create side wallet * basic transaction creation flow * fix fiat amount calculation * edit wallet * minor fix * fix address book parsing * merge commit fixes * minor fixes * Update gradle.properties * fix bch unspent coins * minor fix * fix BitcoinCashTransactionPriority * Fetch tags first before switching to one of them * Update build_haven.sh * Update build_haven.sh * Update build_haven.sh * Update build_haven.sh * update transaction build function * Update build_haven.sh * add ability to rename and delete * fix address format * Update pubspec.lock * Revert "fix address format" This reverts commit 1549bf4d8c3bdb0addbd6e3c5f049ebc3799ff8f. * fix address format for exange * restore from qr * Update configure.dart * [skip ci] minor fix * fix default fee rate * Update onramper_buy_provider.dart * Update wallet_address_list_view_model.dart * PR comments fixes * Update exchange_view_model.dart * fix merge conflict * Update address_validator.dart * merge fixes * update initialMigrationVersion * move cw_bitbox to Cake tech * PR fixes * PR fixes * Fix configure.dart brackets * update the new version text after macos * dummy change to run workflow * Fix Nano restore from QR issue Fix Conflicts with main * PR fixes * Update app_config.sh --------- Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build.yml | 26 +- .gitignore | 1 + android/gradle.properties | 2 +- assets/bitcoin_cash_electrum_server_list.yml | 3 + configure_cake_wallet_android.sh | 1 + .../lib/bitcoin_transaction_priority.dart | 52 ++- cw_bitcoin/lib/bitcoin_unspent.dart | 23 +- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 45 ++- cw_bitcoin/lib/electrum_wallet.dart | 251 +++++++-------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 17 +- cw_bitcoin/pubspec.lock | 9 + cw_bitcoin/pubspec.yaml | 4 + cw_bitcoin_cash/.gitignore | 30 ++ cw_bitcoin_cash/.metadata | 10 + cw_bitcoin_cash/CHANGELOG.md | 3 + cw_bitcoin_cash/LICENSE | 1 + cw_bitcoin_cash/README.md | 39 +++ cw_bitcoin_cash/analysis_options.yaml | 4 + cw_bitcoin_cash/lib/cw_bitcoin_cash.dart | 9 + .../lib/src/bitcoin_cash_address_utils.dart | 5 + .../lib/src/bitcoin_cash_base.dart | 7 + .../lib/src/bitcoin_cash_wallet.dart | 297 ++++++++++++++++++ .../src/bitcoin_cash_wallet_addresses.dart | 34 ++ ...coin_cash_wallet_creation_credentials.dart | 26 ++ .../lib/src/bitcoin_cash_wallet_service.dart | 107 +++++++ ..._cash_mnemonic_is_incorrect_exception.dart | 5 + .../lib/src/exceptions/exceptions.dart | 1 + cw_bitcoin_cash/lib/src/mnemonic.dart | 11 + .../src/pending_bitcoin_cash_transaction.dart | 62 ++++ .../.plugin_symlinks/path_provider_linux | 1 + .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 23 ++ .../Flutter/GeneratedPluginRegistrant.swift | 12 + .../ephemeral/Flutter-Generated.xcconfig | 11 + .../ephemeral/flutter_export_environment.sh | 12 + cw_bitcoin_cash/pubspec.yaml | 76 +++++ .../test/cw_bitcoin_cash_test.dart | 12 + .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 23 ++ cw_core/lib/amount_converter.dart | 1 + cw_core/lib/currency_for_wallet_type.dart | 2 + cw_core/lib/node.dart | 4 + .../lib}/unspent_transaction_output.dart | 0 cw_core/lib/wallet_type.dart | 15 + lib/bitcoin/cw_bitcoin.dart | 12 +- lib/bitcoin_cash/cw_bitcoin_cash.dart | 45 +++ lib/buy/onramper/onramper_buy_provider.dart | 2 + lib/core/address_validator.dart | 9 + lib/core/seed_validator.dart | 2 + lib/di.dart | 3 + lib/entities/default_settings_migration.dart | 87 +++-- lib/entities/main_actions.dart | 2 + lib/entities/node_list.dart | 23 +- lib/entities/preferences_key.dart | 2 + lib/entities/priority_for_wallet_type.dart | 3 + lib/ethereum/cw_ethereum.dart | 3 + lib/main.dart | 2 +- .../desktop_wallet_selection_dropdown.dart | 3 + .../dashboard/widgets/menu_widget.dart | 7 +- lib/src/screens/seed/pre_seed_page.dart | 1 + .../unspent_coins_list_page.dart | 7 +- .../screens/wallet_list/wallet_list_page.dart | 3 + lib/store/settings_store.dart | 195 +++++++----- .../dashboard/transaction_list_item.dart | 1 + .../exchange/exchange_view_model.dart | 29 +- .../node_list/node_list_view_model.dart | 3 + .../restore/restore_from_qr_vm.dart | 4 + .../restore/wallet_restore_from_qr_code.dart | 6 + lib/view_model/send/output.dart | 10 +- lib/view_model/send/send_view_model.dart | 46 +-- .../settings/other_settings_view_model.dart | 4 +- .../transaction_details_view_model.dart | 4 + .../unspent_coins_details_view_model.dart | 25 +- .../unspent_coins_list_view_model.dart | 37 +-- ...let_address_edit_or_create_view_model.dart | 3 +- .../wallet_address_list_view_model.dart | 26 +- lib/view_model/wallet_keys_view_model.dart | 6 +- lib/view_model/wallet_new_vm.dart | 5 +- lib/view_model/wallet_restore_view_model.dart | 19 +- model_generator.sh | 1 + pubspec_base.yaml | 1 + scripts/android/app_config.sh | 2 +- scripts/android/pubspec_gen.sh | 2 +- scripts/ios/app_config.sh | 4 +- scripts/macos/app_config.sh | 2 +- tool/configure.dart | 121 +++++-- 88 files changed, 1685 insertions(+), 416 deletions(-) create mode 100644 assets/bitcoin_cash_electrum_server_list.yml create mode 100644 cw_bitcoin_cash/.gitignore create mode 100644 cw_bitcoin_cash/.metadata create mode 100644 cw_bitcoin_cash/CHANGELOG.md create mode 100644 cw_bitcoin_cash/LICENSE create mode 100644 cw_bitcoin_cash/README.md create mode 100644 cw_bitcoin_cash/analysis_options.yaml create mode 100644 cw_bitcoin_cash/lib/cw_bitcoin_cash.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart create mode 100644 cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart create mode 100644 cw_bitcoin_cash/lib/src/exceptions/exceptions.dart create mode 100644 cw_bitcoin_cash/lib/src/mnemonic.dart create mode 100644 cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart create mode 120000 cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux create mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc create mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h create mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugins.cmake create mode 100644 cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig create mode 100644 cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh create mode 100644 cw_bitcoin_cash/pubspec.yaml create mode 100644 cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart create mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc create mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h create mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugins.cmake rename {lib/entities => cw_core/lib}/unspent_transaction_output.dart (100%) create mode 100644 lib/bitcoin_cash/cw_bitcoin_cash.dart mode change 100755 => 100644 model_generator.sh mode change 100755 => 100644 scripts/ios/app_config.sh diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index af03c5e30..5434429b2 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -42,6 +42,7 @@ jobs: cd cake_wallet/scripts/android/ ./install_ndk.sh source ./app_env.sh cakewallet + chmod +x pubspec_gen.sh ./app_config.sh - name: Cache Externals @@ -92,6 +93,7 @@ jobs: cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs @@ -141,18 +143,18 @@ jobs: cd /opt/android/cake_wallet flutter build apk --release - # - name: Push to App Center - # run: | - # echo 'Installing App Center CLI tools' - # npm install -g appcenter-cli - # echo "Publishing test to App Center" - # appcenter distribute release \ - # --group "Testers" \ - # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ - # --release-notes ${GITHUB_HEAD_REF} \ - # --app Cake-Labs/Cake-Wallet \ - # --token ${{ secrets.APP_CENTER_TOKEN }} \ - # --quiet +# - name: Push to App Center +# run: | +# echo 'Installing App Center CLI tools' +# npm install -g appcenter-cli +# echo "Publishing test to App Center" +# appcenter distribute release \ +# --group "Testers" \ +# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ +# --release-notes ${GITHUB_HEAD_REF} \ +# --app Cake-Labs/Cake-Wallet \ +# --token ${{ secrets.APP_CENTER_TOKEN }} \ +# --quiet - name: Rename apk file run: | diff --git a/.gitignore b/.gitignore index e8fb0048c..c735d4058 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart lib/monero/monero.dart lib/haven/haven.dart lib/ethereum/ethereum.dart +lib/bitcoin_cash/bitcoin_cash.dart lib/nano/nano.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png diff --git a/android/gradle.properties b/android/gradle.properties index a5965ab8d..38c8d4544 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.enableR8=true android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true diff --git a/assets/bitcoin_cash_electrum_server_list.yml b/assets/bitcoin_cash_electrum_server_list.yml new file mode 100644 index 000000000..d76668169 --- /dev/null +++ b/assets/bitcoin_cash_electrum_server_list.yml @@ -0,0 +1,3 @@ +- + uri: bitcoincash.stackwallet.com:50002 + is_default: true \ No newline at end of file diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet_android.sh index 792159f29..b8aa433de 100755 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet_android.sh @@ -8,4 +8,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index d82ea429e..10953a2e0 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -1,5 +1,4 @@ import 'package:cw_core/transaction_priority.dart'; -//import 'package:cake_wallet/generated/i18n.dart'; class BitcoinTransactionPriority extends TransactionPriority { const BitcoinTransactionPriority({required String title, required int raw}) @@ -100,4 +99,55 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { return label; } + } +class BitcoinCashTransactionPriority extends BitcoinTransactionPriority { + const BitcoinCashTransactionPriority({required String title, required int raw}) + : super(title: title, raw: raw); + + static const List all = [fast, medium, slow]; + static const BitcoinCashTransactionPriority slow = + BitcoinCashTransactionPriority(title: 'Slow', raw: 0); + static const BitcoinCashTransactionPriority medium = + BitcoinCashTransactionPriority(title: 'Medium', raw: 1); + static const BitcoinCashTransactionPriority fast = + BitcoinCashTransactionPriority(title: 'Fast', raw: 2); + + static BitcoinCashTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception('Unexpected token: $raw for BitcoinCashTransactionPriority deserialize'); + } + } + + @override + String get units => 'Satoshi'; + + @override + String toString() { + var label = ''; + + switch (this) { + case BitcoinCashTransactionPriority.slow: + label = 'Slow'; // S.current.transaction_priority_slow; + break; + case BitcoinCashTransactionPriority.medium: + label = 'Medium'; // S.current.transaction_priority_medium; + break; + case BitcoinCashTransactionPriority.fast: + label = 'Fast'; // S.current.transaction_priority_fast; + break; + default: + break; + } + + return label; + } +} + diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index e5a0e8cac..9c198c27c 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,24 +1,15 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; -class BitcoinUnspent { - BitcoinUnspent(this.address, this.hash, this.value, this.vout) - : isSending = true, - isFrozen = false, - note = ''; +class BitcoinUnspent extends Unspent { + BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout) + : bitcoinAddressRecord = addressRecord, + super(addressRecord.address, hash, value, vout, null); factory BitcoinUnspent.fromJSON( - BitcoinAddressRecord address, Map json) => + BitcoinAddressRecord address, Map json) => BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int); - final BitcoinAddressRecord address; - final String hash; - final int value; - final int vout; - - bool get isP2wpkh => - address.address.startsWith('bc') || address.address.startsWith('ltc'); - bool isSending; - bool isFrozen; - String note; + final BitcoinAddressRecord bitcoinAddressRecord; } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index de3fdfbca..36d37127d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,39 +1,34 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:cw_bitcoin/electrum.dart'; -import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'bitcoin_wallet_addresses.g.dart'; -class BitcoinWalletAddresses = BitcoinWalletAddressesBase - with _$BitcoinWalletAddresses; +class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses; -abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses - with Store { - BitcoinWalletAddressesBase( - WalletInfo walletInfo, +abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { + BitcoinWalletAddressesBase(WalletInfo walletInfo, {required bitcoin.HDWallet mainHd, - required bitcoin.HDWallet sideHd, - required bitcoin.NetworkType networkType, - required ElectrumClient electrumClient, - List? initialAddresses, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: mainHd, - sideHd: sideHd, - electrumClient: electrumClient, - networkType: networkType); + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); @override String getAddress({required int index, required bitcoin.HDWallet hd}) => generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); -} \ No newline at end of file +} diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 1c0a1e4e3..804b53379 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:mobx/mobx.dart'; @@ -34,45 +36,52 @@ import 'package:cw_bitcoin/electrum.dart'; import 'package:hex/hex.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:collection/collection.dart'; +import 'package:bip32/bip32.dart'; part 'electrum_wallet.g.dart'; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; -abstract class ElectrumWalletBase extends WalletBase with Store { +abstract class ElectrumWalletBase + extends WalletBase + with Store { ElectrumWalletBase( {required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required this.networkType, - required this.mnemonic, - required Uint8List seedBytes, - List? initialAddresses, - ElectrumClient? electrumClient, - ElectrumBalance? initialBalance, - CryptoCurrency? currency}) - : hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) - .derivePath("m/0'/0"), + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required this.networkType, + required this.mnemonic, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumClient? electrumClient, + ElectrumBalance? initialBalance, + CryptoCurrency? currency}) + : hd = currency == CryptoCurrency.bch + ? bitcoinCashHDWallet(seedBytes) + : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], _isTransactionUpdating = false, unspentCoins = [], _scripthashesUpdateSubject = {}, - balance = ObservableMap.of( - currency != null - ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, - frozen: 0)} - : {}), + balance = ObservableMap.of(currency != null + ? { + currency: + initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0) + } + : {}), this.unspentCoinsInfo = unspentCoinsInfo, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; - transactionHistory = - ElectrumTransactionHistory(walletInfo: walletInfo, password: password); + transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); } + static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => + bitcoin.HDWallet.fromSeed(seedBytes) + .derivePath("m/44'/145'/0'/0"); + static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 146 + outputsCounts * 33 + 8; @@ -98,9 +107,9 @@ abstract class ElectrumWalletBase extends WalletBase get publicScriptHashes => walletAddresses.addresses - .where((addr) => !addr.isHidden) - .map((addr) => scriptHash(addr.address, networkType: networkType)) - .toList(); + .where((addr) => !addr.isHidden) + .map((addr) => scriptHash(addr.address, networkType: networkType)) + .toList(); String get xpub => hd.base58!; @@ -110,8 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase BitcoinWalletKeys( - wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); + BitcoinWalletKeys get keys => + BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); String _password; List unspentCoins; @@ -139,8 +148,8 @@ abstract class ElectrumWalletBase extends WalletBase _feeRates = await electrumClient.feeRates()); + Timer.periodic( + const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); syncStatus = SyncedSyncStatus(); } catch (e, stacktrace) { @@ -169,8 +178,7 @@ abstract class ElectrumWalletBase extends WalletBase createTransaction( - Object credentials) async { + Future createTransaction(Object credentials) async { const minAmount = 546; final transactionCredentials = credentials as BitcoinTransactionCredentials; final inputs = []; @@ -204,13 +212,11 @@ abstract class ElectrumWalletBase extends WalletBase item.sendAll - || item.formattedCryptoAmount! <= 0)) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { throw BitcoinTransactionWrongBalanceException(currency); } - credentialsAmount = outputs.fold(0, (acc, value) => - acc + value.formattedCryptoAmount!); + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); if (allAmount - credentialsAmount < minAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -227,9 +233,7 @@ abstract class ElectrumWalletBase extends WalletBase allAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -291,8 +295,8 @@ abstract class ElectrumWalletBase extends WalletBase json.encode({ - 'mnemonic': mnemonic, - 'account_index': walletAddresses.currentReceiveAddressIndex.toString(), - 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), - 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), - 'balance': balance[currency]?.toJSON() - }); + 'mnemonic': mnemonic, + 'account_index': walletAddresses.currentReceiveAddressIndex.toString(), + 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), + 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), + 'balance': balance[currency]?.toJSON() + }); int feeRate(TransactionPriority priority) { try { @@ -364,34 +361,29 @@ abstract class ElectrumWalletBase extends WalletBase + int feeAmountForPriority( + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); - int feeAmountWithFeeRate(int feeRate, int inputsCount, - int outputsCount) => + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => feeRate * estimatedTransactionSize(inputsCount, outputsCount); @override - int calculateEstimatedFee(TransactionPriority? priority, int? amount, - {int? outputsCount}) { + int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) { if (priority is BitcoinTransactionPriority) { - return calculateEstimatedFeeWithFeeRate( - feeRate(priority), - amount, - outputsCount: outputsCount); + return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount, + outputsCount: outputsCount); } return 0; } - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, - {int? outputsCount}) { + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { int inputsCount = 0; if (amount != null) { @@ -420,8 +412,7 @@ abstract class ElectrumWalletBase extends WalletBase makePath() async => - pathForWallet(name: walletInfo.name, type: walletInfo.type); + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future updateUnspent() async { final unspent = await Future.wait(walletAddresses .addresses.map((address) => electrumClient .getListUnspentWithAddress(address.address, networkType) .then((unspent) => unspent - .map((unspent) { - try { - return BitcoinUnspent.fromJSON(address, unspent); - } catch(_) { - return null; - } - }).whereNotNull()))); + .map((unspent) { + try { + return BitcoinUnspent.fromJSON(address, unspent); + } catch(_) { + return null; + } + }).whereNotNull()))); unspentCoins = unspent.expand((e) => e).toList(); if (unspentCoinsInfo.isEmpty) { @@ -498,8 +487,8 @@ abstract class ElectrumWalletBase extends WalletBase - element.walletId.contains(id) && element.hash.contains(coin.hash)); + final coinInfoList = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash)); if (coinInfoList.isNotEmpty) { final coinInfo = coinInfoList.first; @@ -518,14 +507,14 @@ abstract class ElectrumWalletBase extends WalletBase _addCoinInfo(BitcoinUnspent coin) async { final newInfo = UnspentCoinsInfo( - walletId: id, - hash: coin.hash, - isFrozen: coin.isFrozen, - isSending: coin.isSending, - noteRaw: coin.note, - address: coin.address.address, - value: coin.value, - vout: coin.vout, + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + noteRaw: coin.note, + address: coin.bitcoinAddressRecord.address, + value: coin.value, + vout: coin.vout, ); await unspentCoinsInfo.add(newInfo); @@ -534,8 +523,8 @@ abstract class ElectrumWalletBase extends WalletBase _refreshUnspentCoinsInfo() async { try { final List keys = []; - final currentWalletUnspentCoins = unspentCoinsInfo.values - .where((element) => element.walletId.contains(id)); + final currentWalletUnspentCoins = + unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); if (currentWalletUnspentCoins.isNotEmpty) { currentWalletUnspentCoins.forEach((element) { @@ -571,27 +560,19 @@ abstract class ElectrumWalletBase extends WalletBase fetchTransactionInfo( {required String hash, required int height}) async { - try { - final tx = await getTransactionExpanded(hash: hash, height: height); - final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); - return ElectrumTransactionInfo.fromElectrumBundle( - tx, - walletInfo.type, - networkType, - addresses: addresses, - height: height); - } catch(_) { - return null; - } + try { + final tx = await getTransactionExpanded(hash: hash, height: height); + final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); + return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType, + addresses: addresses, height: height); + } catch (_) { + return null; + } } @override @@ -602,10 +583,8 @@ abstract class ElectrumWalletBase extends WalletBase electrumClient - .getHistory(scriptHash) - .then((history) => {scriptHash: history})); + final histories = addressHashes.keys.map((scriptHash) => + electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); final historyResults = await Future.wait(histories); historyResults.forEach((history) { history.entries.forEach((historyItem) { @@ -616,19 +595,16 @@ abstract class ElectrumWalletBase extends WalletBase>( - {}, (acc, tx) { + final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) { + try { + return fetchTransactionInfo( + hash: transaction['tx_hash'] as String, height: transaction['height'] as int); + } catch (_) { + return Future.value(null); + } + })); + return historiesWithDetails + .fold>({}, (acc, tx) { if (tx == null) { return acc; } @@ -680,9 +656,8 @@ abstract class ElectrumWalletBase extends WalletBase _fetchBalances() async { final addresses = walletAddresses.addresses.toList(); final balanceFutures = >>[]; - for (var i = 0; i < addresses.length; i++) { - final addressRecord = addresses[i]; + final addressRecord = addresses[i] ; final sh = scriptHash(addressRecord.address, networkType: networkType); final balanceFuture = electrumClient.getBalance(sh); balanceFutures.add(balanceFuture); @@ -691,8 +666,10 @@ abstract class ElectrumWalletBase extends WalletBase updateBalance() async { @@ -727,9 +704,7 @@ abstract class ElectrumWalletBase extends WalletBase addr.isHidden) - .toList(); + var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList(); if (addresses.length < minCountOfHiddenAddresses) { addresses = walletAddresses.addresses.toList(); diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 741c2fe1c..ab99a875c 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,9 +1,11 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:bitbox/bitbox.dart' as bitbox; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; part 'electrum_wallet_addresses.g.dart'; @@ -38,6 +40,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { static const defaultChangeAddressesCount = 17; static const gap = 20; + static String toCashAddr(String address) => bitbox.Address.toCashAddress(address); + final ObservableList addresses; final ObservableList receiveAddresses; final ObservableList changeAddresses; @@ -50,10 +54,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @computed String get address { if (receiveAddresses.isEmpty) { - return generateNewAddress().address; + final address = generateNewAddress().address; + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address; } + final receiveAddress = receiveAddresses.first.address; - return receiveAddresses.first.address; + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress; } @override @@ -105,10 +111,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future getChangeAddress() async { updateChangeAddresses(); - + if (changeAddresses.isEmpty) { - final newAddresses = await _createNewAddresses( - gap, + final newAddresses = await _createNewAddresses(gap, hd: sideHd, startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 @@ -179,7 +184,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } else { addrs = await _createNewAddresses( isHidden - ? defaultChangeAddressesCount + ? defaultChangeAddressesCount : defaultReceiveAddressesCount, startIndex: 0, hd: hd, diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 843daa771..eb75227b6 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -66,6 +66,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + bitbox: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 + url: "https://github.com/cake-tech/bitbox-flutter.git" + source: git + version: "1.0.1" bitcoin_flutter: dependency: "direct main" description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index dae0af39b..693d5af7a 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -23,6 +23,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_flutter.git ref: cake-update-v3 + bitbox: + git: + url: https://github.com/cake-tech/bitbox-flutter.git + ref: master rxdart: ^0.27.5 unorm_dart: ^0.2.0 cryptography: ^2.0.5 diff --git a/cw_bitcoin_cash/.gitignore b/cw_bitcoin_cash/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/cw_bitcoin_cash/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/cw_bitcoin_cash/.metadata b/cw_bitcoin_cash/.metadata new file mode 100644 index 000000000..4161da6ea --- /dev/null +++ b/cw_bitcoin_cash/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b06b8b2710955028a6b562f5aa6fe62941d6febf + channel: stable + +project_type: package diff --git a/cw_bitcoin_cash/CHANGELOG.md b/cw_bitcoin_cash/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_bitcoin_cash/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_bitcoin_cash/LICENSE b/cw_bitcoin_cash/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_bitcoin_cash/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_bitcoin_cash/README.md b/cw_bitcoin_cash/README.md new file mode 100644 index 000000000..02fe8ecab --- /dev/null +++ b/cw_bitcoin_cash/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/cw_bitcoin_cash/analysis_options.yaml b/cw_bitcoin_cash/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_bitcoin_cash/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_bitcoin_cash/lib/cw_bitcoin_cash.dart b/cw_bitcoin_cash/lib/cw_bitcoin_cash.dart new file mode 100644 index 000000000..732474ac4 --- /dev/null +++ b/cw_bitcoin_cash/lib/cw_bitcoin_cash.dart @@ -0,0 +1,9 @@ +library cw_bitcoin_cash; + +export 'src/bitcoin_cash_base.dart'; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart new file mode 100644 index 000000000..ca47ea9f6 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart @@ -0,0 +1,5 @@ +import 'package:bitbox/bitbox.dart' as bitbox; + +class AddressUtils { + static String getCashAddrFormat(String address) => bitbox.Address.toCashAddress(address); +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart new file mode 100644 index 000000000..4699b1649 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart @@ -0,0 +1,7 @@ +export 'bitcoin_cash_wallet.dart'; +export 'bitcoin_cash_wallet_addresses.dart'; +export 'bitcoin_cash_wallet_creation_credentials.dart'; +export 'bitcoin_cash_wallet_service.dart'; +export 'exceptions/exceptions.dart'; +export 'mnemonic.dart'; +export 'bitcoin_cash_address_utils.dart'; diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart new file mode 100644 index 000000000..b4924e2db --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -0,0 +1,297 @@ +import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; + +import 'bitcoin_cash_base.dart'; + +part 'bitcoin_cash_wallet.g.dart'; + +class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet; + +abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { + BitcoinCashWalletBase( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: bitcoin.bitcoin, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.bch) { + walletAddresses = BitcoinCashWalletAddresses(walletInfo, + electrumClient: electrumClient, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: bitcoin.HDWallet.fromSeed(seedBytes) + .derivePath("m/44'/145'/0'/1"), + networkType: networkType); + } + + + static Future create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) async { + return BitcoinCashWallet( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await Mnemonic.toSeed(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex); + } + + static Future open({ + required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + }) async { + final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); + return BitcoinCashWallet( + mnemonic: snp.mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await Mnemonic.toSeed(snp.mnemonic), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex); + } + + @override + Future createTransaction(Object credentials) async { + const minAmount = 546; + final transactionCredentials = credentials as BitcoinTransactionCredentials; + final inputs = []; + final outputs = transactionCredentials.outputs; + final hasMultiDestination = outputs.length > 1; + + var allInputsAmount = 0; + + if (unspentCoins.isEmpty) await updateUnspent(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + allInputsAmount += utx.value; + inputs.add(utx); + } + } + + if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); + + final allAmountFee = transactionCredentials.feeRate != null + ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length) + : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length); + + final allAmount = allInputsAmount - allAmountFee; + + var credentialsAmount = 0; + var amount = 0; + var fee = 0; + + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); + + if (allAmount - credentialsAmount < minAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + amount = credentialsAmount; + + if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, + outputsCount: outputs.length + 1); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount, + outputsCount: outputs.length + 1); + } + } else { + final output = outputs.first; + credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; + + if (credentialsAmount > allAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + amount = output.sendAll || allAmount - credentialsAmount < minAmount + ? allAmount + : credentialsAmount; + + if (output.sendAll || amount == allAmount) { + fee = allAmountFee; + } else if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount); + } + } + + if (fee == 0) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final totalAmount = amount + fee; + + if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + final txb = bitbox.Bitbox.transactionBuilder(testnet: false); + + final changeAddress = await walletAddresses.getChangeAddress(); + var leftAmount = totalAmount; + var totalInputAmount = 0; + + inputs.clear(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + leftAmount = leftAmount - utx.value; + totalInputAmount += utx.value; + inputs.add(utx); + + if (leftAmount <= 0) { + break; + } + } + } + + if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); + + if (amount <= 0 || totalInputAmount < totalAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + inputs.forEach((input) { + txb.addInput(input.hash, input.vout); + }); + + outputs.forEach((item) { + final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; + final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + txb.addOutput(outputAddress, outputAmount!); + }); + + final estimatedSize = bitbox.BitcoinCash.getByteCount(inputs.length, outputs.length + 1); + + var feeAmount = 0; + + if (transactionCredentials.feeRate != null) { + feeAmount = transactionCredentials.feeRate! * estimatedSize; + } else { + feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; + } + + final changeValue = totalInputAmount - amount - feeAmount; + + if (changeValue > minAmount) { + txb.addOutput(changeAddress, changeValue); + } + + for (var i = 0; i < inputs.length; i++) { + final input = inputs[i]; + final keyPair = generateKeyPair( + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: input.bitcoinAddressRecord.index); + txb.sign(i, keyPair, input.value); + } + + // Build the transaction + final tx = txb.build(); + + return PendingBitcoinCashTransaction(tx, type, + electrumClient: electrumClient, amount: amount, fee: fee); + } + + bitbox.ECPair generateKeyPair( + {required bitcoin.HDWallet hd, + required int index}) => + bitbox.ECPair.fromWIF(hd.derive(index).wif!); + + @override + int feeAmountForPriority( + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => + feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); + + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => + feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); + + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { + int inputsCount = 0; + int totalValue = 0; + + for (final input in unspentCoins) { + if (input.isSending) { + inputsCount++; + totalValue += input.value; + } + if (amount != null && totalValue >= amount) { + break; + } + } + + if (amount != null && totalValue < amount) return 0; + + final _outputsCount = outputsCount ?? (amount != null ? 2 : 1); + + return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount); + } + + @override + int feeRate(TransactionPriority priority) { + if (priority is BitcoinCashTransactionPriority) { + switch (priority) { + case BitcoinCashTransactionPriority.slow: + return 1; + case BitcoinCashTransactionPriority.medium: + return 5; + case BitcoinCashTransactionPriority.fast: + return 10; + } + } + + return 0; + } +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart new file mode 100644 index 000000000..1709c4d8f --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -0,0 +1,34 @@ +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum.dart'; +import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:mobx/mobx.dart'; + +part 'bitcoin_cash_wallet_addresses.g.dart'; + +class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses; + +abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store { + BitcoinCashWalletAddressesBase(WalletInfo walletInfo, + {required bitcoin.HDWallet mainHd, + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); + + @override + String getAddress({required int index, required bitcoin.HDWallet hd}) => + generateP2PKHAddress(hd: hd, index: index, networkType: networkType); +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart new file mode 100644 index 000000000..72caa6c58 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart @@ -0,0 +1,26 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; + +class BitcoinCashNewWalletCredentials extends WalletCredentials { + BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); +} + +class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials { + BitcoinCashRestoreWalletFromSeedCredentials( + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String mnemonic; +} + +class BitcoinCashRestoreWalletFromWIFCredentials extends WalletCredentials { + BitcoinCashRestoreWalletFromWIFCredentials( + {required String name, required String password, required this.wif, WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String wif; +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart new file mode 100644 index 000000000..fa572b38b --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -0,0 +1,107 @@ +import 'dart:io'; + +import 'package:bip39/bip39.dart'; +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:collection/collection.dart'; +import 'package:hive/hive.dart'; + +class BitcoinCashWalletService extends WalletService { + BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); + + final Box walletInfoSource; + final Box unspentCoinsInfoSource; + + @override + WalletType getType() => WalletType.bitcoinCash; + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future create( + credentials) async { + final wallet = await BitcoinCashWalletBase.create( + mnemonic: await Mnemonic.generate(), + password: credentials.password!, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.save(); + await wallet.init(); + return wallet; + } + + @override + Future openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; + final wallet = await BitcoinCashWalletBase.open( + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.init(); + return wallet; + } + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())) + .delete(recursive: true); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWallet = await BitcoinCashWalletBase.open( + password: password, + name: currentName, + walletInfo: currentWalletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future + restoreFromKeys(credentials) { + // TODO: implement restoreFromKeys + throw UnimplementedError('restoreFromKeys() is not implemented'); + } + + @override + Future restoreFromSeed( + BitcoinCashRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw BitcoinCashMnemonicIsIncorrectException(); + } + + final wallet = await BitcoinCashWalletBase.create( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.save(); + await wallet.init(); + return wallet; + } +} diff --git a/cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart b/cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart new file mode 100644 index 000000000..7cce59085 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart @@ -0,0 +1,5 @@ +class BitcoinCashMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Bitcoin Cash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_bitcoin_cash/lib/src/exceptions/exceptions.dart b/cw_bitcoin_cash/lib/src/exceptions/exceptions.dart new file mode 100644 index 000000000..746e3248a --- /dev/null +++ b/cw_bitcoin_cash/lib/src/exceptions/exceptions.dart @@ -0,0 +1 @@ +export 'bitcoin_cash_mnemonic_is_incorrect_exception.dart'; \ No newline at end of file diff --git a/cw_bitcoin_cash/lib/src/mnemonic.dart b/cw_bitcoin_cash/lib/src/mnemonic.dart new file mode 100644 index 000000000..b1f1ee984 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/mnemonic.dart @@ -0,0 +1,11 @@ +import 'dart:typed_data'; + +import 'package:bip39/bip39.dart' as bip39; + +class Mnemonic { + /// Generate bip39 mnemonic + static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength); + + /// Create root seed from mnemonic + static Uint8List toSeed(String mnemonic) => bip39.mnemonicToSeed(mnemonic); +} diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart new file mode 100644 index 000000000..d5ac36ce2 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart @@ -0,0 +1,62 @@ +import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; +import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_bitcoin/electrum.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/electrum_transaction_info.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_type.dart'; + +class PendingBitcoinCashTransaction with PendingTransaction { + PendingBitcoinCashTransaction(this._tx, this.type, + {required this.electrumClient, + required this.amount, + required this.fee}) + : _listeners = []; + + final WalletType type; + final bitbox.Transaction _tx; + final ElectrumClient electrumClient; + final int amount; + final int fee; + + @override + String get id => _tx.getId(); + + @override + String get hex => _tx.toHex(); + + @override + String get amountFormatted => bitcoinAmountToString(amount: amount); + + @override + String get feeFormatted => bitcoinAmountToString(amount: fee); + + final List _listeners; + + @override + Future commit() async { + final result = + await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex()); + + if (result.isEmpty) { + throw BitcoinCommitTransactionException(); + } + + _listeners?.forEach((listener) => listener(transactionInfo())); + } + + void addListener( + void Function(ElectrumTransactionInfo transaction) listener) => + _listeners.add(listener); + + ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, + id: id, + height: 0, + amount: amount, + direction: TransactionDirection.outgoing, + date: DateTime.now(), + isPending: true, + confirmations: 0, + fee: fee); +} diff --git a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux new file mode 120000 index 000000000..7d9244fbe --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux @@ -0,0 +1 @@ +C:/Users/borod/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.0/ \ No newline at end of file diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..e71a16d23 --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake b/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..2e1de87a7 --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift b/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..e777c67df --- /dev/null +++ b/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) +} diff --git a/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new file mode 100644 index 000000000..2f46994d3 --- /dev/null +++ b/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -0,0 +1,11 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=C:\Users\borod\flutter +FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=0.0.1 +FLUTTER_BUILD_NUMBER=0.0.1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh b/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh new file mode 100644 index 000000000..2a3bcca5a --- /dev/null +++ b/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=C:\Users\borod\flutter" +export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=0.0.1" +export "FLUTTER_BUILD_NUMBER=0.0.1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml new file mode 100644 index 000000000..30ed49e80 --- /dev/null +++ b/cw_bitcoin_cash/pubspec.yaml @@ -0,0 +1,76 @@ +name: cw_bitcoin_cash +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=2.19.0 <3.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + bip39: ^1.0.6 + bip32: ^2.0.0 + path_provider: ^2.0.11 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 + cw_core: + path: ../cw_core + cw_bitcoin: + path: ../cw_bitcoin + bitcoin_flutter: + git: + url: https://github.com/cake-tech/bitcoin_flutter.git + ref: cake-update-v3 + bitbox: + git: + url: https://github.com/cake-tech/bitbox-flutter.git + ref: master + + + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + +# To add assets to your package, add an assets section, like this: +# assets: +# - images/a_dot_burr.jpeg +# - images/a_dot_ham.jpeg +# +# For details regarding assets in packages, see +# https://flutter.dev/assets-and-images/#from-packages +# +# An image asset can refer to one or more resolution-specific "variants", see +# https://flutter.dev/assets-and-images/#resolution-aware + +# To add custom fonts to your package, add a fonts section here, +# in this "flutter" section. Each entry in this list should have a +# "family" key with the font family name, and a "fonts" key with a +# list giving the asset and other descriptors for the font. For +# example: +# fonts: +# - family: Schyler +# fonts: +# - asset: fonts/Schyler-Regular.ttf +# - asset: fonts/Schyler-Italic.ttf +# style: italic +# - family: Trajan Pro +# fonts: +# - asset: fonts/TrajanPro.ttf +# - asset: fonts/TrajanPro_Bold.ttf +# weight: 700 +# + diff --git a/cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart b/cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart new file mode 100644 index 000000000..f06646a8f --- /dev/null +++ b/cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake b/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..b93c4c30c --- /dev/null +++ b/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index a11907ef2..6fd43dd82 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -80,6 +80,7 @@ class AmountConverter { case CryptoCurrency.xmr: return _moneroAmountToString(amount); case CryptoCurrency.btc: + case CryptoCurrency.bch: return _bitcoinAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 2db858b30..4c330b073 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -13,6 +13,8 @@ CryptoCurrency currencyForWalletType(WalletType type) { return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.bitcoinCash: + return CryptoCurrency.bch; case WalletType.nano: return CryptoCurrency.nano; case WalletType.banano: diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index a07030d64..dc3620ec2 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -78,6 +78,8 @@ class Node extends HiveObject with Keyable { return Uri.http(uriRaw, ''); case WalletType.ethereum: return Uri.https(uriRaw, ''); + case WalletType.bitcoinCash: + return createUriFromElectrumAddress(uriRaw); case WalletType.nano: case WalletType.banano: if (isSSL) { @@ -138,6 +140,8 @@ class Node extends HiveObject with Keyable { return requestMoneroNode(); case WalletType.ethereum: return requestElectrumServer(); + case WalletType.bitcoinCash: + return requestElectrumServer(); case WalletType.nano: case WalletType.banano: return requestNanoNode(); diff --git a/lib/entities/unspent_transaction_output.dart b/cw_core/lib/unspent_transaction_output.dart similarity index 100% rename from lib/entities/unspent_transaction_output.dart rename to cw_core/lib/unspent_transaction_output.dart diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 0125facaf..debf92e11 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -10,6 +10,7 @@ const walletTypes = [ WalletType.litecoin, WalletType.haven, WalletType.ethereum, + WalletType.bitcoinCash, WalletType.nano, WalletType.banano, ]; @@ -39,6 +40,10 @@ enum WalletType { @HiveField(7) banano, + + @HiveField(8) + bitcoinCash, + } int serializeToInt(WalletType type) { @@ -57,6 +62,8 @@ int serializeToInt(WalletType type) { return 5; case WalletType.banano: return 6; + case WalletType.bitcoinCash: + return 7; default: return -1; } @@ -78,6 +85,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.nano; case 6: return WalletType.banano; + case 7: + return WalletType.bitcoinCash; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -95,6 +104,8 @@ String walletTypeToString(WalletType type) { return 'Haven'; case WalletType.ethereum: return 'Ethereum'; + case WalletType.bitcoinCash: + return 'Bitcoin Cash'; case WalletType.nano: return 'Nano'; case WalletType.banano: @@ -116,6 +127,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Haven (XHV)'; case WalletType.ethereum: return 'Ethereum (ETH)'; + case WalletType.bitcoinCash: + return 'Bitcoin Cash (BCH)'; case WalletType.nano: return 'Nano (XNO)'; case WalletType.banano: @@ -137,6 +150,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.bitcoinCash: + return CryptoCurrency.bch; case WalletType.nano: return CryptoCurrency.nano; case WalletType.banano: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 353458937..dd713fd15 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -44,6 +44,7 @@ class CWBitcoin extends Bitcoin { List getTransactionPriorities() => BitcoinTransactionPriority.all; + @override List getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all; @@ -121,16 +122,9 @@ class CWBitcoin extends Bitcoin { => (priority as BitcoinTransactionPriority).labelWithRate(rate); @override - List getUnspents(Object wallet) { + List getUnspents(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; - return bitcoinWallet.unspentCoins - .map((BitcoinUnspent bitcoinUnspent) => Unspent( - bitcoinUnspent.address.address, - bitcoinUnspent.hash, - bitcoinUnspent.value, - bitcoinUnspent.vout, - null)) - .toList(); + return bitcoinWallet.unspentCoins; } void updateUnspents(Object wallet) async { diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart new file mode 100644 index 000000000..7dbb8614f --- /dev/null +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -0,0 +1,45 @@ +part of 'bitcoin_cash.dart'; + +class CWBitcoinCash extends BitcoinCash { + @override + String getMnemonic(int? strength) => Mnemonic.generate(); + + @override + Uint8List getSeedFromMnemonic(String seed) => Mnemonic.toSeed(seed); + + @override + String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address); + + @override + WalletService createBitcoinCashWalletService( + Box walletInfoSource, Box unspentCoinSource) { + return BitcoinCashWalletService(walletInfoSource, unspentCoinSource); + } + + @override + WalletCredentials createBitcoinCashNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + }) => + BitcoinCashNewWalletCredentials(name: name, walletInfo: walletInfo); + + @override + WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials( + {required String name, required String mnemonic, required String password}) => + BitcoinCashRestoreWalletFromSeedCredentials( + name: name, mnemonic: mnemonic, password: password); + + @override + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) => + BitcoinCashTransactionPriority.deserialize(raw: raw); + + @override + TransactionPriority getDefaultTransactionPriority() => BitcoinCashTransactionPriority.medium; + + @override + List getTransactionPriorities() => BitcoinCashTransactionPriority.all; + + @override + TransactionPriority getBitcoinCashTransactionPrioritySlow() => + BitcoinCashTransactionPriority.slow; +} diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 91309a2ca..872fcebf5 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -27,6 +27,8 @@ class OnRamperBuyProvider { return "LTC_LITECOIN"; case CryptoCurrency.xmr: return "XMR_MONERO"; + case CryptoCurrency.bch: + return "BCH_BITCOINCASH"; case CryptoCurrency.nano: return "XNO_NANO"; default: diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index d039a40a7..fcb881943 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -88,7 +88,9 @@ class AddressValidator extends TextValidator { case CryptoCurrency.dai: case CryptoCurrency.dash: case CryptoCurrency.eos: + return '[0-9a-zA-Z]'; case CryptoCurrency.bch: + return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q[0-9a-zA-Z]{42}\$|^bitcoincash:q[0-9a-zA-Z]{41}\$|^bitcoincash:q[0-9a-zA-Z]{42}\$'; case CryptoCurrency.bnb: return '[0-9a-zA-Z]'; case CryptoCurrency.ltc: @@ -172,7 +174,9 @@ class AddressValidator extends TextValidator { case CryptoCurrency.steth: case CryptoCurrency.shib: case CryptoCurrency.avaxc: + return [42]; case CryptoCurrency.bch: + return [42, 43, 44, 54, 55]; case CryptoCurrency.bnb: return [42]; case CryptoCurrency.ltc: @@ -271,6 +275,11 @@ class AddressValidator extends TextValidator { return 'nano_[0-9a-zA-Z]{60}'; case CryptoCurrency.banano: return 'ban_[0-9a-zA-Z]{60}'; + case CryptoCurrency.bch: + return 'bitcoincash:q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)' + '|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)' + '|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)' + '|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'; default: return null; } diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 1c6e7cd20..95ccf89ac 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -29,6 +29,8 @@ class SeedValidator extends Validator { return haven!.getMoneroWordList(language); case WalletType.ethereum: return ethereum!.getEthereumWordList(language); + case WalletType.bitcoinCash: + return getBitcoinWordList(language); case WalletType.nano: case WalletType.banano: return nano!.getNanoWordList(language); diff --git a/lib/di.dart b/lib/di.dart index b871e6a6b..1576c378b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; @@ -820,6 +821,8 @@ Future setup({ return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource); case WalletType.ethereum: return ethereum!.createEthereumWalletService(_walletInfoSource); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!); case WalletType.nano: return nano!.createNanoWalletService(_walletInfoSource); default: diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 690a5e9eb..94283302d 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -26,6 +26,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; +const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; @@ -81,7 +82,10 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); await changeLitecoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); - await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); + await changeHavenCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + await changeBitcoinCashCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); break; case 2: @@ -166,6 +170,11 @@ Future defaultSettingsMigration( await changeNanoCurrentPowNodeToDefault( sharedPreferences: sharedPreferences, nodes: powNodes); break; + case 23: + await addBitcoinCashElectrumServerList(nodes: nodes); + await changeBitcoinCurrentElectrumServerToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + break; default: break; @@ -323,6 +332,12 @@ Node? getNanoDefaultPowNode({required Box nodes}) { nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano)); } +Node? getBitcoinCashDefaultElectrumServer({required Box nodes}) { + return nodes.values.firstWhereOrNull( + (Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri) + ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash); +} + Node getMoneroDefaultNode({required Box nodes}) { final timeZone = DateTime.now().timeZoneOffset.inHours; var nodeUri = ''; @@ -358,6 +373,15 @@ Future changeLitecoinCurrentElectrumServerToDefault( await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId); } +Future changeBitcoinCashCurrentNodeToDefault( + {required SharedPreferences sharedPreferences, + required Box nodes}) async { + final server = getBitcoinCashDefaultElectrumServer(nodes: nodes); + final serverId = server?.key as int ?? 0; + + await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, serverId); +} + Future changeHavenCurrentNodeToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final node = getHavenDefaultNode(nodes: nodes); @@ -411,6 +435,15 @@ Future addLitecoinElectrumServerList({required Box nodes}) async { } } +Future addBitcoinCashElectrumServerList({required Box nodes}) async { + final serverList = await loadBitcoinCashElectrumServerList(); + for (var node in serverList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } +} + Future addHavenNodeList({required Box nodes}) async { final nodeList = await loadDefaultHavenNodes(); for (var node in nodeList) { @@ -497,27 +530,34 @@ Future checkCurrentNodes( final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentBitcoinElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); - final currentLitecoinElectrumSeverId = - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); - final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); - final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); - final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); - final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); - final currentMoneroNode = - nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId); - final currentBitcoinElectrumServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId); - final currentLitecoinElectrumServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId); - final currentHavenNodeServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId); - final currentEthereumNodeServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); + final currentLitecoinElectrumSeverId = sharedPreferences + .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final currentHavenNodeId = sharedPreferences + .getInt(PreferencesKey.currentHavenNodeIdKey); + final currentEthereumNodeId = sharedPreferences + .getInt(PreferencesKey.currentEthereumNodeIdKey); + final currentNanoNodeId = sharedPreferences + .getInt(PreferencesKey.currentNanoNodeIdKey); + final currentNanoPowNodeId = sharedPreferences + .getInt(PreferencesKey.currentNanoPowNodeIdKey); + final currentBitcoinCashNodeId = sharedPreferences + .getInt(PreferencesKey.currentBitcoinCashNodeIdKey); + final currentMoneroNode = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentMoneroNodeId); + final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentBitcoinElectrumSeverId); + final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentLitecoinElectrumSeverId); + final currentHavenNodeServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentHavenNodeId); + final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentEthereumNodeId); final currentNanoNodeServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); + nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); final currentNanoPowNodeServer = - powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId); - + powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId); + final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentBitcoinCashNodeId); if (currentMoneroNode == null) { final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); await nodeSource.add(newCakeWalletNode); @@ -565,6 +605,13 @@ Future checkCurrentNodes( } await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int); } + + if (currentBitcoinCashNodeServer == null) { + final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash); + await nodeSource.add(node); + await sharedPreferences.setInt( + PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 48beab32b..d2c67ef95 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -52,6 +52,7 @@ class MainActions { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.bitcoinCash: case WalletType.nano: case WalletType.banano: switch (defaultBuyProvider) { @@ -123,6 +124,7 @@ class MainActions { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.bitcoinCash: if (viewModel.isEnabledSellAction) { final moonPaySellProvider = MoonPaySellProvider(); final uri = await moonPaySellProvider.requestUrl( diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 0641c0846..53facf18c 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -84,6 +84,23 @@ Future> loadDefaultEthereumNodes() async { return nodes; } +Future> loadBitcoinCashElectrumServerList() async { + final serverListRaw = + await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml'); + final loadedServerList = loadYaml(serverListRaw) as YamlList; + final serverList = []; + + for (final raw in loadedServerList) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + node.type = WalletType.bitcoinCash; + serverList.add(node); + } + } + + return serverList; +} + Future> loadDefaultNanoNodes() async { final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml'); final loadedNodes = loadYaml(nodesRaw) as YamlList; @@ -116,10 +133,11 @@ Future> loadDefaultNanoPowNodes() async { return nodes; } -Future resetToDefault(Box nodeSource) async { +Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); final litecoinElectrumServerList = await loadLitecoinElectrumServerList(); + final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList(); final havenNodes = await loadDefaultHavenNodes(); final ethereumNodes = await loadDefaultEthereumNodes(); final nanoNodes = await loadDefaultNanoNodes(); @@ -129,13 +147,14 @@ Future resetToDefault(Box nodeSource) async { litecoinElectrumServerList + havenNodes + ethereumNodes + + bitcoinCashElectrumServerList + nanoNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); } -Future resetPowToDefault(Box powNodeSource) async { +Future resetPowToDefault(Box powNodeSource) async { final nanoPowNodes = await loadDefaultNanoPowNodes(); final nodes = nanoPowNodes; await powNodeSource.clear(); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index ce2db7cdf..cbd12d777 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -11,6 +11,7 @@ class PreferencesKey { static const currentBananoNodeIdKey = 'current_node_id_banano'; static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow'; static const currentFiatCurrencyKey = 'current_fiat_currency'; + static const currentBitcoinCashNodeIdKey = 'current_node_id_bch'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const shouldSaveRecipientAddressKey = 'save_recipient_address'; @@ -36,6 +37,7 @@ class PreferencesKey { static const havenTransactionPriority = 'current_fee_priority_haven'; static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; + static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 378cf0ea2..bf6f8157d 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -17,6 +18,8 @@ List priorityForWalletType(WalletType type) { return haven!.getTransactionPriorities(); case WalletType.ethereum: return ethereum!.getTransactionPriorities(); + case WalletType.bitcoinCash: + return bitcoinCash!.getTransactionPriorities(); // no such thing for nano/banano: case WalletType.nano: case WalletType.banano: diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 602db2d49..abafc2f26 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -50,6 +50,9 @@ class CWEthereum extends Ethereum { @override TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium; + @override + TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow; + @override List getTransactionPriorities() => EthereumTransactionPriority.all; diff --git a/lib/main.dart b/lib/main.dart index c50c16f61..2d76196c3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -159,7 +159,7 @@ Future initializeAppConfigs() async { transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 22); + initialMigrationVersion: 23); } Future initialSetup( diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index b22acdc8b..1aa7f6c4a 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State { this.havenIcon = Image.asset('assets/images/haven_menu.png'), this.ethereumIcon = Image.asset('assets/images/eth_icon.png'), this.nanoIcon = Image.asset('assets/images/nano_icon.png'), - this.bananoIcon = Image.asset('assets/images/nano_icon.png'); + this.bananoIcon = Image.asset('assets/images/nano_icon.png'), + this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'); final largeScreen = 731; @@ -50,10 +51,10 @@ class MenuWidgetState extends State { Image litecoinIcon; Image havenIcon; Image ethereumIcon; + Image bitcoinCashIcon; Image nanoIcon; Image bananoIcon; - @override void initState() { menuWidth = 0; @@ -212,6 +213,8 @@ class MenuWidgetState extends State { return havenIcon; case WalletType.ethereum: return ethereumIcon; + case WalletType.bitcoinCash: + return bitcoinCashIcon; case WalletType.nano: return nanoIcon; case WalletType.banano: diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index a73e7bbff..7e6bfb1de 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -73,6 +73,7 @@ class PreSeedPage extends BasePage { case WalletType.monero: return 25; case WalletType.ethereum: + case WalletType.bitcoinCash: return 12; default: return 24; diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 1c1fbfa5d..1a173f62a 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -1,8 +1,10 @@ +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -79,6 +81,9 @@ class UnspentCoinsListFormState extends State { itemBuilder: (_, int index) { return Observer(builder: (_) { final item = unspentCoinsListViewModel.items[index]; + final address = unspentCoinsListViewModel.wallet.type == WalletType.bitcoinCash + ? bitcoinCash!.getCashAddrFormat(item.address) + : item.address; return GestureDetector( onTap: () => @@ -88,7 +93,7 @@ class UnspentCoinsListFormState extends State { child: UnspentCoinsListItem( note: item.note, amount: item.amount, - address: item.address, + address: address, isSending: item.isSending, isFrozen: item.isFrozen, onCheckBoxTap: item.isFrozen diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index a0b44f375..11b394460 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -48,6 +48,7 @@ class WalletListBodyState extends State { final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); + final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24); final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; @@ -243,6 +244,8 @@ class WalletListBodyState extends State { return havenIcon; case WalletType.ethereum: return ethereumIcon; + case WalletType.bitcoinCash: + return bitcoinCashIcon; case WalletType.nano: return nanoIcon; default: diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 939d724d6..a2e2570e0 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; @@ -85,7 +86,8 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialLitecoinTransactionPriority, - TransactionPriority? initialEthereumTransactionPriority}) + TransactionPriority? initialEthereumTransactionPriority, + TransactionPriority? initialBitcoinCashTransactionPriority}) : nodes = ObservableMap.of(nodes), powNodes = ObservableMap.of(powNodes), _sharedPreferences = sharedPreferences, @@ -146,6 +148,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.ethereum] = initialEthereumTransactionPriority; } + if (initialBitcoinCashTransactionPriority != null) { + priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; + } + reaction( (_) => fiatCurrency, (FiatCurrency fiatCurrency) => sharedPreferences.setString( @@ -174,6 +180,9 @@ abstract class SettingsStoreBase with Store { case WalletType.ethereum: key = PreferencesKey.ethereumTransactionPriority; break; + case WalletType.bitcoinCash: + key = PreferencesKey.bitcoinCashTransactionPriority; + break; default: key = null; } @@ -526,12 +535,13 @@ abstract class SettingsStoreBase with Store { TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority( raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!); TransactionPriority? bitcoinTransactionPriority = - bitcoin?.deserializeBitcoinTransactionPriority( - sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!); + bitcoin?.deserializeBitcoinTransactionPriority( + sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!); TransactionPriority? havenTransactionPriority; TransactionPriority? litecoinTransactionPriority; TransactionPriority? ethereumTransactionPriority; + TransactionPriority? bitcoinCashTransactionPriority; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { havenTransactionPriority = monero?.deserializeMoneroTransactionPriority( @@ -545,12 +555,17 @@ abstract class SettingsStoreBase with Store { ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { + bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority( + sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); + } moneroTransactionPriority ??= monero?.getDefaultTransactionPriority(); bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority(); havenTransactionPriority ??= monero?.getDefaultTransactionPriority(); litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium(); ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority(); + bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); @@ -560,7 +575,8 @@ abstract class SettingsStoreBase with Store { final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false; final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false; - final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; + final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt( + PreferencesKey.defaultBuyProvider) ?? 0]; final currentFiatApiMode = FiatApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw); @@ -579,7 +595,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? false; final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? false; final shouldRequireTOTP2FAForAddingContacts = sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; @@ -587,7 +603,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? false; final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? false; final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false; final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? ''; @@ -612,7 +628,7 @@ abstract class SettingsStoreBase with Store { ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) : defaultPinCodeTimeOutDuration; final sortBalanceBy = - SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; + SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; final pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; @@ -626,9 +642,11 @@ abstract class SettingsStoreBase with Store { await LanguageService.localeDetection(); final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final litecoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final bitcoinCashElectrumServerId = + sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); @@ -638,13 +656,14 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); final nanoPowNode = powNodeSource.get(nanoPowNodeId); final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; final generateSubaddresses = - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); + sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); final autoGenerateSubaddressStatus = generateSubaddresses != null ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) @@ -672,70 +691,76 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (bitcoinCashElectrumServer != null) { + nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; + } + if (nanoNode != null) { nodes[WalletType.nano] = nanoNode; } + if (nanoPowNode != null) { powNodes[WalletType.nano] = nanoPowNode; } - final savedSyncMode = SyncMode.all.firstWhere((element) { - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); - }); - final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; + final savedSyncMode = SyncMode.all.firstWhere((element) { + return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); + }); + final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; - return SettingsStore( - sharedPreferences: sharedPreferences, - initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, - nodes: nodes, - powNodes: powNodes, - appVersion: packageInfo.version, - deviceName: deviceName, - isBitcoinBuyEnabled: isBitcoinBuyEnabled, - initialFiatCurrency: currentFiatCurrency, - initialBalanceDisplayMode: currentBalanceDisplayMode, - initialSaveRecipientAddress: shouldSaveRecipientAddress, - initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, - initialAppSecure: isAppSecure, - initialDisableBuy: disableBuy, - initialDisableSell: disableSell, - initialDefaultBuyProvider: defaultBuyProvider, - initialFiatMode: currentFiatApiMode, - initialAllowBiometricalAuthentication: allowBiometricalAuthentication, - initialCake2FAPresetOptions: selectedCake2FAPreset, - initialUseTOTP2FA: useTOTP2FA, - initialTotpSecretKey: totpSecretKey, - initialFailedTokenTrial: tokenTrialNumber, - initialExchangeStatus: exchangeStatus, - initialTheme: savedTheme, - actionlistDisplayMode: actionListDisplayMode, - initialPinLength: pinLength, - pinTimeOutDuration: pinCodeTimeOutDuration, - initialLanguageCode: savedLanguageCode, - sortBalanceBy: sortBalanceBy, - pinNativeTokenAtTop: pinNativeTokenAtTop, - useEtherscan: useEtherscan, - initialMoneroTransactionPriority: moneroTransactionPriority, - initialBitcoinTransactionPriority: bitcoinTransactionPriority, - initialHavenTransactionPriority: havenTransactionPriority, - initialLitecoinTransactionPriority: litecoinTransactionPriority, - initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, - initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, - initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, - initialShouldRequireTOTP2FAForSendsToInternalWallets: - shouldRequireTOTP2FAForSendsToInternalWallets, - initialShouldRequireTOTP2FAForExchangesToInternalWallets: - shouldRequireTOTP2FAForExchangesToInternalWallets, - initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, - initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, - initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: - shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - initialEthereumTransactionPriority: ethereumTransactionPriority, - backgroundTasks: backgroundTasks, - initialSyncMode: savedSyncMode, - initialSyncAll: savedSyncAll, - shouldShowYatPopup: shouldShowYatPopup); - } + return SettingsStore( + sharedPreferences: sharedPreferences, + initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, + nodes: nodes, + powNodes: powNodes, + appVersion: packageInfo.version, + deviceName: deviceName, + isBitcoinBuyEnabled: isBitcoinBuyEnabled, + initialFiatCurrency: currentFiatCurrency, + initialBalanceDisplayMode: currentBalanceDisplayMode, + initialSaveRecipientAddress: shouldSaveRecipientAddress, + initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, + initialAppSecure: isAppSecure, + initialDisableBuy: disableBuy, + initialDisableSell: disableSell, + initialDefaultBuyProvider: defaultBuyProvider, + initialFiatMode: currentFiatApiMode, + initialAllowBiometricalAuthentication: allowBiometricalAuthentication, + initialCake2FAPresetOptions: selectedCake2FAPreset, + initialUseTOTP2FA: useTOTP2FA, + initialTotpSecretKey: totpSecretKey, + initialFailedTokenTrial: tokenTrialNumber, + initialExchangeStatus: exchangeStatus, + initialTheme: savedTheme, + actionlistDisplayMode: actionListDisplayMode, + initialPinLength: pinLength, + pinTimeOutDuration: pinCodeTimeOutDuration, + initialLanguageCode: savedLanguageCode, + sortBalanceBy: sortBalanceBy, + pinNativeTokenAtTop: pinNativeTokenAtTop, + useEtherscan: useEtherscan, + initialMoneroTransactionPriority: moneroTransactionPriority, + initialBitcoinTransactionPriority: bitcoinTransactionPriority, + initialHavenTransactionPriority: havenTransactionPriority, + initialLitecoinTransactionPriority: litecoinTransactionPriority, + initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority, + initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, + initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, + initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, + initialShouldRequireTOTP2FAForSendsToInternalWallets: + shouldRequireTOTP2FAForSendsToInternalWallets, + initialShouldRequireTOTP2FAForExchangesToInternalWallets: + shouldRequireTOTP2FAForExchangesToInternalWallets, + initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, + initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, + initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: + shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + initialEthereumTransactionPriority: ethereumTransactionPriority, + backgroundTasks: backgroundTasks, + initialSyncMode: savedSyncMode, + initialSyncAll: savedSyncAll, + shouldShowYatPopup: shouldShowYatPopup); + } Future reload({required Box nodeSource}) async { final sharedPreferences = await getIt.getAsync(); @@ -744,30 +769,35 @@ abstract class SettingsStoreBase with Store { raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!); priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority( - raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? + raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? priority[WalletType.monero]!; priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority( - sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? + sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? priority[WalletType.bitcoin]!; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority( - raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ?? + raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ?? priority[WalletType.haven]!; } if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) { priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority( - sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ?? + sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ?? priority[WalletType.litecoin]!; } if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority( - sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? priority[WalletType.ethereum]!; } + if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { + priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority( + sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ?? + priority[WalletType.bitcoinCash]!; + } final generateSubaddresses = - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); + sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); autoGenerateSubaddressStatus = generateSubaddresses != null ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) @@ -785,7 +815,8 @@ abstract class SettingsStoreBase with Store { isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; - defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; + defaultBuyProvider = + BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; allowBiometricalAuthentication = sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; @@ -802,7 +833,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? false; shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? false; shouldRequireTOTP2FAForAddingContacts = sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; @@ -810,7 +841,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? false; shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? false; shouldShowMarketPlaceInDashboard = sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? @@ -846,9 +877,11 @@ abstract class SettingsStoreBase with Store { final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final litecoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final bitcoinCashElectrumServerId = + sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); @@ -858,6 +891,7 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); if (moneroNode != null) { @@ -880,6 +914,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (bitcoinCashNode != null) { + nodes[WalletType.bitcoinCash] = bitcoinCashNode; + } + if (nanoNode != null) { nodes[WalletType.nano] = nanoNode; } @@ -904,6 +942,9 @@ abstract class SettingsStoreBase with Store { case WalletType.ethereum: await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int); break; + case WalletType.bitcoinCash: + await _sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); + break; case WalletType.nano: await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int); break; diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index e7e640afc..4e1c1aae0 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -72,6 +72,7 @@ class TransactionListItem extends ActionListItem with Keyable { break; case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.bitcoinCash: amount = calculateFiatAmountRaw( cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount), price: price); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 47b408bc2..7bc7927df 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -2,10 +2,12 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; @@ -265,8 +267,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } bool get hasAllAmount => - (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) && - depositCurrency == wallet.currency; + (wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash) && + depositCurrency == wallet.currency; bool get isMoneroWallet => wallet.type == WalletType.monero; @@ -278,7 +282,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.bitcoin: return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow(); case WalletType.litecoin: - return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow(); + return transactionPriority == + bitcoin!.getLitecoinTransactionPrioritySlow(); + case WalletType.ethereum: + return transactionPriority == + ethereum!.getEthereumTransactionPrioritySlow(); + case WalletType.bitcoinCash: + return transactionPriority == + bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); default: return false; } @@ -619,7 +630,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @action void calculateDepositAllAmount() { - if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { + if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) { final availableBalance = wallet.balance[wallet.currency]!.available; final priority = _settingsStore.priority[wallet.type]!; final fee = wallet.calculateEstimatedFee(priority, null); @@ -694,6 +705,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.ltc; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.bitcoinCash: + depositCurrency = CryptoCurrency.bch; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.haven: depositCurrency = CryptoCurrency.xhv; receiveCurrency = CryptoCurrency.btc; @@ -789,6 +804,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.litecoin: _settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium(); break; + case WalletType.ethereum: + _settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority(); + break; + case WalletType.bitcoinCash: + _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority(); + break; default: break; } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index fb1198c41..ae0edba30 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.ethereum: node = getEthereumDefaultNode(nodes: _nodeSource)!; break; + case WalletType.bitcoinCash: + node = getBitcoinCashDefaultElectrumServer(nodes: _nodeSource)!; + break; case WalletType.nano: node = getNanoDefaultNode(nodes: _nodeSource)!; break; diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index 39a7b682f..2bc595469 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; @@ -84,6 +85,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store case WalletType.litecoin: return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromSeedCredentials( name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index e9aed55c6..dd138b66c 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -72,8 +72,13 @@ class WalletRestoreFromQRCode { case 'litecoin': case 'litecoin-wallet': return WalletType.litecoin; + case 'bitcoincash': + case 'bitcoinCash-wallet': + return WalletType.bitcoinCash; case 'ethereum-wallet': return WalletType.ethereum; + case 'nano-wallet': + return WalletType.nano; default: throw Exception('Unexpected wallet type: ${scheme.toString()}'); } @@ -107,6 +112,7 @@ class WalletRestoreFromQRCode { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.bitcoinCash: RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b'); diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 8008812ba..2e696e16f 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -81,10 +81,8 @@ abstract class OutputBase with Store { _amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount); break; case WalletType.bitcoin: - _amount = - bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); - break; case WalletType.litecoin: + case WalletType.bitcoinCash: _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); break; @@ -116,7 +114,8 @@ abstract class OutputBase with Store { _settingsStore.priority[_wallet.type]!, formattedCryptoAmount); if (_wallet.type == WalletType.bitcoin || - _wallet.type == WalletType.litecoin) { + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.bitcoinCash) { return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); } @@ -234,6 +233,9 @@ abstract class OutputBase with Store { case WalletType.litecoin: maximumFractionDigits = 8; break; + case WalletType.bitcoinCash: + maximumFractionDigits = 8; + break; case WalletType.haven: maximumFractionDigits = 12; break; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 2ba6dc784..719298675 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -185,12 +185,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get hasCoinControl => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.monero; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.monero || + wallet.type == WalletType.bitcoinCash; @computed bool get isElectrumWallet => - wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; @computed bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; @@ -345,41 +348,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor _settingsStore.priority[wallet.type] = priority; Object _credentials() { + final priority = _settingsStore.priority[wallet.type]; + + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); + switch (wallet.type) { case WalletType.bitcoin: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - - return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); case WalletType.litecoin: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - + case WalletType.bitcoinCash: return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); + case WalletType.monero: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - return monero! .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority); + case WalletType.haven: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - return haven!.createHavenTransactionCreationCredentials( outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title); + case WalletType.ethereum: final priority = _settingsStore.priority[wallet.type]; @@ -390,9 +376,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return ethereum!.createEthereumTransactionCredentials(outputs, priority: priority, currency: selectedCryptoCurrency); case WalletType.nano: - return nano!.createNanoTransactionCredentials( - outputs, - ); + return nano!.createNanoTransactionCredentials(outputs); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 587e8723b..b4ca46f70 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -63,7 +63,9 @@ abstract class OtherSettingsViewModelBase with Store { String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; - if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin) { + if (_wallet.type == WalletType.bitcoin || + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.bitcoinCash) { final rate = bitcoin!.getFeeRate(_wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index fd001125f..a8c892284 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -39,6 +39,7 @@ abstract class TransactionDetailsViewModelBase with Store { break; case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.bitcoinCash: _addElectrumListItems(tx, dateFormat); break; case WalletType.haven: @@ -115,6 +116,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://mempool.space/tx/${txId}'; case WalletType.litecoin: return 'https://blockchair.com/litecoin/transaction/${txId}'; + case WalletType.bitcoinCash: + return 'https://blockchair.com/bitcoin-cash/transaction/${txId}'; case WalletType.haven: return 'https://explorer.havenprotocol.org/search?value=${txId}'; case WalletType.ethereum: @@ -135,6 +138,7 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.bitcoin: return S.current.view_transaction_on + 'mempool.space'; case WalletType.litecoin: + case WalletType.bitcoinCash: return S.current.view_transaction_on + 'Blockchair.com'; case WalletType.haven: return S.current.view_transaction_on + 'explorer.havenprotocol.org'; diff --git a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart index 992991147..4da43c241 100644 --- a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart @@ -1,9 +1,10 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; import 'package:cw_core/wallet_type.dart'; @@ -19,12 +20,14 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { UnspentCoinsDetailsViewModelBase( {required this.unspentCoinsItem, required this.unspentCoinsListViewModel}) : items = [], + _type = unspentCoinsListViewModel.wallet.type, isFrozen = unspentCoinsItem.isFrozen, note = unspentCoinsItem.note { items = [ StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount), - StandartListItem(title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash), - StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address), + StandartListItem( + title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash), + StandartListItem(title: S.current.widgets_address, value: formattedAddress), TextFieldListItem( title: S.current.note_tap_to_change, value: note, @@ -46,14 +49,13 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { }) ]; - if ([WalletType.bitcoin, WalletType.litecoin].contains(unspentCoinsListViewModel.wallet.type)) { + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(_type)) { items.add(BlockExplorerListItem( title: S.current.view_in_block_explorer, - value: _explorerDescription(unspentCoinsListViewModel.wallet.type), + value: _explorerDescription(_type), onTap: () { try { - final url = Uri.parse( - _explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash)); + final url = Uri.parse(_explorerUrl(_type, unspentCoinsItem.hash)); return launchUrl(url); } catch (e) {} }, @@ -67,6 +69,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { return 'https://ordinals.com/tx/${txId}'; case WalletType.litecoin: return 'https://litecoin.earlyordies.com/tx/${txId}'; + case WalletType.bitcoinCash: + return 'https://blockchair.com/bitcoin-cash/transaction/${txId}'; default: return ''; } @@ -78,6 +82,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { return S.current.view_transaction_on + 'Ordinals.com'; case WalletType.litecoin: return S.current.view_transaction_on + 'Earlyordies.com'; + case WalletType.bitcoinCash: + return S.current.view_transaction_on + 'Blockchair.com'; default: return ''; } @@ -91,5 +97,10 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { final UnspentCoinsItem unspentCoinsItem; final UnspentCoinsListViewModel unspentCoinsListViewModel; + final WalletType _type; List items; + + String get formattedAddress => WalletType.bitcoinCash == _type + ? bitcoinCash!.getCashAddrFormat(unspentCoinsItem.address) + : unspentCoinsItem.address; } diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 657a0cb74..709c50562 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -1,9 +1,10 @@ import 'package:collection/collection.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/entities/unspent_transaction_output.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; @@ -24,11 +25,11 @@ abstract class UnspentCoinsListViewModelBase with Store { final Box _unspentCoinsInfo; @computed - ObservableList get items => - ObservableList.of(_getUnspents().map((elem) { + ObservableList get items => ObservableList.of(_getUnspents().map((elem) { final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}'; - final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); + final info = + getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); return UnspentCoinsItem( address: elem.address, @@ -39,13 +40,13 @@ abstract class UnspentCoinsListViewModelBase with Store { isSending: info?.isSending ?? true, amountRaw: elem.value, vout: elem.vout, - keyImage: elem.keyImage - ); + keyImage: elem.keyImage); })); Future saveUnspentCoinInfo(UnspentCoinsItem item) async { try { - final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage); + final info = + getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage); if (info == null) { final newInfo = UnspentCoinsInfo( walletId: wallet.id, @@ -56,8 +57,7 @@ abstract class UnspentCoinsListViewModelBase with Store { isFrozen: item.isFrozen, isSending: item.isSending, noteRaw: item.note, - keyImage: item.keyImage - ); + keyImage: item.keyImage); await _unspentCoinsInfo.add(newInfo); _updateUnspents(); @@ -76,37 +76,34 @@ abstract class UnspentCoinsListViewModelBase with Store { } } - UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout, String? keyImage) { + UnspentCoinsInfo? getUnspentCoinInfo( + String hash, String address, int value, int vout, String? keyImage) { return _unspentCoinsInfo.values.firstWhereOrNull((element) => element.walletId == wallet.id && element.hash == hash && element.address == address && element.value == value && element.vout == vout && - element.keyImage == keyImage - ); + element.keyImage == keyImage); } String formatAmountToString(int fullBalance) { if (wallet.type == WalletType.monero) return monero!.formatterMoneroAmountToString(amount: fullBalance); - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance); return ''; } - void _updateUnspents() { - if (wallet.type == WalletType.monero) - return monero!.updateUnspents(wallet); - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) + if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet); + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) return bitcoin!.updateUnspents(wallet); } List _getUnspents() { - if (wallet.type == WalletType.monero) - return monero!.getUnspents(wallet); - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) + if (wallet.type == WalletType.monero) return monero!.getUnspents(wallet); + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) return bitcoin!.getUnspents(wallet); return List.empty(); } diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index a4eb3d386..9e2aa7187 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -66,7 +66,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { final wallet = _wallet; if (wallet.type == WalletType.bitcoin - || wallet.type == WalletType.litecoin) { + || wallet.type == WalletType.litecoin + || wallet.type == WalletType.bitcoinCash) { await bitcoin!.generateNewAddress(wallet); await wallet.save(); } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index a24e1635f..4d5eefdb7 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -107,6 +107,23 @@ class EthereumURI extends PaymentURI { } } +class BitcoinCashURI extends PaymentURI { + BitcoinCashURI({required String amount, required String address}) + : super(amount: amount, address: address); + @override + String toString() { + var base = address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } + } + + + class NanoURI extends PaymentURI { NanoURI({required String amount, required String address}) : super(amount: amount, address: address); @@ -114,7 +131,6 @@ class NanoURI extends PaymentURI { @override String toString() { var base = 'nano:' + address; - if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; } @@ -192,6 +208,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return EthereumURI(amount: amount, address: address.address); } + if (wallet.type == WalletType.bitcoinCash) { + return BitcoinCashURI(amount: amount, address: address.address); + } + if (wallet.type == WalletType.nano) { return NanoURI(amount: amount, address: address.address); } @@ -280,7 +300,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @computed bool get showElectrumAddressDisclaimer => - wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; List _baseItems; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index e2938c74e..e106126bc 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -19,6 +19,7 @@ abstract class WalletKeysViewModelBase with Store { WalletKeysViewModelBase(this._appStore) : title = _appStore.wallet!.type == WalletType.bitcoin || _appStore.wallet!.type == WalletType.litecoin || + _appStore.wallet!.type == WalletType.bitcoinCash || _appStore.wallet!.type == WalletType.ethereum ? S.current.wallet_seed : S.current.wallet_keys, @@ -91,7 +92,8 @@ abstract class WalletKeysViewModelBase with Store { } if (_appStore.wallet!.type == WalletType.bitcoin || - _appStore.wallet!.type == WalletType.litecoin) { + _appStore.wallet!.type == WalletType.litecoin || + _appStore.wallet!.type == WalletType.bitcoinCash) { items.addAll([ StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); @@ -145,6 +147,8 @@ abstract class WalletKeysViewModelBase with Store { return 'haven-wallet'; case WalletType.ethereum: return 'ethereum-wallet'; + case WalletType.bitcoinCash: + return 'bitcoinCash-wallet'; case WalletType.nano: return 'nano-wallet'; case WalletType.banano: diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 04da7190e..9b1f0834d 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -46,10 +47,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, language: options as String); case WalletType.ethereum: return ethereum!.createEthereumNewWalletCredentials(name: name); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name); case WalletType.nano: return nano!.createNanoNewWalletCredentials(name: name); default: - throw Exception('Unexpected type: ${type.toString()}');; + throw Exception('Unexpected type: ${type.toString()}'); } } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 4eb69e48f..058948c2f 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -92,14 +93,20 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, height: height, mnemonic: seed, password: password); case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromSeedCredentials( - name: name, mnemonic: seed, password: password); + name: name, + mnemonic: seed, + password: password); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password); case WalletType.nano: return nano!.createNanoRestoreWalletFromSeedCredentials( name: name, mnemonic: seed, password: password, - derivationType: derivationType, - ); + derivationType: derivationType); default: break; } @@ -145,8 +152,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, password: password, seedKey: options['private_key'] as String, - derivationType: options["derivationType"] as DerivationType, - ); + derivationType: options["derivationType"] as DerivationType); default: break; } @@ -167,8 +173,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { return nanoUtil!.compareDerivationMethods( mnemonic: mnemonic, privateKey: seedKey, - node: node, - ); + node: node); default: break; } diff --git a/model_generator.sh b/model_generator.sh old mode 100755 new mode 100644 index 0e4345c25..50cb3d353 --- a/model_generator.sh +++ b/model_generator.sh @@ -4,4 +4,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 1e5a2df26..6f8d51615 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -129,6 +129,7 @@ flutter: - assets/bitcoin_electrum_server_list.yml - assets/litecoin_electrum_server_list.yml - assets/ethereum_server_list.yml + - assets/bitcoin_cash_electrum_server_list.yml - assets/nano_node_list.yml - assets/nano_pow_node_list.yml - assets/text/ diff --git a/scripts/android/app_config.sh b/scripts/android/app_config.sh index 01edb14a4..e2cbd72da 100755 --- a/scripts/android/app_config.sh +++ b/scripts/android/app_config.sh @@ -9,4 +9,4 @@ fi ./app_icon.sh ./pubspec_gen.sh ./manifest.sh -./inject_app_details.sh \ No newline at end of file +./inject_app_details.sh diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index c74108bf1..dd9852072 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" ;; $HAVEN) CONFIG_ARGS="--haven" diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh old mode 100755 new mode 100644 index e62b06548..8d999f594 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -28,9 +28,11 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" ;; $HAVEN) + + CONFIG_ARGS="--haven" ;; esac diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 2af101485..48b680330 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -23,7 +23,7 @@ CONFIG_ARGS="" case $APP_MACOS_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --nano";; #--haven + CONFIG_ARGS="--monero --bitcoin --ethereum --nano --bitcoinCash";; #--haven esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index f7b9dc126..534aeef57 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -4,6 +4,7 @@ const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart'; const moneroOutputPath = 'lib/monero/monero.dart'; const havenOutputPath = 'lib/haven/haven.dart'; const ethereumOutputPath = 'lib/ethereum/ethereum.dart'; +const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart'; const nanoOutputPath = 'lib/nano/nano.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; @@ -15,6 +16,7 @@ Future main(List args) async { final hasMonero = args.contains('${prefix}monero'); final hasHaven = args.contains('${prefix}haven'); final hasEthereum = args.contains('${prefix}ethereum'); + final hasBitcoinCash = args.contains('${prefix}bitcoinCash'); final hasNano = args.contains('${prefix}nano'); final hasBanano = args.contains('${prefix}banano'); @@ -22,6 +24,7 @@ Future main(List args) async { await generateMonero(hasMonero); await generateHaven(hasHaven); await generateEthereum(hasEthereum); + await generateBitcoinCash(hasBitcoinCash); await generateNano(hasNano); // await generateBanano(hasEthereum); @@ -32,6 +35,7 @@ Future main(List args) async { hasEthereum: hasEthereum, hasNano: hasNano, hasBanano: hasBanano, + hasBitcoinCash: hasBitcoinCash, ); await generateWalletTypes( hasMonero: hasMonero, @@ -40,13 +44,13 @@ Future main(List args) async { hasEthereum: hasEthereum, hasNano: hasNano, hasBanano: hasBanano, + hasBitcoinCash: hasBitcoinCash, ); } Future generateBitcoin(bool hasImplementation) async { final outputFile = File(bitcoinOutputPath); const bitcoinCommonHeaders = """ -import 'package:cake_wallet/entities/unspent_transaction_output.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -60,7 +64,6 @@ import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet_service.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; @@ -80,8 +83,8 @@ abstract class Bitcoin { Map getWalletKeys(Object wallet); List getTransactionPriorities(); List getLitecoinTransactionPriorities(); - TransactionPriority deserializeBitcoinTransactionPriority(int raw); - TransactionPriority deserializeLitecoinTransactionPriority(int raw); + TransactionPriority deserializeBitcoinTransactionPriority(int raw); + TransactionPriority deserializeLitecoinTransactionPriority(int raw); int getFeeRate(Object wallet, TransactionPriority priority); Future generateNewAddress(Object wallet); Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate}); @@ -95,7 +98,7 @@ abstract class Bitcoin { int formatterStringDoubleToBitcoinAmount(String amount); String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate); - List getUnspents(Object wallet); + List getUnspents(Object wallet); void updateUnspents(Object wallet); WalletService createBitcoinWalletService(Box walletInfoSource, Box unspentCoinSource); WalletService createLitecoinWalletService(Box walletInfoSource, Box unspentCoinSource); @@ -126,7 +129,7 @@ abstract class Bitcoin { Future generateMonero(bool hasImplementation) async { final outputFile = File(moneroOutputPath); const moneroCommonHeaders = """ -import 'package:cake_wallet/entities/unspent_transaction_output.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_monero/monero_unspent.dart'; import 'package:mobx/mobx.dart'; @@ -521,6 +524,7 @@ abstract class Ethereum { String getPrivateKey(WalletBase wallet); String getPublicKey(WalletBase wallet); TransactionPriority getDefaultTransactionPriority(); + TransactionPriority getEthereumTransactionPrioritySlow(); List getTransactionPriorities(); TransactionPriority deserializeEthereumTransactionPriority(int raw); @@ -568,6 +572,67 @@ abstract class Ethereum { await outputFile.writeAsString(output); } +Future generateBitcoinCash(bool hasImplementation) async { + final outputFile = File(bitcoinCashOutputPath); + const bitcoinCashCommonHeaders = """ +import 'dart:typed_data'; + +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart'; +"""; + const bitcoinCashCWHeaders = """ +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; +"""; + const bitcoinCashCwPart = "part 'cw_bitcoin_cash.dart';"; + const bitcoinCashContent = """ +abstract class BitcoinCash { + String getMnemonic(int? strength); + + Uint8List getSeedFromMnemonic(String seed); + + String getCashAddrFormat(String address); + + WalletService createBitcoinCashWalletService( + Box walletInfoSource, Box unspentCoinSource); + + WalletCredentials createBitcoinCashNewWalletCredentials( + {required String name, WalletInfo? walletInfo}); + + WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials( + {required String name, required String mnemonic, required String password}); + + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw); + + TransactionPriority getDefaultTransactionPriority(); + + List getTransactionPriorities(); + + TransactionPriority getBitcoinCashTransactionPrioritySlow(); +} + """; + + const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n'; + const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n'; + + final output = '$bitcoinCashCommonHeaders\n' + + (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') + + (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') + + (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) + + '\n' + + bitcoinCashContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generateNano(bool hasImplementation) async { final outputFile = File(nanoOutputPath); const nanoCommonHeaders = """ @@ -710,14 +775,14 @@ abstract class NanoUtil { await outputFile.writeAsString(output); } -Future generatePubspec({ - required bool hasMonero, - required bool hasBitcoin, - required bool hasHaven, - required bool hasEthereum, - required bool hasNano, - required bool hasBanano, -}) async { +Future generatePubspec( + {required bool hasMonero, + required bool hasBitcoin, + required bool hasHaven, + required bool hasEthereum, + required bool hasNano, + required bool hasBanano, + required bool hasBitcoinCash}) async { const cwCore = """ cw_core: path: ./cw_core @@ -742,6 +807,10 @@ Future generatePubspec({ cw_ethereum: path: ./cw_ethereum """; + const cwBitcoinCash = """ + cw_bitcoin_cash: + path: ./cw_bitcoin_cash + """; const cwNano = """ cw_nano: path: ./cw_nano @@ -776,6 +845,10 @@ Future generatePubspec({ output += '\n$cwBanano'; } + if (hasBitcoinCash) { + output += '\n$cwBitcoinCash'; + } + if (hasHaven && !hasMonero) { output += '\n$cwSharedExternal\n$cwHaven'; } else if (hasHaven) { @@ -794,14 +867,14 @@ Future generatePubspec({ await outputFile.writeAsString(outputContent); } -Future generateWalletTypes({ - required bool hasMonero, - required bool hasBitcoin, - required bool hasHaven, - required bool hasEthereum, - required bool hasNano, - required bool hasBanano, -}) async { +Future generateWalletTypes( + {required bool hasMonero, + required bool hasBitcoin, + required bool hasHaven, + required bool hasEthereum, + required bool hasNano, + required bool hasBanano, + required bool hasBitcoinCash}) async { final walletTypesFile = File(walletTypesPath); if (walletTypesFile.existsSync()) { @@ -828,6 +901,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.litecoin,\n'; } + if (hasBitcoinCash) { + outputContent += '\tWalletType.bitcoinCash,\n'; + } + if (hasNano) { outputContent += '\tWalletType.nano,\n'; } From 426ac99e34334e25c29c25325b74fa21ef1a4c2e Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 13 Oct 2023 14:49:00 +0300 Subject: [PATCH 11/16] Generic Fixes (#1122) * Fix Hive issue * Disable RobinHood for Nano * Validate context is still mounted [skip ci] * Disable Exolix for new exchanges Remove duplicate ethereum case * add nano/banano to manifest/info.plist * fix qr code issues for nano * Add Nano-wallet to restore form qr Add iOS keychain accessibility config * support app links for ethereum and nano [skip ci] * catch exceptions from gas price and estimated gas * Add bitcoin cash to app links Fix restore from QR for bitcoin cash * Fixate bottom buttons for create/restore wallet in wallet list page --------- Co-authored-by: fosse --- android/app/src/main/AndroidManifestBase.xml | 9 + cw_ethereum/lib/ethereum_client.dart | 16 +- ios/Runner/InfoBase.plist | 60 ++++ lib/entities/main_actions.dart | 4 +- lib/main.dart | 11 +- .../screens/dashboard/edit_token_page.dart | 4 +- .../exchange_trade/exchange_trade_page.dart | 8 +- .../screens/wallet_list/wallet_list_page.dart | 322 +++++++++--------- lib/utils/exception_handler.dart | 2 +- lib/utils/feature_flag.dart | 1 + lib/utils/payment_request.dart | 10 +- .../dashboard/transaction_list_item.dart | 7 - .../exchange/exchange_view_model.dart | 3 +- .../restore/wallet_restore_from_qr_code.dart | 4 +- lib/view_model/wallet_keys_view_model.dart | 2 +- 15 files changed, 275 insertions(+), 188 deletions(-) diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index f22ba9c4f..77a555db8 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -52,6 +52,15 @@ + + + + + + + + + getGasUnitPrice() async { - final gasPrice = await _client!.getGasPrice(); - return gasPrice.getInWei.toInt(); + try { + final gasPrice = await _client!.getGasPrice(); + return gasPrice.getInWei.toInt(); + } catch (_) { + return 0; + } } Future getEstimatedGas() async { - final estimatedGas = await _client!.estimateGas(); - return estimatedGas.toInt(); + try { + final estimatedGas = await _client!.estimateGas(); + return estimatedGas.toInt(); + } catch (_) { + return 0; + } } Future signTransaction({ diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index e4d07c717..6cea7a730 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -100,6 +100,66 @@ litecoin-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + ethereum + CFBundleURLSchemes + + ethereum + + + + CFBundleTypeRole + Viewer + CFBundleURLName + ethereum-wallet + CFBundleURLSchemes + + ethereum-wallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + nano + CFBundleURLSchemes + + nano + + + + CFBundleTypeRole + Viewer + CFBundleURLName + nano-wallet + CFBundleURLSchemes + + nano-wallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + bitcoincash + CFBundleURLSchemes + + bitcoincash + + + + CFBundleTypeRole + Viewer + CFBundleURLName + bitcoincash-wallet + CFBundleURLSchemes + + bitcoincash-wallet + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index d2c67ef95..46865cbcc 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -53,8 +53,6 @@ class MainActions { case WalletType.litecoin: case WalletType.ethereum: case WalletType.bitcoinCash: - case WalletType.nano: - case WalletType.banano: switch (defaultBuyProvider) { case BuyProviderType.AskEachTime: Navigator.pushNamed(context, Routes.buy); @@ -67,6 +65,8 @@ class MainActions { break; } break; + case WalletType.nano: + case WalletType.banano: case WalletType.monero: await getIt.get().launchProvider(context); break; diff --git a/lib/main.dart b/lib/main.dart index 2d76196c3..98ba1e195 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -97,10 +97,10 @@ Future initializeAppConfigs() async { CakeHive.registerAdapter(WalletInfoAdapter()); } - if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) { + if (!CakeHive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) { CakeHive.registerAdapter(DerivationTypeAdapter()); } - + if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) { CakeHive.registerAdapter(WalletTypeAdapter()); } @@ -125,14 +125,17 @@ Future initializeAppConfigs() async { CakeHive.registerAdapter(AnonpayInvoiceInfoAdapter()); } - final secureStorage = FlutterSecureStorage(); + final secureStorage = FlutterSecureStorage( + iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), + ); final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey); final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey); final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey); final contacts = await CakeHive.openBox(Contact.boxName); final nodes = await CakeHive.openBox(Node.boxName); - final powNodes = await CakeHive.openBox(Node.boxName + "pow");// must be different from Node.boxName + final powNodes = + await CakeHive.openBox(Node.boxName + "pow"); // must be different from Node.boxName final transactionDescriptions = await CakeHive.openBox( TransactionDescription.boxName, encryptionKey: transactionDescriptionsBoxKey); diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index 50bcb24e1..df6d3bd09 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -194,7 +194,9 @@ class _EditTokenPageBodyState extends State { contractAddress: _contractAddressController.text, decimal: int.parse(_tokenDecimalController.text), )); - Navigator.pop(context); + if (context.mounted) { + Navigator.pop(context); + } } }, text: S.of(context).save, diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index dbf6676a1..22606c21e 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -5,7 +5,6 @@ import 'package:cake_wallet/utils/request_review_handler.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/execution_state.dart'; @@ -26,16 +25,15 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; void showInformation( ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { - final fetchingLabel = S.current.fetching; final trade = exchangeTradeViewModel.trade; final walletName = exchangeTradeViewModel.wallet.name; final information = exchangeTradeViewModel.isSendable ? S.current.exchange_result_confirm( - trade.amount ?? fetchingLabel, trade.from.toString(), walletName) + + trade.amount, trade.from.toString(), walletName) + exchangeTradeViewModel.extraInfo : S.current.exchange_result_description( - trade.amount ?? fetchingLabel, trade.from.toString()) + + trade.amount, trade.from.toString()) + exchangeTradeViewModel.extraInfo; showPopUp( @@ -177,7 +175,7 @@ class ExchangeTradeState extends State { ), itemBuilder: (context, index) { final item = widget.exchangeTradeViewModel.items[index]; - final value = item.data ?? fetchingLabel; + final value = item.data; final content = ListRow( title: item.title, diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 11b394460..cce4a2581 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -13,7 +13,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; @@ -65,169 +64,178 @@ class WalletListBodyState extends State { return Container( padding: EdgeInsets.only(top: 16), - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(bottom: 20), - content: Container( - child: Observer( - builder: (_) => ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - separatorBuilder: (_, index) => - Divider(color: Theme.of(context).colorScheme.background, height: 32), - itemCount: widget.walletListViewModel.wallets.length, - itemBuilder: (__, index) { - final wallet = widget.walletListViewModel.wallets[index]; - final currentColor = wallet.isCurrent - ? Theme.of(context) - .extension()! - .createNewWalletButtonBackgroundColor - : Theme.of(context).colorScheme.background; - final row = GestureDetector( - onTap: () => wallet.isCurrent ? null : _loadWallet(wallet), - child: Container( - height: tileHeight, - width: double.infinity, - child: Row( - children: [ - Container( - height: tileHeight, - width: 4, - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(4), bottomRight: Radius.circular(4)), - color: currentColor), - ), - Expanded( - child: Container( - height: tileHeight, - padding: EdgeInsets.only(left: 20, right: 20), - color: Theme.of(context).colorScheme.background, - alignment: Alignment.centerLeft, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon, - SizedBox(width: 10), - Flexible( - child: Text( - wallet.name, - maxLines: null, - softWrap: true, - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor, - ), - ), - ), - ], + child: Column( + children: [ + Expanded( + child: Container( + child: Observer( + builder: (_) => ListView.separated( + physics: const BouncingScrollPhysics(), + separatorBuilder: (_, index) => + Divider(color: Theme.of(context).colorScheme.background, height: 32), + itemCount: widget.walletListViewModel.wallets.length, + itemBuilder: (__, index) { + final wallet = widget.walletListViewModel.wallets[index]; + final currentColor = wallet.isCurrent + ? Theme.of(context) + .extension()! + .createNewWalletButtonBackgroundColor + : Theme.of(context).colorScheme.background; + final row = GestureDetector( + onTap: () => wallet.isCurrent ? null : _loadWallet(wallet), + child: Container( + height: tileHeight, + width: double.infinity, + child: Row( + children: [ + Container( + height: tileHeight, + width: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4)), + color: currentColor), ), - ), - ), - ], - ), - ), - ); - - return wallet.isCurrent - ? row - : Row( - children: [ - Expanded(child: row), - GestureDetector( - onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit, - arguments: [widget.walletListViewModel, wallet]), - child: Container( - padding: EdgeInsets.only(right: 20), - child: Center( - child: Container( - height: 40, - width: 44, - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context) - .extension()! - .iconsBackgroundColor, - ), - child: Icon( - Icons.edit, - size: 14, - color: - Theme.of(context).extension()!.iconsColor, - ), + Expanded( + child: Container( + height: tileHeight, + padding: EdgeInsets.only(left: 20, right: 20), + color: Theme.of(context).colorScheme.background, + alignment: Alignment.centerLeft, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + wallet.isEnabled + ? _imageFor(type: wallet.type) + : nonWalletTypeIcon, + SizedBox(width: 10), + Flexible( + child: Text( + wallet.name, + maxLines: null, + softWrap: true, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension()! + .titleColor, + ), + ), + ), + ], ), ), ), - ), - ], - ); - }, + ], + ), + ), + ); + + return wallet.isCurrent + ? row + : Row( + children: [ + Expanded(child: row), + GestureDetector( + onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit, + arguments: [widget.walletListViewModel, wallet]), + child: Container( + padding: EdgeInsets.only(right: 20), + child: Center( + child: Container( + height: 40, + width: 44, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context) + .extension()! + .iconsBackgroundColor, + ), + child: Icon( + Icons.edit, + size: 14, + color: Theme.of(context) + .extension()! + .iconsColor, + ), + ), + ), + ), + ), + ], + ); + }, + ), + ), ), ), - ), - bottomSectionPadding: EdgeInsets.only(bottom: 24, right: 24, left: 24), - bottomSection: Column( - children: [ - PrimaryImageButton( - onPressed: () { - //TODO(David): Find a way to optimize this - if (isSingleCoin) { - if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { - widget.authService.authenticateAction( - context, - route: Routes.newWallet, - arguments: widget.walletListViewModel.currentWalletType, - conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, - ); - } else { - Navigator.of(context).pushNamed( - Routes.newWallet, - arguments: widget.walletListViewModel.currentWalletType, - ); - } - } else { - if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { - widget.authService.authenticateAction( - context, - route: Routes.newWalletType, - conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, - ); - } else { - Navigator.of(context).pushNamed(Routes.newWalletType); - } - } - }, - image: newWalletImage, - text: S.of(context).wallet_list_create_new_wallet, - color: Theme.of(context).primaryColor, - textColor: Colors.white, + Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + PrimaryImageButton( + onPressed: () { + //TODO(David): Find a way to optimize this + if (isSingleCoin) { + if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { + widget.authService.authenticateAction( + context, + route: Routes.newWallet, + arguments: widget.walletListViewModel.currentWalletType, + conditionToDetermineIfToUse2FA: + widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, + ); + } else { + Navigator.of(context).pushNamed( + Routes.newWallet, + arguments: widget.walletListViewModel.currentWalletType, + ); + } + } else { + if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { + widget.authService.authenticateAction( + context, + route: Routes.newWalletType, + conditionToDetermineIfToUse2FA: + widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, + ); + } else { + Navigator.of(context).pushNamed(Routes.newWalletType); + } + } + }, + image: newWalletImage, + text: S.of(context).wallet_list_create_new_wallet, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + ), + SizedBox(height: 10.0), + PrimaryImageButton( + onPressed: () { + if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { + widget.authService.authenticateAction( + context, + route: Routes.restoreOptions, + arguments: false, + conditionToDetermineIfToUse2FA: + widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, + ); + } else { + Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); + } + }, + image: restoreWalletImage, + text: S.of(context).wallet_list_restore_wallet, + color: Theme.of(context).cardColor, + textColor: Theme.of(context).extension()!.buttonTextColor, + ) + ], ), - SizedBox(height: 10.0), - PrimaryImageButton( - onPressed: () { - if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { - widget.authService.authenticateAction( - context, - route: Routes.restoreOptions, - arguments: false, - conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, - ); - } else { - Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); - } - }, - image: restoreWalletImage, - text: S.of(context).wallet_list_restore_wallet, - color: Theme.of(context).cardColor, - textColor: Theme.of(context).extension()!.buttonTextColor, - ) - ], - ), + ), + ], ), ); } diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index b9c659872..bea43a6c6 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -161,7 +161,7 @@ class ExceptionHandler { "Handshake error in client", "Error while launching http", "OS Error: Network is unreachable", - "ClientException: Write failed, uri=https:", + "ClientException: Write failed, uri=http", ]; static Future _addDeviceInfo(File file) async { diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 2b0d5a2b9..628023f85 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -1,3 +1,4 @@ class FeatureFlag { static const bool isCakePayEnabled = false; + static const bool isExolixEnabled = false; } \ No newline at end of file diff --git a/lib/utils/payment_request.dart b/lib/utils/payment_request.dart index 6f82c3b94..00093b413 100644 --- a/lib/utils/payment_request.dart +++ b/lib/utils/payment_request.dart @@ -17,10 +17,12 @@ class PaymentRequest { } if (nano != null) { - if (address.contains("nano")) { - amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano); - } else if (address.contains("ban")) { - amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano); + if (amount.isNotEmpty) { + if (address.contains("nano")) { + amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano); + } else if (address.contains("ban")) { + amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano); + } } } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 4e1c1aae0..fd7971001 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -97,13 +97,6 @@ class TransactionListItem extends ActionListItem with Keyable { nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), price: price); break; - case WalletType.ethereum: - final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction); - final price = balanceViewModel.fiatConvertationStore.prices[asset]; - amount = calculateFiatAmountRaw( - cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction), - price: price); - break; default: break; } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 7bc7927df..f7db98245 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -16,6 +16,7 @@ import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dar import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_request.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -154,7 +155,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider(useTorOnly: _useTorOnly), - ExolixExchangeProvider(), + if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), ]; @observable diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index dd138b66c..bc100f9fe 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -73,10 +73,12 @@ class WalletRestoreFromQRCode { case 'litecoin-wallet': return WalletType.litecoin; case 'bitcoincash': - case 'bitcoinCash-wallet': + case 'bitcoincash-wallet': return WalletType.bitcoinCash; + case 'ethereum': case 'ethereum-wallet': return WalletType.ethereum; + case 'nano': case 'nano-wallet': return WalletType.nano; default: diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index e106126bc..81f521d27 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -148,7 +148,7 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.ethereum: return 'ethereum-wallet'; case WalletType.bitcoinCash: - return 'bitcoinCash-wallet'; + return 'bitcoincash-wallet'; case WalletType.nano: return 'nano-wallet'; case WalletType.banano: From 69a77d4f71a4d4c0615b679779aa146638f4d7ac Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 16 Oct 2023 16:44:20 +0300 Subject: [PATCH 12/16] New versions 4.10.1 and 1.7.1 (#1127) --- assets/text/Release_Notes.txt | 6 ++---- ios/Podfile.lock | 2 +- scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_config.sh | 0 scripts/ios/app_env.sh | 8 ++++---- scripts/macos/app_env.sh | 4 ++-- tool/configure.dart | 6 ++++-- 7 files changed, 17 insertions(+), 17 deletions(-) mode change 100644 => 100755 scripts/ios/app_config.sh diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 50c88833d..795ec3427 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,5 +1,3 @@ -Add Nano wallet -Add WalletConnect to connect your ETH wallet with your favorite dApp -Support getting Addresses from ENS and Mastodon +Add BitcoinCash (BCH) Bug fixes -Minor enhancements \ No newline at end of file +Accessibility enhancements \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6f441c587..acc3b8824 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -272,7 +272,7 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 + flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 771c1f89a..356cf03cd 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.7.0" -MONERO_COM_BUILD_NUMBER=61 +MONERO_COM_VERSION="1.7.1" +MONERO_COM_BUILD_NUMBER=62 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.10.0" -CAKEWALLET_BUILD_NUMBER=175 +CAKEWALLET_VERSION="4.10.1" +CAKEWALLET_BUILD_NUMBER=176 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh old mode 100644 new mode 100755 diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index aa3116418..ae5a90bc3 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.7.0" -MONERO_COM_BUILD_NUMBER=59 +MONERO_COM_VERSION="1.7.1" +MONERO_COM_BUILD_NUMBER=60 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.10.0" -CAKEWALLET_BUILD_NUMBER=189 +CAKEWALLET_VERSION="4.10.1" +CAKEWALLET_BUILD_NUMBER=190 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index d649d752d..37c2b3441 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.3.0" -CAKEWALLET_BUILD_NUMBER=36 +CAKEWALLET_VERSION="1.3.1" +CAKEWALLET_BUILD_NUMBER=37 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/tool/configure.dart b/tool/configure.dart index 534aeef57..90e275994 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -51,6 +51,7 @@ Future main(List args) async { Future generateBitcoin(bool hasImplementation) async { final outputFile = File(bitcoinOutputPath); const bitcoinCommonHeaders = """ +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -98,7 +99,7 @@ abstract class Bitcoin { int formatterStringDoubleToBitcoinAmount(String amount); String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate); - List getUnspents(Object wallet); + List getUnspents(Object wallet); void updateUnspents(Object wallet); WalletService createBitcoinWalletService(Box walletInfoSource, Box unspentCoinSource); WalletService createLitecoinWalletService(Box walletInfoSource, Box unspentCoinSource); @@ -577,7 +578,7 @@ Future generateBitcoinCash(bool hasImplementation) async { const bitcoinCashCommonHeaders = """ import 'dart:typed_data'; -import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_credentials.dart'; @@ -587,6 +588,7 @@ import 'package:hive/hive.dart'; """; const bitcoinCashCWHeaders = """ import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; """; const bitcoinCashCwPart = "part 'cw_bitcoin_cash.dart';"; const bitcoinCashContent = """ From 98a9edc656b98ea4373a7265c9a973780b903c87 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Tue, 17 Oct 2023 11:59:41 -0400 Subject: [PATCH 13/16] fix nano sending, update restore page wording, and other minor fixes (#1130) * fix nano sending, update restore page wording, and other minor fixes * fix bch address parsing * minor code enhancement [skip ci] * Register the main secure storage as the long lived instance of secure storage throughout the app session --------- Co-authored-by: Serhii Co-authored-by: OmarHatem --- .../lib/nano_wallet_creation_credentials.dart | 13 +++++---- lib/di.dart | 3 +- lib/entities/parse_address_from_domain.dart | 8 ++++- lib/main.dart | 5 ++-- lib/nano/cw_nano.dart | 18 ++++++++++-- .../wallet_restore_from_keys_form.dart | 9 +++++- lib/src/widgets/seed_widget.dart | 3 +- lib/view_model/send/send_view_model.dart | 29 ++++++++----------- res/values/strings_ar.arb | 6 ++-- res/values/strings_bg.arb | 6 ++-- res/values/strings_cs.arb | 6 ++-- res/values/strings_de.arb | 6 ++-- res/values/strings_en.arb | 6 ++-- res/values/strings_es.arb | 6 ++-- res/values/strings_fr.arb | 6 ++-- res/values/strings_ha.arb | 6 ++-- res/values/strings_hi.arb | 6 ++-- res/values/strings_hr.arb | 6 ++-- res/values/strings_id.arb | 6 ++-- res/values/strings_it.arb | 6 ++-- res/values/strings_ja.arb | 6 ++-- res/values/strings_ko.arb | 6 ++-- res/values/strings_my.arb | 6 ++-- res/values/strings_nl.arb | 6 ++-- res/values/strings_pl.arb | 6 ++-- res/values/strings_pt.arb | 6 ++-- res/values/strings_ru.arb | 6 ++-- res/values/strings_th.arb | 6 ++-- res/values/strings_tl.arb | 6 ++-- res/values/strings_tr.arb | 6 ++-- res/values/strings_uk.arb | 6 ++-- res/values/strings_ur.arb | 6 ++-- res/values/strings_yo.arb | 6 ++-- res/values/strings_zh.arb | 6 ++-- 34 files changed, 160 insertions(+), 84 deletions(-) diff --git a/cw_nano/lib/nano_wallet_creation_credentials.dart b/cw_nano/lib/nano_wallet_creation_credentials.dart index 84531e24a..3616fcf44 100644 --- a/cw_nano/lib/nano_wallet_creation_credentials.dart +++ b/cw_nano/lib/nano_wallet_creation_credentials.dart @@ -10,13 +10,11 @@ class NanoRestoreWalletFromSeedCredentials extends WalletCredentials { NanoRestoreWalletFromSeedCredentials({ required String name, required this.mnemonic, - int height = 0, String? password, DerivationType? derivationType, }) : super( name: name, password: password, - height: height, derivationType: derivationType, ); @@ -33,9 +31,12 @@ class NanoRestoreWalletFromKeysCredentials extends WalletCredentials { required String name, required String password, required this.seedKey, - this.derivationType, - }) : super(name: name, password: password); + DerivationType? derivationType, + }) : super( + name: name, + password: password, + derivationType: derivationType, + ); final String seedKey; - final DerivationType? derivationType; -} \ No newline at end of file +} diff --git a/lib/di.dart b/lib/di.dart index 1576c378b..cfe2cfdc5 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -248,6 +248,7 @@ Future setup({ required Box ordersSource, required Box unspentCoinsInfoSource, required Box anonpayInvoiceInfoSource, + required FlutterSecureStorage secureStorage, }) async { _walletInfoSource = walletInfoSource; _nodeSource = nodeSource; @@ -289,7 +290,7 @@ Future setup({ getIt.registerFactory>(() => _nodeSource); getIt.registerFactory>(() => _powNodeSource, instanceName: Node.boxName + "pow"); - getIt.registerSingleton(FlutterSecureStorage()); + getIt.registerSingleton(secureStorage); getIt.registerSingleton(AuthenticationStore()); getIt.registerSingleton(WalletListStore()); getIt.registerSingleton(NodeListStoreBase.instance); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 14bfe2f7f..9d274fbcc 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -46,7 +46,13 @@ class AddressResolver { } final match = RegExp(addressPattern).firstMatch(raw); - return match?.group(0)?.replaceAll(RegExp('[^0-9a-zA-Z]'), ''); + return match?.group(0)?.replaceAllMapped(RegExp('[^0-9a-zA-Z]|bitcoincash:|nano_'), (Match match) { + String group = match.group(0)!; + if (group.startsWith('bitcoincash:') || group.startsWith('nano_')) { + return group; + } + return ''; + }); } Future resolve(String text, String ticker) async { diff --git a/lib/main.dart b/lib/main.dart index 98ba1e195..d5b0e2f81 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -163,7 +163,7 @@ Future initializeAppConfigs() async { secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, initialMigrationVersion: 23); - } +} Future initialSetup( {required SharedPreferences sharedPreferences, @@ -202,7 +202,8 @@ Future initialSetup( transactionDescriptionBox: transactionDescriptions, ordersSource: ordersSource, anonpayInvoiceInfoSource: anonpayInvoiceInfo, - unspentCoinsInfoSource: unspentCoinsInfoSource); + unspentCoinsInfoSource: unspentCoinsInfoSource, + secureStorage: secureStorage); await bootstrap(navigatorKey); monero?.onStartup(); } diff --git a/lib/nano/cw_nano.dart b/lib/nano/cw_nano.dart index cd0f0ca8a..778b2584a 100644 --- a/lib/nano/cw_nano.dart +++ b/lib/nano/cw_nano.dart @@ -406,6 +406,13 @@ class CWNanoUtil extends NanoUtil { late String publicAddress; if (seedKey != null) { + if (seedKey.length == 64) { + try { + mnemonic = nanoUtil!.seedToMnemonic(seedKey); + } catch (e) { + print("not a valid 'nano' seed key"); + } + } if (derivationType == DerivationType.bip39) { publicAddress = await hdSeedToAddress(seedKey, 0); } else if (derivationType == DerivationType.nano) { @@ -429,7 +436,8 @@ class CWNanoUtil extends NanoUtil { AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress); if (accountInfo == null) { - accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0); + accountInfo = AccountInfoResponse( + frontier: "", balance: "0", representative: "", confirmationHeight: 0); } accountInfo.address = publicAddress; return accountInfo; @@ -449,7 +457,11 @@ class CWNanoUtil extends NanoUtil { if (seedKey?.length == 128) { return [DerivationType.bip39]; } else if (seedKey?.length == 64) { - return [DerivationType.nano]; + try { + mnemonic = nanoUtil!.seedToMnemonic(seedKey!); + } catch (e) { + print("not a valid 'nano' seed key"); + } } late String publicAddressStandard; @@ -503,7 +515,7 @@ class CWNanoUtil extends NanoUtil { // we don't know for sure: return [DerivationType.nano, DerivationType.bip39]; } catch (e) { - return [DerivationType.unknown]; + return [DerivationType.nano, DerivationType.bip39]; } } } diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index e42488183..2c0dfa53f 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -124,9 +124,16 @@ class WalletRestoreFromKeysFromState extends State { Widget _restoreFromKeysFormFields() { if (widget.displayPrivateKeyField) { + // the term "private key" isn't actually what we're accepting here, and it's confusing to + // users of the nano community, what this form actually accepts (when importing for nano) is a nano seed in it's hex form, referred to in code as a "seed key" + // so we should change the placeholder text to reflect this + // supporting actual nano private keys is possible, but it's super niche in the nano community / they're not really used + + bool nanoBased = widget.walletRestoreViewModel.type == WalletType.nano || + widget.walletRestoreViewModel.type == WalletType.banano; return AddressTextField( controller: privateKeyController, - placeholder: S.of(context).private_key, + placeholder: nanoBased ? S.of(context).seed_key : S.of(context).private_key, options: [AddressTextFieldOption.paste], buttonColor: Theme.of(context).hintColor, onPushPasteButton: (_) { diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart index 7015e0acf..bf9a85b32 100644 --- a/lib/src/widgets/seed_widget.dart +++ b/lib/src/widgets/seed_widget.dart @@ -1,3 +1,4 @@ +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'; @@ -75,7 +76,7 @@ class SeedWidgetState extends State { Positioned( top: 10, left: 0, - child: Text('Enter your seed', + child: Text(S.of(context).enter_seed_phrase, style: TextStyle( fontSize: 16.0, color: Theme.of(context).hintColor))), Padding( diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 719298675..be822aff3 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -185,15 +185,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get hasCoinControl => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.monero || - wallet.type == WalletType.bitcoinCash; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.monero || + wallet.type == WalletType.bitcoinCash; @computed bool get isElectrumWallet => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.bitcoinCash; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; @computed bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; @@ -281,7 +281,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor List conditionsList = []; for (var output in outputs) { - final show = checkThroughChecksToDisplayTOTP(output.extractedAddress); conditionsList.add(show); } @@ -350,31 +349,27 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor Object _credentials() { final priority = _settingsStore.priority[wallet.type]; - if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); + if (priority == null && wallet.type != WalletType.nano) { + throw Exception('Priority is null for wallet type: ${wallet.type}'); + } switch (wallet.type) { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); + return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority!); case WalletType.monero: return monero! - .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority); + .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority!); case WalletType.haven: return haven!.createHavenTransactionCreationCredentials( - outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title); + outputs: outputs, priority: priority!, assetType: selectedCryptoCurrency.title); case WalletType.ethereum: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - return ethereum!.createEthereumTransactionCredentials(outputs, - priority: priority, currency: selectedCryptoCurrency); + priority: priority!, currency: selectedCryptoCurrency); case WalletType.nano: return nano!.createNanoTransactionCredentials(outputs); default: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index a730a9dd8..fcb1b090c 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -717,5 +717,7 @@ "message": "ﺔﻟﺎﺳﺭ", "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", "totp_auth_url": "TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ", - "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ" -} + "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ", + "seed_key": "مفتاح البذور", + "enter_seed_phrase": "أدخل عبارة البذور الخاصة بك" +} \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 212f7f2a8..d85b30118 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -713,5 +713,7 @@ "message": "Съобщение", "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката." -} + "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката.", + "seed_key": "Ключ за семена", + "enter_seed_phrase": "Въведете вашата фраза за семена" +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 3042cadfe..998f0b915 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -713,5 +713,7 @@ "message": "Zpráva", "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", "totp_auth_url": "URL AUTH TOTP", - "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování." -} + "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování.", + "seed_key": "Klíč semen", + "enter_seed_phrase": "Zadejte svou frázi semen" +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9971aff0e..82575200f 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -721,5 +721,7 @@ "message": "Nachricht", "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", "totp_auth_url": "TOTP-Auth-URL", - "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat." -} + "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat.", + "seed_key": "Samenschlüssel", + "enter_seed_phrase": "Geben Sie Ihre Samenphrase ein" +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b981c0177..f30d224c0 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -722,5 +722,7 @@ "message": "Message", "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Kindly wait for the dApp to finish processing." -} + "awaitDAppProcessing": "Kindly wait for the dApp to finish processing.", + "seed_key": "Seed key", + "enter_seed_phrase": "Enter your seed phrase" +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 01bd3207e..049a83e40 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -721,5 +721,7 @@ "message": "Mensaje", "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", "totp_auth_url": "URL de autenticación TOTP", - "awaitDAppProcessing": "Espere a que la dApp termine de procesarse." -} + "awaitDAppProcessing": "Espere a que la dApp termine de procesarse.", + "seed_key": "Llave de semilla", + "enter_seed_phrase": "Ingrese su frase de semillas" +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f171cbb68..e6516fb87 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -721,5 +721,7 @@ "message": "Message", "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", "totp_auth_url": "URL D'AUTORISATION TOTP", - "awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement." -} + "awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement.", + "seed_key": "Clé de graines", + "enter_seed_phrase": "Entrez votre phrase de semence" +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 917cad8a9..7d8e8969c 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -699,5 +699,7 @@ "message": "Sako", "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki." -} + "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki.", + "seed_key": "Maɓallin iri", + "enter_seed_phrase": "Shigar da Sert Sentarku" +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 104c871bf..4a9e6b284 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -721,5 +721,7 @@ "message": "संदेश", "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", "totp_auth_url": "TOTP प्रामाणिक यूआरएल", - "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।" -} + "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।", + "seed_key": "बीज कुंजी", + "enter_seed_phrase": "अपना बीज वाक्यांश दर्ज करें" +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 93ab87319..17391a6d7 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -719,5 +719,7 @@ "message": "Poruka", "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu." -} + "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu.", + "seed_key": "Sjemenski ključ", + "enter_seed_phrase": "Unesite svoju sjemensku frazu" +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f6b20c213..5330ec96c 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -709,5 +709,7 @@ "message": "Pesan", "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", "totp_auth_url": "URL Otentikasi TOTP", - "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan." -} + "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan.", + "seed_key": "Kunci benih", + "enter_seed_phrase": "Masukkan frasa benih Anda" +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 946deb91c..d0c5a1a87 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -721,5 +721,7 @@ "message": "Messaggio", "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", "totp_auth_url": "URL DI AUT. TOTP", - "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione." -} + "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione.", + "seed_key": "Chiave di semi", + "enter_seed_phrase": "Inserisci la tua frase di semi" +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index f74f853d4..1d8012237 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -721,5 +721,7 @@ "message": "メッセージ", "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", "totp_auth_url": "TOTP認証URL", - "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。" -} + "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。", + "seed_key": "シードキー", + "enter_seed_phrase": "シードフレーズを入力してください" +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 5762822e1..df5d6f8ea 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -719,5 +719,7 @@ "message": "메시지", "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", "totp_auth_url": "TOTP 인증 URL", - "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요." -} + "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요.", + "seed_key": "시드 키", + "enter_seed_phrase": "시드 문구를 입력하십시오" +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 33ec70494..e2c4a55f9 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -719,5 +719,7 @@ "message": "မက်ဆေ့ချ်", "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။" -} + "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။", + "seed_key": "မျိုးစေ့သော့", + "enter_seed_phrase": "သင့်ရဲ့မျိုးစေ့စကားစုကိုရိုက်ထည့်ပါ" +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 767959855..fd95f5299 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -721,5 +721,7 @@ "message": "Bericht", "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", "totp_auth_url": "TOTP AUTH-URL", - "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken." -} + "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken.", + "seed_key": "Zaadsleutel", + "enter_seed_phrase": "Voer uw zaadzin in" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index f243a2343..561d0a731 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -721,5 +721,7 @@ "message": "Wiadomość", "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", "totp_auth_url": "Adres URL TOTP AUTH", - "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie." -} + "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie.", + "seed_key": "Klucz nasion", + "enter_seed_phrase": "Wprowadź swoją frazę nasienną" +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 49a446f92..21378e9ba 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -720,5 +720,7 @@ "message": "Mensagem", "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", "totp_auth_url": "URL de autenticação TOTP", - "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento." -} + "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento.", + "seed_key": "Chave de semente", + "enter_seed_phrase": "Digite sua frase de semente" +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 305ce68f2..e12b010dd 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -721,5 +721,7 @@ "message": "Сообщение", "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ", - "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку." -} + "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку.", + "seed_key": "Ключ семян", + "enter_seed_phrase": "Введите свою семенную фразу" +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 95ba4ef6f..7ac83fc00 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -719,5 +719,7 @@ "message": "ข้อความ", "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP", - "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น" -} + "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น", + "seed_key": "คีย์เมล็ดพันธุ์", + "enter_seed_phrase": "ป้อนวลีเมล็ดพันธุ์ของคุณ" +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 8072eee61..9981469a8 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -716,5 +716,7 @@ "message": "Mensahe", "do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp." -} + "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp.", + "seed_key": "Seed Key", + "enter_seed_phrase": "Ipasok ang iyong pariralang binhi" +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 596b6b000..6c838b036 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -719,5 +719,7 @@ "message": "İleti", "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", "totp_auth_url": "TOTP YETKİ URL'si", - "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin." -} + "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin.", + "seed_key": "Tohum", + "enter_seed_phrase": "Tohum ifadenizi girin" +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index bdc950faa..b7720fb99 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -721,5 +721,7 @@ "message": "повідомлення", "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку." -} + "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку.", + "seed_key": "Насіннєвий ключ", + "enter_seed_phrase": "Введіть свою насіннєву фразу" +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 73c528b69..f7cbdd5e4 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -713,5 +713,7 @@ "message": "ﻡﺎﻐﯿﭘ", "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ" -} + "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ", + "seed_key": "بیج کی کلید", + "enter_seed_phrase": "اپنے بیج کا جملہ درج کریں" +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index f885c2493..a5e3ca263 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -715,5 +715,7 @@ "message": "Ifiranṣẹ", "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ." -} + "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ.", + "seed_key": "Bọtini Ose", + "enter_seed_phrase": "Tẹ ọrọ-iru irugbin rẹ" +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 91642ec73..a2ea6b73f 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -720,5 +720,7 @@ "message": "信息", "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", "totp_auth_url": "TOTP 授权 URL", - "awaitDAppProcessing": "请等待 dApp 处理完成。" -} + "awaitDAppProcessing": "请等待 dApp 处理完成。", + "seed_key": "种子钥匙", + "enter_seed_phrase": "输入您的种子短语" +} \ No newline at end of file From 29bc234dab9986852c20336c81919bafec248a06 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 17 Oct 2023 21:55:54 +0300 Subject: [PATCH 14/16] fix Twiter APiI (#1132) Co-authored-by: Omar Hatem --- lib/entities/parse_address_from_domain.dart | 13 ++---- lib/twitter/twitter_api.dart | 46 +++++++++++++++------ lib/twitter/twitter_user.dart | 12 ++---- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 9d274fbcc..6e52a93c7 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -65,16 +65,11 @@ class AddressResolver { if (addressFromBio != null) { return ParsedAddress.fetchTwitterAddress(address: addressFromBio, name: text); } - final tweets = twitterUser.tweets; - if (tweets != null) { - var subString = StringBuffer(); - tweets.forEach((item) { - subString.writeln(item.text); - }); - final userTweetsText = subString.toString(); - final addressFromPinnedTweet = - extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker)); + final pinnedTweet = twitterUser.pinnedTweet?.text; + if (pinnedTweet != null) { + final addressFromPinnedTweet = + extractAddressByType(raw: pinnedTweet, type: CryptoCurrency.fromString(ticker)); if (addressFromPinnedTweet != null) { return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, name: text); } diff --git a/lib/twitter/twitter_api.dart b/lib/twitter/twitter_api.dart index 41f5df61d..24121c9c0 100644 --- a/lib/twitter/twitter_api.dart +++ b/lib/twitter/twitter_api.dart @@ -1,7 +1,8 @@ import 'dart:convert'; + +import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/twitter/twitter_user.dart'; import 'package:http/http.dart' as http; -import 'package:cake_wallet/.secrets.g.dart' as secrets; class TwitterApi { static const twitterBearerToken = secrets.twitterBearerToken; @@ -10,28 +11,49 @@ class TwitterApi { static const userPath = '/2/users/by/username/'; static Future lookupUserByName({required String userName}) async { - final queryParams = {'user.fields': 'description', 'expansions': 'pinned_tweet_id'}; - + final queryParams = { + 'user.fields': 'description', + 'expansions': 'pinned_tweet_id', + 'tweet.fields': 'note_tweet' + }; final headers = {'authorization': 'Bearer $twitterBearerToken'}; - final uri = Uri( - scheme: httpsScheme, - host: apiHost, - path: userPath + userName, - queryParameters: queryParams, - ); + scheme: httpsScheme, + host: apiHost, + path: userPath + userName, + queryParameters: queryParams); - var response = await http.get(uri, headers: headers); + final response = await http.get(uri, headers: headers).catchError((error) { + throw Exception('HTTP request failed: $error'); + }); if (response.statusCode != 200) { throw Exception('Unexpected http status: ${response.statusCode}'); } - final responseJSON = json.decode(response.body) as Map; + final Map responseJSON = jsonDecode(response.body) as Map; if (responseJSON['errors'] != null) { throw Exception(responseJSON['errors'][0]['detail']); } - return TwitterUser.fromJson(responseJSON); + return TwitterUser.fromJson(responseJSON, _getPinnedTweet(responseJSON)); + } + + static Tweet? _getPinnedTweet(Map responseJSON) { + final tweetId = responseJSON['data']['pinned_tweet_id'] as String?; + if (tweetId == null || responseJSON['includes'] == null) return null; + + final tweetIncludes = List.from(responseJSON['includes']['tweets'] as List); + final pinnedTweetData = tweetIncludes.firstWhere( + (tweet) => tweet['id'] == tweetId, + orElse: () => null, + ) as Map?; + + if (pinnedTweetData == null) return null; + + final pinnedTweetText = + (pinnedTweetData['note_tweet']?['text'] ?? pinnedTweetData['text']) as String; + + return Tweet(id: tweetId, text: pinnedTweetText); } } diff --git a/lib/twitter/twitter_user.dart b/lib/twitter/twitter_user.dart index ac373fd62..c0eb5431c 100644 --- a/lib/twitter/twitter_user.dart +++ b/lib/twitter/twitter_user.dart @@ -4,25 +4,21 @@ class TwitterUser { required this.username, required this.name, required this.description, - this.tweets}); + this.pinnedTweet}); final String id; final String username; final String name; final String description; - final List? tweets; + final Tweet? pinnedTweet; - factory TwitterUser.fromJson(Map json) { + factory TwitterUser.fromJson(Map json, [Tweet? pinnedTweet]) { return TwitterUser( id: json['data']['id'] as String, username: json['data']['username'] as String, name: json['data']['name'] as String, description: json['data']['description'] as String? ?? '', - tweets: json['includes'] != null - ? List.from(json['includes']['tweets'] as List) - .map((e) => Tweet.fromJson(e as Map)) - .toList() - : null, + pinnedTweet: pinnedTweet, ); } } From 2a3b5644d71ab5d895a1e92643f7fda78e2a2406 Mon Sep 17 00:00:00 2001 From: hundehausen <25672277+hundehausen@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:26:05 +0200 Subject: [PATCH 15/16] use working port for seths node (#1133) * use working port for seths node * use ssl for Seth's node --- assets/node_list.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/node_list.yml b/assets/node_list.yml index 2cbd7f780..bc7a9dc4a 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -5,7 +5,8 @@ uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081 is_default: false - - uri: node.sethforprivacy.com:18089 + uri: node.sethforprivacy.com:443 + useSSL: true is_default: false - uri: nodes.hashvault.pro:18081 From 8889f09509045531c3fb9f4c5aed172c1930f69d Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 19 Oct 2023 01:08:29 +0300 Subject: [PATCH 16/16] Accessibility fixes (#1128) --- .../dashboard/widgets/address_page.dart | 29 ++-- .../screens/dashboard/widgets/header_row.dart | 38 +++--- .../present_receive_option_picker.dart | 126 ++++++++---------- .../exchange/widgets/exchange_card.dart | 98 +++++++------- lib/src/widgets/alert_close_button.dart | 19 ++- 5 files changed, 163 insertions(+), 147 deletions(-) diff --git a/lib/src/screens/dashboard/widgets/address_page.dart b/lib/src/screens/dashboard/widgets/address_page.dart index 84100464c..d584ce95b 100644 --- a/lib/src/screens/dashboard/widgets/address_page.dart +++ b/lib/src/screens/dashboard/widgets/address_page.dart @@ -99,19 +99,22 @@ class AddressPage extends BasePage { Widget? trailing(BuildContext context) { return Material( color: Colors.transparent, - child: IconButton( - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - iconSize: 25, - onPressed: () { - ShareUtil.share( - text: addressListViewModel.uri.toString(), - context: context, - ); - }, - icon: Icon(Icons.share, size: 20, color: pageIconColor(context)), + child: Semantics( + label: S.of(context).share, + child: IconButton( + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + iconSize: 25, + onPressed: () { + ShareUtil.share( + text: addressListViewModel.uri.toString(), + context: context, + ); + }, + icon: Icon(Icons.share, size: 20, color: pageIconColor(context)), + ), ), ); } diff --git a/lib/src/screens/dashboard/widgets/header_row.dart b/lib/src/screens/dashboard/widgets/header_row.dart index 79b7b3fe6..2093a238f 100644 --- a/lib/src/screens/dashboard/widgets/header_row.dart +++ b/lib/src/screens/dashboard/widgets/header_row.dart @@ -31,21 +31,29 @@ class HeaderRow extends StatelessWidget { fontWeight: FontWeight.w500, color: Theme.of(context).extension()!.pageTitleTextColor), ), - GestureDetector( - onTap: () { - showPopUp( - context: context, - builder: (context) => - FilterWidget(dashboardViewModel: dashboardViewModel) - ); - }, - child: Container( - height: 36, - width: 36, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).extension()!.buttonColor), - child: filterIcon, + Semantics( + container: true, + child: GestureDetector( + onTap: () { + showPopUp( + context: context, + builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel), + ); + }, + child: Semantics( + label: 'Transaction Filter', + button: true, + enabled: true, + child: Container( + height: 36, + width: 36, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).extension()!.buttonColor, + ), + child: filterIcon, + ), + ), ), ) ], diff --git a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart index aae42049b..33bceeb5c 100644 --- a/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart +++ b/lib/src/screens/dashboard/widgets/present_receive_option_picker.dart @@ -1,5 +1,5 @@ +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/screens/ionia/widgets/rounded_checkbox.dart'; import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/typography.dart'; @@ -71,77 +71,69 @@ class PresentReceiveOptionPicker extends StatelessWidget { builder: (BuildContext popUpContext) => Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.transparent, - body: AlertBackground( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Spacer(), - Container( - margin: EdgeInsets.symmetric(horizontal: 24), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Theme.of(context).colorScheme.background, - ), - child: Padding( - padding: const EdgeInsets.only(top: 24, bottom: 24), - child: (ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - itemCount: receiveOptionViewModel.options.length, - itemBuilder: (_, index) { - final option = receiveOptionViewModel.options[index]; - return InkWell( - onTap: () { - Navigator.pop(popUpContext); + body: Stack( + alignment: AlignmentDirectional.center, + children:[ AlertBackground( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Spacer(), + Container( + margin: EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Theme.of(context).colorScheme.background, + ), + child: Padding( + padding: const EdgeInsets.only(top: 24, bottom: 24), + child: (ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: receiveOptionViewModel.options.length, + itemBuilder: (_, index) { + final option = receiveOptionViewModel.options[index]; + return InkWell( + onTap: () { + Navigator.pop(popUpContext); - receiveOptionViewModel.selectReceiveOption(option); - }, - child: Padding( - padding: const EdgeInsets.only(left: 24, right: 24), - child: Observer(builder: (_) { - final value = receiveOptionViewModel.selectedReceiveOption; + receiveOptionViewModel.selectReceiveOption(option); + }, + child: Padding( + padding: const EdgeInsets.only(left: 24, right: 24), + child: Observer(builder: (_) { + final value = receiveOptionViewModel.selectedReceiveOption; - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(option.toString(), - textAlign: TextAlign.left, - style: textSmall( - color: Theme.of(context).extension()!.titleColor, - ).copyWith( - fontWeight: - value == option ? FontWeight.w800 : FontWeight.w500, - )), - RoundedCheckbox( - value: value == option, - ) - ], - ); - }), - ), - ); - }, - separatorBuilder: (_, index) => SizedBox(height: 30), - )), - ), - ), - Spacer(), - Container( - margin: EdgeInsets.only(bottom: 40), - child: InkWell( - onTap: () => Navigator.pop(popUpContext), - child: CircleAvatar( - child: Icon( - Icons.close, - color: Palette.darkBlueCraiola, - ), - backgroundColor: Colors.white, + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(option.toString(), + textAlign: TextAlign.left, + style: textSmall( + color: Theme.of(context).extension()!.titleColor, + ).copyWith( + fontWeight: + value == option ? FontWeight.w800 : FontWeight.w500, + )), + RoundedCheckbox( + value: value == option, + ) + ], + ); + }), + ), + ); + }, + separatorBuilder: (_, index) => SizedBox(height: 30), + )), ), ), - ) - ], + Spacer() + ], + ), ), + AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40) + ], ), ), context: context, diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 9c4707529..b55e96e85 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -416,37 +416,40 @@ class ExchangeCardState extends State { width: 34, height: 34, padding: EdgeInsets.only(top: 0), - child: InkWell( - onTap: () async { - final contact = - await Navigator.of(context) - .pushNamed( - Routes.pickerAddressBook, - arguments: widget.initialCurrency, - ); + child: Semantics( + label: S.of(context).address_book, + child: InkWell( + onTap: () async { + final contact = + await Navigator.of(context) + .pushNamed( + Routes.pickerAddressBook, + arguments: widget.initialCurrency, + ); - if (contact is ContactBase && - contact.address != null) { - setState(() => - addressController.text = - contact.address); - widget.onPushAddressBookButton - ?.call(context); - } - }, - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: widget - .addressButtonsColor, - borderRadius: - BorderRadius.all( - Radius.circular( - 6))), - child: Image.asset( - 'assets/images/open_book.png', - color: Theme.of(context).extension()!.textFieldButtonIconColor, - )), + if (contact is ContactBase && + contact.address != null) { + setState(() => + addressController.text = + contact.address); + widget.onPushAddressBookButton + ?.call(context); + } + }, + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: widget + .addressButtonsColor, + borderRadius: + BorderRadius.all( + Radius.circular( + 6))), + child: Image.asset( + 'assets/images/open_book.png', + color: Theme.of(context).extension()!.textFieldButtonIconColor, + )), + ), )), ), Padding( @@ -455,22 +458,25 @@ class ExchangeCardState extends State { width: 34, height: 34, padding: EdgeInsets.only(top: 0), - child: InkWell( - onTap: () { - Clipboard.setData(ClipboardData( - text: addressController - .text)); - showBar( - context, - S - .of(context) - .copied_to_clipboard); - }, - child: Container( - padding: EdgeInsets.fromLTRB( - 8, 8, 0, 8), - color: Colors.transparent, - child: copyImage), + child: Semantics( + label: S.of(context).copy_address, + child: InkWell( + onTap: () { + Clipboard.setData(ClipboardData( + text: addressController + .text)); + showBar( + context, + S + .of(context) + .copied_to_clipboard); + }, + child: Container( + padding: EdgeInsets.fromLTRB( + 8, 8, 0, 8), + color: Colors.transparent, + child: copyImage), + ), ))) ]))) ])), diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 925756933..e3ff037a9 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -1,8 +1,10 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/palette.dart'; import 'package:flutter/material.dart'; class AlertCloseButton extends StatelessWidget { AlertCloseButton({this.image, this.bottom, this.onTap}); + final VoidCallback? onTap; final Image? image; @@ -19,12 +21,17 @@ class AlertCloseButton extends StatelessWidget { bottom: bottom ?? 60, child: GestureDetector( onTap: onTap ?? () => Navigator.of(context).pop(), - child: Container( - height: 42, - width: 42, - decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), - child: Center( - child: image ?? closeButton, + child: Semantics( + label: S.of(context).close, + button: true, + enabled: true, + child: Container( + height: 42, + width: 42, + decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), + child: Center( + child: image ?? closeButton, + ), ), ), ),