From ed5a139490a733107faae03857de6480f378bc87 Mon Sep 17 00:00:00 2001 From: fosse Date: Mon, 18 Sep 2023 14:57:57 -0400 Subject: [PATCH 001/161] save --- lib/src/screens/send/send_page.dart | 119 ++++++++++++---------------- 1 file changed, 49 insertions(+), 70 deletions(-) diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 961cf4ba5..6b35158e3 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -66,9 +66,8 @@ class SendPage extends BasePage { color: titleColor(context), size: 16, ); - final _closeButton = currentTheme.type == ThemeType.dark - ? closeButtonImageDarkTheme - : closeButtonImage; + final _closeButton = + currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; bool isMobileView = ResponsiveLayoutUtil.instance.isMobile; @@ -79,13 +78,10 @@ class SendPage extends BasePage { child: ButtonTheme( minWidth: double.minPositive, child: Semantics( - label: !isMobileView - ? S.of(context).close - : S.of(context).seed_alert_back, + label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back, child: TextButton( style: ButtonStyle( - overlayColor: MaterialStateColor.resolveWith( - (states) => Colors.transparent), + overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent), ), onPressed: () => onClose(context), child: !isMobileView ? _closeButton : _backButton, @@ -123,8 +119,7 @@ class SendPage extends BasePage { Padding( padding: const EdgeInsets.only(right: 8.0), child: Observer( - builder: (_) => - SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend), + builder: (_) => SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend), ), ), if (supMiddle != null) supMiddle @@ -158,10 +153,10 @@ class SendPage extends BasePage { _setEffects(context); return GestureDetector( - onLongPress: () => sendViewModel.balanceViewModel.isReversing = - !sendViewModel.balanceViewModel.isReversing, - onLongPressUp: () => sendViewModel.balanceViewModel.isReversing = - !sendViewModel.balanceViewModel.isReversing, + onLongPress: () => + sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing, + onLongPressUp: () => + sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing, child: Form( key: _formKey, child: ScrollableWithBottomSection( @@ -191,8 +186,7 @@ class SendPage extends BasePage { }, )), Padding( - padding: EdgeInsets.only( - top: 10, left: 24, right: 24, bottom: 10), + padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), child: Container( height: 10, child: Observer( @@ -208,8 +202,12 @@ class SendPage extends BasePage { radius: 6.0, dotWidth: 6.0, dotHeight: 6.0, - dotColor: Theme.of(context).extension()!.indicatorDotColor, - activeDotColor: Theme.of(context).extension()!.templateBackgroundColor), + dotColor: Theme.of(context) + .extension()! + .indicatorDotColor, + activeDotColor: Theme.of(context) + .extension()! + .templateBackgroundColor), ) : Offstage(); }, @@ -230,8 +228,7 @@ class SendPage extends BasePage { return Row( children: [ AddTemplateButton( - onTap: () => Navigator.of(context) - .pushNamed(Routes.sendTemplate), + onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), currentTemplatesLength: templates.length, ), ListView.builder( @@ -244,9 +241,8 @@ class SendPage extends BasePage { return TemplateTile( key: UniqueKey(), to: template.name, - hasMultipleRecipients: - template.additionalRecipients != null && - template.additionalRecipients!.length > 1, + hasMultipleRecipients: template.additionalRecipients != null && + template.additionalRecipients!.length > 1, amount: template.isCurrencySelected ? template.amount : template.amountFiat, @@ -257,7 +253,9 @@ class SendPage extends BasePage { if (template.additionalRecipients?.isNotEmpty ?? false) { sendViewModel.clearOutputs(); - for (int i = 0;i < template.additionalRecipients!.length;i++) { + for (int i = 0; + i < template.additionalRecipients!.length; + i++) { Output output; try { output = sendViewModel.outputs[i]; @@ -286,26 +284,17 @@ class SendPage extends BasePage { context: context, builder: (dialogContext) { return AlertWithTwoActions( - alertTitle: - S.of(context).template, - alertContent: S - .of(context) - .confirm_delete_template, - rightButtonText: - S.of(context).delete, - leftButtonText: - S.of(context).cancel, + alertTitle: S.of(context).template, + alertContent: S.of(context).confirm_delete_template, + rightButtonText: S.of(context).delete, + leftButtonText: S.of(context).cancel, actionRightButton: () { - Navigator.of(dialogContext) - .pop(); - sendViewModel - .sendTemplateViewModel - .removeTemplate( - template: template); + Navigator.of(dialogContext).pop(); + sendViewModel.sendTemplateViewModel + .removeTemplate(template: template); }, actionLeftButton: () => - Navigator.of(dialogContext) - .pop()); + Navigator.of(dialogContext).pop()); }, ); }, @@ -321,8 +310,7 @@ class SendPage extends BasePage { ], ), ), - bottomSectionPadding: - EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSection: Column( children: [ if (sendViewModel.hasCurrecyChanger) @@ -331,10 +319,10 @@ class SendPage extends BasePage { padding: EdgeInsets.only(bottom: 12), child: PrimaryButton( onPressed: () => presentCurrencyPicker(context), - text: - 'Change your asset (${sendViewModel.selectedCryptoCurrency})', + text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', color: Colors.transparent, - textColor: Theme.of(context).extension()!.hintTextColor, + textColor: + Theme.of(context).extension()!.hintTextColor, ))), if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) Padding( @@ -343,22 +331,21 @@ class SendPage extends BasePage { onPressed: () { sendViewModel.addOutput(); Future.delayed(const Duration(milliseconds: 250), () { - controller - .jumpToPage(sendViewModel.outputs.length - 1); + controller.jumpToPage(sendViewModel.outputs.length - 1); }); }, text: S.of(context).add_receiver, color: Colors.transparent, textColor: Theme.of(context).extension()!.hintTextColor, isDottedBorder: true, - borderColor: Theme.of(context).extension()!.templateDottedBorderColor, + borderColor: + Theme.of(context).extension()!.templateDottedBorderColor, )), Observer( builder: (_) { return LoadingPrimaryButton( onPressed: () async { - if (_formKey.currentState != null && - !_formKey.currentState!.validate()) { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { if (sendViewModel.outputs.length > 1) { showErrorValidationAlert(context); } @@ -367,9 +354,7 @@ class SendPage extends BasePage { } final notValidItems = sendViewModel.outputs - .where((item) => - item.address.isEmpty || - item.cryptoAmount.isEmpty) + .where((item) => item.address.isEmpty || item.cryptoAmount.isEmpty) .toList(); if (notValidItems.isNotEmpty) { @@ -389,8 +374,7 @@ class SendPage extends BasePage { ); }, text: S.of(context).send, - color: - Theme.of(context).primaryColor, + color: Theme.of(context).primaryColor, textColor: Colors.white, isLoading: sendViewModel.state is IsExecutingState || sendViewModel.state is TransactionCommitting, @@ -433,14 +417,11 @@ class SendPage extends BasePage { return ConfirmSendingAlert( alertTitle: S.of(context).confirm_sending, amount: S.of(context).send_amount, - amountValue: - sendViewModel.pendingTransaction!.amountFormatted, - fiatAmountValue: - sendViewModel.pendingTransactionFiatAmountFormatted, + amountValue: sendViewModel.pendingTransaction!.amountFormatted, + fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted, fee: S.of(context).send_fee, feeValue: sendViewModel.pendingTransaction!.feeFormatted, - feeFiatAmount: sendViewModel - .pendingTransactionFeeFiatAmountFormatted, + feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, outputs: sendViewModel.outputs, rightButtonText: S.of(context).ok, leftButtonText: S.of(context).cancel, @@ -461,8 +442,7 @@ class SendPage extends BasePage { return AlertWithOneAction( alertTitle: '', alertContent: S.of(context).send_success( - sendViewModel.selectedCryptoCurrency - .toString()), + sendViewModel.selectedCryptoCurrency.toString()), buttonText: S.of(context).ok, buttonAction: () { Navigator.of(context).pop(); @@ -492,8 +472,8 @@ class SendPage extends BasePage { Future _setInputsFromTemplate(BuildContext context, {required Output output, required Template template}) async { - final fiatFromTemplate = FiatCurrency.all - .singleWhere((element) => element.title == template.fiatCurrency); + final fiatFromTemplate = + FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); output.address = template.address; @@ -534,12 +514,11 @@ class SendPage extends BasePage { builder: (_) => Picker( items: sendViewModel.currencies, displayItem: (Object item) => item.toString(), - selectedAtIndex: sendViewModel.currencies - .indexOf(sendViewModel.selectedCryptoCurrency), + selectedAtIndex: + sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), title: S.of(context).please_select, mainAxisAlignment: MainAxisAlignment.center, - onItemSelected: (CryptoCurrency cur) => - sendViewModel.selectedCryptoCurrency = cur, + onItemSelected: (CryptoCurrency cur) => sendViewModel.selectedCryptoCurrency = cur, ), context: context); } From 6b1c7e832d500c6cbdd3491a6a41d8ee084b81ea Mon Sep 17 00:00:00 2001 From: fosse Date: Tue, 19 Sep 2023 12:09:13 -0400 Subject: [PATCH 002/161] ens changes --- cw_ethereum/lib/ethereum_client.dart | 16 ++++++++++++++++ cw_ethereum/pubspec.yaml | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index e10e79f1e..0ef8ad54a 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -6,6 +6,7 @@ import 'package:cw_ethereum/erc20_balance.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_ethereum/ethereum_transaction_model.dart'; import 'package:cw_ethereum/pending_ethereum_transaction.dart'; +import 'package:ens_dart/ens_dart.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:web3dart/web3dart.dart'; @@ -210,6 +211,21 @@ I/flutter ( 4474): Gas Used: 53000 } } + Future checkEnsName(String ensName) async { + if (_client == null) { + return null; + } + try { + final ens = Ens(client: _client!); + + final addr = await ens.withName(ensName).getAddress(); + return addr.hex; + } catch (e) { + print(e); + return null; + } + } + // Future _getDecimalPlacesForContract(DeployedContract contract) async { // final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); // final contractAbi = ContractAbi.fromJson(abi, "ERC20"); diff --git a/cw_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index 5d19589f3..895098658 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: hex: ^0.2.0 http: ^1.1.0 shared_preferences: ^2.0.15 + ens_dart: ^1.0.0 cw_core: path: ../cw_core @@ -31,6 +32,9 @@ dev_dependencies: mobx_codegen: ^2.0.7 hive_generator: ^1.1.3 +dependency_overrides: + http: 1.1.0 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From d8d2335a5fadbac519dd8400f94d11d379b55641 Mon Sep 17 00:00:00 2001 From: fosse Date: Tue, 19 Sep 2023 12:21:51 -0400 Subject: [PATCH 003/161] version fix --- cw_ethereum/pubspec.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cw_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index 895098658..7e3572e80 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -21,7 +21,10 @@ dependencies: hex: ^0.2.0 http: ^1.1.0 shared_preferences: ^2.0.15 - ens_dart: ^1.0.0 + ens_dart: + git: + url: https://github.com/cake-tech/ens_dart.git + ref: main cw_core: path: ../cw_core @@ -32,9 +35,6 @@ dev_dependencies: mobx_codegen: ^2.0.7 hive_generator: ^1.1.3 -dependency_overrides: - http: 1.1.0 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From a52c7b053e4f153b945f723d2a7e9270caf82f9d Mon Sep 17 00:00:00 2001 From: fosse Date: Thu, 21 Sep 2023 10:16:27 -0400 Subject: [PATCH 004/161] ens working --- cw_ethereum/lib/ethereum_client.dart | 6 +- cw_ethereum/lib/ethereum_wallet.dart | 4 + lib/core/address_validator.dart | 2 +- lib/entities/parse_address_from_domain.dart | 13 ++++ lib/entities/parsed_address.dart | 11 ++- lib/ethereum/cw_ethereum.dart | 5 ++ .../widgets/extract_address_from_parsed.dart | 5 ++ 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_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 +- tool/configure.dart | 75 +++++++++++-------- 33 files changed, 133 insertions(+), 63 deletions(-) diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index 0ef8ad54a..4c74f1ec0 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -211,9 +211,9 @@ I/flutter ( 4474): Gas Used: 53000 } } - Future checkEnsName(String ensName) async { + Future checkEnsName(String ensName) async { if (_client == null) { - return null; + return ""; } try { final ens = Ens(client: _client!); @@ -222,7 +222,7 @@ I/flutter ( 4474): Gas Used: 53000 return addr.hex; } catch (e) { print(e); - return null; + return ""; } } diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 012b33a4b..b0712d059 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -508,4 +508,8 @@ abstract class EthereumWalletBase @override String signMessage(String message, {String? address = null}) => bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); + + Future fetchEnsAddress(String name) async { + return await _client.checkEnsName(name); + } } diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index f2a235363..abafea0d5 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -267,4 +267,4 @@ class AddressValidator extends TextValidator { return null; } } -} +} \ No newline at end of file diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index c0eab6d65..f36629df3 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -1,13 +1,17 @@ import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/yat_service.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/twitter/twitter_api.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; +import 'package:cw_ethereum/ethereum_wallet.dart'; class AddressResolver { AddressResolver({required this.yatService, required this.walletType}); @@ -104,6 +108,15 @@ class AddressResolver { return ParsedAddress.fetchOpenAliasAddress(record: record, name: text); } } + if (text.contains(".")) { + var wallet = getIt.get().wallet!; + if (wallet.type == WalletType.ethereum) { + final address = await ethereum!.fetchEnsAddress(wallet, text); + if (address.isNotEmpty) { + return ParsedAddress.fetchEnsAddress(name: text, address: address); + } + } + } } catch (e) { print(e.toString()); } diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index 67caebcb5..b8c0a81d5 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/yat_record.dart'; -enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, contact } +enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, ens, contact } class ParsedAddress { ParsedAddress({ @@ -77,8 +77,17 @@ class ParsedAddress { ); } + factory ParsedAddress.fetchEnsAddress({required String address, required String name}) { + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.ens, + ); + } + final List addresses; final String name; final String description; final ParseFrom parseFrom; + } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index f2db7741e..d3a8862de 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -131,4 +131,9 @@ class CWEthereum extends Ethereum { void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) { (wallet as EthereumWallet).updateEtherscanUsageState(isEnabled); } + + @override + Future fetchEnsAddress(WalletBase wallet, String name) async { + return (wallet as EthereumWallet).fetchEnsAddress(name); + } } diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index d7e0e3d7f..86274f29b 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -18,6 +18,11 @@ Future extractAddressFromParsed( content = S.of(context).address_from_domain(parsedAddress.name); address = parsedAddress.addresses.first; break; + case ParseFrom.ens: + title = S.of(context).address_detected; + content = S.of(context).address_from_ens(parsedAddress.name); + address = parsedAddress.addresses.first; + break; case ParseFrom.openAlias: title = S.of(context).address_detected; content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)'); diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7c7b0535b..9924dcbc9 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -688,5 +688,6 @@ "onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.", "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", - "buy_provider_unavailable": "مزود حاليا غير متوفر." + "buy_provider_unavailable": "مزود حاليا غير متوفر.", + "address_from_ens": "هذا ${domain} العنوان من ON ENS" } \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 9f4c21c44..cb848d87e 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -684,5 +684,6 @@ "onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.", "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", - "buy_provider_unavailable": "Понастоящем доставчик не е наличен." + "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", + "address_from_ens": "Този адрес ${domain} е от on ens" } \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 82d0fd9ed..b53a56a3a 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -684,5 +684,6 @@ "onramper_option_description": "Rychle si koupte krypto s mnoha metodami plateb. K dispozici ve většině zemí. Rozpětí a poplatky se liší.", "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", - "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný." + "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", + "address_from_ens": "Tato adresa je z ${domain} On On ENS" } \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e5bb7ce51..1776ea68e 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Kaufen Sie schnell Krypto mit vielen Zahlungsmethoden. In den meisten Ländern erhältlich. Spreads und Gebühren variieren.", "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", - "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar." + "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", + "address_from_ens": "Diese Adresse ${domain} stammt von one" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8cf6e2acb..c26dc260e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Default Buy Provider", "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", - "buy_provider_unavailable": "Provider currently unavailable." + "buy_provider_unavailable": "Provider currently unavailable.", + "address_from_ens": "This address is from ${domain} on ENS" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index d84b21b94..1a7a04adf 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.", "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", - "buy_provider_unavailable": "Proveedor actualmente no disponible." + "buy_provider_unavailable": "Proveedor actualmente no disponible.", + "address_from_ens": "Esta dirección ${domain} es de en ENS" } \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2daea4733..85e80ab40 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Achetez rapidement la crypto avec de nombreux méthodes de paiement. Disponible dans la plupart des pays. Les écarts et les frais varient.", "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", - "buy_provider_unavailable": "Fournisseur actuellement indisponible." + "buy_provider_unavailable": "Fournisseur actuellement indisponible.", + "address_from_ens": "Cette adresse ${domain} est d'où" } \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 666664ec2..fff3dbbc4 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -670,5 +670,6 @@ "onramper_option_description": "Da sauri sayi Crypto tare da hanyoyin biyan kuɗi da yawa. Akwai a yawancin ƙasashe. Yaduwa da kudade sun bambanta.", "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", - "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu." + "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", + "address_from_ens": "Wannan adireshin ${domain} yana fitowa ne" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9504baf36..af444215c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -692,5 +692,6 @@ "onramper_option_description": "जल्दी से कई भुगतान विधियों के साथ क्रिप्टो खरीदें। अधिकांश देशों में उपलब्ध है। फैलता है और फीस अलग -अलग होती है।", "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", - "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।" + "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", + "address_from_ens": "यह पता ${domain} ENS से ​​है" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 08549439a..106a82dae 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Brzo kupite kriptovalute s mnogim načinima plaćanja. Dostupno u većini zemalja. Širenja i naknade variraju.", "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", - "buy_provider_unavailable": "Davatelj trenutno nije dostupan." + "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", + "address_from_ens": "Ova adresa je ${domain} iz ENS -a" } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 031777aea..9d3ba7c1c 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -680,5 +680,6 @@ "onramper_option_description": "Beli crypto dengan cepat dengan banyak metode pembayaran. Tersedia di sebagian besar negara. Spread dan biaya bervariasi.", "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", - "buy_provider_unavailable": "Penyedia saat ini tidak tersedia." + "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", + "address_from_ens": "Alamat ini ${domain} berasal dari ENS" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b14531b75..c74443868 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Acquista rapidamente la criptovaluta con molti metodi di pagamento. Disponibile nella maggior parte dei paesi. Gli spread e le commissioni variano.", "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", - "buy_provider_unavailable": "Provider attualmente non disponibile." + "buy_provider_unavailable": "Provider attualmente non disponibile.", + "address_from_ens": "Questo indirizzo ${domain} è da On ENS" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index baf9b8a6d..53931137e 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -692,5 +692,6 @@ "onramper_option_description": "多くの支払い方法で暗号をすばやく購入してください。ほとんどの国で利用可能です。スプレッドと料金は異なります。", "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", - "buy_provider_unavailable": "現在、プロバイダーは利用できません。" + "buy_provider_unavailable": "現在、プロバイダーは利用できません。", + "address_from_ens": "このアドレスは ${domain} onsからです" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 3b9ed1e74..e023221d9 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -692,5 +692,6 @@ "onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.", "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", - "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다." + "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", + "address_from_ens": "이 주소는 ${domain} ON ENS에서 나온 것입니다" } \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 04d2f58ef..7868de431 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -690,5 +690,6 @@ "onramper_option_description": "ငွေပေးချေမှုနည်းလမ်းများစွာဖြင့် Crypto ကိုလျင်မြန်စွာ 0 ယ်ပါ။ နိုင်ငံအများစုတွင်ရရှိနိုင်ပါသည်။ ဖြန့်ဖြူးနှင့်အခကြေးငွေကွဲပြားခြားနားသည်။", "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", - "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။" + "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", + "address_from_ens": "ဒီလိပ်စာ ${domain} ens ကနေဖြစ်ပါတယ်" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 676653dcd..109caf470 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Koop snel crypto met veel betaalmethoden. Beschikbaar in de meeste landen. Spreads en vergoedingen variëren.", "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", - "buy_provider_unavailable": "Provider momenteel niet beschikbaar." + "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", + "address_from_ens": "Dit adres ${domain} is van ON ENS" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cae3d0075..0cd53df66 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Szybko kup kryptowaluty z wieloma metodami płatności. Dostępne w większości krajów. Spready i opłaty różnią się.", "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", - "buy_provider_unavailable": "Dostawca obecnie niedostępny." + "buy_provider_unavailable": "Dostawca obecnie niedostępny.", + "address_from_ens": "Ten adres ${domain} pochodzi z end" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 46c62c590..293afbcef 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -691,5 +691,6 @@ "onramper_option_description": "Compre rapidamente criptografia com muitos métodos de pagamento. Disponível na maioria dos países. Os spreads e taxas variam.", "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", - "buy_provider_unavailable": "Provedor atualmente indisponível." + "buy_provider_unavailable": "Provedor atualmente indisponível.", + "address_from_ens": "Este endereço ${domain} é de ON ENS" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 523b4c77e..92f688ef5 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Быстро купите крипто со многими способами оплаты. Доступно в большинстве стран. Спреды и сборы различаются.", "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", - "buy_provider_unavailable": "Поставщик в настоящее время недоступен." + "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", + "address_from_ens": "Этот адрес ${domain} с On ons" } \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 68c13dd2d..1e70d2dfd 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -690,5 +690,6 @@ "onramper_option_description": "ซื้อ crypto อย่างรวดเร็วด้วยวิธีการชำระเงินจำนวนมาก มีให้บริการในประเทศส่วนใหญ่ สเปรดและค่าธรรมเนียมแตกต่างกันไป", "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", - "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน" + "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", + "address_from_ens": "ที่อยู่นี้มาจาก ${domain} ENS" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 2694bb1bc..ae3d2fddd 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -690,5 +690,6 @@ "onramper_option_description": "Birçok ödeme yöntemi ile hızlı bir şekilde kripto satın alın. Çoğu ülkede mevcuttur. Forma ve ücretler değişir.", "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", - "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor." + "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", + "address_from_ens": "Bu adres ${domain} ON ENS'ten" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 38fe226b1..b27076932 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Швидко купуйте криптовалюту з багатьма методами оплати. Доступний у більшості країн. Поширення та збори різняться.", "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", - "buy_provider_unavailable": "В даний час постачальник недоступний." + "buy_provider_unavailable": "В даний час постачальник недоступний.", + "address_from_ens": "Ця адреса ${domain} з ENS" } \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 1ecdb5c0e..ca2ccbb85 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -684,5 +684,6 @@ "onramper_option_description": "ادائیگی کے بہت سے طریقوں سے جلدی سے کرپٹو خریدیں۔ زیادہ تر ممالک میں دستیاب ہے۔ پھیلاؤ اور فیس مختلف ہوتی ہے۔", "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", - "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔" + "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", + "address_from_ens": "یہ پتہ آن ${domain}پر ہے" } \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 62909f128..3df81d158 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -686,5 +686,6 @@ "onramper_option_description": "Ni kiakia Ra Crypto pẹlu ọpọlọpọ awọn ọna isanwo. Wa ni ọpọlọpọ awọn orilẹ-ede. Itankale ati awọn idiyele yatọ.", "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", - "buy_provider_unavailable": "Olupese lọwọlọwọ ko si." + "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", + "address_from_ens": "Adirẹsi yii ti ${domain} wa ni ens" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 24295e0ca..4d76bd838 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -691,5 +691,6 @@ "onramper_option_description": "快速使用许多付款方式购买加密货币。在大多数国家 /地区可用。利差和费用各不相同。", "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", - "buy_provider_unavailable": "提供者目前不可用。" + "buy_provider_unavailable": "提供者目前不可用。", + "address_from_ens": "此地址${domain}来自ENS" } \ No newline at end of file diff --git a/tool/configure.dart b/tool/configure.dart index 5172f4244..19e8ef327 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -18,8 +18,10 @@ Future main(List args) async { await generateMonero(hasMonero); await generateHaven(hasHaven); await generateEthereum(hasEthereum); - await generatePubspec(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum); - await generateWalletTypes(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum); + await generatePubspec( + hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum); + await generateWalletTypes( + hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum); } Future generateBitcoin(bool hasImplementation) async { @@ -88,12 +90,12 @@ abstract class Bitcoin { const bitcoinEmptyDefinition = 'Bitcoin? bitcoin;\n'; const bitcoinCWDefinition = 'Bitcoin? bitcoin = CWBitcoin();\n'; - final output = '$bitcoinCommonHeaders\n' - + (hasImplementation ? '$bitcoinCWHeaders\n' : '\n') - + (hasImplementation ? '$bitcoinCwPart\n\n' : '\n') - + (hasImplementation ? bitcoinCWDefinition : bitcoinEmptyDefinition) - + '\n' - + bitcoinContent; + final output = '$bitcoinCommonHeaders\n' + + (hasImplementation ? '$bitcoinCWHeaders\n' : '\n') + + (hasImplementation ? '$bitcoinCwPart\n\n' : '\n') + + (hasImplementation ? bitcoinCWDefinition : bitcoinEmptyDefinition) + + '\n' + + bitcoinContent; if (outputFile.existsSync()) { await outputFile.delete(); @@ -268,12 +270,12 @@ abstract class MoneroAccountList { const moneroEmptyDefinition = 'Monero? monero;\n'; const moneroCWDefinition = 'Monero? monero = CWMonero();\n'; - final output = '$moneroCommonHeaders\n' - + (hasImplementation ? '$moneroCWHeaders\n' : '\n') - + (hasImplementation ? '$moneroCwPart\n\n' : '\n') - + (hasImplementation ? moneroCWDefinition : moneroEmptyDefinition) - + '\n' - + moneroContent; + final output = '$moneroCommonHeaders\n' + + (hasImplementation ? '$moneroCWHeaders\n' : '\n') + + (hasImplementation ? '$moneroCwPart\n\n' : '\n') + + (hasImplementation ? moneroCWDefinition : moneroEmptyDefinition) + + '\n' + + moneroContent; if (outputFile.existsSync()) { await outputFile.delete(); @@ -283,7 +285,6 @@ abstract class MoneroAccountList { } Future generateHaven(bool hasImplementation) async { - final outputFile = File(havenOutputPath); const havenCommonHeaders = """ import 'package:mobx/mobx.dart'; @@ -448,12 +449,12 @@ abstract class HavenAccountList { const havenEmptyDefinition = 'Haven? haven;\n'; const havenCWDefinition = 'Haven? haven = CWHaven();\n'; - final output = '$havenCommonHeaders\n' - + (hasImplementation ? '$havenCWHeaders\n' : '\n') - + (hasImplementation ? '$havenCwPart\n\n' : '\n') - + (hasImplementation ? havenCWDefinition : havenEmptyDefinition) - + '\n' - + havenContent; + final output = '$havenCommonHeaders\n' + + (hasImplementation ? '$havenCWHeaders\n' : '\n') + + (hasImplementation ? '$havenCwPart\n\n' : '\n') + + (hasImplementation ? havenCWDefinition : havenEmptyDefinition) + + '\n' + + havenContent; if (outputFile.existsSync()) { await outputFile.delete(); @@ -463,7 +464,6 @@ abstract class HavenAccountList { } Future generateEthereum(bool hasImplementation) async { - final outputFile = File(ethereumOutputPath); const ethereumCommonHeaders = """ import 'package:cake_wallet/view_model/send/output.dart'; @@ -525,18 +525,19 @@ abstract class Ethereum { CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); void updateEtherscanUsageState(WalletBase wallet, bool isEnabled); + Future fetchEnsAddress(WalletBase wallet, String name); } """; const ethereumEmptyDefinition = 'Ethereum? ethereum;\n'; const ethereumCWDefinition = 'Ethereum? ethereum = CWEthereum();\n'; - final output = '$ethereumCommonHeaders\n' - + (hasImplementation ? '$ethereumCWHeaders\n' : '\n') - + (hasImplementation ? '$ethereumCwPart\n\n' : '\n') - + (hasImplementation ? ethereumCWDefinition : ethereumEmptyDefinition) - + '\n' - + ethereumContent; + final output = '$ethereumCommonHeaders\n' + + (hasImplementation ? '$ethereumCWHeaders\n' : '\n') + + (hasImplementation ? '$ethereumCwPart\n\n' : '\n') + + (hasImplementation ? ethereumCWDefinition : ethereumEmptyDefinition) + + '\n' + + ethereumContent; if (outputFile.existsSync()) { await outputFile.delete(); @@ -545,8 +546,12 @@ abstract class Ethereum { await outputFile.writeAsString(output); } -Future generatePubspec({required bool hasMonero, required bool hasBitcoin, required bool hasHaven, required bool hasEthereum}) async { - const cwCore = """ +Future generatePubspec( + {required bool hasMonero, + required bool hasBitcoin, + required bool hasHaven, + required bool hasEthereum}) async { + const cwCore = """ cw_core: path: ./cw_core """; @@ -598,7 +603,7 @@ Future generatePubspec({required bool hasMonero, required bool hasBitcoin, inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); final outputFile = File(pubspecOutputPath); - + if (outputFile.existsSync()) { await outputFile.delete(); } @@ -606,9 +611,13 @@ Future generatePubspec({required bool hasMonero, required bool hasBitcoin, await outputFile.writeAsString(outputContent); } -Future generateWalletTypes({required bool hasMonero, required bool hasBitcoin, required bool hasHaven, required bool hasEthereum}) async { +Future generateWalletTypes( + {required bool hasMonero, + required bool hasBitcoin, + required bool hasHaven, + required bool hasEthereum}) async { final walletTypesFile = File(walletTypesPath); - + if (walletTypesFile.existsSync()) { await walletTypesFile.delete(); } From 147abc620390de6d4477b3cd48b5235fd292ecc0 Mon Sep 17 00:00:00 2001 From: fosse Date: Thu, 21 Sep 2023 10:16:57 -0400 Subject: [PATCH 005/161] small fix --- lib/entities/parse_address_from_domain.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index f36629df3..f2c2f5b3e 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -11,7 +11,6 @@ import 'package:cake_wallet/twitter/twitter_api.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; -import 'package:cw_ethereum/ethereum_wallet.dart'; class AddressResolver { AddressResolver({required this.yatService, required this.walletType}); From f8cb8657f6c59bc1e9860d46fe462ac2171ee218 Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 22 Sep 2023 09:50:09 -0400 Subject: [PATCH 006/161] remove bad translations --- 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_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 +-- 25 files changed, 25 insertions(+), 50 deletions(-) diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 9924dcbc9..7c7b0535b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -688,6 +688,5 @@ "onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.", "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", - "buy_provider_unavailable": "مزود حاليا غير متوفر.", - "address_from_ens": "هذا ${domain} العنوان من ON ENS" + "buy_provider_unavailable": "مزود حاليا غير متوفر." } \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index cb848d87e..9f4c21c44 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -684,6 +684,5 @@ "onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.", "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", - "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", - "address_from_ens": "Този адрес ${domain} е от on ens" + "buy_provider_unavailable": "Понастоящем доставчик не е наличен." } \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index b53a56a3a..82d0fd9ed 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -684,6 +684,5 @@ "onramper_option_description": "Rychle si koupte krypto s mnoha metodami plateb. K dispozici ve většině zemí. Rozpětí a poplatky se liší.", "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", - "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", - "address_from_ens": "Tato adresa je z ${domain} On On ENS" + "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný." } \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 1776ea68e..e5bb7ce51 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Kaufen Sie schnell Krypto mit vielen Zahlungsmethoden. In den meisten Ländern erhältlich. Spreads und Gebühren variieren.", "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", - "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", - "address_from_ens": "Diese Adresse ${domain} stammt von one" + "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar." } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index c26dc260e..8cf6e2acb 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -693,6 +693,5 @@ "default_buy_provider": "Default Buy Provider", "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", - "buy_provider_unavailable": "Provider currently unavailable.", - "address_from_ens": "This address is from ${domain} on ENS" + "buy_provider_unavailable": "Provider currently unavailable." } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 1a7a04adf..d84b21b94 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.", "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", - "buy_provider_unavailable": "Proveedor actualmente no disponible.", - "address_from_ens": "Esta dirección ${domain} es de en ENS" + "buy_provider_unavailable": "Proveedor actualmente no disponible." } \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 85e80ab40..2daea4733 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Achetez rapidement la crypto avec de nombreux méthodes de paiement. Disponible dans la plupart des pays. Les écarts et les frais varient.", "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", - "buy_provider_unavailable": "Fournisseur actuellement indisponible.", - "address_from_ens": "Cette adresse ${domain} est d'où" + "buy_provider_unavailable": "Fournisseur actuellement indisponible." } \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index fff3dbbc4..666664ec2 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -670,6 +670,5 @@ "onramper_option_description": "Da sauri sayi Crypto tare da hanyoyin biyan kuɗi da yawa. Akwai a yawancin ƙasashe. Yaduwa da kudade sun bambanta.", "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", - "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", - "address_from_ens": "Wannan adireshin ${domain} yana fitowa ne" + "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu." } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index af444215c..9504baf36 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -692,6 +692,5 @@ "onramper_option_description": "जल्दी से कई भुगतान विधियों के साथ क्रिप्टो खरीदें। अधिकांश देशों में उपलब्ध है। फैलता है और फीस अलग -अलग होती है।", "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", - "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", - "address_from_ens": "यह पता ${domain} ENS से ​​है" + "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 106a82dae..08549439a 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Brzo kupite kriptovalute s mnogim načinima plaćanja. Dostupno u većini zemalja. Širenja i naknade variraju.", "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", - "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", - "address_from_ens": "Ova adresa je ${domain} iz ENS -a" + "buy_provider_unavailable": "Davatelj trenutno nije dostupan." } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 9d3ba7c1c..031777aea 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -680,6 +680,5 @@ "onramper_option_description": "Beli crypto dengan cepat dengan banyak metode pembayaran. Tersedia di sebagian besar negara. Spread dan biaya bervariasi.", "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", - "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", - "address_from_ens": "Alamat ini ${domain} berasal dari ENS" + "buy_provider_unavailable": "Penyedia saat ini tidak tersedia." } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index c74443868..b14531b75 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Acquista rapidamente la criptovaluta con molti metodi di pagamento. Disponibile nella maggior parte dei paesi. Gli spread e le commissioni variano.", "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", - "buy_provider_unavailable": "Provider attualmente non disponibile.", - "address_from_ens": "Questo indirizzo ${domain} è da On ENS" + "buy_provider_unavailable": "Provider attualmente non disponibile." } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 53931137e..baf9b8a6d 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -692,6 +692,5 @@ "onramper_option_description": "多くの支払い方法で暗号をすばやく購入してください。ほとんどの国で利用可能です。スプレッドと料金は異なります。", "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", - "buy_provider_unavailable": "現在、プロバイダーは利用できません。", - "address_from_ens": "このアドレスは ${domain} onsからです" + "buy_provider_unavailable": "現在、プロバイダーは利用できません。" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e023221d9..3b9ed1e74 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -692,6 +692,5 @@ "onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.", "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", - "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", - "address_from_ens": "이 주소는 ${domain} ON ENS에서 나온 것입니다" + "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다." } \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 7868de431..04d2f58ef 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -690,6 +690,5 @@ "onramper_option_description": "ငွေပေးချေမှုနည်းလမ်းများစွာဖြင့် Crypto ကိုလျင်မြန်စွာ 0 ယ်ပါ။ နိုင်ငံအများစုတွင်ရရှိနိုင်ပါသည်။ ဖြန့်ဖြူးနှင့်အခကြေးငွေကွဲပြားခြားနားသည်။", "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", - "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", - "address_from_ens": "ဒီလိပ်စာ ${domain} ens ကနေဖြစ်ပါတယ်" + "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 109caf470..676653dcd 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Koop snel crypto met veel betaalmethoden. Beschikbaar in de meeste landen. Spreads en vergoedingen variëren.", "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", - "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", - "address_from_ens": "Dit adres ${domain} is van ON ENS" + "buy_provider_unavailable": "Provider momenteel niet beschikbaar." } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 0cd53df66..cae3d0075 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Szybko kup kryptowaluty z wieloma metodami płatności. Dostępne w większości krajów. Spready i opłaty różnią się.", "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", - "buy_provider_unavailable": "Dostawca obecnie niedostępny.", - "address_from_ens": "Ten adres ${domain} pochodzi z end" + "buy_provider_unavailable": "Dostawca obecnie niedostępny." } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 293afbcef..46c62c590 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -691,6 +691,5 @@ "onramper_option_description": "Compre rapidamente criptografia com muitos métodos de pagamento. Disponível na maioria dos países. Os spreads e taxas variam.", "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", - "buy_provider_unavailable": "Provedor atualmente indisponível.", - "address_from_ens": "Este endereço ${domain} é de ON ENS" + "buy_provider_unavailable": "Provedor atualmente indisponível." } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 92f688ef5..523b4c77e 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Быстро купите крипто со многими способами оплаты. Доступно в большинстве стран. Спреды и сборы различаются.", "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", - "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", - "address_from_ens": "Этот адрес ${domain} с On ons" + "buy_provider_unavailable": "Поставщик в настоящее время недоступен." } \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 1e70d2dfd..68c13dd2d 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -690,6 +690,5 @@ "onramper_option_description": "ซื้อ crypto อย่างรวดเร็วด้วยวิธีการชำระเงินจำนวนมาก มีให้บริการในประเทศส่วนใหญ่ สเปรดและค่าธรรมเนียมแตกต่างกันไป", "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", - "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", - "address_from_ens": "ที่อยู่นี้มาจาก ${domain} ENS" + "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index ae3d2fddd..2694bb1bc 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -690,6 +690,5 @@ "onramper_option_description": "Birçok ödeme yöntemi ile hızlı bir şekilde kripto satın alın. Çoğu ülkede mevcuttur. Forma ve ücretler değişir.", "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", - "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", - "address_from_ens": "Bu adres ${domain} ON ENS'ten" + "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor." } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index b27076932..38fe226b1 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -692,6 +692,5 @@ "onramper_option_description": "Швидко купуйте криптовалюту з багатьма методами оплати. Доступний у більшості країн. Поширення та збори різняться.", "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", - "buy_provider_unavailable": "В даний час постачальник недоступний.", - "address_from_ens": "Ця адреса ${domain} з ENS" + "buy_provider_unavailable": "В даний час постачальник недоступний." } \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index ca2ccbb85..1ecdb5c0e 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -684,6 +684,5 @@ "onramper_option_description": "ادائیگی کے بہت سے طریقوں سے جلدی سے کرپٹو خریدیں۔ زیادہ تر ممالک میں دستیاب ہے۔ پھیلاؤ اور فیس مختلف ہوتی ہے۔", "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", - "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", - "address_from_ens": "یہ پتہ آن ${domain}پر ہے" + "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔" } \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 3df81d158..62909f128 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -686,6 +686,5 @@ "onramper_option_description": "Ni kiakia Ra Crypto pẹlu ọpọlọpọ awọn ọna isanwo. Wa ni ọpọlọpọ awọn orilẹ-ede. Itankale ati awọn idiyele yatọ.", "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", - "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", - "address_from_ens": "Adirẹsi yii ti ${domain} wa ni ens" + "buy_provider_unavailable": "Olupese lọwọlọwọ ko si." } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 4d76bd838..24295e0ca 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -691,6 +691,5 @@ "onramper_option_description": "快速使用许多付款方式购买加密货币。在大多数国家 /地区可用。利差和费用各不相同。", "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", - "buy_provider_unavailable": "提供者目前不可用。", - "address_from_ens": "此地址${domain}来自ENS" + "buy_provider_unavailable": "提供者目前不可用。" } \ No newline at end of file From 14d3401137e44c34bcc93e323c1c2d55899e4373 Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 22 Sep 2023 09:56:50 -0400 Subject: [PATCH 007/161] translation fixes --- 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 ++- tool/utils/translation/translation_constants.dart | 2 +- 27 files changed, 53 insertions(+), 27 deletions(-) diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7c7b0535b..505ceaf41 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -688,5 +688,6 @@ "onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.", "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", - "buy_provider_unavailable": "مزود حاليا غير متوفر." + "buy_provider_unavailable": "مزود حاليا غير متوفر.", + "address_from_ens": "هذا العنوان من ${domain} على ENS" } \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 9f4c21c44..b30333d26 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -684,5 +684,6 @@ "onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.", "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", - "buy_provider_unavailable": "Понастоящем доставчик не е наличен." + "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", + "address_from_ens": "Този адрес е от ${domain} на ENS" } \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 82d0fd9ed..0c9c94300 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -684,5 +684,6 @@ "onramper_option_description": "Rychle si koupte krypto s mnoha metodami plateb. K dispozici ve většině zemí. Rozpětí a poplatky se liší.", "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", - "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný." + "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", + "address_from_ens": "Tato adresa pochází z ${domain} na ENS" } \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e5bb7ce51..802dd7d97 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Kaufen Sie schnell Krypto mit vielen Zahlungsmethoden. In den meisten Ländern erhältlich. Spreads und Gebühren variieren.", "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", - "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar." + "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", + "address_from_ens": "Diese Adresse stammt von ${domain} auf Ens" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8cf6e2acb..c26dc260e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Default Buy Provider", "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", - "buy_provider_unavailable": "Provider currently unavailable." + "buy_provider_unavailable": "Provider currently unavailable.", + "address_from_ens": "This address is from ${domain} on ENS" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index d84b21b94..91e9838b9 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.", "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", - "buy_provider_unavailable": "Proveedor actualmente no disponible." + "buy_provider_unavailable": "Proveedor actualmente no disponible.", + "address_from_ens": "Esta dirección es de ${domain} en ENS" } \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2daea4733..b3d0cae81 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Achetez rapidement la crypto avec de nombreux méthodes de paiement. Disponible dans la plupart des pays. Les écarts et les frais varient.", "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", - "buy_provider_unavailable": "Fournisseur actuellement indisponible." + "buy_provider_unavailable": "Fournisseur actuellement indisponible.", + "address_from_ens": "Cette adresse provient de ${domain} sur ENS" } \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 666664ec2..25ffad55f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -670,5 +670,6 @@ "onramper_option_description": "Da sauri sayi Crypto tare da hanyoyin biyan kuɗi da yawa. Akwai a yawancin ƙasashe. Yaduwa da kudade sun bambanta.", "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", - "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu." + "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", + "address_from_ens": "Wannan adireshin daga ${domain} Don tabbatarwa" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9504baf36..cd8f83224 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -692,5 +692,6 @@ "onramper_option_description": "जल्दी से कई भुगतान विधियों के साथ क्रिप्टो खरीदें। अधिकांश देशों में उपलब्ध है। फैलता है और फीस अलग -अलग होती है।", "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", - "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।" + "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", + "address_from_ens": "यह पता ENS पर ${domain} से है" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 08549439a..da9b0d561 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Brzo kupite kriptovalute s mnogim načinima plaćanja. Dostupno u većini zemalja. Širenja i naknade variraju.", "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", - "buy_provider_unavailable": "Davatelj trenutno nije dostupan." + "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", + "address_from_ens": "Ova je adresa od ${domain} na ens" } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 031777aea..c15773e29 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -680,5 +680,6 @@ "onramper_option_description": "Beli crypto dengan cepat dengan banyak metode pembayaran. Tersedia di sebagian besar negara. Spread dan biaya bervariasi.", "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", - "buy_provider_unavailable": "Penyedia saat ini tidak tersedia." + "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", + "address_from_ens": "Alamat ini dari ${domain} di ENS" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b14531b75..9ad4ad963 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Acquista rapidamente la criptovaluta con molti metodi di pagamento. Disponibile nella maggior parte dei paesi. Gli spread e le commissioni variano.", "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", - "buy_provider_unavailable": "Provider attualmente non disponibile." + "buy_provider_unavailable": "Provider attualmente non disponibile.", + "address_from_ens": "Questo indirizzo è da ${domain} su ENS" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index baf9b8a6d..8190801f8 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -692,5 +692,6 @@ "onramper_option_description": "多くの支払い方法で暗号をすばやく購入してください。ほとんどの国で利用可能です。スプレッドと料金は異なります。", "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", - "buy_provider_unavailable": "現在、プロバイダーは利用できません。" + "buy_provider_unavailable": "現在、プロバイダーは利用できません。", + "address_from_ens": "このアドレスはENSの${domain}からです" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 3b9ed1e74..940a2ad7a 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -692,5 +692,6 @@ "onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.", "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", - "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다." + "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", + "address_from_ens": "이 주소는 ens의 ${domain}에서입니다." } \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 04d2f58ef..a78a5bc0b 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -690,5 +690,6 @@ "onramper_option_description": "ငွေပေးချေမှုနည်းလမ်းများစွာဖြင့် Crypto ကိုလျင်မြန်စွာ 0 ယ်ပါ။ နိုင်ငံအများစုတွင်ရရှိနိုင်ပါသည်။ ဖြန့်ဖြူးနှင့်အခကြေးငွေကွဲပြားခြားနားသည်။", "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", - "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။" + "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", + "address_from_ens": "ဒီလိပ်စာက ens အပေါ် {domain} မှဖြစ်ပါတယ်" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 676653dcd..efe307d25 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Koop snel crypto met veel betaalmethoden. Beschikbaar in de meeste landen. Spreads en vergoedingen variëren.", "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", - "buy_provider_unavailable": "Provider momenteel niet beschikbaar." + "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", + "address_from_ens": "Dit adres is van ${domain} op ENS" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cae3d0075..f80ff9bfe 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Szybko kup kryptowaluty z wieloma metodami płatności. Dostępne w większości krajów. Spready i opłaty różnią się.", "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", - "buy_provider_unavailable": "Dostawca obecnie niedostępny." + "buy_provider_unavailable": "Dostawca obecnie niedostępny.", + "address_from_ens": "Ten adres pochodzi od ${domain} na ens" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 46c62c590..cdb8d607b 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -691,5 +691,6 @@ "onramper_option_description": "Compre rapidamente criptografia com muitos métodos de pagamento. Disponível na maioria dos países. Os spreads e taxas variam.", "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", - "buy_provider_unavailable": "Provedor atualmente indisponível." + "buy_provider_unavailable": "Provedor atualmente indisponível.", + "address_from_ens": "Este endereço é de ${domain} no ENS" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 523b4c77e..31be7e764 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Быстро купите крипто со многими способами оплаты. Доступно в большинстве стран. Спреды и сборы различаются.", "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", - "buy_provider_unavailable": "Поставщик в настоящее время недоступен." + "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", + "address_from_ens": "Этот адрес от ${domain} на ENS" } \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 68c13dd2d..97905648a 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -690,5 +690,6 @@ "onramper_option_description": "ซื้อ crypto อย่างรวดเร็วด้วยวิธีการชำระเงินจำนวนมาก มีให้บริการในประเทศส่วนใหญ่ สเปรดและค่าธรรมเนียมแตกต่างกันไป", "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", - "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน" + "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", + "address_from_ens": "ที่อยู่นี้มาจาก ${domain} บน Ens" } \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d65e36609..23983cac9 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -687,5 +687,6 @@ "support_title_other_links": "Iba pang mga link sa suporta", "support_description_other_links": "Sumali sa aming mga komunidad o maabot sa amin ang aming mga kasosyo sa pamamagitan ng iba pang mga pamamaraan", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup file.", - "save_to_downloads": "I -save sa mga pag -download" + "save_to_downloads": "I -save sa mga pag -download", + "address_from_ens": "Ang address na ito ay mula sa ${domain} sa ens" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 2694bb1bc..caa7bf130 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -690,5 +690,6 @@ "onramper_option_description": "Birçok ödeme yöntemi ile hızlı bir şekilde kripto satın alın. Çoğu ülkede mevcuttur. Forma ve ücretler değişir.", "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", - "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor." + "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", + "address_from_ens": "Bu adres ens'deki ${domain} 'den" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 38fe226b1..8b565ee65 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Швидко купуйте криптовалюту з багатьма методами оплати. Доступний у більшості країн. Поширення та збори різняться.", "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", - "buy_provider_unavailable": "В даний час постачальник недоступний." + "buy_provider_unavailable": "В даний час постачальник недоступний.", + "address_from_ens": "Ця адреса від ${domain} на ENS" } \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 1ecdb5c0e..41fb0ad9f 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -684,5 +684,6 @@ "onramper_option_description": "ادائیگی کے بہت سے طریقوں سے جلدی سے کرپٹو خریدیں۔ زیادہ تر ممالک میں دستیاب ہے۔ پھیلاؤ اور فیس مختلف ہوتی ہے۔", "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", - "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔" + "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", + "address_from_ens": "یہ پتہ ENS پر ${domain} سے ہے" } \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 62909f128..485f5ed5c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -686,5 +686,6 @@ "onramper_option_description": "Ni kiakia Ra Crypto pẹlu ọpọlọpọ awọn ọna isanwo. Wa ni ọpọlọpọ awọn orilẹ-ede. Itankale ati awọn idiyele yatọ.", "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", - "buy_provider_unavailable": "Olupese lọwọlọwọ ko si." + "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", + "address_from_ens": "Adirẹsi yii ni lati ${domain} Lori ens" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 24295e0ca..458b864d4 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -691,5 +691,6 @@ "onramper_option_description": "快速使用许多付款方式购买加密货币。在大多数国家 /地区可用。利差和费用各不相同。", "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", - "buy_provider_unavailable": "提供者目前不可用。" + "buy_provider_unavailable": "提供者目前不可用。", + "address_from_ens": "此地址来自ENS上的${domain}" } \ No newline at end of file diff --git a/tool/utils/translation/translation_constants.dart b/tool/utils/translation/translation_constants.dart index 6563feb32..3a472d8c4 100644 --- a/tool/utils/translation/translation_constants.dart +++ b/tool/utils/translation/translation_constants.dart @@ -1,6 +1,6 @@ const defaultLang = "en"; const langs = [ "ar", "bg", "cs", "de", "en", "es", "fr", "ha", "hi", "hr", "id", "it", - "ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tr", "uk", "ur", "yo", + "ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tl", "tr", "uk", "ur", "yo", "zh-cn" // zh, but Google Translate uses zh-cn for Chinese (Simplified) ]; From b5852509f30bae9061e98ff996e22cc90b66871a Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 22 Sep 2023 10:24:16 -0400 Subject: [PATCH 008/161] fix --- res/values/strings_my.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index a78a5bc0b..928b9eca6 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -691,5 +691,5 @@ "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", - "address_from_ens": "ဒီလိပ်စာက ens အပေါ် {domain} မှဖြစ်ပါတယ်" + "address_from_ens": "ဒီလိပ်စာက ens အပေါ် ${domain} မှဖြစ်ပါတယ်" } \ No newline at end of file From 95ea8257282afa6e26ab4f9e0e726ea158e3bbcc Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 22 Sep 2023 17:53:30 +0300 Subject: [PATCH 009/161] Add Brightness Util to check for platform before increasing brightness (#1096) --- .../screens/receive/anonpay_receive_page.dart | 25 +++++++------------ .../screens/receive/widgets/qr_widget.dart | 24 ++---------------- .../screens/wallet_keys/wallet_keys_page.dart | 20 ++++++--------- lib/utils/brightness_util.dart | 23 +++++++++++++++++ 4 files changed, 42 insertions(+), 50 deletions(-) create mode 100644 lib/utils/brightness_util.dart diff --git a/lib/src/screens/receive/anonpay_receive_page.dart b/lib/src/screens/receive/anonpay_receive_page.dart index 1dae8e452..b602abde6 100644 --- a/lib/src/screens/receive/anonpay_receive_page.dart +++ b/lib/src/screens/receive/anonpay_receive_page.dart @@ -10,7 +10,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/anonpay_status_section.d import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; import 'package:cake_wallet/src/screens/receive/widgets/copy_link_item.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; -import 'package:device_display_brightness/device_display_brightness.dart'; +import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart' as qr; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; @@ -82,8 +82,7 @@ class AnonPayReceivePage extends BasePage { @override Widget Function(BuildContext, Widget) get rootWrapper => - (BuildContext context, Widget scaffold) => - GradientBackground(scaffold: scaffold); + (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); @override Widget body(BuildContext context) { @@ -101,19 +100,13 @@ class AnonPayReceivePage extends BasePage { ), child: GestureDetector( onTap: () async { - final double brightness = await DeviceDisplayBrightness.getBrightness(); - - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(1.0); - await Navigator.pushNamed( - context, - Routes.fullscreenQR, - arguments: QrViewData(data: invoiceInfo.clearnetUrl, - version: qr.QrVersions.auto, - ) - ); - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(brightness); + BrightnessUtil.changeBrightnessForFunction(() async { + await Navigator.pushNamed(context, Routes.fullscreenQR, + arguments: QrViewData( + data: invoiceInfo.clearnetUrl, + version: qr.QrVersions.auto, + )); + }); }, child: Hero( tag: Key(invoiceInfo.clearnetUrl), diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 0d2a7c80d..a34893cd8 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -3,10 +3,9 @@ import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; -import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:device_display_brightness/device_display_brightness.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -64,7 +63,7 @@ class QRWidget extends StatelessWidget { flex: 5, child: GestureDetector( onTap: () { - changeBrightnessForRoute( + BrightnessUtil.changeBrightnessForFunction( () async { await Navigator.pushNamed(context, Routes.fullscreenQR, arguments: QrViewData( @@ -178,23 +177,4 @@ class QRWidget extends StatelessWidget { // update amount if currency changed addressListViewModel.changeAmount(amountController.text); } - - Future changeBrightnessForRoute(Future Function() navigation) async { - // if not mobile, just navigate - if (!DeviceInfo.instance.isMobile) { - navigation(); - return; - } - - // Get the current brightness: - final brightness = await DeviceDisplayBrightness.getBrightness(); - - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(1.0); - - await navigation(); - - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(brightness); - } } diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 37ed3a692..5117f152f 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -5,10 +5,10 @@ import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:cake_wallet/utils/clipboard_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/view_model/wallet_keys_view_model.dart'; -import 'package:device_display_brightness/device_display_brightness.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -26,19 +26,15 @@ class WalletKeysPage extends BasePage { @override Widget trailing(BuildContext context) => IconButton( onPressed: () async { - // Get the current brightness: - final double brightness = await DeviceDisplayBrightness.getBrightness(); final url = await walletKeysViewModel.url; - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(1.0); - await Navigator.pushNamed( - context, - Routes.fullscreenQR, - arguments: QrViewData(data: url.toString(), version: QrVersions.auto), - ); - // ignore: unawaited_futures - DeviceDisplayBrightness.setBrightness(brightness); + BrightnessUtil.changeBrightnessForFunction(() async { + await Navigator.pushNamed( + context, + Routes.fullscreenQR, + arguments: QrViewData(data: url.toString(), version: QrVersions.auto), + ); + }); }, splashColor: Colors.transparent, highlightColor: Colors.transparent, diff --git a/lib/utils/brightness_util.dart b/lib/utils/brightness_util.dart new file mode 100644 index 000000000..5afe065e5 --- /dev/null +++ b/lib/utils/brightness_util.dart @@ -0,0 +1,23 @@ +import 'package:cake_wallet/utils/device_info.dart'; +import 'package:device_display_brightness/device_display_brightness.dart'; + +class BrightnessUtil { + static Future changeBrightnessForFunction(Future Function() func) async { + // if not mobile, just navigate + if (!DeviceInfo.instance.isMobile) { + func(); + return; + } + + // Get the current brightness: + final brightness = await DeviceDisplayBrightness.getBrightness(); + + // ignore: unawaited_futures + DeviceDisplayBrightness.setBrightness(1.0); + + await func(); + + // ignore: unawaited_futures + DeviceDisplayBrightness.setBrightness(brightness); + } +} \ No newline at end of file From 572d9288480cfee6c7f69bdc6c66604b03f3c033 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 22 Sep 2023 18:09:30 +0300 Subject: [PATCH 010/161] CW-442-Update-exchange-selector-checkboxes-with-checkboxes-from-transactions-filter-screen (#1074) * Update checkboxes * fix exchange provider selector * checkbox picker refactoring * Revert "checkbox picker refactoring" This reverts commit 66eea9d69033d497c740331fa42daee338df3b31. * Update check_box_picker.dart --- lib/src/screens/nodes/widgets/node_form.dart | 9 +++ lib/src/widgets/check_box_picker.dart | 71 +++++++++++--------- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/lib/src/screens/nodes/widgets/node_form.dart b/lib/src/screens/nodes/widgets/node_form.dart index 91974fce5..ab8dcafdf 100644 --- a/lib/src/screens/nodes/widgets/node_form.dart +++ b/lib/src/screens/nodes/widgets/node_form.dart @@ -132,6 +132,9 @@ class NodeForm extends StatelessWidget { Observer( builder: (_) => StandardCheckbox( value: nodeViewModel.useSSL, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, onChanged: (value) => nodeViewModel.useSSL = value, caption: S.of(context).use_ssl, ), @@ -148,6 +151,9 @@ class NodeForm extends StatelessWidget { Observer( builder: (_) => StandardCheckbox( value: nodeViewModel.trusted, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, onChanged: (value) => nodeViewModel.trusted = value, caption: S.of(context).trusted, ), @@ -166,6 +172,9 @@ class NodeForm extends StatelessWidget { children: [ StandardCheckbox( value: nodeViewModel.useSocksProxy, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, onChanged: (value) { if (!value) { _socksAddressController.text = ''; diff --git a/lib/src/widgets/check_box_picker.dart b/lib/src/widgets/check_box_picker.dart index 30f81e981..71bfa7ee9 100644 --- a/lib/src/widgets/check_box_picker.dart +++ b/lib/src/widgets/check_box_picker.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; @@ -101,7 +102,7 @@ class CheckBoxPickerState extends State { height: 1, ) : const SizedBox(), - itemCount: items == null || items.isEmpty ? 0 : items.length, + itemCount: items.isEmpty ? 0 : items.length, itemBuilder: (context, index) => buildItem(index), ), ); @@ -112,41 +113,51 @@ class CheckBoxPickerState extends State { return GestureDetector( onTap: () { - Navigator.of(context).pop(); + if (item.isDisabled) { + return; + } + + bool newValue = !item.value; + item.value = newValue; + widget.onChanged(index, newValue); + setState(() {}); }, child: Container( height: 55, color: Theme.of(context).dialogTheme.backgroundColor, padding: EdgeInsets.only(left: 24, right: 24), - child: CheckboxListTile( - value: item.value, - activeColor: item.value - ? Palette.blueCraiola - : Theme.of(context).extension()!.checkboxBackgroundColor, - checkColor: Colors.white, - title: widget.displayItem?.call(item) ?? - Text( - item.title, - style: TextStyle( - fontSize: 14, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: item.isDisabled - ? Colors.grey.withOpacity(0.5) - : Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - ), - ), - onChanged: (bool? value) { - if (value == null) { - return; - } + child: Row( + children: [ + StandardCheckbox( + value: item.value, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (bool? value) { + if (value == null || item.isDisabled) { + return; + } - item.value = value; - widget.onChanged(index, value); - setState(() {}); - }, - controlAffinity: ListTileControlAffinity.leading, + item.value = value; + widget.onChanged(index, value); + setState(() {}); + }, + ), + SizedBox(width: 16), + widget.displayItem?.call(item) ?? + Text( + item.title, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: item.isDisabled + ? Colors.grey.withOpacity(0.5) + : Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ) + ], ), ), ); From 04f5391be1990fb9f4073b8d8d3b7d2e88455f39 Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 23 Sep 2023 01:30:06 +0100 Subject: [PATCH 011/161] feat: Add otp url link to qa screen (#1098) --- .../screens/setup_2fa/setup_2fa_qr_page.dart | 65 +++++++++++++++++-- res/values/strings_ar.arb | 5 +- 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_tr.arb | 5 +- res/values/strings_uk.arb | 5 +- res/values/strings_ur.arb | 5 +- res/values/strings_yo.arb | 5 +- res/values/strings_zh.arb | 5 +- 26 files changed, 133 insertions(+), 57 deletions(-) diff --git a/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart b/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart index 82a079b39..e9ab850af 100644 --- a/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart +++ b/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart @@ -14,7 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart' as qr; - class Setup2FAQRPage extends BasePage { Setup2FAQRPage({required this.setup2FAViewModel}); @@ -25,7 +24,6 @@ class Setup2FAQRPage extends BasePage { @override Widget body(BuildContext context) { - final copyImage = Image.asset( 'assets/images/copy_content.png', height: 12, @@ -126,16 +124,69 @@ class Setup2FAQRPage extends BasePage { ), SizedBox(height: 8), StandardListSeparator(), + SizedBox(height: 13), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.current.totp_auth_url, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Palette.darkGray, + height: 1.8333, + ), + ), + SizedBox(height: 8), + Text( + '${setup2FAViewModel.totpVersionOneLink}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + height: 1.375, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + SizedBox(width: 8), + Container( + width: 32, + height: 32, + child: InkWell( + onTap: () { + ClipboardUtil.setSensitiveDataToClipboard( + ClipboardData(text: '${setup2FAViewModel.totpVersionOneLink}')); + showBar(context, S.of(context).copied_to_clipboard); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: Color(0xFFF2F0FA), + ), + child: copyImage, + ), + ), + ) + ], + ), + SizedBox(height: 8), + StandardListSeparator(), Spacer(), PrimaryButton( onPressed: () { - Navigator.of(context).pushReplacementNamed( - Routes.totpAuthCodePage, + Navigator.of(context).pushReplacementNamed(Routes.totpAuthCodePage, arguments: TotpAuthArgumentsModel( isForSetup: true, - ) - - ); + )); }, text: S.current.continue_text, color: Theme.of(context).primaryColor, diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 7c7b0535b..b10dfbe8e 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -688,5 +688,6 @@ "onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.", "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", - "buy_provider_unavailable": "مزود حاليا غير متوفر." -} \ No newline at end of file + "buy_provider_unavailable": "مزود حاليا غير متوفر.", + "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ" +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 9f4c21c44..828af2102 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -684,5 +684,6 @@ "onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.", "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", - "buy_provider_unavailable": "Понастоящем доставчик не е наличен." -} \ No newline at end of file + "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 82d0fd9ed..ea4dc1856 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -684,5 +684,6 @@ "onramper_option_description": "Rychle si koupte krypto s mnoha metodami plateb. K dispozici ve většině zemí. Rozpětí a poplatky se liší.", "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", - "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný." -} \ No newline at end of file + "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", + "totp_auth_url": "URL AUTH TOTP" +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index e5bb7ce51..09538ccc0 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Kaufen Sie schnell Krypto mit vielen Zahlungsmethoden. In den meisten Ländern erhältlich. Spreads und Gebühren variieren.", "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", - "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar." -} \ No newline at end of file + "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", + "totp_auth_url": "TOTP-Auth-URL" +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 8cf6e2acb..99ba22b18 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Default Buy Provider", "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", - "buy_provider_unavailable": "Provider currently unavailable." -} \ No newline at end of file + "buy_provider_unavailable": "Provider currently unavailable.", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index d84b21b94..a6175c6c0 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.", "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", - "buy_provider_unavailable": "Proveedor actualmente no disponible." -} \ No newline at end of file + "buy_provider_unavailable": "Proveedor actualmente no disponible.", + "totp_auth_url": "URL de autenticación TOTP" +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2daea4733..f659f0b14 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Achetez rapidement la crypto avec de nombreux méthodes de paiement. Disponible dans la plupart des pays. Les écarts et les frais varient.", "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", - "buy_provider_unavailable": "Fournisseur actuellement indisponible." -} \ No newline at end of file + "buy_provider_unavailable": "Fournisseur actuellement indisponible.", + "totp_auth_url": "URL D'AUTORISATION TOTP" +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 666664ec2..2b55ffd2a 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -670,5 +670,6 @@ "onramper_option_description": "Da sauri sayi Crypto tare da hanyoyin biyan kuɗi da yawa. Akwai a yawancin ƙasashe. Yaduwa da kudade sun bambanta.", "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", - "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu." -} \ No newline at end of file + "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9504baf36..69f37ab83 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -692,5 +692,6 @@ "onramper_option_description": "जल्दी से कई भुगतान विधियों के साथ क्रिप्टो खरीदें। अधिकांश देशों में उपलब्ध है। फैलता है और फीस अलग -अलग होती है।", "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", - "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।" -} \ No newline at end of file + "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", + "totp_auth_url": "TOTP प्रामाणिक यूआरएल" +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 08549439a..3c4299092 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Brzo kupite kriptovalute s mnogim načinima plaćanja. Dostupno u većini zemalja. Širenja i naknade variraju.", "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", - "buy_provider_unavailable": "Davatelj trenutno nije dostupan." -} \ No newline at end of file + "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 031777aea..a5b2e3ffa 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -680,5 +680,6 @@ "onramper_option_description": "Beli crypto dengan cepat dengan banyak metode pembayaran. Tersedia di sebagian besar negara. Spread dan biaya bervariasi.", "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", - "buy_provider_unavailable": "Penyedia saat ini tidak tersedia." -} \ No newline at end of file + "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", + "totp_auth_url": "URL Otentikasi TOTP" +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b14531b75..98c13aa2d 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Acquista rapidamente la criptovaluta con molti metodi di pagamento. Disponibile nella maggior parte dei paesi. Gli spread e le commissioni variano.", "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", - "buy_provider_unavailable": "Provider attualmente non disponibile." -} \ No newline at end of file + "buy_provider_unavailable": "Provider attualmente non disponibile.", + "totp_auth_url": "URL DI AUT. TOTP" +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index baf9b8a6d..f5bea4b58 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -692,5 +692,6 @@ "onramper_option_description": "多くの支払い方法で暗号をすばやく購入してください。ほとんどの国で利用可能です。スプレッドと料金は異なります。", "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", - "buy_provider_unavailable": "現在、プロバイダーは利用できません。" -} \ No newline at end of file + "buy_provider_unavailable": "現在、プロバイダーは利用できません。", + "totp_auth_url": "TOTP認証URL" +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 3b9ed1e74..79455ff92 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -692,5 +692,6 @@ "onramper_option_description": "많은 결제 방법으로 암호화를 신속하게 구입하십시오. 대부분의 국가에서 사용할 수 있습니다. 스프레드와 수수료는 다양합니다.", "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", - "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다." -} \ No newline at end of file + "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", + "totp_auth_url": "TOTP 인증 URL" +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 04d2f58ef..4ea13bc27 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -690,5 +690,6 @@ "onramper_option_description": "ငွေပေးချေမှုနည်းလမ်းများစွာဖြင့် Crypto ကိုလျင်မြန်စွာ 0 ယ်ပါ။ နိုင်ငံအများစုတွင်ရရှိနိုင်ပါသည်။ ဖြန့်ဖြူးနှင့်အခကြေးငွေကွဲပြားခြားနားသည်။", "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", - "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။" -} \ No newline at end of file + "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 676653dcd..99a469945 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Koop snel crypto met veel betaalmethoden. Beschikbaar in de meeste landen. Spreads en vergoedingen variëren.", "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", - "buy_provider_unavailable": "Provider momenteel niet beschikbaar." -} \ No newline at end of file + "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", + "totp_auth_url": "TOTP AUTH-URL" +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index cae3d0075..5d32ee12c 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Szybko kup kryptowaluty z wieloma metodami płatności. Dostępne w większości krajów. Spready i opłaty różnią się.", "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", - "buy_provider_unavailable": "Dostawca obecnie niedostępny." -} \ No newline at end of file + "buy_provider_unavailable": "Dostawca obecnie niedostępny.", + "totp_auth_url": "Adres URL TOTP AUTH" +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 46c62c590..ca46a26e7 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -691,5 +691,6 @@ "onramper_option_description": "Compre rapidamente criptografia com muitos métodos de pagamento. Disponível na maioria dos países. Os spreads e taxas variam.", "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", - "buy_provider_unavailable": "Provedor atualmente indisponível." -} \ No newline at end of file + "buy_provider_unavailable": "Provedor atualmente indisponível.", + "totp_auth_url": "URL de autenticação TOTP" +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 523b4c77e..fb3883338 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Быстро купите крипто со многими способами оплаты. Доступно в большинстве стран. Спреды и сборы различаются.", "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", - "buy_provider_unavailable": "Поставщик в настоящее время недоступен." -} \ No newline at end of file + "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", + "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ" +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 68c13dd2d..0e31518eb 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -690,5 +690,6 @@ "onramper_option_description": "ซื้อ crypto อย่างรวดเร็วด้วยวิธีการชำระเงินจำนวนมาก มีให้บริการในประเทศส่วนใหญ่ สเปรดและค่าธรรมเนียมแตกต่างกันไป", "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", - "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน" -} \ No newline at end of file + "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", + "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP" +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 2694bb1bc..16ed18b96 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -690,5 +690,6 @@ "onramper_option_description": "Birçok ödeme yöntemi ile hızlı bir şekilde kripto satın alın. Çoğu ülkede mevcuttur. Forma ve ücretler değişir.", "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", - "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor." -} \ No newline at end of file + "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", + "totp_auth_url": "TOTP YETKİ URL'si" +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 38fe226b1..c9943dd3a 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -692,5 +692,6 @@ "onramper_option_description": "Швидко купуйте криптовалюту з багатьма методами оплати. Доступний у більшості країн. Поширення та збори різняться.", "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", - "buy_provider_unavailable": "В даний час постачальник недоступний." -} \ No newline at end of file + "buy_provider_unavailable": "В даний час постачальник недоступний.", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 1ecdb5c0e..47c96d9fb 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -684,5 +684,6 @@ "onramper_option_description": "ادائیگی کے بہت سے طریقوں سے جلدی سے کرپٹو خریدیں۔ زیادہ تر ممالک میں دستیاب ہے۔ پھیلاؤ اور فیس مختلف ہوتی ہے۔", "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", - "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔" -} \ No newline at end of file + "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 62909f128..ec84589da 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -686,5 +686,6 @@ "onramper_option_description": "Ni kiakia Ra Crypto pẹlu ọpọlọpọ awọn ọna isanwo. Wa ni ọpọlọpọ awọn orilẹ-ede. Itankale ati awọn idiyele yatọ.", "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", - "buy_provider_unavailable": "Olupese lọwọlọwọ ko si." -} \ No newline at end of file + "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", + "totp_auth_url": "TOTP AUTH URL" +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 24295e0ca..c606d4135 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -691,5 +691,6 @@ "onramper_option_description": "快速使用许多付款方式购买加密货币。在大多数国家 /地区可用。利差和费用各不相同。", "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", - "buy_provider_unavailable": "提供者目前不可用。" -} \ No newline at end of file + "buy_provider_unavailable": "提供者目前不可用。", + "totp_auth_url": "TOTP 授权 URL" +} From d6c641242f3c2ead5ec64fe2c87fab7c188650ca Mon Sep 17 00:00:00 2001 From: Serhii Date: Mon, 25 Sep 2023 15:20:11 +0300 Subject: [PATCH 012/161] CW-475-Better-error-message-if-there-isnt-enough-network-fee-asset-available (#1092) * Better error message * minor fixes * localization * Update strings_tl.arb * Update append_translation.sh --- lib/view_model/send/send_view_model.dart | 14 +++++++++++++- res/values/strings_ar.arb | 3 +++ 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 | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 3 ++- res/values/strings_id.arb | 3 ++- 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 | 1 + 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 | 4 +++- 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 + scripts/append_translation.sh | 2 +- 28 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 329b3c4ad..72ce9fdb2 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -30,6 +30,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/generated/i18n.dart'; part 'send_view_model.g.dart'; @@ -325,7 +326,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor state = TransactionCommitted(); } catch (e) { - state = FailureState(e.toString()); + String translatedError = translateErrorMessage(e.toString(), wallet.type, wallet.currency); + state = FailureState(translatedError); } } @@ -412,4 +414,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor selectedCryptoCurrency = wallet.currency; } } + + String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) { + if (walletType == WalletType.ethereum || walletType == WalletType.haven) { + if (error.contains('gas required exceeds allowance (0)') || error.contains('insufficient funds for gas')) { + return S.current.do_not_have_enough_gas_asset(currency.toString()); + } + } + + return error; + } } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index b10dfbe8e..3a90aa308 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -689,5 +689,8 @@ "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", "buy_provider_unavailable": "مزود حاليا غير متوفر.", + + "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ" } + diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 828af2102..39ce6196b 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -685,5 +685,6 @@ "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", + "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index ea4dc1856..0c063f0c9 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -685,5 +685,6 @@ "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", + "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", "totp_auth_url": "URL AUTH TOTP" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 09538ccc0..4b2cba6d4 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", + "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", "totp_auth_url": "TOTP-Auth-URL" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 99ba22b18..7e3b1b55d 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -694,5 +694,6 @@ "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", "buy_provider_unavailable": "Provider currently unavailable.", + "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a6175c6c0..f2b0729f3 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", "buy_provider_unavailable": "Proveedor actualmente no disponible.", + "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", "totp_auth_url": "URL de autenticación TOTP" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f659f0b14..a081727fb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", "buy_provider_unavailable": "Fournisseur actuellement indisponible.", + "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", "totp_auth_url": "URL D'AUTORISATION TOTP" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 2b55ffd2a..68051b5d9 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -671,5 +671,6 @@ "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", + "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 69f37ab83..9f531bb29 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -693,5 +693,6 @@ "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", + "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", "totp_auth_url": "TOTP प्रामाणिक यूआरएल" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3c4299092..4b56ef344 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", + "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", "totp_auth_url": "TOTP AUTH URL" -} +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index a5b2e3ffa..6200559e3 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -681,5 +681,6 @@ "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", + "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", "totp_auth_url": "URL Otentikasi TOTP" -} +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 98c13aa2d..e3fd1b94b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", "buy_provider_unavailable": "Provider attualmente non disponibile.", + "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", "totp_auth_url": "URL DI AUT. TOTP" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index f5bea4b58..8dbf429c5 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -693,5 +693,6 @@ "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", "buy_provider_unavailable": "現在、プロバイダーは利用できません。", + "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", "totp_auth_url": "TOTP認証URL" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 79455ff92..da9b99c2b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -693,5 +693,6 @@ "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", + "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", "totp_auth_url": "TOTP 인증 URL" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 4ea13bc27..043484feb 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -691,5 +691,6 @@ "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", + "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 99a469945..2d9f3655e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", + "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", "totp_auth_url": "TOTP AUTH-URL" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 5d32ee12c..6f3a41d0b 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", "buy_provider_unavailable": "Dostawca obecnie niedostępny.", + "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", "totp_auth_url": "Adres URL TOTP AUTH" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ca46a26e7..ea442420f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -692,5 +692,6 @@ "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", "buy_provider_unavailable": "Provedor atualmente indisponível.", + "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", "totp_auth_url": "URL de autenticação TOTP" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index fb3883338..5e70bc544 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -693,5 +693,6 @@ "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", + "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 0e31518eb..8778802a9 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -691,5 +691,6 @@ "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", + "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index d65e36609..18c52aa83 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -687,5 +687,7 @@ "support_title_other_links": "Iba pang mga link sa suporta", "support_description_other_links": "Sumali sa aming mga komunidad o maabot sa amin ang aming mga kasosyo sa pamamagitan ng iba pang mga pamamaraan", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup file.", - "save_to_downloads": "I -save sa mga pag -download" + "save_to_downloads": "I -save sa mga pag -download", + "do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.", + "totp_auth_url": "TOTP AUTH URL" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 16ed18b96..fa4568e93 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -691,5 +691,6 @@ "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", + "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", "totp_auth_url": "TOTP YETKİ URL'si" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c9943dd3a..7b739e201 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -693,5 +693,6 @@ "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", "buy_provider_unavailable": "В даний час постачальник недоступний.", + "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 47c96d9fb..e53784923 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -685,5 +685,6 @@ "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", + "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index ec84589da..0532ae2eb 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -687,5 +687,6 @@ "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", + "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index c606d4135..7c947847e 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -692,5 +692,6 @@ "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", "buy_provider_unavailable": "提供者目前不可用。", + "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", "totp_auth_url": "TOTP 授权 URL" } diff --git a/scripts/append_translation.sh b/scripts/append_translation.sh index 2dc373e0e..0cc33fc0f 100755 --- a/scripts/append_translation.sh +++ b/scripts/append_translation.sh @@ -7,7 +7,7 @@ # if you get an error `command not found` # give the correct permissions to this file using `chmod 777 append_translation.sh` -langs=("ar" "bg" "cs" "de" "en" "es" "fr" "ha" "hi" "hr" "id" "it" "ja" "ko" "my" "nl" "pl" "pt" "ru" "th" "tr" "uk" "ur" "yo" "zh") +langs=("ar" "bg" "cs" "de" "en" "es" "fr" "ha" "hi" "hr" "id" "it" "ja" "ko" "my" "nl" "pl" "pt" "ru" "th" "tl" "tr" "uk" "ur" "yo" "zh") name=$1 text=$2 From 8b2b3bda16c008cbb0d08b832f6b20c569d7ee42 Mon Sep 17 00:00:00 2001 From: fosse Date: Mon, 25 Sep 2023 09:47:39 -0400 Subject: [PATCH 013/161] ens review fixes --- cw_ethereum/lib/ethereum_client.dart | 4 +++ cw_ethereum/lib/ethereum_wallet.dart | 4 +-- cw_ethereum/pubspec.yaml | 4 --- lib/entities/ens_record.dart | 30 +++++++++++++++++++++ lib/entities/parse_address_from_domain.dart | 20 +++++++------- lib/ethereum/cw_ethereum.dart | 4 +-- pubspec_base.yaml | 4 +++ tool/configure.dart | 2 +- 8 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 lib/entities/ens_record.dart diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index 4c74f1ec0..d1b3c819c 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -226,6 +226,10 @@ I/flutter ( 4474): Gas Used: 53000 } } + dynamic getWeb3Client() { + return _client; + } + // Future _getDecimalPlacesForContract(DeployedContract contract) async { // final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); // final contractAbi = ContractAbi.fromJson(abi, "ERC20"); diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index b0712d059..db2f60242 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -509,7 +509,5 @@ abstract class EthereumWalletBase String signMessage(String message, {String? address = null}) => bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); - Future fetchEnsAddress(String name) async { - return await _client.checkEnsName(name); - } + dynamic getWeb3Client() => _client; } diff --git a/cw_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index 7e3572e80..5d19589f3 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -21,10 +21,6 @@ dependencies: hex: ^0.2.0 http: ^1.1.0 shared_preferences: ^2.0.15 - ens_dart: - git: - url: https://github.com/cake-tech/ens_dart.git - ref: main cw_core: path: ../cw_core diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart new file mode 100644 index 000000000..4ca2bba6f --- /dev/null +++ b/lib/entities/ens_record.dart @@ -0,0 +1,30 @@ +import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:ens_dart/ens_dart.dart'; +import 'package:http/http.dart'; +import 'package:web3dart/web3dart.dart'; + +class EnsRecord { + static Future fetchEnsAddress(String name, {WalletBase? wallet}) async { + Web3Client? _client; + + if (wallet != null && wallet.type == WalletType.ethereum) { + _client = ethereum!.getWeb3Client(wallet) as Web3Client?; + } + + if (_client == null) { + _client = Web3Client("ethereum.publicnode.com", Client()); + } + + try { + final ens = Ens(client: _client); + + final addr = await ens.withName(name).getAddress(); + return addr.hex; + } catch (e) { + print(e); + return ""; + } + } +} diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index f2c2f5b3e..7ea673995 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/yat_service.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/ens_record.dart'; import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; @@ -66,7 +67,7 @@ class AddressResolver { }); final userTweetsText = subString.toString(); final addressFromPinnedTweet = - extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker)); + extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker)); if (addressFromPinnedTweet != null) { return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, name: text); @@ -99,6 +100,14 @@ class AddressResolver { return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text); } + if (text.contains(".")) { + var wallet = getIt.get().wallet!; + final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); + if (address.isNotEmpty) { + return ParsedAddress.fetchEnsAddress(name: text, address: address); + } + } + if (formattedName.contains(".")) { final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName); if (txtRecord != null) { @@ -107,15 +116,6 @@ class AddressResolver { return ParsedAddress.fetchOpenAliasAddress(record: record, name: text); } } - if (text.contains(".")) { - var wallet = getIt.get().wallet!; - if (wallet.type == WalletType.ethereum) { - final address = await ethereum!.fetchEnsAddress(wallet, text); - if (address.isNotEmpty) { - return ParsedAddress.fetchEnsAddress(name: text, address: address); - } - } - } } catch (e) { print(e.toString()); } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index d3a8862de..23d9287c9 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -133,7 +133,7 @@ class CWEthereum extends Ethereum { } @override - Future fetchEnsAddress(WalletBase wallet, String name) async { - return (wallet as EthereumWallet).fetchEnsAddress(name); + dynamic getWeb3Client(WalletBase wallet) async { + return (wallet as EthereumWallet).getWeb3Client(); } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index da79a5076..c95a7b5bc 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -82,6 +82,10 @@ dependencies: shared_preferences_android: 2.0.17 url_launcher_android: 6.0.24 sensitive_clipboard: ^1.0.0 + ens_dart: + git: + url: https://github.com/cake-tech/ens_dart.git + ref: main bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git diff --git a/tool/configure.dart b/tool/configure.dart index 19e8ef327..1f9ddf1c6 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -525,7 +525,7 @@ abstract class Ethereum { CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); void updateEtherscanUsageState(WalletBase wallet, bool isEnabled); - Future fetchEnsAddress(WalletBase wallet, String name); + dynamic getWeb3Client(WalletBase wallet); } """; From 1521a67c0304b70b1f3c8961380b23d09266b02c Mon Sep 17 00:00:00 2001 From: fosse Date: Mon, 25 Sep 2023 09:56:34 -0400 Subject: [PATCH 014/161] fixes for other coin types --- lib/entities/ens_record.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 4ca2bba6f..59ae6b9a4 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -20,6 +20,24 @@ class EnsRecord { try { final ens = Ens(client: _client); + dynamic res; + + if (wallet != null) { + switch (wallet.type) { + case WalletType.monero: + return await ens.withName(name).getCoinAddress(CoinType.XMR); + case WalletType.bitcoin: + return await ens.withName(name).getCoinAddress(CoinType.BTC); + case WalletType.litecoin: + return await ens.withName(name).getCoinAddress(CoinType.LTC); + case WalletType.haven: + return await ens.withName(name).getCoinAddress(CoinType.XHV); + case WalletType.ethereum: + default: + return (await ens.withName(name).getAddress()).hex; + } + } + final addr = await ens.withName(name).getAddress(); return addr.hex; } catch (e) { From 51bbaf02a5cde449071188d810483acdae0ecf0e Mon Sep 17 00:00:00 2001 From: fosse Date: Mon, 25 Sep 2023 10:01:45 -0400 Subject: [PATCH 015/161] fixes --- cw_ethereum/lib/ethereum_wallet.dart | 2 +- lib/ethereum/cw_ethereum.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index db2f60242..5f4f669d0 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -509,5 +509,5 @@ abstract class EthereumWalletBase String signMessage(String message, {String? address = null}) => bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); - dynamic getWeb3Client() => _client; + dynamic getWeb3Client() => _client.getWeb3Client(); } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 23d9287c9..eab6c26d3 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -133,7 +133,7 @@ class CWEthereum extends Ethereum { } @override - dynamic getWeb3Client(WalletBase wallet) async { + dynamic getWeb3Client(WalletBase wallet) { return (wallet as EthereumWallet).getWeb3Client(); } } From 9eb6867ab98dbb3a5fec64e0c940c716cf1fd43c Mon Sep 17 00:00:00 2001 From: Procyon Lotor <110021993+ProcyonLotor123@users.noreply.github.com> Date: Thu, 28 Sep 2023 19:20:08 +0300 Subject: [PATCH 016/161] Exolix integration (#1080) * Add Exolix exchange integration * update tx payload * remove import * Improve mapping * Additional fixes * fix apiBaseUrl * Update trade_details_view_model.dart * Update exolix_exchange_provider.dart * Fix status URL * Fix fetch rates API error handling update limits API to use a valid amount and validate on success status code --------- Co-authored-by: Justin Ehrenhofer Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build.yml | 1 + assets/images/exolix.png | Bin 0 -> 1203 bytes .../exchange_provider_description.dart | 7 +- .../exolix/exolix_exchange_provider.dart | 294 ++++++++++++++++++ lib/exchange/exolix/exolix_request.dart | 20 ++ lib/exchange/trade_state.dart | 27 ++ .../screens/dashboard/widgets/trade_row.dart | 3 + lib/store/dashboard/trade_filter_store.dart | 18 +- .../dashboard/dashboard_view_model.dart | 5 + .../exchange/exchange_trade_view_model.dart | 4 + .../exchange/exchange_view_model.dart | 14 + lib/view_model/support_view_model.dart | 5 + lib/view_model/trade_details_view_model.dart | 10 + tool/utils/secret_key.dart | 1 + 14 files changed, 404 insertions(+), 5 deletions(-) create mode 100644 assets/images/exolix.png create mode 100644 lib/exchange/exolix/exolix_exchange_provider.dart create mode 100644 lib/exchange/exolix/exolix_request.dart diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 92d2ad8ba..d01d4222e 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -128,6 +128,7 @@ jobs: echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart diff --git a/assets/images/exolix.png b/assets/images/exolix.png new file mode 100644 index 0000000000000000000000000000000000000000..29e5f2db1d659dc3ca64858d800287c29b0a49fa GIT binary patch literal 1203 zcmV;k1WfyhP)Px(ZAnByRA@upT3t+3M-={MH7sigx|iVHa%-9*rD;eiDv(r5E2W5NYg6q{jN0l$ z+cY7PkiIm<#56^T51O>5sXnx%8dB;{OlVT-Pg|@HO{iU3kPSPJ+FaGHkbfx&^k z^U0W+*a2}bR6Yd3mjrNH!J)t?D0U3qQ4d+sXF8S$NDGx?AlPViQUkrCey{p204jnj zL?B02KBK@FA`^(p5}?87RU3KrI0+OuB{?t%z{Lw}Q!e25y4*nbs2PqafgF#gl+rnW znE_45_k=jgeEqloPK2G1Ku)f*jXLn%bQs5rNkZ7}8&I2#jWP5vwM_4iM4r#J+(13SXI#i^9@dtI5a2YU(uNmhna7;QxP2mD@jUbK>q zKq}(AsKv4JvG;_IfFvtIx1e2Ptm^x{YQ*lyACj#62;gn2pefa(IPSlrzNO2B?5;lB zx_!mZizPA!tl6s8U+PSlsD*by+X0qU5`u$vSFs=~3#Tf|?ZTk^%@542F2Ng5=2<3jsJjPET~|%@St>s0RWmgLcHJj-wyt&6a5@~OM#KvE zM#oTk>MXY&b0aeOlKO*Ni$icOA-MM18V*UlNPgSTLOL`=%ZVf_tpMH;(&P|cEz9_( zY#E2-gh<8N4%{4Occ{>4C2m>q8NFeJpeW#3RgC_QOA)oUmsX-^ezqx0bI%QYex=J) zE>=K$V<*f1i+62D{-#%Pvgmz;#>cg}C^KCF-w0xpk)nvSnD z%usM#BM}bb?u-mvtgeh}Ad4VR3;KO5kbMG_V#Bo-9d6(03;45kf8?{e!UEJi`^*#) zRll9X^#>0vuRc?>O`x)^3G)jVO)=uvjXN=PxhL}Zh3ZO385xoPTW;J!!@pN-8E>1w z6P3&H!q@fjjUd2@!Y#TgN$=}5TB>m`7({Wqb-$0E(DDuthXZeR9ZtA{!TZB#EBPQI zQzM01@^42+sqOSoJs97cZtz$#+g zV@bn%SiH5GTfV)b4#RzSx#a}}TZL1J%v?9to9bh|=%%LT743IaSe&ytDEO_oI%QkfGIfCwHv4qWNGF>{&^PuspT8b41Ke23l zSdm2~7M<)4-Gj!|jVMLQ-jB81#`d5T7eBTA_=E0AsS%i-n!jzn1^}#=pgjR*Pg#Yr zN&CXqr)3ePQ%{CSH#56U!b+I8_%}Z`CA*$vtl7DczP<+U={jod Rkx~Ev002ovPDHLkV1hCPI;#Kx literal 0 HcmV?d00001 diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index e545f69ce..151d018e0 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< static const trocador = ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); - static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: ''); + static const exolix = + ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png'); + + static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); static ExchangeProviderDescription deserialize({required int raw}) { switch (raw) { @@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< case 5: return trocador; case 6: + return exolix; + case 7: return all; default: throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); diff --git a/lib/exchange/exolix/exolix_exchange_provider.dart b/lib/exchange/exolix/exolix_exchange_provider.dart new file mode 100644 index 000000000..0768f1160 --- /dev/null +++ b/lib/exchange/exolix/exolix_exchange_provider.dart @@ -0,0 +1,294 @@ +import 'dart:convert'; +import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/exchange_pair.dart'; +import 'package:cake_wallet/exchange/exchange_provider.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; + +class ExolixExchangeProvider extends ExchangeProvider { + ExolixExchangeProvider() : super(pairList: _supportedPairs()); + + static final apiKey = secrets.exolixApiKey; + static const apiBaseUrl = 'exolix.com'; + static const transactionsPath = '/api/v2/transactions'; + static const ratePath = '/api/v2/rate'; + + static const List _notSupported = [ + CryptoCurrency.usdt, + CryptoCurrency.xhv, + CryptoCurrency.btt, + CryptoCurrency.firo, + CryptoCurrency.zaddr, + CryptoCurrency.xvg, + CryptoCurrency.kmd, + CryptoCurrency.paxg, + CryptoCurrency.rune, + CryptoCurrency.scrt, + CryptoCurrency.btcln, + CryptoCurrency.cro, + CryptoCurrency.ftm, + CryptoCurrency.frax, + CryptoCurrency.gusd, + CryptoCurrency.gtc, + CryptoCurrency.weth, + ]; + + static List _supportedPairs() { + final supportedCurrencies = + CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList(); + + return supportedCurrencies + .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true))) + .expand((i) => i) + .toList(); + } + + @override + String get title => 'Exolix'; + + @override + bool get isAvailable => true; + + @override + bool get isEnabled => true; + + @override + bool get supportsFixedRate => true; + + @override + ExchangeProviderDescription get description => ExchangeProviderDescription.exolix; + + @override + Future checkIsAvailable() async => true; + + static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float'; + + @override + Future fetchLimits( + {required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode}) async { + final params = { + 'rateType': getRateType(isFixedRateMode), + 'amount': '1', + }; + if (isFixedRateMode) { + params['coinFrom'] = _normalizeCurrency(to); + params['coinTo'] = _normalizeCurrency(from); + params['networkFrom'] = _networkFor(to); + params['networkTo'] = _networkFor(from); + } else { + params['coinFrom'] = _normalizeCurrency(from); + params['coinTo'] = _normalizeCurrency(to); + params['networkFrom'] = _networkFor(from); + params['networkTo'] = _networkFor(to); + } + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); + + if (response.statusCode != 200) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + return Limits(min: responseJSON['minAmount'] as double?); + } + + @override + Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + final _request = request as ExolixRequest; + + final headers = {'Content-Type': 'application/json'}; + final body = { + 'coinFrom': _normalizeCurrency(_request.from), + 'coinTo': _normalizeCurrency(_request.to), + 'networkFrom': _networkFor(_request.from), + 'networkTo': _networkFor(_request.to), + 'withdrawalAddress': _request.address, + 'refundAddress': _request.refundAddress, + 'rateType': getRateType(isFixedRateMode), + 'apiToken': apiKey, + }; + + if (isFixedRateMode) { + body['withdrawalAmount'] = _request.toAmount; + } else { + body['amount'] = _request.fromAmount; + } + + final uri = Uri.https(apiBaseUrl, transactionsPath); + final response = await post(uri, headers: headers, body: json.encode(body)); + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final errors = responseJSON['errors'] as Map; + final errorMessage = errors.values.join(', '); + throw Exception(errorMessage); + } + + if (response.statusCode != 200 && response.statusCode != 201) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + final id = responseJSON['id'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final refundAddress = responseJSON['refundAddress'] as String?; + final extraId = responseJSON['depositExtraId'] as String?; + final payoutAddress = responseJSON['withdrawalAddress'] as String; + final amount = responseJSON['amount'].toString(); + + return Trade( + id: id, + from: _request.from, + to: _request.to, + provider: description, + inputAddress: inputAddress, + refundAddress: refundAddress, + extraId: extraId, + createdAt: DateTime.now(), + amount: amount, + state: TradeState.created, + payoutAddress: payoutAddress); + } + + @override + Future findTradeById({required String id}) async { + final findTradeByIdPath = transactionsPath + '/$id'; + final uri = Uri.https(apiBaseUrl, findTradeByIdPath); + final response = await get(uri); + + if (response.statusCode == 404) { + throw TradeNotFoundException(id, provider: description); + } + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final errors = responseJSON['errors'] as Map; + final errorMessage = errors.values.join(', '); + + throw TradeNotFoundException(id, provider: description, description: errorMessage); + } + + if (response.statusCode != 200) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + final coinFrom = responseJSON['coinFrom']['coinCode'] as String; + final from = CryptoCurrency.fromString(coinFrom); + final coinTo = responseJSON['coinTo']['coinCode'] as String; + final to = CryptoCurrency.fromString(coinTo); + final inputAddress = responseJSON['depositAddress'] as String; + final amount = responseJSON['amount'].toString(); + final status = responseJSON['status'] as String; + final state = TradeState.deserialize(raw: _prepareStatus(status)); + final extraId = responseJSON['depositExtraId'] as String?; + final outputTransaction = responseJSON['hashOut']['hash'] as String?; + final payoutAddress = responseJSON['withdrawalAddress'] as String; + + return Trade( + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: amount, + state: state, + extraId: extraId, + outputTransaction: outputTransaction, + payoutAddress: payoutAddress); + } + + @override + Future fetchRate( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode, + required bool isReceiveAmount}) async { + try { + if (amount == 0) { + return 0.0; + } + + final params = { + 'coinFrom': _normalizeCurrency(from), + 'coinTo': _normalizeCurrency(to), + 'networkFrom': _networkFor(from), + 'networkTo': _networkFor(to), + 'rateType': getRateType(isFixedRateMode), + }; + + if (isReceiveAmount) { + params['withdrawalAmount'] = amount.toString(); + } else { + params['amount'] = amount.toString(); + } + + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); + final responseJSON = json.decode(response.body) as Map; + + if (response.statusCode != 200) { + final message = responseJSON['message'] as String?; + throw Exception(message); + } + + final rate = responseJSON['rate'] as double; + + return rate; + } catch (e) { + print(e.toString()); + return 0.0; + } + } + + String _prepareStatus(String status) { + switch (status) { + case 'deleted': + case 'error': + return 'overdue'; + default: + return status; + } + } + + String _networkFor(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.arb: + return 'ARBITRUM'; + default: + return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title; + } + } + + String _normalizeCurrency(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.nano: + return 'XNO'; + case CryptoCurrency.bttc: + return 'BTT'; + case CryptoCurrency.zec: + return 'ZEC'; + default: + return currency.title; + } + } + + String _normalizeTag(String tag) { + switch (tag) { + case 'POLY': + return 'Polygon'; + default: + return tag; + } + } +} diff --git a/lib/exchange/exolix/exolix_request.dart b/lib/exchange/exolix/exolix_request.dart new file mode 100644 index 000000000..e97ffa386 --- /dev/null +++ b/lib/exchange/exolix/exolix_request.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; + +class ExolixRequest extends TradeRequest { + ExolixRequest( + {required this.from, + required this.to, + required this.address, + required this.fromAmount, + required this.toAmount, + required this.refundAddress}); + + CryptoCurrency from; + CryptoCurrency to; + String address; + String fromAmount; + String toAmount; + String refundAddress; +} diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index 98737339c..ebf74ce7a 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -35,6 +35,15 @@ class TradeState extends EnumerableItem with Serializable { static const completed = TradeState(raw: 'completed', title: 'Completed'); static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); + static const wait = TradeState(raw: 'wait', title: 'Waiting'); + static const overdue = TradeState(raw: 'overdue', title: 'Overdue'); + static const refund = TradeState(raw: 'refund', title: 'Refund'); + static const refunded = TradeState(raw: 'refunded', title: 'Refunded'); + static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation'); + static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed'); + static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); + static const sending = TradeState(raw: 'sending', title: 'Sending'); + static const success = TradeState(raw: 'success', title: 'Success'); static TradeState deserialize({required String raw}) { switch (raw) { case 'pending': @@ -77,6 +86,24 @@ class TradeState extends EnumerableItem with Serializable { return failed; case 'completed': return completed; + case 'wait': + return wait; + case 'overdue': + return overdue; + case 'refund': + return refund; + case 'refunded': + return refunded; + case 'confirmation': + return confirmation; + case 'confirmed': + return confirmed; + case 'exchanging': + return exchanging; + case 'sending': + return sending; + case 'success': + return success; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); } diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index a42593f24..7f570b98e 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget { borderRadius: BorderRadius.circular(50), child: Image.asset('assets/images/trocador.png', width: 36, height: 36)); break; + case ExchangeProviderDescription.exolix: + image = Image.asset('assets/images/exolix.png', width: 36, height: 36); + break; default: image = null; } diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index c772a35d6..799e8b951 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -13,7 +13,8 @@ abstract class TradeFilterStoreBase with Store { displaySideShift = true, displayMorphToken = true, displaySimpleSwap = true, - displayTrocador = true; + displayTrocador = true, + displayExolix = true; @observable bool displayXMRTO; @@ -33,8 +34,11 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayTrocador; + @observable + bool displayExolix; + @computed - bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador; + bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix; @action void toggleDisplayExchange(ExchangeProviderDescription provider) { @@ -56,7 +60,10 @@ abstract class TradeFilterStoreBase with Store { break; case ExchangeProviderDescription.trocador: displayTrocador = !displayTrocador; - break; + break; + case ExchangeProviderDescription.exolix: + displayExolix = !displayExolix; + break; case ExchangeProviderDescription.all: if (displayAllTrades) { displayChangeNow = false; @@ -65,6 +72,7 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = false; displaySimpleSwap = false; displayTrocador = false; + displayExolix = false; } else { displayChangeNow = true; displaySideShift = true; @@ -72,6 +80,7 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = true; displaySimpleSwap = true; displayTrocador = true; + displayExolix = true; } break; } @@ -98,7 +107,8 @@ abstract class TradeFilterStoreBase with Store { ||(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) - ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)) + ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) + ||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix)) .toList() : _trades; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 9998eb8be..777db8f8d 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -99,6 +99,11 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.trocador.title, onChanged: () => tradeFilterStore .toggleDisplayExchange(ExchangeProviderDescription.trocador)), + FilterItem( + value: () => tradeFilterStore.displayExolix, + caption: ExchangeProviderDescription.exolix.title, + onChanged: () => tradeFilterStore + .toggleDisplayExchange(ExchangeProviderDescription.exolix)), ] }, subname = '', diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index cfabd994f..346844171 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; @@ -53,6 +54,9 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; + case ExchangeProviderDescription.exolix: + _provider = ExolixExchangeProvider(); + break; } _updateItems(); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 2e90a3a33..bd370cc54 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -6,6 +6,8 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -151,6 +153,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider(useTorOnly: _useTorOnly), + ExolixExchangeProvider(), ]; @observable @@ -547,6 +550,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with amount = isFixedRateMode ? receiveAmount : depositAmount; } + if (provider is ExolixExchangeProvider) { + request = ExolixRequest( + from: depositCurrency, + to: receiveCurrency, + fromAmount: depositAmount.replaceAll(',', '.'), + toAmount: receiveAmount.replaceAll(',', '.'), + refundAddress: depositAddress, + address: receiveAddress); + amount = isFixedRateMode ? receiveAmount : depositAmount; + } + amount = amount.replaceAll(',', '.'); if (limitsState is LimitsLoadedSuccessfully) { diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index d3b14c59b..ccef76154 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -53,6 +53,11 @@ abstract class SupportViewModelBase with Store { icon: 'assets/images/simpleSwap.png', linkTitle: 'support@simpleswap.io', link: 'mailto:support@simpleswap.io'), + LinkListItem( + title: 'Exolix', + icon: 'assets/images/exolix.png', + linkTitle: 'support@exolix.com', + link: 'mailto:support@exolix.com'), if (!isMoneroOnly) ... [ LinkListItem( title: 'Wyre', diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index c0b1ac461..393629237 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -54,6 +55,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; + case ExchangeProviderDescription.exolix: + _provider = ExolixExchangeProvider(); + break; } _updateItems(); @@ -157,6 +161,12 @@ abstract class TradeDetailsViewModelBase with Store { items.add(StandartListItem( title: '${trade.providerName} ${S.current.password}', value: trade.password ?? '')); } + + if (trade.provider == ExchangeProviderDescription.exolix) { + final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}'; + items.add( + TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); + } } void _launchUrl(String url) { diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 8c86e1b7f..5e959b99b 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -32,6 +32,7 @@ class SecretKey { SecretKey('fiatApiKey', () => ''), SecretKey('payfuraApiKey', () => ''), SecretKey('chatwootWebsiteToken', () => ''), + SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), ]; From dc36c31197485aff33980d66583cce366aca2cb9 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Thu, 28 Sep 2023 19:49:46 +0200 Subject: [PATCH 017/161] CW-489 Skip Warning if all monero utxo are selected (#1106) --- cw_monero/lib/monero_wallet.dart | 169 +++++++++++++------------------ 1 file changed, 72 insertions(+), 97 deletions(-) diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index f9b3c1997..75c1df89e 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -37,10 +37,10 @@ const moneroBlockSize = 1000; class MoneroWallet = MoneroWalletBase with _$MoneroWallet; -abstract class MoneroWalletBase extends WalletBase with Store { - MoneroWalletBase({required WalletInfo walletInfo, - required Box unspentCoinsInfo}) +abstract class MoneroWalletBase + extends WalletBase with Store { + MoneroWalletBase( + {required WalletInfo walletInfo, required Box unspentCoinsInfo}) : balance = ObservableMap.of({ CryptoCurrency.xmr: MoneroBalance( fullBalance: monero_wallet.getFullBalance(accountIndex: 0), @@ -112,12 +112,12 @@ abstract class MoneroWalletBase extends WalletBase init() async { await walletAddresses.init(); - balance = ObservableMap.of( - { - currency: MoneroBalance( - fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id), - unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id)) - }); + balance = ObservableMap.of({ + currency: MoneroBalance( + fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id), + unlockedBalance: + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id)) + }); _setListeners(); await updateTransactions(); @@ -125,15 +125,14 @@ abstract class MoneroWalletBase extends WalletBase await save()); + _autoSaveTimer = + Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); } + @override Future? updateBalance() => null; @@ -153,7 +152,8 @@ abstract class MoneroWalletBase extends WalletBase 1; final unlockedBalance = - monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); var allInputsAmount = 0; PendingTransactionDescription pendingTransactionDescription; @@ -208,56 +208,42 @@ abstract class MoneroWalletBase extends WalletBase item.sendAll - || (item.formattedCryptoAmount ?? 0) <= 0)) { + if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.'); } - final int totalAmount = outputs.fold(0, (acc, value) => - acc + (value.formattedCryptoAmount ?? 0)); + final int totalAmount = + outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount); if (unlockedBalance < totalAmount) { throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.'); } - if (allInputsAmount < totalAmount + estimatedFee) { + if (!spendAllCoins && (allInputsAmount < totalAmount + estimatedFee)) { throw MoneroTransactionNoInputsException(inputs.length); } final moneroOutputs = outputs.map((output) { - final outputAddress = output.isParsedAddress - ? output.extractedAddress - : output.address; + final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address; - return MoneroOutput( - address: outputAddress!, - amount: output.cryptoAmount!.replaceAll(',', '.')); + return MoneroOutput( + address: outputAddress!, amount: output.cryptoAmount!.replaceAll(',', '.')); }).toList(); - pendingTransactionDescription = - await transaction_history.createTransactionMultDest( + pendingTransactionDescription = await transaction_history.createTransactionMultDest( outputs: moneroOutputs, priorityRaw: _credentials.priority.serialize(), accountIndex: walletAddresses.account!.id, preferredInputs: inputs); } else { final output = outputs.first; - final address = output.isParsedAddress - ? output.extractedAddress - : output.address; - final amount = output.sendAll - ? null - : output.cryptoAmount!.replaceAll(',', '.'); - final formattedAmount = output.sendAll - ? null - : output.formattedCryptoAmount; + final address = output.isParsedAddress ? output.extractedAddress : output.address; + final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.'); + final formattedAmount = output.sendAll ? null : output.formattedCryptoAmount; if ((formattedAmount != null && unlockedBalance < formattedAmount) || (formattedAmount == null && unlockedBalance <= 0)) { @@ -268,8 +254,9 @@ abstract class MoneroWalletBase extends WalletBase - element.walletId.contains(id) && element.hash.contains(coin.hash)); + final coinInfoList = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash)); if (coinInfoList.isNotEmpty) { final coinInfo = coinInfoList.first; @@ -447,16 +430,15 @@ abstract class MoneroWalletBase extends WalletBase _addCoinInfo(MoneroUnspent coin) async { final newInfo = UnspentCoinsInfo( - walletId: id, - hash: coin.hash, - isFrozen: coin.isFrozen, - isSending: coin.isSending, - noteRaw: coin.note, - address: coin.address, - value: coin.value, - vout: 0, - keyImage: coin.keyImage - ); + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + noteRaw: coin.note, + address: coin.address, + value: coin.value, + vout: 0, + keyImage: coin.keyImage); await unspentCoinsInfo.add(newInfo); } @@ -464,8 +446,8 @@ abstract class MoneroWalletBase extends WalletBase _refreshUnspentCoinsInfo() async { try { final List keys = []; - final currentWalletUnspentCoins = unspentCoinsInfo.values - .where((element) => element.walletId.contains(id)); + final currentWalletUnspentCoins = + unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); if (currentWalletUnspentCoins.isNotEmpty) { currentWalletUnspentCoins.forEach((element) { @@ -486,16 +468,14 @@ abstract class MoneroWalletBase extends WalletBase - monero_wallet.getAddress( - accountIndex: accountIndex, - addressIndex: addressIndex); + monero_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex); @override Future> fetchTransactions() async { transaction_history.refreshTransactions(); - return _getAllTransactionsOfAccount(walletAddresses.account?.id).fold>( - {}, - (Map acc, MoneroTransactionInfo tx) { + return _getAllTransactionsOfAccount(walletAddresses.account?.id) + .fold>({}, + (Map acc, MoneroTransactionInfo tx) { acc[tx.id] = tx; return acc; }); @@ -523,12 +503,11 @@ abstract class MoneroWalletBase extends WalletBase _getAllTransactionsOfAccount(int? accountIndex) => - transaction_history - .getAllTransactions() - .map((row) => MoneroTransactionInfo.fromRow(row)) - .where((element) => element.accountIndex == (accountIndex ?? 0)) - .toList(); + List _getAllTransactionsOfAccount(int? accountIndex) => transaction_history + .getAllTransactions() + .map((row) => MoneroTransactionInfo.fromRow(row)) + .where((element) => element.accountIndex == (accountIndex ?? 0)) + .toList(); void _setListeners() { _listener?.stop(); @@ -550,8 +529,7 @@ abstract class MoneroWalletBase extends WalletBase _askForUpdateTransactionHistory() async => - await updateTransactions(); + Future _askForUpdateTransactionHistory() async => await updateTransactions(); - int _getFullBalance() => - monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id); + int _getFullBalance() => monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id); int _getUnlockedBalance() => monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); @@ -595,8 +571,7 @@ abstract class MoneroWalletBase extends WalletBase Date: Thu, 28 Sep 2023 19:40:15 +0100 Subject: [PATCH 018/161] fix: Cake-2FA-setup-issue (#1097) * fix: Cake 2FA setup issue * fix: 2FA Setup issue * fix: 2FA setup bug --- lib/di.dart | 2 +- lib/entities/preferences_key.dart | 2 +- lib/src/screens/setup_2fa/setup_2fa.dart | 6 ++-- .../screens/setup_2fa/setup_2fa_qr_page.dart | 4 +-- lib/store/settings_store.dart | 21 +++++--------- lib/view_model/set_up_2fa_viewmodel.dart | 29 ++++++++++--------- 6 files changed, 30 insertions(+), 34 deletions(-) diff --git a/lib/di.dart b/lib/di.dart index a308b989a..97dd392c5 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -360,7 +360,7 @@ Future setup({ (onAuthFinished, closable) => AuthPage(getIt.get(), onAuthenticationFinished: onAuthFinished, closable: closable)); - getIt.registerFactory( + getIt.registerLazySingleton( () => Setup2FAViewModel( getIt.get(), getIt.get(), diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index f699f89f8..d32dcbca4 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -19,7 +19,6 @@ class PreferencesKey { 'allow_biometrical_authentication'; static const useTOTP2FA = 'use_totp_2fa'; static const failedTotpTokenTrials = 'failed_token_trials'; - static const totpSecretKey = 'totp_qr_secret_key'; static const disableExchangeKey = 'disable_exchange'; static const exchangeStatusKey = 'exchange_status'; static const currentTheme = 'current_theme'; @@ -75,4 +74,5 @@ class PreferencesKey { static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings = 'should_require_totp_2fa_for_all_security_and_backup_settings'; static const selectedCake2FAPreset = 'selected_cake_2fa_preset'; + static const totpSecretKey = 'totp_secret_key'; } diff --git a/lib/src/screens/setup_2fa/setup_2fa.dart b/lib/src/screens/setup_2fa/setup_2fa.dart index a74152e4f..895fbb9c0 100644 --- a/lib/src/screens/setup_2fa/setup_2fa.dart +++ b/lib/src/screens/setup_2fa/setup_2fa.dart @@ -53,8 +53,10 @@ class Setup2FAPage extends BasePage { SizedBox(height: 86), SettingsCellWithArrow( title: S.current.setup_totp_recommended, - handler: (_) => Navigator.of(context) - .pushReplacementNamed(Routes.setup_2faQRPage), + handler: (_) { + setup2FAViewModel.generateSecretKey(); + return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage); + }, ), StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), ], diff --git a/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart b/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart index e9ab850af..43dbab05f 100644 --- a/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart +++ b/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart @@ -89,7 +89,7 @@ class Setup2FAQRPage extends BasePage { ), SizedBox(height: 8), Text( - '${setup2FAViewModel.secretKey}', + '${setup2FAViewModel.totpSecretKey}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, @@ -108,7 +108,7 @@ class Setup2FAQRPage extends BasePage { child: InkWell( onTap: () { ClipboardUtil.setSensitiveDataToClipboard( - ClipboardData(text: '${setup2FAViewModel.secretKey}')); + ClipboardData(text: '${setup2FAViewModel.totpSecretKey}')); showBar(context, S.of(context).copied_to_clipboard); }, child: Container( diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 2690dcb64..54607815d 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -280,14 +280,13 @@ abstract class SettingsStoreBase with Store { reaction( (_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use)); + reaction((_) => totpSecretKey, + (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey)); reaction( (_) => numberOfFailedTokenTrials, (int failedTokenTrail) => sharedPreferences.setInt(PreferencesKey.failedTotpTokenTrials, failedTokenTrail)); - reaction((_) => totpSecretKey, - (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey)); - reaction( (_) => shouldShowMarketPlaceInDashboard, (bool value) => @@ -422,15 +421,10 @@ abstract class SettingsStoreBase with Store { bool shouldRequireTOTP2FAForAllSecurityAndBackupSettings; @observable - String totpSecretKey; - - @computed - String get totpVersionOneLink { - return 'otpauth://totp/Cake%20Wallet:$deviceName?secret=$totpSecretKey&issuer=Cake%20Wallet&algorithm=SHA512&digits=8&period=30'; - } + bool useTOTP2FA; @observable - bool useTOTP2FA; + String totpSecretKey; @observable int numberOfFailedTokenTrials; @@ -575,8 +569,8 @@ abstract class SettingsStoreBase with Store { final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? false; - final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? ''; final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false; + final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? ''; final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0; final shouldShowMarketPlaceInDashboard = sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true; @@ -677,8 +671,8 @@ abstract class SettingsStoreBase with Store { initialFiatMode: currentFiatApiMode, initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialCake2FAPresetOptions: selectedCake2FAPreset, - initialTotpSecretKey: totpSecretKey, initialUseTOTP2FA: useTOTP2FA, + initialTotpSecretKey: totpSecretKey, initialFailedTokenTrial: tokenTrialNumber, initialExchangeStatus: exchangeStatus, initialTheme: savedTheme, @@ -752,9 +746,8 @@ abstract class SettingsStoreBase with Store { shouldSaveRecipientAddress = sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? shouldSaveRecipientAddress; - totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey; useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA; - + totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey; numberOfFailedTokenTrials = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials; isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; diff --git a/lib/view_model/set_up_2fa_viewmodel.dart b/lib/view_model/set_up_2fa_viewmodel.dart index 0b4b614ab..eacd3128d 100644 --- a/lib/view_model/set_up_2fa_viewmodel.dart +++ b/lib/view_model/set_up_2fa_viewmodel.dart @@ -27,7 +27,6 @@ abstract class Setup2FAViewModelBase with Store { unhighlightTabs = false, selected2FASettings = ObservableList(), state = InitialExecutionState() { - _getRandomBase32SecretKey(); selectCakePreset(selectedCake2FAPreset); reaction((_) => state, _saveLastAuthTime); } @@ -36,9 +35,12 @@ abstract class Setup2FAViewModelBase with Store { static const banTimeout = 180; // 3 minutes final banTimeoutKey = S.current.auth_store_ban_timeout; - String get secretKey => _settingsStore.totpSecretKey; String get deviceName => _settingsStore.deviceName; - String get totpVersionOneLink => _settingsStore.totpVersionOneLink; + + @computed + String get totpSecretKey => _settingsStore.totpSecretKey; + + String totpVersionOneLink = ''; @observable ExecutionState state; @@ -84,9 +86,14 @@ abstract class Setup2FAViewModelBase with Store { bool get shouldRequireTOTP2FAForAllSecurityAndBackupSettings => _settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings; - void _getRandomBase32SecretKey() { - final randomBase32Key = Utils.generateRandomBase32SecretKey(16); - _setBase32SecretKey(randomBase32Key); + @action + void generateSecretKey() { + final _totpSecretKey = Utils.generateRandomBase32SecretKey(16); + + totpVersionOneLink = + 'otpauth://totp/Cake%20Wallet:$deviceName?secret=$_totpSecretKey&issuer=Cake%20Wallet&algorithm=SHA512&digits=8&period=30'; + + setTOTPSecretKey(_totpSecretKey); } @action @@ -95,15 +102,10 @@ abstract class Setup2FAViewModelBase with Store { } @action - void _setBase32SecretKey(String value) { + void setTOTPSecretKey(String value) { _settingsStore.totpSecretKey = value; } - @action - void clearBase32SecretKey() { - _settingsStore.totpSecretKey = ''; - } - Duration? banDuration() { final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey); @@ -145,7 +147,7 @@ abstract class Setup2FAViewModelBase with Store { } final result = Utils.verify( - secretKey: secretKey, + secretKey: totpSecretKey, otp: otpText, ); @@ -156,7 +158,6 @@ abstract class Setup2FAViewModelBase with Store { } else { final value = _settingsStore.numberOfFailedTokenTrials + 1; adjustTokenTrialNumber(value); - print(value); if (_failureCounter >= maxFailedTrials) { final banDuration = await ban(); state = AuthenticationBanned( From b1859b9e6385f3bed049c43e587880e0b05634ff Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 29 Sep 2023 01:53:53 +0300 Subject: [PATCH 019/161] Generic Fixes (#1103) * Update trade_details_page.dart * let web3dart calculate gas price and max gas while just providing the priority fee per gas needed --- cw_ethereum/lib/ethereum_client.dart | 6 ++---- lib/src/screens/trade_details/trade_details_page.dart | 5 +++-- lib/utils/exception_handler.dart | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index e10e79f1e..c2639827f 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -65,13 +65,11 @@ class EthereumClient { bool _isEthereum = currency == CryptoCurrency.eth; - final price = await _client!.getGasPrice(); + final price = _client!.getGasPrice(); final Transaction transaction = Transaction( from: privateKey.address, to: EthereumAddress.fromHex(toAddress), - maxGas: gas, - gasPrice: price, maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), ); @@ -101,7 +99,7 @@ class EthereumClient { return PendingEthereumTransaction( signedTransaction: signedTransaction, amount: amount, - fee: BigInt.from(gas) * price.getInWei, + fee: BigInt.from(gas) * (await price).getInWei, sendTransaction: _sendTransaction, exponent: exponent, ); diff --git a/lib/src/screens/trade_details/trade_details_page.dart b/lib/src/screens/trade_details/trade_details_page.dart index 17683c600..1bb8872e8 100644 --- a/lib/src/screens/trade_details/trade_details_page.dart +++ b/lib/src/screens/trade_details/trade_details_page.dart @@ -51,10 +51,11 @@ class TradeDetailsPageBodyState extends State { @override Widget build(BuildContext context) { return Observer(builder: (_) { - // FIX-ME: Added `context` it was not used here before, maby bug ? + int itemsCount = tradeDetailsViewModel.items.length; + return SectionStandardList( sectionCount: 1, - itemCounter: (int _) => tradeDetailsViewModel.items.length, + itemCounter: (int _) => itemsCount, itemBuilder: (__, index) { final item = tradeDetailsViewModel.items[index]; diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 5c51a8757..b9c659872 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -161,6 +161,7 @@ class ExceptionHandler { "Handshake error in client", "Error while launching http", "OS Error: Network is unreachable", + "ClientException: Write failed, uri=https:", ]; static Future _addDeviceInfo(File file) async { From dff1ee2148f5752bb942a7396a8291be143d176e Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 29 Sep 2023 11:24:07 -0400 Subject: [PATCH 020/161] fix default eth node for ens --- lib/entities/ens_record.dart | 2 +- lib/entities/parse_address_from_domain.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 59ae6b9a4..ec5804425 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -14,7 +14,7 @@ class EnsRecord { } if (_client == null) { - _client = Web3Client("ethereum.publicnode.com", Client()); + _client = Web3Client("https://ethereum.publicnode.com", Client()); } try { diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 7ea673995..86efec5a7 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -103,6 +103,7 @@ class AddressResolver { if (text.contains(".")) { var wallet = getIt.get().wallet!; final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); + print(address); if (address.isNotEmpty) { return ParsedAddress.fetchEnsAddress(name: text, address: address); } From 2775e1d9f8b8eb63f81c239c8f4e9f01d674bef9 Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 29 Sep 2023 11:25:10 -0400 Subject: [PATCH 021/161] remove print statement --- lib/entities/parse_address_from_domain.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 86efec5a7..7ea673995 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -103,7 +103,6 @@ class AddressResolver { if (text.contains(".")) { var wallet = getIt.get().wallet!; final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); - print(address); if (address.isNotEmpty) { return ParsedAddress.fetchEnsAddress(name: text, address: address); } From c71e4b46891f223357b3062e4c3ed527bc5bbd9d Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 29 Sep 2023 11:28:23 -0400 Subject: [PATCH 022/161] prevent fillint empty addresses --- lib/entities/parse_address_from_domain.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 7ea673995..b6f92c84b 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -103,7 +103,7 @@ class AddressResolver { if (text.contains(".")) { var wallet = getIt.get().wallet!; final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); - if (address.isNotEmpty) { + if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") { return ParsedAddress.fetchEnsAddress(name: text, address: address); } } From 183d0be20bd12e2c112219aa81832f405ec7e5d9 Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 29 Sep 2023 11:58:19 -0400 Subject: [PATCH 023/161] limit ens to just .eth domains --- lib/entities/parse_address_from_domain.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index b6f92c84b..589063e7b 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -100,7 +100,7 @@ class AddressResolver { return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text); } - if (text.contains(".")) { + if (text.endsWith(".eth")) { var wallet = getIt.get().wallet!; final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") { From 2e9b937a6ddf30ad15aaf46aa48cf374882ac7a1 Mon Sep 17 00:00:00 2001 From: fosse Date: Fri, 29 Sep 2023 13:08:02 -0400 Subject: [PATCH 024/161] updated ens message to be consistent with others --- lib/src/screens/send/widgets/extract_address_from_parsed.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index 86274f29b..2d0847158 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -20,7 +20,7 @@ Future extractAddressFromParsed( break; case ParseFrom.ens: title = S.of(context).address_detected; - content = S.of(context).address_from_ens(parsedAddress.name); + content = S.of(context).extracted_address_content('${parsedAddress.name} (ENS)'); address = parsedAddress.addresses.first; break; case ParseFrom.openAlias: From ae71f2045b00662d6457c803083cdc531c38afec Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 29 Sep 2023 20:28:26 +0300 Subject: [PATCH 025/161] New versions v4.9.2 and v1.6.2 (#1108) --- assets/text/Monerocom_Release_Notes.txt | 6 +++--- assets/text/Release_Notes.txt | 9 ++++----- scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_env.sh | 8 ++++---- scripts/macos/app_env.sh | 4 ++-- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt index cbe201cf8..9393f7768 100644 --- a/assets/text/Monerocom_Release_Notes.txt +++ b/assets/text/Monerocom_Release_Notes.txt @@ -1,3 +1,3 @@ -Enhance Monero coin control -Add Filipino localization -Bug Fixes \ No newline at end of file +Fix 2FA code issue +Bug fixes +Minor enhancements \ No newline at end of file diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt index 263e7ccfe..1fd86c9ca 100644 --- a/assets/text/Release_Notes.txt +++ b/assets/text/Release_Notes.txt @@ -1,5 +1,4 @@ -New Buy Provider Robinhood -Fix sending Ethereum issue -Enhance Monero coin control -Add Filipino localization -Bug Fixes \ No newline at end of file +Ethereum enhancements and bug fixes +Fix 2FA code issue +Bug fixes +Minor enhancements \ No newline at end of file diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index dd8c83948..92c8a0559 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.1" -MONERO_COM_BUILD_NUMBER=57 +MONERO_COM_VERSION="1.6.2" +MONERO_COM_BUILD_NUMBER=58 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.9.1" -CAKEWALLET_BUILD_NUMBER=170 +CAKEWALLET_VERSION="4.9.2" +CAKEWALLET_BUILD_NUMBER=171 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 714441167..078688918 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.6.1" -MONERO_COM_BUILD_NUMBER=55 +MONERO_COM_VERSION="1.6.2" +MONERO_COM_BUILD_NUMBER=56 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.9.1" -CAKEWALLET_BUILD_NUMBER=183 +CAKEWALLET_VERSION="4.9.2" +CAKEWALLET_BUILD_NUMBER=185 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index bb2aa2ebc..5103d42b2 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.2.1" -CAKEWALLET_BUILD_NUMBER=32 +CAKEWALLET_VERSION="1.2.2" +CAKEWALLET_BUILD_NUMBER=33 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then From e83063535eb9791e967dfe0e02dcc8a44bdc1b33 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Fri, 29 Sep 2023 12:32:48 -0500 Subject: [PATCH 026/161] clean-up: remove address_from_ens --- 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 | 1 - res/values/strings_fr.arb | 1 - res/values/strings_ha.arb | 1 - res/values/strings_hi.arb | 1 - res/values/strings_hr.arb | 1 - res/values/strings_id.arb | 1 - 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 | 1 - 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 - 26 files changed, 26 deletions(-) diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 25352ccaa..46a454758 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -689,7 +689,6 @@ "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", "buy_provider_unavailable": "مزود حاليا غير متوفر.", - "address_from_ens": "هذا العنوان من ${domain} على ENS", "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ddb5378c5..39ce6196b 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -685,7 +685,6 @@ "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", - "address_from_ens": "Този адрес е от ${domain} на ENS", "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index a047a29d7..0c063f0c9 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -685,7 +685,6 @@ "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", - "address_from_ens": "Tato adresa pochází z ${domain} na ENS", "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", "totp_auth_url": "URL AUTH TOTP" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index a7192d90f..4b2cba6d4 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", - "address_from_ens": "Diese Adresse stammt von ${domain} auf Ens", "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", "totp_auth_url": "TOTP-Auth-URL" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 97464efef..7e3b1b55d 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -694,7 +694,6 @@ "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", "buy_provider_unavailable": "Provider currently unavailable.", - "address_from_ens": "This address is from ${domain} on ENS", "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 3480ef558..f2b0729f3 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", "buy_provider_unavailable": "Proveedor actualmente no disponible.", - "address_from_ens": "Esta dirección es de ${domain} en ENS", "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", "totp_auth_url": "URL de autenticación TOTP" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f5f556c8f..a081727fb 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", "buy_provider_unavailable": "Fournisseur actuellement indisponible.", - "address_from_ens": "Cette adresse provient de ${domain} sur ENS", "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", "totp_auth_url": "URL D'AUTORISATION TOTP" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index c714038d3..68051b5d9 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -671,7 +671,6 @@ "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", - "address_from_ens": "Wannan adireshin daga ${domain} Don tabbatarwa", "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index c3a5cd57d..9f531bb29 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -693,7 +693,6 @@ "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", - "address_from_ens": "यह पता ENS पर ${domain} से है", "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", "totp_auth_url": "TOTP प्रामाणिक यूआरएल" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index c8d86f778..4b56ef344 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", - "address_from_ens": "Ova je adresa od ${domain} na ens", "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", "totp_auth_url": "TOTP AUTH URL" } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f7f955e8d..6200559e3 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -681,7 +681,6 @@ "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", - "address_from_ens": "Alamat ini dari ${domain} di ENS", "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", "totp_auth_url": "URL Otentikasi TOTP" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 325a7af23..e3fd1b94b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", "buy_provider_unavailable": "Provider attualmente non disponibile.", - "address_from_ens": "Questo indirizzo è da ${domain} su ENS", "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", "totp_auth_url": "URL DI AUT. TOTP" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 6ae012197..8dbf429c5 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -693,7 +693,6 @@ "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", "buy_provider_unavailable": "現在、プロバイダーは利用できません。", - "address_from_ens": "このアドレスはENSの${domain}からです", "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", "totp_auth_url": "TOTP認証URL" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 2264bc778..da9b99c2b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -693,7 +693,6 @@ "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", - "address_from_ens": "이 주소는 ens의 ${domain}에서입니다.", "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", "totp_auth_url": "TOTP 인증 URL" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 62edd91ed..043484feb 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -691,7 +691,6 @@ "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", - "address_from_ens": "ဒီလိပ်စာက ens အပေါ် ${domain} မှဖြစ်ပါတယ်", "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 5f5a5496b..2d9f3655e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", - "address_from_ens": "Dit adres is van ${domain} op ENS", "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", "totp_auth_url": "TOTP AUTH-URL" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 84beb2ff1..6f3a41d0b 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", "buy_provider_unavailable": "Dostawca obecnie niedostępny.", - "address_from_ens": "Ten adres pochodzi od ${domain} na ens", "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", "totp_auth_url": "Adres URL TOTP AUTH" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index dd6d0c297..ea442420f 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -692,7 +692,6 @@ "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", "buy_provider_unavailable": "Provedor atualmente indisponível.", - "address_from_ens": "Este endereço é de ${domain} no ENS", "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", "totp_auth_url": "URL de autenticação TOTP" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 5678da6a2..5e70bc544 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -693,7 +693,6 @@ "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", - "address_from_ens": "Этот адрес от ${domain} на ENS", "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ" } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 1c540c2f8..8778802a9 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -691,7 +691,6 @@ "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", - "address_from_ens": "ที่อยู่นี้มาจาก ${domain} บน Ens", "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 344203165..18c52aa83 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -688,7 +688,6 @@ "support_description_other_links": "Sumali sa aming mga komunidad o maabot sa amin ang aming mga kasosyo sa pamamagitan ng iba pang mga pamamaraan", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup file.", "save_to_downloads": "I -save sa mga pag -download", - "address_from_ens": "Ang address na ito ay mula sa ${domain} sa ens", "do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.", "totp_auth_url": "TOTP AUTH URL" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 573c54d9f..fa4568e93 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -691,7 +691,6 @@ "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", - "address_from_ens": "Bu adres ens'deki ${domain} 'den", "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", "totp_auth_url": "TOTP YETKİ URL'si" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index cc88fce30..7b739e201 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -693,7 +693,6 @@ "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", "buy_provider_unavailable": "В даний час постачальник недоступний.", - "address_from_ens": "Ця адреса від ${domain} на ENS", "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index aff30aec4..e53784923 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -685,7 +685,6 @@ "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", - "address_from_ens": "یہ پتہ ENS پر ${domain} سے ہے", "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 4719793d5..0532ae2eb 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -687,7 +687,6 @@ "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", - "address_from_ens": "Adirẹsi yii ni lati ${domain} Lori ens", "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", "totp_auth_url": "TOTP AUTH URL" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 28958c7f2..7c947847e 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -692,7 +692,6 @@ "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", "buy_provider_unavailable": "提供者目前不可用。", - "address_from_ens": "此地址来自ENS上的${domain}", "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", "totp_auth_url": "TOTP 授权 URL" } From cb48d73ae0c979e96df2cdc31360572a87f92df2 Mon Sep 17 00:00:00 2001 From: fosse Date: Tue, 3 Oct 2023 10:29:56 -0400 Subject: [PATCH 027/161] review fixes --- cw_ethereum/lib/ethereum_client.dart | 17 +---------------- cw_ethereum/lib/ethereum_wallet.dart | 2 +- lib/entities/ens_record.dart | 2 -- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index e5498bf7d..cb09f8f89 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -209,22 +209,7 @@ I/flutter ( 4474): Gas Used: 53000 } } - Future checkEnsName(String ensName) async { - if (_client == null) { - return ""; - } - try { - final ens = Ens(client: _client!); - - final addr = await ens.withName(ensName).getAddress(); - return addr.hex; - } catch (e) { - print(e); - return ""; - } - } - - dynamic getWeb3Client() { + Web3Client? getWeb3Client() { return _client; } diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 5f4f669d0..50b30c5ae 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -509,5 +509,5 @@ abstract class EthereumWalletBase String signMessage(String message, {String? address = null}) => bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); - dynamic getWeb3Client() => _client.getWeb3Client(); + Web3Client? getWeb3Client() => _client.getWeb3Client(); } diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index ec5804425..28a129f1b 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -20,8 +20,6 @@ class EnsRecord { try { final ens = Ens(client: _client); - dynamic res; - if (wallet != null) { switch (wallet.type) { case WalletType.monero: From 431a17ab08e6735571349204772a21b92fcda610 Mon Sep 17 00:00:00 2001 From: fosse Date: Tue, 3 Oct 2023 10:55:38 -0400 Subject: [PATCH 028/161] should've been nullable --- lib/entities/parse_address_from_domain.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 589063e7b..dcaee5815 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/twitter/twitter_api.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; @@ -101,7 +102,7 @@ class AddressResolver { } if (text.endsWith(".eth")) { - var wallet = getIt.get().wallet!; + WalletBase? wallet = getIt.get().wallet!; final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") { return ParsedAddress.fetchEnsAddress(name: text, address: address); From 32643823e58fd634a7180aa7ea59ffe7b01f02b7 Mon Sep 17 00:00:00 2001 From: Adegoke David <64401859+Blazebrain@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:56:10 +0100 Subject: [PATCH 029/161] Cw 451 wallet connect for ethereum (#1049) * Update Flutter Update packages * Feat: Wallet connect for ethereum * Fix localization issues Fix UI issues Update old packages Update workflow Update how to build guide * feat: Wallet connect * feat: Add wallet connect for ethereum * chore: Add eth dependencies in configure file * Minor: `WalletConnect` settings name, not `Wallet connect` * fix: Merge conflicts * fix: Issues with test cases on various dApps, introduce Arbitrum rinkerby as suported chain * ui: Design fixes for WalletConnect flow * chore: Update repo and comment out send apk to channel in workflow * fix: Core implementation * feat: WalletConnect WIP * feat: WalletConnect WIP * feat: WalletConnect WIP * chore: Unused parameters WIP [skip ci] * fix: Code review fixes * Feat: WalletConnect feat WIP * feat: WalletConnect * feat: WalletConnect * feat: WalletConnect * Feat: WalletConnect * Feat: WalletConnect * feat: Remove queue support for the bottomsheet * feat: WalletConnect feature, bug fixes, folder restructuring, localization * Feat: Add positive feedback prompt on successful transaction * fix: Delete session bug * fix: dependencies registration WIP * feat: Registering dependencies for walletconnect * chore: Move key data to secrets * chore: ensure appropriate null checks * chore: localization * chore: Remove unused code * localization * chore: Remove unused code * chore: Remove unused code * chore: Add walletconnect project id key entry * fix: Revert bash command for linnux support * fix: Issues with translation in some languages and making unneeded external variable private * fix: Add bottomsheet listener to desktop dashboard page * Generalize ethereum not enough gas error check --------- Co-authored-by: OmarHatem Co-authored-by: Justin Ehrenhofer --- .github/workflows/pr_test_build.yml | 34 +- assets/images/walletconnect_logo.png | Bin 0 -> 74348 bytes cw_ethereum/lib/ethereum_wallet.dart | 2 + lib/core/wallet_connect/chain_service.dart | 5 + .../wallet_connect/eth_transaction_model.dart | 60 ++++ lib/core/wallet_connect/evm_chain_id.dart | 35 +++ .../wallet_connect/evm_chain_service.dart | 294 ++++++++++++++++++ .../models/auth_request_model.dart | 16 + .../models/bottom_sheet_queue_item_model.dart | 20 ++ .../models/chain_key_model.dart | 16 + .../models/connection_model.dart | 18 ++ .../models/session_request_model.dart | 14 + .../wallet_connect_key_service.dart | 72 +++++ .../wc_bottom_sheet_service.dart | 43 +++ .../wallet_connect/web3wallet_service.dart | 277 +++++++++++++++++ lib/di.dart | 39 ++- lib/entities/preferences_key.dart | 6 +- lib/ethereum/cw_ethereum.dart | 14 + lib/main.dart | 21 +- lib/src/screens/dashboard/dashboard_page.dart | 179 ++++++----- .../dashboard/desktop_dashboard_page.dart | 53 ++-- lib/src/screens/root/root.dart | 16 +- .../settings/connection_sync_page.dart | 22 +- .../widgets/wallet_connect_button.dart | 46 +++ .../utils/namespace_model_builder.dart | 71 +++++ .../wallet_connect/utils/string_parsing.dart | 16 + .../wc_connections_listing_view.dart | 142 +++++++++ .../wc_pairing_detail_page.dart | 186 +++++++++++ .../widgets/connection_item_widget.dart | 102 ++++++ .../widgets/connection_request_widget.dart | 166 ++++++++++ .../widgets/connection_widget.dart | 45 +++ .../widgets/error_display_widget.dart | 36 +++ .../widgets/modals/bottom_sheet_listener.dart | 62 ++++ .../widgets/modals/web3_request_modal.dart | 48 +++ .../widgets/pairing_item_widget.dart | 82 +++++ lib/store/settings_store.dart | 3 +- .../dashboard/dashboard_view_model.dart | 252 ++++++++------- lib/view_model/send/send_view_model.dart | 2 +- pubspec_base.yaml | 2 + res/values/strings_ar.arb | 25 +- res/values/strings_bg.arb | 23 +- res/values/strings_cs.arb | 23 +- res/values/strings_de.arb | 23 +- res/values/strings_en.arb | 23 +- res/values/strings_es.arb | 23 +- res/values/strings_fr.arb | 23 +- res/values/strings_ha.arb | 23 +- res/values/strings_hi.arb | 23 +- res/values/strings_hr.arb | 25 +- res/values/strings_id.arb | 25 +- res/values/strings_it.arb | 23 +- res/values/strings_ja.arb | 23 +- res/values/strings_ko.arb | 23 +- res/values/strings_my.arb | 23 +- res/values/strings_nl.arb | 23 +- res/values/strings_pl.arb | 23 +- res/values/strings_pt.arb | 23 +- res/values/strings_ru.arb | 23 +- res/values/strings_th.arb | 23 +- res/values/strings_tl.arb | 31 +- res/values/strings_tr.arb | 23 +- res/values/strings_uk.arb | 23 +- res/values/strings_ur.arb | 23 +- res/values/strings_yo.arb | 23 +- res/values/strings_zh.arb | 23 +- tool/configure.dart | 3 + tool/utils/secret_key.dart | 1 + 67 files changed, 2819 insertions(+), 314 deletions(-) create mode 100644 assets/images/walletconnect_logo.png create mode 100644 lib/core/wallet_connect/chain_service.dart create mode 100644 lib/core/wallet_connect/eth_transaction_model.dart create mode 100644 lib/core/wallet_connect/evm_chain_id.dart create mode 100644 lib/core/wallet_connect/evm_chain_service.dart create mode 100644 lib/core/wallet_connect/models/auth_request_model.dart create mode 100644 lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart create mode 100644 lib/core/wallet_connect/models/chain_key_model.dart create mode 100644 lib/core/wallet_connect/models/connection_model.dart create mode 100644 lib/core/wallet_connect/models/session_request_model.dart create mode 100644 lib/core/wallet_connect/wallet_connect_key_service.dart create mode 100644 lib/core/wallet_connect/wc_bottom_sheet_service.dart create mode 100644 lib/core/wallet_connect/web3wallet_service.dart create mode 100644 lib/src/screens/settings/widgets/wallet_connect_button.dart create mode 100644 lib/src/screens/wallet_connect/utils/namespace_model_builder.dart create mode 100644 lib/src/screens/wallet_connect/utils/string_parsing.dart create mode 100644 lib/src/screens/wallet_connect/wc_connections_listing_view.dart create mode 100644 lib/src/screens/wallet_connect/wc_pairing_detail_page.dart create mode 100644 lib/src/screens/wallet_connect/widgets/connection_item_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/connection_request_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/connection_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/error_display_widget.dart create mode 100644 lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart create mode 100644 lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart create mode 100644 lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index d01d4222e..0f2dfc25b 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -2,11 +2,10 @@ name: PR Test Build on: pull_request: - branches: [ main ] + branches: [main] jobs: PR_test_build: - runs-on: ubuntu-20.04 env: STORE_PASS: test@cake_wallet @@ -23,12 +22,12 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '8.x' + java-version: "8.x" - name: Flutter action uses: subosito/flutter-action@v1 with: - flutter-version: '3.10.x' + flutter-version: "3.10.x" channel: stable - name: Install package dependencies @@ -131,6 +130,7 @@ jobs: echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - name: Rename app run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties @@ -140,18 +140,18 @@ jobs: cd /opt/android/cake_wallet flutter build apk --release -# - name: Push to App Center -# run: | -# echo 'Installing App Center CLI tools' -# npm install -g appcenter-cli -# echo "Publishing test to App Center" -# appcenter distribute release \ -# --group "Testers" \ -# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ -# --release-notes ${GITHUB_HEAD_REF} \ -# --app Cake-Labs/Cake-Wallet \ -# --token ${{ secrets.APP_CENTER_TOKEN }} \ -# --quiet + # - name: Push to App Center + # run: | + # echo 'Installing App Center CLI tools' + # npm install -g appcenter-cli + # echo "Publishing test to App Center" + # appcenter distribute release \ + # --group "Testers" \ + # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ + # --release-notes ${GITHUB_HEAD_REF} \ + # --app Cake-Labs/Cake-Wallet \ + # --token ${{ secrets.APP_CENTER_TOKEN }} \ + # --quiet - name: Rename apk file run: | @@ -171,6 +171,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' + title: "${{github.head_ref}}.apk" filename: ${{github.head_ref}}.apk initial_comment: ${{ github.event.head_commit.message }} diff --git a/assets/images/walletconnect_logo.png b/assets/images/walletconnect_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9024b972c482b8410134e259589dce15d934cd18 GIT binary patch literal 74348 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 LtDnm{r-UW|z _ethPrivateKey; + late EthereumClient _client; int? _gasPrice; diff --git a/lib/core/wallet_connect/chain_service.dart b/lib/core/wallet_connect/chain_service.dart new file mode 100644 index 000000000..1e3ce3efd --- /dev/null +++ b/lib/core/wallet_connect/chain_service.dart @@ -0,0 +1,5 @@ +abstract class ChainService { + String getNamespace(); + String getChainId(); + List getEvents(); +} diff --git a/lib/core/wallet_connect/eth_transaction_model.dart b/lib/core/wallet_connect/eth_transaction_model.dart new file mode 100644 index 000000000..deb33586f --- /dev/null +++ b/lib/core/wallet_connect/eth_transaction_model.dart @@ -0,0 +1,60 @@ +class WCEthereumTransactionModel { + final String from; + final String to; + final String value; + final String? nonce; + final String? gasPrice; + final String? maxFeePerGas; + final String? maxPriorityFeePerGas; + final String? gas; + final String? gasLimit; + final String? data; + + WCEthereumTransactionModel({ + required this.from, + required this.to, + required this.value, + this.nonce, + this.gasPrice, + this.maxFeePerGas, + this.maxPriorityFeePerGas, + this.gas, + this.gasLimit, + this.data, + }); + + factory WCEthereumTransactionModel.fromJson(Map json) { + return WCEthereumTransactionModel( + from: json['from'] as String, + to: json['to'] as String, + value: json['value'] as String, + nonce: json['nonce'] as String?, + gasPrice: json['gasPrice'] as String?, + maxFeePerGas: json['maxFeePerGas'] as String?, + maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?, + gas: json['gas'] as String?, + gasLimit: json['gasLimit'] as String?, + data: json['data'] as String?, + ); + } + + Map toJson() { + return { + 'from': from, + 'to': to, + 'value': value, + 'nonce': nonce, + 'gasPrice': gasPrice, + 'maxFeePerGas': maxFeePerGas, + 'maxPriorityFeePerGas': maxPriorityFeePerGas, + 'gas': gas, + 'gasLimit': gasLimit, + 'data': data, + }; + } + + @override + String toString() { + return 'EthereumTransactionModel(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)'; + } +} diff --git a/lib/core/wallet_connect/evm_chain_id.dart b/lib/core/wallet_connect/evm_chain_id.dart new file mode 100644 index 000000000..b71fb562e --- /dev/null +++ b/lib/core/wallet_connect/evm_chain_id.dart @@ -0,0 +1,35 @@ +import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart'; + +enum EVMChainId { + ethereum, + polygon, + goerli, + mumbai, + arbitrum, +} + +extension EVMChainIdX on EVMChainId { + String chain() { + String name = ''; + + switch (this) { + case EVMChainId.ethereum: + name = '1'; + break; + case EVMChainId.polygon: + name = '137'; + break; + case EVMChainId.goerli: + name = '5'; + break; + case EVMChainId.arbitrum: + name = '42161'; + break; + case EVMChainId.mumbai: + name = '80001'; + break; + } + + return '${EvmChainServiceImpl.namespace}:$name'; + } +} diff --git a/lib/core/wallet_connect/evm_chain_service.dart b/lib/core/wallet_connect/evm_chain_service.dart new file mode 100644 index 000000000..bcc6622fa --- /dev/null +++ b/lib/core/wallet_connect/evm_chain_service.dart @@ -0,0 +1,294 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:typed_data'; + +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/src/screens/wallet_connect/widgets/error_display_widget.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; +import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart'; +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; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:web3dart/web3dart.dart'; +import 'chain_service.dart'; +import 'wallet_connect_key_service.dart'; + +class EvmChainServiceImpl implements ChainService { + final AppStore appStore; + final BottomSheetService bottomSheetService; + final Web3Wallet wallet; + final WalletConnectKeyService wcKeyService; + + static const namespace = 'eip155'; + static const pSign = 'personal_sign'; + static const eSign = 'eth_sign'; + static const eSignTransaction = 'eth_signTransaction'; + static const eSignTypedData = 'eth_signTypedData_v4'; + static const eSendTransaction = 'eth_sendTransaction'; + + final EVMChainId reference; + + final Web3Client ethClient; + + EvmChainServiceImpl({ + required this.reference, + required this.appStore, + required this.wcKeyService, + required this.bottomSheetService, + required this.wallet, + Web3Client? ethClient, + }) : ethClient = ethClient ?? + Web3Client( + appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(), + http.Client(), + ) { + + for (final String event in getEvents()) { + wallet.registerEventEmitter(chainId: getChainId(), event: event); + } + wallet.registerRequestHandler( + chainId: getChainId(), + method: pSign, + handler: personalSign, + ); + wallet.registerRequestHandler( + chainId: getChainId(), + method: eSign, + handler: ethSign, + ); + wallet.registerRequestHandler( + chainId: getChainId(), + method: eSignTransaction, + handler: ethSignTransaction, + ); + wallet.registerRequestHandler( + chainId: getChainId(), + method: eSendTransaction, + handler: ethSignTransaction, + ); + wallet.registerRequestHandler( + chainId: getChainId(), + method: eSignTypedData, + handler: ethSignTypedData, + ); + } + + @override + String getNamespace() { + return namespace; + } + + @override + String getChainId() { + return reference.chain(); + } + + @override + List getEvents() { + return ['chainChanged', 'accountsChanged']; + } + + Future requestAuthorization(String? text) async { + // Show the bottom sheet + final bool? isApproved = await bottomSheetService.queueBottomSheet( + widget: Web3RequestModal( + child: ConnectionWidget( + title: S.current.signTransaction, + info: [ + ConnectionModel( + text: text, + ), + ], + ), + ), + ) as bool?; + + if (isApproved != null && isApproved == false) { + return 'User rejected signature'; + } + + return null; + } + + Future personalSign(String topic, dynamic parameters) async { + log('received personal sign request: $parameters'); + + final String message; + if (parameters[0] == null) { + message = ''; + } else { + message = parameters[0].toString().utf8Message; + } + + final String? authError = await requestAuthorization(message); + + if (authError != null) { + return authError; + } + + try { + // Load the private key + final List keys = wcKeyService.getKeysForChain(getChainId()); + + final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + final String signature = hex.encode( + credentials.signPersonalMessageToUint8List(Uint8List.fromList(utf8.encode(message))), + ); + + return '0x$signature'; + } catch (e) { + log(e.toString()); + bottomSheetService.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget( + message: '${S.current.errorGettingCredentials} ${e.toString()}', + ), + ); + return 'Failed: Error while getting credentials'; + } + } + + Future ethSign(String topic, dynamic parameters) async { + log('received eth sign request: $parameters'); + + final String message; + if (parameters[1] == null) { + message = ''; + } else { + message = parameters[1].toString().utf8Message; + } + + final String? authError = await requestAuthorization(message); + if (authError != null) { + return authError; + } + + try { + // Load the private key + final List keys = wcKeyService.getKeysForChain(getChainId()); + + final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + final String signature = hex.encode( + credentials.signPersonalMessageToUint8List( + Uint8List.fromList(utf8.encode(message)), + ), + ); + log(signature); + + return '0x$signature'; + } catch (e) { + log('error: ${e.toString()}'); + bottomSheetService.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget(message: '${S.current.error}: ${e.toString()}'), + ); + return 'Failed'; + } + } + + Future ethSignTransaction(String topic, dynamic parameters) async { + log('received eth sign transaction request: $parameters'); + + final paramsData = parameters[0] as Map; + + final message = _convertToReadable(paramsData); + + final String? authError = await requestAuthorization(message); + + if (authError != null) { + return authError; + } + + // Load the private key + final List keys = wcKeyService.getKeysForChain(getChainId()); + + final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); + + WCEthereumTransactionModel ethTransaction = + WCEthereumTransactionModel.fromJson(parameters[0] as Map); + + final transaction = Transaction( + from: EthereumAddress.fromHex(ethTransaction.from), + to: EthereumAddress.fromHex(ethTransaction.to), + maxGas: ethTransaction.gasLimit != null ? int.tryParse(ethTransaction.gasLimit ?? "") : null, + gasPrice: ethTransaction.gasPrice != null + ? EtherAmount.inWei(BigInt.parse(ethTransaction.gasPrice ?? "")) + : null, + value: EtherAmount.inWei(BigInt.parse(ethTransaction.value)), + data: hexToBytes(ethTransaction.data ?? ""), + nonce: ethTransaction.nonce != null ? int.tryParse(ethTransaction.nonce ?? "") : null, + ); + + try { + final result = await ethClient.sendTransaction(credentials, transaction); + + log('Result: $result'); + + bottomSheetService.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget( + message: S.current.awaitDAppProcessing, + isError: false, + ), + ); + + return result; + } catch (e) { + log('An error has occured while signing transaction: ${e.toString()}'); + bottomSheetService.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget( + message: '${S.current.errorSigningTransaction}: ${e.toString()}', + ), + ); + return 'Failed'; + } + } + + Future ethSignTypedData(String topic, dynamic parameters) async { + log('received eth sign typed data request: $parameters'); + final String? data = parameters[1] as String?; + + final String? authError = await requestAuthorization(data); + + if (authError != null) { + return authError; + } + + final List keys = wcKeyService.getKeysForChain(getChainId()); + + return EthSigUtil.signTypedData( + privateKey: keys[0].privateKey, + jsonData: data ?? '', + version: TypedDataVersion.V4, + ); + } + + String _convertToReadable(Map data) { + String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString(); + String value = data['value'] != null + ? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + ' ETH' + : '0 ETH'; + String from = data['from'] as String; + String to = data['to'] as String; + + return ''' + Gas: $gas\n + Value: $value\n + From: $from\n + To: $to + '''; + } +} diff --git a/lib/core/wallet_connect/models/auth_request_model.dart b/lib/core/wallet_connect/models/auth_request_model.dart new file mode 100644 index 000000000..f7fd984c8 --- /dev/null +++ b/lib/core/wallet_connect/models/auth_request_model.dart @@ -0,0 +1,16 @@ +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +class AuthRequestModel { + final String iss; + final AuthRequest request; + + AuthRequestModel({ + required this.iss, + required this.request, + }); + + @override + String toString() { + return 'AuthRequestModel(iss: $iss, request: $request)'; + } +} diff --git a/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart b/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart new file mode 100644 index 000000000..49eecac0f --- /dev/null +++ b/lib/core/wallet_connect/models/bottom_sheet_queue_item_model.dart @@ -0,0 +1,20 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; + +class BottomSheetQueueItemModel { + final Widget widget; + final bool isModalDismissible; + final Completer completer; + + BottomSheetQueueItemModel({ + required this.widget, + required this.completer, + this.isModalDismissible = false, + }); + + @override + String toString() { + return 'BottomSheetQueueItemModel(widget: $widget, completer: $completer)'; + } +} diff --git a/lib/core/wallet_connect/models/chain_key_model.dart b/lib/core/wallet_connect/models/chain_key_model.dart new file mode 100644 index 000000000..5cd2764da --- /dev/null +++ b/lib/core/wallet_connect/models/chain_key_model.dart @@ -0,0 +1,16 @@ +class ChainKeyModel { + final List chains; + final String privateKey; + final String publicKey; + + ChainKeyModel({ + required this.chains, + required this.privateKey, + required this.publicKey, + }); + + @override + String toString() { + return 'ChainKeyModel(chains: $chains, privateKey: $privateKey, publicKey: $publicKey)'; + } +} diff --git a/lib/core/wallet_connect/models/connection_model.dart b/lib/core/wallet_connect/models/connection_model.dart new file mode 100644 index 000000000..63cc8260f --- /dev/null +++ b/lib/core/wallet_connect/models/connection_model.dart @@ -0,0 +1,18 @@ +class ConnectionModel { + final String? title; + final String? text; + final List? elements; + final Map? elementActions; + + ConnectionModel({ + this.title, + this.text, + this.elements, + this.elementActions, + }); + + @override + String toString() { + return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)'; + } +} diff --git a/lib/core/wallet_connect/models/session_request_model.dart b/lib/core/wallet_connect/models/session_request_model.dart new file mode 100644 index 000000000..0c7a5d876 --- /dev/null +++ b/lib/core/wallet_connect/models/session_request_model.dart @@ -0,0 +1,14 @@ +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +class SessionRequestModel { + final ProposalData request; + + SessionRequestModel({ + required this.request, + }); + + @override + String toString() { + return 'SessionRequestModel(request: $request)'; + } +} diff --git a/lib/core/wallet_connect/wallet_connect_key_service.dart b/lib/core/wallet_connect/wallet_connect_key_service.dart new file mode 100644 index 000000000..2e61ebb99 --- /dev/null +++ b/lib/core/wallet_connect/wallet_connect_key_service.dart @@ -0,0 +1,72 @@ +import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.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'; + +abstract class WalletConnectKeyService { + /// Returns a list of all the keys. + List getKeys(); + + /// Returns a list of all the chain ids. + List getChains(); + + /// Returns a list of all the keys for a given chain id. + /// If the chain is not found, returns an empty list. + /// - [chain]: The chain to get the keys for. + List getKeysForChain(String chain); + + /// Returns a list of all the accounts in namespace:chainId:address format. + List getAllAccounts(); +} + +class KeyServiceImpl implements WalletConnectKeyService { + KeyServiceImpl(this.wallet) + : _keys = [ + ChainKeyModel( + chains: [ + 'eip155:1', + 'eip155:5', + 'eip155:137', + 'eip155:42161', + 'eip155:80001', + ], + privateKey: ethereum!.getPrivateKey(wallet), + publicKey: ethereum!.getPublicKey(wallet), + ), + + ]; + + late final WalletBase, TransactionInfo> wallet; + + late final List _keys; + + @override + List getChains() { + final List chainIds = []; + for (final ChainKeyModel key in _keys) { + chainIds.addAll(key.chains); + } + return chainIds; + } + + @override + List getKeys() => _keys; + + @override + List getKeysForChain(String chain) { + return _keys.where((e) => e.chains.contains(chain)).toList(); + } + + @override + List getAllAccounts() { + final List accounts = []; + for (final ChainKeyModel key in _keys) { + for (final String chain in key.chains) { + accounts.add('$chain:${key.publicKey}'); + } + } + return accounts; + } +} diff --git a/lib/core/wallet_connect/wc_bottom_sheet_service.dart b/lib/core/wallet_connect/wc_bottom_sheet_service.dart new file mode 100644 index 000000000..3da8660f0 --- /dev/null +++ b/lib/core/wallet_connect/wc_bottom_sheet_service.dart @@ -0,0 +1,43 @@ +import 'dart:async'; +import 'package:cake_wallet/core/wallet_connect/models/bottom_sheet_queue_item_model.dart'; +import 'package:flutter/material.dart'; + +abstract class BottomSheetService { + abstract final ValueNotifier currentSheet; + + Future queueBottomSheet({ + required Widget widget, + bool isModalDismissible = false, + }); + + void resetCurrentSheet(); +} + +class BottomSheetServiceImpl implements BottomSheetService { + + @override + final ValueNotifier currentSheet = ValueNotifier(null); + + @override + Future queueBottomSheet({ + required Widget widget, + bool isModalDismissible = false, + }) async { + // Create the bottom sheet queue item + final completer = Completer(); + final queueItem = BottomSheetQueueItemModel( + widget: widget, + completer: completer, + isModalDismissible: isModalDismissible, + ); + + currentSheet.value = queueItem; + + return await completer.future; + } + + @override + void resetCurrentSheet() { + currentSheet.value = null; + } +} diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart new file mode 100644 index 000000000..0a7716b71 --- /dev/null +++ b/lib/core/wallet_connect/web3wallet_service.dart @@ -0,0 +1,277 @@ +import 'dart:async'; +import 'dart:developer'; +import 'dart:typed_data'; + +import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart'; +import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart'; +import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; +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/src/screens/wallet_connect/widgets/connection_request_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/error_display_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:eth_sig_util/eth_sig_util.dart'; +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +import 'wc_bottom_sheet_service.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; + +part 'web3wallet_service.g.dart'; + +class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService; + +abstract class Web3WalletServiceBase with Store { + final AppStore appStore; + final BottomSheetService _bottomSheetHandler; + final WalletConnectKeyService walletKeyService; + + late Web3Wallet _web3Wallet; + + @observable + bool isInitialized; + + /// The list of requests from the dapp + /// Potential types include, but aren't limited to: + /// [SessionProposalEvent], [AuthRequest] + @observable + ObservableList pairings; + + @observable + ObservableList sessions; + + @observable + ObservableList auth; + + Web3WalletServiceBase(this._bottomSheetHandler, this.walletKeyService, this.appStore) + : pairings = ObservableList(), + sessions = ObservableList(), + auth = ObservableList(), + isInitialized = false; + + @action + void create() { + // Create the web3wallet client + _web3Wallet = Web3Wallet( + core: Core(projectId: secrets.walletConnectProjectId), + metadata: const PairingMetadata( + name: 'Cake Wallet', + description: 'Cake Wallet', + url: 'https://cakewallet.com', + icons: ['https://cakewallet.com/assets/image/cake_logo.png'], + ), + ); + + // Setup our accounts + List chainKeys = walletKeyService.getKeys(); + for (final chainKey in chainKeys) { + for (final chainId in chainKey.chains) { + _web3Wallet.registerAccount( + chainId: chainId, + accountAddress: chainKey.publicKey, + ); + } + } + + // Setup our listeners + log('Created instance of web3wallet'); + _web3Wallet.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid); + _web3Wallet.core.pairing.onPairingCreate.subscribe(_onPairingCreate); + _web3Wallet.core.pairing.onPairingDelete.subscribe(_onPairingDelete); + _web3Wallet.core.pairing.onPairingExpire.subscribe(_onPairingDelete); + _web3Wallet.pairings.onSync.subscribe(_onPairingsSync); + _web3Wallet.onSessionProposal.subscribe(_onSessionProposal); + _web3Wallet.onSessionProposalError.subscribe(_onSessionProposalError); + _web3Wallet.onSessionConnect.subscribe(_onSessionConnect); + _web3Wallet.onAuthRequest.subscribe(_onAuthRequest); + } + + @action + Future init() async { + // Await the initialization of the web3wallet + log('Intializing web3wallet'); + if (!isInitialized) { + try { + await _web3Wallet.init(); + log('Initialized'); + isInitialized = true; + } catch (e) { + log('Experimentallllll: $e'); + isInitialized = false; + } + } + + _refreshPairings(); + + final newSessions = _web3Wallet.sessions.getAll(); + sessions.addAll(newSessions); + + final newAuthRequests = _web3Wallet.completeRequests.getAll(); + auth.addAll(newAuthRequests); + + for (final cId in EVMChainId.values) { + EvmChainServiceImpl( + reference: cId, + appStore: appStore, + wcKeyService: walletKeyService, + bottomSheetService: _bottomSheetHandler, + wallet: _web3Wallet, + ); + } + } + + @action + FutureOr onDispose() { + log('web3wallet dispose'); + _web3Wallet.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid); + _web3Wallet.pairings.onSync.unsubscribe(_onPairingsSync); + _web3Wallet.onSessionProposal.unsubscribe(_onSessionProposal); + _web3Wallet.onSessionProposalError.unsubscribe(_onSessionProposalError); + _web3Wallet.onSessionConnect.unsubscribe(_onSessionConnect); + _web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest); + _web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); + _web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); + } + + Web3Wallet getWeb3Wallet() { + return _web3Wallet; + } + + void _onPairingsSync(StoreSyncEvent? args) { + if (args != null) { + _refreshPairings(); + } + } + + void _onPairingDelete(PairingEvent? event) { + _refreshPairings(); + } + + @action + void _refreshPairings() { + pairings.clear(); + final allPairings = _web3Wallet.pairings.getAll(); + pairings.addAll(allPairings); + } + + Future _onSessionProposalError(SessionProposalErrorEvent? args) async { + log(args.toString()); + } + + void _onSessionProposal(SessionProposalEvent? args) async { + if (args != null) { + final Widget modalWidget = Web3RequestModal( + child: ConnectionRequestWidget( + wallet: _web3Wallet, + sessionProposal: SessionRequestModel(request: args.params), + ), + ); + // show the bottom sheet + final bool? isApproved = await _bottomSheetHandler.queueBottomSheet( + widget: modalWidget, + ) as bool?; + + if (isApproved != null && isApproved) { + _web3Wallet.approveSession( + id: args.id, + namespaces: args.params.generatedNamespaces!, + ); + } else { + _web3Wallet.rejectSession( + id: args.id, + reason: Errors.getSdkError( + Errors.USER_REJECTED, + ), + ); + } + } + } + + @action + void _onPairingInvalid(PairingInvalidEvent? args) { + log('Pairing Invalid Event: $args'); + _bottomSheetHandler.queueBottomSheet( + isModalDismissible: true, + widget: BottomSheetMessageDisplayWidget(message: '${S.current.pairingInvalidEvent}: $args'), + ); + } + + void _onPairingCreate(PairingEvent? args) { + log('Pairing Create Event: $args'); + } + + @action + void _onSessionConnect(SessionConnect? args) { + if (args != null) { + sessions.add(args.session); + } + } + + @action + Future _onAuthRequest(AuthRequest? args) async { + if (args != null) { + List chainKeys = walletKeyService.getKeysForChain('eip155:1'); + // Create the message to be signed + final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}'; + + final Widget modalWidget = Web3RequestModal( + child: ConnectionRequestWidget( + wallet: _web3Wallet, + authRequest: AuthRequestModel(iss: iss, request: args), + ), + ); + final bool? isAuthenticated = await _bottomSheetHandler.queueBottomSheet( + widget: modalWidget, + ) as bool?; + + if (isAuthenticated != null && isAuthenticated) { + final String message = _web3Wallet.formatAuthMessage( + iss: iss, + cacaoPayload: CacaoRequestPayload.fromPayloadParams( + args.payloadParams, + ), + ); + + final String sig = EthSigUtil.signPersonalMessage( + message: Uint8List.fromList(message.codeUnits), + privateKey: chainKeys.first.privateKey, + ); + + await _web3Wallet.respondAuthRequest( + id: args.id, + iss: iss, + signature: CacaoSignature( + t: CacaoSignature.EIP191, + s: sig, + ), + ); + } else { + await _web3Wallet.respondAuthRequest( + id: args.id, + iss: iss, + error: Errors.getSdkError( + Errors.USER_REJECTED_AUTH, + ), + ); + } + } + } + + @action + Future disconnectSession(String topic) async { + final session = sessions.firstWhere((element) => element.pairingTopic == topic); + + await _web3Wallet.core.pairing.disconnect(topic: topic); + await _web3Wallet.disconnectSession( + topic: session.topic, reason: Errors.getSdkError(Errors.USER_DISCONNECTED)); + } + + @action + List getSessionsForPairingInfo(PairingInfo pairing) { + return sessions.where((element) => element.pairingTopic == pairing.topic).toList(); + } +} diff --git a/lib/di.dart b/lib/di.dart index 97dd392c5..a08e84cfb 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -3,10 +3,12 @@ import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; +import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; +import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; +import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/core/yat_service.dart'; import 'package:cake_wallet/entities/background_tasks.dart'; -import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/receive_page_option.dart'; @@ -399,6 +401,10 @@ Future setup({ } if (appStore.wallet != null) { authStore.allowed(); + + if (appStore.wallet!.type == WalletType.ethereum) { + getIt.get().init(); + } return; } @@ -419,6 +425,10 @@ Future setup({ } else { if (appStore.wallet != null) { authStore.allowed(); + + if (appStore.wallet!.type == WalletType.ethereum) { + getIt.get().init(); + } return; } @@ -438,11 +448,28 @@ Future setup({ }, closable: false); }, instanceName: 'login'); + getIt.registerSingleton(BottomSheetServiceImpl()); + + final appStore = getIt.get(); + + getIt.registerLazySingleton(() => KeyServiceImpl(appStore.wallet!)); + + getIt.registerLazySingleton(() { + final Web3WalletService web3WalletService = Web3WalletService( + getIt.get(), + getIt.get(), + appStore, + ); + web3WalletService.create(); + return web3WalletService; + }); + getIt.registerFactory(() => BalancePage( dashboardViewModel: getIt.get(), settingsStore: getIt.get())); getIt.registerFactory(() => DashboardPage( + bottomSheetService: getIt.get(), balancePage: getIt.get(), dashboardViewModel: getIt.get(), addressListViewModel: getIt.get(), @@ -459,6 +486,7 @@ Future setup({ }); getIt.registerFactoryParam, void>( (desktopKey, _) => DesktopDashboardPage( + bottomSheetService: getIt.get(), balancePage: getIt.get(), dashboardViewModel: getIt.get(), addressListViewModel: getIt.get(), @@ -668,7 +696,9 @@ Future setup({ return NodeListViewModel(_nodeSource, appStore); }); - getIt.registerFactory(() => ConnectionSyncPage(getIt.get())); + getIt.registerFactory( + () => ConnectionSyncPage(getIt.get(), getIt.get()), + ); getIt.registerFactory( () => SecurityBackupPage(getIt.get(), getIt.get())); @@ -851,9 +881,8 @@ Future setup({ getIt.registerFactory(() => SupportPage(getIt.get())); - getIt.registerFactory(() => - SupportChatPage( - getIt.get(), secureStorage: getIt.get())); + getIt.registerFactory(() => SupportChatPage(getIt.get(), + secureStorage: getIt.get())); getIt.registerFactory(() => SupportOtherLinksPage(getIt.get())); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index d32dcbca4..65f97be94 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -15,8 +15,7 @@ class PreferencesKey { static const disableSellKey = 'disable_sell'; static const defaultBuyProvider = 'default_buy_provider'; static const currentFiatApiModeKey = 'current_fiat_api_mode'; - static const allowBiometricalAuthenticationKey = - 'allow_biometrical_authentication'; + static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; static const useTOTP2FA = 'use_totp_2fa'; static const failedTotpTokenTrials = 'failed_token_trials'; static const disableExchangeKey = 'disable_exchange'; @@ -54,8 +53,7 @@ class PreferencesKey { static const clearnetDonationLink = 'clearnet_donation_link'; static const onionDonationLink = 'onion_donation_link'; static const lastSeenAppVersion = 'last_seen_app_version'; - static const shouldShowMarketPlaceInDashboard = - 'should_show_marketplace_in_dashboard'; + static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; static const isNewInstall = 'is_new_install'; static const shouldRequireTOTP2FAForAccessingWallet = 'should_require_totp_2fa_for_accessing_wallets'; diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index f2db7741e..d3c11c361 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -33,6 +33,20 @@ class CWEthereum extends Ethereum { @override String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address; + @override + String getPrivateKey(WalletBase wallet) { + final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey; + String stringKey = bytesToHex(privateKeyHolder.privateKey); + return stringKey; + } + + @override + String getPublicKey(WalletBase wallet) { + final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey; + final publicKey = privateKeyInUnitInt.address.hex; + return publicKey; + } + @override TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium; diff --git a/lib/main.dart b/lib/main.dart index 40d7468f2..bd84d7a05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -39,7 +39,6 @@ import 'package:cake_wallet/src/screens/root/root.dart'; import 'package:uni_links/uni_links.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/cake_hive.dart'; final navigatorKey = GlobalKey(); @@ -155,7 +154,7 @@ Future initializeAppConfigs() async { secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, initialMigrationVersion: 21); - } +} Future initialSetup( {required SharedPreferences sharedPreferences, @@ -308,26 +307,26 @@ class _Home extends StatefulWidget { } class _HomeState extends State<_Home> { - @override + @override void didChangeDependencies() { - if(!ResponsiveLayoutUtil.instance.isMobile){ - _setOrientation(context); + if (!ResponsiveLayoutUtil.instance.isMobile) { + _setOrientation(context); } super.didChangeDependencies(); } - - void _setOrientation(BuildContext context){ + void _setOrientation(BuildContext context) { final orientation = MediaQuery.of(context).orientation; final width = MediaQuery.of(context).size.width; final height = MediaQuery.of(context).size.height; if (orientation == Orientation.portrait && width < height) { - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + SystemChrome.setPreferredOrientations( + [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); } else if (orientation == Orientation.landscape && width > height) { - SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); + SystemChrome.setPreferredOrientations( + [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); } - - } + } @override Widget build(BuildContext context) { diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 68f5ca9aa..a0cf6cd28 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -1,9 +1,12 @@ import 'dart:async'; +import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; +import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/main_actions.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/utils/device_info.dart'; @@ -35,12 +38,14 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; class DashboardPage extends StatelessWidget { DashboardPage({ + required this.bottomSheetService, required this.balancePage, required this.dashboardViewModel, required this.addressListViewModel, }); final BalancePage balancePage; + final BottomSheetService bottomSheetService; final DashboardViewModel dashboardViewModel; final WalletAddressListViewModel addressListViewModel; @@ -55,12 +60,14 @@ class DashboardPage extends StatelessWidget { } else { return _DashboardPageView( balancePage: balancePage, + bottomSheetService: bottomSheetService, dashboardViewModel: dashboardViewModel, addressListViewModel: addressListViewModel, ); } } else if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) { return _DashboardPageView( + bottomSheetService: bottomSheetService, balancePage: balancePage, dashboardViewModel: dashboardViewModel, addressListViewModel: addressListViewModel, @@ -76,6 +83,7 @@ class DashboardPage extends StatelessWidget { class _DashboardPageView extends BasePage { _DashboardPageView({ + required this.bottomSheetService, required this.balancePage, required this.dashboardViewModel, required this.addressListViewModel, @@ -126,6 +134,7 @@ class _DashboardPageView extends BasePage { } final DashboardViewModel dashboardViewModel; + final BottomSheetService bottomSheetService; final WalletAddressListViewModel addressListViewModel; int get initialPage => dashboardViewModel.shouldShowMarketPlaceInDashboard ? 1 : 0; @@ -158,102 +167,106 @@ class _DashboardPageView extends BasePage { return SafeArea( minimum: EdgeInsets.only(bottom: 24), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: Observer( - builder: (context) { - return PageView.builder( - controller: controller, - itemCount: pages.length, - itemBuilder: (context, index) => pages[index], - ); - }, - ), - ), - Padding( - padding: EdgeInsets.only(bottom: 24, top: 10), - child: Observer( - builder: (context) { - return ExcludeSemantics( - child: SmoothPageIndicator( + child: BottomSheetListener( + bottomSheetService: bottomSheetService, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Observer( + builder: (context) { + return PageView.builder( controller: controller, - count: pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).indicatorColor, - activeDotColor: Theme.of(context) - .extension()! - .indicatorDotTheme - .activeIndicatorColor, - ), - ), - ); - }, + itemCount: pages.length, + itemBuilder: (context, index) => pages[index], + ); + }, + ), ), - ), - Observer( - builder: (_) { - return ClipRect( - child: Container( - margin: const EdgeInsets.only(left: 16, right: 16), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.0), - border: Border.all( - color: Theme.of(context).extension()!.cardBorderColor, - width: 1, + Padding( + padding: EdgeInsets.only(bottom: 24, top: 10), + child: Observer( + builder: (context) { + return ExcludeSemantics( + child: SmoothPageIndicator( + controller: controller, + count: pages.length, + effect: ColorTransitionEffect( + spacing: 6.0, + radius: 6.0, + dotWidth: 6.0, + dotHeight: 6.0, + dotColor: Theme.of(context).indicatorColor, + activeDotColor: Theme.of(context) + .extension()! + .indicatorDotTheme + .activeIndicatorColor, ), - color: - Theme.of(context).extension()!.syncedBackgroundColor, ), + ); + }, + ), + ), + Observer( + builder: (_) { + return ClipRect( + child: Container( + margin: const EdgeInsets.only(left: 16, right: 16), child: Container( - padding: EdgeInsets.only(left: 32, right: 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: MainActions.all - .where((element) => element.canShow?.call(dashboardViewModel) ?? true) - .map( - (action) => Semantics( - button: true, - enabled: (action.isEnabled?.call(dashboardViewModel) ?? true), - child: ActionButton( - image: Image.asset( - action.image, - height: 24, - width: 24, - color: action.isEnabled?.call(dashboardViewModel) ?? true - ? Theme.of(context) - .extension()! - .mainActionsIconColor + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.0), + border: Border.all( + color: Theme.of(context).extension()!.cardBorderColor, + width: 1, + ), + color: Theme.of(context) + .extension()! + .syncedBackgroundColor, + ), + child: Container( + padding: EdgeInsets.only(left: 32, right: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: MainActions.all + .where((element) => element.canShow?.call(dashboardViewModel) ?? true) + .map( + (action) => Semantics( + button: true, + enabled: (action.isEnabled?.call(dashboardViewModel) ?? true), + child: ActionButton( + image: Image.asset( + action.image, + height: 24, + width: 24, + color: action.isEnabled?.call(dashboardViewModel) ?? true + ? Theme.of(context) + .extension()! + .mainActionsIconColor + : Theme.of(context) + .extension()! + .labelTextColor, + ), + title: action.name(context), + onClick: () async => + await action.onTap(context, dashboardViewModel), + textColor: action.isEnabled?.call(dashboardViewModel) ?? true + ? null : Theme.of(context) .extension()! .labelTextColor, ), - title: action.name(context), - onClick: () async => - await action.onTap(context, dashboardViewModel), - textColor: action.isEnabled?.call(dashboardViewModel) ?? true - ? null - : Theme.of(context) - .extension()! - .labelTextColor, ), - ), - ) - .toList(), + ) + .toList(), + ), ), ), ), - ), - ); - }, - ), - ], + ); + }, + ), + ], + ), ), ); } diff --git a/lib/src/screens/dashboard/desktop_dashboard_page.dart b/lib/src/screens/dashboard/desktop_dashboard_page.dart index 2332f4db6..41d41dd4f 100644 --- a/lib/src/screens/dashboard/desktop_dashboard_page.dart +++ b/lib/src/screens/dashboard/desktop_dashboard_page.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -19,12 +21,14 @@ import 'package:shared_preferences/shared_preferences.dart'; class DesktopDashboardPage extends StatelessWidget { DesktopDashboardPage({ required this.balancePage, + required this.bottomSheetService, required this.dashboardViewModel, required this.addressListViewModel, required this.desktopKey, }); final BalancePage balancePage; + final BottomSheetService bottomSheetService; final DashboardViewModel dashboardViewModel; final WalletAddressListViewModel addressListViewModel; final GlobalKey desktopKey; @@ -36,31 +40,34 @@ class DesktopDashboardPage extends StatelessWidget { Widget build(BuildContext context) { _setEffects(context); - return Container( - color: Theme.of(context).colorScheme.background, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 400, - child: balancePage, - ), - Flexible( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 500), - child: Navigator( - key: desktopKey, - initialRoute: Routes.desktop_actions, - onGenerateRoute: (settings) => Router.createRoute(settings), - onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { - return [ - navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! - ]; - }, + return BottomSheetListener( + bottomSheetService: bottomSheetService, + child: Container( + color: Theme.of(context).colorScheme.background, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 400, + child: balancePage, + ), + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: Navigator( + key: desktopKey, + initialRoute: Routes.desktop_actions, + onGenerateRoute: (settings) => Router.createRoute(settings), + onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) { + return [ + navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))! + ]; + }, + ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index af775705b..7a903bb69 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; +import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:flutter/material.dart'; @@ -97,8 +98,7 @@ class RootState extends State with WidgetsBindingObserver { return; } - if (!_isInactive && - widget.authenticationStore.state == AuthenticationState.allowed) { + if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) { setState(() => _setInactive(true)); } @@ -125,16 +125,15 @@ class RootState extends State with WidgetsBindingObserver { return; } else { final useTotp = widget.appStore.settingsStore.useTOTP2FA; - final shouldUseTotp2FAToAccessWallets = widget.appStore - .settingsStore.shouldRequireTOTP2FAForAccessingWallet; + final shouldUseTotp2FAToAccessWallets = + widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; if (useTotp && shouldUseTotp2FAToAccessWallets) { _reset(); auth.close( route: Routes.totpAuthCodePage, arguments: TotpAuthArgumentsModel( onTotpAuthenticationFinished: - (bool isAuthenticatedSuccessfully, - TotpAuthCodePageState totpAuth) { + (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) { if (!isAuthenticatedSuccessfully) { return; } @@ -169,7 +168,10 @@ class RootState extends State with WidgetsBindingObserver { launchUri = null; } - return WillPopScope(onWillPop: () async => false, child: widget.child); + return WillPopScope( + onWillPop: () async => false, + child: widget.child, + ); } void _reset() { diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index c59e71be4..d5a26a0fe 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -1,12 +1,15 @@ +import 'package:cake_wallet/core/wallet_connect/web3wallet_service.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'; +import 'package:cake_wallet/src/screens/settings/widgets/wallet_connect_button.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; import 'package:cake_wallet/utils/device_info.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:flutter/cupertino.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -15,11 +18,12 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class ConnectionSyncPage extends BasePage { - ConnectionSyncPage(this.dashboardViewModel); + ConnectionSyncPage(this.dashboardViewModel, this.web3walletService); @override String get title => S.current.connection_sync; + final Web3WalletService web3walletService; final DashboardViewModel dashboardViewModel; @override @@ -66,6 +70,20 @@ class ConnectionSyncPage extends BasePage { handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes), ), const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[ + WalletConnectTile( + onTap: () async { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return WalletConnectConnectionsView(web3walletService: web3walletService); + }, + ), + ); + }, + ), + const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + ] ], ), ); diff --git a/lib/src/screens/settings/widgets/wallet_connect_button.dart b/lib/src/screens/settings/widgets/wallet_connect_button.dart new file mode 100644 index 000000000..d02462619 --- /dev/null +++ b/lib/src/screens/settings/widgets/wallet_connect_button.dart @@ -0,0 +1,46 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; +import 'package:flutter/material.dart'; + +class WalletConnectTile extends StatelessWidget { + const WalletConnectTile({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: EdgeInsets.all(24), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/walletconnect_logo.png', + height: 24, + width: 24, + ), + SizedBox(width: 16), + Expanded( + child: Text( + S.current.walletConnect, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + ), + Image.asset( + 'assets/images/select_arrow.png', + color: Theme.of(context).extension()!.detailsTitlesColor, + ) + ], + ), + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart new file mode 100644 index 000000000..936df93d3 --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/namespace_model_builder.dart @@ -0,0 +1,71 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +import '../../../../core/wallet_connect/models/connection_model.dart'; + +class ConnectionWidgetBuilder { + static List buildFromRequiredNamespaces( + Map requiredNamespaces, + ) { + final List views = []; + for (final key in requiredNamespaces.keys) { + RequiredNamespace ns = requiredNamespaces[key]!; + final List models = []; + // If the chains property is present, add the chain data to the models + if (ns.chains != null) { + models.add(ConnectionModel(title: S.current.chains, elements: ns.chains!)); + } + models.add(ConnectionModel(title: S.current.methods, elements: ns.methods)); + models.add(ConnectionModel(title: S.current.events, elements: ns.events)); + + views.add(ConnectionWidget(title: key, info: models)); + } + + return views; + } + + static List buildFromNamespaces( + String topic, + Map namespaces, + Web3Wallet web3wallet, + ) { + final List views = []; + for (final key in namespaces.keys) { + final Namespace ns = namespaces[key]!; + final List models = []; + // If the chains property is present, add the chain data to the models + models.add( + ConnectionModel( + title: S.current.chains, + elements: ns.accounts, + ), + ); + models.add(ConnectionModel( + title: S.current.methods, + elements: ns.methods, + )); + + Map actions = {}; + for (final String event in ns.events) { + actions[event] = () async { + final String chainId = NamespaceUtils.isValidChainId(key) + ? key + : NamespaceUtils.getChainFromAccount(ns.accounts.first); + await web3wallet.emitSessionEvent( + topic: topic, + chainId: chainId, + event: SessionEventParams(name: event, data: '${S.current.event}: $event'), + ); + }; + } + models.add( + ConnectionModel(title: S.current.events, elements: ns.events, elementActions: actions), + ); + + views.add(ConnectionWidget(title: key, info: models)); + } + + return views; + } +} diff --git a/lib/src/screens/wallet_connect/utils/string_parsing.dart b/lib/src/screens/wallet_connect/utils/string_parsing.dart new file mode 100644 index 000000000..b9fdca7b2 --- /dev/null +++ b/lib/src/screens/wallet_connect/utils/string_parsing.dart @@ -0,0 +1,16 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; + +extension StringParsing on String { + String get utf8Message { + if (startsWith('0x')) { + final List decoded = hex.decode( + substring(2), + ); + return utf8.decode(decoded); + } + + return this; + } +} diff --git a/lib/src/screens/wallet_connect/wc_connections_listing_view.dart b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart new file mode 100644 index 000000000..ead50db07 --- /dev/null +++ b/lib/src/screens/wallet_connect/wc_connections_listing_view.dart @@ -0,0 +1,142 @@ +import 'dart:developer'; +import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; +import 'package:cake_wallet/generated/i18n.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/themes/extensions/cake_text_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; +import 'package:cake_wallet/entities/qr_scanner.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; + +import 'widgets/pairing_item_widget.dart'; +import 'wc_pairing_detail_page.dart'; + +class WalletConnectConnectionsView extends StatelessWidget { + final Web3WalletService web3walletService; + + WalletConnectConnectionsView({required this.web3walletService, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return WCPairingsWidget(web3walletService: web3walletService); + } +} + +class WCPairingsWidget extends BasePage { + WCPairingsWidget({required this.web3walletService, Key? key}) + : web3wallet = web3walletService.getWeb3Wallet(); + + final Web3Wallet web3wallet; + final Web3WalletService web3walletService; + + @override + String get title => S.current.walletConnect; + + Future _onScanQrCode(BuildContext context, Web3Wallet web3Wallet) async { + final String? uri = await presentQRScanner(); + + if (uri == null) return _invalidUriToast(context, S.current.nullURIError); + + try { + log('_onFoundUri: $uri'); + final Uri uriData = Uri.parse(uri); + await web3Wallet.pair(uri: uriData); + } on WalletConnectError catch (e) { + await _invalidUriToast(context, e.message); + } catch (e) { + await _invalidUriToast(context, e.toString()); + } + } + + Future _invalidUriToast(BuildContext context, String message) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: message, + buttonText: S.of(context).ok, + buttonAction: Navigator.of(context).pop, + alertBarrierDismissible: false, + ); + }, + ); + } + + @override + Widget body(BuildContext context) { + return Observer( + builder: (context) { + return Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + SizedBox(height: 24), + Text( + S.current.connectWalletPrompt, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + SizedBox(height: 16), + PrimaryButton( + text: S.current.newConnection, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + onPressed: () => _onScanQrCode(context, web3wallet), + ), + ], + ), + ), + SizedBox(height: 48), + Expanded( + child: Visibility( + visible: web3walletService.pairings.isEmpty, + child: Center( + child: Text( + S.current.activeConnectionsPrompt, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + ), + replacement: ListView.builder( + itemCount: web3walletService.pairings.length, + itemBuilder: (BuildContext context, int index) { + final pairing = web3walletService.pairings[index]; + return PairingItemWidget( + key: ValueKey(pairing.topic), + pairing: pairing, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => WalletConnectPairingDetailsPage( + pairing: pairing, + web3walletService: web3walletService, + ), + ), + ); + }, + ); + }, + ), + ), + ), + SizedBox(height: 48), + ], + ); + }, + ); + } +} diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart new file mode 100644 index 000000000..f99eb9cdb --- /dev/null +++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart @@ -0,0 +1,186 @@ +import 'dart:developer'; + +import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:flutter/material.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +import 'utils/namespace_model_builder.dart'; + +class WalletConnectPairingDetailsPage extends StatefulWidget { + final PairingInfo pairing; + final Web3WalletService web3walletService; + + const WalletConnectPairingDetailsPage({ + required this.pairing, + required this.web3walletService, + super.key, + }); + + @override + WalletConnectPairingDetailsPageState createState() => WalletConnectPairingDetailsPageState(); +} + +class WalletConnectPairingDetailsPageState extends State { + List sessionWidgets = []; + late String expiryDate; + @override + void initState() { + super.initState(); + initDateTime(); + initSessions(); + } + + void initDateTime() { + DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000); + int year = dateTime.year; + int month = dateTime.month; + int day = dateTime.day; + + expiryDate = '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; + } + + void initSessions() { + List sessions = widget.web3walletService.getSessionsForPairingInfo(widget.pairing); + + for (final SessionData session in sessions) { + List namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces( + session.topic, + session.namespaces, + widget.web3walletService.getWeb3Wallet(), + ); + // Loop through and add the namespace widgets, but put 20 pixels between each one + for (int i = 0; i < namespaceWidget.length; i++) { + sessionWidgets.add(namespaceWidget[i]); + if (i != namespaceWidget.length - 1) { + sessionWidgets.add(const SizedBox(height: 20.0)); + } + } + } + } + + @override + Widget build(BuildContext context) { + return WCCDetailsWidget( + widget.pairing, + expiryDate, + sessionWidgets, + widget.web3walletService, + ); + } +} + +class WCCDetailsWidget extends BasePage { + WCCDetailsWidget( + this.pairing, + this.expiryDate, + this.sessionWidgets, + this.web3walletService, + ); + + final PairingInfo pairing; + final String expiryDate; + final List sessionWidgets; + final Web3WalletService web3walletService; + + @override + Widget body(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: CircleAvatar( + backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty + ? NetworkImage(pairing.peerMetadata!.icons[0]) + : const AssetImage('assets/images/default_icon.png')) + as ImageProvider, + ), + ), + const SizedBox(height: 20.0), + Text( + pairing.peerMetadata!.name, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor, + ), + ), + const SizedBox(height: 16.0), + Text( + pairing.peerMetadata!.url, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + const SizedBox(height: 8.0), + Text( + '${S.current.expiresOn}: $expiryDate', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), + ), + const SizedBox(height: 20.0), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: sessionWidgets, + ), + const SizedBox(height: 20.0), + PrimaryButton( + onPressed: () => + _onDeleteButtonPressed(context, pairing.peerMetadata!.name, web3walletService), + text: S.current.delete, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + ), + ], + ), + ), + ), + ); + } + + Future _onDeleteButtonPressed( + BuildContext context, String dAppName, Web3WalletService web3walletService) async { + bool confirmed = false; + + await showPopUp( + context: context, + builder: (BuildContext dialogContext) { + return AlertWithTwoActions( + alertTitle: S.of(context).delete, + alertContent: '${S.current.deleteConnectionConfirmationPrompt} $dAppName?', + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).delete, + actionLeftButton: () => Navigator.of(dialogContext).pop(), + actionRightButton: () { + confirmed = true; + Navigator.of(dialogContext).pop(); + }, + ); + }, + ); + if (confirmed) { + try { + await web3walletService.disconnectSession(pairing.topic); + + Navigator.of(context).pop(); + } catch (e) { + log(e.toString()); + } + } + } +} diff --git a/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart new file mode 100644 index 000000000..77c30417a --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/connection_item_widget.dart @@ -0,0 +1,102 @@ +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:flutter/material.dart'; +import '../../../../core/wallet_connect/models/connection_model.dart'; + +class ConnectionItemWidget extends StatelessWidget { + const ConnectionItemWidget({required this.model, Key? key}) : super(key: key); + + final ConnectionModel model; + + @override + Widget build(BuildContext context) { + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(8), + margin: const EdgeInsetsDirectional.only(top: 8), + child: Visibility( + visible: model.elements != null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + model.title ?? '', + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + if (model.elements != null) + Wrap( + spacing: 4, + runSpacing: 4, + direction: Axis.horizontal, + children: model.elements! + .map((e) => _ModelElementWidget(model: model, modelElement: e)) + .toList(), + ), + ], + ), + replacement: _NoModelElementWidget(model: model), + ), + ); + } +} + +class _NoModelElementWidget extends StatelessWidget { + const _NoModelElementWidget({required this.model}); + + final ConnectionModel model; + + @override + Widget build(BuildContext context) { + return Text( + model.text!, + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ); + } +} + +class _ModelElementWidget extends StatelessWidget { + const _ModelElementWidget({ + required this.model, + required this.modelElement, + }); + + final ConnectionModel model; + final String modelElement; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: model.elementActions != null ? model.elementActions![modelElement] : null, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.all(8), + child: Text( + modelElement, + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + maxLines: 10, + overflow: TextOverflow.ellipsis, + ), + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart new file mode 100644 index 000000000..c73c4bfa8 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart @@ -0,0 +1,166 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; +import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; + +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; + +import '../../../../core/wallet_connect/models/auth_request_model.dart'; +import '../../../../core/wallet_connect/models/connection_model.dart'; +import '../../../../core/wallet_connect/models/session_request_model.dart'; +import '../utils/namespace_model_builder.dart'; +import 'connection_widget.dart'; + +class ConnectionRequestWidget extends StatefulWidget { + const ConnectionRequestWidget({ + required this.wallet, + this.authRequest, + this.sessionProposal, + Key? key, + }) : super(key: key); + + final Web3Wallet wallet; + final AuthRequestModel? authRequest; + final SessionRequestModel? sessionProposal; + + @override + State createState() => _ConnectionRequestWidgetState(); +} + +class _ConnectionRequestWidgetState extends State { + ConnectionMetadata? metadata; + + @override + void initState() { + super.initState(); + // Get the connection metadata + metadata = widget.authRequest?.request.requester ?? widget.sessionProposal?.request.proposer; + } + + @override + Widget build(BuildContext context) { + if (metadata == null) { + return Text( + S.current.error, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + ); + } + + return _ConnectionMetadataDisplayWidget( + metadata: metadata, + authRequest: widget.authRequest, + sessionProposal: widget.sessionProposal, + wallet: widget.wallet, + ); + } +} + +class _ConnectionMetadataDisplayWidget extends StatelessWidget { + const _ConnectionMetadataDisplayWidget({ + required this.metadata, + required this.wallet, + this.authRequest, + required this.sessionProposal, + }); + + final ConnectionMetadata? metadata; + final Web3Wallet wallet; + final AuthRequestModel? authRequest; + final SessionRequestModel? sessionProposal; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Color.fromARGB(255, 18, 18, 19), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + metadata!.metadata.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + textAlign: TextAlign.center, + ), + Text( + S.current.wouoldLikeToConnect, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + metadata!.metadata.url, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Visibility( + visible: authRequest != null, + child: _AuthRequestWidget(wallet: wallet, authRequest: authRequest), + + //If authRequest is null, sessionProposal is not null. + replacement: _SessionProposalWidget(sessionProposal: sessionProposal!), + ), + ], + ), + ); + } +} + +class _AuthRequestWidget extends StatelessWidget { + const _AuthRequestWidget({required this.wallet, this.authRequest}); + + final Web3Wallet wallet; + final AuthRequestModel? authRequest; + + @override + Widget build(BuildContext context) { + final model = ConnectionModel( + text: wallet.formatAuthMessage( + iss: 'did:pkh:eip155:1:${authRequest!.iss}', + cacaoPayload: CacaoRequestPayload.fromPayloadParams( + authRequest!.request.payloadParams, + ), + ), + ); + return ConnectionWidget( + title: S.current.message, + info: [model], + ); + } +} + +class _SessionProposalWidget extends StatelessWidget { + const _SessionProposalWidget({required this.sessionProposal}); + + final SessionRequestModel sessionProposal; + + @override + Widget build(BuildContext context) { + // Create the connection models using the required and optional namespaces provided by the proposal data + // The key is the title and the list of values is the data + final List views = ConnectionWidgetBuilder.buildFromRequiredNamespaces( + sessionProposal.request.requiredNamespaces, + ); + + return Column(children: views); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/connection_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_widget.dart new file mode 100644 index 000000000..921d8ea5c --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/connection_widget.dart @@ -0,0 +1,45 @@ +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/wallet_connect/models/connection_model.dart'; +import 'connection_item_widget.dart'; + +class ConnectionWidget extends StatelessWidget { + const ConnectionWidget({required this.title, required this.info, super.key}); + + final String title; + final List info; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).primaryColorLight, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + ), + ), + ), + const SizedBox(height: 8), + ...info.map((e) => ConnectionItemWidget(model: e)), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/error_display_widget.dart b/lib/src/screens/wallet_connect/widgets/error_display_widget.dart new file mode 100644 index 000000000..0fbbda5c7 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/error_display_widget.dart @@ -0,0 +1,36 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; + +class BottomSheetMessageDisplayWidget extends StatelessWidget { + final String message; + final bool isError; + + const BottomSheetMessageDisplayWidget({super.key, required this.message, this.isError = true}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isError ? S.current.error : S.current.successful, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.white, + ), + ), + SizedBox(height: 8), + Text( + message, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Colors.white, + ), + ), + ], + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart b/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart new file mode 100644 index 000000000..30b6af7e0 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart @@ -0,0 +1,62 @@ +import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; +import 'package:flutter/material.dart'; + +import '../../../../../core/wallet_connect/models/bottom_sheet_queue_item_model.dart'; + +class BottomSheetListener extends StatefulWidget { + final BottomSheetService bottomSheetService; + final Widget child; + + const BottomSheetListener({ + required this.child, + required this.bottomSheetService, + super.key, + }); + + @override + BottomSheetListenerState createState() => BottomSheetListenerState(); +} + +class BottomSheetListenerState extends State { + + @override + void initState() { + super.initState(); + widget.bottomSheetService.currentSheet.addListener(_showBottomSheet); + } + + @override + void dispose() { + widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet); + super.dispose(); + } + + Future _showBottomSheet() async { + if (widget.bottomSheetService.currentSheet.value != null) { + BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!; + final value = await showModalBottomSheet( + context: context, + isDismissible: item.isModalDismissible, + backgroundColor: Color.fromARGB(0, 0, 0, 0), + isScrollControlled: true, + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9), + builder: (context) { + return Container( + decoration: const BoxDecoration( + color: Color.fromARGB(255, 18, 18, 19), + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.all(16), + child: item.widget, + ); + }, + ); + item.completer.complete(value); + widget.bottomSheetService.resetCurrentSheet(); + } + } + + @override + Widget build(BuildContext context) => widget.child; +} diff --git a/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart b/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart new file mode 100644 index 000000000..f16dcc0f8 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart @@ -0,0 +1,48 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:flutter/material.dart'; + +class Web3RequestModal extends StatelessWidget { + const Web3RequestModal({required this.child, this.onAccept, this.onReject, super.key}); + + final Widget child; + final VoidCallback? onAccept; + final VoidCallback? onReject; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + child, + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + + Expanded( + child: PrimaryButton( + onPressed: onReject ?? () => Navigator.of(context).pop(false), + text: S.current.reject, + color: Theme.of(context).colorScheme.error, + textColor: Theme.of(context).colorScheme.onError, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + onPressed: onAccept ?? () => Navigator.of(context).pop(true), + text: S.current.approve, + color: Theme.of(context).primaryColor, + textColor: Theme.of(context).extension()!.titleColor, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart b/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart new file mode 100644 index 000000000..063de8ec3 --- /dev/null +++ b/lib/src/screens/wallet_connect/widgets/pairing_item_widget.dart @@ -0,0 +1,82 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:walletconnect_flutter_v2/apis/core/pairing/utils/pairing_models.dart'; + +class PairingItemWidget extends StatelessWidget { + const PairingItemWidget({required this.pairing, required this.onTap, super.key}); + + final PairingInfo pairing; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + PairingMetadata? metadata = pairing.peerMetadata; + if (metadata == null) { + return SizedBox.shrink(); + } + + DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(pairing.expiry * 1000); + int year = dateTime.year; + int month = dateTime.month; + int day = dateTime.day; + + String expiryDate = + '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; + + return ListTile( + leading: CircleAvatar( + backgroundImage: (metadata.icons.isNotEmpty + ? NetworkImage(metadata.icons[0]) + : const AssetImage( + 'assets/images/default_icon.png', + )) as ImageProvider, + ), + title: Text( + metadata.name, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.titleColor, + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + metadata.url, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + ), + Text( + '${S.current.expiresOn}: $expiryDate', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w700, + color: Theme.of(context).extension()!.secondaryTextColor, + ), + ), + ], + ), + trailing: Container( + height: 40, + width: 44, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).extension()!.iconsBackgroundColor, + ), + child: Icon( + Icons.edit, + size: 14, + color: Theme.of(context).extension()!.iconsColor, + ), + ), + onTap: onTap, + ); + } +} diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 54607815d..6bd3686b5 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -595,8 +595,7 @@ abstract class SettingsStoreBase with Store { SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; final pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; - final useEtherscan = - sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; + final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; // If no value if (pinLength == null || pinLength == 0) { diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 777db8f8d..c8a3a907c 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -47,70 +47,70 @@ abstract class DashboardViewModelBase with Store { required this.yatStore, required this.ordersStore, required this.anonpayTransactionsStore}) - : isOutdatedElectrumWallet = false, - hasSellAction = false, - hasBuyAction = false, - hasExchangeAction = false, - isShowFirstYatIntroduction = false, - isShowSecondYatIntroduction = false, - isShowThirdYatIntroduction = false, - filterItems = { - S.current.transactions: [ - FilterItem( - value: () => transactionFilterStore.displayAll, - caption: S.current.all_transactions, - onChanged: transactionFilterStore.toggleAll), - FilterItem( - value: () => transactionFilterStore.displayIncoming, - caption: S.current.incoming, - onChanged:transactionFilterStore.toggleIncoming), - FilterItem( - value: () => transactionFilterStore.displayOutgoing, - caption: S.current.outgoing, - onChanged: transactionFilterStore.toggleOutgoing), - // FilterItem( - // value: () => false, - // caption: S.current.transactions_by_date, - // onChanged: null), - ], - S.current.trades: [ - FilterItem( - value: () => tradeFilterStore.displayAllTrades, - caption: S.current.all_trades, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.all)), - FilterItem( - value: () => tradeFilterStore.displayChangeNow, - caption: ExchangeProviderDescription.changeNow.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.changeNow)), - FilterItem( - value: () => tradeFilterStore.displaySideShift, - caption: ExchangeProviderDescription.sideShift.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.sideShift)), - FilterItem( - value: () => tradeFilterStore.displaySimpleSwap, - caption: ExchangeProviderDescription.simpleSwap.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)), - FilterItem( - value: () => tradeFilterStore.displayTrocador, - caption: ExchangeProviderDescription.trocador.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.trocador)), - FilterItem( - value: () => tradeFilterStore.displayExolix, - caption: ExchangeProviderDescription.exolix.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.exolix)), - ] - }, - subname = '', - name = appStore.wallet!.name, - type = appStore.wallet!.type, - transactions = ObservableList(), - wallet = appStore.wallet! { + : isOutdatedElectrumWallet = false, + hasSellAction = false, + hasBuyAction = false, + hasExchangeAction = false, + isShowFirstYatIntroduction = false, + isShowSecondYatIntroduction = false, + isShowThirdYatIntroduction = false, + filterItems = { + S.current.transactions: [ + FilterItem( + value: () => transactionFilterStore.displayAll, + caption: S.current.all_transactions, + onChanged: transactionFilterStore.toggleAll), + FilterItem( + value: () => transactionFilterStore.displayIncoming, + caption: S.current.incoming, + onChanged: transactionFilterStore.toggleIncoming), + FilterItem( + value: () => transactionFilterStore.displayOutgoing, + caption: S.current.outgoing, + onChanged: transactionFilterStore.toggleOutgoing), + // FilterItem( + // value: () => false, + // caption: S.current.transactions_by_date, + // onChanged: null), + ], + S.current.trades: [ + FilterItem( + value: () => tradeFilterStore.displayAllTrades, + caption: S.current.all_trades, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)), + FilterItem( + value: () => tradeFilterStore.displayChangeNow, + caption: ExchangeProviderDescription.changeNow.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)), + FilterItem( + value: () => tradeFilterStore.displaySideShift, + caption: ExchangeProviderDescription.sideShift.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)), + FilterItem( + value: () => tradeFilterStore.displaySimpleSwap, + caption: ExchangeProviderDescription.simpleSwap.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)), + FilterItem( + value: () => tradeFilterStore.displayTrocador, + caption: ExchangeProviderDescription.trocador.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)), + FilterItem( + value: () => tradeFilterStore.displayExolix, + caption: ExchangeProviderDescription.exolix.title, + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)), + ] + }, + subname = '', + name = appStore.wallet!.name, + type = appStore.wallet!.type, + transactions = ObservableList(), + wallet = appStore.wallet! { name = wallet.name; type = wallet.type; isOutdatedElectrumWallet = @@ -125,15 +125,17 @@ abstract class DashboardViewModelBase with Store { if (_wallet.type == WalletType.monero) { subname = monero!.getCurrentAccount(_wallet).label; - _onMoneroAccountChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet) - .account, (Account account) => _onMoneroAccountChange(_wallet)); + _onMoneroAccountChangeReaction = reaction( + (_) => monero!.getMoneroWalletDetails(wallet).account, + (Account account) => _onMoneroAccountChange(_wallet)); - _onMoneroBalanceChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet).balance, + _onMoneroBalanceChangeReaction = reaction( + (_) => monero!.getMoneroWalletDetails(wallet).balance, (MoneroBalance balance) => _onMoneroTransactionsUpdate(_wallet)); - final _accountTransactions = _wallet - .transactionHistory.transactions.values - .where((tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) + final _accountTransactions = _wallet.transactionHistory.transactions.values + .where((tx) => + monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) .toList(); transactions = ObservableList.of(_accountTransactions.map((transaction) => @@ -142,34 +144,33 @@ abstract class DashboardViewModelBase with Store { balanceViewModel: balanceViewModel, settingsStore: appStore.settingsStore))); } else { - transactions = ObservableList.of(wallet - .transactionHistory.transactions.values - .map((transaction) => TransactionListItem( + transactions = ObservableList.of(wallet.transactionHistory.transactions.values.map( + (transaction) => TransactionListItem( transaction: transaction, balanceViewModel: balanceViewModel, settingsStore: appStore.settingsStore))); } reaction((_) => appStore.wallet, _onWalletChange); - + connectMapToListWithTransform( appStore.wallet!.transactionHistory.transactions, transactions, (TransactionInfo? transaction) => TransactionListItem( transaction: transaction!, balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore), - filter: (TransactionInfo? transaction) { - if (transaction == null) { - return false; - } + settingsStore: appStore.settingsStore), filter: (TransactionInfo? transaction) { + if (transaction == null) { + return false; + } - final wallet = _wallet; - if (wallet.type == WalletType.monero) { - return monero!.getTransactionInfoAccountId(transaction) == monero!.getCurrentAccount(wallet).id; - } + final wallet = _wallet; + if (wallet.type == WalletType.monero) { + return monero!.getTransactionInfoAccountId(transaction) == + monero!.getCurrentAccount(wallet).id; + } - return true; + return true; }); } @@ -216,24 +217,21 @@ abstract class DashboardViewModelBase with Store { } @computed - BalanceDisplayMode get balanceDisplayMode => - appStore.settingsStore.balanceDisplayMode; - + BalanceDisplayMode get balanceDisplayMode => appStore.settingsStore.balanceDisplayMode; + @computed bool get shouldShowMarketPlaceInDashboard { return appStore.settingsStore.shouldShowMarketPlaceInDashboard; } @computed - List get trades => tradesStore.trades - .where((trade) => trade.trade.walletId == wallet.id) - .toList(); + List get trades => + tradesStore.trades.where((trade) => trade.trade.walletId == wallet.id).toList(); @computed - List get orders => ordersStore.orders - .where((item) => item.order.walletId == wallet.id) - .toList(); - + List get orders => + ordersStore.orders.where((item) => item.order.walletId == wallet.id).toList(); + @computed List get anonpayTransactons => anonpayTransactionsStore.transactions .where((item) => item.transaction.walletId == wallet.id) @@ -250,7 +248,8 @@ abstract class DashboardViewModelBase with Store { List get items { final _items = []; - _items.addAll(transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons])); + _items.addAll( + transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons])); _items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet)); _items.addAll(orders); @@ -258,8 +257,7 @@ abstract class DashboardViewModelBase with Store { } @observable - WalletBase, TransactionInfo> - wallet; + WalletBase, TransactionInfo> wallet; bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; @@ -283,7 +281,6 @@ abstract class DashboardViewModelBase with Store { Map> filterItems; - BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider; bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; @@ -291,8 +288,7 @@ abstract class DashboardViewModelBase with Store { bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; @action - void furtherShowYatPopup(bool shouldShow) => - settingsStore.shouldShowYatPopup = shouldShow; + void furtherShowYatPopup(bool shouldShow) => settingsStore.shouldShowYatPopup = shouldShow; @computed bool get isEnabledExchangeAction => settingsStore.exchangeStatus != ExchangeApiMode.disabled; @@ -301,8 +297,7 @@ abstract class DashboardViewModelBase with Store { bool hasExchangeAction; @computed - bool get isEnabledBuyAction => - !settingsStore.disableBuy && wallet.type != WalletType.haven; + bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven; @observable bool hasBuyAction; @@ -330,9 +325,7 @@ abstract class DashboardViewModelBase with Store { @action void _onWalletChange( - WalletBase, - TransactionInfo>? - wallet) { + WalletBase, TransactionInfo>? wallet) { if (wallet == null) { return; } @@ -350,10 +343,12 @@ abstract class DashboardViewModelBase with Store { _onMoneroAccountChangeReaction?.reaction.dispose(); _onMoneroBalanceChangeReaction?.reaction.dispose(); - _onMoneroAccountChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet) - .account, (Account account) => _onMoneroAccountChange(wallet)); + _onMoneroAccountChangeReaction = reaction( + (_) => monero!.getMoneroWalletDetails(wallet).account, + (Account account) => _onMoneroAccountChange(wallet)); - _onMoneroBalanceChangeReaction = reaction((_) => monero!.getMoneroWalletDetails(wallet).balance, + _onMoneroBalanceChangeReaction = reaction( + (_) => monero!.getMoneroWalletDetails(wallet).balance, (MoneroBalance balance) => _onMoneroTransactionsUpdate(wallet)); _onMoneroTransactionsUpdate(wallet); @@ -364,8 +359,8 @@ abstract class DashboardViewModelBase with Store { transactions.clear(); - transactions.addAll(wallet.transactionHistory.transactions.values.map( - (transaction) => TransactionListItem( + transactions.addAll(wallet.transactionHistory.transactions.values.map((transaction) => + TransactionListItem( transaction: transaction, balanceViewModel: balanceViewModel, settingsStore: appStore.settingsStore))); @@ -374,21 +369,19 @@ abstract class DashboardViewModelBase with Store { connectMapToListWithTransform( appStore.wallet!.transactionHistory.transactions, transactions, - (TransactionInfo? transaction) - => TransactionListItem( + (TransactionInfo? transaction) => TransactionListItem( transaction: transaction!, balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore), - filter: (TransactionInfo? tx) { - if (tx == null) { - return false; - } + settingsStore: appStore.settingsStore), filter: (TransactionInfo? tx) { + if (tx == null) { + return false; + } - if (wallet.type == WalletType.monero) { - return monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id; - } + if (wallet.type == WalletType.monero) { + return monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id; + } - return true; + return true; }); } @@ -402,15 +395,18 @@ abstract class DashboardViewModelBase with Store { void _onMoneroTransactionsUpdate(WalletBase wallet) { transactions.clear(); - final _accountTransactions = monero!.getTransactionHistory(wallet).transactions.values - .where((tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) + final _accountTransactions = monero! + .getTransactionHistory(wallet) + .transactions + .values + .where( + (tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) .toList(); - transactions.addAll(_accountTransactions.map((transaction) => - TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore))); } void updateActions() { diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 72ce9fdb2..0c322bfe6 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -417,7 +417,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) { if (walletType == WalletType.ethereum || walletType == WalletType.haven) { - if (error.contains('gas required exceeds allowance (0)') || error.contains('insufficient funds for gas')) { + if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for gas')) { return S.current.do_not_have_enough_gas_asset(currency.toString()); } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index da79a5076..f11876423 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -82,6 +82,8 @@ dependencies: shared_preferences_android: 2.0.17 url_launcher_android: 6.0.24 sensitive_clipboard: ^1.0.0 + walletconnect_flutter_v2: ^2.1.4 + eth_sig_util: ^0.0.9 bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 3a90aa308..f7bb19c45 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -689,8 +689,27 @@ "default_buy_provider": "مزود شراء الافتراضي", "ask_each_time": "اسأل في كل مرة", "buy_provider_unavailable": "مزود حاليا غير متوفر.", - + "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", + "errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ", + "errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ", + "pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ", + "chains": "ﻞﺳﻼﺴﻟﺍ", + "methods": " ﻕﺮﻃُ", + "events": "ﺙﺍﺪﺣﻷﺍ", + "reject": "ﺾﻓﺮﻳ", + "approve": "ﺪﻤﺘﻌﻳ", + "expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ", + "walletConnect": "WalletConnect", + "nullURIError": "ﻍﺭﺎﻓ (URI) ﻢﻈﺘﻨﻤﻟﺍ ﺩﺭﺍﻮﻤﻟﺍ ﻑﺮﻌﻣ", + "connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ", + "newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ", + "activeConnectionsPrompt": "ﺎﻨﻫ ﺔﻄﺸﻨﻟﺍ ﺕﻻﺎﺼﺗﻻﺍ ﺮﻬﻈﺘﺳ", + "deleteConnectionConfirmationPrompt": "ـﺑ ﻝﺎﺼﺗﻻﺍ ﻑﺬﺣ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ", + "event": "ﺙﺪﺣ", + "successful": "ﺢﺟﺎﻧ", + "wouoldLikeToConnect": "ﻝﺎﺼﺗﻻﺍ ﻲﻓ ﺐﻏﺮﺗ", + "message": "ﺔﻟﺎﺳﺭ", "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", - "totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ" + "totp_auth_url": "TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ", + "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ" } - diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 39ce6196b..644e18d33 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -685,6 +685,27 @@ "default_buy_provider": "Доставчик по подразбиране купува", "ask_each_time": "Питайте всеки път", "buy_provider_unavailable": "Понастоящем доставчик не е наличен.", + "signTransaction": "Подпишете транзакция", + "errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни", + "errorSigningTransaction": "Възникна грешка при подписване на транзакция", + "pairingInvalidEvent": "Невалидно събитие при сдвояване", + "chains": "Вериги", + "methods": "Методи", + "events": "събития", + "reject": "Отхвърляне", + "approve": "Одобряване", + "expiresOn": "Изтича на", + "walletConnect": "WalletConnect", + "nullURIError": "URI е нула", + "connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции", + "newConnection": "Нова връзка", + "activeConnectionsPrompt": "Тук ще се появят активни връзки", + "deleteConnectionConfirmationPrompt": "Сигурни ли сте, че искате да изтриете връзката към", + "event": "Събитие", + "successful": "Успешен", + "wouoldLikeToConnect": "иска да се свърже", + "message": "Съобщение", "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката." } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 0c063f0c9..41d428d7b 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -685,6 +685,27 @@ "default_buy_provider": "Výchozí poskytovatel nákupu", "ask_each_time": "Zeptejte se pokaždé", "buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.", + "signTransaction": "Podepsat transakci", + "errorGettingCredentials": "Selhalo: Chyba při získávání přihlašovacích údajů", + "errorSigningTransaction": "Při podepisování transakce došlo k chybě", + "pairingInvalidEvent": "Neplatná událost párování", + "chains": "Řetězy", + "methods": "Metody", + "events": "Události", + "reject": "Odmítnout", + "approve": "Schvalovat", + "expiresOn": "Vyprší dne", + "walletConnect": "WalletConnect", + "nullURIError": "URI je nulové", + "connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce", + "newConnection": "Nové připojení", + "activeConnectionsPrompt": "Zde se zobrazí aktivní připojení", + "deleteConnectionConfirmationPrompt": "Jste si jisti, že chcete smazat připojení k?", + "event": "událost", + "successful": "Úspěšný", + "wouoldLikeToConnect": "by se chtělo připojit", + "message": "Zpráva", "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", - "totp_auth_url": "URL AUTH TOTP" + "totp_auth_url": "URL AUTH TOTP", + "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování." } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 4b2cba6d4..4fcf08363 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Standard-Kaufanbieter", "ask_each_time": "Jedes Mal fragen", "buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.", + "signTransaction": "Transaktion unterzeichnen", + "errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen", + "errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten", + "pairingInvalidEvent": "Paarung ungültiges Ereignis", + "chains": "Ketten", + "methods": "Methoden", + "events": "Veranstaltungen", + "reject": "Ablehnen", + "approve": "Genehmigen", + "expiresOn": "Läuft aus am", + "walletConnect": "WalletConnect", + "nullURIError": "URI ist null", + "connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen", + "newConnection": "Neue Verbindung", + "activeConnectionsPrompt": "Hier werden aktive Verbindungen angezeigt", + "deleteConnectionConfirmationPrompt": "Sind Sie sicher, dass Sie die Verbindung zu löschen möchten?", + "event": "Ereignis", + "successful": "Erfolgreich", + "wouoldLikeToConnect": "möchte mich gerne vernetzen", + "message": "Nachricht", "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", - "totp_auth_url": "TOTP-Auth-URL" + "totp_auth_url": "TOTP-Auth-URL", + "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat." } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 7e3b1b55d..d1d356f29 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -694,6 +694,27 @@ "ask_each_time": "Ask each time", "robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.", "buy_provider_unavailable": "Provider currently unavailable.", + "signTransaction": "Sign Transaction", + "errorGettingCredentials": "Failed: Error while getting credentials", + "errorSigningTransaction": "An error has occured while signing transaction", + "pairingInvalidEvent": "Pairing Invalid Event", + "chains": "Chains", + "methods": "Methods", + "events": "Events", + "reject": "Reject", + "approve": "Approve", + "expiresOn": "Expires on", + "walletConnect": "WalletConnect", + "nullURIError": "URI is null", + "connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions", + "newConnection": "New Connection", + "activeConnectionsPrompt": "Active connections will appear here", + "deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to", + "event": "Event", + "successful": "Successful", + "wouoldLikeToConnect": "would like to connect", + "message": "Message", "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Kindly wait for the dApp to finish processing." } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index f2b0729f3..8dea97b87 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Proveedor de compra predeterminado", "ask_each_time": "Pregunta cada vez", "buy_provider_unavailable": "Proveedor actualmente no disponible.", + "signTransaction": "Firmar transacción", + "errorGettingCredentials": "Error: error al obtener las credenciales", + "errorSigningTransaction": "Se ha producido un error al firmar la transacción.", + "pairingInvalidEvent": "Evento de emparejamiento no válido", + "chains": "Cadenas", + "methods": "Métodos", + "events": "Eventos", + "reject": "Rechazar", + "approve": "Aprobar", + "expiresOn": "Expira el", + "walletConnect": "MonederoConectar", + "nullURIError": "URI es nula", + "connectWalletPrompt": "Conecte su billetera con WalletConnect para realizar transacciones", + "newConnection": "Nueva conexión", + "activeConnectionsPrompt": "Las conexiones activas aparecerán aquí", + "deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a", + "event": "Evento", + "successful": "Exitoso", + "wouoldLikeToConnect": "quisiera conectar", + "message": "Mensaje", "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", - "totp_auth_url": "URL de autenticación TOTP" + "totp_auth_url": "URL de autenticación TOTP", + "awaitDAppProcessing": "Espere a que la dApp termine de procesarse." } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index a081727fb..bdc92e40f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Fournisseur d'achat par défaut", "ask_each_time": "Demandez à chaque fois", "buy_provider_unavailable": "Fournisseur actuellement indisponible.", + "signTransaction": "Signer une transaction", + "errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification", + "errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction", + "pairingInvalidEvent": "Événement de couplage non valide", + "chains": "Chaînes", + "methods": "Méthodes", + "events": "Événements", + "reject": "Rejeter", + "approve": "Approuver", + "expiresOn": "Expire le", + "walletConnect": "PortefeuilleConnect", + "nullURIError": "L'URI est nul", + "connectWalletPrompt": "Connectez votre portefeuille avec WalletConnect pour effectuer des transactions", + "newConnection": "Nouvelle connexion", + "activeConnectionsPrompt": "Les connexions actives apparaîtront ici", + "deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à", + "event": "Événement", + "successful": "Réussi", + "wouoldLikeToConnect": "je voudrais me connecter", + "message": "Message", "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", - "totp_auth_url": "URL D'AUTORISATION TOTP" + "totp_auth_url": "URL D'AUTORISATION TOTP", + "awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement." } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 68051b5d9..18b65fb28 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -671,6 +671,27 @@ "default_buy_provider": "Tsohuwar Siyarwa", "ask_each_time": "Tambaya kowane lokaci", "buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.", + "signTransaction": "Sa hannu Ma'amala", + "errorGettingCredentials": "Ba a yi nasara ba: Kuskure yayin samun takaddun shaida", + "errorSigningTransaction": "An sami kuskure yayin sanya hannu kan ciniki", + "pairingInvalidEvent": "Haɗa Lamarin mara inganci", + "chains": "Sarkoki", + "methods": "Hanyoyin", + "events": "Abubuwan da suka faru", + "reject": "Ƙi", + "approve": "Amincewa", + "expiresOn": "Yana ƙarewa", + "walletConnect": "WalletConnect", + "nullURIError": "URI banza ne", + "connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala", + "newConnection": "Sabuwar Haɗi", + "activeConnectionsPrompt": "Haɗin kai mai aiki zai bayyana a nan", + "deleteConnectionConfirmationPrompt": "Shin kun tabbata cewa kuna son share haɗin zuwa", + "event": "Lamarin", + "successful": "Nasara", + "wouoldLikeToConnect": "ina son haɗi", + "message": "Sako", "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki." } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 9f531bb29..1a2edd9ad 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -693,6 +693,27 @@ "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता", "ask_each_time": "हर बार पूछें", "buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।", + "signTransaction": "लेन-देन पर हस्ताक्षर करें", + "errorGettingCredentials": "विफल: क्रेडेंशियल प्राप्त करते समय त्रुटि", + "errorSigningTransaction": "लेन-देन पर हस्ताक्षर करते समय एक त्रुटि उत्पन्न हुई है", + "pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना", + "chains": "चेन", + "methods": "तरीकों", + "events": "आयोजन", + "reject": "अस्वीकार करना", + "approve": "मंज़ूरी देना", + "expiresOn": "पर समय सीमा समाप्त", + "walletConnect": "वॉलेटकनेक्ट", + "nullURIError": "यूआरआई शून्य है", + "connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें", + "newConnection": "नया कनेक्शन", + "activeConnectionsPrompt": "सक्रिय कनेक्शन यहां दिखाई देंगे", + "deleteConnectionConfirmationPrompt": "क्या आप वाकई कनेक्शन हटाना चाहते हैं?", + "event": "आयोजन", + "successful": "सफल", + "wouoldLikeToConnect": "जुड़ना चाहेंगे", + "message": "संदेश", "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", - "totp_auth_url": "TOTP प्रामाणिक यूआरएल" + "totp_auth_url": "TOTP प्रामाणिक यूआरएल", + "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 4b56ef344..d47890556 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Zadani davatelj kupnje", "ask_each_time": "Pitajte svaki put", "buy_provider_unavailable": "Davatelj trenutno nije dostupan.", + "signTransaction": "Potpišite transakciju", + "errorGettingCredentials": "Neuspješno: Pogreška prilikom dobivanja vjerodajnica", + "errorSigningTransaction": "Došlo je do pogreške prilikom potpisivanja transakcije", + "pairingInvalidEvent": "Nevažeći događaj uparivanja", + "chains": "Lanci", + "methods": "Metode", + "events": "Događaji", + "reject": "Odbiti", + "approve": "Odobriti", + "expiresOn": "Istječe", + "walletConnect": "WalletConnect", + "nullURIError": "URI je nula", + "connectWalletPrompt": "Povežite svoj novčanik s WalletConnectom za obavljanje transakcija", + "newConnection": "Nova veza", + "activeConnectionsPrompt": "Ovdje će se pojaviti aktivne veze", + "deleteConnectionConfirmationPrompt": "Jeste li sigurni da želite izbrisati vezu s", + "event": "Događaj", + "successful": "Uspješno", + "wouoldLikeToConnect": "želio bi se povezati", + "message": "Poruka", "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", - "totp_auth_url": "TOTP AUTH URL" -} \ No newline at end of file + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu." +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 6200559e3..6adbe2a1f 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -681,6 +681,27 @@ "default_buy_provider": "Penyedia beli default", "ask_each_time": "Tanyakan setiap kali", "buy_provider_unavailable": "Penyedia saat ini tidak tersedia.", + "signTransaction": "Tandatangani Transaksi", + "errorGettingCredentials": "Gagal: Terjadi kesalahan saat mendapatkan kredensial", + "errorSigningTransaction": "Terjadi kesalahan saat menandatangani transaksi", + "pairingInvalidEvent": "Menyandingkan Acara Tidak Valid", + "chains": "Rantai", + "methods": "Metode", + "events": "Acara", + "reject": "Menolak", + "approve": "Menyetujui", + "expiresOn": "Kadaluarsa pada", + "walletConnect": "DompetConnect", + "nullURIError": "URI adalah nol", + "connectWalletPrompt": "Hubungkan dompet Anda dengan WalletConnect untuk melakukan transaksi", + "newConnection": "Koneksi Baru", + "activeConnectionsPrompt": "Koneksi aktif akan muncul di sini", + "deleteConnectionConfirmationPrompt": "Apakah Anda yakin ingin menghapus koneksi ke", + "event": "Peristiwa", + "successful": "Berhasil", + "wouoldLikeToConnect": "ingin terhubung", + "message": "Pesan", "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", - "totp_auth_url": "URL Otentikasi TOTP" -} \ No newline at end of file + "totp_auth_url": "URL Otentikasi TOTP", + "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan." +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index e3fd1b94b..db687ffe7 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Provider di acquisto predefinito", "ask_each_time": "Chiedi ogni volta", "buy_provider_unavailable": "Provider attualmente non disponibile.", + "signTransaction": "Firma la transazione", + "errorGettingCredentials": "Non riuscito: errore durante il recupero delle credenziali", + "errorSigningTransaction": "Si è verificato un errore durante la firma della transazione", + "pairingInvalidEvent": "Associazione evento non valido", + "chains": "Catene", + "methods": "Metodi", + "events": "Eventi", + "reject": "Rifiutare", + "approve": "Approvare", + "expiresOn": "Scade il", + "walletConnect": "PortafoglioConnetti", + "nullURIError": "L'URI è nullo", + "connectWalletPrompt": "Collega il tuo portafoglio con WalletConnect per effettuare transazioni", + "newConnection": "Nuova connessione", + "activeConnectionsPrompt": "Le connessioni attive verranno visualizzate qui", + "deleteConnectionConfirmationPrompt": "Sei sicuro di voler eliminare la connessione a", + "event": "Evento", + "successful": "Riuscito", + "wouoldLikeToConnect": "vorrei connettermi", + "message": "Messaggio", "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", - "totp_auth_url": "URL DI AUT. TOTP" + "totp_auth_url": "URL DI AUT. TOTP", + "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione." } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 8dbf429c5..32dd157a4 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -693,6 +693,27 @@ "default_buy_provider": "デフォルトの購入プロバイダー", "ask_each_time": "毎回尋ねてください", "buy_provider_unavailable": "現在、プロバイダーは利用できません。", + "signTransaction": "トランザクションに署名する", + "errorGettingCredentials": "失敗: 認証情報の取得中にエラーが発生しました", + "errorSigningTransaction": "トランザクションの署名中にエラーが発生しました", + "pairingInvalidEvent": "ペアリング無効イベント", + "chains": "チェーン", + "methods": "メソッド", + "events": "イベント", + "reject": "拒否する", + "approve": "承認する", + "expiresOn": "有効期限は次のとおりです", + "walletConnect": "ウォレットコネクト", + "nullURIError": "URIがnullです", + "connectWalletPrompt": "ウォレットを WalletConnect に接続して取引を行う", + "newConnection": "新しい接続", + "activeConnectionsPrompt": "アクティブな接続がここに表示されます", + "deleteConnectionConfirmationPrompt": "への接続を削除してもよろしいですか?", + "event": "イベント", + "successful": "成功", + "wouoldLikeToConnect": "接続したいです", + "message": "メッセージ", "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", - "totp_auth_url": "TOTP認証URL" + "totp_auth_url": "TOTP認証URL", + "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index da9b99c2b..2ac9ed4c6 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -693,6 +693,27 @@ "default_buy_provider": "기본 구매 제공자", "ask_each_time": "매번 물어보십시오", "buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.", + "signTransaction": "거래 서명", + "errorGettingCredentials": "실패: 자격 증명을 가져오는 중 오류가 발생했습니다.", + "errorSigningTransaction": "거래에 서명하는 동안 오류가 발생했습니다.", + "pairingInvalidEvent": "잘못된 이벤트 페어링", + "chains": "쇠사슬", + "methods": "행동 양식", + "events": "이벤트", + "reject": "거부하다", + "approve": "승인하다", + "expiresOn": "만료 날짜", + "walletConnect": "월렛커넥트", + "nullURIError": "URI가 null입니다.", + "connectWalletPrompt": "거래를 하려면 WalletConnect에 지갑을 연결하세요.", + "newConnection": "새로운 연결", + "activeConnectionsPrompt": "활성 연결이 여기에 표시됩니다", + "deleteConnectionConfirmationPrompt": "다음 연결을 삭제하시겠습니까?", + "event": "이벤트", + "successful": "성공적인", + "wouoldLikeToConnect": "연결하고 싶습니다", + "message": "메시지", "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", - "totp_auth_url": "TOTP 인증 URL" + "totp_auth_url": "TOTP 인증 URL", + "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요." } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 043484feb..80f8d6206 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -691,6 +691,27 @@ "default_buy_provider": "Default Provider ကိုဝယ်ပါ", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", "buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။", + "signTransaction": "ငွေလွှဲဝင်ပါ။", + "errorGettingCredentials": "မအောင်မြင်ပါ- အထောက်အထားများ ရယူနေစဉ် အမှားအယွင်း", + "errorSigningTransaction": "ငွေပေးငွေယူ လက်မှတ်ထိုးစဉ် အမှားအယွင်းတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။", + "pairingInvalidEvent": "မမှန်ကန်သောဖြစ်ရပ်ကို တွဲချိတ်ခြင်း။", + "chains": "ဆွဲကြိုး", + "methods": "နည်းလမ်းများ", + "events": "အဲ့ဒါနဲ့", + "reject": "ငြင်းပယ်ပါ။", + "approve": "လက်မခံပါ။", + "expiresOn": "သက်တမ်းကုန်သည်။", + "walletConnect": "Wallet ချိတ်ဆက်မှု", + "nullURIError": "URI သည် null ဖြစ်သည်။", + "connectWalletPrompt": "အရောင်းအဝယ်ပြုလုပ်ရန် သင့်ပိုက်ဆံအိတ်ကို WalletConnect နှင့် ချိတ်ဆက်ပါ။", + "newConnection": "ချိတ်ဆက်မှုအသစ်", + "activeConnectionsPrompt": "လက်ရှိချိတ်ဆက်မှုများ ဤနေရာတွင် ပေါ်လာပါမည်။", + "deleteConnectionConfirmationPrompt": "ချိတ်ဆက်မှုကို ဖျက်လိုသည်မှာ သေချာပါသလား။", + "event": "ပွဲ", + "successful": "အောင်မြင်တယ်။", + "wouoldLikeToConnect": "ချိတ်ဆက်ချင်ပါတယ်။", + "message": "မက်ဆေ့ချ်", "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 2d9f3655e..45cab5d09 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Standaard Koopprovider", "ask_each_time": "Vraag het elke keer", "buy_provider_unavailable": "Provider momenteel niet beschikbaar.", + "signTransaction": "Transactie ondertekenen", + "errorGettingCredentials": "Mislukt: fout bij het ophalen van inloggegevens", + "errorSigningTransaction": "Er is een fout opgetreden tijdens het ondertekenen van de transactie", + "pairingInvalidEvent": "Koppelen Ongeldige gebeurtenis", + "chains": "Ketens", + "methods": "Methoden", + "events": "Evenementen", + "reject": "Afwijzen", + "approve": "Goedkeuren", + "expiresOn": "Verloopt op", + "walletConnect": "WalletConnect", + "nullURIError": "URI is nul", + "connectWalletPrompt": "Verbind uw portemonnee met WalletConnect om transacties uit te voeren", + "newConnection": "Nieuwe verbinding", + "activeConnectionsPrompt": "Actieve verbindingen worden hier weergegeven", + "deleteConnectionConfirmationPrompt": "Weet u zeker dat u de verbinding met", + "event": "Evenement", + "successful": "Succesvol", + "wouoldLikeToConnect": "wil graag verbinden", + "message": "Bericht", "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", - "totp_auth_url": "TOTP AUTH-URL" + "totp_auth_url": "TOTP AUTH-URL", + "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken." } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 6f3a41d0b..0d9c22c79 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Domyślny dostawca zakupu", "ask_each_time": "Zapytaj za każdym razem", "buy_provider_unavailable": "Dostawca obecnie niedostępny.", + "signTransaction": "Podpisz transakcję", + "errorGettingCredentials": "Niepowodzenie: Błąd podczas uzyskiwania poświadczeń", + "errorSigningTransaction": "Wystąpił błąd podczas podpisywania transakcji", + "pairingInvalidEvent": "Nieprawidłowe zdarzenie parowania", + "chains": "Więzy", + "methods": "Metody", + "events": "Wydarzenia", + "reject": "Odrzucić", + "approve": "Zatwierdzić", + "expiresOn": "Upływa w dniu", + "walletConnect": "PortfelPołącz", + "nullURIError": "URI ma wartość zerową", + "connectWalletPrompt": "Połącz swój portfel z WalletConnect, aby dokonywać transakcji", + "newConnection": "Nowe połączenie", + "activeConnectionsPrompt": "Tutaj pojawią się aktywne połączenia", + "deleteConnectionConfirmationPrompt": "Czy na pewno chcesz usunąć połączenie z", + "event": "Wydarzenie", + "successful": "Udany", + "wouoldLikeToConnect": "chciałbym się połączyć", + "message": "Wiadomość", "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", - "totp_auth_url": "Adres URL TOTP AUTH" + "totp_auth_url": "Adres URL TOTP AUTH", + "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie." } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ea442420f..302955780 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -692,6 +692,27 @@ "default_buy_provider": "Provedor de compra padrão", "ask_each_time": "Pergunte cada vez", "buy_provider_unavailable": "Provedor atualmente indisponível.", + "signTransaction": "Assinar transação", + "errorGettingCredentials": "Falha: Erro ao obter credenciais", + "errorSigningTransaction": "Ocorreu um erro ao assinar a transação", + "pairingInvalidEvent": "Emparelhamento de evento inválido", + "chains": "Correntes", + "methods": "Métodos", + "events": "Eventos", + "reject": "Rejeitar", + "approve": "Aprovar", + "expiresOn": "Expira em", + "walletConnect": "CarteiraConectada", + "nullURIError": "URI é nulo", + "connectWalletPrompt": "Conecte sua carteira ao WalletConnect para fazer transações", + "newConnection": "Nova conexão", + "activeConnectionsPrompt": "Conexões ativas aparecerão aqui", + "deleteConnectionConfirmationPrompt": "Tem certeza de que deseja excluir a conexão com", + "event": "Evento", + "successful": "Bem-sucedido", + "wouoldLikeToConnect": "gostaria de me conectar", + "message": "Mensagem", "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", - "totp_auth_url": "URL de autenticação TOTP" + "totp_auth_url": "URL de autenticação TOTP", + "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento." } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 5e70bc544..c1af0eb74 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -693,6 +693,27 @@ "default_buy_provider": "По умолчанию поставщик покупки", "ask_each_time": "Спросите каждый раз", "buy_provider_unavailable": "Поставщик в настоящее время недоступен.", + "signTransaction": "Подписать транзакцию", + "errorGettingCredentials": "Не удалось: ошибка при получении учетных данных.", + "errorSigningTransaction": "Произошла ошибка при подписании транзакции", + "pairingInvalidEvent": "Недействительное событие сопряжения", + "chains": "Цепи", + "methods": "Методы", + "events": "События", + "reject": "Отклонять", + "approve": "Утвердить", + "expiresOn": "Годен до", + "walletConnect": "КошелекПодключиться", + "nullURIError": "URI имеет значение null", + "connectWalletPrompt": "Подключите свой кошелек к WalletConnect для совершения транзакций.", + "newConnection": "Новое соединение", + "activeConnectionsPrompt": "Здесь появятся активные подключения", + "deleteConnectionConfirmationPrompt": "Вы уверены, что хотите удалить подключение к", + "event": "Событие", + "successful": "Успешный", + "wouoldLikeToConnect": "хотел бы подключиться", + "message": "Сообщение", "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", - "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ" + "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ", + "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку." } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 8778802a9..eb4b1ac31 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -691,6 +691,27 @@ "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น", "ask_each_time": "ถามทุกครั้ง", "buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน", + "signTransaction": "ลงนามในการทำธุรกรรม", + "errorGettingCredentials": "ล้มเหลว: เกิดข้อผิดพลาดขณะรับข้อมูลรับรอง", + "errorSigningTransaction": "เกิดข้อผิดพลาดขณะลงนามธุรกรรม", + "pairingInvalidEvent": "การจับคู่เหตุการณ์ที่ไม่ถูกต้อง", + "chains": "ห่วงโซ่", + "methods": "วิธีการ", + "events": "กิจกรรม", + "reject": "ปฏิเสธ", + "approve": "อนุมัติ", + "expiresOn": "หมดอายุวันที่", + "walletConnect": "WalletConnect", + "nullURIError": "URI เป็นโมฆะ", + "connectWalletPrompt": "เชื่อมต่อกระเป๋าเงินของคุณด้วย WalletConnect เพื่อทำธุรกรรม", + "newConnection": "การเชื่อมต่อใหม่", + "activeConnectionsPrompt": "การเชื่อมต่อที่ใช้งานอยู่จะปรากฏที่นี่", + "deleteConnectionConfirmationPrompt": "คุณแน่ใจหรือไม่ว่าต้องการลบการเชื่อมต่อไปยัง", + "event": "เหตุการณ์", + "successful": "ประสบความสำเร็จ", + "wouoldLikeToConnect": "ต้องการเชื่อมต่อ", + "message": "ข้อความ", "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", - "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP" + "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP", + "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 18c52aa83..8072eee61 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -688,6 +688,33 @@ "support_description_other_links": "Sumali sa aming mga komunidad o maabot sa amin ang aming mga kasosyo sa pamamagitan ng iba pang mga pamamaraan", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup file.", "save_to_downloads": "I -save sa mga pag -download", + "select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.", + "onramper_option_description": "Mabilis na bumili ng crypto na may maraming paraan ng pagbabayad. Available sa karamihan ng mga bansa. Iba-iba ang mga spread at bayarin.", + "default_buy_provider": "Default na Provider ng Pagbili", + "ask_each_time": "Magtanong sa bawat oras", + "robinhood_option_description": "Bumili at ilipat kaagad gamit ang iyong debit card, bank account, o balanse ng Robinhood. USA lang.", + "buy_provider_unavailable": "Kasalukuyang hindi available ang provider.", + "signTransaction": "Mag-sign Transaksyon", + "errorGettingCredentials": "Nabigo: Error habang kumukuha ng mga kredensyal", + "errorSigningTransaction": "May naganap na error habang pinipirmahan ang transaksyon", + "pairingInvalidEvent": "Pagpares ng Di-wastong Kaganapan", + "chains": "Mga tanikala", + "methods": "Paraan", + "events": "Mga kaganapan", + "reject": "Tanggihan", + "approve": "Aprubahan", + "expiresOn": "Mag-e-expire sa", + "walletConnect": "WalletConnect", + "nullURIError": "Ang URI ay null", + "connectWalletPrompt": "Ikonekta ang iyong wallet sa WalletConnect upang gumawa ng mga transaksyon", + "newConnection": "Bagong Koneksyon", + "activeConnectionsPrompt": "Lalabas dito ang mga aktibong koneksyon", + "deleteConnectionConfirmationPrompt": "Sigurado ka bang gusto mong tanggalin ang koneksyon sa", + "event": "Kaganapan", + "successful": "Matagumpay", + "wouoldLikeToConnect": "gustong kumonekta", + "message": "Mensahe", "do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.", - "totp_auth_url": "TOTP AUTH URL" -} \ No newline at end of file + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp." +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index fa4568e93..d0afc5825 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -691,6 +691,27 @@ "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı", "ask_each_time": "Her seferinde sor", "buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.", + "signTransaction": "İşlem İmzala", + "errorGettingCredentials": "Başarısız: Kimlik bilgileri alınırken hata oluştu", + "errorSigningTransaction": "İşlem imzalanırken bir hata oluştu", + "pairingInvalidEvent": "Geçersiz Etkinliği Eşleştirme", + "chains": "Zincirler", + "methods": "Yöntemler", + "events": "Olaylar", + "reject": "Reddetmek", + "approve": "Onaylamak", + "expiresOn": "Tarihinde sona eriyor", + "walletConnect": "WalletConnect", + "nullURIError": "URI boş", + "connectWalletPrompt": "İşlem yapmak için cüzdanınızı WalletConnect'e bağlayın", + "newConnection": "Yeni bağlantı", + "activeConnectionsPrompt": "Aktif bağlantılar burada görünecek", + "deleteConnectionConfirmationPrompt": "Bağlantıyı silmek istediğinizden emin misiniz?", + "event": "Etkinlik", + "successful": "Başarılı", + "wouoldLikeToConnect": "bağlanmak istiyorum", + "message": "İleti", "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", - "totp_auth_url": "TOTP YETKİ URL'si" + "totp_auth_url": "TOTP YETKİ URL'si", + "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin." } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 7b739e201..99720ca51 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -693,6 +693,27 @@ "default_buy_provider": "Постачальник покупки за замовчуванням", "ask_each_time": "Запитайте кожен раз", "buy_provider_unavailable": "В даний час постачальник недоступний.", + "signTransaction": "Підписати транзакцію", + "errorGettingCredentials": "Помилка: помилка під час отримання облікових даних", + "errorSigningTransaction": "Під час підписання транзакції сталася помилка", + "pairingInvalidEvent": "Недійсна подія сполучення", + "chains": "Ланцюги", + "methods": "методи", + "events": "Події", + "reject": "Відхиляти", + "approve": "Затвердити", + "expiresOn": "Термін дії закінчується", + "walletConnect": "WalletConnect", + "nullURIError": "URI нульовий", + "connectWalletPrompt": "Підключіть свій гаманець до WalletConnect, щоб здійснювати транзакції", + "newConnection": "Нове підключення", + "activeConnectionsPrompt": "Тут з’являться активні підключення", + "deleteConnectionConfirmationPrompt": "Ви впевнені, що хочете видалити з’єднання з", + "event": "Подія", + "successful": "Успішний", + "wouoldLikeToConnect": "хотів би підключитися", + "message": "повідомлення", "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку." } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index e53784923..21c247fd1 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -685,6 +685,27 @@ "default_buy_provider": "پہلے سے طے شدہ خریدنے والا", "ask_each_time": "ہر بار پوچھیں", "buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔", + "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", + "errorGettingCredentials": "۔ﯽﺑﺍﺮﺧ ﮟﯿﻣ ﮯﻧﺮﮐ ﻞﺻﺎﺣ ﺩﺎﻨﺳﺍ :ﻡﺎﮐﺎﻧ", + "errorSigningTransaction": "۔ﮯﮨ ﯽﺌﮔﺁ ﺶﯿﭘ ﯽﺑﺍﺮﺧ ﮏﯾﺍ ﺖﻗﻭ ﮯﺗﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ", + "pairingInvalidEvent": "ﭧﻧﻮﯾﺍ ﻂﻠﻏ ﺎﻧﺎﻨﺑ ﺍﮌﻮﺟ", + "chains": "ﮟﯾﺮﯿﺠﻧﺯ", + "methods": "ﮯﻘﯾﺮﻃ", + "events": "ﺕﺎﺒﯾﺮﻘﺗ", + "reject": "ﺎﻧﺮﮐ ﺩﺭ", + "approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ", + "expiresOn": "ﺩﺎﻌﯿﻣ ﯽﻣﺎﺘﺘﺧﺍ", + "walletConnect": "WalletConnect", + "nullURIError": "URI ۔ﮯﮨ ﻡﺪﻌﻟﺎﮐ", + "connectWalletPrompt": "۔ﮟﯾﮌﻮﺟ ﮫﺗﺎﺳ ﮯﮐ WalletConnect ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻦﯾﺩ ﻦﯿﻟ", + "newConnection": "ﻦﺸﮑﻨﮐ ﺎﯿﻧ", + "activeConnectionsPrompt": "۔ﮯﮔ ﮞﻮﮨ ﺮﮨﺎﻇ ﮞﺎﮩﯾ ﺰﻨﺸﮑﻨﮐ ﻝﺎﻌﻓ", + "deleteConnectionConfirmationPrompt": "۔ﮟﯿﮨ ﮯﺘﮨﺎﭼ ﺎﻧﺮﮐ ﻑﺬﺣ ﻮﮐ ﻦﺸﮑﻨﮐ ﭖﺁ ﮧﮐ ﮯﮨ ﻦﯿﻘﯾ ﻮﮐ ﭖﺁ ﺎﯿﮐ", + "event": "ﺐﯾﺮﻘﺗ", + "successful": "ﺏﺎﯿﻣﺎﮐ", + "wouoldLikeToConnect": "؟ﮯﮔ ﮟﯿﮨﺎﭼ ﺎﻧﮍﺟ", + "message": "ﻡﺎﻐﯿﭘ", "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ" } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 0532ae2eb..e362df16d 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -687,6 +687,27 @@ "default_buy_provider": "Aiyipada Ra Olupese", "ask_each_time": "Beere lọwọ kọọkan", "buy_provider_unavailable": "Olupese lọwọlọwọ ko si.", + "signTransaction": "Wole Idunadura", + "errorGettingCredentials": "Kuna: Aṣiṣe lakoko gbigba awọn iwe-ẹri", + "errorSigningTransaction": "Aṣiṣe kan ti waye lakoko ti o fowo si iṣowo", + "pairingInvalidEvent": "Pipọpọ Iṣẹlẹ Ti ko tọ", + "chains": "Awọn ẹwọn", + "methods": "Awọn ọna", + "events": "Awọn iṣẹlẹ", + "reject": "Kọ", + "approve": "Fi ọwọ si", + "expiresOn": "Ipari lori", + "walletConnect": "Asopọmọra apamọwọ", + "nullURIError": "URI jẹ asan", + "connectWalletPrompt": "So apamọwọ rẹ pọ pẹlu WalletConnect lati ṣe awọn iṣowo", + "newConnection": "Tuntun Asopọ", + "activeConnectionsPrompt": "Awọn asopọ ti nṣiṣe lọwọ yoo han nibi", + "deleteConnectionConfirmationPrompt": "Ṣe o da ọ loju pe o fẹ paarẹ asopọ si", + "event": "Iṣẹlẹ", + "successful": "Aseyori", + "wouoldLikeToConnect": "yoo fẹ lati sopọ", + "message": "Ifiranṣẹ", "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", - "totp_auth_url": "TOTP AUTH URL" + "totp_auth_url": "TOTP AUTH URL", + "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ." } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 7c947847e..19928f5e2 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -692,6 +692,27 @@ "default_buy_provider": "默认购买提供商", "ask_each_time": "每次问", "buy_provider_unavailable": "提供者目前不可用。", + "signTransaction": "签署交易", + "errorGettingCredentials": "失败:获取凭据时出错", + "errorSigningTransaction": "签署交易时发生错误", + "pairingInvalidEvent": "配对无效事件", + "chains": "链条", + "methods": "方法", + "events": "活动", + "reject": "拒绝", + "approve": "批准", + "expiresOn": "到期", + "walletConnect": "钱包连接", + "nullURIError": "URI 为空", + "connectWalletPrompt": "将您的钱包与 WalletConnect 连接以进行交易", + "newConnection": "新连接", + "activeConnectionsPrompt": "活动连接将出现在这里", + "deleteConnectionConfirmationPrompt": "您确定要删除与", + "event": "事件", + "successful": "成功的", + "wouoldLikeToConnect": "想要连接", + "message": "信息", "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", - "totp_auth_url": "TOTP 授权 URL" + "totp_auth_url": "TOTP 授权 URL", + "awaitDAppProcessing": "请等待 dApp 处理完成。" } diff --git a/tool/configure.dart b/tool/configure.dart index 5172f4244..2f594925e 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -477,6 +477,7 @@ 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:eth_sig_util/util/utils.dart'; import 'package:hive/hive.dart'; """; const ethereumCWHeaders = """ @@ -498,6 +499,8 @@ abstract class Ethereum { WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); String getAddress(WalletBase wallet); + String getPrivateKey(WalletBase wallet); + String getPublicKey(WalletBase wallet); TransactionPriority getDefaultTransactionPriority(); List getTransactionPriorities(); TransactionPriority deserializeEthereumTransactionPriority(int raw); diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 5e959b99b..a8c6a6166 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -35,6 +35,7 @@ class SecretKey { SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), + SecretKey('walletConnectProjectId', () => ''), ]; static final ethereumSecrets = [ From a5150766966213af2cbb80bd806d292efd46ed34 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Tue, 3 Oct 2023 20:07:05 +0300 Subject: [PATCH 030/161] Code Enhancements --- cw_ethereum/lib/ethereum_client.dart | 1 - lib/di.dart | 2 +- lib/entities/ens_record.dart | 2 +- lib/entities/parse_address_from_domain.dart | 7 ++----- lib/ethereum/cw_ethereum.dart | 2 +- tool/configure.dart | 4 ++-- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index cb09f8f89..532eb2b99 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -6,7 +6,6 @@ import 'package:cw_ethereum/erc20_balance.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_ethereum/ethereum_transaction_model.dart'; import 'package:cw_ethereum/pending_ethereum_transaction.dart'; -import 'package:ens_dart/ens_dart.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:web3dart/web3dart.dart'; diff --git a/lib/di.dart b/lib/di.dart index 97dd392c5..e069a966b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -883,7 +883,7 @@ Future setup({ getIt.registerFactory(() => YatService()); getIt.registerFactory(() => AddressResolver( - yatService: getIt.get(), walletType: getIt.get().wallet!.type)); + yatService: getIt.get(), wallet: getIt.get().wallet!)); getIt.registerFactoryParam( (QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData)); diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 28a129f1b..8cf62d79b 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -10,7 +10,7 @@ class EnsRecord { Web3Client? _client; if (wallet != null && wallet.type == WalletType.ethereum) { - _client = ethereum!.getWeb3Client(wallet) as Web3Client?; + _client = ethereum!.getWeb3Client(wallet); } if (_client == null) { diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index dcaee5815..515b9a2df 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -1,13 +1,10 @@ import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/yat_service.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/ens_record.dart'; import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; -import 'package:cake_wallet/ethereum/ethereum.dart'; -import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/twitter/twitter_api.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; @@ -15,10 +12,11 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/entities/fio_address_provider.dart'; class AddressResolver { - AddressResolver({required this.yatService, required this.walletType}); + AddressResolver({required this.yatService, required this.wallet}) : walletType = wallet.type; final YatService yatService; final WalletType walletType; + final WalletBase wallet; static const unstoppableDomains = [ 'crypto', @@ -102,7 +100,6 @@ class AddressResolver { } if (text.endsWith(".eth")) { - WalletBase? wallet = getIt.get().wallet!; final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet); if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") { return ParsedAddress.fetchEnsAddress(name: text, address: address); diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index eab6c26d3..74a298bf9 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -133,7 +133,7 @@ class CWEthereum extends Ethereum { } @override - dynamic getWeb3Client(WalletBase wallet) { + Web3Client? getWeb3Client(WalletBase wallet) { return (wallet as EthereumWallet).getWeb3Client(); } } diff --git a/tool/configure.dart b/tool/configure.dart index 1f9ddf1c6..e3a2efe35 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -467,7 +467,6 @@ Future generateEthereum(bool hasImplementation) async { final outputFile = File(ethereumOutputPath); const ethereumCommonHeaders = """ import 'package:cake_wallet/view_model/send/output.dart'; -import 'package:cw_core/crypto_amount_format.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/output_info.dart'; @@ -478,6 +477,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:hive/hive.dart'; +import 'package:web3dart/web3dart.dart'; """; const ethereumCWHeaders = """ import 'package:cw_ethereum/ethereum_formatter.dart'; @@ -525,7 +525,7 @@ abstract class Ethereum { CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); void updateEtherscanUsageState(WalletBase wallet, bool isEnabled); - dynamic getWeb3Client(WalletBase wallet); + Web3Client? getWeb3Client(WalletBase wallet); } """; From 6233422643e5aed57c90281afab060f012660eb2 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 3 Oct 2023 22:05:59 +0300 Subject: [PATCH 031/161] CW-478-Startup-splash-screen-is-white-in-all-themes.-Change-to-black-in-dark-themes (#1105) * fix splash screen color * Update AndroidManifestBase.xml --- android/app/src/main/AndroidManifestBase.xml | 4 ---- .../main/res/drawable-night/launch_background.xml | 6 ++++++ .../app/src/main/res/drawable/launch_background.xml | 12 +++--------- 3 files changed, 9 insertions(+), 13 deletions(-) create mode 100644 android/app/src/main/res/drawable-night/launch_background.xml diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 9b3f47314..f22ba9c4f 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -25,10 +25,6 @@ android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:exported="true"> - diff --git a/android/app/src/main/res/drawable-night/launch_background.xml b/android/app/src/main/res/drawable-night/launch_background.xml new file mode 100644 index 000000000..11d0cb8e6 --- /dev/null +++ b/android/app/src/main/res/drawable-night/launch_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f88..50af1a418 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,6 @@ - - - - - + + + From 4c60b178be958d21cb1fd3fa3ee48137a487310e Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Wed, 4 Oct 2023 21:09:07 -0400 Subject: [PATCH 032/161] CW-438 add nano (#1015) * Fix web3dart versioning issue * Add primary receive address extracted from private key * Implement open wallet functionality * Implement restore wallet from seed functionality * Fixate web3dart version as higher versions cause some issues * Add Initial Transaction priorities for eth Add estimated gas price * Rename priority value to tip * Re-order wallet types * Change ethereum node Fix connection issues * Fix estimating gas for priority * Add case for ethereum to fetch it's seeds * Add case for ethereum to request node * Fix Exchange screen initial pairs * Add initial send transaction flow * Add missing configure for ethereum class * Add Eth address initial setup * Fix Private key for Ethereum wallets * Change sign/send transaction flow * - Fix Conflicts with main - Remove unused function from Haven configure.dart * Add build command for ethereum package * Add missing Node list file to pubspec * - Fix balance display - Fix parsing of Ethereum amount - Add more Ethereum Nodes [skip ci] * - Fix extracting Ethereum Private key from seeds - Integrate signing/sending transaction with the send view model * - Update and Fix Conflicts with main * Add Balances for ERC20 tokens * Fix conflicts with main * Add erc20 abi json * Add send erc20 tokens initial function * add missing getHeightByDate in Haven [skip ci] * Allow contacts and wallets from the same tag * Add Shiba Inu icon * Add send ERC-20 tokens initial flow * Add missing import in generated file * Add initial approach for transaction sending for ERC-20 tokens * Refactor signing/sending transactions * Add initial flow for transactions subscription * Refactor signing/sending transactions * Add home settings icon * Fix conflicts with main * Initial flow for home settings * Add logic flow for adding erc20 tokens * Fix initial UI * Finalize UI for Tokens * Integrate UI with Ethereum flow * Add "Enable/Disable" feature for ERC20 tokens * Add initial Erc20 tokens * Add Sorting and Pin Native Token features * Fix price sorting * Sort tokens list as well when Sort criteria changes * - Improve sorting balances flow - Add initial add token from search bar flow * Fix Accounts Popup UI * Fix Pin native token * Fix Enabling/Disabling tokens Fix sorting by fiat once app is opened Improve token availability mechanism * Fix deleting token Fix renaming tokens * Fix issue with search * Add more tokens * - Fix scroll issue - Add ERC20 tokens placeholder image in picker * - Separate and organize default erc20 tokens - Fix scrolling - Add token placeholder images in picker - Sort disabled tokens alphabetically * Change BNB token initial availability [skip ci] * Fix Conflicts with main * Fix Conflicts with main * Add Verse ERC20 token to the initial tokens list * Add rename wallet to Ethereum * Integrate EtherScan API for fetching address transactions Generate Ethereum specific secrets in Ethereum package * Adjust transactions fiat price for ERC20 tokens * Free Up GitHub Actions Ubuntu Runner Disk Space * Free Up GitHub Actions Ubuntu Runner Disk space (trial 2) * Fix Transaction Fee display * Save transaction history * Enhance loading time for erc20 tokens transactions * Minor Fixes and Enhancements * Fix sending erc20 fix block explorer issue * Fix int overflow * Fix transaction amount conversions * Minor: `slow` -> `Slow` [skip-ci] * initial changes * more base config stuff * config changes * successfully builds! * save * successfully add nano wallet * save * seed generation * receive screen + node screen working * tx history working and fiat fixes * balance working * derivation updates * nano-unfinished * sends working * remove fees from send screen, send and receive transactions working * fixes + auto receive incoming txs * fix for scanning QR codes * save * update translations * fixes * more fixes * more strings * small fix * fix github actions workflow * potential fix * potential fix * ci/cd fix * change rep working * seed generation fixes * fixes * save * change rep screen functional * save * banano changes * fixes, start adding ui for PoW * pow node changes * update translations * fix * account changing barely working * save * disable account generation * small fix * save * UI work * save * fixes after merge main * fixes * remove monero stuff, work on derivation ui * lots of fixes + finish up seed derivation * last minute fixes * node related fixes * more fixes * small fix * more fixes * fixes * pretty big refactor for pow, still some bugs * finally works! * get transactions after send * fix * merge conflict fixes * save * fix pow node showing up twice * done * initial changes * small fix * more merge fixes * fixes * more fixes * fix * save * fix manage pow nodes setting appearing on other wallets * fix contact bug * fixes * fiat fixes * save * save * save * save * updates * cleanup * restore fix * fixes * remove deprecated alert * fix * small fix * remove outdated warning * electrum restore fixes * fixes * fixes * fix * derivation fixes * nano fixes pt.1 * nano fixes pt.2 * bip39 fixes * pownode refactor * nodes pages fixes * observer fix * ssl fix * remove old references * remove unused imports * code cleanup * small fix * small potential fix * save * undo all bitcoin related changes * remove dead code * review fixes * more fixes * fix * fix * review fix * small fix * nano derivation and nanoutil fixes * exchange nano fix * nano review fixes pt.1 * nano fixes pt.2 * nano fixes pt.3 * remove old imports + stop using dynamic in di * nanoutil fixes * add nano.dart to gitignore, configure fixes * review fixes, getnanowalletservice removed * fix settings screen, add changeRep to configure.dart, other minor fixes * remove manage_pow_nodes_page, key derivation edge case handled * remove old refs * more small fixes * Generic Enhancements/Minor fixes * review fixes * hopefully final fixes * review fixes * node connection fixes --------- Co-authored-by: OmarHatem Co-authored-by: Justin Ehrenhofer Co-authored-by: fossephate --- .github/workflows/pr_test_build.yml | 1 + .gitignore | 1 + assets/nano_node_list.yml | 6 + assets/nano_pow_node_list.yml | 9 + configure_cake_wallet_android.sh | 1 + cw_bitcoin/lib/bitcoin_mnemonic.dart | 2 +- cw_bitcoin/lib/bitcoin_wallet.dart | 2 +- .../bitcoin_wallet_creation_credentials.dart | 2 +- cw_bitcoin/lib/bitcoin_wallet_service.dart | 2 +- cw_bitcoin/lib/electrum_wallet_snapshot.dart | 2 +- cw_core/lib/crypto_currency.dart | 4 +- cw_core/lib/currency_for_wallet_type.dart | 4 + cw_core/lib/hive_type_ids.dart | 5 +- cw_core/lib/nano_account.dart | 23 + cw_core/lib/nano_account_info_response.dart | 23 + cw_core/lib/node.dart | 84 +- cw_core/lib/wallet_base.dart | 7 +- cw_core/lib/wallet_credentials.dart | 7 +- cw_core/lib/wallet_info.dart | 109 +- cw_core/lib/wallet_service.dart | 6 +- cw_core/lib/wallet_type.dart | 28 + cw_monero/ios/Classes/monero_api.cpp | 3 +- cw_monero/lib/monero_wallet_service.dart | 2 +- cw_nano/lib/banano_balance.dart | 20 + cw_nano/lib/cw_nano.dart | 7 + cw_nano/lib/file.dart | 39 + cw_nano/lib/nano_account_list.dart | 69 + cw_nano/lib/nano_balance.dart | 34 + cw_nano/lib/nano_client.dart | 424 ++++ cw_nano/lib/nano_mnemonic.dart | 2088 +++++++++++++++++ cw_nano/lib/nano_transaction_credentials.dart | 7 + cw_nano/lib/nano_transaction_history.dart | 72 + cw_nano/lib/nano_transaction_info.dart | 70 + cw_nano/lib/nano_transaction_model.dart | 39 + cw_nano/lib/nano_util.dart | 193 ++ cw_nano/lib/nano_wallet.dart | 437 ++++ cw_nano/lib/nano_wallet_addresses.dart | 50 + .../lib/nano_wallet_creation_credentials.dart | 41 + cw_nano/lib/nano_wallet_keys.dart | 5 + cw_nano/lib/nano_wallet_service.dart | 163 ++ cw_nano/lib/pending_nano_transaction.dart | 40 + cw_nano/pubspec.lock | 756 ++++++ cw_nano/pubspec.yaml | 69 + cw_nano/test/cw_nano_test.dart | 12 + lib/bitcoin/cw_bitcoin.dart | 2 +- lib/buy/onramper/onramper_buy_provider.dart | 2 + lib/core/address_validator.dart | 4 + lib/core/seed_validator.dart | 4 + lib/core/wallet_creation_service.dart | 37 +- lib/di.dart | 94 +- lib/entities/default_settings_migration.dart | 301 +-- lib/entities/main_actions.dart | 6 +- lib/entities/node_list.dart | 59 +- lib/entities/preferences_key.dart | 4 + lib/entities/priority_for_wallet_type.dart | 5 +- lib/main.dart | 15 +- lib/nano/cw_nano.dart | 499 ++++ lib/reactions/on_current_node_change.dart | 7 + lib/reactions/on_current_wallet_change.dart | 9 +- lib/router.dart | 266 +-- lib/routes.dart | 6 + lib/src/screens/dashboard/dashboard_page.dart | 23 - .../dashboard/desktop_dashboard_page.dart | 17 - .../desktop_wallet_selection_dropdown.dart | 6 + .../dashboard/widgets/address_page.dart | 25 - .../dashboard/widgets/menu_widget.dart | 34 +- .../screens/nano/nano_change_rep_page.dart | 103 + .../nano_account_edit_or_create_page.dart | 66 + .../nano_accounts/nano_account_list_page.dart | 92 + .../nano_accounts/widgets/account_tile.dart | 96 + .../nodes/pow_node_create_or_edit_page.dart | 196 ++ .../screens/nodes/widgets/node_list_row.dart | 6 +- lib/src/screens/receive/receive_page.dart | 24 +- .../wallet_restore_choose_derivation.dart | 129 + .../wallet_restore_from_keys_form.dart | 5 +- .../screens/restore/wallet_restore_page.dart | 170 +- .../send/widgets/confirm_sending_alert.dart | 478 ++-- lib/src/screens/send/widgets/send_card.dart | 176 +- .../settings/connection_sync_page.dart | 15 + .../screens/settings/manage_nodes_page.dart | 31 +- .../screens/settings/other_settings_page.dart | 23 +- .../screens/wallet_list/wallet_list_page.dart | 3 + lib/src/screens/welcome/welcome_page.dart | 2 +- lib/store/settings_store.dart | 55 + lib/utils/payment_request.dart | 15 +- .../contact_list/contact_list_view_model.dart | 23 +- .../dashboard/dashboard_view_model.dart | 144 +- .../dashboard/transaction_list_item.dart | 44 +- .../exchange/exchange_view_model.dart | 9 +- ...ano_account_edit_or_create_view_model.dart | 58 + .../nano_account_list_view_model.dart | 54 + .../node_list/node_list_view_model.dart | 3 + .../node_list/pow_node_list_view_model.dart | 79 + lib/view_model/send/send_view_model.dart | 41 +- .../settings/other_settings_view_model.dart | 10 +- .../transaction_details_view_model.dart | 25 + .../wallet_address_list_view_model.dart | 35 +- lib/view_model/wallet_creation_vm.dart | 49 +- lib/view_model/wallet_keys_view_model.dart | 21 + lib/view_model/wallet_new_vm.dart | 3 + ..._restore_choose_derivation_view_model.dart | 23 + lib/view_model/wallet_restore_view_model.dart | 125 +- model_generator.sh | 1 + pubspec_base.yaml | 2 + res/values/strings_ar.arb | 8 +- res/values/strings_bg.arb | 8 +- res/values/strings_cs.arb | 6 + res/values/strings_de.arb | 8 +- res/values/strings_en.arb | 8 +- res/values/strings_es.arb | 8 +- res/values/strings_fr.arb | 8 +- res/values/strings_ha.arb | 10 +- res/values/strings_hi.arb | 8 +- res/values/strings_hr.arb | 8 +- res/values/strings_id.arb | 6 + res/values/strings_it.arb | 6 + res/values/strings_ja.arb | 6 + res/values/strings_ko.arb | 4 + res/values/strings_my.arb | 6 + res/values/strings_nl.arb | 6 + res/values/strings_pl.arb | 6 + res/values/strings_pt.arb | 6 + res/values/strings_ru.arb | 6 + res/values/strings_th.arb | 6 + res/values/strings_tr.arb | 6 + res/values/strings_uk.arb | 6 + res/values/strings_ur.arb | 6 + res/values/strings_yo.arb | 6 + res/values/strings_zh.arb | 6 + scripts/android/pubspec_gen.sh | 2 +- scripts/ios/app_config.sh | 2 +- scripts/macos/app_config.sh | 2 +- tool/append_translation.dart | 2 +- tool/configure.dart | 223 +- 134 files changed, 8171 insertions(+), 1055 deletions(-) create mode 100644 assets/nano_node_list.yml create mode 100644 assets/nano_pow_node_list.yml mode change 100644 => 100755 configure_cake_wallet_android.sh create mode 100644 cw_core/lib/nano_account.dart create mode 100644 cw_core/lib/nano_account_info_response.dart create mode 100644 cw_nano/lib/banano_balance.dart create mode 100644 cw_nano/lib/cw_nano.dart create mode 100644 cw_nano/lib/file.dart create mode 100644 cw_nano/lib/nano_account_list.dart create mode 100644 cw_nano/lib/nano_balance.dart create mode 100644 cw_nano/lib/nano_client.dart create mode 100644 cw_nano/lib/nano_mnemonic.dart create mode 100644 cw_nano/lib/nano_transaction_credentials.dart create mode 100644 cw_nano/lib/nano_transaction_history.dart create mode 100644 cw_nano/lib/nano_transaction_info.dart create mode 100644 cw_nano/lib/nano_transaction_model.dart create mode 100644 cw_nano/lib/nano_util.dart create mode 100644 cw_nano/lib/nano_wallet.dart create mode 100644 cw_nano/lib/nano_wallet_addresses.dart create mode 100644 cw_nano/lib/nano_wallet_creation_credentials.dart create mode 100644 cw_nano/lib/nano_wallet_keys.dart create mode 100644 cw_nano/lib/nano_wallet_service.dart create mode 100644 cw_nano/lib/pending_nano_transaction.dart create mode 100644 cw_nano/pubspec.lock create mode 100644 cw_nano/pubspec.yaml create mode 100644 cw_nano/test/cw_nano_test.dart create mode 100644 lib/nano/cw_nano.dart create mode 100644 lib/src/screens/nano/nano_change_rep_page.dart create mode 100644 lib/src/screens/nano_accounts/nano_account_edit_or_create_page.dart create mode 100644 lib/src/screens/nano_accounts/nano_account_list_page.dart create mode 100644 lib/src/screens/nano_accounts/widgets/account_tile.dart create mode 100644 lib/src/screens/nodes/pow_node_create_or_edit_page.dart create mode 100644 lib/src/screens/restore/wallet_restore_choose_derivation.dart create mode 100644 lib/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart create mode 100644 lib/view_model/nano_account_list/nano_account_list_view_model.dart create mode 100644 lib/view_model/node_list/pow_node_list_view_model.dart create mode 100644 lib/view_model/wallet_restore_choose_derivation_view_model.dart mode change 100644 => 100755 model_generator.sh diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 0f2dfc25b..af03c5e30 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -92,6 +92,7 @@ jobs: cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs - name: Add secrets diff --git a/.gitignore b/.gitignore index 09583004b..e8fb0048c 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart lib/monero/monero.dart lib/haven/haven.dart lib/ethereum/ethereum.dart +lib/nano/nano.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png diff --git a/assets/nano_node_list.yml b/assets/nano_node_list.yml new file mode 100644 index 000000000..63b4baec1 --- /dev/null +++ b/assets/nano_node_list.yml @@ -0,0 +1,6 @@ +- + uri: rpc.nano.to + useSSL: true + is_default: true +- + uri: node.perish.co:9076 \ No newline at end of file diff --git a/assets/nano_pow_node_list.yml b/assets/nano_pow_node_list.yml new file mode 100644 index 000000000..b90845034 --- /dev/null +++ b/assets/nano_pow_node_list.yml @@ -0,0 +1,9 @@ +- + uri: rpc.nano.to + useSSL: true + is_default: true +- + uri: workers.perish.co +- + uri: worker.nanoriver.cc + useSSL: true \ No newline at end of file diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet_android.sh old mode 100644 new mode 100755 index b80ebc46e..792159f29 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet_android.sh @@ -7,4 +7,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build - cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/cw_bitcoin/lib/bitcoin_mnemonic.dart b/cw_bitcoin/lib/bitcoin_mnemonic.dart index f4ebd7e5d..9163fcb11 100644 --- a/cw_bitcoin/lib/bitcoin_mnemonic.dart +++ b/cw_bitcoin/lib/bitcoin_mnemonic.dart @@ -2297,4 +2297,4 @@ final englishWordlist = [ 'zero', 'zone', 'zoo' -]; +]; \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index c4675df1c..2c66d02fe 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -89,4 +89,4 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { initialRegularAddressIndex: snp.regularAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex); } -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 82173b2d2..37b272a1b 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -20,4 +20,4 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { : super(name: name, password: password, walletInfo: walletInfo); final String wif; -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index bfadaf2a3..3a97e0682 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -100,4 +100,4 @@ class BitcoinWalletService extends WalletService< await wallet.init(); return wallet; } -} +} \ No newline at end of file diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 6db0c23f2..def991ebe 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -56,4 +56,4 @@ class ElectrumWallletSnapshot { regularAddressIndex: regularAddressIndex, changeAddressIndex: changeAddressIndex); } -} +} \ No newline at end of file diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 86ea3f214..f6ffcdc8b 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -90,6 +90,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.zrx, CryptoCurrency.dydx, CryptoCurrency.steth, + CryptoCurrency.banano, ]; static const havenCurrencies = [ @@ -119,7 +120,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const eos = CryptoCurrency(title: 'EOS', fullName: 'EOS', raw: 7, name: 'eos', iconPath: 'assets/images/eos_icon.png'); static const eth = CryptoCurrency(title: 'ETH', fullName: 'Ethereum', raw: 8, name: 'eth', iconPath: 'assets/images/eth_icon.png'); static const ltc = CryptoCurrency(title: 'LTC', fullName: 'Litecoin', raw: 9, name: 'ltc', iconPath: 'assets/images/litecoin-ltc_icon.png'); - static const nano = CryptoCurrency(title: 'NANO', raw: 10, name: 'nano', iconPath: 'assets/images/nano.png'); + static const nano = CryptoCurrency(title: 'XNO', raw: 10, fullName: 'Nano', name: 'xno', iconPath: 'assets/images/nano_icon.png'); static const trx = CryptoCurrency(title: 'TRX', fullName: 'TRON', raw: 11, name: 'trx', iconPath: 'assets/images/trx_icon.png'); static const usdt = CryptoCurrency(title: 'USDT', tag: 'OMNI', fullName: 'USDT Tether', raw: 12, name: 'usdt', iconPath: 'assets/images/usdt_icon.png'); static const usdterc20 = CryptoCurrency(title: 'USDT', tag: 'ETH', fullName: 'USDT Tether', raw: 13, name: 'usdterc20', iconPath: 'assets/images/usdterc20_icon.png'); @@ -198,6 +199,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const zrx = CryptoCurrency(title: 'ZRX', tag: 'ETH', fullName: '0x Protocol', raw: 83, name: 'zrx', iconPath: 'assets/images/zrx_icon.png'); static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png'); static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png'); + static const banano = CryptoCurrency(title: 'BAN', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png'); static final Map _rawCurrencyMap = diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 8ac8c1fc6..2db858b30 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -13,6 +13,10 @@ CryptoCurrency currencyForWalletType(WalletType type) { return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.nano: + return CryptoCurrency.nano; + case WalletType.banano: + return CryptoCurrency.banano; default: throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); } diff --git a/cw_core/lib/hive_type_ids.dart b/cw_core/lib/hive_type_ids.dart index 950f39e1f..4d4d1a6a8 100644 --- a/cw_core/lib/hive_type_ids.dart +++ b/cw_core/lib/hive_type_ids.dart @@ -1,4 +1,4 @@ -const CONTACT_TYPE_ID = 0; +const CONTACT_TYPE_ID = 0; const NODE_TYPE_ID = 1; const TRANSACTION_TYPE_ID = 2; const TRADE_TYPE_ID = 3; @@ -11,3 +11,6 @@ const UNSPENT_COINS_INFO_TYPE_ID = 9; const ANONPAY_INVOICE_INFO_TYPE_ID = 10; const ADDRESS_INFO_TYPE_ID = 11; const ERC20_TOKEN_TYPE_ID = 12; +const NANO_ACCOUNT_TYPE_ID = 13; +const POW_NODE_TYPE_ID = 14; +const DERIVATION_TYPE_TYPE_ID = 15; \ No newline at end of file diff --git a/cw_core/lib/nano_account.dart b/cw_core/lib/nano_account.dart new file mode 100644 index 000000000..91df62e75 --- /dev/null +++ b/cw_core/lib/nano_account.dart @@ -0,0 +1,23 @@ +import 'package:cw_core/hive_type_ids.dart'; +import 'package:hive/hive.dart'; + +part 'nano_account.g.dart'; + +@HiveType(typeId: NanoAccount.typeId) +class NanoAccount extends HiveObject { + NanoAccount({required this.label, required this.id, this.balance, this.isSelected = false}); + + static const typeId = NANO_ACCOUNT_TYPE_ID; + + @HiveField(0) + String label; + + @HiveField(1) + final int id; + + @HiveField(2) + bool isSelected; + + @HiveField(3) + String? balance; +} diff --git a/cw_core/lib/nano_account_info_response.dart b/cw_core/lib/nano_account_info_response.dart new file mode 100644 index 000000000..319bbb861 --- /dev/null +++ b/cw_core/lib/nano_account_info_response.dart @@ -0,0 +1,23 @@ +class AccountInfoResponse { + String frontier; + int confirmationHeight; + String balance; + String representative; + String? address; + + AccountInfoResponse({ + required this.frontier, + required this.balance, + required this.representative, + required this.confirmationHeight, + }); + + factory AccountInfoResponse.fromJson(Map json) { + return AccountInfoResponse( + frontier: json['frontier'] as String, + representative: json['representative'] as String, + balance: json['balance'] as String, + confirmationHeight: int.parse(json['confirmation_height'] as String), + ); + } +} diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 59a1450f6..a07030d64 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -9,19 +9,19 @@ import 'package:http/io_client.dart' as ioc; part 'node.g.dart'; -Uri createUriFromElectrumAddress(String address) => - Uri.tryParse('tcp://$address')!; +Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!; @HiveType(typeId: Node.typeId) class Node extends HiveObject with Keyable { - Node( - {this.login, - this.password, - this.useSSL, - this.trusted = false, - this.socksProxyAddress, - String? uri, - WalletType? type,}) { + Node({ + this.login, + this.password, + this.useSSL, + this.trusted = false, + this.socksProxyAddress, + String? uri, + WalletType? type, + }) { if (uri != null) { uriRaw = uri; } @@ -78,6 +78,13 @@ class Node extends HiveObject with Keyable { return Uri.http(uriRaw, ''); case WalletType.ethereum: return Uri.https(uriRaw, ''); + case WalletType.nano: + case WalletType.banano: + if (isSSL) { + return Uri.https(uriRaw, ''); + } else { + return Uri.http(uriRaw, ''); + } default: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -86,13 +93,13 @@ class Node extends HiveObject with Keyable { @override bool operator ==(other) => other is Node && - (other.uriRaw == uriRaw && - other.login == login && - other.password == password && - other.typeRaw == typeRaw && - other.useSSL == useSSL && - other.trusted == trusted && - other.socksProxyAddress == socksProxyAddress); + (other.uriRaw == uriRaw && + other.login == login && + other.password == password && + other.typeRaw == typeRaw && + other.useSSL == useSSL && + other.trusted == trusted && + other.socksProxyAddress == socksProxyAddress); @override int get hashCode => @@ -120,7 +127,9 @@ class Node extends HiveObject with Keyable { try { switch (type) { case WalletType.monero: - return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode(); + return useSocksProxy + ? requestNodeWithProxy(socksProxyAddress ?? '') + : requestMoneroNode(); case WalletType.bitcoin: return requestElectrumServer(); case WalletType.litecoin: @@ -129,6 +138,9 @@ class Node extends HiveObject with Keyable { return requestMoneroNode(); case WalletType.ethereum: return requestElectrumServer(); + case WalletType.nano: + case WalletType.banano: + return requestNanoNode(); default: return false; } @@ -141,27 +153,23 @@ class Node extends HiveObject with Keyable { final path = '/json_rpc'; final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final realm = 'monero-rpc'; - final body = { - 'jsonrpc': '2.0', - 'id': '0', - 'method': 'get_info' - }; + final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'}; try { final authenticatingClient = HttpClient(); authenticatingClient.addCredentials( - rpcUri, - realm, - HttpClientDigestCredentials(login ?? '', password ?? ''), + rpcUri, + realm, + HttpClientDigestCredentials(login ?? '', password ?? ''), ); final http.Client client = ioc.IOClient(authenticatingClient); final response = await client.post( - rpcUri, - headers: {'Content-Type': 'application/json'}, - body: json.encode(body), + rpcUri, + headers: {'Content-Type': 'application/json'}, + body: json.encode(body), ); client.close(); @@ -173,8 +181,24 @@ class Node extends HiveObject with Keyable { } } - Future requestNodeWithProxy(String proxy) async { + Future requestNanoNode() async { + http.Response response = await http.post( + uri, + headers: {'Content-type': 'application/json'}, + body: json.encode( + { + "action": "block_count", + }, + ), + ); + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } + Future requestNodeWithProxy(String proxy) async { if (proxy.isEmpty || !proxy.contains(':')) { return false; } diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index f8db67b24..d15ea42cd 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -13,9 +13,7 @@ import 'package:cw_core/sync_status.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/wallet_type.dart'; -abstract class WalletBase< - BalanceType extends Balance, - HistoryType extends TransactionHistoryBase, +abstract class WalletBase { WalletBase(this.walletInfo); @@ -58,6 +56,9 @@ abstract class WalletBase< Future connectToNode({required Node node}); + // there is a default definition here because only coins with a pow node (nano based) need to override this + Future connectToPowNode({required Node node}) async {} + Future startSync(); Future createTransaction(Object credentials); diff --git a/cw_core/lib/wallet_credentials.dart b/cw_core/lib/wallet_credentials.dart index e028232e8..0cdf892bd 100644 --- a/cw_core/lib/wallet_credentials.dart +++ b/cw_core/lib/wallet_credentials.dart @@ -5,10 +5,15 @@ abstract class WalletCredentials { required this.name, this.height, this.walletInfo, - this.password}); + this.password, + this.derivationType, + this.derivationPath, + }); final String name; final int? height; String? password; + DerivationType? derivationType; + String? derivationPath; WalletInfo? walletInfo; } diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index 210adb9a4..c4ccea00a 100644 --- a/cw_core/lib/wallet_info.dart +++ b/cw_core/lib/wallet_info.dart @@ -6,29 +6,92 @@ import 'package:hive/hive.dart'; part 'wallet_info.g.dart'; +@HiveType(typeId: DERIVATION_TYPE_TYPE_ID) +enum DerivationType { + @HiveField(0) + unknown, + @HiveField(1) + def, // default is a reserved word + @HiveField(2) + nano, + @HiveField(3) + bip39, + @HiveField(4) + electrum1, + @HiveField(5) + electrum2, +} + +class DerivationInfo { + DerivationInfo({ + required this.derivationType, + this.derivationPath, + this.balance = "", + this.address = "", + this.height = 0, + this.script_type, + this.description, + }); + + String balance; + String address; + int height; + final DerivationType derivationType; + final String? derivationPath; + final String? script_type; + final String? description; +} + @HiveType(typeId: WalletInfo.typeId) class WalletInfo extends HiveObject { - WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight, - this.timestamp, this.dirPath, this.path, this.address, this.yatEid, - this.yatLastUsedAddressRaw, this.showIntroCakePayCard) + WalletInfo( + this.id, + this.name, + this.type, + this.isRecovery, + this.restoreHeight, + this.timestamp, + this.dirPath, + this.path, + this.address, + this.yatEid, + this.yatLastUsedAddressRaw, + this.showIntroCakePayCard, + this.derivationType, + this.derivationPath) : _yatLastUsedAddressController = StreamController.broadcast(); - factory WalletInfo.external( - {required String id, - required String name, - required WalletType type, - required bool isRecovery, - required int restoreHeight, - required DateTime date, - required String dirPath, - required String path, - required String address, - bool? showIntroCakePayCard, - String yatEid ='', - String yatLastUsedAddressRaw = ''}) { - return WalletInfo(id, name, type, isRecovery, restoreHeight, - date.millisecondsSinceEpoch, dirPath, path, address, - yatEid, yatLastUsedAddressRaw, showIntroCakePayCard); + factory WalletInfo.external({ + required String id, + required String name, + required WalletType type, + required bool isRecovery, + required int restoreHeight, + required DateTime date, + required String dirPath, + required String path, + required String address, + bool? showIntroCakePayCard, + String yatEid = '', + String yatLastUsedAddressRaw = '', + DerivationType? derivationType, + String? derivationPath, + }) { + return WalletInfo( + id, + name, + type, + isRecovery, + restoreHeight, + date.millisecondsSinceEpoch, + dirPath, + path, + address, + yatEid, + yatLastUsedAddressRaw, + showIntroCakePayCard, + derivationType, + derivationPath); } static const typeId = WALLET_INFO_TYPE_ID; @@ -79,6 +142,12 @@ class WalletInfo extends HiveObject { @HiveField(15) List? usedAddresses; + @HiveField(16) + DerivationType? derivationType; + + @HiveField(17) + String? derivationPath; + String get yatLastUsedAddress => yatLastUsedAddressRaw ?? ''; set yatLastUsedAddress(String address) { @@ -89,7 +158,7 @@ class WalletInfo extends HiveObject { String get yatEmojiId => yatEid ?? ''; bool get isShowIntroCakePayCard { - if(showIntroCakePayCard == null) { + if (showIntroCakePayCard == null) { return type != WalletType.haven; } return showIntroCakePayCard!; diff --git a/cw_core/lib/wallet_service.dart b/cw_core/lib/wallet_service.dart index f95bc1a44..f6d0ca192 100644 --- a/cw_core/lib/wallet_service.dart +++ b/cw_core/lib/wallet_service.dart @@ -1,9 +1,11 @@ +import 'package:cw_core/node.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_type.dart'; -abstract class WalletService { +abstract class WalletService { WalletType getType(); Future create(N credentials); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 62c2ad410..0125facaf 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -10,6 +10,8 @@ const walletTypes = [ WalletType.litecoin, WalletType.haven, WalletType.ethereum, + WalletType.nano, + WalletType.banano, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -31,6 +33,12 @@ enum WalletType { @HiveField(5) ethereum, + + @HiveField(6) + nano, + + @HiveField(7) + banano, } int serializeToInt(WalletType type) { @@ -45,6 +53,10 @@ int serializeToInt(WalletType type) { return 3; case WalletType.ethereum: return 4; + case WalletType.nano: + return 5; + case WalletType.banano: + return 6; default: return -1; } @@ -62,6 +74,10 @@ WalletType deserializeFromInt(int raw) { return WalletType.haven; case 4: return WalletType.ethereum; + case 5: + return WalletType.nano; + case 6: + return WalletType.banano; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -79,6 +95,10 @@ String walletTypeToString(WalletType type) { return 'Haven'; case WalletType.ethereum: return 'Ethereum'; + case WalletType.nano: + return 'Nano'; + case WalletType.banano: + return 'Banano'; default: return ''; } @@ -96,6 +116,10 @@ String walletTypeToDisplayName(WalletType type) { return 'Haven (XHV)'; case WalletType.ethereum: return 'Ethereum (ETH)'; + case WalletType.nano: + return 'Nano (XNO)'; + case WalletType.banano: + return 'Banano (BAN)'; default: return ''; } @@ -113,6 +137,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.nano: + return CryptoCurrency.nano; + case WalletType.banano: + return CryptoCurrency.banano; default: throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 1cb01fac7..e04282fe8 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -234,7 +234,6 @@ extern "C" } void setUnlocked(bool unlocked); - }; Monero::Coins *m_coins; @@ -568,7 +567,7 @@ extern "C" _preferred_inputs.insert(std::string(*preferred_inputs)); preferred_inputs++; } - + auto priority = static_cast(priority_raw); std::string _payment_id; Monero::PendingTransaction *transaction; diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 9d1a0b3e4..9ee4cf374 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -57,7 +57,7 @@ class MoneroWalletService extends WalletService< final Box walletInfoSource; final Box unspentCoinsInfoSource; - + static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync(); diff --git a/cw_nano/lib/banano_balance.dart b/cw_nano/lib/banano_balance.dart new file mode 100644 index 000000000..b85609b60 --- /dev/null +++ b/cw_nano/lib/banano_balance.dart @@ -0,0 +1,20 @@ +import 'package:cw_core/balance.dart'; +import 'package:cw_nano/nano_util.dart'; + +class BananoBalance extends Balance { + final BigInt currentBalance; + final BigInt receivableBalance; + + BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { + } + + @override + String get formattedAvailableBalance { + return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano); + } + + @override + String get formattedAdditionalBalance { + return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano); + } +} diff --git a/cw_nano/lib/cw_nano.dart b/cw_nano/lib/cw_nano.dart new file mode 100644 index 000000000..08e23a232 --- /dev/null +++ b/cw_nano/lib/cw_nano.dart @@ -0,0 +1,7 @@ +library cw_nano; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_nano/lib/file.dart b/cw_nano/lib/file.dart new file mode 100644 index 000000000..8fd236ec3 --- /dev/null +++ b/cw_nano/lib/file.dart @@ -0,0 +1,39 @@ +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 writeData( + {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 read({required String path, required String password}) async { + final file = File(path); + + if (!file.existsSync()) { + file.createSync(); + } + + final encrypted = file.readAsStringSync(); + + return decode(password: password, data: encrypted); +} diff --git a/cw_nano/lib/nano_account_list.dart b/cw_nano/lib/nano_account_list.dart new file mode 100644 index 000000000..7207eafe1 --- /dev/null +++ b/cw_nano/lib/nano_account_list.dart @@ -0,0 +1,69 @@ +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/nano_account.dart'; +import 'package:mobx/mobx.dart'; +import 'package:hive/hive.dart'; + +part 'nano_account_list.g.dart'; + +class NanoAccountList = NanoAccountListBase with _$NanoAccountList; + +abstract class NanoAccountListBase with Store { + NanoAccountListBase(this.address) + : accounts = ObservableList(), + _isRefreshing = false, + _isUpdating = false { + refresh(); + } + + @observable + ObservableList accounts; + bool _isRefreshing; + bool _isUpdating; + + String address; + + Future update(String? address) async { + if (_isUpdating) { + return; + } + + try { + _isUpdating = true; + + final accounts = await getAll(address: address ?? this.address); + + if (accounts.isNotEmpty) { + this.accounts.clear(); + this.accounts.addAll(accounts); + } + + _isUpdating = false; + } catch (e) { + _isUpdating = false; + rethrow; + } + } + + Future> getAll({String? address}) async { + final box = await CakeHive.openBox(address ?? this.address); + + // get all accounts in box: + return box.values.toList(); + } + + Future addAccount({required String label}) async { + final box = await CakeHive.openBox(address); + final account = NanoAccount(id: box.length, label: label, balance: "0.00", isSelected: false); + await box.add(account); + await account.save(); + } + + Future setLabelAccount({required int accountIndex, required String label}) async { + final box = await CakeHive.openBox(address); + final account = box.getAt(accountIndex); + account!.label = label; + await account.save(); + } + + void refresh() {} +} diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart new file mode 100644 index 000000000..dbb39d2a3 --- /dev/null +++ b/cw_nano/lib/nano_balance.dart @@ -0,0 +1,34 @@ +import 'package:cw_core/balance.dart'; +import 'package:cw_nano/nano_util.dart'; + +BigInt stringAmountToBigInt(String amount) { + return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano)); +} + +class NanoBalance extends Balance { + final BigInt currentBalance; + final BigInt receivableBalance; + late String formattedCurrentBalance; + late String formattedReceivableBalance; + + NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { + this.formattedCurrentBalance = ""; + this.formattedReceivableBalance = ""; + } + + NanoBalance.fromString( + {required this.formattedCurrentBalance, required this.formattedReceivableBalance}) + : currentBalance = stringAmountToBigInt(formattedCurrentBalance), + receivableBalance = stringAmountToBigInt(formattedReceivableBalance), + super(0, 0); + + @override + String get formattedAvailableBalance { + return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano); + } + + @override + String get formattedAdditionalBalance { + return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano); + } +} diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart new file mode 100644 index 000000000..29f47cc2d --- /dev/null +++ b/cw_nano/lib/nano_client.dart @@ -0,0 +1,424 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:cw_core/nano_account_info_response.dart'; +import 'package:cw_nano/nano_balance.dart'; +import 'package:cw_nano/nano_transaction_model.dart'; +import 'package:cw_nano/nano_util.dart'; +import 'package:http/http.dart' as http; +import 'package:nanodart/nanodart.dart'; +import 'package:cw_core/node.dart'; + +class NanoClient { + static const String DEFAULT_REPRESENTATIVE = + "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579"; + + Node? _node; + Node? _powNode; + + bool connect(Node node) { + try { + _node = node; + return true; + } catch (e) { + return false; + } + } + + bool connectPow(Node node) { + try { + _powNode = node; + return true; + } catch (e) { + return false; + } + } + + Future getBalance(String address) async { + final response = await http.post( + _node!.uri, + headers: {"Content-Type": "application/json"}, + body: jsonEncode( + { + "action": "account_balance", + "account": address, + }, + ), + ); + final data = await jsonDecode(response.body); + final String currentBalance = data["balance"] as String; + final String receivableBalance = data["receivable"] as String; + final BigInt cur = BigInt.parse(currentBalance); + final BigInt rec = BigInt.parse(receivableBalance); + return NanoBalance(currentBalance: cur, receivableBalance: rec); + } + + Future getAccountInfo(String address) async { + try { + final response = await http.post( + _node!.uri, + headers: {"Content-Type": "application/json"}, + body: jsonEncode( + { + "action": "account_info", + "representative": "true", + "account": address, + }, + ), + ); + final data = await jsonDecode(response.body); + return AccountInfoResponse.fromJson(data as Map); + } catch (e) { + print("error while getting account info"); + return null; + } + } + + Future changeRep({ + required String privateKey, + required String repAddress, + required String ourAddress, + }) async { + try { + AccountInfoResponse? accountInfo = await getAccountInfo(ourAddress); + + if (accountInfo == null) { + throw Exception("error while getting account info"); + } + + // construct the change block: + Map changeBlock = { + "type": "state", + "account": ourAddress, + "previous": accountInfo.frontier, + "representative": repAddress, + "balance": accountInfo.balance, + "link": "0000000000000000000000000000000000000000000000000000000000000000", + "link_as_account": "nano_1111111111111111111111111111111111111111111111111111hifc8npp", + }; + + // sign the change block: + final String hash = NanoBlocks.computeStateHash( + NanoAccountType.NANO, + changeBlock["account"]!, + changeBlock["previous"]!, + changeBlock["representative"]!, + BigInt.parse(changeBlock["balance"]!), + changeBlock["link"]!, + ); + final String signature = NanoSignatures.signBlock(hash, privateKey); + + // get PoW for the send block: + final String work = await requestWork(accountInfo.frontier); + + changeBlock["signature"] = signature; + changeBlock["work"] = work; + + return await processBlock(changeBlock, "change"); + } catch (e) { + throw Exception("error while changing representative"); + } + } + + Future requestWork(String hash) async { + final response = await http.post( + _powNode!.uri, + headers: {'Content-type': 'application/json'}, + body: json.encode( + { + "action": "work_generate", + "hash": hash, + }, + ), + ); + if (response.statusCode == 200) { + final Map decoded = json.decode(response.body) as Map; + if (decoded.containsKey("error")) { + throw Exception("Received error ${decoded["error"]}"); + } + return decoded["work"] as String; + } else { + throw Exception("Received work error ${response.body}"); + } + } + + Future send({ + required String privateKey, + required String amountRaw, + required String destinationAddress, + }) async { + final Map sendBlock = await constructSendBlock( + privateKey: privateKey, + amountRaw: amountRaw, + destinationAddress: destinationAddress, + ); + + return await processBlock(sendBlock, "send"); + } + + Future processBlock(Map block, String subtype) async { + final headers = {"Content-Type": "application/json"}; + final processBody = jsonEncode({ + "action": "process", + "json_block": "true", + "subtype": subtype, + "block": block, + }); + + final processResponse = await http.post( + _node!.uri, + headers: headers, + body: processBody, + ); + + final Map decoded = json.decode(processResponse.body) as Map; + if (decoded.containsKey("error")) { + throw Exception("Received error ${decoded["error"]}"); + } + + // return the hash of the transaction: + return decoded["hash"].toString(); + } + + Future> constructSendBlock({ + required String privateKey, + required String amountRaw, + required String destinationAddress, + BigInt? balanceAfterTx, + String? previousHash, + }) async { + try { + // our address: + final String publicAddress = NanoUtil.privateKeyToAddress(privateKey); + + // first get the current account balance: + if (balanceAfterTx == null) { + final BigInt currentBalance = (await getBalance(publicAddress)).currentBalance; + final BigInt txAmount = BigInt.parse(amountRaw); + balanceAfterTx = currentBalance - txAmount; + } + + // get the account info (we need the frontier and representative): + AccountInfoResponse? infoResponse = await getAccountInfo(publicAddress); + if (infoResponse == null) { + throw Exception( + "error while getting account info! (we probably don't have an open account yet)"); + } + + String frontier = infoResponse.frontier; + // override if provided: + if (previousHash != null) { + frontier = previousHash; + } + final String representative = infoResponse.representative; + // link = destination address: + final String link = NanoAccounts.extractPublicKey(destinationAddress); + final String linkAsAccount = destinationAddress; + + // construct the send block: + Map sendBlock = { + "type": "state", + "account": publicAddress, + "previous": frontier, + "representative": representative, + "balance": balanceAfterTx.toString(), + "link": link, + }; + + // sign the send block: + final String hash = NanoBlocks.computeStateHash( + NanoAccountType.NANO, + sendBlock["account"]!, + sendBlock["previous"]!, + sendBlock["representative"]!, + BigInt.parse(sendBlock["balance"]!), + sendBlock["link"]!, + ); + final String signature = NanoSignatures.signBlock(hash, privateKey); + + // get PoW for the send block: + final String work = await requestWork(frontier); + + sendBlock["link_as_account"] = linkAsAccount; + sendBlock["signature"] = signature; + sendBlock["work"] = work; + + // ready to post send block: + return sendBlock; + } catch (e) { + print(e); + rethrow; + } + } + + Future receiveBlock({ + required String blockHash, + required String source, + required String amountRaw, + required String destinationAddress, + required String privateKey, + }) async { + bool openBlock = false; + + final headers = { + "Content-Type": "application/json", + }; + + // first check if the account is open: + // get the account info (we need the frontier and representative): + AccountInfoResponse? infoData = await getAccountInfo(destinationAddress); + String? frontier; + String? representative; + + if (infoData == null) { + // account is not open yet, we need to create an open block: + openBlock = true; + // we don't have a representative set yet: + representative = DEFAULT_REPRESENTATIVE; + // we don't have a frontier yet: + frontier = "0000000000000000000000000000000000000000000000000000000000000000"; + } else { + frontier = infoData.frontier; + representative = infoData.representative; + } + + // first get the account balance: + final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance; + final BigInt txAmount = BigInt.parse(amountRaw); + final BigInt balanceAfterTx = currentBalance + txAmount; + + // link = send block hash: + final String link = blockHash; + // this "linkAsAccount" is meaningless: + final String linkAsAccount = NanoAccounts.createAccount(NanoAccountType.NANO, blockHash); + + // construct the receive block: + Map receiveBlock = { + "type": "state", + "account": destinationAddress, + "previous": frontier, + "representative": representative, + "balance": balanceAfterTx.toString(), + "link": link, + "link_as_account": linkAsAccount, + }; + + // sign the receive block: + final String hash = NanoBlocks.computeStateHash( + NanoAccountType.NANO, + receiveBlock["account"]!, + receiveBlock["previous"]!, + receiveBlock["representative"]!, + BigInt.parse(receiveBlock["balance"]!), + receiveBlock["link"]!, + ); + final String signature = NanoSignatures.signBlock(hash, privateKey); + + // get PoW for the receive block: + String? work; + if (openBlock) { + work = await requestWork(NanoAccounts.extractPublicKey(destinationAddress)); + } else { + work = await requestWork(frontier); + } + receiveBlock["link_as_account"] = linkAsAccount; + receiveBlock["signature"] = signature; + receiveBlock["work"] = work; + + // process the receive block: + + final processBody = jsonEncode({ + "action": "process", + "json_block": "true", + "subtype": "receive", + "block": receiveBlock, + }); + final processResponse = await http.post( + _node!.uri, + headers: headers, + body: processBody, + ); + + final Map decoded = json.decode(processResponse.body) as Map; + if (decoded.containsKey("error")) { + throw Exception("Received error ${decoded["error"]}"); + } + } + + // returns the number of blocks received: + Future confirmAllReceivable({ + required String destinationAddress, + required String privateKey, + }) async { + final receivableResponse = await http.post(_node!.uri, + headers: {"Content-Type": "application/json"}, + body: jsonEncode({ + "action": "receivable", + "account": destinationAddress, + "count": "-1", + "source": true, + })); + + final receivableData = await jsonDecode(receivableResponse.body); + if (receivableData["blocks"] == "" || receivableData["blocks"] == null) { + return 0; + } + + dynamic blocks; + if (receivableData["blocks"] is List) { + var listBlocks = receivableData["blocks"] as List; + if (listBlocks.isEmpty) { + return 0; + } + blocks = {for (var block in listBlocks) block['hash']: block}; + } else { + blocks = receivableData["blocks"] as Map; + } + + blocks = blocks as Map; + + // confirm all receivable blocks: + for (final blockHash in blocks.keys) { + final block = blocks[blockHash]; + final String amountRaw = block["amount"] as String; + final String source = block["source"] as String; + await receiveBlock( + blockHash: blockHash, + source: source, + amountRaw: amountRaw, + privateKey: privateKey, + destinationAddress: destinationAddress, + ); + // a bit of a hack: + await Future.delayed(const Duration(seconds: 2)); + } + + return blocks.keys.length; + } + + void stop() {} + + Future> fetchTransactions(String address) async { + try { + final response = await http.post(_node!.uri, + headers: {"Content-Type": "application/json"}, + body: jsonEncode({ + "action": "account_history", + "account": address, + "count": "250", // TODO: pick a number + // "raw": true, + })); + final data = await jsonDecode(response.body); + final transactions = data["history"] is List ? data["history"] as List : []; + + // Map the transactions list to NanoTransactionModel using the factory + // reversed so that the DateTime is correct when local_timestamp is absent + return transactions.reversed + .map((transaction) => NanoTransactionModel.fromJson(transaction)) + .toList(); + } catch (e) { + print(e); + return []; + } + } +} diff --git a/cw_nano/lib/nano_mnemonic.dart b/cw_nano/lib/nano_mnemonic.dart new file mode 100644 index 000000000..2a06fe515 --- /dev/null +++ b/cw_nano/lib/nano_mnemonic.dart @@ -0,0 +1,2088 @@ +import 'package:bip39/bip39.dart' as bip39; +import 'package:nanodart/nanodart.dart'; + +class NanoMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Nano mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} + +class NanoMnemomics { + /// Converts a nano seed to a 24-word mnemonic word list + static List seedToMnemonic(String seed) { + if (!NanoSeeds.isValidSeed(seed)) { + throw Exception('Invalid Seed'); + } + String words = bip39.entropyToMnemonic(seed); + return words.split(' '); + } + + /// Convert a 24-word mnemonic word list to a nano seed + static String mnemonicListToSeed(List words) { + if (words.length != 24) { + throw Exception('Expected a 24-word list, got a ${words.length} list'); + } + return bip39.mnemonicToEntropy(words.join(' ')).toUpperCase(); + } + + /// Validate a mnemonic word list + static bool validateMnemonic(List words) { + return bip39.validateMnemonic(words.join(' ')); + } + + /// Validate a specific menmonic word + static bool isValidWord(String word) { + return WORDLIST.contains(word); + } + + static const WORDLIST = [ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo" + ]; +} diff --git a/cw_nano/lib/nano_transaction_credentials.dart b/cw_nano/lib/nano_transaction_credentials.dart new file mode 100644 index 000000000..6ede488a1 --- /dev/null +++ b/cw_nano/lib/nano_transaction_credentials.dart @@ -0,0 +1,7 @@ +import 'package:cw_core/output_info.dart'; + +class NanoTransactionCredentials { + NanoTransactionCredentials(this.outputs); + + final List outputs; +} diff --git a/cw_nano/lib/nano_transaction_history.dart b/cw_nano/lib/nano_transaction_history.dart new file mode 100644 index 000000000..dadd353c4 --- /dev/null +++ b/cw_nano/lib/nano_transaction_history.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; +import 'dart:core'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_nano/file.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_nano/nano_transaction_info.dart'; + +part 'nano_transaction_history.g.dart'; +const transactionsHistoryFileName = 'transactions.json'; + +class NanoTransactionHistory = NanoTransactionHistoryBase with _$NanoTransactionHistory; + +abstract class NanoTransactionHistoryBase + extends TransactionHistoryBase with Store { + NanoTransactionHistoryBase({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) { + print('Error while save nano transaction history: ${e.toString()}'); + } + } + + @override + void addOne(NanoTransactionInfo 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); + 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 = NanoTransactionInfo.fromJson(val); + _update(tx); + } + }); + } catch (e) { + print(e); + } + } + + void _update(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction; +} diff --git a/cw_nano/lib/nano_transaction_info.dart b/cw_nano/lib/nano_transaction_info.dart new file mode 100644 index 000000000..8958086dd --- /dev/null +++ b/cw_nano/lib/nano_transaction_info.dart @@ -0,0 +1,70 @@ +import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_nano/nano_util.dart'; + +class NanoTransactionInfo extends TransactionInfo { + NanoTransactionInfo({ + required this.id, + required this.height, + required this.amountRaw, + this.tokenSymbol = "XNO", + required this.direction, + required this.confirmed, + required this.date, + required this.confirmations, + }) : this.amount = amountRaw.toInt(); + + final String id; + final int height; + final int amount; + final BigInt amountRaw; + final TransactionDirection direction; + final DateTime date; + final bool confirmed; + final int confirmations; + final String tokenSymbol; + String? _fiatAmount; + + bool get isPending => !this.confirmed; + + @override + String amountFormatted() { + final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano); + final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano); + return "$acc$amt $tokenSymbol"; + } + + @override + String fiatAmount() => _fiatAmount ?? ''; + + @override + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + + @override + String feeFormatted() => "0 XNO"; + + factory NanoTransactionInfo.fromJson(Map data) { + return NanoTransactionInfo( + id: data['id'] as String, + height: data['height'] as int, + amountRaw: BigInt.parse(data['amountRaw'] as String), + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + confirmed: data['confirmed'] as bool, + confirmations: data['confirmations'] as int, + tokenSymbol: data['tokenSymbol'] as String, + ); + } + + Map toJson() => { + 'id': id, + 'height': height, + 'amountRaw': amountRaw.toString(), + 'direction': direction.index, + 'date': date.millisecondsSinceEpoch, + 'confirmed': confirmed, + 'confirmations': confirmations, + 'tokenSymbol': tokenSymbol, + }; +} diff --git a/cw_nano/lib/nano_transaction_model.dart b/cw_nano/lib/nano_transaction_model.dart new file mode 100644 index 000000000..e9c59da5a --- /dev/null +++ b/cw_nano/lib/nano_transaction_model.dart @@ -0,0 +1,39 @@ +class NanoTransactionModel { + final DateTime? date; + final String hash; + final bool confirmed; + final String account; + final BigInt amount; + final int height; + final String type; + + NanoTransactionModel({ + this.date, + required this.hash, + required this.height, + required this.amount, + required this.confirmed, + required this.type, + required this.account, + }); + + factory NanoTransactionModel.fromJson(dynamic json) { + DateTime? localTimestamp; + try { + localTimestamp = DateTime.fromMillisecondsSinceEpoch( + int.parse(json["local_timestamp"] as String) * 1000); + } catch (e) { + localTimestamp = DateTime.now(); + } + + return NanoTransactionModel( + date: localTimestamp, + hash: json["hash"] as String, + height: int.parse(json["height"] as String), + type: json["type"] as String, + amount: BigInt.parse(json["amount"] as String), + account: json["account"] as String, + confirmed: (json["confirmed"] as String) == "true", + ); + } +} diff --git a/cw_nano/lib/nano_util.dart b/cw_nano/lib/nano_util.dart new file mode 100644 index 000000000..13d6f5649 --- /dev/null +++ b/cw_nano/lib/nano_util.dart @@ -0,0 +1,193 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart'; +import "package:ed25519_hd_key/ed25519_hd_key.dart"; +import 'package:libcrypto/libcrypto.dart'; +import 'package:nanodart/nanodart.dart'; +import 'package:decimal/decimal.dart'; + +class NanoUtil { + // standard: + static String seedToPrivate(String seed, int index) { + return NanoKeys.seedToPrivate(seed, index); + } + + static String seedToAddress(String seed, int index) { + return NanoAccounts.createAccount( + NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index))); + } + + static String seedToMnemonic(String seed) { + return NanoMnemomics.seedToMnemonic(seed).join(" "); + } + + static Future mnemonicToSeed(String mnemonic) async { + return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' ')); + } + + static String privateKeyToPublic(String privateKey) { + // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!); + return NanoKeys.createPublicKey(privateKey); + } + + static String addressToPublicKey(String publicAddress) { + return NanoAccounts.extractPublicKey(publicAddress); + } + + // universal: + static String privateKeyToAddress(String privateKey) { + return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey)); + } + + static String publicKeyToAddress(String publicKey) { + return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey); + } + + // standard + hd: + static bool isValidSeed(String seed) { + // Ensure seed is 64 or 128 characters long + if (seed == null || (seed.length != 64 && seed.length != 128)) { + return false; + } + // Ensure seed only contains hex characters, 0-9;A-F + return NanoHelpers.isHexString(seed); + } + + // // hd: + static Future hdMnemonicListToSeed(List words) async { + // if (words.length != 24) { + // throw Exception('Expected a 24-word list, got a ${words.length} list'); + // } + final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic')); + final Pbkdf2 hasher = Pbkdf2(iterations: 2048); + final String seed = await hasher.sha512(words.join(' '), salt); + return seed; + } + + static Future hdSeedToPrivate(String seed, int index) async { + List seedBytes = hex.decode(seed); + KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes); + return hex.encode(data.key); + } + + static Future hdSeedToAddress(String seed, int index) async { + return NanoAccounts.createAccount( + NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index))); + } + + static Future uniSeedToAddress(String seed, int index, String type) { + if (type == "standard") { + return Future.value(seedToAddress(seed, index)); + } else if (type == "hd") { + return hdSeedToAddress(seed, index); + } else { + throw Exception('Unknown seed type'); + } + } + + static Future uniSeedToPrivate(String seed, int index, String type) { + if (type == "standard") { + return Future.value(seedToPrivate(seed, index)); + } else if (type == "hd") { + return hdSeedToPrivate(seed, index); + } else { + throw Exception('Unknown seed type'); + } + } + + static bool isValidBip39Seed(String seed) { + // Ensure seed is 128 characters long + if (seed.length != 128) { + return false; + } + // Ensure seed only contains hex characters, 0-9;A-F + return NanoHelpers.isHexString(seed); + } + + // number util: + + static const int maxDecimalDigits = 6; // Max digits after decimal + static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000"); + static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000"); + static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); + static BigInt rawPerXMR = BigInt.parse("1000000000000"); + static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); + // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000"); + + /// Convert raw to ban and return as BigDecimal + /// + /// @param raw 100000000000000000000000000000 + /// @return Decimal value 1.000000000000000000000000000000 + /// + static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) { + rawPerCur ??= rawPerNano; + final Decimal amount = Decimal.parse(raw.toString()); + final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal(); + return result; + } + + static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) { + Decimal bigger = input.shift(digits); + bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05 + bigger = bigger.shift(-digits); + return bigger.toString(); + } + + /// Return raw as a NANO amount. + /// + /// @param raw 100000000000000000000000000000 + /// @returns 1 + /// + static String getRawAsUsableString(String? raw, BigInt rawPerCur) { + final String res = + truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9); + + if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") { + return "0"; + } + + if (!res.contains(".")) { + return res; + } + + final String numAmount = res.split(".")[0]; + String decAmount = res.split(".")[1]; + + // truncate: + if (decAmount.length > maxDecimalDigits) { + decAmount = decAmount.substring(0, maxDecimalDigits); + // remove trailing zeros: + decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => ''); + if (decAmount.isEmpty) { + return numAmount; + } + } + + return "$numAmount.$decAmount"; + } + + static String getRawAccuracy(String? raw, BigInt rawPerCur) { + final String rawString = getRawAsUsableString(raw, rawPerCur); + final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString(); + + if (raw == null || raw.isEmpty || raw == "0") { + return ""; + } + + if (rawString != rawDecimalString) { + return "~"; + } + return ""; + } + + /// Return readable string amount as raw string + /// @param amount 1.01 + /// @returns 101000000000000000000000000000 + /// + static String getAmountAsRaw(String amount, BigInt rawPerCur) { + final Decimal asDecimal = Decimal.parse(amount); + final Decimal rawDecimal = Decimal.parse(rawPerCur.toString()); + return (asDecimal * rawDecimal).toString(); + } +} diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart new file mode 100644 index 000000000..da50f4ebb --- /dev/null +++ b/cw_nano/lib/nano_wallet.dart @@ -0,0 +1,437 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/nano_account_info_response.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_info.dart'; +import 'package:cw_nano/file.dart'; +import 'package:cw_core/nano_account.dart'; +import 'package:cw_nano/nano_balance.dart'; +import 'package:cw_nano/nano_client.dart'; +import 'package:cw_nano/nano_transaction_credentials.dart'; +import 'package:cw_nano/nano_transaction_history.dart'; +import 'package:cw_nano/nano_transaction_info.dart'; +import 'package:cw_nano/nano_util.dart'; +import 'package:cw_nano/nano_wallet_keys.dart'; +import 'package:cw_nano/pending_nano_transaction.dart'; +import 'package:mobx/mobx.dart'; +import 'dart:async'; +import 'package:cw_nano/nano_wallet_addresses.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:nanodart/nanodart.dart'; +import 'package:bip39/bip39.dart' as bip39; + +part 'nano_wallet.g.dart'; + +class NanoWallet = NanoWalletBase with _$NanoWallet; + +abstract class NanoWalletBase + extends WalletBase with Store { + NanoWalletBase({ + required WalletInfo walletInfo, + required String mnemonic, + required String password, + NanoBalance? initialBalance, + }) : syncStatus = NotConnectedSyncStatus(), + _password = password, + _mnemonic = mnemonic, + _derivationType = walletInfo.derivationType!, + _isTransactionUpdating = false, + _client = NanoClient(), + walletAddresses = NanoWalletAddresses(walletInfo), + balance = ObservableMap.of({ + CryptoCurrency.nano: initialBalance ?? + NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero) + }), + super(walletInfo) { + this.walletInfo = walletInfo; + transactionHistory = NanoTransactionHistory(walletInfo: walletInfo, password: password); + if (!CakeHive.isAdapterRegistered(NanoAccount.typeId)) { + CakeHive.registerAdapter(NanoAccountAdapter()); + } + } + + final String _mnemonic; + final String _password; + final DerivationType _derivationType; + + String? _privateKey; + String? _publicAddress; + String? _seedKey; + + String? _representativeAddress; + Timer? _receiveTimer; + + late final NanoClient _client; + bool _isTransactionUpdating; + + @override + NanoWalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + late ObservableMap balance; + + // initialize the different forms of private / public key we'll need: + Future init() async { + final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; + + // our "mnemonic" is actually a seedkey: + if (!_mnemonic.contains(' ')) { + _seedKey = _mnemonic; + } + + if (_seedKey == null) { + if (_derivationType == DerivationType.nano) { + _seedKey = bip39.mnemonicToEntropy(_mnemonic).toUpperCase(); + } else { + _seedKey = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' ')); + } + } + _privateKey = await NanoUtil.uniSeedToPrivate(_seedKey!, 0, type); + _publicAddress = await NanoUtil.uniSeedToAddress(_seedKey!, 0, type); + this.walletInfo.address = _publicAddress!; + + await walletAddresses.init(); + await transactionHistory.init(); + await save(); + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int? amount) { + return 0; // always 0 :) + } + + @override + Future changePassword(String password) { + throw UnimplementedError("changePassword"); + } + + @override + void close() { + _client.stop(); + } + + @action + @override + Future connectToNode({required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + final isConnected = _client.connect(node); + if (!isConnected) { + throw Exception("Nano Node connection failed"); + } + + try { + await _updateBalance(); + await _updateRep(); + await _receiveAll(); + } catch (e) { + print(e); + } + + syncStatus = ConnectedSyncStatus(); + } catch (e) { + print(e); + syncStatus = FailedSyncStatus(); + } + } + + @override + Future connectToPowNode({required Node node}) async { + _client.connectPow(node); + } + + @override + Future createTransaction(Object credentials) async { + credentials = credentials as NanoTransactionCredentials; + + BigInt runningAmount = BigInt.zero; + await _updateBalance(); + BigInt runningBalance = balance[currency]?.currentBalance ?? BigInt.zero; + + final List> blocks = []; + String? previousHash; + + for (var txOut in credentials.outputs) { + late BigInt amt; + if (txOut.sendAll) { + amt = balance[currency]?.currentBalance ?? BigInt.zero; + } else { + amt = BigInt.tryParse( + NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ?? + BigInt.zero; + } + + if (balance[currency]?.currentBalance != null && amt > balance[currency]!.currentBalance) { + throw Exception("Trying to send more than entire balance!"); + } + + runningBalance = runningBalance - amt; + + final block = await _client.constructSendBlock( + amountRaw: amt.toString(), + destinationAddress: txOut.extractedAddress ?? txOut.address, + privateKey: _privateKey!, + balanceAfterTx: runningBalance, + previousHash: previousHash, + ); + previousHash = NanoBlocks.computeStateHash( + NanoAccountType.NANO, + block["account"]!, + block["previous"]!, + block["representative"]!, + BigInt.parse(block["balance"]!), + block["link"]!, + ); + + blocks.add(block); + runningAmount += amt; + } + + try { + if (runningAmount > balance[currency]!.currentBalance || runningBalance < BigInt.zero) { + throw Exception(("Trying to send more than entire balance!")); + } + } catch (e) { + rethrow; + } + + return PendingNanoTransaction( + amount: runningAmount, + id: "", + nanoClient: _client, + blocks: blocks, + ); + } + + Future _receiveAll() async { + await _updateBalance(); + int blocksReceived = await this._client.confirmAllReceivable( + destinationAddress: _publicAddress!, + privateKey: _privateKey!, + ); + + if (blocksReceived > 0) { + await Future.delayed(Duration(seconds: 3)); + _updateBalance(); + updateTransactions(); + } + } + + Future updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (_) { + _isTransactionUpdating = false; + } + } + + @override + Future> fetchTransactions() async { + String address = _publicAddress!; + + final transactions = await _client.fetchTransactions(address); + + final Map result = {}; + + for (var transactionModel in transactions) { + result[transactionModel.hash] = NanoTransactionInfo( + id: transactionModel.hash, + amountRaw: transactionModel.amount, + height: transactionModel.height, + direction: transactionModel.type == "send" + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + confirmed: transactionModel.confirmed, + date: transactionModel.date ?? DateTime.now(), + confirmations: transactionModel.confirmed ? 1 : 0, + ); + } + + return result; + } + + @override + NanoWalletKeys get keys { + return NanoWalletKeys(seedKey: _seedKey!); + } + + @override + String? get privateKey => _seedKey!; + + @override + Future rescan({required int height}) async { + updateTransactions(); + _updateBalance(); + return; + } + + @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; + + String get representative => _representativeAddress ?? ""; + + @action + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + await _updateBalance(); + await updateTransactions(); + + _receiveTimer?.cancel(); + _receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async { + // get our balance: + await _updateBalance(); + // if we have anything to receive, process it: + if (balance[currency]!.receivableBalance > BigInt.zero) { + await _receiveAll(); + } + }); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + print(e); + syncStatus = FailedSyncStatus(); + rethrow; + } + } + + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + String toJSON() => json.encode({ + 'seedKey': _seedKey, + 'mnemonic': _mnemonic, + 'currentBalance': balance[currency]?.currentBalance.toString() ?? "0", + 'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0", + 'derivationType': _derivationType.toString() + }); + + 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 balance = NanoBalance.fromString( + formattedCurrentBalance: data['currentBalance'] as String? ?? "0", + formattedReceivableBalance: data['receivableBalance'] as String? ?? "0"); + + DerivationType derivationType = DerivationType.bip39; + if (data['derivationType'] == "DerivationType.nano") { + derivationType = DerivationType.nano; + } + + walletInfo.derivationType = derivationType; + + return NanoWallet( + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + initialBalance: balance, + ); + // init() should always be run after this! + } + + Future _updateBalance() async { + try { + balance[currency] = await _client.getBalance(_publicAddress!); + } catch (e) { + print("Failed to get balance $e"); + } + await save(); + } + + Future _updateRep() async { + try { + AccountInfoResponse accountInfo = (await _client.getAccountInfo(_publicAddress!))!; + _representativeAddress = accountInfo.representative; + } catch (e) { + // account not found: + _representativeAddress = NanoClient.DEFAULT_REPRESENTATIVE; + throw Exception("Failed to get representative address $e"); + } + } + + Future regenerateAddress() async { + final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; + _privateKey = + await NanoUtil.uniSeedToPrivate(_seedKey!, this.walletAddresses.account!.id, type); + _publicAddress = + await NanoUtil.uniSeedToAddress(_seedKey!, this.walletAddresses.account!.id, type); + + this.walletInfo.address = _publicAddress!; + this.walletAddresses.address = _publicAddress!; + } + + Future changeRep(String address) async { + try { + final String hash = await _client.changeRep( + privateKey: _privateKey!, + repAddress: address, + ourAddress: _publicAddress!, + ); + if (hash.isNotEmpty) { + _representativeAddress = address; + } + } catch (e) { + throw Exception("Failed to change representative address $e"); + } + } + + Future? updateBalance() async => await _updateBalance(); + + @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); + } +} diff --git a/cw_nano/lib/nano_wallet_addresses.dart b/cw_nano/lib/nano_wallet_addresses.dart new file mode 100644 index 000000000..cc532d2c7 --- /dev/null +++ b/cw_nano/lib/nano_wallet_addresses.dart @@ -0,0 +1,50 @@ +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/nano_account.dart'; +import 'package:cw_nano/nano_account_list.dart'; +import 'package:mobx/mobx.dart'; + +part 'nano_wallet_addresses.g.dart'; + +class NanoWalletAddresses = NanoWalletAddressesBase with _$NanoWalletAddresses; + +abstract class NanoWalletAddressesBase extends WalletAddresses with Store { + NanoWalletAddressesBase(WalletInfo walletInfo) + : accountList = NanoAccountList(walletInfo.address), + address = '', + super(walletInfo); + @override + @observable + String address; + + @observable + NanoAccount? account; + + NanoAccountList accountList; + + @override + Future init() async { + var box = await CakeHive.openBox(walletInfo.address); + try { + box.getAt(0); + } catch (e) { + box.add(NanoAccount(id: 0, label: "Primary Account", balance: "0.00")); + } + + await accountList.update(walletInfo.address); + account = accountList.accounts.first; + address = walletInfo.address; + } + + @override + Future updateAddressesInBox() async { + try { + addressesMap.clear(); + addressesMap[address] = ''; + await saveAddressesInBox(); + } catch (e) { + print(e.toString()); + } + } +} diff --git a/cw_nano/lib/nano_wallet_creation_credentials.dart b/cw_nano/lib/nano_wallet_creation_credentials.dart new file mode 100644 index 000000000..84531e24a --- /dev/null +++ b/cw_nano/lib/nano_wallet_creation_credentials.dart @@ -0,0 +1,41 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; + +class NanoNewWalletCredentials extends WalletCredentials { + NanoNewWalletCredentials({required String name, String? password}) + : super(name: name, password: password); +} + +class NanoRestoreWalletFromSeedCredentials extends WalletCredentials { + NanoRestoreWalletFromSeedCredentials({ + required String name, + required this.mnemonic, + int height = 0, + String? password, + DerivationType? derivationType, + }) : super( + name: name, + password: password, + height: height, + derivationType: derivationType, + ); + + final String mnemonic; +} + +class NanoWalletLoadingException implements Exception { + @override + String toString() => 'Failure to load the wallet.'; +} + +class NanoRestoreWalletFromKeysCredentials extends WalletCredentials { + NanoRestoreWalletFromKeysCredentials({ + required String name, + required String password, + required this.seedKey, + this.derivationType, + }) : super(name: name, password: password); + + final String seedKey; + final DerivationType? derivationType; +} \ No newline at end of file diff --git a/cw_nano/lib/nano_wallet_keys.dart b/cw_nano/lib/nano_wallet_keys.dart new file mode 100644 index 000000000..80a845e64 --- /dev/null +++ b/cw_nano/lib/nano_wallet_keys.dart @@ -0,0 +1,5 @@ +class NanoWalletKeys { + const NanoWalletKeys({required this.seedKey}); + + final String seedKey; +} diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart new file mode 100644 index 000000000..2f183d1cc --- /dev/null +++ b/cw_nano/lib/nano_wallet_service.dart @@ -0,0 +1,163 @@ +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_nano/nano_mnemonic.dart' as nm; +import 'package:cw_nano/nano_util.dart'; +import 'package:cw_nano/nano_wallet.dart'; +import 'package:cw_nano/nano_wallet_creation_credentials.dart'; +import 'package:hive/hive.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:nanodart/nanodart.dart'; + +class NanoWalletService extends WalletService { + NanoWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + static bool walletFilesExist(String path) => + !File(path).existsSync() && !File('$path.keys').existsSync(); + + @override + WalletType getType() => WalletType.nano; + + @override + Future create(NanoNewWalletCredentials credentials) async { + // nano standard: + DerivationType derivationType = DerivationType.nano; + String seedKey = NanoSeeds.generateSeed(); + String mnemonic = NanoUtil.seedToMnemonic(seedKey); + + credentials.walletInfo!.derivationType = derivationType; + + final wallet = NanoWallet( + walletInfo: credentials.walletInfo!, + mnemonic: mnemonic, + password: credentials.password!, + ); + wallet.init(); + return wallet; + } + + @override + Future remove(String wallet) async { + final path = await pathForWalletDir(name: wallet, type: getType()); + final file = Directory(path); + final isExist = file.existsSync(); + + if (isExist) { + await file.delete(recursive: true); + } + + final walletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + + String randomWords = + (List.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' '); + final currentWallet = + NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async { + if (credentials.seedKey.contains(' ')) { + throw Exception("Invalid key!"); + } else { + if (credentials.seedKey.length != 64 && credentials.seedKey.length != 128) { + throw Exception("Invalid key length!"); + } + } + + DerivationType derivationType = credentials.derivationType ?? DerivationType.nano; + credentials.walletInfo!.derivationType = derivationType; + + String? mnemonic; + + // we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed + if (credentials.seedKey.length == 64) { + try { + mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey); + } catch (e) { + throw Exception("Wasn't a valid nano style seed!"); + } + } + + final wallet = await NanoWallet( + password: credentials.password!, + mnemonic: mnemonic ?? credentials.seedKey, + walletInfo: credentials.walletInfo!, + ); + await wallet.init(); + await wallet.save(); + return wallet; + } + + @override + Future restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async { + if (credentials.mnemonic.contains(' ')) { + if (!bip39.validateMnemonic(credentials.mnemonic)) { + throw nm.NanoMnemonicIsIncorrectException(); + } + + if (!NanoMnemomics.validateMnemonic(credentials.mnemonic.split(' '))) { + throw nm.NanoMnemonicIsIncorrectException(); + } + } else { + if (credentials.mnemonic.length != 64 && credentials.mnemonic.length != 128) { + throw Exception("Invalid seed length"); + } + } + + DerivationType derivationType = credentials.derivationType ?? DerivationType.nano; + + credentials.walletInfo!.derivationType = derivationType; + + final wallet = await NanoWallet( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + await wallet.save(); + return wallet; + } + + @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 NanoWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); + + await wallet.init(); + await wallet.save(); + return wallet; + } +} diff --git a/cw_nano/lib/pending_nano_transaction.dart b/cw_nano/lib/pending_nano_transaction.dart new file mode 100644 index 000000000..727f02534 --- /dev/null +++ b/cw_nano/lib/pending_nano_transaction.dart @@ -0,0 +1,40 @@ +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_nano/nano_client.dart'; +import 'package:cw_nano/nano_util.dart'; + +class PendingNanoTransaction with PendingTransaction { + PendingNanoTransaction({ + required this.nanoClient, + required this.amount, + required this.id, + required this.blocks, + }); + + final NanoClient nanoClient; + final BigInt amount; + final String id; + final List> blocks; + String hex = "unused"; + + @override + String get amountFormatted { + final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano); + return amt; + } + + String get accurateAmountFormatted { + final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano); + final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano); + return "$acc$amt"; + } + + @override + String get feeFormatted => "0"; + + @override + Future commit() async { + for (var block in blocks) { + await nanoClient.processBlock(block, "send"); + } + } +} diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock new file mode 100644 index 000000000..ea932baee --- /dev/null +++ b/cw_nano/pubspec.lock @@ -0,0 +1,756 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + url: "https://pub.dev" + source: hosted + version: "47.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bip32: + dependency: "direct main" + description: + name: bip32 + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + bs58check: + dependency: transitive + description: + name: bs58check + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + build: + dependency: transitive + description: + name: build + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + url: "https://pub.dev" + source: hosted + version: "2.0.10" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" + source: hosted + version: "2.3.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + url: "https://pub.dev" + source: hosted + version: "7.2.7+1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" + url: "https://pub.dev" + source: hosted + version: "8.6.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" + url: "https://pub.dev" + source: hosted + version: "4.5.0" + collection: + dependency: transitive + description: + name: collection + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" + source: hosted + version: "1.17.1" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cw_core: + dependency: "direct main" + description: + path: "../cw_core" + relative: true + source: path + version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + decimal: + dependency: "direct main" + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + ed25519_hd_key: + dependency: "direct main" + description: + name: ed25519_hd_key + sha256: "326608234e986ea826a5db4cf4cd6826058d860875a3fff7926c0725fe1a604d" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + fixnum_nanodart: + dependency: transitive + description: + name: fixnum_nanodart + sha256: "4b0132d11ecddc0d2ca64b6d7dee6726db432ed02cac1349d7532a08be5c54fc" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_mobx: + dependency: transitive + description: + name: flutter_mobx + sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + url: "https://pub.dev" + source: hosted + version: "2.0.6+5" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hex: + dependency: "direct main" + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + libcrypto: + dependency: "direct main" + description: + name: libcrypto + sha256: "18a97db8d88147b0b60d2755f29b5e4944181c4c1a9f52bd1ecbea1b0a5aab03" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" + source: hosted + version: "0.12.15" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mobx: + dependency: "direct main" + description: + name: mobx + sha256: "0afcf88b3ee9d6819890bf16c11a727fc8c62cf736fda8e5d3b9b4eace4e62ea" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c + url: "https://pub.dev" + source: hosted + version: "2.3.0" + nanodart: + dependency: "direct main" + description: + name: nanodart + sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + url: "https://pub.dev" + source: hosted + version: "2.0.15" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + url: "https://pub.dev" + source: hosted + version: "2.0.27" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + url: "https://pub.dev" + source: hosted + version: "2.1.11" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + url: "https://pub.dev" + source: hosted + version: "2.1.7" + pinenacl: + dependency: transitive + description: + name: pinenacl + sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9 + url: "https://pub.dev" + source: hosted + version: "0.3.4" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + rational: + dependency: transitive + description: + name: rational + sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" + source: hosted + version: "0.5.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + url: "https://pub.dev" + source: hosted + version: "4.1.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff + url: "https://pub.dev" + source: hosted + version: "1.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.3.0" diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml new file mode 100644 index 000000000..054dd5df4 --- /dev/null +++ b/cw_nano/pubspec.yaml @@ -0,0 +1,69 @@ +name: cw_nano +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=2.18.2 <3.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + mobx: ^2.0.7+4 + bip39: ^1.0.6 + bip32: ^2.0.0 + nanodart: ^2.0.0 + decimal: ^2.3.3 + libcrypto: ^0.2.2 + ed25519_hd_key: ^2.2.0 + hex: ^0.2.0 + http: ^1.1.0 + cw_core: + path: ../cw_core + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_nano/test/cw_nano_test.dart b/cw_nano/test/cw_nano_test.dart new file mode 100644 index 000000000..fbabc7b54 --- /dev/null +++ b/cw_nano/test/cw_nano_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_nano/cw_nano.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/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 3164aacaa..353458937 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -161,4 +161,4 @@ class CWBitcoin extends Bitcoin { @override TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow; -} +} \ No newline at end of file diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 2f0e47f02..91309a2ca 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -27,6 +27,8 @@ class OnRamperBuyProvider { return "LTC_LITECOIN"; case CryptoCurrency.xmr: return "XMR_MONERO"; + case CryptoCurrency.nano: + return "XNO_NANO"; default: return _wallet.currency.title; } diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index abafea0d5..5f5b004ba 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -28,6 +28,8 @@ class AddressValidator extends TextValidator { return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$'; case CryptoCurrency.nano: return '[0-9a-zA-Z_]'; + case CryptoCurrency.banano: + return '[0-9a-zA-Z_]'; case CryptoCurrency.usdc: case CryptoCurrency.usdcpoly: case CryptoCurrency.ape: @@ -177,6 +179,8 @@ class AddressValidator extends TextValidator { return [34, 43, 63]; case CryptoCurrency.nano: return [64, 65]; + case CryptoCurrency.banano: + return [64, 65]; case CryptoCurrency.sc: return [76]; case CryptoCurrency.sol: diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index eba1bbda4..1c6e7cd20 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/utils/language_list.dart'; class SeedValidator extends Validator { @@ -28,6 +29,9 @@ class SeedValidator extends Validator { return haven!.getMoneroWordList(language); case WalletType.ethereum: return ethereum!.getEthereumWordList(language); + case WalletType.nano: + case WalletType.banano: + return nano!.getNanoWordList(language); default: return []; } diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 3b28f36c3..dda591115 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -39,15 +39,11 @@ class WalletCreationService { bool exists(String name) { final walletName = name.toLowerCase(); - return walletInfoSource - .values - .any((walletInfo) => walletInfo.name.toLowerCase() == walletName); + return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName); } bool typeExists(WalletType type) { - return walletInfoSource - .values - .any((walletInfo) => walletInfo.type == type); + return walletInfoSource.values.any((walletInfo) => walletInfo.type == type); } void checkIfExists(String name) { @@ -60,15 +56,12 @@ class WalletCreationService { checkIfExists(credentials.name); final password = generateWalletPassword(); credentials.password = password; - await keyService.saveWalletPassword( - password: password, walletName: credentials.name); - final wallet = await _service!.create(credentials); + await keyService.saveWalletPassword(password: password, walletName: credentials.name); + final wallet = await _service!.create(credentials); if (wallet.type == WalletType.monero) { - await sharedPreferences - .setBool( - PreferencesKey.moneroWalletUpdateV1Key(wallet.name), - _isNewMoneroWalletPasswordUpdated); + await sharedPreferences.setBool( + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated); } return wallet; @@ -78,15 +71,12 @@ class WalletCreationService { checkIfExists(credentials.name); final password = generateWalletPassword(); credentials.password = password; - await keyService.saveWalletPassword( - password: password, walletName: credentials.name); + await keyService.saveWalletPassword(password: password, walletName: credentials.name); final wallet = await _service!.restoreFromKeys(credentials); if (wallet.type == WalletType.monero) { - await sharedPreferences - .setBool( - PreferencesKey.moneroWalletUpdateV1Key(wallet.name), - _isNewMoneroWalletPasswordUpdated); + await sharedPreferences.setBool( + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated); } return wallet; @@ -96,15 +86,12 @@ class WalletCreationService { checkIfExists(credentials.name); final password = generateWalletPassword(); credentials.password = password; - await keyService.saveWalletPassword( - password: password, walletName: credentials.name); + await keyService.saveWalletPassword(password: password, walletName: credentials.name); final wallet = await _service!.restoreFromSeed(credentials); if (wallet.type == WalletType.monero) { - await sharedPreferences - .setBool( - PreferencesKey.moneroWalletUpdateV1Key(wallet.name), - _isNewMoneroWalletPasswordUpdated); + await sharedPreferences.setBool( + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated); } return wallet; diff --git a/lib/di.dart b/lib/di.dart index 678a8a325..96c7505ef 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -13,6 +13,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/receive_page_option.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +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'; @@ -26,8 +27,13 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart'; import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart'; +import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart'; +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart'; +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart'; +import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart'; import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; @@ -73,6 +79,9 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart'; +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/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'; @@ -83,7 +92,9 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; +import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/nano_account.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cake_wallet/core/backup_service.dart'; import 'package:cw_core/wallet_service.dart'; @@ -206,6 +217,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'package:cake_wallet/nano/nano.dart' as nanoNano; import 'core/totp_request_details.dart'; @@ -214,6 +226,7 @@ final getIt = GetIt.instance; var _isSetupFinished = false; late Box _walletInfoSource; late Box _nodeSource; +late Box _powNodeSource; late Box _contactSource; late Box _tradesSource; late Box