From fa25a4b2240bdd32170dee3944da88d0ce37d81b Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Sat, 25 Nov 2023 01:31:49 +0200 Subject: [PATCH 01/18] Generic fixes (#1191) * check on the url validity before using it * handle tx nullability * add localization script to configure --- configure_cake_wallet_android.sh | 1 + cw_bitcoin/lib/electrum_wallet.dart | 2 +- lib/src/screens/wallet_connect/wc_connections_listing_view.dart | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet_android.sh index b8aa433de..6998555d5 100755 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet_android.sh @@ -2,6 +2,7 @@ cd scripts/android source ./app_env.sh cakewallet ./app_config.sh cd ../.. && flutter pub get +flutter packages pub run tool/generate_localization.dart cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 05486aa20..903fa14cc 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -480,7 +480,7 @@ abstract class ElectrumWalletBase unspentCoins = unspent.expand((e) => e).toList(); unspentCoins.forEach((coin) async { final tx = await fetchTransactionInfo(hash: coin.hash, height: 0); - coin.isChange = tx!.direction == TransactionDirection.outgoing; + coin.isChange = tx?.direction == TransactionDirection.outgoing; }); if (unspentCoinsInfo.isEmpty) { diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart index 359d96b26..eda2a748f 100644 --- a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -31,6 +31,8 @@ class WalletConnectConnectionsView extends StatelessWidget { final actualLinkList = launchUri.query.split("uri="); + if (actualLinkList.length <= 1) return; + final query = actualLinkList[1]; final uri = Uri.decodeComponent(query); From eeb9976d090d70691e0f7ef4d92e94c5e99d99cd Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Fri, 24 Nov 2023 18:35:00 -0500 Subject: [PATCH 02/18] update wallet restore page to be less confusing for nano users (#1186) * update wallet restore page to be less confusing for nano users * update translations * only include private key if seed phrase is null * restore fixes * fix default derivation being wrong on restore * fix default nano derivationType & qr code parameters * update language on nano restore (hex form) * support hex seed from qr restore --- cw_core/lib/wallet_base.dart | 2 + cw_nano/lib/nano_wallet.dart | 44 +++++++++++-------- .../wallet_restore_from_keys_form.dart | 2 +- lib/view_model/restore/restore_wallet.dart | 2 +- .../restore/wallet_restore_from_qr_code.dart | 8 ++++ lib/view_model/wallet_keys_view_model.dart | 24 +++++----- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 5 ++- res/values/strings_cs.arb | 5 ++- res/values/strings_de.arb | 5 ++- res/values/strings_en.arb | 5 ++- res/values/strings_es.arb | 5 ++- res/values/strings_fr.arb | 5 ++- res/values/strings_ha.arb | 5 ++- res/values/strings_hi.arb | 5 ++- res/values/strings_hr.arb | 5 ++- res/values/strings_id.arb | 5 ++- res/values/strings_it.arb | 5 ++- res/values/strings_ja.arb | 5 ++- res/values/strings_ko.arb | 5 ++- res/values/strings_my.arb | 5 ++- res/values/strings_nl.arb | 5 ++- res/values/strings_pl.arb | 5 ++- res/values/strings_pt.arb | 5 ++- res/values/strings_ru.arb | 5 ++- res/values/strings_th.arb | 5 ++- res/values/strings_tl.arb | 5 ++- res/values/strings_tr.arb | 5 ++- res/values/strings_uk.arb | 5 ++- res/values/strings_ur.arb | 3 +- res/values/strings_yo.arb | 5 ++- res/values/strings_zh.arb | 5 ++- 32 files changed, 125 insertions(+), 81 deletions(-) diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index d15ea42cd..09b423c14 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -44,6 +44,8 @@ abstract class WalletBase null; + String? get hexSeed => null; + Object get keys; WalletAddresses get walletAddresses; diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index 44d564fc6..795634849 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -58,13 +58,13 @@ abstract class NanoWalletBase } } - final String _mnemonic; + String _mnemonic; final String _password; - final DerivationType _derivationType; + DerivationType _derivationType; String? _privateKey; String? _publicAddress; - String? _seedKey; + String? _hexSeed; String? _representativeAddress; Timer? _receiveTimer; @@ -85,22 +85,26 @@ abstract class NanoWalletBase // initialize the different forms of private / public key we'll need: Future init() async { + if (_derivationType == DerivationType.unknown) { + _derivationType = DerivationType.nano; + } final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; - // our "mnemonic" is actually a seedkey: + // our "mnemonic" is actually a hex form seed: if (!_mnemonic.contains(' ')) { - _seedKey = _mnemonic; + _hexSeed = _mnemonic; + _mnemonic = ""; } - if (_seedKey == null) { + if (_hexSeed == null) { if (_derivationType == DerivationType.nano) { - _seedKey = bip39.mnemonicToEntropy(_mnemonic).toUpperCase(); + _hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase(); } else { - _seedKey = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' ')); + _hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' ')); } } - _privateKey = await NanoUtil.uniSeedToPrivate(_seedKey!, 0, type); - _publicAddress = await NanoUtil.uniSeedToAddress(_seedKey!, 0, type); + _privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type); + _publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type); this.walletInfo.address = _publicAddress!; await walletAddresses.init(); @@ -275,11 +279,11 @@ abstract class NanoWalletBase @override NanoWalletKeys get keys { - return NanoWalletKeys(seedKey: _seedKey!); + return NanoWalletKeys(seedKey: _hexSeed!); } @override - String? get privateKey => _seedKey!; + String? get privateKey => _privateKey!; @override Future rescan({required int height}) async { @@ -297,7 +301,9 @@ abstract class NanoWalletBase } @override - String get seed => _mnemonic; + String? get seed => _mnemonic.isNotEmpty ? _mnemonic : null; + + String get hexSeed => _hexSeed!; String get representative => _representativeAddress ?? ""; @@ -330,7 +336,7 @@ abstract class NanoWalletBase Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); String toJSON() => json.encode({ - 'seedKey': _seedKey, + 'seedKey': _hexSeed, 'mnemonic': _mnemonic, 'currentBalance': balance[currency]?.currentBalance.toString() ?? "0", 'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0", @@ -351,9 +357,9 @@ abstract class NanoWalletBase formattedCurrentBalance: data['currentBalance'] as String? ?? "0", formattedReceivableBalance: data['receivableBalance'] as String? ?? "0"); - DerivationType derivationType = DerivationType.bip39; - if (data['derivationType'] == "DerivationType.nano") { - derivationType = DerivationType.nano; + DerivationType derivationType = DerivationType.nano; + if (data['derivationType'] == "DerivationType.bip39") { + derivationType = DerivationType.bip39; } walletInfo.derivationType = derivationType; @@ -390,9 +396,9 @@ abstract class NanoWalletBase Future regenerateAddress() async { final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; _privateKey = - await NanoUtil.uniSeedToPrivate(_seedKey!, this.walletAddresses.account!.id, type); + await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type); _publicAddress = - await NanoUtil.uniSeedToAddress(_seedKey!, this.walletAddresses.account!.id, type); + await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type); this.walletInfo.address = _publicAddress!; this.walletAddresses.address = _publicAddress!; 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 2c0dfa53f..588fd7187 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -133,7 +133,7 @@ class WalletRestoreFromKeysFromState extends State { widget.walletRestoreViewModel.type == WalletType.banano; return AddressTextField( controller: privateKeyController, - placeholder: nanoBased ? S.of(context).seed_key : S.of(context).private_key, + placeholder: nanoBased ? S.of(context).seed_hex_form : S.of(context).private_key, options: [AddressTextFieldOption.paste], buttonColor: Theme.of(context).hintColor, onPushPasteButton: (_) { diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart index 264b3d421..d46c48092 100644 --- a/lib/view_model/restore/restore_wallet.dart +++ b/lib/view_model/restore/restore_wallet.dart @@ -45,7 +45,7 @@ class RestoredWallet { factory RestoredWallet.fromSeed(Map json) { final height = json['height'] as String?; final mnemonic_seed = json['mnemonic_seed'] as String?; - final seed = json['seed'] as String?; + final seed = json['seed'] as String? ?? json['hexSeed'] as String?; return RestoredWallet( restoreMode: json['mode'] as WalletRestoreMode, type: json['type'] as WalletType, 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 077675d1f..145db74d7 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -151,6 +151,14 @@ class WalletRestoreFromQRCode { return WalletRestoreMode.keys; } + if ((type == WalletType.nano || type == WalletType.banano) && credentials.containsKey('hexSeed')) { + final hexSeed = credentials['hexSeed'] as String; + if (hexSeed.isEmpty) { + throw Exception('Unexpected restore mode: hexSeed'); + } + return WalletRestoreMode.seed; + } + throw Exception('Unexpected restore mode: restore params are invalid'); } } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 257bae1fd..d9de9473d 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -107,19 +107,18 @@ abstract class WalletKeysViewModelBase with Store { ]); } - if (_appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano) { + bool nanoBased = + _appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano; - // we don't necessarily have the seed phrase for nano / banano: - if (_appStore.wallet!.seed != null) { - items.addAll([ - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), - ]); - } - - // we always have the hex version of the seed: + if (nanoBased) { + // we always have the hex version of the seed and private key: items.addAll([ + if (_appStore.wallet!.seed != null) + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + if (_appStore.wallet!.hexSeed != null) + StandartListItem(title: S.current.seed_hex_form, value: _appStore.wallet!.hexSeed!), if (_appStore.wallet!.privateKey != null) - StandartListItem(title: S.current.spend_key_private, value: _appStore.wallet!.privateKey!), + StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!), ]); } } @@ -172,7 +171,10 @@ abstract class WalletKeysViewModelBase with Store { final restoreHeightResult = await restoreHeight; return { if (_appStore.wallet!.seed != null) 'seed': _appStore.wallet!.seed!, - if (_appStore.wallet!.privateKey != null) 'private_key': _appStore.wallet!.privateKey!, + if (_appStore.wallet!.seed == null && _appStore.wallet!.hexSeed != null) + 'hexSeed': _appStore.wallet!.hexSeed!, + if (_appStore.wallet!.seed == null && _appStore.wallet!.privateKey != null) + 'private_key': _appStore.wallet!.privateKey!, if (restoreHeightResult != null) ...{'height': restoreHeightResult} }; } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index fb883eb13..8065a5bd1 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -738,5 +738,6 @@ "unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ", "unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ", "unspent_change": "يتغير", + "seed_hex_form": "بذور المحفظة (شكل عرافة)", "tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 1b3ded5d6..ab45efba3 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -734,5 +734,6 @@ "unavailable_balance": "Неналично салдо", "unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.", "unspent_change": "Промяна", - "tor_connection": "Tor връзка" -} + "tor_connection": "Tor връзка", + "seed_hex_form": "Семена от портфейл (шестнадесетична форма)" +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 4f0e89bd3..88792ec15 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -734,5 +734,6 @@ "unavailable_balance": "Nedostupný zůstatek", "unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.", "unspent_change": "Změna", - "tor_connection": "Připojení Tor" -} + "tor_connection": "Připojení Tor", + "seed_hex_form": "Semeno peněženky (hex formulář)" +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 360e621db..7b204f20c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Nicht verfügbares Guthaben", "unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.", "unspent_change": "Wechselgeld", - "tor_connection": "Tor-Verbindung" -} + "tor_connection": "Tor-Verbindung", + "seed_hex_form": "Brieftaschensamen (Sechskantform)" +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 0f059759d..bbcbdb6c0 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -743,5 +743,6 @@ "unavailable_balance": "Unavailable balance", "unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.", "unspent_change": "Change", - "tor_connection": "Tor connection" -} + "tor_connection": "Tor connection", + "seed_hex_form": "Wallet seed (hex form)" +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 8a4f180f6..e47f3e744 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Saldo no disponible", "unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.", "unspent_change": "Cambiar", - "tor_connection": "conexión tor" -} + "tor_connection": "conexión tor", + "seed_hex_form": "Semilla de billetera (forma hexadecimal)" +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 158aaadb4..f87ed9809 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -744,5 +744,6 @@ "camera_permission_is_required": "L'autorisation d'accès à la caméra est requise.\nVeuillez l'activer depuis les paramètres de l'application.", "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer", "unspent_change": "Changement", - "tor_connection": "Connexion Tor" -} + "tor_connection": "Connexion Tor", + "seed_hex_form": "Graine du portefeuille (forme hexagonale)" +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 8b6f38d05..2dce6f80d 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -720,5 +720,6 @@ "unavailable_balance": "Ma'aunin da ba ya samuwa", "unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.", "unspent_change": "Canza", - "tor_connection": "Tor haɗin gwiwa" -} + "tor_connection": "Tor haɗin gwiwa", + "seed_hex_form": "Gany Sero (form form)" +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9afc420a2..66a4c8e47 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -742,5 +742,6 @@ "unavailable_balance": "अनुपलब्ध शेष", "unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।", "unspent_change": "परिवर्तन", - "tor_connection": "टोर कनेक्शन" -} + "tor_connection": "टोर कनेक्शन", + "seed_hex_form": "वॉलेट सीड (हेक्स फॉर्म)" +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index fd4efa97e..4e84d3ed4 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -740,5 +740,6 @@ "unavailable_balance": "Nedostupno stanje", "unavailable_balance_description": "Nedostupno stanje: Ovaj ukupni iznos uključuje sredstva koja su zaključana u transakcijama na čekanju i ona koja ste aktivno zamrznuli u postavkama kontrole novčića. Zaključani saldi postat će dostupni kada se dovrše njihove transakcije, dok zamrznuti saldi ostaju nedostupni za transakcije sve dok ih ne odlučite odmrznuti.", "unspent_change": "Promijeniti", - "tor_connection": "Tor veza" -} + "tor_connection": "Tor veza", + "seed_hex_form": "Sjeme novčanika (šesterokutni oblik)" +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 4de382641..82e9bca56 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -730,5 +730,6 @@ "unavailable_balance": "Saldo tidak tersedia", "unavailable_balance_description": "Saldo Tidak Tersedia: Total ini termasuk dana yang terkunci dalam transaksi yang tertunda dan dana yang telah Anda bekukan secara aktif di pengaturan kontrol koin Anda. Saldo yang terkunci akan tersedia setelah transaksi masing-masing selesai, sedangkan saldo yang dibekukan tetap tidak dapat diakses untuk transaksi sampai Anda memutuskan untuk mencairkannya.", "unspent_change": "Mengubah", - "tor_connection": "koneksi Tor" -} + "tor_connection": "koneksi Tor", + "seed_hex_form": "Biji dompet (bentuk hex)" +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 4fc35b3e3..0005fb5e7 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Saldo non disponibile", "unavailable_balance_description": "Saldo non disponibile: questo totale include i fondi bloccati nelle transazioni in sospeso e quelli che hai congelato attivamente nelle impostazioni di controllo delle monete. I saldi bloccati diventeranno disponibili una volta completate le rispettive transazioni, mentre i saldi congelati rimarranno inaccessibili per le transazioni finché non deciderai di sbloccarli.", "unspent_change": "Modifica", - "tor_connection": "Connessione Tor" -} + "tor_connection": "Connessione Tor", + "seed_hex_form": "Seme di portafoglio (forma esadecimale)" +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index a4fe8b46f..d57eaef5d 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -742,5 +742,6 @@ "unavailable_balance": "利用できない残高", "unavailable_balance_description": "利用不可能な残高: この合計には、保留中のトランザクションにロックされている資金と、コイン管理設定でアクティブに凍結した資金が含まれます。ロックされた残高は、それぞれの取引が完了すると利用可能になりますが、凍結された残高は、凍結を解除するまで取引にアクセスできません。", "unspent_change": "変化", - "tor_connection": "Tor接続" -} + "tor_connection": "Tor接続", + "seed_hex_form": "ウォレットシード(ヘックスフォーム)" +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 2b638e9d5..775918659 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -740,5 +740,6 @@ "unavailable_balance": "사용할 수 없는 잔액", "unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.", "unspent_change": "변화", - "tor_connection": "토르 연결" -} + "tor_connection": "토르 연결", + "seed_hex_form": "지갑 씨앗 (16 진 양식)" +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 19ee9c051..4aacb0bd3 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -740,5 +740,6 @@ "unavailable_balance": "လက်ကျန်ငွေ မရရှိနိုင်ပါ။", "unavailable_balance_description": "မရရှိနိုင်သော လက်ကျန်ငွေ- ဤစုစုပေါင်းတွင် ဆိုင်းငံ့ထားသော ငွေပေးငွေယူများတွင် သော့ခတ်ထားသော ငွေကြေးများနှင့် သင်၏ coin ထိန်းချုပ်မှုဆက်တင်များတွင် သင် တက်ကြွစွာ အေးခဲထားသော ငွေများ ပါဝင်သည်။ သော့ခတ်ထားသော လက်ကျန်ငွေများကို ၎င်းတို့၏ သက်ဆိုင်ရာ ငွေပေးငွေယူများ ပြီးမြောက်သည်နှင့် တပြိုင်နက် ရရှိနိုင်မည်ဖြစ်ပြီး၊ အေးခဲထားသော လက်ကျန်များကို ၎င်းတို့အား ပြန်ဖြုတ်ရန် သင်ဆုံးဖြတ်သည်အထိ ငွေပေးငွေယူများအတွက် ဆက်လက်၍မရနိုင်ပါ။", "unspent_change": "ပေြာင်းလဲခြင်း", - "tor_connection": "Tor ချိတ်ဆက်မှု" -} + "tor_connection": "Tor ချိတ်ဆက်မှု", + "seed_hex_form": "ပိုက်ဆံအိတ်မျိုးစေ့ (Hex Form)" +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 838fec0c1..cf779afab 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Onbeschikbaar saldo", "unavailable_balance_description": "Niet-beschikbaar saldo: Dit totaal omvat het geld dat is vergrendeld in lopende transacties en het geld dat u actief hebt bevroren in uw muntcontrole-instellingen. Vergrendelde saldi komen beschikbaar zodra de betreffende transacties zijn voltooid, terwijl bevroren saldi ontoegankelijk blijven voor transacties totdat u besluit ze weer vrij te geven.", "unspent_change": "Wijziging", - "tor_connection": "Tor-verbinding" -} + "tor_connection": "Tor-verbinding", + "seed_hex_form": "Portemonnee zaad (hexvorm)" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 309f20613..04c420db4 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Niedostępne saldo", "unavailable_balance_description": "Niedostępne saldo: Suma ta obejmuje środki zablokowane w transakcjach oczekujących oraz te, które aktywnie zamroziłeś w ustawieniach kontroli monet. Zablokowane salda staną się dostępne po zakończeniu odpowiednich transakcji, natomiast zamrożone salda pozostaną niedostępne dla transakcji, dopóki nie zdecydujesz się ich odblokować.", "unspent_change": "Zmiana", - "tor_connection": "Połączenie Torem" -} + "tor_connection": "Połączenie Torem", + "seed_hex_form": "Nasiona portfela (forma sześciokątna)" +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 8feeb732c..a461141f3 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -741,5 +741,6 @@ "unavailable_balance": "Saldo indisponível", "unavailable_balance_description": "Saldo Indisponível: Este total inclui fundos bloqueados em transações pendentes e aqueles que você congelou ativamente nas configurações de controle de moedas. Os saldos bloqueados ficarão disponíveis assim que suas respectivas transações forem concluídas, enquanto os saldos congelados permanecerão inacessíveis para transações até que você decida descongelá-los.", "unspent_change": "Mudar", - "tor_connection": "Conexão Tor" -} + "tor_connection": "Conexão Tor", + "seed_hex_form": "Semente de carteira (forma hexadecimal)" +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 98eddcae7..8d0cc647a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Недоступный баланс", "unavailable_balance_description": "Недоступный баланс: в эту сумму входят средства, заблокированные в ожидающих транзакциях, и средства, которые вы активно заморозили в настройках управления монетами. Заблокированные балансы станут доступны после завершения соответствующих транзакций, а замороженные балансы останутся недоступными для транзакций, пока вы не решите их разморозить.", "unspent_change": "Изменять", - "tor_connection": "Тор соединение" -} + "tor_connection": "Тор соединение", + "seed_hex_form": "Семя кошелька (шестнадцатеричная форма)" +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index c9cfca47c..151c65cd3 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -740,5 +740,6 @@ "unavailable_balance": "ยอดคงเหลือไม่พร้อมใช้งาน", "unavailable_balance_description": "ยอดคงเหลือที่ไม่พร้อมใช้งาน: ยอดรวมนี้รวมถึงเงินทุนที่ถูกล็อคในการทำธุรกรรมที่รอดำเนินการและที่คุณได้แช่แข็งไว้ในการตั้งค่าการควบคุมเหรียญของคุณ ยอดคงเหลือที่ถูกล็อคจะพร้อมใช้งานเมื่อธุรกรรมที่เกี่ยวข้องเสร็จสมบูรณ์ ในขณะที่ยอดคงเหลือที่แช่แข็งจะไม่สามารถเข้าถึงได้สำหรับธุรกรรมจนกว่าคุณจะตัดสินใจยกเลิกการแช่แข็ง", "unspent_change": "เปลี่ยน", - "tor_connection": "การเชื่อมต่อทอร์" -} + "tor_connection": "การเชื่อมต่อทอร์", + "seed_hex_form": "เมล็ดกระเป๋าเงิน (รูปแบบฐานสิบหก)" +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d68dbc656..258eb6f12 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -737,5 +737,6 @@ "unavailable_balance": "Hindi available na balanse", "unavailable_balance_description": "Hindi Available na Balanse: Kasama sa kabuuang ito ang mga pondong naka-lock sa mga nakabinbing transaksyon at ang mga aktibong na-freeze mo sa iyong mga setting ng kontrol ng coin. Magiging available ang mga naka-lock na balanse kapag nakumpleto na ang kani-kanilang mga transaksyon, habang ang mga nakapirming balanse ay nananatiling hindi naa-access para sa mga transaksyon hanggang sa magpasya kang i-unfreeze ang mga ito.", "unspent_change": "Baguhin", - "tor_connection": "Koneksyon ng Tor" -} + "tor_connection": "Koneksyon ng Tor", + "seed_hex_form": "Wallet seed (hex form)" +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index c0fc58feb..33362b83c 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -740,5 +740,6 @@ "unavailable_balance": "Kullanılamayan bakiye", "unavailable_balance_description": "Kullanılamayan Bakiye: Bu toplam, bekleyen işlemlerde kilitlenen fonları ve jeton kontrol ayarlarınızda aktif olarak dondurduğunuz fonları içerir. Kilitli bakiyeler, ilgili işlemleri tamamlandıktan sonra kullanılabilir hale gelir; dondurulmuş bakiyeler ise siz onları dondurmaya karar verene kadar işlemler için erişilemez durumda kalır.", "unspent_change": "Değiştirmek", - "tor_connection": "Tor bağlantısı" -} + "tor_connection": "Tor bağlantısı", + "seed_hex_form": "Cüzdan tohumu (onaltılık form)" +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 0ad5fa97a..7b79ac6e4 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -742,5 +742,6 @@ "unavailable_balance": "Недоступний баланс", "unavailable_balance_description": "Недоступний баланс: ця сума включає кошти, заблоковані в незавершених транзакціях, і ті, які ви активно заморозили в налаштуваннях контролю монет. Заблоковані баланси стануть доступними після завершення відповідних транзакцій, тоді як заморожені баланси залишаються недоступними для транзакцій, доки ви не вирішите їх розморозити.", "unspent_change": "Зміна", - "tor_connection": "Підключення Tor" -} + "tor_connection": "Підключення Tor", + "seed_hex_form": "Насіння гаманця (шістнадцяткова форма)" +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index e26c3b4d4..c8362ab33 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -734,5 +734,6 @@ "unavailable_balance": "ﺲﻨﻠﯿﺑ ﺏﺎﯿﺘﺳﺩ ﺮﯿﻏ", "unavailable_balance_description": "۔ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﮧﻠﺼﯿﻓ ﺎﮐ ﮯﻧﺮﮐ ﺪﻤﺠﻨﻣ ﻥﺍ ﮟﯿﮩﻧﺍ ﭖﺁ ﮧﮐ ﮏﺗ ﺐﺟ ﮟﯿﮨ ﮯﺘﮨﺭ ﯽﺋﺎﺳﺭ ﻞﺑﺎﻗﺎﻧ ﮏﺗ ﺖﻗﻭ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﻦﯾﺩ ﻦﯿﻟ ﺲﻨﻠﯿﺑ ﺪﻤﺠﻨﻣ ﮧﮐ ﺐﺟ ،ﮯﮔ ﮟﯿﺋﺎﺟ ﻮﮨ ﺏﺎﯿﺘﺳﺩ ﺲﻨﻠﯿﺑ ﻞﻔﻘﻣ ﺪﻌﺑ ﮯﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﻦﯾﺩ ﻦﯿﻟ ﮧﻘﻠﻌﺘﻣ ﮯﮐ ﻥﺍ ۔ﮯﮨ ﺎﮭﮐﺭ ﺮ", "unspent_change": "تبدیل کریں", + "seed_hex_form": "پرس بیج (ہیکس فارم)", "tor_connection": "ﻦﺸﮑﻨﮐ ﺭﻮﭨ" -} +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index d512f5e8f..7b9b075d7 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -736,5 +736,6 @@ "unavailable_balance": "Iwontunwonsi ti ko si", "unavailable_balance_description": "Iwontunws.funfun ti ko si: Lapapọ yii pẹlu awọn owo ti o wa ni titiipa ni awọn iṣowo isunmọ ati awọn ti o ti didi ni itara ninu awọn eto iṣakoso owo rẹ. Awọn iwọntunwọnsi titiipa yoo wa ni kete ti awọn iṣowo oniwun wọn ba ti pari, lakoko ti awọn iwọntunwọnsi tio tutunini ko ni iraye si fun awọn iṣowo titi iwọ o fi pinnu lati mu wọn kuro.", "unspent_change": "Yipada", - "tor_connection": "Tor asopọ" -} + "tor_connection": "Tor asopọ", + "seed_hex_form": "Irú Opamọwọ apamọwọ (HOX)" +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 974008594..e95bc077b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -741,5 +741,6 @@ "unavailable_balance": "不可用余额", "unavailable_balance_description": "不可用余额:此总额包括锁定在待处理交易中的资金以及您在硬币控制设置中主动冻结的资金。一旦各自的交易完成,锁定的余额将变得可用,而冻结的余额在您决定解冻之前仍然无法进行交易。", "unspent_change": "改变", - "tor_connection": "Tor连接" -} + "tor_connection": "Tor连接", + "seed_hex_form": "钱包种子(十六进制形式)" +} \ No newline at end of file From 00c97c74b81d3d0af272379b9971aa8402773a69 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 25 Nov 2023 01:37:12 +0100 Subject: [PATCH 03/18] Cw 462 monero polyseed restore support (#1109) * CW-462 Mark Places to integrate Polyseed * CW-462 Add Restore from Polyseed * CW-462 Add Restore from Polyseed * CW-462 Add new Monero date-height pairs * CW-462 Little Cleanup * CW-462 Ups I missed that Debug line :/ * CW-462 Fix Polyseed not showing in Wallet-Seed/Keys Page * CW-462 Prepare for Wallet creation * CW-462 Fix merge conflict * CW-462 Fix generating monero.dart * CW-462 Add Polyseed generation * CW-462 Add Polyseed Languages to SeedLanguagePicker * CW-462 Apply requested changes * CW-462 Minor bug fixes in restore screen * Update wallet_restore_from_seed_form.dart * CW-462 Minor Bugfix * CW-462 Fix Restore from QR for Polyseeds * CW-462 Fix null-check-operator exception for Polyseeds and minor inconveniences * CW-462 Fix minor inconveniences * Fix conflicts and review comments and wrap unspent issue with try and catch with reporting failure --------- Co-authored-by: OmarHatem --- cw_core/lib/get_height_by_date.dart | 36 +++- cw_core/pubspec.lock | 4 +- cw_core/pubspec.yaml | 2 +- cw_monero/example/pubspec.lock | 15 +- cw_monero/ios/Classes/monero_api.cpp | 34 ++++ cw_monero/lib/api/signatures.dart | 3 + cw_monero/lib/api/types.dart | 3 + cw_monero/lib/api/wallet_manager.dart | 77 ++++++++- cw_monero/lib/monero_wallet.dart | 91 ++++++---- cw_monero/lib/monero_wallet_service.dart | 61 ++++++- cw_monero/pubspec.lock | 15 +- cw_monero/pubspec.yaml | 3 + lib/entities/preferences_key.dart | 1 + lib/entities/seed_type.dart | 36 ++++ lib/monero/cw_monero.dart | 8 +- lib/router.dart | 16 +- lib/routes.dart | 1 - .../advanced_privacy_settings_page.dart | 32 ++-- .../screens/new_wallet/new_wallet_page.dart | 43 +++-- .../wallet_restore_from_seed_form.dart | 161 ++++++++++++------ .../screens/restore/wallet_restore_page.dart | 84 ++++----- .../seed_language/seed_language_page.dart | 95 ----------- .../widgets/seed_language_picker.dart | 82 --------- lib/src/widgets/seed_language_picker.dart | 93 ++++++++++ lib/src/widgets/seed_language_selector.dart | 24 +-- lib/store/settings_store.dart | 76 ++++++--- .../advanced_privacy_settings_view_model.dart | 9 + .../restore/wallet_restore_from_qr_code.dart | 12 +- lib/view_model/wallet_new_vm.dart | 9 +- pubspec_base.yaml | 3 + res/values/strings_ar.arb | 8 +- res/values/strings_bg.arb | 8 +- res/values/strings_cs.arb | 8 +- res/values/strings_de.arb | 14 +- res/values/strings_en.arb | 8 +- res/values/strings_es.arb | 8 +- res/values/strings_fr.arb | 9 +- res/values/strings_ha.arb | 8 +- res/values/strings_hi.arb | 8 +- res/values/strings_hr.arb | 8 +- res/values/strings_id.arb | 8 +- res/values/strings_it.arb | 8 +- res/values/strings_ja.arb | 8 +- res/values/strings_ko.arb | 8 +- res/values/strings_my.arb | 8 +- res/values/strings_nl.arb | 8 +- res/values/strings_pl.arb | 8 +- res/values/strings_pt.arb | 8 +- res/values/strings_ru.arb | 8 +- res/values/strings_th.arb | 8 +- res/values/strings_tl.arb | 7 +- res/values/strings_tr.arb | 8 +- res/values/strings_uk.arb | 8 +- res/values/strings_ur.arb | 8 +- res/values/strings_yo.arb | 8 +- res/values/strings_zh.arb | 8 +- tool/configure.dart | 7 +- 57 files changed, 906 insertions(+), 444 deletions(-) create mode 100644 lib/entities/seed_type.dart delete mode 100644 lib/src/screens/seed_language/seed_language_page.dart delete mode 100644 lib/src/screens/seed_language/widgets/seed_language_picker.dart create mode 100644 lib/src/widgets/seed_language_picker.dart diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index 819990e0a..a680e6c25 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -84,7 +84,41 @@ final dates = { "2020-8": 2153983, "2020-9": 2176466, "2020-10": 2198453, - "2020-11": 2220000 + "2020-11": 2220000, + "2020-12": 2242240, + "2021-1": 2264584, + "2021-2": 2286892, + "2021-3": 2307079, + "2021-4": 2329385, + "2021-5": 2351004, + "2021-6": 2373306, + "2021-7": 2394882, + "2021-8": 2417162, + "2021-9": 2439490, + "2021-10": 2461020, + "2021-11": 2483377, + "2021-12": 2504932, + "2022-1": 2527316, + "2022-2": 2549605, + "2022-3": 2569711, + "2022-4": 2591995, + "2022-5": 2613603, + "2022-6": 2635840, + "2022-7": 2657395, + "2022-8": 2679705, + "2022-9": 2701991, + "2022-10": 2723607, + "2022-11": 2745899, + "2022-12": 2767427, + "2023-1": 2789763, + "2023-2": 2811996, + "2023-3": 2832118, + "2023-4": 2854365, + "2023-5": 2875972, + "2023-6": 2898234, + "2023-7": 2919771, + "2023-8": 2942045, + "2023-9": 2964280 }; int getMoneroHeigthByDate({required DateTime date}) { diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 058f33ba4..f351759ed 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -271,10 +271,10 @@ packages: dependency: "direct dev" description: name: hive_generator - sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "2.0.1" http: dependency: "direct main" description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 3763a7318..533b578ad 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -31,7 +31,7 @@ dev_dependencies: build_runner: ^2.1.11 build_resolvers: ^2.0.9 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index 661341e96..3dd2b5b61 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -301,10 +301,19 @@ packages: dependency: transitive description: name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.3" + polyseed: + dependency: transitive + description: + path: "." + ref: HEAD + resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7" + url: "https://github.com/cake-tech/polyseed_dart.git" + source: git + version: "0.0.1" process: dependency: transitive description: @@ -416,5 +425,5 @@ packages: source: hosted version: "0.2.0+3" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.0.6 <4.0.0" flutter: ">=3.7.0" diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index a0712255a..7ad873647 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -374,6 +374,35 @@ extern "C" return true; } + bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey( + std::string(path), + std::string(password), + std::string(language), + _networkType, + (uint64_t)restoreHeight, + std::string(spendKey)); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + // Cache Raw to support Polyseed + wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); + + change_current_wallet(wallet); + return true; + } + bool load_wallet(char *path, char *password, int32_t nettype) { nice(19); @@ -438,6 +467,11 @@ extern "C" const char *seed() { + std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed"); + if (!_rawSeed.empty()) + { + return strdup(_rawSeed.c_str()); + } return strdup(get_current_wallet()->seed().c_str()); } diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 9be828df0..17099db91 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -14,6 +14,9 @@ typedef restore_wallet_from_seed = Int8 Function( typedef restore_wallet_from_keys = Int8 Function(Pointer, Pointer, Pointer, Pointer, Pointer, Pointer, Int32, Int64, Pointer); +typedef restore_wallet_from_spend_key = Int8 Function(Pointer, Pointer, Pointer, + Pointer, Pointer, Int32, Int64, Pointer); + typedef is_wallet_exist = Int8 Function(Pointer); typedef load_wallet = Int8 Function(Pointer, Pointer, Int8); diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 4c0c980dc..1d3904870 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -14,6 +14,9 @@ typedef RestoreWalletFromSeed = int Function( typedef RestoreWalletFromKeys = int Function(Pointer, Pointer, Pointer, Pointer, Pointer, Pointer, int, int, Pointer); +typedef RestoreWalletFromSpendKey = int Function(Pointer, Pointer, Pointer, + Pointer, Pointer, int, int, Pointer); + typedef IsWalletExist = int Function(Pointer); typedef LoadWallet = int Function(Pointer, Pointer, int); diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 093d7e63b..260b420c5 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -5,7 +5,6 @@ import 'package:cw_monero/api/convert_utf8_to_string.dart'; import 'package:cw_monero/api/signatures.dart'; import 'package:cw_monero/api/types.dart'; import 'package:cw_monero/api/monero_api.dart'; -import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; @@ -25,6 +24,11 @@ final restoreWalletFromKeysNative = moneroApi 'restore_wallet_from_keys') .asFunction(); +final restoreWalletFromSpendKeyNative = moneroApi + .lookup>( + 'restore_wallet_from_spend_key') + .asFunction(); + final isWalletExistNative = moneroApi .lookup>('is_wallet_exist') .asFunction(); @@ -141,6 +145,42 @@ void restoreWalletFromKeysSync( } } +void restoreWalletFromSpendKeySync( + {required String path, + required String password, + required String seed, + required String language, + required String spendKey, + int nettype = 0, + int restoreHeight = 0}) { + final pathPointer = path.toNativeUtf8(); + final passwordPointer = password.toNativeUtf8(); + final seedPointer = seed.toNativeUtf8(); + final languagePointer = language.toNativeUtf8(); + final spendKeyPointer = spendKey.toNativeUtf8(); + final errorMessagePointer = ''.toNativeUtf8(); + final isWalletRestored = restoreWalletFromSpendKeyNative( + pathPointer, + passwordPointer, + seedPointer, + languagePointer, + spendKeyPointer, + nettype, + restoreHeight, + errorMessagePointer) != + 0; + + calloc.free(pathPointer); + calloc.free(passwordPointer); + calloc.free(languagePointer); + calloc.free(spendKeyPointer); + + if (!isWalletRestored) { + throw WalletRestoreFromKeysException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } +} + void loadWallet({ required String path, required String password, @@ -194,6 +234,23 @@ void _restoreFromKeys(Map args) { spendKey: spendKey); } +void _restoreFromSpendKey(Map args) { + final path = args['path'] as String; + final password = args['password'] as String; + final seed = args['seed'] as String; + final language = args['language'] as String; + final spendKey = args['spendKey'] as String; + final restoreHeight = args['restoreHeight'] as int; + + restoreWalletFromSpendKeySync( + path: path, + password: password, + seed: seed, + language: language, + restoreHeight: restoreHeight, + spendKey: spendKey); +} + Future _openWallet(Map args) async => loadWallet(path: args['path'] as String, password: args['password'] as String); @@ -251,4 +308,22 @@ Future restoreFromKeys( 'restoreHeight': restoreHeight }); +Future restoreFromSpendKey( + {required String path, + required String password, + required String seed, + required String language, + required String spendKey, + int nettype = 0, + int restoreHeight = 0}) async => + compute, void>(_restoreFromSpendKey, { + 'path': path, + 'password': password, + 'seed': seed, + 'language': language, + 'spendKey': spendKey, + 'nettype': nettype, + 'restoreHeight': restoreHeight + }); + Future isWalletExist({required String path}) => compute(_isWalletExist, path); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index d7e66ecec..71c7e3967 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -29,6 +29,7 @@ import 'package:cw_monero/monero_transaction_info.dart'; import 'package:cw_monero/monero_unspent.dart'; import 'package:cw_monero/monero_wallet_addresses.dart'; import 'package:cw_monero/pending_monero_transaction.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -78,6 +79,8 @@ abstract class MoneroWalletBase Box unspentCoinsInfo; + void Function(FlutterErrorDetails)? _onError; + @override late MoneroWalletAddresses walletAddresses; @@ -388,46 +391,59 @@ abstract class MoneroWalletBase } Future updateUnspent() async { - refreshCoins(walletAddresses.account!.id); + try { + refreshCoins(walletAddresses.account!.id); - unspentCoins.clear(); + unspentCoins.clear(); - final coinCount = countOfCoins(); - for (var i = 0; i < coinCount; i++) { - final coin = getCoin(i); - if (coin.spent == 0) { - final unspent = MoneroUnspent.fromCoinsInfoRow(coin); - unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1; - unspentCoins.add(unspent); - } - } - - if (unspentCoinsInfo.isEmpty) { - unspentCoins.forEach((coin) => _addCoinInfo(coin)); - return; - } - - if (unspentCoins.isNotEmpty) { - unspentCoins.forEach((coin) { - final coinInfoList = unspentCoinsInfo.values.where((element) => - element.walletId.contains(id) && - element.accountIndex == walletAddresses.account!.id && - element.keyImage!.contains(coin.keyImage!)); - - if (coinInfoList.isNotEmpty) { - final coinInfo = coinInfoList.first; - - coin.isFrozen = coinInfo.isFrozen; - coin.isSending = coinInfo.isSending; - coin.note = coinInfo.note; - } else { - _addCoinInfo(coin); + final coinCount = countOfCoins(); + for (var i = 0; i < coinCount; i++) { + final coin = getCoin(i); + if (coin.spent == 0) { + final unspent = MoneroUnspent.fromCoinsInfoRow(coin); + if (unspent.hash.isNotEmpty) { + unspent.isChange = transaction_history + .getTransaction(unspent.hash) + .direction == 1; + } + unspentCoins.add(unspent); } - }); - } + } - await _refreshUnspentCoinsInfo(); - _askForUpdateBalance(); + if (unspentCoinsInfo.isEmpty) { + unspentCoins.forEach((coin) => _addCoinInfo(coin)); + return; + } + + if (unspentCoins.isNotEmpty) { + unspentCoins.forEach((coin) { + final coinInfoList = unspentCoinsInfo.values.where((element) => + element.walletId.contains(id) && + element.accountIndex == walletAddresses.account!.id && + element.keyImage!.contains(coin.keyImage!)); + + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; + + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + } else { + _addCoinInfo(coin); + } + }); + } + + await _refreshUnspentCoinsInfo(); + _askForUpdateBalance(); + } catch (e, s) { + print(e.toString()); + _onError?.call(FlutterErrorDetails( + exception: e, + stack: s, + library: this.runtimeType.toString(), + )); + } } Future _addCoinInfo(MoneroUnspent coin) async { @@ -632,4 +648,7 @@ abstract class MoneroWalletBase walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0); } } + + @override + void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; } diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 8cb3aa948..8e9eb6eac 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -7,16 +7,20 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/monero_wallet.dart'; import 'package:hive/hive.dart'; +import 'package:polyseed/polyseed.dart'; +import 'package:polyseed/src/utils/key_utils.dart'; class MoneroNewWalletCredentials extends WalletCredentials { - MoneroNewWalletCredentials({required String name, required this.language, String? password}) + MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password}) : super(name: name, password: password); final String language; + final bool isPolyseed; } class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials { @@ -68,10 +72,17 @@ class MoneroWalletService extends WalletService< Future create(MoneroNewWalletCredentials credentials) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); + + if (credentials.isPolyseed) { + final polyseed = Polyseed.create(); + final lang = PolyseedLang.getByEnglishName(credentials.language); + + return _restoreFromPolyseed( + path, credentials.password!, polyseed, credentials.walletInfo!, lang); + } + await monero_wallet_manager.createWallet( - path: path, - password: credentials.password!, - language: credentials.language); + path: path, password: credentials.password!, language: credentials.language); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); @@ -215,6 +226,12 @@ class MoneroWalletService extends WalletService< @override Future restoreFromSeed( MoneroRestoreWalletFromSeedCredentials credentials) async { + + // Restore from Polyseed + if (Polyseed.isValidSeed(credentials.mnemonic)) { + return restoreFromPolyseed(credentials); + } + try { final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.restoreFromSeed( @@ -234,6 +251,42 @@ class MoneroWalletService extends WalletService< } } + Future restoreFromPolyseed(MoneroRestoreWalletFromSeedCredentials credentials) async { + try { + final path = await pathForWallet(name: credentials.name, type: getType()); + final polyseedCoin = PolyseedCoin.POLYSEED_MONERO; + final lang = PolyseedLang.getByPhrase(credentials.mnemonic); + final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin); + + return _restoreFromPolyseed(path, credentials.password!, polyseed, credentials.walletInfo!, lang); + } catch (e) { + // TODO: Implement Exception for wallet list service. + print('MoneroWalletsManager Error: $e'); + rethrow; + } + } + + Future _restoreFromPolyseed(String path, String password, Polyseed polyseed, + WalletInfo walletInfo, PolyseedLang lang, + {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO}) async { + final height = getMoneroHeigthByDate( + date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); + final spendKey = keyToHexString(polyseed.generateKey(coin, 32)); + + await monero_wallet_manager.restoreFromSpendKey( + path: path, + password: password, + seed: polyseed.encode(lang, coin), + language: lang.nameEnglish, + restoreHeight: height, + spendKey: spendKey); + final wallet = MoneroWallet( + walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.init(); + + return wallet; + } + Future repairOldAndroidWallet(String name) async { try { if (!Platform.isAndroid) { diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index dc0f9aeed..46ebe8028 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -478,10 +478,19 @@ packages: dependency: transitive description: name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.3" + polyseed: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7" + url: "https://github.com/cake-tech/polyseed_dart.git" + source: git + version: "0.0.1" pool: dependency: transitive description: @@ -689,5 +698,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.0.6 <4.0.0" flutter: ">=3.7.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index cf2993ef3..55e9b354c 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -19,6 +19,9 @@ dependencies: flutter_mobx: ^2.0.6+1 intl: ^0.18.0 encrypt: ^5.0.1 + polyseed: + git: + url: https://github.com/cake-tech/polyseed_dart.git cw_core: path: ../cw_core diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 85df63b69..bdd6c24a7 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -64,6 +64,7 @@ class PreferencesKey { static const exchangeProvidersSelection = 'exchange-providers-selection'; static const autoGenerateSubaddressStatusKey = 'auto_generate_subaddress_status'; + static const moneroSeedType = 'monero_seed_type'; static const clearnetDonationLink = 'clearnet_donation_link'; static const onionDonationLink = 'onion_donation_link'; static const lastSeenAppVersion = 'last_seen_app_version'; diff --git a/lib/entities/seed_type.dart b/lib/entities/seed_type.dart new file mode 100644 index 000000000..0a1a191c6 --- /dev/null +++ b/lib/entities/seed_type.dart @@ -0,0 +1,36 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cw_core/enumerable_item.dart'; + +class SeedType extends EnumerableItem with Serializable { + const SeedType({required String title, required int raw}) : super(title: title, raw: raw); + + static const all = [SeedType.legacy, SeedType.polyseed]; + + static const defaultSeedType = legacy; + + static const legacy = SeedType(raw: 0, title: 'Legacy (25 words)'); + static const polyseed = SeedType(raw: 1, title: 'Polyseed (16 words)'); + + static SeedType deserialize({required int raw}) { + switch (raw) { + case 0: + return legacy; + case 1: + return polyseed; + default: + throw Exception('Unexpected token: $raw for SeedType deserialize'); + } + } + + @override + String toString() { + switch (this) { + case SeedType.legacy: + return S.current.seedtype_legacy; + case SeedType.polyseed: + return S.current.seedtype_polyseed; + default: + return ''; + } + } +} diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 9ae248ca0..947d274a8 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -166,6 +166,10 @@ class CWMonero extends Monero { @override List getMoneroWordList(String language) { + if (language.startsWith("POLYSEED_")) { + final lang = language.replaceAll("POLYSEED_", ""); + return PolyseedLang.getByEnglishName(lang).words; + } switch (language.toLowerCase()) { case 'english': return EnglishMnemonics.words; @@ -223,8 +227,10 @@ class CWMonero extends Monero { WalletCredentials createMoneroNewWalletCredentials({ required String name, required String language, + required bool isPolyseed, String? password}) => - MoneroNewWalletCredentials(name: name, password: password, language: language); + MoneroNewWalletCredentials( + name: name, password: password, language: language, isPolyseed: isPolyseed); @override Map getKeys(Object wallet) { diff --git a/lib/router.dart b/lib/router.dart index fb21ba32c..313751ab3 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -56,6 +56,7 @@ import 'package:cake_wallet/src/screens/support_other_links/support_other_links_ import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; @@ -87,7 +88,6 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; import 'package:cake_wallet/src/screens/send/send_page.dart'; import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; -import 'package:cake_wallet/src/screens/seed_language/seed_language_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart'; import 'package:cake_wallet/src/screens/contact/contact_list_page.dart'; @@ -147,8 +147,9 @@ Route createRoute(RouteSettings settings) { case Routes.newWallet: final type = settings.arguments as WalletType; final walletNewVM = getIt.get(param1: type); + final settingsStore = getIt.get(); - return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM)); + return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM, settingsStore)); case Routes.setupPin: Function(PinCodeState, String)? callback; @@ -264,17 +265,6 @@ Route createRoute(RouteSettings settings) { case Routes.changeRep: return CupertinoPageRoute(builder: (_) => getIt.get()); - case Routes.seedLanguage: - final args = settings.arguments as List; - final type = args.first as WalletType; - final redirectRoute = args[1] as String; - - return CupertinoPageRoute(builder: (_) { - return SeedLanguage( - onConfirm: (context, lang) => - Navigator.of(context).popAndPushNamed(redirectRoute, arguments: [type, lang])); - }); - case Routes.walletList: return MaterialPageRoute( fullscreenDialog: true, builder: (_) => getIt.get()); diff --git a/lib/routes.dart b/lib/routes.dart index 4511d48e1..60801297a 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -18,7 +18,6 @@ class Routes { static const disclaimer = '/disclaimer'; static const readDisclaimer = '/read_disclaimer'; static const changeRep = '/change_representative'; - static const seedLanguage = '/seed_language'; static const walletList = '/view_model.wallet_list'; static const auth = '/auth'; static const newNode = '/new_node_list'; diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index 66a28042d..4b61e7889 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; @@ -97,16 +98,27 @@ class _AdvancedPrivacySettingsBodyState extends State( - title: S.current.seed_phrase_length, - items: SeedPhraseLength.values, - selectedItem: widget.privacySettingsViewModel.seedPhraseLength, - onItemSelected: (SeedPhraseLength length) { - widget.privacySettingsViewModel.setSeedPhraseLength(length); - }, - ); - }), + Observer(builder: (_) { + return SettingsPickerCell( + title: S.current.seed_phrase_length, + items: SeedPhraseLength.values, + selectedItem: widget.privacySettingsViewModel.seedPhraseLength, + onItemSelected: (SeedPhraseLength length) { + widget.privacySettingsViewModel.setSeedPhraseLength(length); + }, + ); + }), + if (widget.privacySettingsViewModel.hasSeedTypeOption) + Observer(builder: (_) { + return SettingsChoicesCell( + ChoicesListItem( + title: S.current.seedtype, + items: SeedType.all, + selectedItem: widget.privacySettingsViewModel.seedType, + onItemSelected: widget.privacySettingsViewModel.setSeedType, + ), + ); + }), ], ), bottomSectionPadding: EdgeInsets.all(24), diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 5577fcd88..d86d51e93 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -14,17 +14,20 @@ import 'package:cake_wallet/src/widgets/seed_language_selector.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart'; +import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/store/settings_store.dart'; class NewWalletPage extends BasePage { - NewWalletPage(this._walletNewVM); + NewWalletPage(this._walletNewVM, this._settingsStore); final WalletNewVM _walletNewVM; + final SettingsStore _settingsStore; final walletNameImage = Image.asset('assets/images/wallet_name.png'); @@ -35,14 +38,15 @@ class NewWalletPage extends BasePage { @override Widget body(BuildContext context) => WalletNameForm( - _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage); + _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _settingsStore); } class WalletNameForm extends StatefulWidget { - WalletNameForm(this._walletNewVM, this.walletImage); + WalletNameForm(this._walletNewVM, this.walletImage, this._settingsStore); final WalletNewVM _walletNewVM; final Image walletImage; + final SettingsStore _settingsStore; @override _WalletNameFormState createState() => _WalletNameFormState(_walletNewVM); @@ -125,15 +129,20 @@ class _WalletNameFormState extends State { hintStyle: TextStyle( fontSize: 18.0, fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.hintTextColor), + color: + Theme.of(context).extension()!.hintTextColor), hintText: S.of(context).wallet_name, focusedBorder: UnderlineInputBorder( borderSide: BorderSide( - color: Theme.of(context).extension()!.underlineColor, + color: Theme.of(context) + .extension()! + .underlineColor, width: 1.0)), enabledBorder: UnderlineInputBorder( borderSide: BorderSide( - color: Theme.of(context).extension()!.underlineColor, + color: Theme.of(context) + .extension()! + .underlineColor, width: 1.0), ), suffixIcon: Semantics( @@ -160,7 +169,9 @@ class _WalletNameFormState extends State { height: 34, child: Image.asset( 'assets/images/refresh_icon.png', - color: Theme.of(context).extension()!.textFieldButtonIconColor, + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor, ), ), ), @@ -184,10 +195,15 @@ class _WalletNameFormState extends State { color: Theme.of(context).extension()!.titleColor), ), ), - Padding( - padding: EdgeInsets.only(top: 24), - child: SeedLanguageSelector( - key: _languageSelectorKey, initialSelected: defaultSeedLanguage), + Observer( + builder: (BuildContext build) => Padding( + padding: EdgeInsets.only(top: 24), + child: SeedLanguageSelector( + key: _languageSelectorKey, + initialSelected: defaultSeedLanguage, + seedType: widget._settingsStore.moneroSeedType, + ), + ), ) ] ], @@ -237,9 +253,10 @@ class _WalletNameFormState extends State { buttonAction: () => Navigator.of(context).pop()); }); } else { + final isPolyseed = widget._settingsStore.moneroSeedType == SeedType.polyseed; _walletNewVM.create( options: _walletNewVM.hasLanguageSelector - ? _languageSelectorKey.currentState!.selected + ? [_languageSelectorKey.currentState!.selected, isPolyseed] : null); } } diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index d10277ced..ac8413212 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -1,17 +1,17 @@ +import 'package:cake_wallet/core/wallet_name_validator.dart'; import 'package:cake_wallet/entities/generate_name.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart'; -import 'package:cake_wallet/src/widgets/seed_widget.dart'; +import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/wallet_name_validator.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; +import 'package:cake_wallet/src/widgets/seed_widget.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:polyseed/polyseed.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { WalletRestoreFromSeedForm( @@ -34,8 +34,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget { final void Function(String)? onLanguageChange; @override - WalletRestoreFromSeedFormState createState() => - WalletRestoreFromSeedFormState('English'); + WalletRestoreFromSeedFormState createState() => WalletRestoreFromSeedFormState('English'); } class WalletRestoreFromSeedFormState extends State { @@ -44,77 +43,116 @@ class WalletRestoreFromSeedFormState extends State { blockchainHeightKey = GlobalKey(), formKey = GlobalKey(), languageController = TextEditingController(), - nameTextEditingController = TextEditingController(); + nameTextEditingController = TextEditingController(), + seedTypeController = TextEditingController(); final GlobalKey seedWidgetStateKey; final GlobalKey blockchainHeightKey; final TextEditingController languageController; final TextEditingController nameTextEditingController; + final TextEditingController seedTypeController; final GlobalKey formKey; String language; + bool isPolyseed = false; @override void initState() { _setLanguageLabel(language); + _setSeedType(SeedType.defaultSeedType); super.initState(); } + void onSeedChange(String seed) { + if (widget.type == WalletType.monero && Polyseed.isValidSeed(seed)) { + final lang = PolyseedLang.getByPhrase(seed); + + _changeSeedType(SeedType.polyseed); + _changeLanguage(lang.nameEnglish); + } + widget.onSeedChange?.call(seed); + } + @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only(left: 24, right: 24), child: Column(children: [ Form( - key: formKey, - child: Stack( - alignment: Alignment.centerRight, - children: [ - BaseTextFormField( - controller: nameTextEditingController, - hintText: S.of(context).wallet_name, - suffixIcon: IconButton( - onPressed: () async { - final rName = await generateName(); - FocusManager.instance.primaryFocus?.unfocus(); + key: formKey, + child: Stack( + alignment: Alignment.centerRight, + children: [ + BaseTextFormField( + controller: nameTextEditingController, + hintText: S.of(context).wallet_name, + suffixIcon: IconButton( + onPressed: () async { + final rName = await generateName(); + FocusManager.instance.primaryFocus?.unfocus(); - setState(() { - nameTextEditingController.text = rName; - nameTextEditingController.selection = - TextSelection.fromPosition(TextPosition( - offset: nameTextEditingController.text.length)); - }); - }, - icon: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - color: Theme.of(context).hintColor, - ), - width: 34, - height: 34, - child: Image.asset( - 'assets/images/refresh_icon.png', - color: Theme.of(context).extension()!.textFieldButtonIconColor, + setState(() { + nameTextEditingController.text = rName; + nameTextEditingController.selection = TextSelection.fromPosition( + TextPosition(offset: nameTextEditingController.text.length)); + }); + }, + icon: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + color: Theme.of(context).hintColor, + ), + width: 34, + height: 34, + child: Image.asset( + 'assets/images/refresh_icon.png', + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor, + ), + ), ), + validator: WalletNameValidator(), ), - ), - validator: WalletNameValidator(), - ), - ], - )), + ], + )), Container(height: 20), SeedWidget( key: seedWidgetStateKey, language: language, type: widget.type, - onSeedChange: widget.onSeedChange), + onSeedChange: onSeedChange), + if (widget.type == WalletType.monero) + GestureDetector( + onTap: () async { + await showPopUp( + context: context, + builder: (_) => Picker( + items: SeedType.all, + selectedAtIndex: isPolyseed ? 1 : 0, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: _changeSeedType, + isSeparated: false, + )); + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: seedTypeController, + enableInteractiveSelection: false, + readOnly: true)))), if (widget.displayLanguageSelector) GestureDetector( onTap: () async { await showPopUp( context: context, builder: (_) => SeedLanguagePicker( - selected: language, onItemSelected: _changeLanguage)); + selected: language, + onItemSelected: _changeLanguage, + seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy, + )); }, child: Container( color: Colors.transparent, @@ -124,24 +162,35 @@ class WalletRestoreFromSeedFormState extends State { controller: languageController, enableInteractiveSelection: false, readOnly: true)))), - if (widget.displayBlockHeightSelector) + if (!isPolyseed && widget.displayBlockHeightSelector) BlockchainHeightWidget( focusNode: widget.blockHeightFocusNode, key: blockchainHeightKey, onHeightOrDateEntered: widget.onHeightOrDateEntered, - hasDatePicker: widget.type == WalletType.monero) + hasDatePicker: widget.type == WalletType.monero), ])); } void _changeLanguage(String language) { + final setLang = isPolyseed ? "POLYSEED_$language" : language; setState(() { - this.language = language; - seedWidgetStateKey.currentState!.changeSeedLanguage(language); - _setLanguageLabel(language); - widget.onLanguageChange?.call(language); + this.language = setLang; + seedWidgetStateKey.currentState!.changeSeedLanguage(setLang); + _setLanguageLabel(setLang); + widget.onLanguageChange?.call(setLang); }); } void _setLanguageLabel(String language) => - languageController.text = '$language (Seed language)'; + languageController.text = '${language.replaceAll("POLYSEED_", "")} (Seed language)'; + + void _changeSeedType(SeedType item) { + _setSeedType(item); + _changeLanguage('English'); + } + + void _setSeedType(SeedType item) { + setState(() => isPolyseed = item == SeedType.polyseed); + seedTypeController.text = item.toString(); + } } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index d77c8aa2a..ccac6f34c 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -1,29 +1,30 @@ +import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; -import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:polyseed/polyseed.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; -import 'package:cake_wallet/src/screens/restore/wallet_restore_from_keys_form.dart'; -import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/restore/restore_mode.dart'; -import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; class WalletRestorePage extends BasePage { WalletRestorePage(this.walletRestoreViewModel) @@ -48,27 +49,13 @@ class WalletRestorePage extends BasePage { } }, onSeedChange: (String seed) { - if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { - final hasHeight = walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey - .currentState!.restoreHeightController.text.isNotEmpty; - if (hasHeight) { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); - } - } else { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); - } + final isPolyseed = + walletRestoreViewModel.type == WalletType.monero && Polyseed.isValidSeed(seed); + _validateOnChange(isPolyseed: isPolyseed); }, - onLanguageChange: (_) { - if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { - final hasHeight = walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey - .currentState!.restoreHeightController.text.isNotEmpty; - - if (hasHeight) { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); - } - } else { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); - } + onLanguageChange: (String language) { + final isPolyseed = language.startsWith("POLYSEED_"); + _validateOnChange(isPolyseed: isPolyseed); })); break; case WalletRestoreMode.keys: @@ -163,7 +150,8 @@ class WalletRestorePage extends BasePage { color: Theme.of(context).colorScheme.background, child: Center( child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -219,8 +207,8 @@ class WalletRestorePage extends BasePage { const SizedBox(height: 25), GestureDetector( onTap: () { - Navigator.of(context) - .pushNamed(Routes.advancedPrivacySettings, arguments: walletRestoreViewModel.type); + Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, + arguments: walletRestoreViewModel.type); }, child: Text(S.of(context).advanced_privacy_settings), ), @@ -235,9 +223,26 @@ class WalletRestorePage extends BasePage { ); } + void _validateOnChange({bool isPolyseed = false}) { + if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + final hasHeight = walletRestoreFromSeedFormKey + .currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty; + + if (hasHeight == true) { + walletRestoreViewModel.isButtonEnabled = _isValidSeed(); + } + } else { + walletRestoreViewModel.isButtonEnabled = _isValidSeed(); + } + } + bool _isValidSeed() { - final seedWords = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text.split(' '); + final seedPhrase = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; + if (walletRestoreViewModel.type == WalletType.monero && Polyseed.isValidSeed(seedPhrase)) + return true; + + final seedWords = seedPhrase.split(' '); if ((walletRestoreViewModel.type == WalletType.monero || walletRestoreViewModel.type == WalletType.haven) && @@ -282,7 +287,8 @@ class WalletRestorePage extends BasePage { if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { credentials['height'] = - walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState!.height; + walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState?.height ?? + -1; } credentials['name'] = diff --git a/lib/src/screens/seed_language/seed_language_page.dart b/lib/src/screens/seed_language/seed_language_page.dart deleted file mode 100644 index b15da0375..000000000 --- a/lib/src/screens/seed_language/seed_language_page.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/src/widgets/seed_language_selector.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; -import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart'; - -class SeedLanguage extends BasePage { - SeedLanguage({required this.onConfirm}); - - final Function(BuildContext, String) onConfirm; - - final walletNameImage = Image.asset('assets/images/wallet_name.png'); - final walletNameLightImage = - Image.asset('assets/images/wallet_name_light.png'); - - @override - String get title => S.current.wallet_list_restore_wallet; - - @override - Widget body(BuildContext context) => - SeedLanguageForm( - onConfirm: onConfirm, - walletImage: currentTheme.type == ThemeType.dark - ? walletNameImage : walletNameLightImage); -} - -class SeedLanguageForm extends StatefulWidget { - SeedLanguageForm({required this.onConfirm, required this.walletImage}); - - final Function(BuildContext, String) onConfirm; - final Image walletImage; - - @override - SeedLanguageFormState createState() => SeedLanguageFormState(); -} - -class SeedLanguageFormState extends State { - static const aspectRatioImage = 1.22; - final _languageSelectorKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(top: 24), - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - content: - Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - padding: EdgeInsets.only(left: 12, right: 12), - child: AspectRatio( - aspectRatio: aspectRatioImage, - child: FittedBox(child: widget.walletImage, - fit: BoxFit.fill)), - ), - Padding( - padding: EdgeInsets.only(top: 40), - child: Text( - S.of(context).seed_language_choose, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor), - ), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: SeedLanguageSelector( - key: _languageSelectorKey, - initialSelected: defaultSeedLanguage), - ) - ]), - bottomSectionPadding: - EdgeInsets.only(left: 24, right: 24, bottom: 24), - bottomSection: Observer( - builder: (context) { - return PrimaryButton( - onPressed: () => widget.onConfirm( - context, _languageSelectorKey.currentState!.selected), - text: S.of(context).seed_language_next, - color: Colors.green, - textColor: Colors.white); - }, - )), - ); - } -} diff --git a/lib/src/screens/seed_language/widgets/seed_language_picker.dart b/lib/src/screens/seed_language/widgets/seed_language_picker.dart deleted file mode 100644 index 742a31e61..000000000 --- a/lib/src/screens/seed_language/widgets/seed_language_picker.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/picker.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:cake_wallet/generated/i18n.dart'; - -List flagImages = [ - Image.asset('assets/images/flags/usa.png'), - Image.asset('assets/images/flags/chn.png'), - Image.asset('assets/images/flags/nld.png'), - Image.asset('assets/images/flags/deu.png'), - Image.asset('assets/images/flags/jpn.png'), - Image.asset('assets/images/flags/prt.png'), - Image.asset('assets/images/flags/rus.png'), - Image.asset('assets/images/flags/esp.png'), - Image.asset('assets/images/flags/fra.png'), - Image.asset('assets/images/flags/ita.png'), -]; - -const List languageCodes = [ - 'Eng', - 'Chi', - 'Ned', - 'Ger', - 'Jap', - 'Por', - 'Rus', - 'Esp', - 'Fre', - 'Ita', -]; - -const defaultSeedLanguage = 'English'; - -const List seedLanguages = [ - defaultSeedLanguage, - 'Chinese (simplified)', - 'Dutch', - 'German', - 'Japanese', - 'Portuguese', - 'Russian', - 'Spanish', - 'French', - 'Italian', -]; - -enum Places { topLeft, topRight, bottomLeft, bottomRight, inside } - -class SeedLanguagePicker extends StatefulWidget { - SeedLanguagePicker({Key? key, this.selected = defaultSeedLanguage, required this.onItemSelected}) - : super(key: key); - - final String selected; - final Function(String) onItemSelected; - - @override - SeedLanguagePickerState createState() => - SeedLanguagePickerState(selected: selected, onItemSelected: onItemSelected); -} - -class SeedLanguagePickerState extends State { - SeedLanguagePickerState({required this.selected, required this.onItemSelected}); - - final String selected; - final Function(String) onItemSelected; - - @override - Widget build(BuildContext context) { - return Picker( - selectedAtIndex: seedLanguages.indexOf(selected), - items: seedLanguages, - images: flagImages, - isGridView: true, - title: S.of(context).seed_choose, - hintText: S.of(context).seed_choose, - matchingCriteria: (String language, String searchText) { - return language.toLowerCase().contains(searchText); - }, - onItemSelected: onItemSelected, - ); - } -} diff --git a/lib/src/widgets/seed_language_picker.dart b/lib/src/widgets/seed_language_picker.dart new file mode 100644 index 000000000..3bb0376b1 --- /dev/null +++ b/lib/src/widgets/seed_language_picker.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +import 'package:cake_wallet/entities/seed_type.dart'; + +class SeedLanguagePickerOption { + SeedLanguagePickerOption(this.name, this.nameLocalized, this.image, this.supportedSeedTypes); + + final String name; + final String nameLocalized; + final Image image; + final List supportedSeedTypes; +} + +final List seedLanguages = [ + SeedLanguagePickerOption('English', S.current.seed_language_english, + Image.asset('assets/images/flags/usa.png'), [SeedType.legacy, SeedType.polyseed]), + SeedLanguagePickerOption('Chinese (simplified)', S.current.seed_language_chinese, + Image.asset('assets/images/flags/chn.png'), [SeedType.legacy, SeedType.polyseed]), + SeedLanguagePickerOption('Chinese (Traditional)', S.current.seed_language_chinese_traditional, + Image.asset('assets/images/flags/chn.png'), [SeedType.polyseed]), + SeedLanguagePickerOption('Dutch', S.current.seed_language_dutch, + Image.asset('assets/images/flags/nld.png'), [SeedType.legacy]), + SeedLanguagePickerOption('German', S.current.seed_language_german, + Image.asset('assets/images/flags/deu.png'), [SeedType.legacy]), + SeedLanguagePickerOption('Japanese', S.current.seed_language_japanese, + Image.asset('assets/images/flags/jpn.png'), [SeedType.legacy, SeedType.polyseed]), + SeedLanguagePickerOption('Korean', S.current.seed_language_korean, + Image.asset('assets/images/flags/kor.png'), [SeedType.polyseed]), + SeedLanguagePickerOption('Portuguese', S.current.seed_language_portuguese, + Image.asset('assets/images/flags/prt.png'), [SeedType.legacy, SeedType.polyseed]), + SeedLanguagePickerOption('Russian', S.current.seed_language_russian, + Image.asset('assets/images/flags/rus.png'), [SeedType.legacy]), + SeedLanguagePickerOption('Czech', S.current.seed_language_czech, + Image.asset('assets/images/flags/czk.png'), [SeedType.polyseed]), + SeedLanguagePickerOption('Spanish', S.current.seed_language_spanish, + Image.asset('assets/images/flags/esp.png'), [SeedType.legacy, SeedType.polyseed]), + SeedLanguagePickerOption('French', S.current.seed_language_french, + Image.asset('assets/images/flags/fra.png'), [SeedType.legacy, SeedType.polyseed]), + SeedLanguagePickerOption('Italian', S.current.seed_language_italian, + Image.asset('assets/images/flags/ita.png'), [SeedType.legacy, SeedType.polyseed]), +]; + +const defaultSeedLanguage = 'English'; + +enum Places { topLeft, topRight, bottomLeft, bottomRight, inside } + +class SeedLanguagePicker extends StatefulWidget { + SeedLanguagePicker( + {Key? key, + this.selected = defaultSeedLanguage, + this.seedType = SeedType.defaultSeedType, + required this.onItemSelected}) + : super(key: key); + + final SeedType seedType; + final String selected; + final Function(String) onItemSelected; + + @override + SeedLanguagePickerState createState() => SeedLanguagePickerState( + selected: selected, onItemSelected: onItemSelected, seedType: seedType); +} + +class SeedLanguagePickerState extends State { + SeedLanguagePickerState( + {required this.selected, required this.onItemSelected, required this.seedType}); + + final SeedType seedType; + final String selected; + final Function(String) onItemSelected; + + @override + Widget build(BuildContext context) { + final availableSeedLanguages = seedLanguages + .where((SeedLanguagePickerOption e) => e.supportedSeedTypes.contains(seedType)); + + return Picker( + selectedAtIndex: availableSeedLanguages.map((e) => e.name).toList().indexOf(selected), + items: availableSeedLanguages.map((e) => e.name).toList(), + images: availableSeedLanguages.map((e) => e.image).toList(), + isGridView: true, + title: S.of(context).seed_choose, + hintText: S.of(context).seed_choose, + matchingCriteria: (String language, String searchText) { + return language.toLowerCase().contains(searchText); + }, + onItemSelected: onItemSelected, + ); + } +} diff --git a/lib/src/widgets/seed_language_selector.dart b/lib/src/widgets/seed_language_selector.dart index 4db3684a8..07638bb2f 100644 --- a/lib/src/widgets/seed_language_selector.dart +++ b/lib/src/widgets/seed_language_selector.dart @@ -1,14 +1,15 @@ +import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; +import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; -import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart'; class SeedLanguageSelector extends StatefulWidget { - SeedLanguageSelector({Key? key, required this.initialSelected}) + SeedLanguageSelector({Key? key, required this.initialSelected, this.seedType = SeedType.defaultSeedType}) : super(key: key); final String initialSelected; + final SeedType seedType; @override SeedLanguageSelectorState createState() => @@ -18,30 +19,19 @@ class SeedLanguageSelector extends StatefulWidget { class SeedLanguageSelectorState extends State { SeedLanguageSelectorState({required this.selected}); - final seedLocales = [ - S.current.seed_language_english, - S.current.seed_language_chinese, - S.current.seed_language_dutch, - S.current.seed_language_german, - S.current.seed_language_japanese, - S.current.seed_language_portuguese, - S.current.seed_language_russian, - S.current.seed_language_spanish, - S.current.seed_language_french, - S.current.seed_language_italian, - ]; String selected; @override Widget build(BuildContext context) { return SelectButton( image: null, - text: seedLocales[seedLanguages.indexOf(selected)], + text: seedLanguages.firstWhere((e) => e.name == selected).nameLocalized, onTap: () async { await showPopUp( context: context, builder: (_) => SeedLanguagePicker( selected: this.selected, + seedType: widget.seedType, onItemSelected: (String selected) => setState(() => this.selected = selected))); }, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 1522ea354..4aaa44e13 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; @@ -48,6 +49,7 @@ abstract class SettingsStoreBase with Store { required BalanceDisplayMode initialBalanceDisplayMode, required bool initialSaveRecipientAddress, required AutoGenerateSubaddressStatus initialAutoGenerateSubaddressStatus, + required SeedType initialMoneroSeedType, required bool initialAppSecure, required bool initialDisableBuy, required bool initialDisableSell, @@ -108,6 +110,7 @@ abstract class SettingsStoreBase with Store { balanceDisplayMode = initialBalanceDisplayMode, shouldSaveRecipientAddress = initialSaveRecipientAddress, autoGenerateSubaddressStatus = initialAutoGenerateSubaddressStatus, + moneroSeedType = initialMoneroSeedType, fiatApiMode = initialFiatMode, allowBiometricalAuthentication = initialAllowBiometricalAuthentication, selectedCake2FAPreset = initialCake2FAPresetOptions, @@ -240,6 +243,11 @@ abstract class SettingsStoreBase with Store { (AutoGenerateSubaddressStatus autoGenerateSubaddressStatus) => sharedPreferences.setInt( PreferencesKey.autoGenerateSubaddressStatusKey, autoGenerateSubaddressStatus.value)); + reaction( + (_) => moneroSeedType, + (SeedType moneroSeedType) => sharedPreferences.setInt( + PreferencesKey.moneroSeedType, moneroSeedType.raw)); + reaction( (_) => fiatApiMode, (FiatApiMode mode) => @@ -435,6 +443,7 @@ abstract class SettingsStoreBase with Store { static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes; static const defaultAutoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.initialized; static const defaultSeedPhraseLength = SeedPhraseLength.twelveWords; + static const defaultMoneroSeedType = SeedType.defaultSeedType; @observable FiatCurrency fiatCurrency; @@ -460,6 +469,9 @@ abstract class SettingsStoreBase with Store { @observable AutoGenerateSubaddressStatus autoGenerateSubaddressStatus; + @observable + SeedType moneroSeedType; + @observable bool isAppSecure; @@ -775,12 +787,20 @@ abstract class SettingsStoreBase with Store { final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; + final generateSubaddresses = sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); final autoGenerateSubaddressStatus = generateSubaddresses != null ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) : defaultAutoGenerateSubaddressStatus; + + final _moneroSeedType = sharedPreferences.getInt(PreferencesKey.moneroSeedType); + + final moneroSeedType = _moneroSeedType != null + ? SeedType.deserialize(raw: _moneroSeedType) + : defaultMoneroSeedType; + final nodes = {}; final powNodes = {}; @@ -833,27 +853,27 @@ abstract class SettingsStoreBase with Store { 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, - seedPhraseLength: seedPhraseWordCount, - initialLanguageCode: savedLanguageCode, - sortBalanceBy: sortBalanceBy, - pinNativeTokenAtTop: pinNativeTokenAtTop, - useEtherscan: useEtherscan, - defaultNanoRep: defaultNanoRep, + initialMoneroSeedType: moneroSeedType, + 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, + seedPhraseLength: seedPhraseWordCount,initialLanguageCode: savedLanguageCode, + sortBalanceBy: sortBalanceBy, + pinNativeTokenAtTop: pinNativeTokenAtTop, + useEtherscan: useEtherscan, + defaultNanoRep: defaultNanoRep, defaultBananoRep: defaultBananoRep, lookupsTwitter: lookupsTwitter, lookupsMastodon: lookupsMastodon, @@ -862,10 +882,10 @@ abstract class SettingsStoreBase with Store { lookupsOpenAlias: lookupsOpenAlias, lookupsENS: lookupsENS, initialMoneroTransactionPriority: moneroTransactionPriority, - initialBitcoinTransactionPriority: bitcoinTransactionPriority, - initialHavenTransactionPriority: havenTransactionPriority, - initialLitecoinTransactionPriority: litecoinTransactionPriority, - initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority, + initialBitcoinTransactionPriority: bitcoinTransactionPriority, + initialHavenTransactionPriority: havenTransactionPriority, + initialLitecoinTransactionPriority: litecoinTransactionPriority, + initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority, initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, @@ -927,6 +947,12 @@ abstract class SettingsStoreBase with Store { ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) : defaultAutoGenerateSubaddressStatus; + final _moneroSeedType = sharedPreferences.getInt(PreferencesKey.moneroSeedType); + + moneroSeedType = _moneroSeedType != null + ? SeedType.deserialize(raw: _moneroSeedType) + : defaultMoneroSeedType; + balanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); shouldSaveRecipientAddress = diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index b67a4378a..f62815346 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -19,6 +20,9 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @computed FiatApiMode get fiatApiMode => _settingsStore.fiatApiMode; + @computed + SeedType get seedType => _settingsStore.moneroSeedType; + @observable bool _addCustomNode = false; @@ -29,6 +33,8 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { bool get hasSeedPhraseLengthOption => type == WalletType.bitcoinCash || type == WalletType.ethereum; + bool get hasSeedTypeOption => type == WalletType.monero; + @computed bool get addCustomNode => _addCustomNode; @@ -38,6 +44,9 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @action void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode; + @action + void setSeedType(SeedType seedType) => _settingsStore.moneroSeedType = seedType; + @action void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value; 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 145db74d7..6085f4354 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -10,6 +10,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:collection/collection.dart'; +import 'package:polyseed/polyseed.dart'; class WalletRestoreFromQRCode { WalletRestoreFromQRCode(); @@ -51,7 +52,7 @@ class WalletRestoreFromQRCode { RegExp _getPattern(int wordCount) => RegExp(r'(?<=\W|^)((?:\w+\s+){' + (wordCount - 1).toString() + r'}\w+)(?=\W|$)'); - List patternCounts = walletType == WalletType.monero ? [25, 14, 13] : [24, 18, 12]; + List patternCounts = walletType == WalletType.monero ? [25, 16, 14, 13] : [24, 18, 12]; for (final count in patternCounts) { final pattern = _getPattern(count); @@ -123,12 +124,17 @@ class WalletRestoreFromQRCode { } if (credentials['seed'] != null) { - final seedValue = credentials['seed']; + final seedValue = credentials['seed'] as String; final words = SeedValidator.getWordList(type: type, language: 'english'); + + if (type == WalletType.monero && Polyseed.isValidSeed(seedValue)) { + return WalletRestoreMode.seed; + } + seedValue.split(' ').forEach((element) { if (!words.contains(element)) { throw Exception( - 'Unexpected restore mode: mnemonic_seed is invalid or does\'t match wallet type'); + "Unexpected restore mode: mnemonic_seed is invalid or doesn't match wallet type"); } }); return WalletRestoreMode.seed; diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 9b1f0834d..696559198 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,6 +1,4 @@ -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'; @@ -33,18 +31,19 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { bool get hasLanguageSelector => type == WalletType.monero || type == WalletType.haven; @override - WalletCredentials getCredentials(dynamic options) { + WalletCredentials getCredentials(dynamic _options) { + final options = _options as List; switch (type) { case WalletType.monero: return monero!.createMoneroNewWalletCredentials( - name: name, language: options as String); + name: name, language: options.first as String, isPolyseed: options.last as bool); case WalletType.bitcoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.litecoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.haven: return haven!.createHavenNewWalletCredentials( - name: name, language: options as String); + name: name, language: options.first as String); case WalletType.ethereum: return ethereum!.createEthereumNewWalletCredentials(name: name); case WalletType.bitcoinCash: diff --git a/pubspec_base.yaml b/pubspec_base.yaml index edfc64890..f71d38578 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -104,6 +104,9 @@ dependencies: ref: main socks5_proxy: ^1.0.4 flutter_svg: ^2.0.9 + polyseed: + git: + url: https://github.com/cake-tech/polyseed_dart.git dev_dependencies: flutter_test: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 8065a5bd1..47b11c78e 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -739,5 +739,11 @@ "unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ", "unspent_change": "يتغير", "seed_hex_form": "بذور المحفظة (شكل عرافة)", - "tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ" + "tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ", + "seedtype": "البذور", + "seedtype_legacy": "إرث (25 كلمة)", + "seedtype_polyseed": "بوليسيد (16 كلمة)", + "seed_language_czech": "التشيكية", + "seed_language_korean": "الكورية", + "seed_language_chinese_traditional": "تقاليد صينية)" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ab45efba3..162e85cad 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -735,5 +735,11 @@ "unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.", "unspent_change": "Промяна", "tor_connection": "Tor връзка", - "seed_hex_form": "Семена от портфейл (шестнадесетична форма)" + "seed_hex_form": "Семена от портфейл (шестнадесетична форма)", + "seedtype": "Семенна тип", + "seedtype_legacy": "Наследство (25 думи)", + "seedtype_polyseed": "Поли семе (16 думи)", + "seed_language_czech": "Чех", + "seed_language_korean": "Корейски", + "seed_language_chinese_traditional": "Традиционен китайски)" } \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 88792ec15..8efdb59b5 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -735,5 +735,11 @@ "unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.", "unspent_change": "Změna", "tor_connection": "Připojení Tor", - "seed_hex_form": "Semeno peněženky (hex formulář)" + "seed_hex_form": "Semeno peněženky (hex formulář)", + "seedtype": "SeedType", + "seedtype_legacy": "Legacy (25 slov)", + "seedtype_polyseed": "Polyseed (16 slov)", + "seed_language_czech": "čeština", + "seed_language_korean": "korejština", + "seed_language_chinese_traditional": "Číňan (tradiční)" } \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 7b204f20c..754a0f22e 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -722,9 +722,9 @@ "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat.", "copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein", "enterWalletConnectURI": "Geben Sie den WalletConnect-URI ein", - "seed_key": "Samenschlüssel", - "enter_seed_phrase": "Geben Sie Ihre Samenphrase ein", - "change_rep_successful": "Erfolgreich veränderte Vertreter", + "seed_key": "Seed-Schlüssel", + "enter_seed_phrase": "Geben Sie Ihre Seed-Phrase ein", + "change_rep_successful": "Vertreter erfolgreich gerändert", "add_contact": "Kontakt hinzufügen", "exchange_provider_unsupported": "${providerName} wird nicht mehr unterstützt!", "domain_looks_up": "Domain-Suchen", @@ -743,5 +743,11 @@ "unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.", "unspent_change": "Wechselgeld", "tor_connection": "Tor-Verbindung", - "seed_hex_form": "Brieftaschensamen (Sechskantform)" + "seed_hex_form": "Brieftaschensamen (Sechskantform)", + "seedtype": "Seedtyp", + "seedtype_legacy": "Veraltet (25 Wörter)", + "seedtype_polyseed": "Polyseed (16 Wörter)", + "seed_language_czech": "Tschechisch", + "seed_language_korean": "Koreanisch", + "seed_language_chinese_traditional": "Chinesisch (Traditionell)" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index bbcbdb6c0..d65aebe15 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -744,5 +744,11 @@ "unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.", "unspent_change": "Change", "tor_connection": "Tor connection", - "seed_hex_form": "Wallet seed (hex form)" + "seed_hex_form": "Wallet seed (hex form)", + "seedtype": "Seedtype", + "seedtype_legacy": "Legacy (25 words)", + "seedtype_polyseed": "Polyseed (16 words)", + "seed_language_czech": "Czech", + "seed_language_korean": "Korean", + "seed_language_chinese_traditional": "Chinese (Traditional)" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index e47f3e744..f785a15d5 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.", "unspent_change": "Cambiar", "tor_connection": "conexión tor", - "seed_hex_form": "Semilla de billetera (forma hexadecimal)" + "seed_hex_form": "Semilla de billetera (forma hexadecimal)", + "seedtype": "Type de semillas", + "seedtype_legacy": "Legado (25 palabras)", + "seedtype_polyseed": "Polieta (16 palabras)", + "seed_language_czech": "checo", + "seed_language_korean": "coreano", + "seed_language_chinese_traditional": "Chino tradicional)" } \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f87ed9809..88263ec05 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -745,5 +745,12 @@ "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer", "unspent_change": "Changement", "tor_connection": "Connexion Tor", - "seed_hex_form": "Graine du portefeuille (forme hexagonale)" + "seed_hex_form": "Graine du portefeuille (forme hexagonale)", + "camera_permission_is_required": "L'autorisation de la caméra est requise.\nVeuillez l'activer à partir des paramètres de l'application.", + "seedtype": "Type de type graine", + "seedtype_legacy": "Héritage (25 mots)", + "seedtype_polyseed": "Polyseed (16 mots)", + "seed_language_czech": "tchèque", + "seed_language_korean": "coréen", + "seed_language_chinese_traditional": "Chinois (Traditionnel)" } \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 2dce6f80d..39b98b51a 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -721,5 +721,11 @@ "unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.", "unspent_change": "Canza", "tor_connection": "Tor haɗin gwiwa", - "seed_hex_form": "Gany Sero (form form)" + "seed_hex_form": "Gany Sero (form form)", + "seedtype": "Seedtype", + "seedtype_legacy": "Legacy (25 kalmomi)", + "seedtype_polyseed": "Polyseed (16 kalmomi)", + "seed_language_czech": "Czech", + "seed_language_korean": "Yaren Koriya", + "seed_language_chinese_traditional": "Sinanci (na gargajiya)" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 66a4c8e47..b050ef43b 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।", "unspent_change": "परिवर्तन", "tor_connection": "टोर कनेक्शन", - "seed_hex_form": "वॉलेट सीड (हेक्स फॉर्म)" + "seed_hex_form": "वॉलेट सीड (हेक्स फॉर्म)", + "seedtype": "बीज", + "seedtype_legacy": "विरासत (25 शब्द)", + "seedtype_polyseed": "पॉलीसीड (16 शब्द)", + "seed_language_czech": "चेक", + "seed_language_korean": "कोरियाई", + "seed_language_chinese_traditional": "चीनी पारंपरिक)" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4e84d3ed4..3c0f895e7 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -741,5 +741,11 @@ "unavailable_balance_description": "Nedostupno stanje: Ovaj ukupni iznos uključuje sredstva koja su zaključana u transakcijama na čekanju i ona koja ste aktivno zamrznuli u postavkama kontrole novčića. Zaključani saldi postat će dostupni kada se dovrše njihove transakcije, dok zamrznuti saldi ostaju nedostupni za transakcije sve dok ih ne odlučite odmrznuti.", "unspent_change": "Promijeniti", "tor_connection": "Tor veza", - "seed_hex_form": "Sjeme novčanika (šesterokutni oblik)" + "seed_hex_form": "Sjeme novčanika (šesterokutni oblik)", + "seedtype": "Sjemenska vrsta", + "seedtype_legacy": "Nasljeđe (25 riječi)", + "seedtype_polyseed": "Poliseed (16 riječi)", + "seed_language_czech": "češki", + "seed_language_korean": "korejski", + "seed_language_chinese_traditional": "Kinesko tradicionalno)" } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 82e9bca56..b29572746 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -731,5 +731,11 @@ "unavailable_balance_description": "Saldo Tidak Tersedia: Total ini termasuk dana yang terkunci dalam transaksi yang tertunda dan dana yang telah Anda bekukan secara aktif di pengaturan kontrol koin Anda. Saldo yang terkunci akan tersedia setelah transaksi masing-masing selesai, sedangkan saldo yang dibekukan tetap tidak dapat diakses untuk transaksi sampai Anda memutuskan untuk mencairkannya.", "unspent_change": "Mengubah", "tor_connection": "koneksi Tor", - "seed_hex_form": "Biji dompet (bentuk hex)" + "seed_hex_form": "Biji dompet (bentuk hex)", + "seedtype": "Seedtype", + "seedtype_legacy": "Legacy (25 kata)", + "seedtype_polyseed": "Polyseed (16 kata)", + "seed_language_czech": "Ceko", + "seed_language_korean": "Korea", + "seed_language_chinese_traditional": "Cina tradisional)" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 0005fb5e7..19912d277 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "Saldo non disponibile: questo totale include i fondi bloccati nelle transazioni in sospeso e quelli che hai congelato attivamente nelle impostazioni di controllo delle monete. I saldi bloccati diventeranno disponibili una volta completate le rispettive transazioni, mentre i saldi congelati rimarranno inaccessibili per le transazioni finché non deciderai di sbloccarli.", "unspent_change": "Modifica", "tor_connection": "Connessione Tor", - "seed_hex_form": "Seme di portafoglio (forma esadecimale)" + "seed_hex_form": "Seme di portafoglio (forma esadecimale)", + "seedtype": "Seedtype", + "seedtype_legacy": "Legacy (25 parole)", + "seedtype_polyseed": "Polyseed (16 parole)", + "seed_language_czech": "ceco", + "seed_language_korean": "coreano", + "seed_language_chinese_traditional": "Cinese tradizionale)" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d57eaef5d..ea138b317 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "利用不可能な残高: この合計には、保留中のトランザクションにロックされている資金と、コイン管理設定でアクティブに凍結した資金が含まれます。ロックされた残高は、それぞれの取引が完了すると利用可能になりますが、凍結された残高は、凍結を解除するまで取引にアクセスできません。", "unspent_change": "変化", "tor_connection": "Tor接続", - "seed_hex_form": "ウォレットシード(ヘックスフォーム)" + "seed_hex_form": "ウォレットシード(ヘックスフォーム)", + "seedtype": "SeedType", + "seedtype_legacy": "レガシー(25語)", + "seedtype_polyseed": "ポリシード(16語)", + "seed_language_czech": "チェコ", + "seed_language_korean": "韓国語", + "seed_language_chinese_traditional": "中国の伝統的な)" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 775918659..4df43018d 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -741,5 +741,11 @@ "unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.", "unspent_change": "변화", "tor_connection": "토르 연결", - "seed_hex_form": "지갑 씨앗 (16 진 양식)" + "seed_hex_form": "지갑 씨앗 (16 진 양식)", + "seedtype": "시드 타입", + "seedtype_legacy": "레거시 (25 단어)", + "seedtype_polyseed": "다문 (16 단어)", + "seed_language_czech": "체코 사람", + "seed_language_korean": "한국인", + "seed_language_chinese_traditional": "중국 전통)" } \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 4aacb0bd3..d82e30116 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -741,5 +741,11 @@ "unavailable_balance_description": "မရရှိနိုင်သော လက်ကျန်ငွေ- ဤစုစုပေါင်းတွင် ဆိုင်းငံ့ထားသော ငွေပေးငွေယူများတွင် သော့ခတ်ထားသော ငွေကြေးများနှင့် သင်၏ coin ထိန်းချုပ်မှုဆက်တင်များတွင် သင် တက်ကြွစွာ အေးခဲထားသော ငွေများ ပါဝင်သည်။ သော့ခတ်ထားသော လက်ကျန်ငွေများကို ၎င်းတို့၏ သက်ဆိုင်ရာ ငွေပေးငွေယူများ ပြီးမြောက်သည်နှင့် တပြိုင်နက် ရရှိနိုင်မည်ဖြစ်ပြီး၊ အေးခဲထားသော လက်ကျန်များကို ၎င်းတို့အား ပြန်ဖြုတ်ရန် သင်ဆုံးဖြတ်သည်အထိ ငွေပေးငွေယူများအတွက် ဆက်လက်၍မရနိုင်ပါ။", "unspent_change": "ပေြာင်းလဲခြင်း", "tor_connection": "Tor ချိတ်ဆက်မှု", - "seed_hex_form": "ပိုက်ဆံအိတ်မျိုးစေ့ (Hex Form)" + "seed_hex_form": "ပိုက်ဆံအိတ်မျိုးစေ့ (Hex Form)", + "seedtype": "မျိုးပွားခြင်း", + "seedtype_legacy": "အမွေအနှစ် (စကားလုံး 25 လုံး)", + "seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)", + "seed_language_czech": "ချက်", + "seed_language_korean": "ကိုးရီးယား", + "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index cf779afab..8a290618d 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "Niet-beschikbaar saldo: Dit totaal omvat het geld dat is vergrendeld in lopende transacties en het geld dat u actief hebt bevroren in uw muntcontrole-instellingen. Vergrendelde saldi komen beschikbaar zodra de betreffende transacties zijn voltooid, terwijl bevroren saldi ontoegankelijk blijven voor transacties totdat u besluit ze weer vrij te geven.", "unspent_change": "Wijziging", "tor_connection": "Tor-verbinding", - "seed_hex_form": "Portemonnee zaad (hexvorm)" + "seed_hex_form": "Portemonnee zaad (hexvorm)", + "seedtype": "Zaadtype", + "seedtype_legacy": "Legacy (25 woorden)", + "seedtype_polyseed": "Polyseed (16 woorden)", + "seed_language_czech": "Tsjechisch", + "seed_language_korean": "Koreaans", + "seed_language_chinese_traditional": "Chinese traditionele)" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 04c420db4..b84ddc98c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "Niedostępne saldo: Suma ta obejmuje środki zablokowane w transakcjach oczekujących oraz te, które aktywnie zamroziłeś w ustawieniach kontroli monet. Zablokowane salda staną się dostępne po zakończeniu odpowiednich transakcji, natomiast zamrożone salda pozostaną niedostępne dla transakcji, dopóki nie zdecydujesz się ich odblokować.", "unspent_change": "Zmiana", "tor_connection": "Połączenie Torem", - "seed_hex_form": "Nasiona portfela (forma sześciokątna)" + "seed_hex_form": "Nasiona portfela (forma sześciokątna)", + "seedtype": "Sedtype", + "seedtype_legacy": "Dziedzictwo (25 słów)", + "seedtype_polyseed": "Poliqueed (16 słów)", + "seed_language_czech": "Czech", + "seed_language_korean": "koreański", + "seed_language_chinese_traditional": "Chiński tradycyjny)" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index a461141f3..ed7b8a801 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -742,5 +742,11 @@ "unavailable_balance_description": "Saldo Indisponível: Este total inclui fundos bloqueados em transações pendentes e aqueles que você congelou ativamente nas configurações de controle de moedas. Os saldos bloqueados ficarão disponíveis assim que suas respectivas transações forem concluídas, enquanto os saldos congelados permanecerão inacessíveis para transações até que você decida descongelá-los.", "unspent_change": "Mudar", "tor_connection": "Conexão Tor", - "seed_hex_form": "Semente de carteira (forma hexadecimal)" + "seed_hex_form": "Semente de carteira (forma hexadecimal)", + "seedtype": "SeedType", + "seedtype_legacy": "Legado (25 palavras)", + "seedtype_polyseed": "Polyseed (16 palavras)", + "seed_language_czech": "Tcheco", + "seed_language_korean": "coreano", + "seed_language_chinese_traditional": "Chinês tradicional)" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 8d0cc647a..c7749d0ea 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "Недоступный баланс: в эту сумму входят средства, заблокированные в ожидающих транзакциях, и средства, которые вы активно заморозили в настройках управления монетами. Заблокированные балансы станут доступны после завершения соответствующих транзакций, а замороженные балансы останутся недоступными для транзакций, пока вы не решите их разморозить.", "unspent_change": "Изменять", "tor_connection": "Тор соединение", - "seed_hex_form": "Семя кошелька (шестнадцатеричная форма)" + "seed_hex_form": "Семя кошелька (шестнадцатеричная форма)", + "seedtype": "SEEDTYPE", + "seedtype_legacy": "Наследие (25 слов)", + "seedtype_polyseed": "Полиса (16 слов)", + "seed_language_czech": "Чешский", + "seed_language_korean": "Корейский", + "seed_language_chinese_traditional": "Китайский традиционный)" } \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 151c65cd3..791b0f005 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -741,5 +741,11 @@ "unavailable_balance_description": "ยอดคงเหลือที่ไม่พร้อมใช้งาน: ยอดรวมนี้รวมถึงเงินทุนที่ถูกล็อคในการทำธุรกรรมที่รอดำเนินการและที่คุณได้แช่แข็งไว้ในการตั้งค่าการควบคุมเหรียญของคุณ ยอดคงเหลือที่ถูกล็อคจะพร้อมใช้งานเมื่อธุรกรรมที่เกี่ยวข้องเสร็จสมบูรณ์ ในขณะที่ยอดคงเหลือที่แช่แข็งจะไม่สามารถเข้าถึงได้สำหรับธุรกรรมจนกว่าคุณจะตัดสินใจยกเลิกการแช่แข็ง", "unspent_change": "เปลี่ยน", "tor_connection": "การเชื่อมต่อทอร์", - "seed_hex_form": "เมล็ดกระเป๋าเงิน (รูปแบบฐานสิบหก)" + "seed_hex_form": "เมล็ดกระเป๋าเงิน (รูปแบบฐานสิบหก)", + "seedtype": "เมล็ดพันธุ์", + "seedtype_legacy": "มรดก (25 คำ)", + "seedtype_polyseed": "โพลีส (16 คำ)", + "seed_language_czech": "ภาษาเช็ก", + "seed_language_korean": "เกาหลี", + "seed_language_chinese_traditional": "จีน (ดั้งเดิม)" } \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 258eb6f12..8891563b0 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -738,5 +738,10 @@ "unavailable_balance_description": "Hindi Available na Balanse: Kasama sa kabuuang ito ang mga pondong naka-lock sa mga nakabinbing transaksyon at ang mga aktibong na-freeze mo sa iyong mga setting ng kontrol ng coin. Magiging available ang mga naka-lock na balanse kapag nakumpleto na ang kani-kanilang mga transaksyon, habang ang mga nakapirming balanse ay nananatiling hindi naa-access para sa mga transaksyon hanggang sa magpasya kang i-unfreeze ang mga ito.", "unspent_change": "Baguhin", "tor_connection": "Koneksyon ng Tor", - "seed_hex_form": "Wallet seed (hex form)" + "seed_hex_form": "Wallet seed (hex form)", + "seedtype_legacy": "Pamana (25 salita)", + "seedtype_polyseed": "Polyseed (16 na salita)", + "seed_language_czech": "Czech", + "seed_language_korean": "Korean", + "seed_language_chinese_traditional": "Intsik (tradisyonal)" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 33362b83c..a5d6a330a 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -741,5 +741,11 @@ "unavailable_balance_description": "Kullanılamayan Bakiye: Bu toplam, bekleyen işlemlerde kilitlenen fonları ve jeton kontrol ayarlarınızda aktif olarak dondurduğunuz fonları içerir. Kilitli bakiyeler, ilgili işlemleri tamamlandıktan sonra kullanılabilir hale gelir; dondurulmuş bakiyeler ise siz onları dondurmaya karar verene kadar işlemler için erişilemez durumda kalır.", "unspent_change": "Değiştirmek", "tor_connection": "Tor bağlantısı", - "seed_hex_form": "Cüzdan tohumu (onaltılık form)" + "seed_hex_form": "Cüzdan tohumu (onaltılık form)", + "seedtype": "Tohum", + "seedtype_legacy": "Miras (25 kelime)", + "seedtype_polyseed": "Polyseed (16 kelime)", + "seed_language_czech": "Çek", + "seed_language_korean": "Koreli", + "seed_language_chinese_traditional": "Çin geleneği)" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 7b79ac6e4..81bc3c8aa 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -743,5 +743,11 @@ "unavailable_balance_description": "Недоступний баланс: ця сума включає кошти, заблоковані в незавершених транзакціях, і ті, які ви активно заморозили в налаштуваннях контролю монет. Заблоковані баланси стануть доступними після завершення відповідних транзакцій, тоді як заморожені баланси залишаються недоступними для транзакцій, доки ви не вирішите їх розморозити.", "unspent_change": "Зміна", "tor_connection": "Підключення Tor", - "seed_hex_form": "Насіння гаманця (шістнадцяткова форма)" + "seed_hex_form": "Насіння гаманця (шістнадцяткова форма)", + "seedtype": "Насіннєвий тип", + "seedtype_legacy": "Спадщина (25 слів)", + "seedtype_polyseed": "Полісей (16 слів)", + "seed_language_czech": "Чеський", + "seed_language_korean": "Корейський", + "seed_language_chinese_traditional": "Китайський традиційний)" } \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index c8362ab33..5b7f18ae7 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -735,5 +735,11 @@ "unavailable_balance_description": "۔ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﮧﻠﺼﯿﻓ ﺎﮐ ﮯﻧﺮﮐ ﺪﻤﺠﻨﻣ ﻥﺍ ﮟﯿﮩﻧﺍ ﭖﺁ ﮧﮐ ﮏﺗ ﺐﺟ ﮟﯿﮨ ﮯﺘﮨﺭ ﯽﺋﺎﺳﺭ ﻞﺑﺎﻗﺎﻧ ﮏﺗ ﺖﻗﻭ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﻦﯾﺩ ﻦﯿﻟ ﺲﻨﻠﯿﺑ ﺪﻤﺠﻨﻣ ﮧﮐ ﺐﺟ ،ﮯﮔ ﮟﯿﺋﺎﺟ ﻮﮨ ﺏﺎﯿﺘﺳﺩ ﺲﻨﻠﯿﺑ ﻞﻔﻘﻣ ﺪﻌﺑ ﮯﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﻦﯾﺩ ﻦﯿﻟ ﮧﻘﻠﻌﺘﻣ ﮯﮐ ﻥﺍ ۔ﮯﮨ ﺎﮭﮐﺭ ﺮ", "unspent_change": "تبدیل کریں", "seed_hex_form": "پرس بیج (ہیکس فارم)", - "tor_connection": "ﻦﺸﮑﻨﮐ ﺭﻮﭨ" + "tor_connection": "ﻦﺸﮑﻨﮐ ﺭﻮﭨ", + "seedtype": "سیڈ ٹائپ", + "seedtype_legacy": "میراث (25 الفاظ)", + "seedtype_polyseed": "پالیسیڈ (16 الفاظ)", + "seed_language_czech": "چیک", + "seed_language_korean": "کورین", + "seed_language_chinese_traditional": "چینی (روایتی)" } \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 7b9b075d7..6821aa7f9 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -737,5 +737,11 @@ "unavailable_balance_description": "Iwontunws.funfun ti ko si: Lapapọ yii pẹlu awọn owo ti o wa ni titiipa ni awọn iṣowo isunmọ ati awọn ti o ti didi ni itara ninu awọn eto iṣakoso owo rẹ. Awọn iwọntunwọnsi titiipa yoo wa ni kete ti awọn iṣowo oniwun wọn ba ti pari, lakoko ti awọn iwọntunwọnsi tio tutunini ko ni iraye si fun awọn iṣowo titi iwọ o fi pinnu lati mu wọn kuro.", "unspent_change": "Yipada", "tor_connection": "Tor asopọ", - "seed_hex_form": "Irú Opamọwọ apamọwọ (HOX)" + "seed_hex_form": "Irú Opamọwọ apamọwọ (HOX)", + "seedtype": "Irugbin-seetypu", + "seedtype_legacy": "Legacy (awọn ọrọ 25)", + "seedtype_polyseed": "Polyseed (awọn ọrọ 16)", + "seed_language_czech": "Czech", + "seed_language_korean": "Ara ẹni", + "seed_language_chinese_traditional": "Kannada (ibile)" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index e95bc077b..b66d0e65e 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -742,5 +742,11 @@ "unavailable_balance_description": "不可用余额:此总额包括锁定在待处理交易中的资金以及您在硬币控制设置中主动冻结的资金。一旦各自的交易完成,锁定的余额将变得可用,而冻结的余额在您决定解冻之前仍然无法进行交易。", "unspent_change": "改变", "tor_connection": "Tor连接", - "seed_hex_form": "钱包种子(十六进制形式)" + "seed_hex_form": "钱包种子(十六进制形式)", + "seedtype": "籽粒", + "seedtype_legacy": "遗产(25个单词)", + "seedtype_polyseed": "多种物品(16个单词)", + "seed_language_czech": "捷克", + "seed_language_korean": "韩国人", + "seed_language_chinese_traditional": "中国传统的)" } \ No newline at end of file diff --git a/tool/configure.dart b/tool/configure.dart index bc32581a6..7d50ddf53 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -134,7 +134,6 @@ 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'; -import 'package:flutter/foundation.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -144,7 +143,8 @@ import 'package:cw_core/balance.dart'; import 'package:cw_core/output_info.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/wallet_service.dart'; -import 'package:hive/hive.dart';"""; +import 'package:hive/hive.dart'; +import 'package:polyseed/polyseed.dart';"""; const moneroCWHeaders = """ import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_amount_format.dart'; @@ -152,7 +152,6 @@ import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_monero/monero_wallet_service.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_transaction_info.dart'; -import 'package:cw_monero/monero_transaction_history.dart'; import 'package:cw_monero/monero_transaction_creation_credentials.dart'; import 'package:cw_core/account.dart' as monero_account; import 'package:cw_monero/api/wallet.dart' as monero_wallet_api; @@ -255,7 +254,7 @@ abstract class Monero { required String language, required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic}); - WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, String password,}); + WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String password}); Map getKeys(Object wallet); Object createMoneroTransactionCreationCredentials({required List outputs, required TransactionPriority priority}); Object createMoneroTransactionCreationCredentialsRaw({required List outputs, required TransactionPriority priority}); From 615d016dd5b00afb6fd2792640cf3c83947a6c41 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 27 Nov 2023 08:28:34 -0500 Subject: [PATCH 04/18] be absolutely sure we delete secure storage keys before writing them (#1182) * be absolutely sure we delete secure storage keys before writing them * sync with other PR --------- Co-authored-by: fossephate Co-authored-by: Omar Hatem --- lib/core/backup_service.dart | 4 ++++ lib/core/key_service.dart | 1 + lib/entities/default_settings_migration.dart | 1 + lib/entities/fs_migration.dart | 1 + lib/entities/get_encryption_key.dart | 4 +++- lib/src/screens/support_chat/widgets/chatwoot_widget.dart | 6 ++++-- lib/view_model/edit_backup_password_view_model.dart | 1 + 7 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index b13a01889..7dd0b50f3 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -436,6 +436,7 @@ class BackupService { final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPassword = keychainJSON[backupPasswordKey] as String; + await _flutterSecureStorage.delete(key: backupPasswordKey); await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword); keychainWalletsInfo.forEach((dynamic rawInfo) async { @@ -443,6 +444,7 @@ class BackupService { await importWalletKeychainInfo(info); }); + await _flutterSecureStorage.delete(key: pinCodeKey); await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); keychainDumpFile.deleteSync(); @@ -462,6 +464,7 @@ class BackupService { final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPassword = keychainJSON[backupPasswordKey] as String; + await _flutterSecureStorage.delete(key: backupPasswordKey); await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword); keychainWalletsInfo.forEach((dynamic rawInfo) async { @@ -469,6 +472,7 @@ class BackupService { await importWalletKeychainInfo(info); }); + await _flutterSecureStorage.delete(key: pinCodeKey); await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); keychainDumpFile.deleteSync(); diff --git a/lib/core/key_service.dart b/lib/core/key_service.dart index 337f1ef21..fce254ea2 100644 --- a/lib/core/key_service.dart +++ b/lib/core/key_service.dart @@ -19,6 +19,7 @@ class KeyService { key: SecretStoreKey.moneroWalletPassword, walletName: walletName); final encodedPassword = encodeWalletPassword(password: password); + await _secureStorage.delete(key: key); await _secureStorage.write(key: key, value: encodedPassword); } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 47092c22f..a65742e99 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -496,6 +496,7 @@ Future generateBackupPassword(FlutterSecureStorage secureStorage) async { } final password = encrypt.Key.fromSecureRandom(32).base16; + await secureStorage.delete(key: key); await secureStorage.write(key: key, value: password); } diff --git a/lib/entities/fs_migration.dart b/lib/entities/fs_migration.dart index e3088ff54..4280949cd 100644 --- a/lib/entities/fs_migration.dart +++ b/lib/entities/fs_migration.dart @@ -147,6 +147,7 @@ Future ios_migrate_pin() async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPassword = encodedPinCode(pin: pinPassword); + await flutterSecureStorage.delete(key: key); await flutterSecureStorage.write(key: key, value: encodedPassword); await prefs.setBool('ios_migration_pin_completed', true); } diff --git a/lib/entities/get_encryption_key.dart b/lib/entities/get_encryption_key.dart index e67380bb8..a32d4e311 100644 --- a/lib/entities/get_encryption_key.dart +++ b/lib/entities/get_encryption_key.dart @@ -9,7 +9,9 @@ Future> getEncryptionKey( if (stringifiedKey == null) { key = CakeHive.generateSecureKey(); final keyStringified = key.join(','); - await secureStorage.write(key: 'transactionDescriptionsBoxKey', value: keyStringified); + String storageKey = 'transactionDescriptionsBoxKey'; + await secureStorage.delete(key: storageKey); + await secureStorage.write(key: storageKey, value: keyStringified); } else { key = stringifiedKey.split(',').map((i) => int.parse(i)).toList(); } diff --git a/lib/src/screens/support_chat/widgets/chatwoot_widget.dart b/lib/src/screens/support_chat/widgets/chatwoot_widget.dart index d9a5f9bb8..73403d667 100644 --- a/lib/src/screens/support_chat/widgets/chatwoot_widget.dart +++ b/lib/src/screens/support_chat/widgets/chatwoot_widget.dart @@ -57,6 +57,8 @@ class ChatwootWidgetState extends State { return true; } - Future storeCookie(String value) async => - await widget.secureStorage.write(key: COOKIE_KEY, value: value); + Future storeCookie(String value) async { + await widget.secureStorage.delete(key: COOKIE_KEY); + await widget.secureStorage.write(key: COOKIE_KEY, value: value); + } } diff --git a/lib/view_model/edit_backup_password_view_model.dart b/lib/view_model/edit_backup_password_view_model.dart index e7b8ee017..aca76502a 100644 --- a/lib/view_model/edit_backup_password_view_model.dart +++ b/lib/view_model/edit_backup_password_view_model.dart @@ -37,6 +37,7 @@ abstract class EditBackupPasswordViewModelBase with Store { @action Future save() async { final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword); + await secureStorage.delete(key: key); await secureStorage.write(key: key, value: backupPassword); secretStore.write(key: key, value: backupPassword); } From 177d89b904fd1ec509c4be5449cb1112f1fcb273 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 27 Nov 2023 13:44:48 -0500 Subject: [PATCH 05/18] fix nano being sent to only the first output address in list (#1198) --- cw_nano/lib/nano_wallet.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index 795634849..b0d98efec 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -186,9 +186,9 @@ abstract class NanoWalletBase final block = await _client.constructSendBlock( amountRaw: amt.toString(), - destinationAddress: credentials.outputs.first.isParsedAddress - ? credentials.outputs.first.extractedAddress! - : credentials.outputs.first.address, + destinationAddress: txOut.isParsedAddress + ? txOut.extractedAddress! + : txOut.address, privateKey: _privateKey!, balanceAfterTx: runningBalance, previousHash: previousHash, From d07efc81ca257b0dadbcd11ba11d871df10bcd23 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 27 Nov 2023 22:50:12 +0100 Subject: [PATCH 06/18] Lookup NFTs only if the current wallet is an Ethereum Wallet (#1197) --- lib/view_model/dashboard/nft_view_model.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index ec5919b7d..c5acf5523 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -38,6 +39,8 @@ abstract class NFTViewModelBase with Store { @action Future getNFTAssetByWallet() async { + if (appStore.wallet!.type != WalletType.ethereum) return; + final walletAddress = appStore.wallet!.walletInfo.address; log('Fetching wallet NFTs for $walletAddress'); From 89cb216bbf15a178acbfb5f4d72788e768a4be6e Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Wed, 29 Nov 2023 15:18:46 +0100 Subject: [PATCH 07/18] Fix refresh from 0 on creating polyseeds (#1202) * Fix refresh from 0 on creating polyseeds * Try to fix refresh from 0 on creating polyseeds --- cw_monero/lib/monero_wallet_service.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 8e9eb6eac..077ab4e54 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -273,6 +273,9 @@ class MoneroWalletService extends WalletService< date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); final spendKey = keyToHexString(polyseed.generateKey(coin, 32)); + walletInfo.isRecovery = true; + walletInfo.restoreHeight = height; + await monero_wallet_manager.restoreFromSpendKey( path: path, password: password, From 4d245b7ff159a09484f29f0a3466e5eaea7376f6 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Thu, 30 Nov 2023 16:51:19 +0200 Subject: [PATCH 08/18] v4.11.0 and v1.8.0 (#1192) * v4.11.0 and v1.8.0 * Add missing functionalities to mac os monero api.cpp * Fix creating a new wallet issue * Remove un-necessary linux, macos, and windows files from bitcoincash package --- assets/text/Monerocom_Release_Notes.txt | 9 +--- assets/text/Release_Notes.txt | 10 ++-- .../.plugin_symlinks/path_provider_linux | 1 - .../flutter/ephemeral/.plugin_symlinks/tor | 1 - .../flutter/generated_plugin_registrant.cc | 11 ---- .../flutter/generated_plugin_registrant.h | 15 ------ .../linux/flutter/generated_plugins.cmake | 24 --------- .../Flutter/GeneratedPluginRegistrant.swift | 12 ----- .../ephemeral/Flutter-Generated.xcconfig | 11 ---- .../ephemeral/flutter_export_environment.sh | 12 ----- .../flutter/generated_plugin_registrant.cc | 11 ---- .../flutter/generated_plugin_registrant.h | 15 ------ .../windows/flutter/generated_plugins.cmake | 23 --------- cw_ethereum/pubspec.yaml | 1 - cw_monero/macos/Classes/monero_api.cpp | 50 ++++++++++++++++++- ios/Podfile.lock | 6 --- lib/view_model/wallet_new_vm.dart | 6 +-- scripts/android/app_env.sh | 8 +-- scripts/ios/app_env.sh | 8 +-- scripts/macos/app_env.sh | 4 +- 20 files changed, 67 insertions(+), 171 deletions(-) delete mode 120000 cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux delete mode 120000 cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor delete mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc delete mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h delete mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugins.cmake delete mode 100644 cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift delete mode 100644 cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig delete mode 100644 cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh delete mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc delete mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h delete mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugins.cmake diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index caac086d1..2830ecdd8 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,7 +1,2 @@ -Coin control fixes and enhancements -In-app Tor connection -Accessibility enhancements -Privacy settings enhancements -UI enhancements -Backup flow fixes -Bug fixes \ No newline at end of file +Monero Polyseed support, under Advanced privacy settings, create and restore from a 16 words phrase and without the need to remember the wallet creation date +Minor bug fixes and enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index caac086d1..accdbb080 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,7 +1,3 @@ -Coin control fixes and enhancements -In-app Tor connection -Accessibility enhancements -Privacy settings enhancements -UI enhancements -Backup flow fixes -Bug fixes \ No newline at end of file +Monero Polyseed support, under Advanced privacy settings, create and restore from a 16 words phrase and without the need to remember the wallet creation date +Add Ethereum NFTs tab to see all of your purchased NFTs +Minor bug fixes and enhancements \ No newline at end of file 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 deleted file mode 120000 index 0ed52b295..000000000 --- a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux +++ /dev/null @@ -1 +0,0 @@ -/Users/blazebrain/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ \ No newline at end of file diff --git a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor deleted file mode 120000 index 7ee2b206d..000000000 --- a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor +++ /dev/null @@ -1 +0,0 @@ -/Users/blazebrain/.pub-cache/git/tor-09ba92cb11d4e3cacf97256e57863b805f79f2e5/ \ 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 deleted file mode 100644 index e71a16d23..000000000 --- a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// 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 deleted file mode 100644 index e0f0a47bc..000000000 --- a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// 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 deleted file mode 100644 index 3d57782b2..000000000 --- a/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST - tor -) - -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 deleted file mode 100644 index e777c67df..000000000 --- a/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// 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 deleted file mode 100644 index 2f46994d3..000000000 --- a/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -// 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 deleted file mode 100644 index 2a3bcca5a..000000000 --- a/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/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/windows/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680a..000000000 --- a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// 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 deleted file mode 100644 index dc139d85a..000000000 --- a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// 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 deleted file mode 100644 index b93c4c30c..000000000 --- a/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# 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_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index 5d19589f3..6946a43a1 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: mobx: ^2.0.7+4 bip39: ^1.0.6 bip32: ^2.0.0 - ed25519_hd_key: ^2.2.0 hex: ^0.2.0 http: ^1.1.0 shared_preferences: ^2.0.15 diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp index ac8a64861..a5ca13822 100644 --- a/cw_monero/macos/Classes/monero_api.cpp +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -234,7 +234,6 @@ extern "C" } void setUnlocked(bool unlocked); - }; Monero::Coins *m_coins; @@ -375,6 +374,35 @@ extern "C" return true; } + bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey( + std::string(path), + std::string(password), + std::string(language), + _networkType, + (uint64_t)restoreHeight, + std::string(spendKey)); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + // Cache Raw to support Polyseed + wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); + + change_current_wallet(wallet); + return true; + } + bool load_wallet(char *path, char *password, int32_t nettype) { nice(19); @@ -439,6 +467,11 @@ extern "C" const char *seed() { + std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed"); + if (!_rawSeed.empty()) + { + return strdup(_rawSeed.c_str()); + } return strdup(get_current_wallet()->seed().c_str()); } @@ -842,6 +875,12 @@ extern "C" return m_transaction_history->count(); } + TransactionInfoRow* get_transaction(char * txId) + { + Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId)); + return new TransactionInfoRow(row); + } + int LedgerExchange( unsigned char *command, unsigned int cmd_len, @@ -971,6 +1010,15 @@ extern "C" return result; } + void freeze_coin(int index) + { + m_coins->setFrozen(index); + } + + void thaw_coin(int index) + { + m_coins->thaw(index); + } #ifdef __cplusplus } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index bbb088325..0ecb48591 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -122,8 +122,6 @@ PODS: - Flutter - MTBBarcodeScanner (5.0.11) - OrderedSet (5.0.0) - - package_info (0.0.1): - - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -177,7 +175,6 @@ DEPENDENCIES: - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - - package_info (from `.symlinks/plugins/package_info/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -239,8 +236,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/in_app_review/ios" local_auth_ios: :path: ".symlinks/plugins/local_auth_ios/ios" - package_info: - :path: ".symlinks/plugins/package_info/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -287,7 +282,6 @@ SPEC CHECKSUMS: local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 696559198..acf829168 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -32,18 +32,18 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { @override WalletCredentials getCredentials(dynamic _options) { - final options = _options as List; + final options = _options as List?; switch (type) { case WalletType.monero: return monero!.createMoneroNewWalletCredentials( - name: name, language: options.first as String, isPolyseed: options.last as bool); + name: name, language: options!.first as String, isPolyseed: options.last as bool); case WalletType.bitcoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.litecoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.haven: return haven!.createHavenNewWalletCredentials( - name: name, language: options.first as String); + name: name, language: options!.first as String); case WalletType.ethereum: return ethereum!.createEthereumNewWalletCredentials(name: name); case WalletType.bitcoinCash: diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 5dd9f2edf..d241c4421 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.5" -MONERO_COM_BUILD_NUMBER=67 +MONERO_COM_VERSION="1.8.0" +MONERO_COM_BUILD_NUMBER=68 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.5" -CAKEWALLET_BUILD_NUMBER=181 +CAKEWALLET_VERSION="4.11.0" +CAKEWALLET_BUILD_NUMBER=182 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 810b2b78c..874501cb5 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.5" -MONERO_COM_BUILD_NUMBER=65 +MONERO_COM_VERSION="1.8.0" +MONERO_COM_BUILD_NUMBER=66 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.10.5" -CAKEWALLET_BUILD_NUMBER=199 +CAKEWALLET_VERSION="4.11.0" +CAKEWALLET_BUILD_NUMBER=200 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 665da3425..f69670c9a 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.5" -CAKEWALLET_BUILD_NUMBER=42 +CAKEWALLET_VERSION="1.4.0" +CAKEWALLET_BUILD_NUMBER=43 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From 2bddeb6ac2e3e89c4cbabb8c3ba03de8905302f5 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Thu, 30 Nov 2023 16:38:28 +0100 Subject: [PATCH 09/18] Polyseed design changes (#1211) * Add icons to Language- and Seedtype-picker to indicate it's clickability * Add Seed Type Selector to New Wallet Page * Rename "Advanced Privacy Settings" to "Advanced Settings" --- .../screens/new_wallet/new_wallet_page.dart | 51 +++++++++---- .../wallet_restore_from_seed_form.dart | 75 ++++++++++++------- .../screens/restore/wallet_restore_page.dart | 2 +- lib/src/widgets/seed_language_selector.dart | 23 +++--- lib/view_model/wallet_new_vm.dart | 2 + res/values/strings_ar.arb | 4 +- 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 | 8 +- res/values/strings_fr.arb | 9 +-- res/values/strings_ha.arb | 6 +- res/values/strings_hi.arb | 6 +- res/values/strings_hr.arb | 8 +- res/values/strings_id.arb | 8 +- 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 | 8 +- 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 | 8 +- res/values/strings_ur.arb | 6 +- res/values/strings_yo.arb | 6 +- res/values/strings_zh.arb | 6 +- 31 files changed, 183 insertions(+), 137 deletions(-) diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index d86d51e93..5c9533ace 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -1,4 +1,6 @@ import 'package:cake_wallet/entities/generate_name.dart'; +import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/routes.dart'; @@ -88,6 +90,8 @@ class _WalletNameFormState extends State { }); } }); + + _setSeedType(SeedType.defaultSeedType); super.initState(); } @@ -183,25 +187,38 @@ class _WalletNameFormState extends State { ), ), ), + if (_walletNewVM.hasLanguageSelector) ...[ - Padding( - padding: EdgeInsets.only(top: 40), - child: Text( - S.of(context).seed_language_choose, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.titleColor), + if (_walletNewVM.hasSeedType) ...[ + Observer( + builder: (BuildContext build) => Padding( + padding: EdgeInsets.only(top: 24), + child: SelectButton( + text: widget._settingsStore.moneroSeedType.title, + onTap: () async { + await showPopUp( + context: context, + builder: (_) => Picker( + items: SeedType.all, + selectedAtIndex: isPolyseed ? 1 : 0, + onItemSelected: _setSeedType, + isSeparated: false, + ), + ); + }, + ), + ), ), - ), + ], Observer( builder: (BuildContext build) => Padding( - padding: EdgeInsets.only(top: 24), + padding: EdgeInsets.only(top: 10), child: SeedLanguageSelector( key: _languageSelectorKey, initialSelected: defaultSeedLanguage, - seedType: widget._settingsStore.moneroSeedType, + seedType: _walletNewVM.hasSeedType + ? widget._settingsStore.moneroSeedType + : SeedType.legacy, ), ), ) @@ -231,7 +248,7 @@ class _WalletNameFormState extends State { Navigator.of(context) .pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type); }, - child: Text(S.of(context).advanced_privacy_settings), + child: Text(S.of(context).advanced_settings), ), ], )), @@ -253,11 +270,17 @@ class _WalletNameFormState extends State { buttonAction: () => Navigator.of(context).pop()); }); } else { - final isPolyseed = widget._settingsStore.moneroSeedType == SeedType.polyseed; _walletNewVM.create( options: _walletNewVM.hasLanguageSelector ? [_languageSelectorKey.currentState!.selected, isPolyseed] : null); } } + + bool get isPolyseed => widget._settingsStore.moneroSeedType == SeedType.polyseed; + + void _setSeedType(SeedType item) { + widget._settingsStore.moneroSeedType = item; + _languageSelectorKey.currentState?.selected = defaultSeedLanguage; // Reset Seed language + } } diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index ac8413212..f31657743 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -124,25 +124,30 @@ class WalletRestoreFromSeedFormState extends State { onSeedChange: onSeedChange), if (widget.type == WalletType.monero) GestureDetector( - onTap: () async { - await showPopUp( - context: context, - builder: (_) => Picker( - items: SeedType.all, - selectedAtIndex: isPolyseed ? 1 : 0, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: _changeSeedType, - isSeparated: false, - )); - }, - child: Container( - color: Colors.transparent, - padding: EdgeInsets.only(top: 20.0), - child: IgnorePointer( - child: BaseTextFormField( - controller: seedTypeController, - enableInteractiveSelection: false, - readOnly: true)))), + onTap: () async { + await showPopUp( + context: context, + builder: (_) => Picker( + items: SeedType.all, + selectedAtIndex: isPolyseed ? 1 : 0, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: _changeSeedType, + isSeparated: false, + )); + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: seedTypeController, + enableInteractiveSelection: false, + readOnly: true, + suffixIcon: expandIcon, + ), + ), + ), + ), if (widget.displayLanguageSelector) GestureDetector( onTap: () async { @@ -154,14 +159,19 @@ class WalletRestoreFromSeedFormState extends State { seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy, )); }, - child: Container( - color: Colors.transparent, - padding: EdgeInsets.only(top: 20.0), - child: IgnorePointer( - child: BaseTextFormField( - controller: languageController, - enableInteractiveSelection: false, - readOnly: true)))), + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: languageController, + enableInteractiveSelection: false, + readOnly: true, + suffixIcon: expandIcon, + ), + ), + ), + ), if (!isPolyseed && widget.displayBlockHeightSelector) BlockchainHeightWidget( focusNode: widget.blockHeightFocusNode, @@ -171,6 +181,17 @@ class WalletRestoreFromSeedFormState extends State { ])); } + Widget get expandIcon => Container( + padding: EdgeInsets.all(18), + width: 24, + height: 24, + child: Image.asset( + 'assets/images/arrow_bottom_purple_icon.png', + height: 8, + color: Theme.of(context).hintColor, + ), + ); + void _changeLanguage(String language) { final setLang = isPolyseed ? "POLYSEED_$language" : language; setState(() { diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index ccac6f34c..445858f30 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -210,7 +210,7 @@ class WalletRestorePage extends BasePage { Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: walletRestoreViewModel.type); }, - child: Text(S.of(context).advanced_privacy_settings), + child: Text(S.of(context).advanced_settings), ), ], ), diff --git a/lib/src/widgets/seed_language_selector.dart b/lib/src/widgets/seed_language_selector.dart index 07638bb2f..dde78c58c 100644 --- a/lib/src/widgets/seed_language_selector.dart +++ b/lib/src/widgets/seed_language_selector.dart @@ -1,19 +1,20 @@ import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; class SeedLanguageSelector extends StatefulWidget { - SeedLanguageSelector({Key? key, required this.initialSelected, this.seedType = SeedType.defaultSeedType}) + SeedLanguageSelector( + {Key? key, required this.initialSelected, this.seedType = SeedType.defaultSeedType}) : super(key: key); final String initialSelected; final SeedType seedType; @override - SeedLanguageSelectorState createState() => - SeedLanguageSelectorState(selected: initialSelected); + SeedLanguageSelectorState createState() => SeedLanguageSelectorState(selected: initialSelected); } class SeedLanguageSelectorState extends State { @@ -25,15 +26,17 @@ class SeedLanguageSelectorState extends State { Widget build(BuildContext context) { return SelectButton( image: null, - text: seedLanguages.firstWhere((e) => e.name == selected).nameLocalized, + text: + "${seedLanguages.firstWhere((e) => e.name == selected).nameLocalized} (${S.of(context).seed_language})", onTap: () async { await showPopUp( - context: context, - builder: (_) => SeedLanguagePicker( - selected: this.selected, - seedType: widget.seedType, - onItemSelected: (String selected) => - setState(() => this.selected = selected))); + context: context, + builder: (_) => SeedLanguagePicker( + selected: this.selected, + seedType: widget.seedType, + onItemSelected: (String selected) => setState(() => this.selected = selected), + ), + ); }, ); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index acf829168..a36d68482 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -30,6 +30,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { bool get hasLanguageSelector => type == WalletType.monero || type == WalletType.haven; + bool get hasSeedType => type == WalletType.monero; + @override WalletCredentials getCredentials(dynamic _options) { final options = _options as List?; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 47b11c78e..6158342f3 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -160,7 +160,7 @@ "seed_title": "سييد", "seed_share": "شارك السييد", "copy": "نسخ", - "seed_language_choose": "الرجاء اختيار لغة السييد:", + "seed_language": "لغة البذور", "seed_choose": "اختر لغة السييد", "seed_language_next": "التالي", "seed_language_english": "إنجليزي", @@ -569,7 +569,7 @@ "always": "دائماً", "minutes_to_pin_code": "${minutes} دقيقة", "disable_exchange": "تعطيل التبادل", - "advanced_privacy_settings": "إعدادات الخصوصية المتقدمة", + "advanced_settings": "إعدادات متقدمة", "settings_can_be_changed_later": "يمكن تغيير هذه الإعدادات لاحقًا في إعدادات التطبيق", "add_custom_node": "إضافة عقدة مخصصة جديدة", "disable_fiat": "تعطيل fiat", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 162e85cad..8c3a528bb 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Споделяне на seed", "copy": "Копиране", - "seed_language_choose": "Моля, изберете език на seed-а:", + "seed_language": "Език на семената", "seed_choose": "Изберете език на seed-а", "seed_language_next": "Следващ", "seed_language_english": "Английски", @@ -569,7 +569,7 @@ "always": "Винаги", "minutes_to_pin_code": "${minute} минути", "disable_exchange": "Деактивиране на борса", - "advanced_privacy_settings": "Допълнителни настройки за поверителност", + "advanced_settings": "Разширени настройки", "settings_can_be_changed_later": "Тези настройки могат да бъдат променени по-късно от приложението", "add_custom_node": "Добавяне на нов персонализиран Node", "disable_fiat": "Деактивиране на fiat", @@ -742,4 +742,4 @@ "seed_language_czech": "Чех", "seed_language_korean": "Корейски", "seed_language_chinese_traditional": "Традиционен китайски)" -} \ No newline at end of file +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 8efdb59b5..64f2b45e5 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Sdílet seed", "copy": "Kopírovat", - "seed_language_choose": "Prosím zvolte si jazyk seedu:", + "seed_language": "Jazyk semen", "seed_choose": "Zvolte si jazyk seedu", "seed_language_next": "Další", "seed_language_english": "Angličtina", @@ -569,7 +569,7 @@ "always": "Vždy", "minutes_to_pin_code": "${minute} minutách", "disable_exchange": "Zakázat směnárny", - "advanced_privacy_settings": "Pokročilá nastavení soukromí", + "advanced_settings": "Pokročilé nastavení", "settings_can_be_changed_later": "Tato nastavení mohou být změněna později v nastavení v této aplikaci", "add_custom_node": "Přidat vlastní uzel", "disable_fiat": "Zakázat fiat", @@ -742,4 +742,4 @@ "seed_language_czech": "čeština", "seed_language_korean": "korejština", "seed_language_chinese_traditional": "Číňan (tradiční)" -} \ No newline at end of file +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 754a0f22e..2bb9e1404 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Seed teilen", "copy": "Kopieren", - "seed_language_choose": "Bitte wählen Sie die Sprache des Seeds:", + "seed_language": "Seed-Sprache", "seed_choose": "Seed-Sprache auswählen", "seed_language_next": "Weiter", "seed_language_english": "Englisch", @@ -571,7 +571,7 @@ "always": "immer", "minutes_to_pin_code": "${minute} Minuten", "disable_exchange": "Exchange deaktivieren", - "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", + "advanced_settings": "Erweiterte Einstellungen", "settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden", "add_custom_node": "Neuen benutzerdefinierten Knoten hinzufügen", "disable_fiat": "Fiat deaktivieren", @@ -750,4 +750,4 @@ "seed_language_czech": "Tschechisch", "seed_language_korean": "Koreanisch", "seed_language_chinese_traditional": "Chinesisch (Traditionell)" -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index d65aebe15..88949525c 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Share seed", "copy": "Copy", - "seed_language_choose": "Please choose seed language:", + "seed_language": "Seed language", "seed_choose": "Choose seed language", "seed_language_next": "Next", "seed_language_english": "English", @@ -572,7 +572,7 @@ "always": "Always", "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Disable exchange", - "advanced_privacy_settings": "Advanced Privacy Settings", + "advanced_settings": "Advanced Settings", "settings_can_be_changed_later": "These settings can be changed later in the app settings", "add_custom_node": "Add New Custom Node", "disable_fiat": "Disable fiat", @@ -751,4 +751,4 @@ "seed_language_czech": "Czech", "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Chinese (Traditional)" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index f785a15d5..7a3907fb2 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -160,7 +160,7 @@ "seed_title": "Semilla", "seed_share": "Compartir semillas", "copy": "Dupdo", - "seed_language_choose": "Por favor elija el idioma semilla:", + "seed_language": "Lenguaje de semillas", "seed_choose": "Elige el idioma semilla", "seed_language_next": "Próximo", "seed_language_english": "Inglés", @@ -571,7 +571,7 @@ "always": "siempre", "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Deshabilitar intercambio", - "advanced_privacy_settings": "Configuración avanzada de privacidad", + "advanced_settings": "Ajustes avanzados", "settings_can_be_changed_later": "Estas configuraciones se pueden cambiar más tarde en la configuración de la aplicación", "add_custom_node": "Agregar nuevo nodo personalizado", "disable_fiat": "Deshabilitar fiat", @@ -749,5 +749,5 @@ "seedtype_polyseed": "Polieta (16 palabras)", "seed_language_czech": "checo", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Chino tradicional)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chino (tradicional)" +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 88263ec05..0b2f54c6c 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -160,7 +160,7 @@ "seed_title": "Phrase secrète (seed)", "seed_share": "Partager la phrase secrète (seed)", "copy": "Copier", - "seed_language_choose": "Merci de choisir la langue de la phrase secrète (seed) :", + "seed_language": "Langage de la phrase secrète", "seed_choose": "Choisissez la langue de la phrase secrète (seed)", "seed_language_next": "Suivant", "seed_language_english": "Anglais", @@ -571,7 +571,7 @@ "always": "toujours", "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Désactiver l'échange", - "advanced_privacy_settings": "Paramètres de confidentialité avancés", + "advanced_settings": "Réglages avancés", "settings_can_be_changed_later": "Ces paramètres peuvent être modifiés ultérieurement dans les paramètres de l'application", "add_custom_node": "Ajouter un nouveau nœud personnalisé", "disable_fiat": "Désactiver les montants en fiat", @@ -741,16 +741,13 @@ "seed_phrase_length": "Longueur de la phrase de départ", "unavailable_balance": "Solde indisponible", "unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.", - "camera_permission_is_required": "L'autorisation d'accès à la caméra est requise.\nVeuillez l'activer depuis les paramètres de l'application.", - "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer", "unspent_change": "Changement", "tor_connection": "Connexion Tor", "seed_hex_form": "Graine du portefeuille (forme hexagonale)", - "camera_permission_is_required": "L'autorisation de la caméra est requise.\nVeuillez l'activer à partir des paramètres de l'application.", "seedtype": "Type de type graine", "seedtype_legacy": "Héritage (25 mots)", "seedtype_polyseed": "Polyseed (16 mots)", "seed_language_czech": "tchèque", "seed_language_korean": "coréen", "seed_language_chinese_traditional": "Chinois (Traditionnel)" -} \ No newline at end of file +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 39b98b51a..107599684 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -160,7 +160,7 @@ "seed_title": "iri", "seed_share": "Raba iri", "copy": "Kwafi", - "seed_language_choose": "Don Allah zaɓi harshen seed:", + "seed_language": "Harshen Magani", "seed_choose": "Zaɓi harshen seed", "seed_language_next": "Na gaba", "seed_language_english": "Ingilishi", @@ -570,7 +570,7 @@ "always": "Koyaushe", "minutes_to_pin_code": "${minute} minti", "disable_exchange": "Kashe musanya", - "advanced_privacy_settings": "Babban Saitunan Sirri", + "advanced_settings": "Saitunan ci gaba", "settings_can_be_changed_later": "Ana iya canza waɗannan saitunan daga baya a cikin saitunan app", "add_custom_node": "Ƙara Sabon Kulli na Custom", "disable_fiat": "Dakatar da fiat", @@ -728,4 +728,4 @@ "seed_language_czech": "Czech", "seed_language_korean": "Yaren Koriya", "seed_language_chinese_traditional": "Sinanci (na gargajiya)" -} \ No newline at end of file +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b050ef43b..20cf5dd40 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -160,7 +160,7 @@ "seed_title": "बीज", "seed_share": "बीज साझा करें", "copy": "प्रतिलिपि", - "seed_language_choose": "कृपया बीज भाषा चुनें:", + "seed_language": "बीज", "seed_choose": "बीज भाषा चुनें", "seed_language_next": "आगामी", "seed_language_english": "अंग्रेज़ी", @@ -571,7 +571,7 @@ "always": "हमेशा", "minutes_to_pin_code": "${minute} मिनट", "disable_exchange": "एक्सचेंज अक्षम करें", - "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", + "advanced_settings": "एडवांस सेटिंग", "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है", "add_custom_node": "नया कस्टम नोड जोड़ें", "disable_fiat": "िएट को अक्षम करें", @@ -750,4 +750,4 @@ "seed_language_czech": "चेक", "seed_language_korean": "कोरियाई", "seed_language_chinese_traditional": "चीनी पारंपरिक)" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3c0f895e7..156d885ea 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -160,7 +160,7 @@ "seed_title": "Prisupni izraz", "seed_share": "Podijeli pristupni izraz", "copy": "Kopiraj", - "seed_language_choose": "Molimo odaberite jezik pristupnog izraza:", + "seed_language": "Sjemeni jezik", "seed_choose": "Odaberi jezik pristupnog izraza", "seed_language_next": "Dalje", "seed_language_english": "Engleski", @@ -571,7 +571,7 @@ "always": "Uvijek", "minutes_to_pin_code": "${minute} minuta", "disable_exchange": "Onemogući exchange", - "advanced_privacy_settings": "Napredne postavke privatnosti", + "advanced_settings": "Napredne postavke", "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije", "add_custom_node": "Dodaj novi prilagođeni čvor", "disable_fiat": "Isključi, fiat", @@ -747,5 +747,5 @@ "seedtype_polyseed": "Poliseed (16 riječi)", "seed_language_czech": "češki", "seed_language_korean": "korejski", - "seed_language_chinese_traditional": "Kinesko tradicionalno)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Kinesko (tradicionalno)" +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b29572746..c6836b65b 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -160,7 +160,7 @@ "seed_title": "Bibit", "seed_share": "Bagikan bibit", "copy": "Salin", - "seed_language_choose": "Silakan pilih bahasa bibit:", + "seed_language": "Bahasa benih", "seed_choose": "Pilih bahasa bibit", "seed_language_next": "Selanjutnya", "seed_language_english": "Inggris", @@ -570,7 +570,7 @@ "always": "Selalu", "minutes_to_pin_code": "${minute} menit", "disable_exchange": "Nonaktifkan pertukaran", - "advanced_privacy_settings": "Pengaturan Privasi Lanjutan", + "advanced_settings": "Pengaturan lanjutan", "settings_can_be_changed_later": "Pengaturan ini dapat diubah nanti di pengaturan aplikasi", "add_custom_node": "Tambahkan Node Kustom Baru", "disable_fiat": "Nonaktifkan fiat", @@ -737,5 +737,5 @@ "seedtype_polyseed": "Polyseed (16 kata)", "seed_language_czech": "Ceko", "seed_language_korean": "Korea", - "seed_language_chinese_traditional": "Cina tradisional)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Cina (tradisional)" +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 19912d277..1d6d355b6 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -160,7 +160,7 @@ "seed_title": "Seme", "seed_share": "Condividi seme", "copy": "Copia", - "seed_language_choose": "Gentilmente scegli la lingua del seme:", + "seed_language": "Linguaggio di semi", "seed_choose": "Scegli la lingua del seme", "seed_language_next": "Prossimo", "seed_language_english": "Inglese", @@ -571,7 +571,7 @@ "always": "sempre", "minutes_to_pin_code": "${minute} minuti", "disable_exchange": "Disabilita scambio", - "advanced_privacy_settings": "Impostazioni avanzate sulla privacy", + "advanced_settings": "Impostazioni avanzate", "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app", "add_custom_node": "Aggiungi nuovo nodo personalizzato", "disable_fiat": "Disabilita fiat", @@ -750,4 +750,4 @@ "seed_language_czech": "ceco", "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Cinese tradizionale)" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ea138b317..595a348d3 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -160,7 +160,7 @@ "seed_title": "シード", "seed_share": "シードを共有する", "copy": "コピー", - "seed_language_choose": "シード言語を選択してください:", + "seed_language": "シード言語", "seed_choose": "シード言語を選択してください", "seed_language_next": "次", "seed_language_english": "英語", @@ -571,7 +571,7 @@ "always": "いつも", "minutes_to_pin_code": "${minute} 分", "disable_exchange": "交換を無効にする", - "advanced_privacy_settings": "高度なプライバシー設定", + "advanced_settings": "高度な設定", "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます", "add_custom_node": "新しいカスタム ノードを追加", "disable_fiat": "フィアットを無効にする", @@ -750,4 +750,4 @@ "seed_language_czech": "チェコ", "seed_language_korean": "韓国語", "seed_language_chinese_traditional": "中国の伝統的な)" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 4df43018d..0b0fff282 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -160,7 +160,7 @@ "seed_title": "씨", "seed_share": "시드 공유", "copy": "부", - "seed_language_choose": "종자 언어를 선택하십시오:", + "seed_language": "종자 언어", "seed_choose": "시드 언어를 선택하십시오", "seed_language_next": "다음 것", "seed_language_english": "영어", @@ -571,7 +571,7 @@ "always": "언제나", "minutes_to_pin_code": "${minute}분", "disable_exchange": "교환 비활성화", - "advanced_privacy_settings": "고급 개인 정보 설정", + "advanced_settings": "고급 설정", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", "add_custom_node": "새 사용자 정의 노드 추가", "disable_fiat": "법정화폐 비활성화", @@ -748,4 +748,4 @@ "seed_language_czech": "체코 사람", "seed_language_korean": "한국인", "seed_language_chinese_traditional": "중국 전통)" -} \ No newline at end of file +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index d82e30116..fea305f2d 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -160,7 +160,7 @@ "seed_title": "မျိုးစေ့", "seed_share": "မျိုးစေ့မျှဝေပါ။", "copy": "ကော်ပီ", - "seed_language_choose": "ကျေးဇူးပြု၍ မျိုးစေ့ဘာသာစကားကို ရွေးပါ-", + "seed_language": "မျိုးစေ့ဘာသာ", "seed_choose": "မျိုးစေ့ဘာသာစကားကို ရွေးချယ်ပါ။", "seed_language_next": "နောက်တစ်ခု", "seed_language_english": "အင်္ဂလိပ်စာ", @@ -569,7 +569,7 @@ "always": "အမြဲတမ်း", "minutes_to_pin_code": "${minute} မိနစ်", "disable_exchange": "လဲလှယ်မှုကို ပိတ်ပါ။", - "advanced_privacy_settings": "အဆင့်မြင့် ကိုယ်ရေးကိုယ်တာ ဆက်တင်များ", + "advanced_settings": "အဆင့်မြင့်ချိန်ညှိချက်များ", "settings_can_be_changed_later": "အက်ပ်ဆက်တင်များတွင် ဤဆက်တင်များကို နောက်ပိုင်းတွင် ပြောင်းလဲနိုင်သည်။", "add_custom_node": "စိတ်ကြိုက် Node အသစ်ကို ထည့်ပါ။", "disable_fiat": "Fiat ကိုပိတ်ပါ။", @@ -748,4 +748,4 @@ "seed_language_czech": "ချက်", "seed_language_korean": "ကိုးရီးယား", "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)" -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 8a290618d..e400de4c6 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -160,7 +160,7 @@ "seed_title": "Zaad", "seed_share": "Deel zaad", "copy": "Kopiëren", - "seed_language_choose": "Kies een starttaal:", + "seed_language": "Zaadtaal", "seed_choose": "Kies een starttaal", "seed_language_next": "Volgende", "seed_language_english": "Engels", @@ -571,7 +571,7 @@ "always": "altijd", "minutes_to_pin_code": "${minute} minuten", "disable_exchange": "Uitwisseling uitschakelen", - "advanced_privacy_settings": "Geavanceerde privacy-instellingen", + "advanced_settings": "Geavanceerde instellingen", "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen", "add_custom_node": "Voeg een nieuw aangepast knooppunt toe", "disable_fiat": "Schakel Fiat uit", @@ -749,5 +749,5 @@ "seedtype_polyseed": "Polyseed (16 woorden)", "seed_language_czech": "Tsjechisch", "seed_language_korean": "Koreaans", - "seed_language_chinese_traditional": "Chinese traditionele)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chinese (traditionele)" +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b84ddc98c..698115f8b 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Udostępnij seed", "copy": "Kopiuj", - "seed_language_choose": "Proszę wybrać język słów we frazie seed:", + "seed_language": "Język nasion", "seed_choose": "Wybierz język", "seed_language_next": "Następny", "seed_language_english": "Angielski", @@ -571,7 +571,7 @@ "always": "zawsze", "minutes_to_pin_code": "${minute} minut", "disable_exchange": "Wyłącz wymianę", - "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", + "advanced_settings": "Zaawansowane ustawienia", "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji", "add_custom_node": "Dodaj nowy węzeł niestandardowy", "disable_fiat": "Wyłącz waluty FIAT", @@ -750,4 +750,4 @@ "seed_language_czech": "Czech", "seed_language_korean": "koreański", "seed_language_chinese_traditional": "Chiński tradycyjny)" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ed7b8a801..9a926834a 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -160,7 +160,7 @@ "seed_title": "Semente", "seed_share": "Compartilhar semente", "copy": "Copiar", - "seed_language_choose": "Por favor, escolha o idioma da semente:", + "seed_language": "Linguagem de semente", "seed_choose": "Escolha o idioma da semente", "seed_language_next": "Próximo", "seed_language_english": "Inglesa", @@ -570,7 +570,7 @@ "always": "sempre", "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Desativar troca", - "advanced_privacy_settings": "Configurações de privacidade avançadas", + "advanced_settings": "Configurações avançadas", "settings_can_be_changed_later": "Essas configurações podem ser alteradas posteriormente nas configurações do aplicativo", "add_custom_node": "Adicionar novo nó personalizado", "disable_fiat": "Desativar fiat", @@ -749,4 +749,4 @@ "seed_language_czech": "Tcheco", "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Chinês tradicional)" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index c7749d0ea..e2a09009d 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -160,7 +160,7 @@ "seed_title": "Мнемоническая фраза", "seed_share": "Поделиться мнемонической фразой", "copy": "Скопировать", - "seed_language_choose": "Пожалуйста, выберите язык мнемонической фразы:", + "seed_language": "Язык семян", "seed_choose": "Выберите язык мнемонической фразы", "seed_language_next": "Продолжить", "seed_language_english": "Английский", @@ -571,7 +571,7 @@ "always": "всегда", "minutes_to_pin_code": "${minute} минут", "disable_exchange": "Отключить обмен", - "advanced_privacy_settings": "Расширенные настройки конфиденциальности", + "advanced_settings": "Расширенные настройки", "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.", "add_custom_node": "Добавить новый пользовательский узел", "disable_fiat": "Отключить фиат", @@ -750,4 +750,4 @@ "seed_language_czech": "Чешский", "seed_language_korean": "Корейский", "seed_language_chinese_traditional": "Китайский традиционный)" -} \ No newline at end of file +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 791b0f005..a4d7f405e 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "แบ่งปัน seed", "copy": "คัดลอก", - "seed_language_choose": "โปรดเลือกภาษาของ seed:", + "seed_language": "ภาษาเมล็ด", "seed_choose": "เลือกภาษาของ seed", "seed_language_next": "ถัดไป", "seed_language_english": "อังกฤษ", @@ -569,7 +569,7 @@ "always": "เสมอ", "minutes_to_pin_code": "${minute} นาที", "disable_exchange": "ปิดใช้งานการแลกเปลี่ยน", - "advanced_privacy_settings": "การตั้งค่าความเป็นส่วนตัวขั้นสูง", + "advanced_settings": "ตั้งค่าขั้นสูง", "settings_can_be_changed_later": "การตั้งค่านี้สามารถเปลี่ยนแปลงได้ภายหลังในการตั้งค่าแอพฯ", "add_custom_node": "เพิ่มจุดโหนดแบบกำหนดเอง", "disable_fiat": "ปิดใช้งานสกุลเงินตรา", @@ -748,4 +748,4 @@ "seed_language_czech": "ภาษาเช็ก", "seed_language_korean": "เกาหลี", "seed_language_chinese_traditional": "จีน (ดั้งเดิม)" -} \ No newline at end of file +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 8891563b0..04ba2af6d 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -160,7 +160,7 @@ "seed_title": "Binhi", "seed_share": "Magbahagi ng binhi", "copy": "Kopya", - "seed_language_choose": "Mangyaring pumili ng wika ng binhi:", + "seed_language": "Wika ng binhi", "seed_choose": "Pumili ng wika ng binhi", "seed_language_next": "Susunod", "seed_language_english": "Ingles", @@ -572,7 +572,7 @@ "always": "Palagi", "minutes_to_pin_code": "${minute} minuto", "disable_exchange": "Huwag paganahin ang palitan", - "advanced_privacy_settings": "Mga setting ng advanced na privacy", + "advanced_settings": "Mga Advanced na Setting", "settings_can_be_changed_later": "Ang mga setting na ito ay maaaring mabago mamaya sa mga setting ng app", "add_custom_node": "Magdagdag ng bagong pasadyang node", "disable_fiat": "Huwag paganahin ang Fiat", @@ -744,4 +744,4 @@ "seed_language_czech": "Czech", "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Intsik (tradisyonal)" -} \ No newline at end of file +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index a5d6a330a..b7b5d33f8 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -160,7 +160,7 @@ "seed_title": "Tohum", "seed_share": "Tohumu paylaş", "copy": "Kopyala", - "seed_language_choose": "Lütfen tohum dilini seç:", + "seed_language": "Tohum dili", "seed_choose": "Tohum dilini seçin", "seed_language_next": "İleri", "seed_language_english": "İngilizce", @@ -569,7 +569,7 @@ "always": "Her Zaman", "minutes_to_pin_code": "${minute} dakika", "disable_exchange": "Borsayı devre dışı bırak", - "advanced_privacy_settings": "Gelişmiş Gizlilik Ayarları", + "advanced_settings": "Gelişmiş Ayarlar", "settings_can_be_changed_later": "Bu ayarlar daha sonra uygulama ayarlarından da değiştirilebilir", "add_custom_node": "Yeni Özel Düğüm Ekleme", "disable_fiat": "İtibari paraları devre dışı bırak", @@ -748,4 +748,4 @@ "seed_language_czech": "Çek", "seed_language_korean": "Koreli", "seed_language_chinese_traditional": "Çin geleneği)" -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 81bc3c8aa..900e95acc 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -160,7 +160,7 @@ "seed_title": "Мнемонічна фраза", "seed_share": "Поділитися мнемонічною фразою", "copy": "Скопіювати", - "seed_language_choose": "Будь ласка, виберіть мову мнемонічної фрази:", + "seed_language": "Насіннєва мова", "seed_choose": "Виберіть мову мнемонічної фрази", "seed_language_next": "Продовжити", "seed_language_english": "Англійська", @@ -571,7 +571,7 @@ "always": "Завжди", "minutes_to_pin_code": "${minute} хвилин", "disable_exchange": "Вимкнути exchange", - "advanced_privacy_settings": "Розширені налаштування конфіденційності", + "advanced_settings": "Розширені налаштування", "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми", "add_custom_node": "Додати новий спеціальний вузол", "disable_fiat": "Вимкнути фиат", @@ -749,5 +749,5 @@ "seedtype_polyseed": "Полісей (16 слів)", "seed_language_czech": "Чеський", "seed_language_korean": "Корейський", - "seed_language_chinese_traditional": "Китайський традиційний)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Китайський (традиційний)" +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 5b7f18ae7..5535bdf73 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -160,7 +160,7 @@ "seed_title": "بیج", "seed_share": "بیج بانٹیں۔", "copy": "کاپی", - "seed_language_choose": "براہ کرم بیج کی زبان کا انتخاب کریں:", + "seed_language": "بیج کی زبان", "seed_choose": "بیج کی زبان کا انتخاب کریں۔", "seed_language_next": "اگلے", "seed_language_english": "انگریزی", @@ -570,7 +570,7 @@ "always": "ہمیشہ", "minutes_to_pin_code": "${minute} منٹ", "disable_exchange": "تبادلے کو غیر فعال کریں۔", - "advanced_privacy_settings": "اعلی درجے کی رازداری کی ترتیبات", + "advanced_settings": "اعلی درجے کی ترتیبات", "settings_can_be_changed_later": "ان ترتیبات کو بعد میں ایپ کی ترتیبات میں تبدیل کیا جا سکتا ہے۔", "add_custom_node": "نیا کسٹم نوڈ شامل کریں۔", "disable_fiat": "فیاٹ کو غیر فعال کریں۔", @@ -742,4 +742,4 @@ "seed_language_czech": "چیک", "seed_language_korean": "کورین", "seed_language_chinese_traditional": "چینی (روایتی)" -} \ No newline at end of file +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 6821aa7f9..a867778f4 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -160,7 +160,7 @@ "seed_title": "Hóró", "seed_share": "Pín hóró", "copy": "Ṣẹ̀dà", - "seed_language_choose": "Ẹ jọ̀wọ́ yan èdè hóró:", + "seed_language": "Ewu ọmọ", "seed_choose": "Yan èdè hóró", "seed_language_next": "Tẹ̀síwájú", "seed_language_english": "Èdè Gẹ̀ẹ́sì", @@ -567,7 +567,7 @@ "always": "Ní gbogbo àwọn ìgbà", "minutes_to_pin_code": "${minute} ìṣẹ́jú", "disable_exchange": "Pa ilé pàṣípààrọ̀", - "advanced_privacy_settings": "Àwọn ààtò àdáni títóbi", + "advanced_settings": "Awọn eto ilọsiwaju", "settings_can_be_changed_later": "Ẹ lè pààrọ̀ àwọn ààtò yìí nínú ààtò áàpù t’ó bá yá", "add_custom_node": "Fikún apẹka títun t'ẹ́ pààrọ̀", "disable_fiat": "Pa owó tí ìjọba pàṣẹ wa lò", @@ -744,4 +744,4 @@ "seed_language_czech": "Czech", "seed_language_korean": "Ara ẹni", "seed_language_chinese_traditional": "Kannada (ibile)" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index b66d0e65e..7762af3a0 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -160,7 +160,7 @@ "seed_title": "种子", "seed_share": "分享种子", "copy": "复制", - "seed_language_choose": "请选择种子语言:", + "seed_language": "种子语言", "seed_choose": "选择种子语言", "seed_language_next": "下一个", "seed_language_english": "英文", @@ -570,7 +570,7 @@ "always": "总是", "minutes_to_pin_code": "${minute} 分钟", "disable_exchange": "禁用交换", - "advanced_privacy_settings": "高级隐私设置", + "advanced_settings": "高级设置", "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置", "add_custom_node": "添加新的自定义节点", "disable_fiat": "禁用法令", @@ -749,4 +749,4 @@ "seed_language_czech": "捷克", "seed_language_korean": "韩国人", "seed_language_chinese_traditional": "中国传统的)" -} \ No newline at end of file +} From 3760285a64405935fafe7aeeb051fd25d850585b Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Thu, 30 Nov 2023 16:39:09 -0500 Subject: [PATCH 10/18] ios/mac secure storage pin migration (potential fix) (#1205) * ios/mac secure storage pin migration * function call was incorrect * Update default_settings_migration.dart --------- Co-authored-by: Omar Hatem --- lib/entities/default_settings_migration.dart | 98 +++++++++++++------- lib/main.dart | 2 +- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index a65742e99..ec31b0539 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -1,5 +1,6 @@ import 'dart:io' show Directory, File, Platform; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; @@ -82,8 +83,7 @@ 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); @@ -175,6 +175,9 @@ Future defaultSettingsMigration( await changeBitcoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); break; + case 25: + await rewriteSecureStoragePin(secureStorage: secureStorage); + break; default: break; @@ -340,9 +343,9 @@ Node? getNanoDefaultPowNode({required Box nodes}) { } Node? getBitcoinCashDefaultElectrumServer({required Box nodes}) { - return nodes.values.firstWhereOrNull( - (Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri) - ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash); + return nodes.values + .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri) ?? + nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash); } Node getMoneroDefaultNode({required Box nodes}) { @@ -364,6 +367,37 @@ Node getMoneroDefaultNode({required Box nodes}) { } } +Future rewriteSecureStoragePin({required FlutterSecureStorage secureStorage}) async { + // the bug only affects ios/mac: + if (!Platform.isIOS && !Platform.isMacOS) { + return; + } + + // first, get the encoded pin: + final keyForPinCode = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + String? encodedPin; + try { + encodedPin = await secureStorage.read(key: keyForPinCode); + } catch (e) { + // either we don't have a pin, or we can't read it (maybe even because of the bug!) + // the only option here is to abort the migration or we risk losing the pin and locking the user out + return; + } + + if (encodedPin == null) { + return; + } + + // ensure we overwrite by deleting the old key first: + await secureStorage.delete(key: keyForPinCode); + await secureStorage.write( + key: keyForPinCode, + value: encodedPin, + iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), + mOptions: MacOsOptions(accessibility: KeychainAccessibility.first_unlock), + ); +} + Future changeBitcoinCurrentElectrumServerToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final server = getBitcoinDefaultElectrumServer(nodes: nodes); @@ -381,8 +415,7 @@ Future changeLitecoinCurrentElectrumServerToDefault( } Future changeBitcoinCashCurrentNodeToDefault( - {required SharedPreferences sharedPreferences, - required Box nodes}) async { + {required SharedPreferences sharedPreferences, required Box nodes}) async { final server = getBitcoinCashDefaultElectrumServer(nodes: nodes); final serverId = server?.key as int ?? 0; @@ -538,34 +571,30 @@ 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 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 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); - final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull( - (node) => node.key == currentBitcoinCashNodeId); + 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); @@ -617,8 +646,7 @@ Future checkCurrentNodes( if (currentBitcoinCashNodeServer == null) { final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash); await nodeSource.add(node); - await sharedPreferences.setInt( - PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); + await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); } } diff --git a/lib/main.dart b/lib/main.dart index 6807e9185..165db1ddd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -163,7 +163,7 @@ Future initializeAppConfigs() async { transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 23); + initialMigrationVersion: 25); } Future initialSetup( From 3b7f9a297cbe5aa4eb7ce9656af7685626e12618 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 2 Dec 2023 02:02:55 +0100 Subject: [PATCH 11/18] Bug missing polyseed (#1214) * Move file.dart * Add seedFallback for missing polyseeds * Remove unused dependency of cw_bitcoin * Minor fix MacOS * Add more blockheight-date matching * Scan only last 2 days for new Polyseed wallets --- .../lib/electrum_transaction_history.dart | 7 +++-- cw_bitcoin/lib/electrum_wallet.dart | 2 +- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 4 +-- cw_bitcoin/pubspec.yaml | 1 - cw_core/lib/get_height_by_date.dart | 5 ++- .../lib => cw_core/lib/utils}/file.dart | 13 ++------ cw_monero/ios/Classes/monero_api.cpp | 6 ++-- cw_monero/lib/monero_wallet.dart | 9 ++++-- cw_monero/lib/monero_wallet_service.dart | 31 ++++++++++++++++--- cw_monero/macos/Classes/monero_api.cpp | 6 ++-- 10 files changed, 52 insertions(+), 32 deletions(-) rename {cw_bitcoin/lib => cw_core/lib/utils}/file.dart (66%) diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index be039fa36..d478c3b12 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -1,10 +1,11 @@ import 'dart:convert'; + +import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/transaction_history.dart'; -import 'package:cw_bitcoin/file.dart'; -import 'package:cw_bitcoin/electrum_transaction_info.dart'; part 'electrum_transaction_history.g.dart'; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 903fa14cc..85053133f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -18,7 +18,6 @@ import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; -import 'package:cw_bitcoin/file.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; @@ -30,6 +29,7 @@ import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:flutter/foundation.dart'; diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index def991ebe..86d3e2fed 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/file.dart'; import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_type.dart'; class ElectrumWallletSnapshot { @@ -56,4 +56,4 @@ class ElectrumWallletSnapshot { regularAddressIndex: regularAddressIndex, changeAddressIndex: changeAddressIndex); } -} \ No newline at end of file +} diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 693d5af7a..e90f9cda4 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -30,7 +30,6 @@ dependencies: rxdart: ^0.27.5 unorm_dart: ^0.2.0 cryptography: ^2.0.5 - encrypt: ^5.0.1 dev_dependencies: flutter_test: diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index a680e6c25..6f3ccaf68 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -118,7 +118,10 @@ final dates = { "2023-6": 2898234, "2023-7": 2919771, "2023-8": 2942045, - "2023-9": 2964280 + "2023-9": 2964280, + "2023-10": 2985937, + "2023-11": 3008178, + "2023-12": 3029759 }; int getMoneroHeigthByDate({required DateTime date}) { diff --git a/cw_bitcoin/lib/file.dart b/cw_core/lib/utils/file.dart similarity index 66% rename from cw_bitcoin/lib/file.dart rename to cw_core/lib/utils/file.dart index 8fd236ec3..0b1c5cffd 100644 --- a/cw_bitcoin/lib/file.dart +++ b/cw_core/lib/utils/file.dart @@ -2,17 +2,8 @@ import 'dart:io'; import 'package:cw_core/key.dart'; import 'package:encrypt/encrypt.dart' as encrypt; -Future write( - {required String path, - required String password, - required String data}) async { - final keys = extractKeys(password); - final key = encrypt.Key.fromBase64(keys.first); - final iv = encrypt.IV.fromBase64(keys.last); - final encrypted = await encode(key: key, iv: iv, data: data); - final f = File(path); - f.writeAsStringSync(encrypted); -} +Future write({required String path, required String password, required String data}) async => + writeData(path: path, password: password, data: data); Future writeData( {required String path, diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 7ad873647..6162375b2 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -385,6 +385,9 @@ extern "C" (uint64_t)restoreHeight, std::string(spendKey)); + // Cache Raw to support Polyseed + wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); + int status; std::string errorString; @@ -396,9 +399,6 @@ extern "C" return false; } - // Cache Raw to support Polyseed - wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); - change_current_wallet(wallet); return true; } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 71c7e3967..924a987cd 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -97,7 +97,8 @@ abstract class MoneroWalletBase ObservableMap balance; @override - String get seed => monero_wallet.getSeed(); + String get seed => _seed; + String _seed = monero_wallet.getSeed(); @override MoneroWalletKeys get keys => MoneroWalletKeys( @@ -113,7 +114,11 @@ abstract class MoneroWalletBase Timer? _autoSaveTimer; List unspentCoins; - Future init() async { + Future init({String seedFallback = ""}) async { + if (_seed.isEmpty) { + _seed = seedFallback; + } + await walletAddresses.init(); balance = ObservableMap.of({ currency: MoneroBalance( diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 077ab4e54..c71d43e83 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cw_core/monero_wallet_utils.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -77,8 +78,12 @@ class MoneroWalletService extends WalletService< final polyseed = Polyseed.create(); final lang = PolyseedLang.getByEnglishName(credentials.language); + final heightOverride = + getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2))); + return _restoreFromPolyseed( - path, credentials.password!, polyseed, credentials.walletInfo!, lang); + path, credentials.password!, polyseed, credentials.walletInfo!, lang, + overrideHeight: heightOverride); } await monero_wallet_manager.createWallet( @@ -129,7 +134,12 @@ class MoneroWalletService extends WalletService< return openWallet(name, password); } - await wallet.init(); + if (wallet.seed.isEmpty) { + final seedFallback = await _getSeedBackup(path, password); + await wallet.init(seedFallback: seedFallback); + } else { + await wallet.init(); + } return wallet; } catch (e) { @@ -268,10 +278,11 @@ class MoneroWalletService extends WalletService< Future _restoreFromPolyseed(String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, - {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO}) async { - final height = getMoneroHeigthByDate( + {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async { + final height = overrideHeight ?? getMoneroHeigthByDate( date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); final spendKey = keyToHexString(polyseed.generateKey(coin, 32)); + final seed = polyseed.encode(lang, coin); walletInfo.isRecovery = true; walletInfo.restoreHeight = height; @@ -279,10 +290,13 @@ class MoneroWalletService extends WalletService< await monero_wallet_manager.restoreFromSpendKey( path: path, password: password, - seed: polyseed.encode(lang, coin), + seed: seed, language: lang.nameEnglish, restoreHeight: height, spendKey: spendKey); + + await writeData(path: "$path.seed", password: password, data: seed); + final wallet = MoneroWallet( walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); @@ -290,6 +304,13 @@ class MoneroWalletService extends WalletService< return wallet; } + Future _getSeedBackup(String path, String password) async { + final seedFilePath = "$path.seed"; + final seedFile = File(seedFilePath); + if (!seedFile.existsSync()) return ""; + return read(path: seedFilePath, password: password); + } + Future repairOldAndroidWallet(String name) async { try { if (!Platform.isAndroid) { diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp index a5ca13822..6fabc29fd 100644 --- a/cw_monero/macos/Classes/monero_api.cpp +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -385,6 +385,9 @@ extern "C" (uint64_t)restoreHeight, std::string(spendKey)); + // Cache Raw to support Polyseed + wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); + int status; std::string errorString; @@ -396,9 +399,6 @@ extern "C" return false; } - // Cache Raw to support Polyseed - wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); - change_current_wallet(wallet); return true; } From b3d579c24aa81b9e34a3a2710edec6618114c7be Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 2 Dec 2023 03:26:43 +0100 Subject: [PATCH 12/18] CW-527-Add-Polygon-MATIC-Wallet (#1179) * chore: Initial setup for polygon package * feat: Add polygon node urls * feat: Add Polygon(MATIC) wallet WIP * feat: Add Polygon(MATIC) wallet WIP * feat: Add Polygon MATIC wallet [skip ci] * fix: Issue with create/restore wallet for polygon * feat: Add erc20 tokens for polygon * feat: Adding Polygon MATIC Wallet * fix: Add build command for polygon to workflow file to fix failing action * fix: Switch evm to not display additional balance * chore: Sync with remote * fix: Revert change to inject app script * feat: Add polygon erc20 tokens * feat: Increase migration version * fix: Restore from QR address validator fix * fix: Adjust wallet connect connection flow to adapt to wallet type * fix: Make wallet fetch nfts based on the current wallet type * fix: Make wallet fetch nfts based on the current wallet type * fix: Try fetching transactions with moralis * fix: Requested review changes * fix: Error creating new wallet * fix: Revert script * fix: Exclude spam NFTs from nft listing API response * Update default_erc20_tokens.dart * replace matic with matic poly * Add polygon wallet scheme to app links * style: reformat default_settings_migration.dart * minor enhancement * fix using different wallet function for setting the transaction priorities * fix: Add chain to calls * Add USDC.e to initial coins * Add other default polygon node * Use Polygon scan some UI fixes * Add polygon scan api key to secrets generation code --------- Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build.yml | 2 + .gitignore | 1 + android/app/src/main/AndroidManifestBase.xml | 3 + assets/polygon_node_list.yml | 6 + cw_core/lib/currency_for_wallet_type.dart | 2 + cw_core/lib/erc20_token.dart | 1 + cw_core/lib/node.dart | 4 + cw_core/lib/wallet_type.dart | 16 +- cw_ethereum/lib/ethereum_client.dart | 32 +- .../lib/ethereum_transaction_info.dart | 13 +- .../lib/ethereum_transaction_model.dart | 1 + .../lib/pending_ethereum_transaction.dart | 8 +- cw_polygon/.gitignore | 30 + cw_polygon/.metadata | 10 + cw_polygon/CHANGELOG.md | 3 + cw_polygon/LICENSE | 1 + cw_polygon/README.md | 39 ++ cw_polygon/analysis_options.yaml | 4 + cw_polygon/lib/cw_polygon.dart | 7 + cw_polygon/lib/default_erc20_tokens.dart | 86 +++ .../lib/pending_polygon_transaction.dart | 19 + cw_polygon/lib/polygon_client.dart | 34 ++ cw_polygon/lib/polygon_exceptions.dart | 6 + cw_polygon/lib/polygon_formatter.dart | 25 + .../lib/polygon_mnemonics_exception.dart | 5 + .../lib/polygon_transaction_credentials.dart | 18 + .../lib/polygon_transaction_history.dart | 77 +++ cw_polygon/lib/polygon_transaction_info.dart | 49 ++ cw_polygon/lib/polygon_transaction_model.dart | 49 ++ .../lib/polygon_transaction_priority.dart | 51 ++ cw_polygon/lib/polygon_wallet.dart | 540 ++++++++++++++++++ cw_polygon/lib/polygon_wallet_addresses.dart | 5 + .../polygon_wallet_creation_credentials.dart | 28 + cw_polygon/lib/polygon_wallet_service.dart | 123 ++++ cw_polygon/pubspec.yaml | 73 +++ cw_polygon/test/cw_polygon_test.dart | 12 + ios/Runner/InfoBase.plist | 20 + lib/core/address_validator.dart | 2 + lib/core/fiat_conversion_service.dart | 2 +- lib/core/seed_validator.dart | 3 + .../wallet_connect/evm_chain_service.dart | 28 +- .../wallet_connect_key_service.dart | 28 +- .../wallet_connect/web3wallet_service.dart | 10 +- lib/di.dart | 6 +- lib/entities/default_settings_migration.dart | 39 ++ lib/entities/ens_record.dart | 6 + lib/entities/main_actions.dart | 5 +- lib/entities/node_list.dart | 21 +- lib/entities/preferences_key.dart | 3 + lib/entities/priority_for_wallet_type.dart | 3 + lib/polygon/cw_polygon.dart | 156 +++++ lib/reactions/fiat_rate_update.dart | 14 +- lib/reactions/on_current_wallet_change.dart | 12 +- lib/reactions/wallet_connect.dart | 46 ++ .../desktop_wallet_selection_dropdown.dart | 3 + .../screens/dashboard/pages/balance_page.dart | 10 +- .../dashboard/widgets/menu_widget.dart | 7 +- .../receive/widgets/currency_input_field.dart | 12 +- lib/src/screens/root/root.dart | 8 +- lib/src/screens/seed/pre_seed_page.dart | 18 +- .../settings/connection_sync_page.dart | 5 +- lib/src/screens/settings/privacy_page.dart | 8 + .../widgets/connection_request_widget.dart | 26 +- .../screens/wallet_list/wallet_list_page.dart | 3 + lib/store/app_store.dart | 4 +- lib/store/settings_store.dart | 174 ++++-- .../dashboard/balance_view_model.dart | 8 +- .../dashboard/home_settings_view_model.dart | 63 +- lib/view_model/dashboard/nft_view_model.dart | 14 +- .../dashboard/transaction_list_item.dart | 8 + .../exchange/exchange_trade_view_model.dart | 20 +- .../exchange/exchange_view_model.dart | 10 + .../node_list/node_list_view_model.dart | 3 + .../restore/restore_from_qr_vm.dart | 7 + .../restore/wallet_restore_from_qr_code.dart | 12 +- lib/view_model/send/output.dart | 26 +- .../send/send_template_view_model.dart | 4 +- lib/view_model/send/send_view_model.dart | 17 +- .../settings/privacy_settings_view_model.dart | 15 +- .../transaction_details_view_model.dart | 27 +- .../wallet_address_list_view_model.dart | 27 + lib/view_model/wallet_keys_view_model.dart | 8 +- lib/view_model/wallet_new_vm.dart | 4 + lib/view_model/wallet_restore_view_model.dart | 19 +- macos/Podfile.lock | 7 +- model_generator.sh | 1 + pubspec_base.yaml | 1 + res/values/strings_ar.arb | 3 +- res/values/strings_bg.arb | 3 +- res/values/strings_cs.arb | 3 +- res/values/strings_de.arb | 3 +- res/values/strings_en.arb | 3 +- res/values/strings_es.arb | 3 +- res/values/strings_fr.arb | 3 +- res/values/strings_ha.arb | 3 +- res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 3 +- res/values/strings_id.arb | 3 +- res/values/strings_it.arb | 3 +- res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_my.arb | 3 +- res/values/strings_nl.arb | 3 +- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 3 +- res/values/strings_ru.arb | 3 +- res/values/strings_th.arb | 3 +- res/values/strings_tl.arb | 3 +- res/values/strings_tr.arb | 3 +- res/values/strings_uk.arb | 3 +- res/values/strings_ur.arb | 3 +- res/values/strings_yo.arb | 3 +- res/values/strings_zh.arb | 3 +- scripts/android/pubspec_gen.sh | 2 +- tool/configure.dart | 110 +++- tool/utils/secret_key.dart | 1 + 116 files changed, 2351 insertions(+), 206 deletions(-) create mode 100644 assets/polygon_node_list.yml create mode 100644 cw_polygon/.gitignore create mode 100644 cw_polygon/.metadata create mode 100644 cw_polygon/CHANGELOG.md create mode 100644 cw_polygon/LICENSE create mode 100644 cw_polygon/README.md create mode 100644 cw_polygon/analysis_options.yaml create mode 100644 cw_polygon/lib/cw_polygon.dart create mode 100644 cw_polygon/lib/default_erc20_tokens.dart create mode 100644 cw_polygon/lib/pending_polygon_transaction.dart create mode 100644 cw_polygon/lib/polygon_client.dart create mode 100644 cw_polygon/lib/polygon_exceptions.dart create mode 100644 cw_polygon/lib/polygon_formatter.dart create mode 100644 cw_polygon/lib/polygon_mnemonics_exception.dart create mode 100644 cw_polygon/lib/polygon_transaction_credentials.dart create mode 100644 cw_polygon/lib/polygon_transaction_history.dart create mode 100644 cw_polygon/lib/polygon_transaction_info.dart create mode 100644 cw_polygon/lib/polygon_transaction_model.dart create mode 100644 cw_polygon/lib/polygon_transaction_priority.dart create mode 100644 cw_polygon/lib/polygon_wallet.dart create mode 100644 cw_polygon/lib/polygon_wallet_addresses.dart create mode 100644 cw_polygon/lib/polygon_wallet_creation_credentials.dart create mode 100644 cw_polygon/lib/polygon_wallet_service.dart create mode 100644 cw_polygon/pubspec.yaml create mode 100644 cw_polygon/test/cw_polygon_test.dart create mode 100644 lib/polygon/cw_polygon.dart create mode 100644 lib/reactions/wallet_connect.dart diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 7b2b611d3..8fef4fdb6 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -97,6 +97,7 @@ jobs: 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 .. + cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs - name: Add secrets @@ -131,6 +132,7 @@ jobs: echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart 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 polygonScanApiKey = '${{ secrets.POLYGON_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 diff --git a/.gitignore b/.gitignore index c735d4058..0a883dd18 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ lib/haven/haven.dart lib/ethereum/ethereum.dart lib/bitcoin_cash/bitcoin_cash.dart lib/nano/nano.dart +lib/polygon/polygon.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 910149f60..f32482e22 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -62,6 +62,9 @@ + + + (other is Erc20Token && other.contractAddress == contractAddress) || diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 484325f91..cc749f23a 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable { } else { return Uri.http(uriRaw, ''); } + case WalletType.polygon: + return Uri.https(uriRaw, ''); default: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -146,6 +148,8 @@ class Node extends HiveObject with Keyable { case WalletType.nano: case WalletType.banano: return requestNanoNode(); + case WalletType.polygon: + return requestElectrumServer(); default: return false; } diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index debf92e11..20f0bdb19 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -13,6 +13,7 @@ const walletTypes = [ WalletType.bitcoinCash, WalletType.nano, WalletType.banano, + WalletType.polygon, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -44,6 +45,8 @@ enum WalletType { @HiveField(8) bitcoinCash, + @HiveField(9) + polygon } int serializeToInt(WalletType type) { @@ -64,6 +67,8 @@ int serializeToInt(WalletType type) { return 6; case WalletType.bitcoinCash: return 7; + case WalletType.polygon: + return 8; default: return -1; } @@ -87,6 +92,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.banano; case 7: return WalletType.bitcoinCash; + case 8: + return WalletType.polygon; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -110,6 +117,8 @@ String walletTypeToString(WalletType type) { return 'Nano'; case WalletType.banano: return 'Banano'; + case WalletType.polygon: + return 'Polygon'; default: return ''; } @@ -133,6 +142,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Nano (XNO)'; case WalletType.banano: return 'Banano (BAN)'; + case WalletType.polygon: + return 'Polygon (MATIC)'; default: return ''; } @@ -156,7 +167,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.nano; case WalletType.banano: return CryptoCurrency.banano; + case WalletType.polygon: + return CryptoCurrency.maticpoly; default: - throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); + throw Exception( + 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } } diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index f0c7381e8..5e408bb9e 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -15,12 +15,12 @@ import 'package:cw_ethereum/ethereum_transaction_priority.dart'; import 'package:cw_ethereum/.secrets.g.dart' as secrets; class EthereumClient { - final _httpClient = Client(); + final httpClient = Client(); Web3Client? _client; bool connect(Node node) { try { - _client = Web3Client(node.uri.toString(), _httpClient); + _client = Web3Client(node.uri.toString(), httpClient); return true; } catch (e) { @@ -74,9 +74,11 @@ class EthereumClient { required int exponent, String? contractAddress, }) async { - assert(currency == CryptoCurrency.eth || contractAddress != null); + assert(currency == CryptoCurrency.eth || + currency == CryptoCurrency.maticpoly || + contractAddress != null); - bool _isEthereum = currency == CryptoCurrency.eth; + bool _isEVMCompatibleChain = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly; final price = _client!.getGasPrice(); @@ -84,19 +86,23 @@ class EthereumClient { from: privateKey.address, to: EthereumAddress.fromHex(toAddress), maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), - value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), + value: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), ); - final signedTransaction = await _client!.signTransaction(privateKey, transaction); + final chainId = _getChainIdForCurrency(currency); + + final signedTransaction = + await _client!.signTransaction(privateKey, transaction, chainId: chainId); final Function _sendTransaction; - if (_isEthereum) { + if (_isEVMCompatibleChain) { _sendTransaction = () async => await sendTransaction(signedTransaction); } else { final erc20 = ERC20( client: _client!, address: EthereumAddress.fromHex(contractAddress!), + chainId: chainId, ); _sendTransaction = () async { @@ -118,6 +124,16 @@ class EthereumClient { ); } + int _getChainIdForCurrency(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.maticpoly: + return 137; + case CryptoCurrency.eth: + default: + return 1; + } + } + Future sendTransaction(Uint8List signedTransaction) async => await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction)); @@ -198,7 +214,7 @@ I/flutter ( 4474): Gas Used: 53000 Future> fetchTransactions(String address, {String? contractAddress}) async { try { - final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", { + final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", { "module": "account", "action": contractAddress != null ? "tokentx" : "txlist", if (contractAddress != null) "contractaddress": contractAddress, diff --git a/cw_ethereum/lib/ethereum_transaction_info.dart b/cw_ethereum/lib/ethereum_transaction_info.dart index a0649ba25..f0deae931 100644 --- a/cw_ethereum/lib/ethereum_transaction_info.dart +++ b/cw_ethereum/lib/ethereum_transaction_info.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -34,8 +36,10 @@ class EthereumTransactionInfo extends TransactionInfo { final String? to; @override - String amountFormatted() => - '${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol'; + String amountFormatted() { + final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString()); + return '${amount.substring(0, min(10, amount.length))} $tokenSymbol'; + } @override String fiatAmount() => _fiatAmount ?? ''; @@ -44,7 +48,10 @@ class EthereumTransactionInfo extends TransactionInfo { void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); @override - String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH'; + String feeFormatted() { + final amount = (ethFee / BigInt.from(10).pow(18)).toString(); + return '${amount.substring(0, min(10, amount.length))} ETH'; + } factory EthereumTransactionInfo.fromJson(Map data) { return EthereumTransactionInfo( diff --git a/cw_ethereum/lib/ethereum_transaction_model.dart b/cw_ethereum/lib/ethereum_transaction_model.dart index c1260795a..3b5f724fc 100644 --- a/cw_ethereum/lib/ethereum_transaction_model.dart +++ b/cw_ethereum/lib/ethereum_transaction_model.dart @@ -1,3 +1,4 @@ +//! Model used for in parsing transactions fetched using etherscan class EthereumTransactionModel { final DateTime date; final String hash; diff --git a/cw_ethereum/lib/pending_ethereum_transaction.dart b/cw_ethereum/lib/pending_ethereum_transaction.dart index 35b0123cc..d47630fd6 100644 --- a/cw_ethereum/lib/pending_ethereum_transaction.dart +++ b/cw_ethereum/lib/pending_ethereum_transaction.dart @@ -21,8 +21,8 @@ class PendingEthereumTransaction with PendingTransaction { @override String get amountFormatted { - final _amount = BigInt.parse(amount) / BigInt.from(pow(10, exponent)); - return _amount.toStringAsFixed(min(15, _amount.toString().length)); + final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString(); + return _amount.substring(0, min(10, _amount.length)); } @override @@ -30,8 +30,8 @@ class PendingEthereumTransaction with PendingTransaction { @override String get feeFormatted { - final _fee = fee / BigInt.from(pow(10, 18)); - return _fee.toStringAsFixed(min(15, _fee.toString().length)); + final _fee = (fee / BigInt.from(pow(10, 18))).toString(); + return _fee.substring(0, min(10, _fee.length)); } @override diff --git a/cw_polygon/.gitignore b/cw_polygon/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/cw_polygon/.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_polygon/.metadata b/cw_polygon/.metadata new file mode 100644 index 000000000..fa347fc6a --- /dev/null +++ b/cw_polygon/.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: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: package diff --git a/cw_polygon/CHANGELOG.md b/cw_polygon/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_polygon/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_polygon/LICENSE b/cw_polygon/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_polygon/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_polygon/README.md b/cw_polygon/README.md new file mode 100644 index 000000000..02fe8ecab --- /dev/null +++ b/cw_polygon/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_polygon/analysis_options.yaml b/cw_polygon/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_polygon/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_polygon/lib/cw_polygon.dart b/cw_polygon/lib/cw_polygon.dart new file mode 100644 index 000000000..5d4e447d1 --- /dev/null +++ b/cw_polygon/lib/cw_polygon.dart @@ -0,0 +1,7 @@ +library cw_polygon; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_polygon/lib/default_erc20_tokens.dart b/cw_polygon/lib/default_erc20_tokens.dart new file mode 100644 index 000000000..132c52e4c --- /dev/null +++ b/cw_polygon/lib/default_erc20_tokens.dart @@ -0,0 +1,86 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; + +class DefaultPolygonErc20Tokens { + final List _defaultTokens = [ + Erc20Token( + name: "Wrapped Ether", + symbol: "WETH", + contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Tether USD (PoS)", + symbol: "USDT", + contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "USD Coin", + symbol: "USDC", + contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "USD Coin (POS)", + symbol: "USDC.e", + contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + decimal: 6, + enabled: false, + ), + Erc20Token( + name: "Avalanche Token", + symbol: "AVAX", + contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Wrapped BTC (PoS)", + symbol: "WBTC", + contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + decimal: 8, + enabled: false, + ), + Erc20Token( + name: "Dai (PoS)", + symbol: "DAI", + contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + decimal: 18, + enabled: true, + ), + Erc20Token( + name: "SHIBA INU (PoS)", + symbol: "SHIB", + contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Uniswap (PoS)", + symbol: "UNI", + contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f", + decimal: 18, + enabled: false, + ), + ]; + + List get initialPolygonErc20Tokens => _defaultTokens.map((token) { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => + element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + + if (iconPath != null) { + return Erc20Token.copyWith(token, iconPath); + } + + return token; + }).toList(); +} diff --git a/cw_polygon/lib/pending_polygon_transaction.dart b/cw_polygon/lib/pending_polygon_transaction.dart new file mode 100644 index 000000000..50f1f0638 --- /dev/null +++ b/cw_polygon/lib/pending_polygon_transaction.dart @@ -0,0 +1,19 @@ +import 'dart:typed_data'; + +import 'package:cw_ethereum/pending_ethereum_transaction.dart'; + +class PendingPolygonTransaction extends PendingEthereumTransaction { + PendingPolygonTransaction({ + required Function sendTransaction, + required Uint8List signedTransaction, + required BigInt fee, + required String amount, + required int exponent, + }) : super( + amount: amount, + sendTransaction: sendTransaction, + signedTransaction: signedTransaction, + fee: fee, + exponent: exponent, + ); +} diff --git a/cw_polygon/lib/polygon_client.dart b/cw_polygon/lib/polygon_client.dart new file mode 100644 index 000000000..86c2253af --- /dev/null +++ b/cw_polygon/lib/polygon_client.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:cw_ethereum/ethereum_client.dart'; +import 'package:cw_polygon/polygon_transaction_model.dart'; +import 'package:cw_ethereum/.secrets.g.dart' as secrets; + +class PolygonClient extends EthereumClient { + @override + Future> fetchTransactions(String address, + {String? contractAddress}) async { + try { + final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", { + "module": "account", + "action": contractAddress != null ? "tokentx" : "txlist", + if (contractAddress != null) "contractaddress": contractAddress, + "address": address, + "apikey": secrets.polygonScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => PolygonTransactionModel.fromJson(e as Map)) + .toList(); + } + + return []; + } catch (e) { + print(e); + return []; + } + } +} diff --git a/cw_polygon/lib/polygon_exceptions.dart b/cw_polygon/lib/polygon_exceptions.dart new file mode 100644 index 000000000..2d08106b6 --- /dev/null +++ b/cw_polygon/lib/polygon_exceptions.dart @@ -0,0 +1,6 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_ethereum/ethereum_exceptions.dart'; + +class PolygonTransactionCreationException extends EthereumTransactionCreationException { + PolygonTransactionCreationException(CryptoCurrency currency) : super(currency); +} diff --git a/cw_polygon/lib/polygon_formatter.dart b/cw_polygon/lib/polygon_formatter.dart new file mode 100644 index 000000000..f016db7ab --- /dev/null +++ b/cw_polygon/lib/polygon_formatter.dart @@ -0,0 +1,25 @@ +import 'package:intl/intl.dart'; + +const polygonAmountLength = 12; +const polygonAmountDivider = 1000000000000; +final polygonAmountFormat = NumberFormat() + ..maximumFractionDigits = polygonAmountLength + ..minimumFractionDigits = 1; + +class PolygonFormatter { + static int parsePolygonAmount(String amount) { + try { + return (double.parse(amount) * polygonAmountDivider).round(); + } catch (_) { + return 0; + } + } + + static double parsePolygonAmountToDouble(int amount) { + try { + return amount / polygonAmountDivider; + } catch (_) { + return 0; + } + } +} diff --git a/cw_polygon/lib/polygon_mnemonics_exception.dart b/cw_polygon/lib/polygon_mnemonics_exception.dart new file mode 100644 index 000000000..c1a2fcc84 --- /dev/null +++ b/cw_polygon/lib/polygon_mnemonics_exception.dart @@ -0,0 +1,5 @@ +class PolygonMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_polygon/lib/polygon_transaction_credentials.dart b/cw_polygon/lib/polygon_transaction_credentials.dart new file mode 100644 index 000000000..6611e15da --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_credentials.dart @@ -0,0 +1,18 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_ethereum/ethereum_transaction_credentials.dart'; +import 'package:cw_polygon/polygon_transaction_priority.dart'; + +class PolygonTransactionCredentials extends EthereumTransactionCredentials { + PolygonTransactionCredentials( + List outputs, { + required PolygonTransactionPriority? priority, + required CryptoCurrency currency, + final int? feeRate, + }) : super( + outputs, + currency: currency, + priority: priority, + feeRate: feeRate, + ); +} diff --git a/cw_polygon/lib/polygon_transaction_history.dart b/cw_polygon/lib/polygon_transaction_history.dart new file mode 100644 index 000000000..a06b8be4a --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_history.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; +import 'dart:core'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_ethereum/file.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; + +part 'polygon_transaction_history.g.dart'; + +const transactionsHistoryFileName = 'polygon_transactions.json'; + +class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory; + +abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase + with Store { + PolygonTransactionHistoryBase({required this.walletInfo, required String password}) + : _password = password { + transactions = ObservableMap(); + } + + final WalletInfo walletInfo; + String _password; + + Future init() async => await _load(); + + @override + Future save() async { + try { + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$transactionsHistoryFileName'; + final data = json.encode({'transactions': transactions}); + await writeData(path: path, password: _password, data: data); + } catch (e, s) { + print('Error while saving polygon transaction history: ${e.toString()}'); + print(s); + } + } + + @override + void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); + + Future> _read() async { + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$transactionsHistoryFileName'; + final content = await read(path: path, password: _password); + if (content.isEmpty) { + return {}; + } + return json.decode(content) as Map; + } + + Future _load() async { + try { + final content = await _read(); + final txs = content['transactions'] as Map? ?? {}; + + txs.entries.forEach((entry) { + final val = entry.value; + + if (val is Map) { + final tx = PolygonTransactionInfo.fromJson(val); + _update(tx); + } + }); + } catch (e) { + print(e); + } + } + + void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction; +} diff --git a/cw_polygon/lib/polygon_transaction_info.dart b/cw_polygon/lib/polygon_transaction_info.dart new file mode 100644 index 000000000..f1976a601 --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_info.dart @@ -0,0 +1,49 @@ +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_ethereum/ethereum_transaction_info.dart'; + +class PolygonTransactionInfo extends EthereumTransactionInfo { + PolygonTransactionInfo({ + required String id, + required int height, + required BigInt ethAmount, + int exponent = 18, + required TransactionDirection direction, + required DateTime date, + required bool isPending, + required BigInt ethFee, + required int confirmations, + String tokenSymbol = "MATIC", + required String? to, + }) : super( + confirmations: confirmations, + id: id, + height: height, + ethAmount: ethAmount, + exponent: exponent, + direction: direction, + date: date, + isPending: isPending, + ethFee: ethFee, + to: to, + tokenSymbol: tokenSymbol, + ); + + factory PolygonTransactionInfo.fromJson(Map data) { + return PolygonTransactionInfo( + id: data['id'] as String, + height: data['height'] as int, + ethAmount: BigInt.parse(data['amount']), + exponent: data['exponent'] as int, + ethFee: BigInt.parse(data['fee']), + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + confirmations: data['confirmations'] as int, + tokenSymbol: data['tokenSymbol'] as String, + to: data['to'], + ); + } + + @override + String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC'; +} diff --git a/cw_polygon/lib/polygon_transaction_model.dart b/cw_polygon/lib/polygon_transaction_model.dart new file mode 100644 index 000000000..704d674e5 --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_model.dart @@ -0,0 +1,49 @@ +import 'package:cw_ethereum/ethereum_transaction_model.dart'; + +class PolygonTransactionModel extends EthereumTransactionModel { + PolygonTransactionModel({ + required DateTime date, + required String hash, + required String from, + required String to, + required BigInt amount, + required int gasUsed, + required BigInt gasPrice, + required String contractAddress, + required int confirmations, + required int blockNumber, + required String? tokenSymbol, + required int? tokenDecimal, + required bool isError, + }) : super( + amount: amount, + date: date, + hash: hash, + from: from, + to: to, + gasPrice: gasPrice, + gasUsed: gasUsed, + confirmations: confirmations, + contractAddress: contractAddress, + blockNumber: blockNumber, + tokenDecimal: tokenDecimal, + tokenSymbol: tokenSymbol, + isError: isError, + ); + + factory PolygonTransactionModel.fromJson(Map json) => PolygonTransactionModel( + date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000), + hash: json["hash"], + from: json["from"], + to: json["to"], + amount: BigInt.parse(json["value"]), + gasUsed: int.parse(json["gasUsed"]), + gasPrice: BigInt.parse(json["gasPrice"]), + contractAddress: json["contractAddress"], + confirmations: int.parse(json["confirmations"]), + blockNumber: int.parse(json["blockNumber"]), + tokenSymbol: json["tokenSymbol"] ?? "MATIC", + tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""), + isError: json["isError"] == "1", + ); +} diff --git a/cw_polygon/lib/polygon_transaction_priority.dart b/cw_polygon/lib/polygon_transaction_priority.dart new file mode 100644 index 000000000..dba1dab55 --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_priority.dart @@ -0,0 +1,51 @@ +import 'package:cw_ethereum/ethereum_transaction_priority.dart'; + +class PolygonTransactionPriority extends EthereumTransactionPriority { + const PolygonTransactionPriority({required String title, required int raw, required int tip}) + : super(title: title, raw: raw, tip: tip); + + static const List all = [fast, medium, slow]; + static const PolygonTransactionPriority slow = + PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1); + static const PolygonTransactionPriority medium = + PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2); + static const PolygonTransactionPriority fast = + PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4); + + static PolygonTransactionPriority 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 PolygonTransactionPriority deserialize'); + } + } + + @override + String get units => 'gas'; + + @override + String toString() { + var label = ''; + + switch (this) { + case PolygonTransactionPriority.slow: + label = 'Slow'; + break; + case PolygonTransactionPriority.medium: + label = 'Medium'; + break; + case PolygonTransactionPriority.fast: + label = 'Fast'; + break; + default: + break; + } + + return label; + } +} diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart new file mode 100644 index 000000000..66e6797c6 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet.dart @@ -0,0 +1,540 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_ethereum/erc20_balance.dart'; +import 'package:cw_ethereum/ethereum_formatter.dart'; +import 'package:cw_ethereum/ethereum_transaction_model.dart'; +import 'package:cw_ethereum/file.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_polygon/default_erc20_tokens.dart'; +import 'package:cw_polygon/polygon_client.dart'; +import 'package:cw_polygon/polygon_exceptions.dart'; +import 'package:cw_polygon/polygon_formatter.dart'; +import 'package:cw_polygon/polygon_transaction_credentials.dart'; +import 'package:cw_polygon/polygon_transaction_history.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:cw_polygon/polygon_transaction_model.dart'; +import 'package:cw_polygon/polygon_transaction_priority.dart'; +import 'package:cw_polygon/polygon_wallet_addresses.dart'; +import 'package:hive/hive.dart'; +import 'package:hex/hex.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:web3dart/crypto.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:bip32/bip32.dart' as bip32; + +part 'polygon_wallet.g.dart'; + +class PolygonWallet = PolygonWalletBase with _$PolygonWallet; + +abstract class PolygonWalletBase extends WalletBase with Store { + PolygonWalletBase({ + required WalletInfo walletInfo, + String? mnemonic, + String? privateKey, + required String password, + ERC20Balance? initialBalance, + }) : syncStatus = NotConnectedSyncStatus(), + _password = password, + _mnemonic = mnemonic, + _hexPrivateKey = privateKey, + _isTransactionUpdating = false, + _client = PolygonClient(), + walletAddresses = PolygonWalletAddresses(walletInfo), + balance = ObservableMap.of({ + CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero) + }), + super(walletInfo) { + this.walletInfo = walletInfo; + transactionHistory = + PolygonTransactionHistory(walletInfo: walletInfo, password: password); + + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) { + CakeHive.registerAdapter(Erc20TokenAdapter()); + } + + _sharedPrefs.complete(SharedPreferences.getInstance()); + } + + final String? _mnemonic; + final String? _hexPrivateKey; + final String _password; + + late final Box polygonErc20TokensBox; + + late final EthPrivateKey _polygonPrivateKey; + + EthPrivateKey get polygonPrivateKey => _polygonPrivateKey; + + late PolygonClient _client; + + int? _gasPrice; + int? _estimatedGas; + bool _isTransactionUpdating; + + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter + Timer? _transactionsUpdateTimer; + + @override + WalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + late ObservableMap balance; + + Completer _sharedPrefs = Completer(); + + Future init() async { + polygonErc20TokensBox = + await CakeHive.openBox(Erc20Token.polygonBoxName); + await walletAddresses.init(); + await transactionHistory.init(); + _polygonPrivateKey = await getPrivateKey( + mnemonic: _mnemonic, + privateKey: _hexPrivateKey, + password: _password, + ); + walletAddresses.address = _polygonPrivateKey.address.toString(); + await save(); + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int? amount) { + try { + if (priority is PolygonTransactionPriority) { + final priorityFee = + EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); + } + + return 0; + } catch (e) { + return 0; + } + } + + @override + Future changePassword(String password) { + throw UnimplementedError("changePassword"); + } + + @override + void close() { + _client.stop(); + _transactionsUpdateTimer?.cancel(); + } + + @action + @override + Future connectToNode({required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + + final isConnected = _client.connect(node); + + if (!isConnected) { + throw Exception("Polygon Node connection failed"); + } + + _client.setListeners(_polygonPrivateKey.address, _onNewTransaction); + + _setTransactionUpdateTimer(); + + syncStatus = ConnectedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } + } + + @override + Future createTransaction(Object credentials) async { + final _credentials = credentials as PolygonTransactionCredentials; + final outputs = _credentials.outputs; + final hasMultiDestination = outputs.length > 1; + + final CryptoCurrency transactionCurrency = balance.keys + .firstWhere((element) => element.title == _credentials.currency.title); + + final _erc20Balance = balance[transactionCurrency]!; + BigInt totalAmount = BigInt.zero; + int exponent = + transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; + num amountToPolygonMultiplier = pow(10, exponent); + + // so far this can not be made with Polygon as Polygon does not support multiple recipients + if (hasMultiDestination) { + if (outputs.any( + (item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + throw PolygonTransactionCreationException(transactionCurrency); + } + + final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble( + outputs.fold( + 0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); + totalAmount = + BigInt.from(totalOriginalAmount * amountToPolygonMultiplier); + + if (_erc20Balance.balance < totalAmount) { + throw PolygonTransactionCreationException(transactionCurrency); + } + } else { + final output = outputs.first; + // since the fees are taken from Ethereum + // then no need to subtract the fees from the amount if send all + final BigInt allAmount; + if (transactionCurrency is Erc20Token) { + allAmount = _erc20Balance.balance; + } else { + allAmount = _erc20Balance.balance - + BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); + } + final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble( + output.formattedCryptoAmount ?? 0); + totalAmount = output.sendAll + ? allAmount + : BigInt.from(totalOriginalAmount * amountToPolygonMultiplier); + + if (_erc20Balance.balance < totalAmount) { + throw PolygonTransactionCreationException(transactionCurrency); + } + } + + final pendingPolygonTransaction = await _client.signTransaction( + privateKey: _polygonPrivateKey, + toAddress: _credentials.outputs.first.isParsedAddress + ? _credentials.outputs.first.extractedAddress! + : _credentials.outputs.first.address, + amount: totalAmount.toString(), + gas: _estimatedGas!, + priority: _credentials.priority!, + currency: transactionCurrency, + exponent: exponent, + contractAddress: transactionCurrency is Erc20Token + ? transactionCurrency.contractAddress + : null, + ); + + return pendingPolygonTransaction; + } + + Future _updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true; + if (!isPolygonScanEnabled) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (_) { + _isTransactionUpdating = false; + } + } + + @override + Future> fetchTransactions() async { + final address = _polygonPrivateKey.address.hex; + final transactions = await _client.fetchTransactions(address); + + final List>> polygonErc20TokensTransactions = + []; + + for (var token in balance.keys) { + if (token is Erc20Token) { + polygonErc20TokensTransactions.add(_client.fetchTransactions( + address, + contractAddress: token.contractAddress, + ) as Future>); + } + } + + final tokensTransaction = await Future.wait(polygonErc20TokensTransactions); + transactions.addAll(tokensTransaction.expand((element) => element)); + + final Map result = {}; + + for (var transactionModel in transactions) { + if (transactionModel.isError) { + continue; + } + + result[transactionModel.hash] = PolygonTransactionInfo( + id: transactionModel.hash, + height: transactionModel.blockNumber, + ethAmount: transactionModel.amount, + direction: transactionModel.from == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + isPending: false, + date: transactionModel.date, + confirmations: transactionModel.confirmations, + ethFee: + BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + exponent: transactionModel.tokenDecimal ?? 18, + tokenSymbol: transactionModel.tokenSymbol ?? "MATIC", + to: transactionModel.to, + ); + } + + return result; + } + + @override + Object get keys => throw UnimplementedError("keys"); + + @override + Future rescan({required int height}) { + throw UnimplementedError("rescan"); + } + + @override + Future save() async { + await walletAddresses.updateAddressesInBox(); + final path = await makePath(); + await write(path: path, password: _password, data: toJSON()); + await transactionHistory.save(); + } + + @override + String? get seed => _mnemonic; + + @override + String get privateKey => HEX.encode(_polygonPrivateKey.privateKey); + + @action + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + await _updateBalance(); + await _updateTransactions(); + _gasPrice = await _client.getGasUnitPrice(); + _estimatedGas = await _client.getEstimatedGas(); + + Timer.periodic(const Duration(minutes: 1), + (timer) async => _gasPrice = await _client.getGasUnitPrice()); + Timer.periodic(const Duration(seconds: 10), + (timer) async => _estimatedGas = await _client.getEstimatedGas()); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } + } + + Future makePath() async => + pathForWallet(name: walletInfo.name, type: walletInfo.type); + + String toJSON() => json.encode({ + 'mnemonic': _mnemonic, + 'private_key': privateKey, + 'balance': balance[currency]!.toJSON(), + }); + + static Future open({ + required String name, + required String password, + required WalletInfo walletInfo, + }) async { + final path = await pathForWallet(name: name, type: walletInfo.type); + final jsonSource = await read(path: path, password: password); + final data = json.decode(jsonSource) as Map; + final mnemonic = data['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? + ERC20Balance(BigInt.zero); + + return PolygonWallet( + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + privateKey: privateKey, + initialBalance: balance, + ); + } + + Future _updateBalance() async { + balance[currency] = await _fetchMaticBalance(); + + await _fetchErc20Balances(); + await save(); + } + + Future _fetchMaticBalance() async { + final balance = await _client.getBalance(_polygonPrivateKey.address); + return ERC20Balance(balance.getInWei); + } + + Future _fetchErc20Balances() async { + for (var token in polygonErc20TokensBox.values) { + try { + if (token.enabled) { + balance[token] = await _client.fetchERC20Balances( + _polygonPrivateKey.address, + token.contractAddress, + ); + } else { + balance.remove(token); + } + } catch (_) {} + } + } + + Future getPrivateKey( + {String? mnemonic, String? privateKey, required String password}) async { + assert(mnemonic != null || privateKey != null); + + if (privateKey != null) { + return EthPrivateKey.fromHex(privateKey); + } + + final seed = bip39.mnemonicToSeed(mnemonic!); + + final root = bip32.BIP32.fromSeed(seed); + + const _hdPathPolygon = "m/44'/60'/0'/0"; + const index = 0; + final addressAtIndex = root.derivePath("$_hdPathPolygon/$index"); + + return EthPrivateKey.fromHex( + HEX.encode(addressAtIndex.privateKey as List)); + } + + Future? updateBalance() async => await _updateBalance(); + + List get erc20Currencies => polygonErc20TokensBox.values.toList(); + + Future addErc20Token(Erc20Token token) async { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => + element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + + final _token = Erc20Token( + name: token.name, + symbol: token.symbol, + contractAddress: token.contractAddress, + decimal: token.decimal, + enabled: token.enabled, + iconPath: iconPath, + ); + + await polygonErc20TokensBox.put(_token.contractAddress, _token); + + if (_token.enabled) { + balance[_token] = await _client.fetchERC20Balances( + _polygonPrivateKey.address, + _token.contractAddress, + ); + } else { + balance.remove(_token); + } + } + + Future deleteErc20Token(Erc20Token token) async { + await token.delete(); + + balance.remove(token); + _updateBalance(); + } + + Future getErc20Token(String contractAddress) async => + await _client.getErc20Token(contractAddress); + + void _onNewTransaction() { + _updateBalance(); + _updateTransactions(); + } + + void addInitialTokens() { + final initialErc20Tokens = + DefaultPolygonErc20Tokens().initialPolygonErc20Tokens; + + for (var token in initialErc20Tokens) { + polygonErc20TokensBox.put(token.contractAddress, token); + } + } + + @override + Future renameWalletFiles(String newWalletName) async { + final currentWalletPath = + await pathForWallet(name: walletInfo.name, type: type); + final currentWalletFile = File(currentWalletPath); + + final currentDirPath = + await pathForWalletDir(name: walletInfo.name, type: type); + final currentTransactionsFile = + File('$currentDirPath/$transactionsHistoryFileName'); + + // Copies current wallet files into new wallet name's dir and files + if (currentWalletFile.existsSync()) { + final newWalletPath = + await pathForWallet(name: newWalletName, type: type); + await currentWalletFile.copy(newWalletPath); + } + if (currentTransactionsFile.existsSync()) { + final newDirPath = + await pathForWalletDir(name: newWalletName, type: type); + await currentTransactionsFile + .copy('$newDirPath/$transactionsHistoryFileName'); + } + + // Delete old name's dir and files + await Directory(currentDirPath).delete(recursive: true); + } + + void _setTransactionUpdateTimer() { + if (_transactionsUpdateTimer?.isActive ?? false) { + _transactionsUpdateTimer!.cancel(); + } + + _transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) { + _updateTransactions(); + _updateBalance(); + }); + } + + void updatePolygonScanUsageState(bool isEnabled) { + if (isEnabled) { + _updateTransactions(); + _setTransactionUpdateTimer(); + } else { + _transactionsUpdateTimer?.cancel(); + } + } + + @override + String signMessage(String message, {String? address = null}) => bytesToHex( + _polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); + + Web3Client? getWeb3Client() => _client.getWeb3Client(); +} diff --git a/cw_polygon/lib/polygon_wallet_addresses.dart b/cw_polygon/lib/polygon_wallet_addresses.dart new file mode 100644 index 000000000..0a6a407c7 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet_addresses.dart @@ -0,0 +1,5 @@ +import 'package:cw_ethereum/ethereum_wallet_addresses.dart'; + +class PolygonWalletAddresses extends EthereumWalletAddresses { + PolygonWalletAddresses(super.walletInfo); +} diff --git a/cw_polygon/lib/polygon_wallet_creation_credentials.dart b/cw_polygon/lib/polygon_wallet_creation_credentials.dart new file mode 100644 index 000000000..74c7c5ed7 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet_creation_credentials.dart @@ -0,0 +1,28 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +class PolygonNewWalletCredentials extends WalletCredentials { + PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); +} + +class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials { + PolygonRestoreWalletFromSeedCredentials( + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String mnemonic; +} + +class PolygonRestoreWalletFromPrivateKey extends WalletCredentials { + PolygonRestoreWalletFromPrivateKey( + {required String name, + required String password, + required this.privateKey, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String privateKey; +} diff --git a/cw_polygon/lib/polygon_wallet_service.dart b/cw_polygon/lib/polygon_wallet_service.dart new file mode 100644 index 000000000..dafe3bab0 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet_service.dart @@ -0,0 +1,123 @@ +import 'dart:io'; + +import 'package:cw_core/pathForWallet.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:cw_ethereum/ethereum_mnemonics.dart'; +import 'package:cw_polygon/polygon_wallet.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:hive/hive.dart'; +import 'polygon_wallet_creation_credentials.dart'; +import 'package:collection/collection.dart'; + +class PolygonWalletService extends WalletService { + PolygonWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + @override + Future create(PolygonNewWalletCredentials credentials) async { + final strength = (credentials.seedPhraseLength == 12) + ? 128 + : (credentials.seedPhraseLength == 24) + ? 256 + : 128; + + final mnemonic = bip39.generateMnemonic(strength: strength); + final wallet = PolygonWallet( + walletInfo: credentials.walletInfo!, + mnemonic: mnemonic, + password: credentials.password!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + WalletType getType() => WalletType.polygon; + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future openWallet(String name, String password) async { + final walletInfo = + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final wallet = await PolygonWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); + + await wallet.init(); + await wallet.save(); + + 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 restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async { + final wallet = PolygonWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async { + if (!bip39.validateMnemonic(credentials.mnemonic)) { + throw EthereumMnemonicIsIncorrectException(); + } + + final wallet = PolygonWallet( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWallet = await PolygonWalletBase.open( + password: password, name: currentName, walletInfo: currentWalletInfo); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } +} diff --git a/cw_polygon/pubspec.yaml b/cw_polygon/pubspec.yaml new file mode 100644 index 000000000..e99e6dbbb --- /dev/null +++ b/cw_polygon/pubspec.yaml @@ -0,0 +1,73 @@ +name: cw_polygon +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=3.0.6 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + cw_core: + path: ../cw_core + cw_ethereum: + path: ../cw_ethereum + mobx: ^2.0.7+4 + intl: ^0.18.0 + bip39: ^1.0.6 + hive: ^2.2.3 + collection: ^1.17.1 + web3dart: ^2.7.1 + bip32: ^2.0.0 + hex: ^0.2.0 + shared_preferences: ^2.0.15 + + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + 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 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_polygon/test/cw_polygon_test.dart b/cw_polygon/test/cw_polygon_test.dart new file mode 100644 index 000000000..554e28795 --- /dev/null +++ b/cw_polygon/test/cw_polygon_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_polygon/cw_polygon.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/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 6cea7a730..4f7036498 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -160,6 +160,26 @@ bitcoincash-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + polygon + CFBundleURLSchemes + + polygon + + + + CFBundleTypeRole + Viewer + CFBundleURLName + polygon-wallet + CFBundleURLSchemes + + polygon-wallet + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index fcb881943..1bc0d1ae2 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -271,6 +271,8 @@ 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.maticpoly: + return '0x[0-9a-zA-Z]{42}'; case CryptoCurrency.nano: return 'nano_[0-9a-zA-Z]{60}'; case CryptoCurrency.banano: diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 479aa3b82..8a37175b4 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -16,7 +16,7 @@ Future _fetchPrice(Map args) async { final Map queryParams = { 'interval_count': '1', - 'base': crypto, + 'base': crypto.split(".").first, 'quote': fiat, 'key': secrets.fiatApiKey, }; diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 95ccf89ac..8f65159e1 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; @@ -34,6 +35,8 @@ class SeedValidator extends Validator { case WalletType.nano: case WalletType.banano: return nano!.getNanoWordList(language); + case WalletType.polygon: + return polygon!.getPolygonWordList(language); default: return []; } diff --git a/lib/core/wallet_connect/evm_chain_service.dart b/lib/core/wallet_connect/evm_chain_service.dart index dc22e3dda..b9849fdac 100644 --- a/lib/core/wallet_connect/evm_chain_service.dart +++ b/lib/core/wallet_connect/evm_chain_service.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart'; import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; @@ -14,7 +15,6 @@ import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart'; import 'package:convert/convert.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/eth_sig_util.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:http/http.dart' as http; @@ -46,13 +46,12 @@ class EvmChainServiceImpl implements ChainService { required this.wcKeyService, required this.bottomSheetService, required this.wallet, - Web3Client? ethClient, - }) : ethClient = ethClient ?? + Web3Client? web3Client, + }) : ethClient = web3Client ?? Web3Client( - appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(), + appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(), http.Client(), ) { - for (final String event in getEvents()) { wallet.registerEventEmitter(chainId: getChainId(), event: event); } @@ -138,7 +137,8 @@ class EvmChainServiceImpl implements ChainService { try { // Load the private key - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type)); final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -176,13 +176,15 @@ class EvmChainServiceImpl implements ChainService { try { // Load the private key - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type)); final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey); final String signature = hex.encode( credentials.signPersonalMessageToUint8List( Uint8List.fromList(utf8.encode(message)), + chainId: getChainIdBasedOnWalletType(appStore.wallet!.type), ), ); log(signature); @@ -212,7 +214,8 @@ class EvmChainServiceImpl implements ChainService { } // Load the private key - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type)); final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -232,7 +235,11 @@ class EvmChainServiceImpl implements ChainService { ); try { - final result = await ethClient.sendTransaction(credentials, transaction); + final result = await ethClient.sendTransaction( + credentials, + transaction, + chainId: getChainIdBasedOnWalletType(appStore.wallet!.type), + ); log('Result: $result'); @@ -267,7 +274,8 @@ class EvmChainServiceImpl implements ChainService { return authError; } - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type)); return EthSigUtil.signTypedData( privateKey: keys[0].privateKey, diff --git a/lib/core/wallet_connect/wallet_connect_key_service.dart b/lib/core/wallet_connect/wallet_connect_key_service.dart index 2e61ebb99..bb0c9d14c 100644 --- a/lib/core/wallet_connect/wallet_connect_key_service.dart +++ b/lib/core/wallet_connect/wallet_connect_key_service.dart @@ -1,9 +1,11 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; abstract class WalletConnectKeyService { /// Returns a list of all the keys. @@ -32,16 +34,36 @@ class KeyServiceImpl implements WalletConnectKeyService { 'eip155:42161', 'eip155:80001', ], - privateKey: ethereum!.getPrivateKey(wallet), - publicKey: ethereum!.getPublicKey(wallet), + privateKey: _getPrivateKeyForWallet(wallet), + publicKey: _getPublicKeyForWallet(wallet), ), - ]; late final WalletBase, TransactionInfo> wallet; late final List _keys; + static String _getPrivateKeyForWallet(WalletBase wallet) { + switch (wallet.type) { + case WalletType.ethereum: + return ethereum!.getPrivateKey(wallet); + case WalletType.polygon: + return polygon!.getPrivateKey(wallet); + default: + return ''; + } + } + + static String _getPublicKeyForWallet(WalletBase wallet) { + switch (wallet.type) { + case WalletType.ethereum: + return ethereum!.getPublicKey(wallet); + case WalletType.polygon: + return polygon!.getPublicKey(wallet); + default: + return ''; + } + } @override List getChains() { final List chainIds = []; diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart index c69692c9d..7a1dac6cc 100644 --- a/lib/core/wallet_connect/web3wallet_service.dart +++ b/lib/core/wallet_connect/web3wallet_service.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart'; import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; @@ -164,8 +165,10 @@ abstract class Web3WalletServiceBase with Store { void _onSessionProposal(SessionProposalEvent? args) async { if (args != null) { + final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); final Widget modalWidget = Web3RequestModal( child: ConnectionRequestWidget( + chaindIdNamespace: chaindIdNamespace, wallet: _web3Wallet, sessionProposal: SessionRequestModel(request: args.params), ), @@ -232,12 +235,13 @@ abstract class Web3WalletServiceBase with Store { @action Future _onAuthRequest(AuthRequest? args) async { if (args != null) { - List chainKeys = walletKeyService.getKeysForChain('eip155:1'); + final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); + List chainKeys = walletKeyService.getKeysForChain(chaindIdNamespace); // Create the message to be signed - final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}'; - + final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}'; final Widget modalWidget = Web3RequestModal( child: ConnectionRequestWidget( + chaindIdNamespace: chaindIdNamespace, wallet: _web3Wallet, authRequest: AuthRequestModel(iss: iss, request: args), ), diff --git a/lib/di.dart b/lib/di.dart index 0a7097b7b..e0faa6db1 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -18,6 +18,8 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_options_page.dart'; @@ -750,7 +752,7 @@ Future setup({ final wallet = getIt.get().wallet; return ConnectionSyncPage( getIt.get(), - wallet?.type == WalletType.ethereum ? getIt.get() : null, + isEVMCompatibleChain(wallet!.type) ? getIt.get() : null, ); }); @@ -847,6 +849,8 @@ Future setup({ .createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!); case WalletType.nano: return nano!.createNanoWalletService(_walletInfoSource); + case WalletType.polygon: + return polygon!.createPolygonWalletService(_walletInfoSource); default: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index ec31b0539..45803899e 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -27,6 +27,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 polygonDefaultNodeUri = 'polygon-bor.publicnode.com'; const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; @@ -65,6 +66,8 @@ Future defaultSettingsMigration( final migrationVersions = List.generate(migrationVersionsLength, (i) => currentVersion + (i + 1)); + /// When you add a new case, increase the initialMigrationVersion parameter in the main.dart file. + /// This ensures that this switch case runs the newly added case. await Future.forEach(migrationVersions, (int version) async { try { switch (version) { @@ -175,6 +178,11 @@ Future defaultSettingsMigration( await changeBitcoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); break; + case 24: + await addPolygonNodeList(nodes: nodes); + await changePolygonCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + break; case 25: await rewriteSecureStoragePin(secureStorage: secureStorage); break; @@ -332,6 +340,11 @@ Node? getEthereumDefaultNode({required Box nodes}) { nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum); } +Node? getPolygonDefaultNode({required Box nodes}) { + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == polygonDefaultNodeUri) ?? + nodes.values.firstWhereOrNull((node) => node.type == WalletType.polygon); +} + Node? getNanoDefaultNode({required Box nodes}) { return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano); @@ -575,6 +588,7 @@ Future checkCurrentNodes( sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); + final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final currentBitcoinCashNodeId = @@ -589,6 +603,8 @@ Future checkCurrentNodes( nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId); final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); + final currentPolygonNodeServer = + nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId); final currentNanoNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); final currentNanoPowNodeServer = @@ -648,6 +664,12 @@ Future checkCurrentNodes( await nodeSource.add(node); await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); } + + if (currentPolygonNodeServer == null) { + final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon); + await nodeSource.add(node); + await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( @@ -742,3 +764,20 @@ Future changeNanoCurrentPowNodeToDefault( final nodeId = node?.key as int? ?? 0; await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId); } + +Future addPolygonNodeList({required Box nodes}) async { + final nodeList = await loadDefaultPolygonNodes(); + for (var node in nodeList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } +} + +Future changePolygonCurrentNodeToDefault( + {required SharedPreferences sharedPreferences, required Box nodes}) async { + final node = getPolygonDefaultNode(nodes: nodes); + final nodeId = node?.key as int? ?? 0; + + await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId); +} diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 8cf62d79b..b2ce51806 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:ens_dart/ens_dart.dart'; @@ -12,6 +13,10 @@ class EnsRecord { if (wallet != null && wallet.type == WalletType.ethereum) { _client = ethereum!.getWeb3Client(wallet); } + + if (wallet != null && wallet.type == WalletType.polygon) { + _client = polygon!.getWeb3Client(wallet); + } if (_client == null) { _client = Web3Client("https://ethereum.publicnode.com", Client()); @@ -31,6 +36,7 @@ class EnsRecord { case WalletType.haven: return await ens.withName(name).getCoinAddress(CoinType.XHV); case WalletType.ethereum: + case WalletType.polygon: default: return (await ens.withName(name).getAddress()).hex; } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 46865cbcc..1ef388d31 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -19,7 +19,8 @@ class MainActions { final bool Function(DashboardViewModel viewModel)? isEnabled; final bool Function(DashboardViewModel viewModel)? canShow; - final Future Function(BuildContext context, DashboardViewModel viewModel) onTap; + final Future Function( + BuildContext context, DashboardViewModel viewModel) onTap; MainActions._({ required this.name, @@ -52,6 +53,7 @@ class MainActions { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.polygon: case WalletType.bitcoinCash: switch (defaultBuyProvider) { case BuyProviderType.AskEachTime: @@ -124,6 +126,7 @@ class MainActions { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.polygon: case WalletType.bitcoinCash: if (viewModel.isEnabledSellAction) { final moonPaySellProvider = MoonPaySellProvider(); diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 53facf18c..6d1cf299d 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -133,6 +133,22 @@ Future> loadDefaultNanoPowNodes() async { return nodes; } +Future> loadDefaultPolygonNodes() async { + final nodesRaw = await rootBundle.loadString('assets/polygon_node_list.yml'); + final loadedNodes = loadYaml(nodesRaw) as YamlList; + final nodes = []; + + for (final raw in loadedNodes) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + node.type = WalletType.polygon; + nodes.add(node); + } + } + + return nodes; +} + Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); @@ -141,6 +157,8 @@ Future resetToDefault(Box nodeSource) async { final havenNodes = await loadDefaultHavenNodes(); final ethereumNodes = await loadDefaultEthereumNodes(); final nanoNodes = await loadDefaultNanoNodes(); + final polygonNodes = await loadDefaultPolygonNodes(); + final nodes = moneroNodes + bitcoinElectrumServerList + @@ -148,7 +166,8 @@ Future resetToDefault(Box nodeSource) async { havenNodes + ethereumNodes + bitcoinCashElectrumServerList + - nanoNodes; + nanoNodes + + polygonNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index bdd6c24a7..67f1aca97 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -6,6 +6,7 @@ class PreferencesKey { static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc'; static const currentHavenNodeIdKey = 'current_node_id_xhv'; static const currentEthereumNodeIdKey = 'current_node_id_eth'; + static const currentPolygonNodeIdKey = 'current_node_id_matic'; static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; static const currentBananoNodeIdKey = 'current_node_id_banano'; @@ -37,6 +38,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 polygonTransactionPriority = 'current_fee_priority_polygon'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; @@ -50,6 +52,7 @@ class PreferencesKey { static const sortBalanceBy = 'sort_balance_by'; static const pinNativeTokenAtTop = 'pin_native_token_at_top'; static const useEtherscan = 'use_etherscan'; + static const usePolygonScan = 'use_polygonscan'; static const defaultNanoRep = 'default_nano_representative'; static const defaultBananoRep = 'default_banano_representative'; static const lookupsTwitter = 'looks_up_twitter'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index bf6f8157d..70b072c55 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -3,6 +3,7 @@ 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'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; @@ -24,6 +25,8 @@ List priorityForWalletType(WalletType type) { case WalletType.nano: case WalletType.banano: return []; + case WalletType.polygon: + return polygon!.getTransactionPriorities(); default: return []; } diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart new file mode 100644 index 000000000..066b29d43 --- /dev/null +++ b/lib/polygon/cw_polygon.dart @@ -0,0 +1,156 @@ +part of 'polygon.dart'; + +class CWPolygon extends Polygon { + @override + List getPolygonWordList(String language) => EthereumMnemonics.englishWordlist; + + WalletService createPolygonWalletService(Box walletInfoSource) => + PolygonWalletService(walletInfoSource); + + @override + WalletCredentials createPolygonNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + }) => + PolygonNewWalletCredentials(name: name, walletInfo: walletInfo); + + @override + WalletCredentials createPolygonRestoreWalletFromSeedCredentials({ + required String name, + required String mnemonic, + required String password, + }) => + PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); + + @override + WalletCredentials createPolygonRestoreWalletFromPrivateKey({ + required String name, + required String privateKey, + required String password, + }) => + PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + + @override + String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address; + + @override + String getPrivateKey(WalletBase wallet) { + final privateKeyHolder = (wallet as PolygonWallet).polygonPrivateKey; + String stringKey = bytesToHex(privateKeyHolder.privateKey); + return stringKey; + } + + @override + String getPublicKey(WalletBase wallet) { + final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey; + final publicKey = privateKeyInUnitInt.address.hex; + return publicKey; + } + + @override + TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium; + + @override + TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow; + + @override + List getTransactionPriorities() => PolygonTransactionPriority.all; + + @override + TransactionPriority deserializePolygonTransactionPriority(int raw) => + PolygonTransactionPriority.deserialize(raw: raw); + + Object createPolygonTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }) => + PolygonTransactionCredentials( + outputs + .map((out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount)) + .toList(), + priority: priority as PolygonTransactionPriority, + currency: currency, + feeRate: feeRate, + ); + + Object createPolygonTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }) => + PolygonTransactionCredentials( + outputs, + priority: priority as PolygonTransactionPriority?, + currency: currency, + feeRate: feeRate, + ); + + @override + int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount); + + @override + double formatterPolygonAmountToDouble( + {TransactionInfo? transaction, BigInt? amount, int exponent = 18}) { + assert(transaction != null || amount != null); + + if (transaction != null) { + transaction as PolygonTransactionInfo; + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); + } else { + return (amount!) / BigInt.from(10).pow(exponent); + } + } + + @override + List getERC20Currencies(WalletBase wallet) { + final polygonWallet = wallet as PolygonWallet; + return polygonWallet.erc20Currencies; + } + + @override + Future addErc20Token(WalletBase wallet, Erc20Token token) async => + await (wallet as PolygonWallet).addErc20Token(token); + + @override + Future deleteErc20Token(WalletBase wallet, Erc20Token token) async => + await (wallet as PolygonWallet).deleteErc20Token(token); + + @override + Future getErc20Token(WalletBase wallet, String contractAddress) async { + final polygonWallet = wallet as PolygonWallet; + return await polygonWallet.getErc20Token(contractAddress); + } + + @override + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { + transaction as PolygonTransactionInfo; + if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) { + return CryptoCurrency.maticpoly; + } + + wallet as PolygonWallet; + return wallet.erc20Currencies.firstWhere( + (element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase()); + } + + @override + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) { + (wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled); + } + + @override + Web3Client? getWeb3Client(WalletBase wallet) { + return (wallet as PolygonWallet).getWeb3Client(); + } +} diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart index 141401e6a..2b757ad44 100644 --- a/lib/reactions/fiat_rate_update.dart +++ b/lib/reactions/fiat_rate_update.dart @@ -3,9 +3,11 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/update_haven_rate.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -33,10 +35,18 @@ Future startFiatRateUpdate( torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly); } + Iterable? currencies; if (appStore.wallet!.type == WalletType.ethereum) { - final currencies = - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + currencies = + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (appStore.wallet!.type == WalletType.polygon) { + currencies = + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + + if (currencies != null) { for (final currency in currencies) { () async { fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 5f956dc1a..42fbd182e 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -2,7 +2,8 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/update_haven_rate.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; -import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_info.dart'; @@ -107,10 +108,17 @@ void startCurrentWalletChangeReaction( fiat: settingsStore.fiatCurrency, torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly); + Iterable? currencies; if (wallet.type == WalletType.ethereum) { - final currencies = + currencies = ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (wallet.type == WalletType.polygon) { + currencies = + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (currencies != null) { for (final currency in currencies) { () async { fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart new file mode 100644 index 000000000..d0ff37267 --- /dev/null +++ b/lib/reactions/wallet_connect.dart @@ -0,0 +1,46 @@ +import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart'; +import 'package:cw_core/wallet_type.dart'; + +bool isEVMCompatibleChain(WalletType walletType) { + switch (walletType) { + case WalletType.polygon: + case WalletType.ethereum: + return true; + default: + return false; + } +} + +String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.ethereum: + return EVMChainId.ethereum.chain(); + case WalletType.polygon: + return EVMChainId.polygon.chain(); + default: + return ''; + } +} + +int getChainIdBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.polygon: + return 137; + + // For now, we return eth chain Id as the default, we'll modify as we add more wallets + case WalletType.ethereum: + default: + return 1; + } +} + +String getChainNameBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.ethereum: + return 'eth'; + case WalletType.polygon: + return 'polygon'; + default: + return ''; + } +} 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 1aa7f6c4a..59c31aa62 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.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.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'); + this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'), + this.polygonIcon = Image.asset('assets/images/matic_icon.png'); final largeScreen = 731; @@ -54,6 +55,8 @@ class MenuWidgetState extends State { Image bitcoinCashIcon; Image nanoIcon; Image bananoIcon; + Image polygonIcon; + @override void initState() { @@ -219,6 +222,8 @@ class MenuWidgetState extends State { return nanoIcon; case WalletType.banano: return bananoIcon; + case WalletType.polygon: + return polygonIcon; default: throw Exception('No icon for ${type.toString()}'); } diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart index 1241b2ba7..84b2a7bca 100644 --- a/lib/src/screens/receive/widgets/currency_input_field.dart +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -23,6 +24,13 @@ class CurrencyInputField extends StatelessWidget { final TextEditingController controller; final bool isLight; + String get _currencyName { + if (selectedCurrency is CryptoCurrency) { + return (selectedCurrency as CryptoCurrency).title.toUpperCase(); + } + return selectedCurrency.name.toUpperCase(); + } + @override Widget build(BuildContext context) { final arrowBottomPurple = Image.asset( @@ -74,7 +82,7 @@ class CurrencyInputField extends StatelessWidget { child: arrowBottomPurple, ), Text( - selectedCurrency.name.toUpperCase(), + _currencyName, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, @@ -83,7 +91,7 @@ class CurrencyInputField extends StatelessWidget { ), if (selectedCurrency.tag != null) Padding( - padding: const EdgeInsets.only(right: 3.0), + padding: const EdgeInsets.symmetric(horizontal: 3.0), child: Container( decoration: BoxDecoration( color: Theme.of(context).extension()!.textFieldButtonColor, diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index ca86cdccc..67cd689b1 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; @@ -169,7 +169,7 @@ class RootState extends State with WidgetsBindingObserver { ); launchUri = null; } else if (isWalletConnectLink) { - if (widget.appStore.wallet!.type == WalletType.ethereum) { + if (isEVMCompatibleChain(widget.appStore.wallet!.type)) { widget.navigatorKey.currentState?.pushNamed( Routes.walletConnectConnectionsListing, arguments: launchUri, @@ -179,7 +179,7 @@ class RootState extends State with WidgetsBindingObserver { _nonETHWalletErrorToast(S.current.switchToETHWallet); } } - + launchUri = null; return WillPopScope( onWillPop: () async => false, @@ -205,7 +205,7 @@ class RootState extends State with WidgetsBindingObserver { String? _getRouteToGo() { if (isWalletConnectLink) { - if (widget.appStore.wallet!.type != WalletType.ethereum) { + if (isEVMCompatibleChain(widget.appStore.wallet!.type)) { _nonETHWalletErrorToast(S.current.switchToETHWallet); return null; } diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index 947099983..a45d81fc0 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -13,7 +13,8 @@ class PreSeedPage extends BasePage { PreSeedPage(this.type, this.advancedPrivacySettingsViewModel) : imageLight = Image.asset('assets/images/pre_seed_light.png'), imageDark = Image.asset('assets/images/pre_seed_dark.png'), - seedPhraseLength = advancedPrivacySettingsViewModel.seedPhraseLength.value { + seedPhraseLength = + advancedPrivacySettingsViewModel.seedPhraseLength.value { wordsCount = _wordsCount(type, seedPhraseLength); } @@ -40,14 +41,14 @@ class PreSeedPage extends BasePage { alignment: Alignment.center, padding: EdgeInsets.all(24), child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ConstrainedBox( constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.3 - ), + maxHeight: MediaQuery.of(context).size.height * 0.3), child: AspectRatio(aspectRatio: 1, child: image), ), Padding( @@ -58,12 +59,14 @@ class PreSeedPage extends BasePage { style: TextStyle( fontSize: 14, fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor), + color: Theme.of(context) + .extension()! + .secondaryTextColor), ), ), PrimaryButton( - onPressed: () => - Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true), + onPressed: () => Navigator.of(context) + .popAndPushNamed(Routes.seed, arguments: true), text: S.of(context).pre_seed_button_text, color: Theme.of(context).primaryColor, textColor: Colors.white) @@ -79,6 +82,7 @@ class PreSeedPage extends BasePage { return 25; case WalletType.ethereum: case WalletType.bitcoinCash: + case WalletType.polygon: return seedPhraseLength; default: return 24; diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 50881dd57..40a04b0db 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; @@ -8,7 +9,6 @@ import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -85,7 +85,7 @@ class ConnectionSyncPage extends BasePage { ); }, ), - if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[ + if (isEVMCompatibleChain(dashboardViewModel.wallet.type)) ...[ WalletConnectTile( onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing), ), @@ -101,6 +101,7 @@ class ConnectionSyncPage extends BasePage { ); } + Future _presentReconnectAlert(BuildContext context) async { await showPopUp( context: context, diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index b1927648a..dcc41254e 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -87,6 +87,14 @@ class PrivacyPage extends BasePage { onValueChange: (BuildContext _, bool value) { _privacySettingsViewModel.setUseEtherscan(value); }), + if (_privacySettingsViewModel.canUsePolygonScan) + SettingsSwitcherCell( + title: S.current.polygonscan_history, + value: _privacySettingsViewModel.usePolygonScan, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setUsePolygonScan(value); + }, + ), SettingsCellWithArrow( title: S.current.domain_looks_up, handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage), diff --git a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart index c73c4bfa8..179bd6b93 100644 --- a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart @@ -14,12 +14,14 @@ import 'connection_widget.dart'; class ConnectionRequestWidget extends StatefulWidget { const ConnectionRequestWidget({ required this.wallet, + required this.chaindIdNamespace, this.authRequest, this.sessionProposal, Key? key, }) : super(key: key); final Web3Wallet wallet; + final String chaindIdNamespace; final AuthRequestModel? authRequest; final SessionRequestModel? sessionProposal; @@ -52,23 +54,26 @@ class _ConnectionRequestWidgetState extends State { return _ConnectionMetadataDisplayWidget( metadata: metadata, + wallet: widget.wallet, authRequest: widget.authRequest, sessionProposal: widget.sessionProposal, - wallet: widget.wallet, + chaindIdNamespace: widget.chaindIdNamespace, ); } } class _ConnectionMetadataDisplayWidget extends StatelessWidget { const _ConnectionMetadataDisplayWidget({ - required this.metadata, required this.wallet, - this.authRequest, + required this.metadata, required this.sessionProposal, + required this.chaindIdNamespace, + this.authRequest, }); final ConnectionMetadata? metadata; final Web3Wallet wallet; + final String chaindIdNamespace; final AuthRequestModel? authRequest; final SessionRequestModel? sessionProposal; @@ -114,7 +119,11 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget { const SizedBox(height: 8), Visibility( visible: authRequest != null, - child: _AuthRequestWidget(wallet: wallet, authRequest: authRequest), + child: _AuthRequestWidget( + wallet: wallet, + authRequest: authRequest, + chaindIdNamespace: chaindIdNamespace, + ), //If authRequest is null, sessionProposal is not null. replacement: _SessionProposalWidget(sessionProposal: sessionProposal!), @@ -126,16 +135,21 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget { } class _AuthRequestWidget extends StatelessWidget { - const _AuthRequestWidget({required this.wallet, this.authRequest}); + const _AuthRequestWidget({ + required this.wallet, + required this.chaindIdNamespace, + this.authRequest, + }); final Web3Wallet wallet; + final String chaindIdNamespace; final AuthRequestModel? authRequest; @override Widget build(BuildContext context) { final model = ConnectionModel( text: wallet.formatAuthMessage( - iss: 'did:pkh:eip155:1:${authRequest!.iss}', + iss: 'did:pkh:$chaindIdNamespace:${authRequest!.iss}', cacaoPayload: CacaoRequestPayload.fromPayloadParams( authRequest!.request.payloadParams, ), diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index da5df874a..53a9b3eca 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -49,6 +49,7 @@ class WalletListBodyState extends State { 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 polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar? _progressBar; @@ -256,6 +257,8 @@ class WalletListBodyState extends State { return bitcoinCashIcon; case WalletType.nano: return nanoIcon; + case WalletType.polygon: + return polygonIcon; default: return nonWalletTypeIcon; } diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index e814ff44b..46ac2cf1a 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -1,8 +1,8 @@ import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/reactions/wallet_connect.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'; @@ -44,7 +44,7 @@ abstract class AppStoreBase with Store { this.wallet = wallet; this.wallet!.setExceptionHandler(ExceptionHandler.onError); - if (wallet.type == WalletType.ethereum) { + if (isEVMCompatibleChain(wallet.type)) { getIt.get().init(); } } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 4aaa44e13..3e38fed1f 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/utils/device_info.dart'; @@ -88,6 +89,7 @@ abstract class SettingsStoreBase with Store { required this.sortBalanceBy, required this.pinNativeTokenAtTop, required this.useEtherscan, + required this.usePolygonScan, required this.defaultNanoRep, required this.defaultBananoRep, required this.lookupsTwitter, @@ -101,6 +103,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialLitecoinTransactionPriority, TransactionPriority? initialEthereumTransactionPriority, + TransactionPriority? initialPolygonTransactionPriority, TransactionPriority? initialBitcoinCashTransactionPriority}) : nodes = ObservableMap.of(nodes), powNodes = ObservableMap.of(powNodes), @@ -165,6 +168,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.ethereum] = initialEthereumTransactionPriority; } + if (initialPolygonTransactionPriority != null) { + priority[WalletType.polygon] = initialPolygonTransactionPriority; + } + if (initialBitcoinCashTransactionPriority != null) { priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; } @@ -202,6 +209,9 @@ abstract class SettingsStoreBase with Store { case WalletType.bitcoinCash: key = PreferencesKey.bitcoinCashTransactionPriority; break; + case WalletType.polygon: + key = PreferencesKey.polygonTransactionPriority; + break; default: key = null; } @@ -245,8 +255,8 @@ abstract class SettingsStoreBase with Store { reaction( (_) => moneroSeedType, - (SeedType moneroSeedType) => sharedPreferences.setInt( - PreferencesKey.moneroSeedType, moneroSeedType.raw)); + (SeedType moneroSeedType) => + sharedPreferences.setInt(PreferencesKey.moneroSeedType, moneroSeedType.raw)); reaction( (_) => fiatApiMode, @@ -342,9 +352,9 @@ abstract class SettingsStoreBase with Store { sharedPreferences.setString(PreferencesKey.currentLanguageCode, languageCode)); reaction( - (_) => seedPhraseLength, - (SeedPhraseLength seedPhraseWordCount) => - sharedPreferences.setInt(PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value)); + (_) => seedPhraseLength, + (SeedPhraseLength seedPhraseWordCount) => sharedPreferences.setInt( + PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value)); reaction( (_) => pinTimeOutDuration, @@ -388,6 +398,11 @@ abstract class SettingsStoreBase with Store { (bool useEtherscan) => _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan)); + reaction( + (_) => usePolygonScan, + (bool usePolygonScan) => + _sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan)); + reaction((_) => defaultNanoRep, (String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep)); @@ -396,34 +411,32 @@ abstract class SettingsStoreBase with Store { (String bananoRep) => _sharedPreferences.setString(PreferencesKey.defaultBananoRep, bananoRep)); reaction( - (_) => lookupsTwitter, - (bool looksUpTwitter) => + (_) => lookupsTwitter, + (bool looksUpTwitter) => _sharedPreferences.setBool(PreferencesKey.lookupsTwitter, looksUpTwitter)); reaction( - (_) => lookupsMastodon, - (bool looksUpMastodon) => + (_) => lookupsMastodon, + (bool looksUpMastodon) => _sharedPreferences.setBool(PreferencesKey.lookupsMastodon, looksUpMastodon)); reaction( - (_) => lookupsYatService, - (bool looksUpYatService) => + (_) => lookupsYatService, + (bool looksUpYatService) => _sharedPreferences.setBool(PreferencesKey.lookupsYatService, looksUpYatService)); reaction( - (_) => lookupsUnstoppableDomains, - (bool looksUpUnstoppableDomains) => - _sharedPreferences.setBool(PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains)); + (_) => lookupsUnstoppableDomains, + (bool looksUpUnstoppableDomains) => _sharedPreferences.setBool( + PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains)); reaction( - (_) => lookupsOpenAlias, - (bool looksUpOpenAlias) => + (_) => lookupsOpenAlias, + (bool looksUpOpenAlias) => _sharedPreferences.setBool(PreferencesKey.lookupsOpenAlias, looksUpOpenAlias)); - reaction( - (_) => lookupsENS, - (bool looksUpENS) => - _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); + reaction((_) => lookupsENS, + (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); this.nodes.observe((change) { if (change.newValue != null && change.key != null) { @@ -562,6 +575,9 @@ abstract class SettingsStoreBase with Store { @observable bool useEtherscan; + @observable + bool usePolygonScan; + @observable String defaultNanoRep; @@ -651,6 +667,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? havenTransactionPriority; TransactionPriority? litecoinTransactionPriority; TransactionPriority? ethereumTransactionPriority; + TransactionPriority? polygonTransactionPriority; TransactionPriority? bitcoinCashTransactionPriority; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { @@ -662,9 +679,13 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!); } if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { - ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( + ethereumTransactionPriority = ethereum?.deserializeEthereumTransactionPriority( sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) { + polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority( + sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!); + } if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); @@ -676,6 +697,7 @@ abstract class SettingsStoreBase with Store { litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium(); ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority(); bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority(); + polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); @@ -749,12 +771,14 @@ abstract class SettingsStoreBase with Store { final pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; + final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? ""; final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true; final lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true; final lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true; - final lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; + final lookupsUnstoppableDomains = + sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; @@ -774,6 +798,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); + final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final moneroNode = nodeSource.get(nodeId); @@ -781,6 +806,7 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final polygonNode = nodeSource.get(polygonNodeId); final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); final nanoPowNode = powNodeSource.get(nanoPowNodeId); @@ -824,6 +850,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (polygonNode != null) { + nodes[WalletType.polygon] = polygonNode; + } + if (bitcoinCashElectrumServer != null) { nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; } @@ -841,19 +871,19 @@ abstract class SettingsStoreBase with Store { }); 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, - initialMoneroSeedType: moneroSeedType, + 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, + initialMoneroSeedType: moneroSeedType, initialAppSecure: isAppSecure, initialDisableBuy: disableBuy, initialDisableSell: disableSell, @@ -869,42 +899,45 @@ abstract class SettingsStoreBase with Store { actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, pinTimeOutDuration: pinCodeTimeOutDuration, - seedPhraseLength: seedPhraseWordCount,initialLanguageCode: savedLanguageCode, + seedPhraseLength: seedPhraseWordCount, + initialLanguageCode: savedLanguageCode, sortBalanceBy: sortBalanceBy, pinNativeTokenAtTop: pinNativeTokenAtTop, useEtherscan: useEtherscan, + usePolygonScan: usePolygonScan, defaultNanoRep: defaultNanoRep, - defaultBananoRep: defaultBananoRep, - lookupsTwitter: lookupsTwitter, - lookupsMastodon: lookupsMastodon, - lookupsYatService: lookupsYatService, - lookupsUnstoppableDomains: lookupsUnstoppableDomains, - lookupsOpenAlias: lookupsOpenAlias, - lookupsENS: lookupsENS, - initialMoneroTransactionPriority: moneroTransactionPriority, + defaultBananoRep: defaultBananoRep, + lookupsTwitter: lookupsTwitter, + lookupsMastodon: lookupsMastodon, + lookupsYatService: lookupsYatService, + lookupsUnstoppableDomains: lookupsUnstoppableDomains, + lookupsOpenAlias: lookupsOpenAlias, + lookupsENS: lookupsENS, + initialMoneroTransactionPriority: moneroTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority, initialLitecoinTransactionPriority: litecoinTransactionPriority, initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority, - initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, - initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, - initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, - initialShouldRequireTOTP2FAForSendsToInternalWallets: - shouldRequireTOTP2FAForSendsToInternalWallets, - initialShouldRequireTOTP2FAForExchangesToInternalWallets: - shouldRequireTOTP2FAForExchangesToInternalWallets, + initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, + initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, + initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, + initialShouldRequireTOTP2FAForSendsToInternalWallets: + shouldRequireTOTP2FAForSendsToInternalWallets, + initialShouldRequireTOTP2FAForExchangesToInternalWallets: + shouldRequireTOTP2FAForExchangesToInternalWallets, initialShouldRequireTOTP2FAForExchangesToExternalWallets: shouldRequireTOTP2FAForExchangesToExternalWallets, - initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, - initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, - initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: - shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - initialEthereumTransactionPriority: ethereumTransactionPriority, - backgroundTasks: backgroundTasks, - initialSyncMode: savedSyncMode, - initialSyncAll: savedSyncAll, - shouldShowYatPopup: shouldShowYatPopup); - } + initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, + initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, + initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: + shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + initialEthereumTransactionPriority: ethereumTransactionPriority, + initialPolygonTransactionPriority: polygonTransactionPriority, + backgroundTasks: backgroundTasks, + initialSyncMode: savedSyncMode, + initialSyncAll: savedSyncAll, + shouldShowYatPopup: shouldShowYatPopup); + } Future reload({required Box nodeSource}) async { final sharedPreferences = await getIt.getAsync(); @@ -934,6 +967,11 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? priority[WalletType.ethereum]!; } + if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) { + priority[WalletType.polygon] = polygon?.deserializePolygonTransactionPriority( + sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!) ?? + priority[WalletType.polygon]!; + } if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ?? @@ -1027,12 +1065,14 @@ abstract class SettingsStoreBase with Store { .values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index]; pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; + usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? ""; lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true; lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true; lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true; - lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; + lookupsUnstoppableDomains = + sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; @@ -1045,6 +1085,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); + final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final moneroNode = nodeSource.get(nodeId); @@ -1052,6 +1093,7 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final polygonNode = nodeSource.get(polygonNodeId); final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); @@ -1075,6 +1117,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (polygonNode != null) { + nodes[WalletType.polygon] = polygonNode; + } + if (bitcoinCashNode != null) { nodes[WalletType.bitcoinCash] = bitcoinCashNode; } @@ -1110,6 +1156,9 @@ abstract class SettingsStoreBase with Store { case WalletType.nano: await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int); break; + case WalletType.polygon: + await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); + break; default: break; } @@ -1141,7 +1190,6 @@ abstract class SettingsStoreBase with Store { trocadorProviderStates[providerName] = state; } - static Future _getDeviceName() async { String? deviceName = ''; final deviceInfoPlugin = DeviceInfoPlugin(); diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 9366985b5..5c95ab3ab 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/balance.dart'; @@ -81,7 +82,7 @@ abstract class BalanceViewModelBase with Store { bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled; @computed - bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum; + bool get isHomeScreenSettingsEnabled => isEVMCompatibleChain(wallet.type); @computed bool get hasAccounts => wallet.type == WalletType.monero; @@ -123,6 +124,7 @@ abstract class BalanceViewModelBase with Store { case WalletType.monero: case WalletType.haven: case WalletType.ethereum: + case WalletType.polygon: return S.current.xmr_available_balance; default: return S.current.confirmed; @@ -135,6 +137,7 @@ abstract class BalanceViewModelBase with Store { case WalletType.monero: case WalletType.haven: case WalletType.ethereum: + case WalletType.polygon: return S.current.xmr_full_balance; default: return S.current.unconfirmed; @@ -272,7 +275,8 @@ abstract class BalanceViewModelBase with Store { } @computed - bool get hasAdditionalBalance => wallet.type != WalletType.ethereum; + bool get hasAdditionalBalance => !isEVMCompatibleChain(wallet.type); + @computed List get formattedBalances { diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 66620f951..fc2c27a7c 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -2,10 +2,12 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; part 'home_settings_view_model.g.dart'; @@ -42,18 +44,41 @@ abstract class HomeSettingsViewModelBase with Store { void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value; Future addErc20Token(Erc20Token token) async { - await ethereum!.addErc20Token(_balanceViewModel.wallet, token); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + await ethereum!.addErc20Token(_balanceViewModel.wallet, token); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + await polygon!.addErc20Token(_balanceViewModel.wallet, token); + } + _updateTokensList(); _updateFiatPrices(token); } Future deleteErc20Token(Erc20Token token) async { - await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + await polygon!.deleteErc20Token(_balanceViewModel.wallet, token); + } + _updateTokensList(); } - Future getErc20Token(String contractAddress) async => - await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress); + Future getErc20Token(String contractAddress) async { + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress); + } + + return null; + } CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency; @@ -69,7 +94,12 @@ abstract class HomeSettingsViewModelBase with Store { void changeTokenAvailability(Erc20Token token, bool value) async { token.enabled = value; - ethereum!.addErc20Token(_balanceViewModel.wallet, token); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + ethereum!.addErc20Token(_balanceViewModel.wallet, token); + } + if (_balanceViewModel.wallet.type == WalletType.polygon) { + polygon!.addErc20Token(_balanceViewModel.wallet, token); + } _refreshTokensList(); } @@ -83,7 +113,8 @@ abstract class HomeSettingsViewModelBase with Store { return -1; } else if (e2.enabled && !e1.enabled) { return 1; - } else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically + } else if (!e1.enabled && !e2.enabled) { + // if both are disabled then sort alphabetically return e1.name.compareTo(e2.name); } @@ -92,11 +123,21 @@ abstract class HomeSettingsViewModelBase with Store { tokens.clear(); - tokens.addAll(ethereum! - .getERC20Currencies(_balanceViewModel.wallet) - .where((element) => _matchesSearchText(element)) - .toList() - ..sort(_sortFunc)); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + tokens.addAll(ethereum! + .getERC20Currencies(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() + ..sort(_sortFunc)); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + tokens.addAll(polygon! + .getERC20Currencies(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() + ..sort(_sortFunc)); + } } @action diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index c5acf5523..f00f929a3 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -1,10 +1,9 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; import 'dart:developer'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -39,23 +38,26 @@ abstract class NFTViewModelBase with Store { @action Future getNFTAssetByWallet() async { - if (appStore.wallet!.type != WalletType.ethereum) return; + if (!isEVMCompatibleChain(appStore.wallet!.type)) return; final walletAddress = appStore.wallet!.walletInfo.address; log('Fetching wallet NFTs for $walletAddress'); + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type); // the [chain] refers to the chain network that the nft is on // the [format] refers to the number format type of the responses // the [normalizedMetadata] field is a boolean that determines if // the response would include a json string of the NFT Metadata that can be decoded // and used within the wallet + // the [excludeSpam] field is a boolean that determines if spam nfts be excluded from the response. final uri = Uri.https( 'deep-index.moralis.io', '/api/v2.2/$walletAddress/nft', { - "chain": "eth", + "chain": chainName, "format": "decimal", "media_items": "false", + "exclude_spam": "true", "normalizeMetadata": "true", }, ); @@ -94,7 +96,7 @@ abstract class NFTViewModelBase with Store { @action Future importNFT(String tokenAddress, String tokenId) async { - + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type); // the [chain] refers to the chain network that the nft is on // the [format] refers to the number format type of the responses // the [normalizedMetadata] field is a boolean that determines if @@ -104,7 +106,7 @@ abstract class NFTViewModelBase with Store { 'deep-index.moralis.io', '/api/v2.2/nft/$tokenAddress/$tokenId', { - "chain": "eth", + "chain": chainName, "format": "decimal", "media_items": "false", "normalizeMetadata": "true", diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index fd7971001..bc7f70517 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -91,6 +92,13 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction), price: price); break; + case WalletType.polygon: + final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction); + final price = balanceViewModel.fiatConvertationStore.prices[asset]; + amount = calculateFiatAmountRaw( + cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction), + price: price); + break; case WalletType.nano: amount = calculateFiatAmountRaw( cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString( diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index bc7f53af0..93877a525 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -28,10 +28,7 @@ abstract class ExchangeTradeViewModelBase with Store { required this.tradesStore, required this.sendViewModel}) : trade = tradesStore.trade!, - isSendable = tradesStore.trade!.from == wallet.currency || - tradesStore.trade!.provider == ExchangeProviderDescription.xmrto || - (wallet.currency == CryptoCurrency.eth && - tradesStore.trade!.from.tag == CryptoCurrency.eth.title), + isSendable = _checkIfCanSend(tradesStore, wallet), items = ObservableList() { switch (trade.provider) { case ExchangeProviderDescription.changeNow: @@ -155,4 +152,19 @@ abstract class ExchangeTradeViewModelBase with Store { isCopied: true), ]); } + + static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) { + bool _isEthToken() => + wallet.currency == CryptoCurrency.eth && + tradesStore.trade!.from.tag == CryptoCurrency.eth.title; + + bool _isPolygonToken() => + wallet.currency == CryptoCurrency.maticpoly && + tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag; + + return tradesStore.trade!.from == wallet.currency || + tradesStore.trade!.provider == ExchangeProviderDescription.xmrto || + _isEthToken() || + _isPolygonToken(); + } } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 80c331ab2..fb7019885 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -23,6 +23,7 @@ import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/dashboard/trades_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -287,6 +288,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with return transactionPriority == ethereum!.getEthereumTransactionPrioritySlow(); case WalletType.bitcoinCash: return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); + case WalletType.polygon: + return transactionPriority == polygon!.getPolygonTransactionPrioritySlow(); default: return false; } @@ -626,6 +629,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.nano; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.polygon: + depositCurrency = CryptoCurrency.maticpoly; + receiveCurrency = CryptoCurrency.xmr; + break; default: break; } @@ -713,6 +720,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.bitcoinCash: _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority(); break; + case WalletType.polygon: + _settingsStore.priority[wallet.type] = polygon!.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 ae0edba30..0cd4d7491 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -72,6 +72,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.nano: node = getNanoDefaultNode(nodes: _nodeSource)!; break; + case WalletType.polygon: + node = getPolygonDefaultNode(nodes: _nodeSource)!; + break; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}'); } diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index 4ffc81cef..c8637c4be 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -2,6 +2,7 @@ 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/nano/nano.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:hive/hive.dart'; @@ -71,6 +72,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromPrivateKey( name: name, password: password, privateKey: restoreWallet.privateKey!); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromPrivateKey( + name: name, password: password, privateKey: restoreWallet.privateKey!); default: throw Exception('Unexpected type: ${restoreWallet.type.toString()}'); } @@ -95,6 +99,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store case WalletType.nano: return nano!.createNanoRestoreWalletFromSeedCredentials( name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromSeedCredentials( + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); default: throw Exception('Unexpected type: ${type.toString()}'); } 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 6085f4354..bfc9b7980 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -26,6 +26,7 @@ class WalletRestoreFromQRCode { 'litecoin-wallet': WalletType.litecoin, 'litecoin_wallet': WalletType.litecoin, 'ethereum-wallet': WalletType.ethereum, + 'polygon-wallet': WalletType.polygon, 'nano-wallet': WalletType.nano, 'nano_wallet': WalletType.nano, 'bitcoincash': WalletType.bitcoinCash, @@ -157,7 +158,16 @@ class WalletRestoreFromQRCode { return WalletRestoreMode.keys; } - if ((type == WalletType.nano || type == WalletType.banano) && credentials.containsKey('hexSeed')) { + if (type == WalletType.polygon && credentials.containsKey('private_key')) { + final privateKey = credentials['private_key'] as String; + if (privateKey.isEmpty) { + throw Exception('Unexpected restore mode: private_key'); + } + return WalletRestoreMode.keys; + } + + if ((type == WalletType.nano || type == WalletType.banano) && + credentials.containsKey('hexSeed')) { final hexSeed = credentials['hexSeed'] as String; if (hexSeed.isEmpty) { throw Exception('Unexpected restore mode: hexSeed'); diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 2e696e16f..73fb535f2 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -4,6 +4,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; @@ -27,7 +29,8 @@ const String cryptoNumberPattern = '0.0'; class Output = OutputBase with _$Output; abstract class OutputBase with Store { - OutputBase(this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler) + OutputBase( + this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler) : _cryptoNumberFormat = NumberFormat(cryptoNumberPattern), key = UniqueKey(), sendAll = false, @@ -65,8 +68,7 @@ abstract class OutputBase with Store { @computed bool get isParsedAddress => - parsedAddress.parseFrom != ParseFrom.notParsed && - parsedAddress.name.isNotEmpty; + parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty; @computed int get formattedCryptoAmount { @@ -83,8 +85,7 @@ abstract class OutputBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - _amount = - bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); + _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); break; case WalletType.haven: _amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount); @@ -92,6 +93,9 @@ abstract class OutputBase with Store { case WalletType.ethereum: _amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount); break; + case WalletType.polygon: + _amount = polygon!.formatterPolygonParseAmount(_cryptoAmount); + break; default: break; } @@ -130,6 +134,10 @@ abstract class OutputBase with Store { if (_wallet.type == WalletType.ethereum) { return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee)); } + + if (_wallet.type == WalletType.polygon) { + return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee)); + } } catch (e) { print(e.toString()); } @@ -140,10 +148,11 @@ abstract class OutputBase with Store { @computed String get estimatedFeeFiatAmount { try { - final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler(); + final currency = isEVMCompatibleChain(_wallet.type) + ? _wallet.currency + : cryptoCurrencyHandler(); final fiat = calculateFiatAmountRaw( - price: _fiatConversationStore.prices[currency]!, - cryptoAmount: estimatedFee); + price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee); return fiat; } catch (_) { return '0.00'; @@ -240,6 +249,7 @@ abstract class OutputBase with Store { maximumFractionDigits = 12; break; case WalletType.ethereum: + case WalletType.polygon: maximumFractionDigits = 12; break; default: diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart index b881ed71f..007c4b8c0 100644 --- a/lib/view_model/send/send_template_view_model.dart +++ b/lib/view_model/send/send_template_view_model.dart @@ -50,7 +50,9 @@ abstract class SendTemplateViewModelBase with Store { TemplateValidator get templateValidator => TemplateValidator(); bool get hasMultiRecipient => - _wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum; + _wallet.type != WalletType.haven && + _wallet.type != WalletType.ethereum && + _wallet.type != WalletType.polygon; @computed CryptoCurrency get cryptoCurrency => _wallet.currency; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index be822aff3..885e2efe0 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -5,6 +5,8 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; @@ -42,7 +44,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor void onWalletChange(wallet) { currencies = wallet.balance.keys.toList(); selectedCryptoCurrency = wallet.currency; - hasMultipleTokens = wallet.type == WalletType.ethereum; + hasMultipleTokens = isEVMCompatibleChain(wallet.type); } SendViewModelBase( @@ -55,7 +57,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ) : state = InitialExecutionState(), currencies = appStore.wallet!.balance.keys.toList(), selectedCryptoCurrency = appStore.wallet!.currency, - hasMultipleTokens = appStore.wallet!.type == WalletType.ethereum, + hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type), outputs = ObservableList(), _settingsStore = appStore.settingsStore, fiatFromSettings = appStore.settingsStore.fiatCurrency, @@ -119,7 +121,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor try { if (pendingTransaction != null) { final currency = - walletType == WalletType.ethereum ? wallet.currency : selectedCryptoCurrency; + isEVMCompatibleChain(walletType) ? wallet.currency : selectedCryptoCurrency; final fiat = calculateFiatAmount( price: _fiatConversationStore.prices[currency]!, cryptoAmount: pendingTransaction!.feeFormatted); @@ -372,6 +374,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor priority: priority!, currency: selectedCryptoCurrency); case WalletType.nano: return nano!.createNanoTransactionCredentials(outputs); + case WalletType.polygon: + return polygon!.createPolygonTransactionCredentials(outputs, + priority: priority!, currency: selectedCryptoCurrency); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } @@ -412,11 +417,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor WalletType walletType, CryptoCurrency currency, ) { - if (walletType == WalletType.ethereum || walletType == WalletType.haven) { + if (walletType == WalletType.ethereum || + walletType == WalletType.polygon || + walletType == WalletType.haven) { if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for')) { return S.current.do_not_have_enough_gas_asset(currency.toString()); } + + return error; } return error; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index b3ffeb353..e4dd86b37 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; @@ -12,8 +13,7 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart'; part 'privacy_settings_view_model.g.dart'; -class -PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel; +class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel; abstract class PrivacySettingsViewModelBase with Store { PrivacySettingsViewModelBase(this._settingsStore, this._wallet); @@ -58,6 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get useEtherscan => _settingsStore.useEtherscan; + @computed + bool get usePolygonScan => _settingsStore.usePolygonScan; + @computed bool get lookupTwitter => _settingsStore.lookupsTwitter; @@ -78,6 +81,8 @@ abstract class PrivacySettingsViewModelBase with Store { bool get canUseEtherscan => _wallet.type == WalletType.ethereum; + bool get canUsePolygonScan => _wallet.type == WalletType.polygon; + @action void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; @@ -120,4 +125,10 @@ abstract class PrivacySettingsViewModelBase with Store { _settingsStore.useEtherscan = value; ethereum!.updateEtherscanUsageState(_wallet, value); } + + @action + void setUsePolygonScan(bool value) { + _settingsStore.usePolygonScan = value; + polygon!.updatePolygonScanUsageState(_wallet, value); + } } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index a8c892284..4e17866cb 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -51,6 +51,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.nano: _addNanoListItems(tx, dateFormat); break; + case WalletType.polygon: + _addPolygonListItems(tx, dateFormat); + break; default: break; } @@ -125,7 +128,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.nano: return 'https://nanolooker.com/block/${txId}'; case WalletType.banano: - return 'https://bananolooker.com/block/${txId}'; + return 'https://bananolooker.com/block/${txId}'; + case WalletType.polygon: + return 'https://polygonscan.com/tx/${txId}'; default: return ''; } @@ -148,6 +153,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'nanolooker.com'; case WalletType.banano: return S.current.view_transaction_on + 'bananolooker.com'; + case WalletType.polygon: + return S.current.view_transaction_on + 'polygonscan.com'; default: return ''; } @@ -237,7 +244,6 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } - void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), @@ -250,4 +256,21 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + + void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) { + final _items = [ + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + StandartListItem( + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), + StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()), + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + if (showRecipientAddress && tx.to != null) + StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + ]; + + items.addAll(_items); + } } 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 4d5eefdb7..ade279124 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 @@ -1,6 +1,7 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cw_core/currency.dart'; @@ -139,6 +140,22 @@ class NanoURI extends PaymentURI { } } +class PolygonURI extends PaymentURI { + PolygonURI({required String amount, required String address}) + : super(amount: amount, address: address); + + @override + String toString() { + var base = 'polygon:' + address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ required AppStore appStore, @@ -216,6 +233,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return NanoURI(amount: amount, address: address.address); } + if (wallet.type == WalletType.polygon) { + return PolygonURI(amount: amount, address: address.address); + } + throw Exception('Unexpected type: ${type.toString()}'); } @@ -272,6 +293,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); } + if (wallet.type == WalletType.polygon) { + final primaryAddress = polygon!.getAddress(wallet); + + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); + } + return addressList; } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index d9de9473d..56ca190be 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -19,7 +20,8 @@ abstract class WalletKeysViewModelBase with Store { : title = _appStore.wallet!.type == WalletType.bitcoin || _appStore.wallet!.type == WalletType.litecoin || _appStore.wallet!.type == WalletType.bitcoinCash || - _appStore.wallet!.type == WalletType.ethereum + _appStore.wallet!.type == WalletType.ethereum || + _appStore.wallet!.type == WalletType.polygon ? S.current.wallet_seed : S.current.wallet_keys, _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight, @@ -98,7 +100,7 @@ abstract class WalletKeysViewModelBase with Store { ]); } - if (_appStore.wallet!.type == WalletType.ethereum) { + if (isEVMCompatibleChain(_appStore.wallet!.type)) { items.addAll([ if (_appStore.wallet!.privateKey != null) StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!), @@ -151,6 +153,8 @@ abstract class WalletKeysViewModelBase with Store { return 'nano-wallet'; case WalletType.banano: return 'banano-wallet'; + case WalletType.polygon: + return 'polygon-wallet'; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}'); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index a36d68482..5e508d646 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -14,6 +14,8 @@ import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; +import '../polygon/polygon.dart'; + part 'wallet_new_vm.g.dart'; class WalletNewVM = WalletNewVMBase with _$WalletNewVM; @@ -52,6 +54,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name); case WalletType.nano: return nano!.createNanoNewWalletCredentials(name: name); + case WalletType.polygon: + return polygon!.createPolygonNewWalletCredentials(name: name); default: 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 058948c2f..8d1e3b223 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -3,6 +3,7 @@ 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:cake_wallet/polygon/polygon.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -28,7 +29,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { : hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasRestoreFromPrivateKey = - type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano, + type == WalletType.ethereum || + type == WalletType.polygon || + type == WalletType.nano || + type == WalletType.banano, isButtonEnabled = false, mode = WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { @@ -36,6 +40,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.monero: case WalletType.haven: case WalletType.ethereum: + case WalletType.polygon: availableModes = WalletRestoreMode.values; break; case WalletType.nano: @@ -107,6 +112,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { mnemonic: seed, password: password, derivationType: derivationType); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + ); default: break; } @@ -153,6 +164,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, seedKey: options['private_key'] as String, derivationType: options["derivationType"] as DerivationType); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromPrivateKey( + name: name, + password: password, + privateKey: options['private_key'] as String, + ); default: break; } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 329732075..c1659f4cc 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -41,6 +41,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - tor (0.0.1) - url_launcher_macos (0.0.1): - FlutterMacOS - wakelock_plus (0.0.1): @@ -59,6 +60,7 @@ DEPENDENCIES: - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - tor (from `Flutter/ephemeral/.symlinks/plugins/tor/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) @@ -91,6 +93,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + tor: + :path: Flutter/ephemeral/.symlinks/plugins/tor/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos wakelock_plus: @@ -98,7 +102,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308 - cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4 + cw_monero: f8b7f104508efba2591548e76b5c058d05cba3f0 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea @@ -110,6 +114,7 @@ SPEC CHECKSUMS: ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + tor: 2138c48428e696b83eacdda404de6d5574932e26 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 diff --git a/model_generator.sh b/model_generator.sh index 50cb3d353..32d863aeb 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -5,4 +5,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build -- 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 .. +cd cw_polygon && 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 f71d38578..d0a295047 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -147,6 +147,7 @@ flutter: - assets/bitcoin_cash_electrum_server_list.yml - assets/nano_node_list.yml - assets/nano_pow_node_list.yml + - assets/polygon_node_list.yml - assets/text/ - assets/faq/ - assets/animation/ diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 6158342f3..45ad96764 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -745,5 +745,6 @@ "seedtype_polyseed": "بوليسيد (16 كلمة)", "seed_language_czech": "التشيكية", "seed_language_korean": "الكورية", - "seed_language_chinese_traditional": "تقاليد صينية)" + "seed_language_chinese_traditional": "تقاليد صينية)", + "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 8c3a528bb..d52843798 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -741,5 +741,6 @@ "seedtype_polyseed": "Поли семе (16 думи)", "seed_language_czech": "Чех", "seed_language_korean": "Корейски", - "seed_language_chinese_traditional": "Традиционен китайски)" + "seed_language_chinese_traditional": "Традиционен китайски)", + "polygonscan_history": "История на PolygonScan" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 64f2b45e5..3e1d68543 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -741,5 +741,6 @@ "seedtype_polyseed": "Polyseed (16 slov)", "seed_language_czech": "čeština", "seed_language_korean": "korejština", - "seed_language_chinese_traditional": "Číňan (tradiční)" + "seed_language_chinese_traditional": "Číňan (tradiční)", + "polygonscan_history": "Historie PolygonScan" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 2bb9e1404..62a73361c 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Polyseed (16 Wörter)", "seed_language_czech": "Tschechisch", "seed_language_korean": "Koreanisch", - "seed_language_chinese_traditional": "Chinesisch (Traditionell)" + "seed_language_chinese_traditional": "Chinesisch (Traditionell)", + "polygonscan_history": "PolygonScan-Verlauf" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 88949525c..19c435ce8 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -750,5 +750,6 @@ "seedtype_polyseed": "Polyseed (16 words)", "seed_language_czech": "Czech", "seed_language_korean": "Korean", - "seed_language_chinese_traditional": "Chinese (Traditional)" + "seed_language_chinese_traditional": "Chinese (Traditional)", + "polygonscan_history": "PolygonScan history" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 7a3907fb2..2b94e51b9 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Polieta (16 palabras)", "seed_language_czech": "checo", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Chino (tradicional)" + "seed_language_chinese_traditional": "Chino (tradicional)", + "polygonscan_history": "Historial de PolygonScan" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 0b2f54c6c..0c71ba06e 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Polyseed (16 mots)", "seed_language_czech": "tchèque", "seed_language_korean": "coréen", - "seed_language_chinese_traditional": "Chinois (Traditionnel)" + "seed_language_chinese_traditional": "Chinois (Traditionnel)", + "polygonscan_history": "Historique de PolygonScan" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 107599684..0eb9bc7cd 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -727,5 +727,6 @@ "seedtype_polyseed": "Polyseed (16 kalmomi)", "seed_language_czech": "Czech", "seed_language_korean": "Yaren Koriya", - "seed_language_chinese_traditional": "Sinanci (na gargajiya)" + "seed_language_chinese_traditional": "Sinanci (na gargajiya)", + "polygonscan_history": "PolygonScan tarihin kowane zamani" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 20cf5dd40..c4c11fa58 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "पॉलीसीड (16 शब्द)", "seed_language_czech": "चेक", "seed_language_korean": "कोरियाई", - "seed_language_chinese_traditional": "चीनी पारंपरिक)" + "seed_language_chinese_traditional": "चीनी पारंपरिक)", + "polygonscan_history": "पॉलीगॉनस्कैन इतिहास" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 156d885ea..82df37e69 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -747,5 +747,6 @@ "seedtype_polyseed": "Poliseed (16 riječi)", "seed_language_czech": "češki", "seed_language_korean": "korejski", - "seed_language_chinese_traditional": "Kinesko (tradicionalno)" + "seed_language_chinese_traditional": "Kinesko (tradicionalno)", + "polygonscan_history": "Povijest PolygonScan" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index c6836b65b..de04ecb12 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -737,5 +737,6 @@ "seedtype_polyseed": "Polyseed (16 kata)", "seed_language_czech": "Ceko", "seed_language_korean": "Korea", - "seed_language_chinese_traditional": "Cina (tradisional)" + "seed_language_chinese_traditional": "Cina (tradisional)", + "polygonscan_history": "Sejarah PolygonScan" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 1d6d355b6..acb7a71c3 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Polyseed (16 parole)", "seed_language_czech": "ceco", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Cinese tradizionale)" + "seed_language_chinese_traditional": "Cinese tradizionale)", + "polygonscan_history": "Cronologia PolygonScan" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 595a348d3..7907d3835 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "ポリシード(16語)", "seed_language_czech": "チェコ", "seed_language_korean": "韓国語", - "seed_language_chinese_traditional": "中国の伝統的な)" + "seed_language_chinese_traditional": "中国の伝統的な)", + "polygonscan_history": "ポリゴンスキャン履歴" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 0b0fff282..e9a54974e 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -747,5 +747,6 @@ "seedtype_polyseed": "다문 (16 단어)", "seed_language_czech": "체코 사람", "seed_language_korean": "한국인", - "seed_language_chinese_traditional": "중국 전통)" + "seed_language_chinese_traditional": "중국 전통)", + "polygonscan_history": "다각형 스캔 기록" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index fea305f2d..02e3d38c2 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -747,5 +747,6 @@ "seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)", "seed_language_czech": "ချက်", "seed_language_korean": "ကိုးရီးယား", - "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)" + "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)", + "polygonscan_history": "PolygonScan မှတ်တမ်း" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index e400de4c6..4fd0fbab7 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Polyseed (16 woorden)", "seed_language_czech": "Tsjechisch", "seed_language_korean": "Koreaans", - "seed_language_chinese_traditional": "Chinese (traditionele)" + "seed_language_chinese_traditional": "Chinese (traditionele)", + "polygonscan_history": "PolygonScan-geschiedenis" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 698115f8b..bf3cc5329 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Poliqueed (16 słów)", "seed_language_czech": "Czech", "seed_language_korean": "koreański", - "seed_language_chinese_traditional": "Chiński tradycyjny)" + "seed_language_chinese_traditional": "Chiński tradycyjny)", + "polygonscan_history": "Historia PolygonScan" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 9a926834a..ba4beb6c5 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -748,5 +748,6 @@ "seedtype_polyseed": "Polyseed (16 palavras)", "seed_language_czech": "Tcheco", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Chinês tradicional)" + "seed_language_chinese_traditional": "Chinês tradicional)", + "polygonscan_history": "História do PolygonScan" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index e2a09009d..83a1aaf2b 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Полиса (16 слов)", "seed_language_czech": "Чешский", "seed_language_korean": "Корейский", - "seed_language_chinese_traditional": "Китайский традиционный)" + "seed_language_chinese_traditional": "Китайский традиционный)", + "polygonscan_history": "История PolygonScan" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index a4d7f405e..e046eaba8 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -747,5 +747,6 @@ "seedtype_polyseed": "โพลีส (16 คำ)", "seed_language_czech": "ภาษาเช็ก", "seed_language_korean": "เกาหลี", - "seed_language_chinese_traditional": "จีน (ดั้งเดิม)" + "seed_language_chinese_traditional": "จีน (ดั้งเดิม)", + "polygonscan_history": "ประวัติ PolygonScan" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 04ba2af6d..06ee4bc89 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -743,5 +743,6 @@ "seedtype_polyseed": "Polyseed (16 na salita)", "seed_language_czech": "Czech", "seed_language_korean": "Korean", - "seed_language_chinese_traditional": "Intsik (tradisyonal)" + "seed_language_chinese_traditional": "Intsik (tradisyonal)", + "polygonscan_history": "Kasaysayan ng PolygonScan" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index b7b5d33f8..f300951ba 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -747,5 +747,6 @@ "seedtype_polyseed": "Polyseed (16 kelime)", "seed_language_czech": "Çek", "seed_language_korean": "Koreli", - "seed_language_chinese_traditional": "Çin geleneği)" + "seed_language_chinese_traditional": "Çin geleneği)", + "polygonscan_history": "PolygonScan geçmişi" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 900e95acc..6f8d87e86 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -749,5 +749,6 @@ "seedtype_polyseed": "Полісей (16 слів)", "seed_language_czech": "Чеський", "seed_language_korean": "Корейський", - "seed_language_chinese_traditional": "Китайський (традиційний)" + "seed_language_chinese_traditional": "Китайський (традиційний)", + "polygonscan_history": "Історія PolygonScan" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 5535bdf73..f75d287ef 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -741,5 +741,6 @@ "seedtype_polyseed": "پالیسیڈ (16 الفاظ)", "seed_language_czech": "چیک", "seed_language_korean": "کورین", - "seed_language_chinese_traditional": "چینی (روایتی)" + "seed_language_chinese_traditional": "چینی (روایتی)", + "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index a867778f4..b5937a49c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -743,5 +743,6 @@ "seedtype_polyseed": "Polyseed (awọn ọrọ 16)", "seed_language_czech": "Czech", "seed_language_korean": "Ara ẹni", - "seed_language_chinese_traditional": "Kannada (ibile)" + "seed_language_chinese_traditional": "Kannada (ibile)", + "polygonscan_history": "PolygonScan itan" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 7762af3a0..b487f83f5 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -748,5 +748,6 @@ "seedtype_polyseed": "多种物品(16个单词)", "seed_language_czech": "捷克", "seed_language_korean": "韩国人", - "seed_language_chinese_traditional": "中国传统的)" + "seed_language_chinese_traditional": "中国传统的)", + "polygonscan_history": "多边形扫描历史" } diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index dd9852072..0277922d3 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 --bitcoinCash" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash --polygon" ;; $HAVEN) CONFIG_ARGS="--haven" diff --git a/tool/configure.dart b/tool/configure.dart index 7d50ddf53..e3efb3275 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -6,6 +6,7 @@ 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 polygonOutputPath = 'lib/polygon/polygon.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; const pubspecOutputPath = 'pubspec.yaml'; @@ -19,6 +20,7 @@ Future main(List args) async { final hasBitcoinCash = args.contains('${prefix}bitcoinCash'); final hasNano = args.contains('${prefix}nano'); final hasBanano = args.contains('${prefix}banano'); + final hasPolygon = args.contains('${prefix}polygon'); await generateBitcoin(hasBitcoin); await generateMonero(hasMonero); @@ -26,6 +28,7 @@ Future main(List args) async { await generateEthereum(hasEthereum); await generateBitcoinCash(hasBitcoinCash); await generateNano(hasNano); + await generatePolygon(hasPolygon); // await generateBanano(hasEthereum); await generatePubspec( @@ -36,6 +39,7 @@ Future main(List args) async { hasNano: hasNano, hasBanano: hasBanano, hasBitcoinCash: hasBitcoinCash, + hasPolygon: hasPolygon, ); await generateWalletTypes( hasMonero: hasMonero, @@ -45,6 +49,7 @@ Future main(List args) async { hasNano: hasNano, hasBanano: hasBanano, hasBitcoinCash: hasBitcoinCash, + hasPolygon: hasPolygon, ); } @@ -572,6 +577,93 @@ abstract class Ethereum { await outputFile.writeAsString(output); } +Future generatePolygon(bool hasImplementation) async { + final outputFile = File(polygonOutputPath); + const polygonCommonHeaders = """ +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_ethereum/ethereum_mnemonics.dart'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:hive/hive.dart'; +import 'package:web3dart/web3dart.dart'; +"""; + const polygonCWHeaders = """ +import 'package:cw_polygon/polygon_formatter.dart'; +import 'package:cw_polygon/polygon_transaction_credentials.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:cw_polygon/polygon_wallet.dart'; +import 'package:cw_polygon/polygon_wallet_creation_credentials.dart'; +import 'package:cw_polygon/polygon_wallet_service.dart'; +import 'package:cw_polygon/polygon_transaction_priority.dart'; +"""; + const polygonCwPart = "part 'cw_polygon.dart';"; + const polygonContent = """ +abstract class Polygon { + List getPolygonWordList(String language); + WalletService createPolygonWalletService(Box walletInfoSource); + WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo}); + WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); + WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); + String getAddress(WalletBase wallet); + String getPrivateKey(WalletBase wallet); + String getPublicKey(WalletBase wallet); + TransactionPriority getDefaultTransactionPriority(); + TransactionPriority getPolygonTransactionPrioritySlow(); + List getTransactionPriorities(); + TransactionPriority deserializePolygonTransactionPriority(int raw); + + Object createPolygonTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }); + + Object createPolygonTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }); + + int formatterPolygonParseAmount(String amount); + double formatterPolygonAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18}); + List getERC20Currencies(WalletBase wallet); + Future addErc20Token(WalletBase wallet, Erc20Token token); + Future deleteErc20Token(WalletBase wallet, Erc20Token token); + Future getErc20Token(WalletBase wallet, String contractAddress); + + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled); + Web3Client? getWeb3Client(WalletBase wallet); +} + """; + + const polygonEmptyDefinition = 'Polygon? polygon;\n'; + const polygonCWDefinition = 'Polygon? polygon = CWPolygon();\n'; + + final output = '$polygonCommonHeaders\n' + + (hasImplementation ? '$polygonCWHeaders\n' : '\n') + + (hasImplementation ? '$polygonCwPart\n\n' : '\n') + + (hasImplementation ? polygonCWDefinition : polygonEmptyDefinition) + + '\n' + + polygonContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generateBitcoinCash(bool hasImplementation) async { final outputFile = File(bitcoinCashOutputPath); const bitcoinCashCommonHeaders = """ @@ -783,7 +875,8 @@ Future generatePubspec( required bool hasEthereum, required bool hasNano, required bool hasBanano, - required bool hasBitcoinCash}) async { + required bool hasBitcoinCash, + required bool hasPolygon}) async { const cwCore = """ cw_core: path: ./cw_core @@ -820,6 +913,10 @@ Future generatePubspec( cw_banano: path: ./cw_banano """; + const cwPolygon = """ + cw_polygon: + path: ./cw_polygon + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -850,6 +947,10 @@ Future generatePubspec( output += '\n$cwBitcoinCash'; } + if (hasPolygon) { + output += '\n$cwPolygon'; + } + if (hasHaven && !hasMonero) { output += '\n$cwSharedExternal\n$cwHaven'; } else if (hasHaven) { @@ -875,7 +976,8 @@ Future generateWalletTypes( required bool hasEthereum, required bool hasNano, required bool hasBanano, - required bool hasBitcoinCash}) async { + required bool hasBitcoinCash, + required bool hasPolygon}) async { final walletTypesFile = File(walletTypesPath); if (walletTypesFile.existsSync()) { @@ -906,6 +1008,10 @@ Future generateWalletTypes( outputContent += '\tWalletType.bitcoinCash,\n'; } + if (hasPolygon) { + outputContent += '\tWalletType.polygon,\n'; + } + if (hasNano) { outputContent += '\tWalletType.nano,\n'; } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 163b80135..e6f625426 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -41,6 +41,7 @@ class SecretKey { static final ethereumSecrets = [ SecretKey('etherScanApiKey', () => ''), + SecretKey('polygonScanApiKey', () => ''), ]; final String name; From 3c28e2525970f380be9610367ceb9262a1c73b08 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 4 Dec 2023 12:52:43 +0100 Subject: [PATCH 13/18] Remove polyseed seed workaround and fix properly by just storing the wallet (#1215) --- cw_monero/lib/api/wallet_manager.dart | 16 ++++++++++------ cw_monero/lib/monero_wallet.dart | 9 ++------- cw_monero/lib/monero_wallet_service.dart | 17 +---------------- 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 260b420c5..0aa694e9a 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,14 +1,16 @@ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; + import 'package:cw_monero/api/convert_utf8_to_string.dart'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/types.dart'; -import 'package:cw_monero/api/monero_api.dart'; -import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart'; +import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; +import 'package:cw_monero/api/monero_api.dart'; +import 'package:cw_monero/api/signatures.dart'; +import 'package:cw_monero/api/types.dart'; +import 'package:cw_monero/api/wallet.dart'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; final createWalletNative = moneroApi .lookup>('create_wallet') @@ -175,6 +177,8 @@ void restoreWalletFromSpendKeySync( calloc.free(languagePointer); calloc.free(spendKeyPointer); + storeSync(); + if (!isWalletRestored) { throw WalletRestoreFromKeysException( message: convertUTF8ToString(pointer: errorMessagePointer)); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 924a987cd..71c7e3967 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -97,8 +97,7 @@ abstract class MoneroWalletBase ObservableMap balance; @override - String get seed => _seed; - String _seed = monero_wallet.getSeed(); + String get seed => monero_wallet.getSeed(); @override MoneroWalletKeys get keys => MoneroWalletKeys( @@ -114,11 +113,7 @@ abstract class MoneroWalletBase Timer? _autoSaveTimer; List unspentCoins; - Future init({String seedFallback = ""}) async { - if (_seed.isEmpty) { - _seed = seedFallback; - } - + Future init() async { await walletAddresses.init(); balance = ObservableMap.of({ currency: MoneroBalance( diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index c71d43e83..a6b3227b2 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:cw_core/monero_wallet_utils.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -134,12 +133,7 @@ class MoneroWalletService extends WalletService< return openWallet(name, password); } - if (wallet.seed.isEmpty) { - final seedFallback = await _getSeedBackup(path, password); - await wallet.init(seedFallback: seedFallback); - } else { - await wallet.init(); - } + await wallet.init(); return wallet; } catch (e) { @@ -295,8 +289,6 @@ class MoneroWalletService extends WalletService< restoreHeight: height, spendKey: spendKey); - await writeData(path: "$path.seed", password: password, data: seed); - final wallet = MoneroWallet( walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); @@ -304,13 +296,6 @@ class MoneroWalletService extends WalletService< return wallet; } - Future _getSeedBackup(String path, String password) async { - final seedFilePath = "$path.seed"; - final seedFile = File(seedFilePath); - if (!seedFile.existsSync()) return ""; - return read(path: seedFilePath, password: password); - } - Future repairOldAndroidWallet(String name) async { try { if (!Platform.isAndroid) { From b95af3a649fc4f28701f6d8dd173921a95b95d10 Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Tue, 5 Dec 2023 02:35:33 +0200 Subject: [PATCH 14/18] V4.11.0 v1.8.0 (#1216) * V4.11.0 and v1.8.0 * Add polygon to iOS and macOS Minor import fix for monero.com * Remove Polygon temporarily * Remove tor temorarily [skip ci] * Remove tor temorarily [skip ci] --- assets/text/Monerocom_Release_Notes.txt | 4 +- assets/text/Release_Notes.txt | 6 +-- configure_cake_wallet_android.sh | 1 + cw_core/lib/node.dart | 11 +++-- cw_core/pubspec.yaml | 8 +-- lib/src/screens/settings/tor_page.dart | 66 ++++++++++++------------- pubspec_base.yaml | 8 +-- scripts/android/app_env.sh | 4 +- scripts/android/pubspec_gen.sh | 2 +- scripts/ios/app_env.sh | 4 +- scripts/macos/app_env.sh | 2 +- tool/configure.dart | 2 +- 12 files changed, 61 insertions(+), 57 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index 2830ecdd8..90be3b8e8 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,2 +1,2 @@ -Monero Polyseed support, under Advanced privacy settings, create and restore from a 16 words phrase and without the need to remember the wallet creation date -Minor bug fixes and enhancements \ No newline at end of file +Monero Polyseed support, create and restore from a 16 words phrase and without the need to remember the wallet creation date +Bug fixes and enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index accdbb080..655d59a7d 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,3 +1,3 @@ -Monero Polyseed support, under Advanced privacy settings, create and restore from a 16 words phrase and without the need to remember the wallet creation date -Add Ethereum NFTs tab to see all of your purchased NFTs -Minor bug fixes and enhancements \ No newline at end of file +Monero Polyseed support, create and restore from a 16 words phrase and without the need to remember the wallet creation date +Add NFTs tab to see all of your purchased NFTs on Ethereum +Bug fixes and enhancements \ No newline at end of file diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet_android.sh index 6998555d5..da794a35c 100755 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet_android.sh @@ -10,4 +10,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build -- 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 .. +cd cw_polygon && 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_core/lib/node.dart b/cw_core/lib/node.dart index cc749f23a..bd96e1395 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -6,7 +6,7 @@ import 'package:hive/hive.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:http/io_client.dart' as ioc; -import 'package:tor/tor.dart'; +// import 'package:tor/tor.dart'; part 'node.g.dart'; @@ -214,14 +214,17 @@ class Node extends HiveObject with Keyable { } Future requestNodeWithProxy() async { - if (!isValidProxyAddress && !Tor.instance.enabled) { + if (!isValidProxyAddress/* && !Tor.instance.enabled*/) { return false; } String? proxy = socksProxyAddress; - if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) { - proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; + // if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) { + // proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; + // } + if (proxy == null) { + return false; } final proxyAddress = proxy!.split(':')[0]; final proxyPort = int.parse(proxy.split(':')[1]); diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 533b578ad..04a840d4e 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -20,10 +20,10 @@ dependencies: intl: ^0.18.0 encrypt: ^5.0.1 socks5_proxy: ^1.0.4 - tor: - git: - url: https://github.com/cake-tech/tor.git - ref: main +# tor: +# git: +# url: https://github.com/cake-tech/tor.git +# ref: main dev_dependencies: flutter_test: diff --git a/lib/src/screens/settings/tor_page.dart b/lib/src/screens/settings/tor_page.dart index d1d5c7e83..ae1ef1677 100644 --- a/lib/src/screens/settings/tor_page.dart +++ b/lib/src/screens/settings/tor_page.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:flutter/material.dart'; -import 'package:tor/tor.dart'; +// import 'package:tor/tor.dart'; class TorPage extends BasePage { final AppStore appStore; @@ -40,44 +40,44 @@ class _TorPageBodyState extends State { connecting = true; // Update flag }); - await Tor.init(); - - // Start the proxy - await Tor.instance.start(); - - // Toggle started flag. - setState(() { - torEnabled = Tor.instance.enabled; // Update flag - connecting = false; - }); - - final node = widget.appStore.settingsStore.getCurrentNode(widget.appStore.wallet!.type); - if (node.socksProxyAddress?.isEmpty ?? true) { - node.socksProxyAddress = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; - } - widget.appStore.wallet!.connectToNode(node: node); + // await Tor.init(); + // + // // Start the proxy + // await Tor.instance.start(); + // + // // Toggle started flag. + // setState(() { + // torEnabled = Tor.instance.enabled; // Update flag + // connecting = false; + // }); + // + // final node = widget.appStore.settingsStore.getCurrentNode(widget.appStore.wallet!.type); + // if (node.socksProxyAddress?.isEmpty ?? true) { + // node.socksProxyAddress = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; + // } + // widget.appStore.wallet!.connectToNode(node: node); print('Done awaiting; tor should be running'); } Future endTor() async { - // Start the proxy - Tor.instance.disable(); - - // Toggle started flag. - setState(() { - torEnabled = Tor.instance.enabled; // Update flag - }); - - print('Done awaiting; tor should be stopped'); - } - - @override - void initState() { - super.initState(); - - torEnabled = Tor.instance.enabled; + // // Start the proxy + // Tor.instance.disable(); + // + // // Toggle started flag. + // setState(() { + // torEnabled = Tor.instance.enabled; // Update flag + // }); + // + // print('Done awaiting; tor should be stopped'); } + // + // @override + // void initState() { + // super.initState(); + // + // torEnabled = Tor.instance.enabled; + // } @override void dispose() { diff --git a/pubspec_base.yaml b/pubspec_base.yaml index d0a295047..a4738bf82 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -98,10 +98,10 @@ dependencies: url: https://github.com/cake-tech/bitcoin_flutter.git ref: cake-update-v3 fluttertoast: 8.1.4 - tor: - git: - url: https://github.com/cake-tech/tor.git - ref: main +# tor: +# git: +# url: https://github.com/cake-tech/tor.git +# ref: main socks5_proxy: ^1.0.4 flutter_svg: ^2.0.9 polyseed: diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index d241c4421..63a29221b 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -16,14 +16,14 @@ APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="1.8.0" -MONERO_COM_BUILD_NUMBER=68 +MONERO_COM_BUILD_NUMBER=69 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.11.0" -CAKEWALLET_BUILD_NUMBER=182 +CAKEWALLET_BUILD_NUMBER=184 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index 0277922d3..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 --bitcoinCash --polygon" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" ;; $HAVEN) CONFIG_ARGS="--haven" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 874501cb5..a831396f1 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -14,12 +14,12 @@ APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" MONERO_COM_VERSION="1.8.0" -MONERO_COM_BUILD_NUMBER=66 +MONERO_COM_BUILD_NUMBER=67 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.11.0" -CAKEWALLET_BUILD_NUMBER=200 +CAKEWALLET_BUILD_NUMBER=202 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index f69670c9a..f6fa8b5e8 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,7 +16,7 @@ fi CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.4.0" -CAKEWALLET_BUILD_NUMBER=43 +CAKEWALLET_BUILD_NUMBER=45 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/tool/configure.dart b/tool/configure.dart index e3efb3275..67732faa9 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -590,7 +590,6 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; -import 'package:cw_ethereum/ethereum_mnemonics.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:hive/hive.dart'; import 'package:web3dart/web3dart.dart'; @@ -603,6 +602,7 @@ import 'package:cw_polygon/polygon_wallet.dart'; import 'package:cw_polygon/polygon_wallet_creation_credentials.dart'; import 'package:cw_polygon/polygon_wallet_service.dart'; import 'package:cw_polygon/polygon_transaction_priority.dart'; +import 'package:cw_ethereum/ethereum_mnemonics.dart'; """; const polygonCwPart = "part 'cw_polygon.dart';"; const polygonContent = """ From d370c754c5dd39d1cb30fde643882fd106a3dad6 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Wed, 6 Dec 2023 06:53:26 -0700 Subject: [PATCH 15/18] allow manual builds on any branch (#1219) * allow manual builds on any branch * small fix * see if this works * test fix * test fix * fix * fix * test * fix * fix * consistent variable naming --- .github/workflows/pr_test_build.yml | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 8fef4fdb6..5b2bb11d3 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -3,6 +3,12 @@ name: PR Test Build on: pull_request: branches: [main] + workflow_dispatch: + inputs: + branch: + description: 'Branch name to build' + required: true + default: 'main' jobs: PR_test_build: @@ -12,6 +18,14 @@ jobs: KEY_PASS: test@cake_wallet steps: + - name: is pr + if: github.event_name == 'pull_request' + run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + + - name: is not pr + if: github.event_name != 'pull_request' + run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV + - name: Free Up GitHub Actions Ubuntu Runner Disk Space run: | sudo rm -rf /usr/share/dotnet @@ -40,7 +54,7 @@ jobs: cd /opt/android -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch $GITHUB_HEAD_REF + git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} cd cake_wallet/scripts/android/ ./install_ndk.sh source ./app_env.sh cakewallet @@ -141,7 +155,7 @@ jobs: echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - name: Rename app - run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties + run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - name: Build run: | @@ -156,7 +170,7 @@ jobs: # appcenter distribute release \ # --group "Testers" \ # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ -# --release-notes ${GITHUB_HEAD_REF} \ +# --release-notes ${{ env.BRANCH_NAME }} \ # --app Cake-Labs/Cake-Wallet \ # --token ${{ secrets.APP_CENTER_TOKEN }} \ # --quiet @@ -165,7 +179,7 @@ jobs: run: | cd /opt/android/cake_wallet/build/app/outputs/apk/release mkdir test-apk - cp app-release.apk test-apk/$GITHUB_HEAD_REF.apk + cp app-release.apk test-apk/${{env.BRANCH_NAME}}.apk - name: Upload Artifact uses: kittaakos/upload-artifact-as-is@v0 @@ -179,6 +193,6 @@ jobs: token: ${{ secrets.SLACK_APP_TOKEN }} path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk channel: ${{ secrets.SLACK_APK_CHANNEL }} - title: "${{github.head_ref}}.apk" - filename: ${{github.head_ref}}.apk + title: "${{ env.BRANCH_NAME }}.apk" + filename: ${{ env.BRANCH_NAME }}.apk initial_comment: ${{ github.event.head_commit.message }} From 2138c35e38680f021b2f23b716a65d5e0e9673c5 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 8 Dec 2023 16:05:52 +0200 Subject: [PATCH 16/18] CW-532-DFX-buy-provider (#1209) * dfx buy provider ui * fix signing flow * fixed provider determination based on wallet type and app settings * update localization files * minor fix * Fix BTC, LTC und BCH signMessages * Add signMessage to monero * open dfx in webview * Update dfx_buy_provider.dart * Revert merge conflict * Update bitcoin_flutter ref --------- Co-authored-by: Konstantin Ullrich --- assets/images/dfx_dark.png | Bin 0 -> 3515 bytes assets/images/dfx_light.png | Bin 0 -> 3502 bytes cw_bitcoin/lib/electrum_wallet.dart | 5 +- cw_bitcoin/pubspec.yaml | 2 +- .../lib/src/bitcoin_cash_wallet.dart | 8 +- cw_bitcoin_cash/pubspec.yaml | 2 +- cw_monero/ios/Classes/monero_api.cpp | 9 + cw_monero/ios/Classes/monero_api.h | 3 +- cw_monero/lib/api/signatures.dart | 2 + cw_monero/lib/api/types.dart | 2 + cw_monero/lib/api/wallet.dart | 22 ++- cw_monero/lib/monero_wallet.dart | 6 + cw_monero/macos/Classes/monero_api.cpp | 7 + cw_monero/macos/Classes/monero_api.h | 3 +- lib/buy/dfx/dfx_buy_provider.dart | 179 ++++++++++++++++++ lib/di.dart | 6 +- lib/entities/buy_provider_types.dart | 40 +++- lib/entities/main_actions.dart | 81 ++++---- lib/src/screens/buy/buy_options_page.dart | 82 +++++--- .../screens/settings/other_settings_page.dart | 5 +- lib/store/settings_store.dart | 28 ++- .../dashboard/dashboard_view_model.dart | 6 +- .../settings/other_settings_view_model.dart | 35 ++-- pubspec_base.yaml | 2 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 50 files changed, 456 insertions(+), 109 deletions(-) create mode 100644 assets/images/dfx_dark.png create mode 100644 assets/images/dfx_light.png create mode 100644 lib/buy/dfx/dfx_buy_provider.dart diff --git a/assets/images/dfx_dark.png b/assets/images/dfx_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..cbba87372adad4c0c3f5d14dec996aacea01ee88 GIT binary patch literal 3515 zcmV;s4Mg&ZP)BE1ZQLxAY_vg;$?V;oX(lt{{G@3qJ`_O^!KmPq;Qw2}Cn0L9?Tam?khm;cmfj@-EE=vY<#k5o0f^m&*sM6pBI0EL{uRZtU4R5Z z`^X8`V)zNfJ8YaF;u6EP09P2E5Ad?jnUOMxUqvh^s*L``+lU9}BlcPZ2XpY-jyyG} zttleQMaed+noc9ry@UIIwng1*#M^^UkFzj+6yF25_LnMhmf?pGpH<}CbcnAmX|u%; z|7E#p3GpKUUAw%6atiT2#A8)^yd3Cssj4^8z$>TJnP(8+gLrkpJj%xIMEtceUz`ec zeo~wa<_{hK;NmJRuzL3+9o0iVC08s$)vACv7|KExH6jmbd4gn8^0FkllzkRT6fd|P0n zrwSVAXrbt(XrcJk;OyVRDf#{Yw^VBW$m{6QFhZvPBk<6w!tZERBfJcLd1$M3&)TehHS09YRtkN(Z(GDi^F2;vKBX;HY+Q*UEnTqUvgh@A9J5aYSek}G;8j! zqp=F+X_rylpF&#LEj%b>g+0Q~5^H*5N-?MwSLAe5T{?GXqX;9WfX;*4pXw6&V*io?m|<6;W0GAb4f<-njQ>$>Q>9MPr@ zwU=n+0I+*9IKpp9{{0u*d8K7@p9VPmCp-6AwmA>rtOL;dXv;Q$XXCx^0IWWyc=l3w zC&Xgk0yx!!!@*Zi8qcdA>-AfBZYACT*nGWS|NH%Vee(m<-vGFv{{#Oecnig2*2@3@ z061k>NoGw=04e|g00;m9hiL!=000010000Q0000000N)_00aO40096105hNi00aO4 z0096105bpp008X8Z(;xd2_#8GK~!i3)mmF@lSdXl{$o2ACous6B-;cFaS4mKNb40F zXb3koDYw#uix8z1LZambnnm!&!=^}t=7D`#o_L@jDm4oNL4hO)q23TT2|-YwXv-!M zs38du2;^e_v*(+5CclmS+qp z%%t4hT*}MKqk@70^7(v@M~@zfmX;QJ^5h9UefpGMzI;jJ#*ORacYSYfZ!NR&%jmE& zJS@5C>FGauz1|=*Z*JPO$vl1fw0QI8jYy>Q^z;b8Z{EDw#CYHVK4v9%c{4-^8tjK7{)^9+x@#7EU-WMr7jmMt^y-@hM|xsh}L z?}`;GOweJr>Jn!yamXCD?~eKN=bP8BUmppt36BMx1q&9Kpv!FLB+jZmq(3`5JFsEH z1~U){B+Sfc0D>M&8T6S&zdf7rdw0Ty4;?zBc^m2a(1?amsi~JB+4paa-lU8 z-zmU*V6i8I0I_(etgJM1a&p?Eua3diI=EluwUjnuA$?cv;BVf%d00Q+4YD3y^I)S= z`$#Ytd{?|CL>GL9HrBTz1%xQr*%QK)BtXlTFIS;Y*t8&M)FDc;npdO_5&DGopTXdi z8-B!28worX{)2Duucrp#H=lV4(JDZ zl2imM4HZGJU%#fZvN8f}TU#4>Jegz|4qCKmQB<8?yLL^s1Mm9v>!l!Q0RLC7UM(Y+ z%Vowp>aJb8R=C}6_;vm8q7%Fe3%}nlm>FrpSZ`{>oiSqu*pK^MxpGCEJ9kbn9~ySg zo;?zu^{2SFSohDIIn&xDa0F+inZyjm^D6_d#pvsOP4OmaXWVG&~P4)Ck$70b+urtkq;CYt&AT(-gF|1602}(f(*9F#U&-w z#u*he#X(NPATkAYJbOkhEv*#j>m#07I(6z4`FuV)c<^8tI>E5zAC{BLkdnOX^par*GelYD$?zH z_wLp0@hZ?fW`UU%9PkQ`6*V4qRC0Ku3IaBvBIJ6z<~p@ z4UU6TQFQO#Jvn6Bv}ttl;ze1H=BSXLbFyhjA0weGKi!AFrIcS=s5>o48NC2;NN42D z$e^El9#ctu6ZQZ12l8z!CAN1ua^#2{edo>{3zmUub8|Bt{m)Sf1o9fci(eB*~ttn$$fT;VN23S-qWN9xj**Bm}f>}pBP zD4U*KL)P(;b;w~LWg1yPK$QQTTx?oFGAU@7Vg?^s1?Vg zNt0xoVk8o`-G8+weY9?!Ru15Sf6baTc0(nM6S?1LMSS@&1-|`(QhS3GNEw{Gt~al# z)H#Vd3%rzH@xEL$&)Bnp5omTDGlF8(__l4^=o5arc(a4C!q8x5(aO@LOR2TBRd)HN zPNhG2Gv$4R8XFs__Ul^Ow|`%>VKRqpyU$##Ju2=NtP!n$sSr2EeIWkreouU#zEITg z8|B1_Z$$o-DRRSO=7n#nPqyQysAyoHVanp4RaLs(uEK6zI1MK>C)8fUM`ZaQ`x#=e zSOxnGG-_f8wJV=~_NmC7GFjxkmoM`2^F=;CS=m{V2#?pqv74(>6xuYw7%z0yZ2RVQ zBn-v}oYp9SJ+7nyTzp&}z+uU6>j;21n;vB2?T*PSSFY5^fH3lzJb5zD(g{gK?E$l9 z&C+n6f4@jzX8 zHfX|x3DQGZ*8)#1EnK*ejvqg+VO2leDITWE?=0OPt+IXlc6n#x^=5PQn?`(slwb;1 zL16)H*|LS0Pq`MY&IK^sE?ABR2w;1j;Yff(81~5{ELijdJ5^$ctndLdz&wPa?}*`0 z79tN8rUaY8FP!!iw{G2%-Y5cgSb~5+WEqMe7;A->f<(cI8A3eAvjc(yH*YvYCwt;g zFOrA~_y+%0<>~B9e}8`jcyj*5xv8nCNu;HvA@B2QBeIOeWoIxfo$5 zB6uhc^guVXMuu!!v83i>cuqU6-r(O+IyyQ8{QYPatPM`!OO&7kdZ5cp#lMV=#ko!U zcer}9emld*ZqeDapL$?E@JgI8mop2KML|FC03TwlP>AyOUFDG292&6v-i+;8es6~F z#&F_`cLbN*^AF$vuf8_}o>24~9yAVtraTgz69LQXZW(`&!4-QF+%P))|Y^6 pQp9UReF@mWv-maR81a{Y{|Bm1`FlyA?co3b002ovPDHLkV1keosl5OI literal 0 HcmV?d00001 diff --git a/assets/images/dfx_light.png b/assets/images/dfx_light.png new file mode 100644 index 0000000000000000000000000000000000000000..e4836be3ebecc08d3d0dee8fe3af672c511c7c2f GIT binary patch literal 3502 zcmY*bc|2768$Om0LY7?n7$nOWA?wVzwubCG$!^R{7?T+pvR*_@*^07cZK!06tV7v} zETy;zMH+i4OV;1$-rv2y-}ijZIq!Mi=Y5{cVsu7h)TYy547|tz&D@ze zbZM29yj;3Mh7{IV6*9-3*&fJ&#xy`oFT zlnkSya5?1j+fUWq^Bo=9#MX$R6A?oYg`{-(^wSI?vu!WNSJpsXQ=^-8TZibSaw{Z5 z;)nRLr<$+8pHRSoK<6{pXlA|Lp5vol+s8X}{BuB`c-x-?j=$w{e|9>zIhOOs-ar;Gbry z-@GRul<_fvC(nLvl9*Vbgstx6IYc#JkAIT_T$`xzLG0fU9p3iWsG>CD<0l%!FBM8g zGSNKKv83Qu*sHEp!@cKc-E-K3BW-dx=efm!nNRYN%CIdd@k(>7d}7~iM~r&+dg6Bd z{3&0?h*bz0t35iTMe)6`@Nh+t|74 z_#l(*Q<|0_hwv>KsKfJb`aMyO&t)I-xYrV+l0QxMm`~h!#gO+RPx!Od9f>pw8~{LC+FxSXsaL zQ0nW@leRUCy%d!*G89u9JapU_FEw)31Zt8NyDbX7Gp8&hSp-Ux?X~r};*20TR+lQwNgvK?O2jGFIa+glwig$GL|mNr~SIGHJ6EWFw6aCbi} z;ycsGG*F*^$aE+};^_2#-$x%1Aa7$Ea{vfC4FJ(`0I)-cqGdL zIgQpZdcqLpWa4XX4$vnGzy>ff@B&Qqh=G0p30Q#nR}27jCHi8_XZ#0I&1d>IK1v>o z7~7;5I%Dmeh)(8a+TMWyN}fJ}UMM9}0RD&oz)0HkFaSmL1d#&#aRhA=9Q=!+O^=UY zC>ZpMLiB@!oy@I327y5+kh+qxk}?><4g!H-K|a3PHiqZ_E~h`i!5AVDuMLHUgoG%C zs44{pp`mBBw6vhgDo_;_2%P~TgyM*vBnXZm^_R$hbqrAi?;tFmhz-Pnj&wb}0)vTg zF!;#mpY_*1iCEu%op6M|+oCrJJ<34ODk($%(WaNej!U@1r@v5os|a=&^q<#;U>9=x$O-_gVJ3!pc9>%yE@I8>k^G1)2*@KJETB+G zsVci@%87kAJeJl)5$G(FJiYHy=3?tzW}QNVVpq6hTOD@0yVMS_w7G=0MN_rnRR&B++rnN)iLWp{LB$K>l} z&rKDMs2hGY21${$L@duu= zIxgF>KaCsm>Uh1b%ggWj#7yKy35<8$G3%Tq*$K~#kKrR3@Y*B}w=^@>Nq)U<7Lmet?5a4& z=u3#n2h|>j?E6f04=P4!2c3)5fjzsJ=|RCKh{gieA|~97>~j@TZemY0^Y5GtaFiI| zN6s9KuS3$6;Qo`VE7Br|{+{BzNY3}*{0!`P4VIse&%g~@tH!5joT+y`=BA#twY8nA zD`dB;yZg@DpSN~7o|Kh!CW5lG2*v(nGP&sHP4;Th@sA1NrxG?R!@thxeI#ldT|+j_ zB_(kU|M{n!I3oOvMJ`Ov#@6<;6S))Yub%zXy*bRDGtPG~7owt~VrXfZyxJ1_eIBGJ zYmqawuHabC+`AvybC07SuCZ*pr2S%QIm!Avd))*?Y@#WoVmWZT*!5C9UhnEgBMJ#u ztW3+zRq9@uY_KJdkB`>`n5{PkFELkC`k3Wskx+e6`)m{bjMb;r>h~RIAr2S5hS7YT zh0@K;B}@BE;bZEjVv8+u&4eU;QXukKUf$l=>6QpPM@M9Lvczbk)!C;kVoC@z)MpgG znArZME|Kx`eQ+(<*r4?I8GTmU%67HScMv`IJ8;vSPOoj z@FqD0Didk8Yh~BmWCJtq6R0~~!SDW7x+-CcPlpx&SIzT@P|V88`f&>px^CEa!PGQ( z_xcGDqK?Q)^}s-{z{{Ea-7T^zdH@4Y;1Wyou6kfVi;-xR>*vO17Vf{cFC^I1V$9tj zIXC=_3>Jm0@1`g%`aTuVeX)`1P>UG`WBWf(e|M?Fr9UGJLND`(Xy8WnUxNU7p0S$( z3gJ7Kl;Gm&OQRJDBc5-*bjHLQ=VM``2y&+W`8smrvih@7=3pa^n<2a?>L$y(8nvO4 z*)*)%r8H5(!Cq+NwJjx~Q;)z|x=}5PuXW#+Se8)olqk!~{;%9xaxzrCbry`jto0`i zFT5>T=t-68h%qe11O!lWazrDmk($2+ z8V)lxkqft{Oloy4%f+}@DHMu{$noPnuR9=US=PTFS*w`2u8>2}G)$-i8&@5W0t&TsaZj^hEO6mTZHZf1Za4`-KQh$uRHTsql z8dANKoFf)ycHzR!azagNaR0hT@8@-R)?RO^x43S|>Lq!3`9;eI#A4aX(Lk|{5pj(q z#XlV5i&9&$E~eCY+;e#GpL{k8j-v)oXt>n&?fGIk+v1e&*^f-NRwgDl{E@EKdU_1a zx@azxukRabf9SJIR8kf1mVC09&Uw-DpC0FEz9^aCkPy@6Z1sRb`%=f=mXvtV0;`*s z?u&+}Eyh(cTCRKNyfYt#p>Llt>EU%xk!&g8Pi?0xzVPl2DI{~-ijp*0adwkK&)(Qf zYsF@7l`1m;Vf|<$9IhN68m`fz?Z?i+QJ|lTroO9*jBmATOP&$}Dh!6A_o(64yMeds zUF{(_M@Mqh{p{>4%E>9=XK`0@<}ZXkJ{*d}Pt*o9y7n5RzvfE7S0iX@XomFk^fxS# z1G#X8U?JE7Ln@EEz#KXraa#-?W~abm|M>CalN^FdceU>_5HM3M`xA(b_Nw*upt7CG z20}AY#=`6__U>ZkwJn^SM#Yn9?#y#iBAkAB%gC1ckhRj z?^?IZ*NqQ)SI${v7c66dJ*%HPKAzMN=n>WQT6|;>xAu-@Z)ZVe*@a63DQA=@nPxs5 z7ekoyjL{AI=8%$VeA`;42e4G0goORu=wcOUMD%$GZGdn80CKWcgfaC{J+?ZGA z-DPK34&3f`IYzKBX0Wb&V5?(*;uEC9!$bn2ciWh#StIZwVYkO2uX}zV&|Utwd>Iil zKTyT{pw6x2mJE$IDl;PduAin-S;Sr;@Xb8--yT`9OTA-(L5=Q(5+A element.address == address).index : null; - return index == null - ? base64Encode(hd.sign(message)) - : base64Encode(hd.derive(index).sign(message)); + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } } diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index e90f9cda4..a50ff68ad 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 5f2a33ab6..fa41de767 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -302,10 +302,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { final index = address != null ? walletAddresses.addresses .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) - .index - : null; - return index == null - ? base64Encode(hd.sign(message)) - : base64Encode(hd.derive(index).sign(message)); + .index : null; + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } } diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 30ed49e80..eb1f057d8 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 6162375b2..87be785ac 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -926,6 +926,8 @@ extern "C" return m_wallet->trustedDaemon(); } + // Coin Control // + CoinsInfoRow* coin(int index) { if (index >= 0 && index < m_coins_info.size()) { @@ -1020,6 +1022,13 @@ extern "C" m_coins->thaw(index); } + // Sign Messages // + + char *sign_message(char *message, char *address = "") + { + return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h index 74258ba4c..fa92a038d 100644 --- a/cw_monero/ios/Classes/monero_api.h +++ b/cw_monero/ios/Classes/monero_api.h @@ -32,7 +32,8 @@ void store(char *path); void set_trusted_daemon(bool arg); bool trusted_daemon(); +char *sign_message(char *message, char *address); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 17099db91..bc4fc9d38 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -149,3 +149,5 @@ typedef coin = Pointer Function(Int32 index); typedef freeze_coin = Void Function(Int32 index); typedef thaw_coin = Void Function(Int32 index); + +typedef sign_message = Pointer Function(Pointer message, Pointer address); diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 1d3904870..40a1e0321 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -149,3 +149,5 @@ typedef GetCoin = Pointer Function(int); typedef FreezeCoin = void Function(int); typedef ThawCoin = void Function(int); + +typedef SignMessage = Pointer Function(Pointer, Pointer); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 1680918e5..ffa5fe13b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart'; import 'package:cw_monero/api/monero_api.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; int _boolToInt(bool value) => value ? 1 : 0; @@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi .lookup>('trusted_daemon') .asFunction(); +final signMessageNative = moneroApi + .lookup>('sign_message') + .asFunction(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -296,7 +299,7 @@ class SyncListener { final bchHeight = await getNodeHeightOrUpdate(syncHeight); - if (_lastKnownBlockHeight == syncHeight || syncHeight == null) { + if (_lastKnownBlockHeight == syncHeight) { return; } @@ -311,7 +314,7 @@ class SyncListener { } // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents; - onNewBlock?.call(syncHeight, left, ptc); + onNewBlock.call(syncHeight, left, ptc); }); } @@ -382,4 +385,15 @@ String getSubaddressLabel(int accountIndex, int addressIndex) { Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); -Future trustedDaemon() async => trustedDaemonNative() != 0; \ No newline at end of file +Future trustedDaemon() async => trustedDaemonNative() != 0; + +String signMessage(String message, {String address = ""}) { + final messagePointer = message.toNativeUtf8(); + final addressPointer = address.toNativeUtf8(); + + final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer)); + calloc.free(messagePointer); + calloc.free(addressPointer); + + return signature; +} diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 71c7e3967..4b71fb5ff 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -651,4 +651,10 @@ abstract class MoneroWalletBase @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; + + @override + String signMessage(String message, {String? address}) { + final useAddress = address ?? ""; + return monero_wallet.signMessage(message, address: useAddress); + } } diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp index 6fabc29fd..fe75dea98 100644 --- a/cw_monero/macos/Classes/monero_api.cpp +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -1020,6 +1020,13 @@ extern "C" m_coins->thaw(index); } + // Sign Messages // + + char *sign_message(char *message, char *address = "") + { + return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/macos/Classes/monero_api.h b/cw_monero/macos/Classes/monero_api.h index 74258ba4c..fa92a038d 100644 --- a/cw_monero/macos/Classes/monero_api.h +++ b/cw_monero/macos/Classes/monero_api.h @@ -32,7 +32,8 @@ void store(char *path); void set_trusted_daemon(bool arg); bool trusted_daemon(); +char *sign_message(char *message, char *address); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart new file mode 100644 index 000000000..d5cf50d10 --- /dev/null +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -0,0 +1,179 @@ +import 'dart:convert'; + +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:url_launcher/url_launcher.dart'; + +class DFXBuyProvider { + DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet; + + final WalletBase _wallet; + + static const _baseUrl = 'api.dfx.swiss'; + static const _authPath = '/v1/auth/signMessage'; + static const _signUpPath = '/v1/auth/signUp'; + static const _signInPath = '/v1/auth/signIn'; + static const walletName = 'CakeWallet'; + + String get assetOut { + switch (_wallet.type) { + case WalletType.bitcoin: + return 'BTC'; + case WalletType.bitcoinCash: + return 'BCH'; + case WalletType.litecoin: + return 'LTC'; + case WalletType.monero: + return 'XMR'; + case WalletType.ethereum: + return 'ETH'; + default: + throw Exception("WalletType is not available for DFX ${_wallet.type}"); + } + } + + String get blockchain { + switch (_wallet.type) { + case WalletType.bitcoin: + case WalletType.bitcoinCash: + case WalletType.litecoin: + return 'Bitcoin'; + case WalletType.monero: + return 'Monero'; + case WalletType.ethereum: + return 'Ethereum'; + default: + throw Exception("WalletType is not available for DFX ${_wallet.type}"); + } + } + + Future getSignMessage() async { + final walletAddress = _wallet.walletAddresses.address; + final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); + + var response = await http.get(uri, headers: {'accept': 'application/json'}); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body); + return responseBody['message'] as String; + } else { + throw Exception( + 'Failed to get sign message. Status: ${response.statusCode} ${response.body}'); + } + } + + Future signUp() async { + final signMessage = getSignature(await getSignMessage()); + final walletAddress = _wallet.walletAddresses.address; + + final requestBody = jsonEncode({ + 'wallet': walletName, + 'address': walletAddress, + 'signature': signMessage, + }); + + final uri = Uri.https(_baseUrl, _signUpPath); + var response = await http.post(uri, + headers: {'Content-Type': 'application/json'}, body: requestBody); + + if (response.statusCode == 201) { + final responseBody = jsonDecode(response.body); + return responseBody['accessToken'] as String; + } else { + throw Exception( + 'Failed to sign up. Status: ${response.statusCode} ${response.body}'); + } + } + + Future signIn() async { + final signMessage = getSignature(await getSignMessage()); + final walletAddress = _wallet.walletAddresses.address; + + final requestBody = jsonEncode({ + 'address': walletAddress, + 'signature': signMessage, + }); + + final uri = Uri.https(_baseUrl, _signInPath); + var response = await http.post(uri, + headers: {'Content-Type': 'application/json'}, body: requestBody); + + if (response.statusCode == 201) { + final responseBody = jsonDecode(response.body); + return responseBody['accessToken'] as String; + } else { + throw Exception( + 'Failed to sign in. Status: ${response.statusCode} ${response.body}'); + } + } + + String getSignature(String message) { + switch (_wallet.type) { + case WalletType.ethereum: + return _wallet.signMessage(message); + case WalletType.monero: + case WalletType.litecoin: + case WalletType.bitcoin: + case WalletType.bitcoinCash: + return _wallet.signMessage(message, + address: _wallet.walletAddresses.address); + default: + throw Exception("WalletType is not available for DFX ${_wallet.type}"); + } + } + + Future launchProvider(BuildContext context) async { + try { + final assetOut = this.assetOut; + final blockchain = this.blockchain; + + String accessToken; + + try { + accessToken = await signUp(); + } on Exception catch (e) { + if (e.toString().contains('409')) { + accessToken = await signIn(); + } else { + rethrow; + } + } + + final uri = Uri.https('services.dfx.swiss', '/buy', { + 'session': accessToken, + 'lang': 'en', + 'asset-out': assetOut, + 'blockchain': blockchain, + 'asset-in': 'EUR', + }); + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.webViewPage, + arguments: [S.of(context).buy, uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: "DFX Connect", + alertContent: S.of(context).buy_provider_unavailable + ': $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + } +} diff --git a/lib/di.dart b/lib/di.dart index e0faa6db1..493ff84b9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -227,6 +227,7 @@ 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 'buy/dfx/dfx_buy_provider.dart'; import 'core/totp_request_details.dart'; import 'src/screens/settings/desktop_settings/desktop_settings_page.dart'; @@ -795,6 +796,9 @@ Future setup({ getIt.registerFactory( () => RobinhoodBuyProvider(wallet: getIt.get().wallet!)); + getIt.registerFactory( + () => DFXBuyProvider(wallet: getIt.get().wallet!)); + getIt.registerFactory(() => OnRamperBuyProvider( settingsStore: getIt.get().settingsStore, wallet: getIt.get().wallet!, @@ -940,7 +944,7 @@ Future setup({ getIt.registerFactory(() => BuyAmountViewModel()); - getIt.registerFactory(() => BuyOptionsPage()); + getIt.registerFactory(() => BuyOptionsPage(getIt.get())); getIt.registerFactory(() { final wallet = getIt.get().wallet; diff --git a/lib/entities/buy_provider_types.dart b/lib/entities/buy_provider_types.dart index 90c070e86..1faf2b281 100644 --- a/lib/entities/buy_provider_types.dart +++ b/lib/entities/buy_provider_types.dart @@ -1,9 +1,11 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cw_core/wallet_type.dart'; enum BuyProviderType { AskEachTime, Robinhood, - Onramper; + Onramper, + DFX; @override String toString() { @@ -14,6 +16,42 @@ enum BuyProviderType { return "Robinhood"; case BuyProviderType.Onramper: return "Onramper"; + case BuyProviderType.DFX: + return "DFX"; + } + } + + static List getAvailableProviders(WalletType walletType) { + switch (walletType) { + case WalletType.nano: + case WalletType.banano: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper + ]; + case WalletType.monero: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper, + BuyProviderType.DFX + ]; + case WalletType.bitcoin: + case WalletType.ethereum: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper, + BuyProviderType.DFX, + BuyProviderType.Robinhood + ]; + case WalletType.litecoin: + case WalletType.bitcoinCash: + return [ + BuyProviderType.AskEachTime, + BuyProviderType.Onramper, + BuyProviderType.Robinhood + ]; + default: + return []; } } } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 1ef388d31..4c629e768 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; @@ -44,48 +45,54 @@ class MainActions { isEnabled: (viewModel) => viewModel.isEnabledBuyAction, canShow: (viewModel) => viewModel.hasBuyAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { + if (!viewModel.isEnabledBuyAction) { + await _showErrorDialog(context, S.of(context).unsupported_asset); + return; + } + final defaultBuyProvider = viewModel.defaultBuyProvider; - final walletType = viewModel.type; - - if (!viewModel.isEnabledBuyAction) return; - - switch (walletType) { - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.polygon: - case WalletType.bitcoinCash: - switch (defaultBuyProvider) { - case BuyProviderType.AskEachTime: - Navigator.pushNamed(context, Routes.buy); - break; - case BuyProviderType.Onramper: - await getIt.get().launchProvider(context); - break; - case BuyProviderType.Robinhood: - await getIt.get().launchProvider(context); - break; - } - break; - case WalletType.nano: - case WalletType.banano: - case WalletType.monero: - await getIt.get().launchProvider(context); - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).buy, - alertContent: S.of(context).unsupported_asset, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + try { + await _launchProviderByType(context, defaultBuyProvider); + } catch (e) { + await _showErrorDialog(context, e.toString()); } }, ); + static Future _launchProviderByType(BuildContext context, BuyProviderType providerType) async { + switch (providerType) { + case BuyProviderType.AskEachTime: + Navigator.pushNamed(context, Routes.buy); + break; + case BuyProviderType.Onramper: + await getIt.get().launchProvider(context); + break; + case BuyProviderType.Robinhood: + await getIt.get().launchProvider(context); + break; + case BuyProviderType.DFX: + await getIt.get().launchProvider(context); + break; + default: + throw UnsupportedError('Unsupported buy provider type'); + } + } + + + static Future _showErrorDialog(BuildContext context, String errorMessage) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).buy, + alertContent: errorMessage, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + static MainActions receiveAction = MainActions._( name: (context) => S.of(context).receive, image: 'assets/images/received.png', diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 930878544..62dfc4ec4 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,18 +1,26 @@ +import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; class BuyOptionsPage extends BasePage { + BuyOptionsPage(this.dashboardViewModel); + + final DashboardViewModel dashboardViewModel; final iconDarkRobinhood = 'assets/images/robinhood_dark.png'; final iconLightRobinhood = 'assets/images/robinhood_light.png'; final iconDarkOnramper = 'assets/images/onramper_dark.png'; final iconLightOnramper = 'assets/images/onramper_light.png'; + final iconDarkDFX = 'assets/images/dfx_dark.png'; + final iconLightDFX = 'assets/images/dfx_light.png'; @override String get title => S.current.buy; @@ -22,11 +30,19 @@ class BuyOptionsPage extends BasePage { @override Widget body(BuildContext context) { - final isLightMode = Theme.of(context).extension()?.useDarkImage ?? false; - final iconRobinhood = - Image.asset(isLightMode ? iconLightRobinhood : iconDarkRobinhood, height: 40, width: 40); - final iconOnramper = - Image.asset(isLightMode ? iconLightOnramper : iconDarkOnramper, height: 40, width: 40); + final isLightMode = + Theme.of(context).extension()?.useDarkImage ?? false; + final iconRobinhood = Image.asset( + isLightMode ? iconLightRobinhood : iconDarkRobinhood, + height: 40, + width: 40); + final iconOnramper = Image.asset( + isLightMode ? iconLightOnramper : iconDarkOnramper, + height: 40, + width: 40); + final iconDFX = Image.asset(isLightMode ? iconLightDFX : iconDarkDFX, + height: 40, width: 40); + final availableProviders = dashboardViewModel.availableProviders; return Container( child: Center( @@ -34,26 +50,42 @@ class BuyOptionsPage extends BasePage { constraints: BoxConstraints(maxWidth: 330), child: Column( children: [ - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconOnramper, - title: "Onramper", - description: S.of(context).onramper_option_description, - onPressed: () async => - await getIt.get().launchProvider(context), + if (availableProviders.contains(BuyProviderType.Onramper)) + Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + image: iconOnramper, + title: "Onramper", + description: S.of(context).onramper_option_description, + onPressed: () async => await getIt + .get() + .launchProvider(context), + ), ), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconRobinhood, - title: "Robinhood Connect", - description: S.of(context).robinhood_option_description, - onPressed: () async => - await getIt.get().launchProvider(context), + if (availableProviders.contains(BuyProviderType.Robinhood)) + Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + image: iconRobinhood, + title: "Robinhood Connect", + description: S.of(context).robinhood_option_description, + onPressed: () async => await getIt + .get() + .launchProvider(context), + ), + ), + if (availableProviders.contains(BuyProviderType.DFX)) + Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + image: iconDFX, + title: "DFX Connect", + description: S.of(context).dfx_option_description, + onPressed: () async => await getIt + .get() + .launchProvider(context), + ), ), - ), Spacer(), Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), @@ -63,7 +95,9 @@ class BuyOptionsPage extends BasePage { style: TextStyle( fontSize: 14, fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.detailsTitlesColor, + color: Theme.of(context) + .extension()! + .detailsTitlesColor, ), ), ), diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index ede816893..cc92641f3 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -1,5 +1,3 @@ -import 'package:cake_wallet/buy/buy_provider.dart'; -import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -42,9 +40,10 @@ class OtherSettingsPage extends BasePage { handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.changeRep), ), + if(_otherSettingsViewModel.isEnabledBuyAction) SettingsPickerCell( title: S.current.default_buy_provider, - items: BuyProviderType.values, + items: _otherSettingsViewModel.availableBuyProviders, displayItem: _otherSettingsViewModel.getBuyProviderType, selectedItem: _otherSettingsViewModel.buyProviderType, onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 3e38fed1f..3984bd81a 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -36,7 +36,6 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cw_core/set_app_secure_native.dart'; - part 'settings_store.g.dart'; class SettingsStore = SettingsStoreBase with _$SettingsStore; @@ -123,7 +122,6 @@ abstract class SettingsStoreBase with Store { isAppSecure = initialAppSecure, disableBuy = initialDisableBuy, disableSell = initialDisableSell, - defaultBuyProvider = initialDefaultBuyProvider, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, exchangeStatus = initialExchangeStatus, currentTheme = initialTheme, @@ -178,6 +176,12 @@ abstract class SettingsStoreBase with Store { initializeTrocadorProviderStates(); + WalletType.values.forEach((walletType) { + final key = 'defaultBuyProvider_${walletType.toString()}'; + final providerIndex = sharedPreferences.getInt(key); + defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime; + }); + reaction( (_) => fiatCurrency, (FiatCurrency fiatCurrency) => sharedPreferences.setString( @@ -244,9 +248,14 @@ abstract class SettingsStoreBase with Store { sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell)); reaction( - (_) => defaultBuyProvider, - (BuyProviderType defaultBuyProvider) => - sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider.index)); + (_) => defaultBuyProviders.asObservable(), + (ObservableMap providers) { + providers.forEach((walletType, provider) { + final key = 'defaultBuyProvider_${walletType.toString()}'; + sharedPreferences.setInt(key, provider.index); + }); + } + ); reaction( (_) => autoGenerateSubaddressStatus, @@ -333,6 +342,7 @@ abstract class SettingsStoreBase with Store { reaction((_) => totpSecretKey, (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey)); + reaction( (_) => numberOfFailedTokenTrials, (int failedTokenTrail) => @@ -494,9 +504,6 @@ abstract class SettingsStoreBase with Store { @observable bool disableSell; - @observable - BuyProviderType defaultBuyProvider; - @observable bool allowBiometricalAuthentication; @@ -566,6 +573,9 @@ abstract class SettingsStoreBase with Store { @observable ObservableMap trocadorProviderStates = ObservableMap(); + @observable + ObservableMap defaultBuyProviders = ObservableMap(); + @observable SortBalanceBy sortBalanceBy; @@ -1003,8 +1013,6 @@ 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]; allowBiometricalAuthentication = sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index fbb2fc76f..84ba54202 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -282,10 +282,14 @@ abstract class DashboardViewModelBase with Store { Map> filterItems; - BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider; + BuyProviderType get defaultBuyProvider => + settingsStore.defaultBuyProviders[wallet.type] ?? + BuyProviderType.AskEachTime; bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; + List get availableProviders => BuyProviderType.getAvailableProviders(wallet.type); + bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; @action diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index b4ca46f70..4e712dae1 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -13,14 +13,15 @@ import 'package:package_info/package_info.dart'; part 'other_settings_view_model.g.dart'; -class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; +class OtherSettingsViewModel = OtherSettingsViewModelBase + with _$OtherSettingsViewModel; abstract class OtherSettingsViewModelBase with Store { OtherSettingsViewModelBase(this._settingsStore, this._wallet) : walletType = _wallet.type, currentVersion = '' { - PackageInfo.fromPlatform() - .then((PackageInfo packageInfo) => currentVersion = packageInfo.version); + PackageInfo.fromPlatform().then( + (PackageInfo packageInfo) => currentVersion = packageInfo.version); final priority = _settingsStore.priority[_wallet.type]; final priorities = priorityForWalletType(_wallet.type); @@ -31,7 +32,8 @@ abstract class OtherSettingsViewModelBase with Store { } final WalletType walletType; - final WalletBase, TransactionInfo> _wallet; + final WalletBase, + TransactionInfo> _wallet; @observable String currentVersion; @@ -50,15 +52,19 @@ abstract class OtherSettingsViewModelBase with Store { } @computed - bool get changeRepresentativeEnabled { - if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) { - return true; - } + bool get changeRepresentativeEnabled => + _wallet.type == WalletType.nano || _wallet.type == WalletType.banano; - return false; - } - - BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; } + @computed + bool get isEnabledBuyAction => + !_settingsStore.disableBuy && _wallet.type != WalletType.haven; + + List get availableBuyProviders => + BuyProviderType.getAvailableProviders(walletType); + + BuyProviderType get buyProviderType => + _settingsStore.defaultBuyProviders[walletType] ?? + BuyProviderType.AskEachTime; String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -73,7 +79,7 @@ abstract class OtherSettingsViewModelBase with Store { return priority.toString(); } - String getBuyProviderType (dynamic buyProviderType) { + String getBuyProviderType(dynamic buyProviderType) { final _buyProviderType = buyProviderType as BuyProviderType; return _buyProviderType.toString(); @@ -83,6 +89,5 @@ abstract class OtherSettingsViewModelBase with Store { _settingsStore.priority[_wallet.type] = priority; void onBuyProviderTypeSelected(BuyProviderType buyProviderType) => - _settingsStore.defaultBuyProvider = buyProviderType; - + _settingsStore.defaultBuyProviders[walletType] = buyProviderType; } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a4738bf82..b87347630 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -96,7 +96,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 fluttertoast: 8.1.4 # tor: # git: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 45ad96764..269400ffc 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -746,5 +746,6 @@ "seed_language_czech": "التشيكية", "seed_language_korean": "الكورية", "seed_language_chinese_traditional": "تقاليد صينية)", + "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index d52843798..27c54cde5 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -742,5 +742,6 @@ "seed_language_czech": "Чех", "seed_language_korean": "Корейски", "seed_language_chinese_traditional": "Традиционен китайски)", + "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", "polygonscan_history": "История на PolygonScan" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 3e1d68543..10ed3b0e9 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -742,5 +742,6 @@ "seed_language_czech": "čeština", "seed_language_korean": "korejština", "seed_language_chinese_traditional": "Číňan (tradiční)", + "dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě", "polygonscan_history": "Historie PolygonScan" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 62a73361c..eef930971 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -750,5 +750,6 @@ "seed_language_czech": "Tschechisch", "seed_language_korean": "Koreanisch", "seed_language_chinese_traditional": "Chinesisch (Traditionell)", + "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa", "polygonscan_history": "PolygonScan-Verlauf" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 19c435ce8..053ed0488 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -751,5 +751,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Chinese (Traditional)", + "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe", "polygonscan_history": "PolygonScan history" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 2b94e51b9..fcdaf17b1 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -749,6 +749,8 @@ "seedtype_polyseed": "Polieta (16 palabras)", "seed_language_czech": "checo", "seed_language_korean": "coreano", + "seed_language_chinese_traditional": "Chino tradicional)", + "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", "seed_language_chinese_traditional": "Chino (tradicional)", "polygonscan_history": "Historial de PolygonScan" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 0c71ba06e..3fe73f0b4 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -750,5 +750,6 @@ "seed_language_czech": "tchèque", "seed_language_korean": "coréen", "seed_language_chinese_traditional": "Chinois (Traditionnel)", + "dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe", "polygonscan_history": "Historique de PolygonScan" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 0eb9bc7cd..d35f3a89e 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -728,5 +728,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Yaren Koriya", "seed_language_chinese_traditional": "Sinanci (na gargajiya)", + "dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai", "polygonscan_history": "PolygonScan tarihin kowane zamani" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c4c11fa58..ce4ebc453 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -750,5 +750,6 @@ "seed_language_czech": "चेक", "seed_language_korean": "कोरियाई", "seed_language_chinese_traditional": "चीनी पारंपरिक)", + "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 82df37e69..4ab734f9f 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -747,6 +747,8 @@ "seedtype_polyseed": "Poliseed (16 riječi)", "seed_language_czech": "češki", "seed_language_korean": "korejski", + "seed_language_chinese_traditional": "Kinesko tradicionalno)", + "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", "seed_language_chinese_traditional": "Kinesko (tradicionalno)", "polygonscan_history": "Povijest PolygonScan" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index de04ecb12..041bbd93c 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -737,6 +737,8 @@ "seedtype_polyseed": "Polyseed (16 kata)", "seed_language_czech": "Ceko", "seed_language_korean": "Korea", + "seed_language_chinese_traditional": "Cina tradisional)", + "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", "seed_language_chinese_traditional": "Cina (tradisional)", "polygonscan_history": "Sejarah PolygonScan" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index acb7a71c3..250edda6b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -750,5 +750,6 @@ "seed_language_czech": "ceco", "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Cinese tradizionale)", + "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa", "polygonscan_history": "Cronologia PolygonScan" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 7907d3835..37b5e60fd 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -750,5 +750,6 @@ "seed_language_czech": "チェコ", "seed_language_korean": "韓国語", "seed_language_chinese_traditional": "中国の伝統的な)", + "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", "polygonscan_history": "ポリゴンスキャン履歴" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e9a54974e..611915f56 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -748,5 +748,6 @@ "seed_language_czech": "체코 사람", "seed_language_korean": "한국인", "seed_language_chinese_traditional": "중국 전통)", + "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", "polygonscan_history": "다각형 스캔 기록" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 02e3d38c2..cf12b9b16 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -748,5 +748,6 @@ "seed_language_czech": "ချက်", "seed_language_korean": "ကိုးရီးယား", "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)", + "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", "polygonscan_history": "PolygonScan မှတ်တမ်း" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 4fd0fbab7..b36a5a234 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -749,6 +749,8 @@ "seedtype_polyseed": "Polyseed (16 woorden)", "seed_language_czech": "Tsjechisch", "seed_language_korean": "Koreaans", + "seed_language_chinese_traditional": "Chinese traditionele)", + "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", "seed_language_chinese_traditional": "Chinese (traditionele)", "polygonscan_history": "PolygonScan-geschiedenis" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index bf3cc5329..afb9bf9ec 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -750,5 +750,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "koreański", "seed_language_chinese_traditional": "Chiński tradycyjny)", + "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie", "polygonscan_history": "Historia PolygonScan" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ba4beb6c5..c2fe03ad3 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -749,5 +749,6 @@ "seed_language_czech": "Tcheco", "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Chinês tradicional)", + "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa", "polygonscan_history": "História do PolygonScan" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 83a1aaf2b..107cbf596 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -750,5 +750,6 @@ "seed_language_czech": "Чешский", "seed_language_korean": "Корейский", "seed_language_chinese_traditional": "Китайский традиционный)", + "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", "polygonscan_history": "История PolygonScan" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index e046eaba8..134868c08 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -748,5 +748,6 @@ "seed_language_czech": "ภาษาเช็ก", "seed_language_korean": "เกาหลี", "seed_language_chinese_traditional": "จีน (ดั้งเดิม)", + "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", "polygonscan_history": "ประวัติ PolygonScan" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 06ee4bc89..d12e89378 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -744,5 +744,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Intsik (tradisyonal)", + "dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe", "polygonscan_history": "Kasaysayan ng PolygonScan" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index f300951ba..30a354caa 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -748,5 +748,6 @@ "seed_language_czech": "Çek", "seed_language_korean": "Koreli", "seed_language_chinese_traditional": "Çin geleneği)", + "dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için", "polygonscan_history": "PolygonScan geçmişi" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 6f8d87e86..f977308b5 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -749,6 +749,7 @@ "seedtype_polyseed": "Полісей (16 слів)", "seed_language_czech": "Чеський", "seed_language_korean": "Корейський", + "dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі", "seed_language_chinese_traditional": "Китайський (традиційний)", "polygonscan_history": "Історія PolygonScan" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index f75d287ef..be7525c1b 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -742,5 +742,6 @@ "seed_language_czech": "چیک", "seed_language_korean": "کورین", "seed_language_chinese_traditional": "چینی (روایتی)", + "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index b5937a49c..63d21666e 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -744,5 +744,6 @@ "seed_language_czech": "Czech", "seed_language_korean": "Ara ẹni", "seed_language_chinese_traditional": "Kannada (ibile)", + "dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu", "polygonscan_history": "PolygonScan itan" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index b487f83f5..25710eff5 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -749,5 +749,6 @@ "seed_language_czech": "捷克", "seed_language_korean": "韩国人", "seed_language_chinese_traditional": "中国传统的)", + "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", "polygonscan_history": "多边形扫描历史" } From 72777fbf2c33086d21c74bff45e684ed4973e594 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Sat, 9 Dec 2023 02:37:39 +0100 Subject: [PATCH 17/18] Cw 534 monero wallet generic enhancements (#1225) * Fix word count for polyseed on pre_seed_page * Fix naming of advanced setting * Set default Seed Type to Polyseed and disable bg-sync by default * Fix inconsistency between seed type field in restore screen and the one in advanced settings * Use pub.dev release of polyseed * Add Legacy Seed to wallet_keys-Page in case of Polyseed --- cw_monero/lib/monero_wallet_service.dart | 3 +- cw_monero/pubspec.lock | 36 +++++++++++-------- cw_monero/pubspec.yaml | 4 +-- lib/di.dart | 11 +++--- lib/entities/seed_type.dart | 2 +- lib/router.dart | 12 +++---- .../advanced_privacy_settings_page.dart | 20 +++++++---- .../screens/new_wallet/new_wallet_page.dart | 20 +++++------ .../wallet_restore_from_seed_form.dart | 24 +++++++++++-- .../screens/restore/wallet_restore_page.dart | 5 ++- lib/src/screens/seed/pre_seed_page.dart | 16 ++++++--- lib/store/settings_store.dart | 2 +- .../advanced_privacy_settings_view_model.dart | 7 ---- lib/view_model/seed_type_view_model.dart | 19 ++++++++++ lib/view_model/wallet_keys_view_model.dart | 29 +++++++++++++++ pubspec_base.yaml | 4 +-- res/values/strings_ar.arb | 3 +- res/values/strings_bg.arb | 3 +- res/values/strings_cs.arb | 3 +- res/values/strings_de.arb | 3 +- res/values/strings_en.arb | 3 +- res/values/strings_es.arb | 5 +-- res/values/strings_fr.arb | 3 +- res/values/strings_ha.arb | 3 +- res/values/strings_hi.arb | 3 +- res/values/strings_hr.arb | 5 +-- res/values/strings_id.arb | 5 +-- res/values/strings_it.arb | 3 +- res/values/strings_ja.arb | 3 +- res/values/strings_ko.arb | 3 +- res/values/strings_my.arb | 3 +- res/values/strings_nl.arb | 5 +-- res/values/strings_pl.arb | 3 +- res/values/strings_pt.arb | 3 +- res/values/strings_ru.arb | 3 +- res/values/strings_th.arb | 3 +- res/values/strings_tl.arb | 3 +- res/values/strings_tr.arb | 3 +- res/values/strings_uk.arb | 5 +-- res/values/strings_ur.arb | 3 +- res/values/strings_yo.arb | 3 +- res/values/strings_zh.arb | 3 +- 42 files changed, 203 insertions(+), 99 deletions(-) create mode 100644 lib/view_model/seed_type_view_model.dart diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index a6b3227b2..9b9e254d0 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -13,7 +13,6 @@ import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/monero_wallet.dart'; import 'package:hive/hive.dart'; import 'package:polyseed/polyseed.dart'; -import 'package:polyseed/src/utils/key_utils.dart'; class MoneroNewWalletCredentials extends WalletCredentials { MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password}) @@ -275,7 +274,7 @@ class MoneroWalletService extends WalletService< {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async { final height = overrideHeight ?? getMoneroHeigthByDate( date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); - final spendKey = keyToHexString(polyseed.generateKey(coin, 32)); + final spendKey = polyseed.generateKey(coin, 32).toHexString(); final seed = polyseed.encode(lang, coin); walletInfo.isRecovery = true; diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 46ebe8028..0f8f2c90e 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -266,6 +266,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + hashlib: + dependency: transitive + description: + name: hashlib + sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + hashlib_codecs: + dependency: transitive + description: + name: hashlib_codecs + sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" + url: "https://pub.dev" + source: hosted + version: "2.2.0" hive: dependency: transitive description: @@ -485,12 +501,11 @@ packages: polyseed: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7" - url: "https://github.com/cake-tech/polyseed_dart.git" - source: git - version: "0.0.1" + name: polyseed + sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f" + url: "https://pub.dev" + source: hosted + version: "0.0.2" pool: dependency: transitive description: @@ -632,15 +647,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tor: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 55e9b354c..a6fe7f967 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -19,9 +19,7 @@ dependencies: flutter_mobx: ^2.0.6+1 intl: ^0.18.0 encrypt: ^5.0.1 - polyseed: - git: - url: https://github.com/cake-tech/polyseed_dart.git + polyseed: ^0.0.2 cw_core: path: ../cw_core diff --git a/lib/di.dart b/lib/di.dart index 493ff84b9..15d008dca 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -90,6 +90,7 @@ import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dar import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; @@ -719,6 +720,8 @@ Future setup({ getIt.registerFactory(() => WalletSeedViewModel(getIt.get().wallet!)); + getIt.registerFactory(() => SeedTypeViewModel(getIt.get())); + getIt.registerFactoryParam((bool isWalletCreated, _) => WalletSeedPage(getIt.get(), isNewWalletCreated: isWalletCreated)); @@ -850,7 +853,7 @@ Future setup({ return ethereum!.createEthereumWalletService(_walletInfoSource); case WalletType.bitcoinCash: return bitcoinCash! - .createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!); + .createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource); case WalletType.nano: return nano!.createNanoWalletService(_walletInfoSource); case WalletType.polygon: @@ -879,8 +882,8 @@ Future setup({ getIt.get(), getIt.get(param1: type), _walletInfoSource, type: type)); - getIt.registerFactoryParam( - (type, _) => WalletRestorePage(getIt.get(param1: type))); + getIt.registerFactoryParam((type, _) => WalletRestorePage( + getIt.get(param1: type), getIt.get())); getIt.registerFactoryParam, void>( (derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations)); @@ -911,7 +914,7 @@ Future setup({ getIt.registerFactoryParam( (WalletType type, AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel) => - PreSeedPage(type, advancedPrivacySettingsViewModel)); + PreSeedPage(type, advancedPrivacySettingsViewModel, getIt.get())); getIt.registerFactoryParam((trade, _) => TradeDetailsViewModel( diff --git a/lib/entities/seed_type.dart b/lib/entities/seed_type.dart index 0a1a191c6..ab9965528 100644 --- a/lib/entities/seed_type.dart +++ b/lib/entities/seed_type.dart @@ -6,7 +6,7 @@ class SeedType extends EnumerableItem with Serializable { static const all = [SeedType.legacy, SeedType.polyseed]; - static const defaultSeedType = legacy; + static const defaultSeedType = polyseed; static const legacy = SeedType(raw: 0, title: 'Legacy (25 words)'); static const polyseed = SeedType(raw: 1, title: 'Polyseed (16 words)'); diff --git a/lib/router.dart b/lib/router.dart index 313751ab3..11eeb582b 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -56,13 +56,13 @@ import 'package:cake_wallet/src/screens/support_other_links/support_other_links_ import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; -import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/nano_account.dart'; import 'package:cw_core/wallet_info.dart'; @@ -147,9 +147,9 @@ Route createRoute(RouteSettings settings) { case Routes.newWallet: final type = settings.arguments as WalletType; final walletNewVM = getIt.get(param1: type); - final settingsStore = getIt.get(); + final seedTypeViewModel = getIt.get(); - return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM, settingsStore)); + return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel)); case Routes.setupPin: Function(PinCodeState, String)? callback; @@ -535,9 +535,9 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => AdvancedPrivacySettingsPage( - getIt.get(param1: type), - getIt.get(param1: type, param2: false), - )); + getIt.get(param1: type), + getIt.get(param1: type, param2: false), + getIt.get())); case Routes.anonPayInvoicePage: final args = settings.arguments as List; diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index 4b61e7889..a4bd6c7b9 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell. import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; @@ -18,25 +19,30 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; class AdvancedPrivacySettingsPage extends BasePage { - AdvancedPrivacySettingsPage(this.advancedPrivacySettingsViewModel, this.nodeViewModel); + AdvancedPrivacySettingsPage( + this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel); final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; final NodeCreateOrEditViewModel nodeViewModel; + final SeedTypeViewModel seedTypeViewModel; @override String get title => S.current.privacy_settings; @override - Widget body(BuildContext context) => - AdvancedPrivacySettingsBody(advancedPrivacySettingsViewModel, nodeViewModel); + Widget body(BuildContext context) => AdvancedPrivacySettingsBody( + advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel); } class AdvancedPrivacySettingsBody extends StatefulWidget { - const AdvancedPrivacySettingsBody(this.privacySettingsViewModel, this.nodeViewModel, {Key? key}) + const AdvancedPrivacySettingsBody( + this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel, + {Key? key}) : super(key: key); final AdvancedPrivacySettingsViewModel privacySettingsViewModel; final NodeCreateOrEditViewModel nodeViewModel; + final SeedTypeViewModel seedTypeViewModel; @override _AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState(); @@ -59,7 +65,7 @@ class _AdvancedPrivacySettingsBodyState extends State( - title: S.current.disable_fiat, + title: S.current.fiat_api, items: FiatApiMode.all, selectedItem: widget.privacySettingsViewModel.fiatApiMode, onItemSelected: (FiatApiMode mode) => @@ -114,8 +120,8 @@ class _AdvancedPrivacySettingsBodyState extends State( title: S.current.seedtype, items: SeedType.all, - selectedItem: widget.privacySettingsViewModel.seedType, - onItemSelected: widget.privacySettingsViewModel.setSeedType, + selectedItem: widget.seedTypeViewModel.moneroSeedType, + onItemSelected: widget.seedTypeViewModel.setMoneroSeedType, ), ); }), diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 5c9533ace..8f913e229 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; @@ -23,13 +24,12 @@ import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/entities/seed_type.dart'; -import 'package:cake_wallet/store/settings_store.dart'; class NewWalletPage extends BasePage { - NewWalletPage(this._walletNewVM, this._settingsStore); + NewWalletPage(this._walletNewVM, this._seedTypeViewModel); final WalletNewVM _walletNewVM; - final SettingsStore _settingsStore; + final SeedTypeViewModel _seedTypeViewModel; final walletNameImage = Image.asset('assets/images/wallet_name.png'); @@ -40,15 +40,15 @@ class NewWalletPage extends BasePage { @override Widget body(BuildContext context) => WalletNameForm( - _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _settingsStore); + _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _seedTypeViewModel); } class WalletNameForm extends StatefulWidget { - WalletNameForm(this._walletNewVM, this.walletImage, this._settingsStore); + WalletNameForm(this._walletNewVM, this.walletImage, this._seedTypeViewModel); final WalletNewVM _walletNewVM; final Image walletImage; - final SettingsStore _settingsStore; + final SeedTypeViewModel _seedTypeViewModel; @override _WalletNameFormState createState() => _WalletNameFormState(_walletNewVM); @@ -194,7 +194,7 @@ class _WalletNameFormState extends State { builder: (BuildContext build) => Padding( padding: EdgeInsets.only(top: 24), child: SelectButton( - text: widget._settingsStore.moneroSeedType.title, + text: widget._seedTypeViewModel.moneroSeedType.title, onTap: () async { await showPopUp( context: context, @@ -217,7 +217,7 @@ class _WalletNameFormState extends State { key: _languageSelectorKey, initialSelected: defaultSeedLanguage, seedType: _walletNewVM.hasSeedType - ? widget._settingsStore.moneroSeedType + ? widget._seedTypeViewModel.moneroSeedType : SeedType.legacy, ), ), @@ -277,10 +277,10 @@ class _WalletNameFormState extends State { } } - bool get isPolyseed => widget._settingsStore.moneroSeedType == SeedType.polyseed; + bool get isPolyseed => widget._seedTypeViewModel.moneroSeedType == SeedType.polyseed; void _setSeedType(SeedType item) { - widget._settingsStore.moneroSeedType = item; + widget._seedTypeViewModel.setMoneroSeedType(item); _languageSelectorKey.currentState?.selected = defaultSeedLanguage; // Reset Seed language } } diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index f31657743..6f8f9eb2b 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -9,8 +9,10 @@ import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; import 'package:cake_wallet/src/widgets/seed_widget.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; import 'package:polyseed/polyseed.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { @@ -19,6 +21,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget { required this.displayLanguageSelector, required this.displayBlockHeightSelector, required this.type, + required this.seedTypeViewModel, this.blockHeightFocusNode, this.onHeightOrDateEntered, this.onSeedChange, @@ -28,6 +31,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget { final WalletType type; final bool displayLanguageSelector; final bool displayBlockHeightSelector; + final SeedTypeViewModel seedTypeViewModel; final FocusNode? blockHeightFocusNode; final Function(bool)? onHeightOrDateEntered; final void Function(String)? onSeedChange; @@ -52,16 +56,28 @@ class WalletRestoreFromSeedFormState extends State { final TextEditingController nameTextEditingController; final TextEditingController seedTypeController; final GlobalKey formKey; + late ReactionDisposer moneroSeedTypeReaction; String language; - bool isPolyseed = false; @override void initState() { + _setSeedType(widget.seedTypeViewModel.moneroSeedType); _setLanguageLabel(language); - _setSeedType(SeedType.defaultSeedType); + moneroSeedTypeReaction = + reaction((_) => widget.seedTypeViewModel.moneroSeedType, (SeedType item) { + _setSeedType(item); + _changeLanguage('English'); + }); + super.initState(); } + @override + void dispose() { + super.dispose(); + moneroSeedTypeReaction(); + } + void onSeedChange(String seed) { if (widget.type == WalletType.monero && Polyseed.isValidSeed(seed)) { final lang = PolyseedLang.getByPhrase(seed); @@ -181,6 +197,8 @@ class WalletRestoreFromSeedFormState extends State { ])); } + bool get isPolyseed => widget.seedTypeViewModel.moneroSeedType == SeedType.polyseed; + Widget get expandIcon => Container( padding: EdgeInsets.all(18), width: 24, @@ -208,10 +226,10 @@ class WalletRestoreFromSeedFormState extends State { void _changeSeedType(SeedType item) { _setSeedType(item); _changeLanguage('English'); + widget.seedTypeViewModel.setMoneroSeedType(item); } void _setSeedType(SeedType item) { - setState(() => isPolyseed = item == SeedType.polyseed); seedTypeController.text = item.toString(); } } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 445858f30..899aacd19 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -15,6 +15,7 @@ import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/wallet_info.dart'; @@ -27,7 +28,7 @@ import 'package:polyseed/polyseed.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; class WalletRestorePage extends BasePage { - WalletRestorePage(this.walletRestoreViewModel) + WalletRestorePage(this.walletRestoreViewModel, this.seedTypeViewModel) : walletRestoreFromSeedFormKey = GlobalKey(), walletRestoreFromKeysFormKey = GlobalKey(), _pages = [], @@ -37,6 +38,7 @@ class WalletRestorePage extends BasePage { switch (mode) { case WalletRestoreMode.seed: _pages.add(WalletRestoreFromSeedForm( + seedTypeViewModel: seedTypeViewModel, displayBlockHeightSelector: walletRestoreViewModel.hasBlockchainHeightLanguageSelector, displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, @@ -91,6 +93,7 @@ class WalletRestorePage extends BasePage { )); final WalletRestoreViewModel walletRestoreViewModel; + final SeedTypeViewModel seedTypeViewModel; final PageController _controller; final List _pages; final GlobalKey walletRestoreFromSeedFormKey; diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index a45d81fc0..64ef5da97 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -1,6 +1,8 @@ +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; @@ -10,19 +12,21 @@ import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; class PreSeedPage extends BasePage { - PreSeedPage(this.type, this.advancedPrivacySettingsViewModel) + PreSeedPage(this.type, this.advancedPrivacySettingsViewModel, this.seedTypeViewModel) : imageLight = Image.asset('assets/images/pre_seed_light.png'), imageDark = Image.asset('assets/images/pre_seed_dark.png'), - seedPhraseLength = - advancedPrivacySettingsViewModel.seedPhraseLength.value { - wordsCount = _wordsCount(type, seedPhraseLength); + seedPhraseLength = advancedPrivacySettingsViewModel.seedPhraseLength.value, + moneroSeedType = seedTypeViewModel.moneroSeedType { + wordsCount = _wordsCount(type, seedPhraseLength, moneroSeedType); } final Image imageDark; final Image imageLight; final WalletType type; final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; + final SeedTypeViewModel seedTypeViewModel; final int seedPhraseLength; + final SeedType moneroSeedType; late final int wordsCount; @override @@ -76,9 +80,11 @@ class PreSeedPage extends BasePage { )); } - static int _wordsCount(WalletType type, int seedPhraseLength) { + static int _wordsCount(WalletType type, int seedPhraseLength, SeedType moneroSeedType) { switch (type) { case WalletType.monero: + if (moneroSeedType == SeedType.polyseed) + return 16; return 25; case WalletType.ethereum: case WalletType.bitcoinCash: diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 3984bd81a..4078827d0 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -877,7 +877,7 @@ abstract class SettingsStoreBase with Store { } final savedSyncMode = SyncMode.all.firstWhere((element) { - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); + return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0); }); final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index f62815346..483f0837b 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -1,7 +1,6 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; -import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -20,9 +19,6 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @computed FiatApiMode get fiatApiMode => _settingsStore.fiatApiMode; - @computed - SeedType get seedType => _settingsStore.moneroSeedType; - @observable bool _addCustomNode = false; @@ -44,9 +40,6 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @action void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode; - @action - void setSeedType(SeedType seedType) => _settingsStore.moneroSeedType = seedType; - @action void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value; diff --git a/lib/view_model/seed_type_view_model.dart b/lib/view_model/seed_type_view_model.dart new file mode 100644 index 000000000..e3597be1c --- /dev/null +++ b/lib/view_model/seed_type_view_model.dart @@ -0,0 +1,19 @@ +import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:mobx/mobx.dart'; + +part 'seed_type_view_model.g.dart'; + +class SeedTypeViewModel = SeedTypeViewModelBase with _$SeedTypeViewModel; + +abstract class SeedTypeViewModelBase with Store { + SeedTypeViewModelBase(this._appStore); + + @computed + SeedType get moneroSeedType => _appStore.settingsStore.moneroSeedType; + + @action + void setMoneroSeedType(SeedType seedType) => _appStore.settingsStore.moneroSeedType = seedType; + + final AppStore _appStore; +} diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 56ca190be..f931fec19 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cw_monero/api/wallet.dart' as monero_wallet; +import 'package:polyseed/polyseed.dart'; part 'wallet_keys_view_model.g.dart'; @@ -74,6 +75,15 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); + + if (_appStore.wallet?.seed != null && Polyseed.isValidSeed(_appStore.wallet!.seed!)) { + final lang = PolyseedLang.getByPhrase(_appStore.wallet!.seed!); + final legacyLang = _getLegacySeedLang(lang); + final legacySeed = + Polyseed.decode(_appStore.wallet!.seed!, lang, PolyseedCoin.POLYSEED_MONERO) + .toLegacySeed(legacyLang); + items.add(StandartListItem(title: S.current.wallet_seed_legacy, value: legacySeed)); + } } if (_appStore.wallet!.type == WalletType.haven) { @@ -207,4 +217,23 @@ abstract class WalletKeysViewModelBase with Store { } String getRoundedRestoreHeight(int height) => ((height / 1000).floor() * 1000).toString(); + + LegacySeedLang _getLegacySeedLang(PolyseedLang lang) { + switch (lang.nameEnglish) { + case "Spanish": + return LegacySeedLang.getByEnglishName("Spanish"); + case "French": + return LegacySeedLang.getByEnglishName("French"); + case "Italian": + return LegacySeedLang.getByEnglishName("Italian"); + case "Japanese": + return LegacySeedLang.getByEnglishName("Japanese"); + case "Portuguese": + return LegacySeedLang.getByEnglishName("Portuguese"); + case "Chinese (Simplified)": + return LegacySeedLang.getByEnglishName("Chinese (simplified)"); + default: + return LegacySeedLang.getByEnglishName("English"); + } + } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index b87347630..b85171ed0 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -104,9 +104,7 @@ dependencies: # ref: main socks5_proxy: ^1.0.4 flutter_svg: ^2.0.9 - polyseed: - git: - url: https://github.com/cake-tech/polyseed_dart.git + polyseed: ^0.0.2 dev_dependencies: flutter_test: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 269400ffc..d1c59cfbb 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -747,5 +747,6 @@ "seed_language_korean": "الكورية", "seed_language_chinese_traditional": "تقاليد صينية)", "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", - "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ" + "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", + "wallet_seed_legacy": "بذرة محفظة قديمة" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 27c54cde5..e0ec30144 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -743,5 +743,6 @@ "seed_language_korean": "Корейски", "seed_language_chinese_traditional": "Традиционен китайски)", "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", - "polygonscan_history": "История на PolygonScan" + "polygonscan_history": "История на PolygonScan", + "wallet_seed_legacy": "Наследено портфейл семе" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 10ed3b0e9..00de47f3a 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -743,5 +743,6 @@ "seed_language_korean": "korejština", "seed_language_chinese_traditional": "Číňan (tradiční)", "dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě", - "polygonscan_history": "Historie PolygonScan" + "polygonscan_history": "Historie PolygonScan", + "wallet_seed_legacy": "Starší semeno peněženky" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index eef930971..ca3cda44f 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -751,5 +751,6 @@ "seed_language_korean": "Koreanisch", "seed_language_chinese_traditional": "Chinesisch (Traditionell)", "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa", - "polygonscan_history": "PolygonScan-Verlauf" + "polygonscan_history": "PolygonScan-Verlauf", + "wallet_seed_legacy": "Legacy Wallet Seed" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 053ed0488..c8667afa9 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -752,5 +752,6 @@ "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Chinese (Traditional)", "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe", - "polygonscan_history": "PolygonScan history" + "polygonscan_history": "PolygonScan history", + "wallet_seed_legacy": "Legacy wallet seed" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index fcdaf17b1..335090eae 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -752,5 +752,6 @@ "seed_language_chinese_traditional": "Chino tradicional)", "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", "seed_language_chinese_traditional": "Chino (tradicional)", - "polygonscan_history": "Historial de PolygonScan" -} + "polygonscan_history": "Historial de PolygonScan", + "wallet_seed_legacy": "Semilla de billetera heredada" +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 3fe73f0b4..916cc935a 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -751,5 +751,6 @@ "seed_language_korean": "coréen", "seed_language_chinese_traditional": "Chinois (Traditionnel)", "dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe", - "polygonscan_history": "Historique de PolygonScan" + "polygonscan_history": "Historique de PolygonScan", + "wallet_seed_legacy": "Graine de portefeuille hérité" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index d35f3a89e..32de934c2 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -729,5 +729,6 @@ "seed_language_korean": "Yaren Koriya", "seed_language_chinese_traditional": "Sinanci (na gargajiya)", "dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai", - "polygonscan_history": "PolygonScan tarihin kowane zamani" + "polygonscan_history": "PolygonScan tarihin kowane zamani", + "wallet_seed_legacy": "Tallarin walat walat" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index ce4ebc453..0ecbd9816 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -751,5 +751,6 @@ "seed_language_korean": "कोरियाई", "seed_language_chinese_traditional": "चीनी पारंपरिक)", "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", - "polygonscan_history": "पॉलीगॉनस्कैन इतिहास" + "polygonscan_history": "पॉलीगॉनस्कैन इतिहास", + "wallet_seed_legacy": "विरासत बटुए बीज" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4ab734f9f..bbe2c14ac 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -750,5 +750,6 @@ "seed_language_chinese_traditional": "Kinesko tradicionalno)", "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", "seed_language_chinese_traditional": "Kinesko (tradicionalno)", - "polygonscan_history": "Povijest PolygonScan" -} + "polygonscan_history": "Povijest PolygonScan", + "wallet_seed_legacy": "Sjeme naslijeđenog novčanika" +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 041bbd93c..8dfe60db8 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -740,5 +740,6 @@ "seed_language_chinese_traditional": "Cina tradisional)", "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", "seed_language_chinese_traditional": "Cina (tradisional)", - "polygonscan_history": "Sejarah PolygonScan" -} + "polygonscan_history": "Sejarah PolygonScan", + "wallet_seed_legacy": "Biji dompet warisan" +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 250edda6b..8ea3e3e0c 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -751,5 +751,6 @@ "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Cinese tradizionale)", "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa", - "polygonscan_history": "Cronologia PolygonScan" + "polygonscan_history": "Cronologia PolygonScan", + "wallet_seed_legacy": "Seme di portafoglio legacy" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 37b5e60fd..f940bf37f 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -751,5 +751,6 @@ "seed_language_korean": "韓国語", "seed_language_chinese_traditional": "中国の伝統的な)", "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", - "polygonscan_history": "ポリゴンスキャン履歴" + "polygonscan_history": "ポリゴンスキャン履歴", + "wallet_seed_legacy": "レガシーウォレットシード" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 611915f56..75d079a63 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -749,5 +749,6 @@ "seed_language_korean": "한국인", "seed_language_chinese_traditional": "중국 전통)", "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", - "polygonscan_history": "다각형 스캔 기록" + "polygonscan_history": "다각형 스캔 기록", + "wallet_seed_legacy": "레거시 지갑 시드" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index cf12b9b16..da26986c9 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -749,5 +749,6 @@ "seed_language_korean": "ကိုးရီးယား", "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)", "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", - "polygonscan_history": "PolygonScan မှတ်တမ်း" + "polygonscan_history": "PolygonScan မှတ်တမ်း", + "wallet_seed_legacy": "အမွေအနှစ်ပိုက်ဆံအိတ်မျိုးစေ့" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index b36a5a234..c3573dee7 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -752,5 +752,6 @@ "seed_language_chinese_traditional": "Chinese traditionele)", "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", "seed_language_chinese_traditional": "Chinese (traditionele)", - "polygonscan_history": "PolygonScan-geschiedenis" -} + "polygonscan_history": "PolygonScan-geschiedenis", + "wallet_seed_legacy": "Legacy portemonnee zaad" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index afb9bf9ec..da60d8fe7 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -751,5 +751,6 @@ "seed_language_korean": "koreański", "seed_language_chinese_traditional": "Chiński tradycyjny)", "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie", - "polygonscan_history": "Historia PolygonScan" + "polygonscan_history": "Historia PolygonScan", + "wallet_seed_legacy": "Dziedziczne ziarno portfela" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index c2fe03ad3..08a2d05ee 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -750,5 +750,6 @@ "seed_language_korean": "coreano", "seed_language_chinese_traditional": "Chinês tradicional)", "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa", - "polygonscan_history": "História do PolygonScan" + "polygonscan_history": "História do PolygonScan", + "wallet_seed_legacy": "Semente de carteira herdada" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 107cbf596..cad832ae0 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -751,5 +751,6 @@ "seed_language_korean": "Корейский", "seed_language_chinese_traditional": "Китайский традиционный)", "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", - "polygonscan_history": "История PolygonScan" + "polygonscan_history": "История PolygonScan", + "wallet_seed_legacy": "Наследие семя кошелька" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 134868c08..ee84b011f 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -749,5 +749,6 @@ "seed_language_korean": "เกาหลี", "seed_language_chinese_traditional": "จีน (ดั้งเดิม)", "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", - "polygonscan_history": "ประวัติ PolygonScan" + "polygonscan_history": "ประวัติ PolygonScan", + "wallet_seed_legacy": "เมล็ดกระเป๋าเงินมรดก" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d12e89378..1fd59d29a 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -745,5 +745,6 @@ "seed_language_korean": "Korean", "seed_language_chinese_traditional": "Intsik (tradisyonal)", "dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe", - "polygonscan_history": "Kasaysayan ng PolygonScan" + "polygonscan_history": "Kasaysayan ng PolygonScan", + "wallet_seed_legacy": "Legacy wallet seed" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 30a354caa..687f741f2 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -749,5 +749,6 @@ "seed_language_korean": "Koreli", "seed_language_chinese_traditional": "Çin geleneği)", "dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için", - "polygonscan_history": "PolygonScan geçmişi" + "polygonscan_history": "PolygonScan geçmişi", + "wallet_seed_legacy": "Eski cüzdan tohumu" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index f977308b5..ee4af583e 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -751,5 +751,6 @@ "seed_language_korean": "Корейський", "dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі", "seed_language_chinese_traditional": "Китайський (традиційний)", - "polygonscan_history": "Історія PolygonScan" -} + "polygonscan_history": "Історія PolygonScan", + "wallet_seed_legacy": "Спадець насіння гаманця" +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index be7525c1b..d50cd62c6 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -743,5 +743,6 @@ "seed_language_korean": "کورین", "seed_language_chinese_traditional": "چینی (روایتی)", "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", - "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ" + "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ", + "wallet_seed_legacy": "میراثی پرس کا بیج" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 63d21666e..538f2d4a9 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -745,5 +745,6 @@ "seed_language_korean": "Ara ẹni", "seed_language_chinese_traditional": "Kannada (ibile)", "dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu", - "polygonscan_history": "PolygonScan itan" + "polygonscan_history": "PolygonScan itan", + "wallet_seed_legacy": "Irugbin akole" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 25710eff5..ee6e9efd9 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -750,5 +750,6 @@ "seed_language_korean": "韩国人", "seed_language_chinese_traditional": "中国传统的)", "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", - "polygonscan_history": "多边形扫描历史" + "polygonscan_history": "多边形扫描历史", + "wallet_seed_legacy": "旧的钱包种子" } From 7ce387c13024df974c327128003853b76b339b8c Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 11 Dec 2023 09:47:22 -0700 Subject: [PATCH 18/18] fix dot indicator color (#1228) --- lib/src/screens/dashboard/dashboard_page.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 2552f4a4a..98b24f783 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -191,7 +191,10 @@ class _DashboardPageView extends BasePage { radius: 6.0, dotWidth: 6.0, dotHeight: 6.0, - dotColor: Theme.of(context).indicatorColor, + dotColor: Theme.of(context) + .extension()! + .indicatorDotTheme + .indicatorColor, activeDotColor: Theme.of(context) .extension()! .indicatorDotTheme