From 13309667940b7ebd7c9fd44098ae088a834e0492 Mon Sep 17 00:00:00 2001 From: M Date: Fri, 7 May 2021 10:36:38 +0300 Subject: [PATCH 1/8] Reworked electrum wallet. Added Litecoin wallet. --- .gitignore | 2 + analysis_options.yaml | 1 - ...t.yml => bitcoin_electrum_server_list.yml} | 0 assets/images/litecoin_icon.png | Bin 0 -> 6006 bytes assets/images/litecoin_menu.png | Bin 0 -> 15547 bytes assets/images/monero_menu.png | Bin 1411 -> 2169 bytes assets/litecoin_electrum_server_list.yml | 2 + cw_monero/ios/Classes/monero_api.cpp | 4 - cw_monero/lib/wallet_manager.dart | 4 +- cw_monero/pubspec.lock | 40 +- lib/bitcoin/address_to_output_script.dart | 16 +- lib/bitcoin/bitcoin_address_record.dart | 11 +- lib/bitcoin/bitcoin_transaction_history.dart | 192 ------- lib/bitcoin/bitcoin_unspent.dart | 3 +- lib/bitcoin/bitcoin_wallet.dart | 481 ++---------------- lib/bitcoin/bitcoin_wallet_service.dart | 42 +- lib/bitcoin/electrum.dart | 35 +- ...oin_balance.dart => electrum_balance.dart} | 12 +- lib/bitcoin/electrum_transaction_history.dart | 98 ++++ ...fo.dart => electrum_transaction_info.dart} | 31 +- lib/bitcoin/electrum_wallet.dart | 467 +++++++++++++++++ lib/bitcoin/electrum_wallet_snapshot.dart | 42 ++ lib/bitcoin/litecoin_network.dart | 9 + lib/bitcoin/litecoin_wallet.dart | 88 ++++ lib/bitcoin/litecoin_wallet_service.dart | 76 +++ lib/bitcoin/pending_bitcoin_transaction.dart | 29 +- lib/bitcoin/script_hash.dart | 16 +- lib/bitcoin/utils.dart | 37 +- lib/core/address_validator.dart | 2 +- lib/core/backup_service.dart | 1 - lib/core/fiat_conversion_service.dart | 11 +- lib/core/generate_wallet_password.dart | 6 +- lib/core/node_port_validator.dart | 1 - lib/core/transaction_history.dart | 67 +-- lib/core/wallet_base.dart | 14 +- lib/core/wallet_service.dart | 3 + lib/di.dart | 5 + lib/entities/currency_for_wallet_type.dart | 4 +- lib/entities/currency_formatter.dart | 12 - lib/entities/default_settings_migration.dart | 134 +++-- lib/entities/fs_migration.dart | 14 +- lib/entities/node_list.dart | 27 +- lib/entities/preferences_key.dart | 10 +- lib/entities/wallet_type.dart | 21 +- lib/main.dart | 4 +- lib/monero/monero_account_list.dart | 4 +- lib/monero/monero_transaction_history.dart | 35 +- lib/monero/monero_transaction_info.dart | 1 + lib/monero/monero_wallet.dart | 74 ++- lib/monero/monero_wallet_service.dart | 37 +- lib/reactions/on_current_wallet_change.dart | 12 +- .../on_wallet_sync_status_change.dart | 19 +- .../dashboard/widgets/menu_widget.dart | 4 + .../new_wallet/new_wallet_type_page.dart | 23 +- .../screens/wallet_list/wallet_list_page.dart | 4 + lib/store/app_store.dart | 12 +- lib/store/settings_store.dart | 12 +- .../dashboard/balance_view_model.dart | 23 +- .../dashboard/dashboard_view_model.dart | 47 +- .../dashboard/transaction_list_item.dart | 6 +- .../exchange/exchange_view_model.dart | 29 +- .../node_list/node_list_view_model.dart | 3 + lib/view_model/send/send_view_model.dart | 17 +- .../settings/settings_view_model.dart | 26 +- .../transaction_details_view_model.dart | 56 +- ...let_address_edit_or_create_view_model.dart | 3 +- .../wallet_address_list_view_model.dart | 17 +- lib/view_model/wallet_keys_view_model.dart | 7 +- lib/view_model/wallet_new_vm.dart | 2 + pubspec.lock | 18 +- pubspec.yaml | 9 +- 71 files changed, 1503 insertions(+), 1071 deletions(-) rename assets/{electrum_server_list.yml => bitcoin_electrum_server_list.yml} (100%) create mode 100644 assets/images/litecoin_icon.png create mode 100644 assets/images/litecoin_menu.png create mode 100644 assets/litecoin_electrum_server_list.yml delete mode 100644 lib/bitcoin/bitcoin_transaction_history.dart rename lib/bitcoin/{bitcoin_balance.dart => electrum_balance.dart} (70%) create mode 100644 lib/bitcoin/electrum_transaction_history.dart rename lib/bitcoin/{bitcoin_transaction_info.dart => electrum_transaction_info.dart} (84%) create mode 100644 lib/bitcoin/electrum_wallet.dart create mode 100644 lib/bitcoin/electrum_wallet_snapshot.dart create mode 100644 lib/bitcoin/litecoin_network.dart create mode 100644 lib/bitcoin/litecoin_wallet.dart create mode 100644 lib/bitcoin/litecoin_wallet_service.dart delete mode 100644 lib/entities/currency_formatter.dart diff --git a/.gitignore b/.gitignore index db43e964a..0580d006c 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,5 @@ vendor/ android/app/.cxx/** ios/Flutter/.last_build_id /lib/generated/** +#**# +/**/#**# \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 52f1e3224..d74761d26 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -25,7 +25,6 @@ linter: - empty_constructor_bodies - empty_statements - hash_and_equals - - implementation_imports - invariant_booleans - iterable_contains_unrelated_type - library_names diff --git a/assets/electrum_server_list.yml b/assets/bitcoin_electrum_server_list.yml similarity index 100% rename from assets/electrum_server_list.yml rename to assets/bitcoin_electrum_server_list.yml diff --git a/assets/images/litecoin_icon.png b/assets/images/litecoin_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc47b6fbc11b6aaa315f86318abb2edad05f845 GIT binary patch literal 6006 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 F@qd$5lzadH literal 0 HcmV?d00001 diff --git a/assets/images/litecoin_menu.png b/assets/images/litecoin_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..5003adc1cc14498388e3f8465dc488bfeae0dba5 GIT binary patch literal 15547 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 JR*M@4{XcWc)sX-I literal 0 HcmV?d00001 diff --git a/assets/images/monero_menu.png b/assets/images/monero_menu.png index 2bb420a807c8b33ab40cde4da0bcacccad02f5ec..51b1e22405ae96d56fe50f1fa11d8fedf3fbe65f 100644 GIT binary patch literal 2169 zcmY*aeLR!v8^7oMWrh@y*jagtZK$xBX~QxZ4${V{FvG|!n}~!^DKF(cRDOjHoe+_? z)KaYS(kU;6YKf?*=%l=KoM+X~`Tg$ab3NDh{@&l~d)?Rb&y(urvRz3*O923&M0K=x zhq56QMshOHd;5h@A(W(oNHh`vS2Got{G_2hlIiG910Y5pfP`cK79c8N5`aik0N(ln zfG+?*gInDED*-x4X3?pEG#c0hVL2cTTLmN`1cM#`(*j~Q2mvS9x)tmWGyJKM0N@xK zNd44!Ls`5Wp%B;n$dVV4$#gE#$^v zd(!!Inlqjm9)|G?2=`}UqQki2DnN+FLokfR_d`a9?c?z9(Ke{>3Ot0xG!}*YF5!pT zpy)I=q+NIf3u%Ed#h9Xq3P>c95D^fFcekhfEQfYBs9-*yi^pQ4qM|TSn=#=LL0B^! z4u>^0$C{g?AqARuki++jMss)@ewh5%$DYMwMzFbjb~pzq_Vx1*kL25+P~t`_>&HF$ z?7)9JadBTJWTt-FrhV zXQe|LHIBPm$306SCQe;jG&DFKA*n-JbR^$YzIAG~Lu%I4&KaFl0a$yeKmb3SrhNu8 zWgBNU`^f6ey8Si=j}i?JJM%W^OJaGHz|pY>XTR-#8;V#uV>gsJRa%!7Fm|Wa!B)EN zl3adj!J8aZf%eK?j?_K9q@3(} zRr4#ZS9%HJG2JP@+}1^A%XfUN9p=pFyzcT(df5w8BcoTKM5n<3n#`5r^ci#O&DBINml2!_HL#RL1i*=bt z&cw=Z>1RC^$Zwh37KddPU3uDu`~oyOOv7yT&47ox?0Y>hkd9s&-KaEnS|g<4ThX@n zN(f1-;2oU;uR@1rJM8-!(1+VLe)>gDLn1+HxY+~OE4CZrx=IxrYCQWt;Y;9vVs zpevD=MVM)+>E-U>!5wH``YtV8U&&x`)!m4?-TYB!Z`oQzvE#c}7{(uxr|^nB!&|N- zop}JRVtZ;3MFCDyMq0y1^iOj7Mu?&(?Ydm%F5j0oj%E}Is9h$rEzQ^yK3}uvh@Ny7_!~>toSP6ZkT;K8E6SVA^dQRV+3fjY>1KsmRqfSod9;POFLhX{@oJTg zr-JWU$WR;}Y)-q@Lvm((_3eFb`oglJN$uDxN_%0Fgbp(Q1?4trwBGzb{cLhrqFH8_ zUsvXRxmx+(vVFU%bcY(#yzlmm?!+DSpr2YFQ;vx_7t9TwOxjB`*h%rwES%mz@LT$v zoc-BZxf8yZo52ZIMPPK0h?8G{KLb{M70j)@Zo{#jr&{v~3%2&Ihs%;<8-x|+qx3eR z4^qB^1S^0uPAK2wYm|S>TTQ|Q;6TNWgsBOqPqXQ&*Qq|iuI@LeF$vnL8o9;+I&$5+ z+pr>d!Jv&AKA!Ea@|%-f)F88PB)4W}P#^bUH_rcoY&xYTM{zjuK{(cdW{eLqq<0kzr1z5|fgx-2 z&hy0kp($64toFzsCSR>H@=I@LWs}z^Yo#%K;^dwzzDYU@OHj^@&N8*hJA)L-gc}TK zIN1`baX~QzyW1OhHAfe54b{yPO(K=%*R50SI+^@KcgmtlwtG|VbTv{c0}bCsd8F}7 z8G8G|+B$Q}`U)FId`~`ve>z8Q{}S07I&?md?${zb%~M=I`d}+u%a#8(^@Gjil?qj4 zY~H{~jmxTYF+uc6Uf)aJS7(h96@01BpiXO!ADWVZrb)zN6E=|&FL=aviiTPXJ(#aj ze4gdEJJ$bNe@Nw4#Kfd_+{l52hgyi%a^n>Fix$$EWJHgDIZK(+vh2E`d|6%HWNV+- Qd+`sI>fmBuMfN@N57BaUqyPW_ delta 1375 zcmV-l1)%!*5Q7VlB$4nGe*ghzuF+Y=Q z0|YM*;V)}ALU2guxxXA`V9g2fg8|c1DCXhT-zg3M4rg)Sw|W5zSc>+aeCzG$qOBEM z3|?51Mr_6Lp8io^&`!X2{_~uB`B1l5B7^YrCp$}D7vU_~`+oR*$u9PIpb3M!RSU%# zLh#w2KEknp@BP(6f4Osz(!H?$vxoe#bCS{dEQSnT+k)dZSo!S!pn{-{McGxGCt3mD2di%t9K|VRv%kXG#`pl2=cHKHgYICF zL7$p)?stDVcF%ZbIPmt_-{jfPdv1Gx`QiKlOf(pC8sf1OKJIlzIG2Lj<{B82osRgsSVG=E}a?@G*Av{NMATL zW~}eAsB0u}V-6D3VeRUD5wh(o^pvJ0kAW+os;!j4#>F!fz-ryusx|Q;oUM6IM&<>y zAG!(I3CFsc`tSYg7ir2%h!PYx4tIWGWyM#Ze?8@YYq|d%To2kUw7ad0(~SoW+ zerjt&q#NUYZHcv@nxGz>AObnWt!-i^E@D+DzXe?I90>S!B(5?uniT9wi+uc~@f z)uiaY;o}btDGDuC3{}Q^gk%ewvWq61e_6OU6AWxq(>(ex=ZaRWN`kl0@8}~x3Db+Q z$k9h13Sj5(i1+63Q#cKpedTyf`biF%8L%k(IS<-)JeLPVIp>ZzJSqndo~3}ES5N*Y z$8X(Ai{o4(d^~jaES}hcGv+NzwonfmhoFRfq~}jR!U5?7R0HWb=d~+AKDx-~RT^KE zNPw~1+ye`IB+J^Nli5o50MVek$GN?P^G;f9DSJHoA$ptS!9h4-i%rylxeG=ntgekz hiySa(GlG98`42GBI57*`7Bv6>002ovPDHLkV1iTMlPUlJ diff --git a/assets/litecoin_electrum_server_list.yml b/assets/litecoin_electrum_server_list.yml new file mode 100644 index 000000000..4922dfba2 --- /dev/null +++ b/assets/litecoin_electrum_server_list.yml @@ -0,0 +1,2 @@ +- + uri: 128.199.34.116:50002 \ No newline at end of file diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index d0313a194..dd1ab3cbb 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -179,8 +179,6 @@ extern "C" Monero::SubaddressAccount *m_account; uint64_t m_last_known_wallet_height; uint64_t m_cached_syncing_blockchain_height = 0; - std::mutex store_mutex; - void change_current_wallet(Monero::Wallet *wallet) { @@ -451,9 +449,7 @@ extern "C" void store(char *path) { - store_mutex.lock(); get_current_wallet()->store(std::string(path)); - store_mutex.unlock(); } bool transaction_create(char *address, char *payment_id, char *amount, diff --git a/cw_monero/lib/wallet_manager.dart b/cw_monero/lib/wallet_manager.dart index e48055cf9..2400c5f3f 100644 --- a/cw_monero/lib/wallet_manager.dart +++ b/cw_monero/lib/wallet_manager.dart @@ -1,12 +1,12 @@ import 'dart:ffi'; -import 'package:cw_monero/exceptions/wallet_opening_exception.dart'; -import 'package:cw_monero/wallet.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:cw_monero/convert_utf8_to_string.dart'; import 'package:cw_monero/signatures.dart'; import 'package:cw_monero/types.dart'; import 'package:cw_monero/monero_api.dart'; +import 'package:cw_monero/wallet.dart'; +import 'package:cw_monero/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/exceptions/wallet_creation_exception.dart'; import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart'; diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 648dce288..d44aacc6b 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -7,49 +7,49 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.15.0" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" ffi: dependency: "direct main" description: @@ -73,21 +73,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.8" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path_provider: dependency: "direct main" description: @@ -113,56 +113,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.5" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.17" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" - flutter: ">=0.1.4 <2.0.0" + dart: ">=2.12.0-0.0 <3.0.0" + flutter: ">=0.1.4" diff --git a/lib/bitcoin/address_to_output_script.dart b/lib/bitcoin/address_to_output_script.dart index 2f6698ff0..01c7b67a5 100644 --- a/lib/bitcoin/address_to_output_script.dart +++ b/lib/bitcoin/address_to_output_script.dart @@ -1,25 +1,29 @@ import 'dart:typed_data'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:bitcoin_flutter/src/utils/constants/op.dart'; import 'package:bitcoin_flutter/src/utils/script.dart' as bscript; import 'package:bitcoin_flutter/src/address.dart'; - Uint8List p2shAddressToOutputScript(String address) { final decodeBase58 = bs58check.decode(address); final hash = decodeBase58.sublist(1); return bscript.compile([OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]); } -Uint8List addressToOutputScript(String address) { +Uint8List addressToOutputScript( + String address, bitcoin.NetworkType networkType) { try { // FIXME: improve validation for p2sh addresses - if (address.startsWith('3')) { + // 3 for bitcoin + // m for litecoin + if (address.startsWith('3') || address.toLowerCase().startsWith('m')) { return p2shAddressToOutputScript(address); } - return Address.addressToOutputScript(address); - } catch (_) { + return Address.addressToOutputScript(address, networkType); + } catch (err) { + print(err); return Uint8List(0); } -} \ No newline at end of file +} diff --git a/lib/bitcoin/bitcoin_address_record.dart b/lib/bitcoin/bitcoin_address_record.dart index af492de2d..5e3967308 100644 --- a/lib/bitcoin/bitcoin_address_record.dart +++ b/lib/bitcoin/bitcoin_address_record.dart @@ -1,14 +1,14 @@ import 'dart:convert'; -import 'package:quiver/core.dart'; class BitcoinAddressRecord { - BitcoinAddressRecord(this.address, {this.index}); + BitcoinAddressRecord(this.address, {this.index, bool isHidden}) + : _isHidden = isHidden; factory BitcoinAddressRecord.fromJSON(String jsonSource) { final decoded = json.decode(jsonSource) as Map; return BitcoinAddressRecord(decoded['address'] as String, - index: decoded['index'] as int); + index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool); } @override @@ -16,10 +16,13 @@ class BitcoinAddressRecord { o is BitcoinAddressRecord && address == o.address; final String address; + bool get isHidden => _isHidden ?? false; int index; + final bool _isHidden; @override int get hashCode => address.hashCode; - String toJSON() => json.encode({'address': address, 'index': index}); + String toJSON() => + json.encode({'address': address, 'index': index, 'isHidden': isHidden}); } diff --git a/lib/bitcoin/bitcoin_transaction_history.dart b/lib/bitcoin/bitcoin_transaction_history.dart deleted file mode 100644 index 16171267c..000000000 --- a/lib/bitcoin/bitcoin_transaction_history.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/core/transaction_history.dart'; -import 'package:cake_wallet/bitcoin/file.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; -import 'package:cake_wallet/bitcoin/electrum.dart'; - -part 'bitcoin_transaction_history.g.dart'; - -const _transactionsHistoryFileName = 'transactions.json'; - -class BitcoinTransactionHistory = BitcoinTransactionHistoryBase - with _$BitcoinTransactionHistory; - -abstract class BitcoinTransactionHistoryBase - extends TransactionHistoryBase with Store { - BitcoinTransactionHistoryBase( - {this.eclient, String dirPath, @required String password}) - : path = '$dirPath/$_transactionsHistoryFileName', - _password = password, - _height = 0, - _isUpdating = false { - transactions = ObservableMap(); - } - - BitcoinWalletBase wallet; - final ElectrumClient eclient; - final String path; - final String _password; - int _height; - bool _isUpdating; - - Future init() async { - await _load(); - } - - @override - Future update() async { - if (_isUpdating) { - return; - } - - try { - _isUpdating = true; - final txs = await fetchTransactions(); - await add(txs); - _isUpdating = false; - } catch (_) { - _isUpdating = false; - rethrow; - } - } - - @override - Future> fetchTransactions() async { - final histories = - wallet.scriptHashes.map((scriptHash) => eclient.getHistory(scriptHash)); - final _historiesWithDetails = await Future.wait(histories) - .then((histories) => histories.expand((i) => i).toList()) - .then((histories) => histories.map((tx) => fetchTransactionInfo( - hash: tx['tx_hash'] as String, height: tx['height'] as int))); - final historiesWithDetails = await Future.wait(_historiesWithDetails); - - return historiesWithDetails.fold>( - {}, (acc, tx) { - acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; - return acc; - }); - } - - Future fetchTransactionInfo( - {@required String hash, @required int height}) async { - final tx = await eclient.getTransactionExpanded(hash: hash); - return BitcoinTransactionInfo.fromElectrumVerbose(tx, - height: height, addresses: wallet.addresses); - } - - Future add(Map transactionsList) async { - transactionsList.entries.forEach((entry) { - _updateOrInsert(entry.value); - - if (entry.value.height > _height) { - _height = entry.value.height; - } - }); - - await save(); - } - - Future addOne(BitcoinTransactionInfo tx) async { - _updateOrInsert(tx); - - if (tx.height > _height) { - _height = tx.height; - } - - await save(); - } - - BitcoinTransactionInfo get(String id) => transactions[id]; - - Future save() async { - try { - final data = json.encode({'height': _height, 'transactions': transactions}); - await writeData(path: path, password: _password, data: data); - } catch(e) { - print('Error while save bitcoin transaction history: ${e.toString()}'); - } - } - - @override - void updateAsync({void Function() onFinished}) { - fetchTransactionsAsync((transaction) => _updateOrInsert(transaction), - onFinished: onFinished); - } - - @override - void fetchTransactionsAsync( - void Function(BitcoinTransactionInfo transaction) onTransactionLoaded, - {void Function() onFinished}) async { - final histories = await Future.wait(wallet.scriptHashes - .map((scriptHash) async => await eclient.getHistory(scriptHash))); - final transactionsCount = - histories.fold(0, (acc, m) => acc + m.length); - var counter = 0; - - final batches = histories.map((metaList) => - _fetchBatchOfTransactions(metaList, onTransactionLoaded: (transaction) { - onTransactionLoaded(transaction); - counter += 1; - - if (counter == transactionsCount) { - onFinished?.call(); - } - })); - - await Future.wait(batches); - } - - Future _fetchBatchOfTransactions( - Iterable> metaList, - {void Function(BitcoinTransactionInfo tranasaction) - onTransactionLoaded}) async => - metaList.forEach((txMeta) => fetchTransactionInfo( - hash: txMeta['tx_hash'] as String, - height: txMeta['height'] as int) - .then((transaction) => onTransactionLoaded(transaction))); - - Future> _read() async { - 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 = BitcoinTransactionInfo.fromJson(val); - _updateOrInsert(tx); - } - }); - - _height = content['height'] as int; - } catch (e) { - print(e); - } - } - - void _updateOrInsert(BitcoinTransactionInfo transaction) { - if (transaction.id == null) { - return; - } - - if (transactions[transaction.id] == null) { - transactions[transaction.id] = transaction; - } else { - final originalTx = transactions[transaction.id]; - originalTx.confirmations = transaction.confirmations; - originalTx.amount = transaction.amount; - originalTx.height = transaction.height; - originalTx.date ??= transaction.date; - originalTx.isPending = transaction.isPending; - } - } -} diff --git a/lib/bitcoin/bitcoin_unspent.dart b/lib/bitcoin/bitcoin_unspent.dart index bacc03dd4..846eb8c7d 100644 --- a/lib/bitcoin/bitcoin_unspent.dart +++ b/lib/bitcoin/bitcoin_unspent.dart @@ -13,5 +13,6 @@ class BitcoinUnspent { final int value; final int vout; - bool get isP2wpkh => address.address.startsWith('bc1'); + bool get isP2wpkh => + address.address.startsWith('bc') || address.address.startsWith('ltc'); } diff --git a/lib/bitcoin/bitcoin_wallet.dart b/lib/bitcoin/bitcoin_wallet.dart index 6cd113904..fd8402887 100644 --- a/lib/bitcoin/bitcoin_wallet.dart +++ b/lib/bitcoin/bitcoin_wallet.dart @@ -1,472 +1,51 @@ -import 'dart:async'; -import 'dart:convert'; -import 'package:cake_wallet/bitcoin/address_to_output_script.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter/foundation.dart'; -import 'package:rxdart/rxdart.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart'; -import 'package:cake_wallet/bitcoin/electrum.dart'; -import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart'; -import 'package:cake_wallet/bitcoin/script_hash.dart'; import 'package:cake_wallet/bitcoin/utils.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; -import 'package:cake_wallet/entities/sync_status.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; import 'package:cake_wallet/entities/wallet_info.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart'; import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart'; -import 'package:cake_wallet/bitcoin/file.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_balance.dart'; -import 'package:cake_wallet/entities/node.dart'; -import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:cake_wallet/bitcoin/electrum_balance.dart'; part 'bitcoin_wallet.g.dart'; class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet; -abstract class BitcoinWalletBase extends WalletBase with Store { - BitcoinWalletBase._internal( - {@required this.eclient, - @required this.path, - @required String password, - @required WalletInfo walletInfo, - @required List initialAddresses, - int accountIndex = 0, - this.transactionHistory, - this.mnemonic, - BitcoinBalance initialBalance}) - : balance = - initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0), - hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic), - network: bitcoin.bitcoin) - .derivePath("m/0'/0"), - addresses = initialAddresses != null - ? ObservableList.of(initialAddresses.toSet()) - : ObservableList(), - syncStatus = NotConnectedSyncStatus(), - _password = password, - _accountIndex = accountIndex, - _feeRates = [], - super(walletInfo) { - _unspent = []; - _scripthashesUpdateSubject = {}; - } - - static BitcoinWallet fromJSON( - {@required String password, - @required String name, - @required String dirPath, - @required WalletInfo walletInfo, - String jsonSource}) { - final data = json.decode(jsonSource) as Map; - final mnemonic = data['mnemonic'] as String; - final accountIndex = - (data['account_index'] == 'null' || data['account_index'] == null) - ? 0 - : int.parse(data['account_index'] as String); - final _addresses = data['addresses'] as List ?? []; - final addresses = []; - final balance = BitcoinBalance.fromJSON(data['balance'] as String) ?? - BitcoinBalance(confirmed: 0, unconfirmed: 0); - - _addresses.forEach((Object el) { - if (el is String) { - addresses.add(BitcoinAddressRecord.fromJSON(el)); - } - }); - - return BitcoinWalletBase.build( - dirPath: dirPath, - mnemonic: mnemonic, - password: password, - name: name, - accountIndex: accountIndex, - initialAddresses: addresses, - initialBalance: balance, - walletInfo: walletInfo); - } - - static BitcoinWallet build( +abstract class BitcoinWalletBase extends ElectrumWallet with Store { + BitcoinWalletBase( {@required String mnemonic, @required String password, - @required String name, - @required String dirPath, @required WalletInfo walletInfo, List initialAddresses, - BitcoinBalance initialBalance, - int accountIndex = 0}) { - final walletPath = '$dirPath/$name'; - final eclient = ElectrumClient(); - final history = BitcoinTransactionHistory( - eclient: eclient, dirPath: dirPath, password: password); + ElectrumBalance initialBalance, + int accountIndex = 0}) + : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + networkType: bitcoin.bitcoin, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + accountIndex: accountIndex); - return BitcoinWallet._internal( - eclient: eclient, - path: walletPath, - mnemonic: mnemonic, + static Future open({ + @required String name, + @required WalletInfo walletInfo, + @required String password, + }) async { + final snp = ElectrumWallletSnapshot(name, walletInfo.type, password); + await snp.load(); + return BitcoinWallet( + mnemonic: snp.mnemonic, password: password, - accountIndex: accountIndex, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - transactionHistory: history, - walletInfo: walletInfo); - } - - static int estimatedTransactionSize(int inputsCount, int outputsCounts) => - inputsCount * 146 + outputsCounts * 33 + 8; - - @override - final BitcoinTransactionHistory transactionHistory; - final String path; - final bitcoin.HDWallet hd; - final ElectrumClient eclient; - final String mnemonic; - - List _unspent; - - @override - @observable - String address; - - @override - @observable - BitcoinBalance balance; - - @override - @observable - SyncStatus syncStatus; - - ObservableList addresses; - - List get scriptHashes => - addresses.map((addr) => scriptHash(addr.address)).toList(); - - String get xpub => hd.base58; - - @override - String get seed => mnemonic; - - @override - BitcoinWalletKeys get keys => BitcoinWalletKeys( - wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey); - - final String _password; - List _feeRates; - int _accountIndex; - Map> _scripthashesUpdateSubject; - - Future init() async { - if (addresses.isEmpty || addresses.length < 33) { - final addressesCount = 33 - addresses.length; - await generateNewAddresses(addressesCount, startIndex: addresses.length); - } - - address = addresses[_accountIndex].address; - transactionHistory.wallet = this; - await transactionHistory.init(); - } - - @action - Future nextAddress() async { - _accountIndex += 1; - - if (_accountIndex >= addresses.length) { - _accountIndex = 0; - } - - address = addresses[_accountIndex].address; - - await save(); - } - - Future generateNewAddress() async { - _accountIndex += 1; - final address = BitcoinAddressRecord(_getAddress(index: _accountIndex), - index: _accountIndex); - addresses.add(address); - - await save(); - - return address; - } - - Future> generateNewAddresses(int count, - {int startIndex = 0}) async { - final list = []; - - for (var i = startIndex; i < count + startIndex; i++) { - final address = BitcoinAddressRecord(_getAddress(index: i), index: i); - list.add(address); - } - - addresses.addAll(list); - await save(); - - return list; - } - - Future updateAddress(String address) async { - for (final addr in addresses) { - if (addr.address == address) { - await save(); - break; - } - } - } - - @action - @override - Future startSync() async { - try { - syncStatus = StartingSyncStatus(); - transactionHistory.updateAsync(onFinished: () { - print('transactionHistory update finished!'); - transactionHistory.save(); - }); - _subscribeForUpdates(); - await _updateBalance(); - await _updateUnspent(); - _feeRates = await eclient.feeRates(); - - Timer.periodic(const Duration(minutes: 1), - (timer) async => _feeRates = await eclient.feeRates()); - - syncStatus = SyncedSyncStatus(); - } catch (e) { - print(e.toString()); - syncStatus = FailedSyncStatus(); - } - } - - @action - @override - Future connectToNode({@required Node node}) async { - try { - syncStatus = ConnectingSyncStatus(); - await eclient.connectToUri(node.uri); - eclient.onConnectionStatusChange = (bool isConnected) { - if (!isConnected) { - syncStatus = LostConnectionSyncStatus(); - } - }; - syncStatus = ConnectedSyncStatus(); - } catch (e) { - print(e.toString()); - syncStatus = FailedSyncStatus(); - } + walletInfo: walletInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + accountIndex: snp.accountIndex); } @override - Future createTransaction( - Object credentials) async { - const minAmount = 546; - final transactionCredentials = credentials as BitcoinTransactionCredentials; - final inputs = []; - final allAmountFee = - calculateEstimatedFee(transactionCredentials.priority, null); - final allAmount = balance.confirmed - allAmountFee; - var fee = 0; - final credentialsAmount = transactionCredentials.amount != null - ? stringDoubleToBitcoinAmount(transactionCredentials.amount) - : 0; - final amount = transactionCredentials.amount == null || - allAmount - credentialsAmount < minAmount - ? allAmount - : credentialsAmount; - final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin); - final changeAddress = address; - var leftAmount = amount; - var totalInputAmount = 0; - - if (_unspent.isEmpty) { - await _updateUnspent(); - } - - for (final utx in _unspent) { - leftAmount = leftAmount - utx.value; - totalInputAmount += utx.value; - inputs.add(utx); - - if (leftAmount <= 0) { - break; - } - } - - if (inputs.isEmpty) { - throw BitcoinTransactionNoInputsException(); - } - - final totalAmount = amount + fee; - fee = transactionCredentials.amount != null - ? feeAmountForPriority(transactionCredentials.priority, inputs.length, - amount == allAmount ? 1 : 2) - : allAmountFee; - - if (totalAmount > balance.confirmed) { - throw BitcoinTransactionWrongBalanceException(); - } - - if (amount <= 0 || totalInputAmount < amount) { - throw BitcoinTransactionWrongBalanceException(); - } - - txb.setVersion(1); - - inputs.forEach((input) { - if (input.isP2wpkh) { - final p2wpkh = bitcoin - .P2WPKH( - data: generatePaymentData(hd: hd, index: input.address.index), - network: bitcoin.bitcoin) - .data; - - txb.addInput(input.hash, input.vout, null, p2wpkh.output); - } else { - txb.addInput(input.hash, input.vout); - } - }); - - txb.addOutput( - addressToOutputScript(transactionCredentials.address), amount); - - final estimatedSize = estimatedTransactionSize(inputs.length, 2); - final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize; - final changeValue = totalInputAmount - amount - feeAmount; - - if (changeValue > minAmount) { - txb.addOutput(changeAddress, changeValue); - } - - for (var i = 0; i < inputs.length; i++) { - final input = inputs[i]; - final keyPair = generateKeyPair(hd: hd, index: input.address.index); - final witnessValue = input.isP2wpkh ? input.value : null; - - txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue); - } - - return PendingBitcoinTransaction(txb.build(), - eclient: eclient, amount: amount, fee: fee) - ..addListener((transaction) async { - transactionHistory.addOne(transaction); - await _updateBalance(); - }); - } - - String toJSON() => json.encode({ - 'mnemonic': mnemonic, - 'account_index': _accountIndex.toString(), - 'addresses': addresses.map((addr) => addr.toJSON()).toList(), - 'balance': balance?.toJSON() - }); - - int feeRate(TransactionPriority priority) { - if (priority is BitcoinTransactionPriority) { - return _feeRates[priority.raw]; - } - - return 0; - } - - int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, - int outputsCount) => - feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); - - @override - int calculateEstimatedFee(TransactionPriority priority, int amount) { - if (priority is BitcoinTransactionPriority) { - int inputsCount = 0; - - if (amount != null) { - int totalValue = 0; - - for (final input in _unspent) { - if (totalValue >= amount) { - break; - } - - totalValue += input.value; - inputsCount += 1; - } - } else { - inputsCount = _unspent.length; - } - // If send all, then we have no change value - return feeAmountForPriority( - priority, inputsCount, amount != null ? 2 : 1); - } - - return 0; - } - - @override - Future save() async { - await write(path: path, password: _password, data: toJSON()); - await transactionHistory.save(); - } - - bitcoin.ECPair keyPairFor({@required int index}) => - generateKeyPair(hd: hd, index: index); - - @override - Future rescan({int height}) async { - // FIXME: Unimplemented - } - - @override - void close() async { - await eclient.close(); - } - - Future _updateUnspent() async { - final unspent = await Future.wait(addresses.map((address) => eclient - .getListUnspentWithAddress(address.address) - .then((unspent) => unspent - .map((unspent) => BitcoinUnspent.fromJSON(address, unspent))))); - _unspent = unspent.expand((e) => e).toList(); - } - - void _subscribeForUpdates() { - scriptHashes.forEach((sh) async { - await _scripthashesUpdateSubject[sh]?.close(); - _scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh); - _scripthashesUpdateSubject[sh].listen((event) async { - try { - await _updateBalance(); - await _updateUnspent(); - transactionHistory.updateAsync(); - } catch (e) { - print(e.toString()); - } - }); - }); - } - - Future _fetchBalances() async { - final balances = await Future.wait( - scriptHashes.map((sHash) => eclient.getBalance(sHash))); - final balance = balances.fold( - BitcoinBalance(confirmed: 0, unconfirmed: 0), - (BitcoinBalance acc, val) => BitcoinBalance( - confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0), - unconfirmed: - (val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0))); - - return balance; - } - - Future _updateBalance() async { - balance = await _fetchBalances(); - await save(); - } - - String _getAddress({@required int index}) => - generateAddress(hd: hd, index: index); + String getAddress({@required int index, @required bitcoin.HDWallet hd}) => + generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); } diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 8e3035a2c..8b09accd3 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; -import 'package:cake_wallet/bitcoin/file.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_service.dart'; @@ -19,44 +18,32 @@ class BitcoinWalletService extends WalletService< final Box walletInfoSource; + @override + WalletType getType() => WalletType.bitcoin; + @override Future create(BitcoinNewWalletCredentials credentials) async { - final dirPath = await pathForWalletDir( - type: WalletType.bitcoin, name: credentials.name); - final wallet = BitcoinWalletBase.build( - dirPath: dirPath, + final wallet = BitcoinWallet( mnemonic: generateMnemonic(), password: credentials.password, - name: credentials.name, walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); - return wallet; } @override Future isWalletExit(String name) async => - File(await pathForWallet(name: name, type: WalletType.bitcoin)) - .existsSync(); + File(await pathForWallet(name: name, type: getType())).existsSync(); @override Future openWallet(String name, String password) async { - final walletDirPath = - await pathForWalletDir(name: name, type: WalletType.bitcoin); - final walletPath = '$walletDirPath/$name'; - final walletJSONRaw = await read(path: walletPath, password: password); final walletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(name, WalletType.bitcoin), + (info) => info.id == WalletBase.idFor(name, getType()), orElse: () => null); - final wallet = BitcoinWalletBase.fromJSON( - password: password, - name: name, - dirPath: walletDirPath, - jsonSource: walletJSONRaw, - walletInfo: walletInfo); + final wallet = await BitcoinWalletBase.open( + password: password, name: name, walletInfo: walletInfo); await wallet.init(); - return wallet; } @@ -67,10 +54,8 @@ class BitcoinWalletService extends WalletService< @override Future restoreFromKeys( - BitcoinRestoreWalletFromWIFCredentials credentials) async { - // TODO: implement restoreFromKeys - throw UnimplementedError(); - } + BitcoinRestoreWalletFromWIFCredentials credentials) async => + throw UnimplementedError(); @override Future restoreFromSeed( @@ -79,17 +64,12 @@ class BitcoinWalletService extends WalletService< throw BitcoinMnemonicIsIncorrectException(); } - final dirPath = await pathForWalletDir( - type: WalletType.bitcoin, name: credentials.name); - final wallet = BitcoinWalletBase.build( - dirPath: dirPath, - name: credentials.name, + final wallet = BitcoinWallet( password: credentials.password, mnemonic: credentials.mnemonic, walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); - return wallet; } } diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index 29e85afa5..ec099037d 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; @@ -173,10 +174,11 @@ class ElectrumClient { }); Future>> getListUnspentWithAddress( - String address) => + String address, NetworkType networkType) => call( - method: 'blockchain.scripthash.listunspent', - params: [scriptHash(address)]).then((dynamic result) { + method: 'blockchain.scripthash.listunspent', + params: [scriptHash(address, networkType: networkType)]) + .then((dynamic result) { if (result is List) { return result.map((dynamic val) { if (val is Map) { @@ -259,7 +261,7 @@ class ElectrumClient { if (result is String) { return result; } - print(result); + return ''; }); @@ -303,14 +305,25 @@ class ElectrumClient { }); Future> feeRates() async { - final topDoubleString = await estimatefee(p: 1); - final middleDoubleString = await estimatefee(p: 20); - final bottomDoubleString = await estimatefee(p: 150); - final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); - final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); - final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); + try { + final topDoubleString = await estimatefee(p: 1); + final middleDoubleString = await estimatefee(p: 20); + final bottomDoubleString = await estimatefee(p: 150); + final top = + (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000) + .round(); + final middle = + (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000) + .round(); + final bottom = + (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000) + .round(); - return [bottom, middle, top]; + final res = [bottom, middle, top]; + return res; + } catch (_) { + return []; + } } BehaviorSubject scripthashUpdate(String scripthash) { diff --git a/lib/bitcoin/bitcoin_balance.dart b/lib/bitcoin/electrum_balance.dart similarity index 70% rename from lib/bitcoin/bitcoin_balance.dart rename to lib/bitcoin/electrum_balance.dart index 7d8441250..66e53f921 100644 --- a/lib/bitcoin/bitcoin_balance.dart +++ b/lib/bitcoin/electrum_balance.dart @@ -1,21 +1,20 @@ import 'dart:convert'; - import 'package:flutter/foundation.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/entities/balance.dart'; -class BitcoinBalance extends Balance { - const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) +class ElectrumBalance extends Balance { + const ElectrumBalance({@required this.confirmed, @required this.unconfirmed}) : super(confirmed, unconfirmed); - factory BitcoinBalance.fromJSON(String jsonSource) { + factory ElectrumBalance.fromJSON(String jsonSource) { if (jsonSource == null) { return null; } final decoded = json.decode(jsonSource) as Map; - return BitcoinBalance( + return ElectrumBalance( confirmed: decoded['confirmed'] as int ?? 0, unconfirmed: decoded['unconfirmed'] as int ?? 0); } @@ -24,7 +23,8 @@ class BitcoinBalance extends Balance { final int unconfirmed; @override - String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed); + String get formattedAvailableBalance => + bitcoinAmountToString(amount: confirmed); @override String get formattedAdditionalBalance => diff --git a/lib/bitcoin/electrum_transaction_history.dart b/lib/bitcoin/electrum_transaction_history.dart new file mode 100644 index 000000000..1d5e894c0 --- /dev/null +++ b/lib/bitcoin/electrum_transaction_history.dart @@ -0,0 +1,98 @@ +import 'dart:convert'; +import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; +import 'package:cake_wallet/bitcoin/file.dart'; +import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart'; + +part 'electrum_transaction_history.g.dart'; + +const _transactionsHistoryFileName = 'transactions.json'; + +class ElectrumTransactionHistory = ElectrumTransactionHistoryBase + with _$ElectrumTransactionHistory; + +abstract class ElectrumTransactionHistoryBase + extends TransactionHistoryBase with Store { + ElectrumTransactionHistoryBase( + {@required this.walletInfo, @required String password}) + : _password = password, + _height = 0 { + transactions = ObservableMap(); + } + + final WalletInfo walletInfo; + final String _password; + int _height; + + Future init() async => await _load(); + + @override + void addOne(ElectrumTransactionInfo transaction) => + transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); + + @override + Future save() async { + try { + final dirPath = + await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$_transactionsHistoryFileName'; + final data = + json.encode({'height': _height, 'transactions': transactions}); + await writeData(path: path, password: _password, data: data); + } catch (e) { + print('Error while save bitcoin transaction history: ${e.toString()}'); + } + } + + 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 = ElectrumTransactionInfo.fromJson(val, walletInfo.type); + _updateOrInsert(tx); + } + }); + + _height = content['height'] as int; + } catch (e) { + print(e); + } + } + + void _updateOrInsert(ElectrumTransactionInfo transaction) { + if (transaction.id == null) { + return; + } + + if (transactions[transaction.id] == null) { + transactions[transaction.id] = transaction; + } else { + final originalTx = transactions[transaction.id]; + originalTx.confirmations = transaction.confirmations; + originalTx.amount = transaction.amount; + originalTx.height = transaction.height; + originalTx.date ??= transaction.date; + originalTx.isPending = transaction.isPending; + } + } +} diff --git a/lib/bitcoin/bitcoin_transaction_info.dart b/lib/bitcoin/electrum_transaction_info.dart similarity index 84% rename from lib/bitcoin/bitcoin_transaction_info.dart rename to lib/bitcoin/electrum_transaction_info.dart index fb1400c5e..7aeebad1b 100644 --- a/lib/bitcoin/bitcoin_transaction_info.dart +++ b/lib/bitcoin/electrum_transaction_info.dart @@ -6,9 +6,10 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/entities/transaction_direction.dart'; import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/entities/format_amount.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; -class BitcoinTransactionInfo extends TransactionInfo { - BitcoinTransactionInfo( +class ElectrumTransactionInfo extends TransactionInfo { + ElectrumTransactionInfo(this.type, {@required String id, @required int height, @required int amount, @@ -27,7 +28,8 @@ class BitcoinTransactionInfo extends TransactionInfo { this.confirmations = confirmations; } - factory BitcoinTransactionInfo.fromElectrumVerbose(Map obj, + factory ElectrumTransactionInfo.fromElectrumVerbose( + Map obj, WalletType type, {@required List addresses, @required int height}) { final addressesSet = addresses.map((addr) => addr.address).toSet(); final id = obj['txid'] as String; @@ -47,7 +49,8 @@ class BitcoinTransactionInfo extends TransactionInfo { final out = vin['tx']['vout'][vout] as Map; final outAddresses = (out['scriptPubKey']['addresses'] as List)?.toSet(); - inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString()); + inputsAmount += + stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString()); if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) { direction = TransactionDirection.outgoing; @@ -58,7 +61,8 @@ class BitcoinTransactionInfo extends TransactionInfo { final outAddresses = out['scriptPubKey']['addresses'] as List ?? []; final ntrs = outAddresses.toSet().intersection(addressesSet); - final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString()); + final value = stringDoubleToBitcoinAmount( + (out['value'] as double ?? 0.0).toString()); totalOutAmount += value; if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || @@ -69,7 +73,7 @@ class BitcoinTransactionInfo extends TransactionInfo { final fee = inputsAmount - totalOutAmount; - return BitcoinTransactionInfo( + return ElectrumTransactionInfo(type, id: id, height: height, isPending: false, @@ -80,7 +84,7 @@ class BitcoinTransactionInfo extends TransactionInfo { confirmations: confirmations); } - factory BitcoinTransactionInfo.fromHexAndHeader(String hex, + factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex, {List addresses, int height, int timestamp, int confirmations}) { final tx = bitcoin.Transaction.fromHex(hex); var exist = false; @@ -104,7 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo { ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now(); - return BitcoinTransactionInfo( + return ElectrumTransactionInfo(type, id: tx.getId(), height: height, isPending: false, @@ -115,8 +119,9 @@ class BitcoinTransactionInfo extends TransactionInfo { confirmations: confirmations); } - factory BitcoinTransactionInfo.fromJson(Map data) { - return BitcoinTransactionInfo( + factory ElectrumTransactionInfo.fromJson( + Map data, WalletType type) { + return ElectrumTransactionInfo(type, id: data['id'] as String, height: data['height'] as int, amount: data['amount'] as int, @@ -127,6 +132,8 @@ class BitcoinTransactionInfo extends TransactionInfo { confirmations: data['confirmations'] as int); } + final WalletType type; + String _fiatAmount; @override @@ -144,8 +151,8 @@ class BitcoinTransactionInfo extends TransactionInfo { @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); - BitcoinTransactionInfo updated(BitcoinTransactionInfo info) { - return BitcoinTransactionInfo( + ElectrumTransactionInfo updated(ElectrumTransactionInfo info) { + return ElectrumTransactionInfo(info.type, id: id, height: info.height, amount: info.amount, diff --git a/lib/bitcoin/electrum_wallet.dart b/lib/bitcoin/electrum_wallet.dart new file mode 100644 index 000000000..bf3dfd542 --- /dev/null +++ b/lib/bitcoin/electrum_wallet.dart @@ -0,0 +1,467 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:mobx/mobx.dart'; +import 'package:rxdart/subjects.dart'; +import 'package:flutter/foundation.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart'; +import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/bitcoin/address_to_output_script.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; +import 'package:cake_wallet/bitcoin/electrum_balance.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart'; +import 'package:cake_wallet/bitcoin/electrum_transaction_history.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart'; +import 'package:cake_wallet/bitcoin/file.dart'; +import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart'; +import 'package:cake_wallet/bitcoin/script_hash.dart'; +import 'package:cake_wallet/bitcoin/utils.dart'; +import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:cake_wallet/entities/node.dart'; +import 'package:cake_wallet/entities/sync_status.dart'; +import 'package:cake_wallet/entities/transaction_priority.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:cake_wallet/bitcoin/electrum.dart'; + +part 'electrum_wallet.g.dart'; + +class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; + +abstract class ElectrumWalletBase extends WalletBase with Store { + ElectrumWalletBase( + {@required String password, + @required WalletInfo walletInfo, + @required List initialAddresses, + @required this.networkType, + @required this.mnemonic, + ElectrumClient electrumClient, + int accountIndex = 0, + ElectrumBalance initialBalance}) + : balance = initialBalance ?? + const ElectrumBalance(confirmed: 0, unconfirmed: 0), + hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic), + network: networkType) + .derivePath("m/0'/0"), + addresses = ObservableList.of( + (initialAddresses ?? []).toSet()), + syncStatus = NotConnectedSyncStatus(), + _password = password, + _accountIndex = accountIndex, + _feeRates = [], + _isTransactionUpdating = false, + super(walletInfo) { + this.electrumClient = electrumClient ?? ElectrumClient(); + this.walletInfo = walletInfo; + transactionHistory = + ElectrumTransactionHistory(walletInfo: walletInfo, password: password); + _unspent = []; + _scripthashesUpdateSubject = {}; + } + + static int estimatedTransactionSize(int inputsCount, int outputsCounts) => + inputsCount * 146 + outputsCounts * 33 + 8; + + final bitcoin.HDWallet hd; + final String mnemonic; + + ElectrumClient electrumClient; + + @override + @observable + String address; + + @override + @observable + ElectrumBalance balance; + + @override + @observable + SyncStatus syncStatus; + + ObservableList addresses; + + List get scriptHashes => addresses + .map((addr) => scriptHash(addr.address, networkType: networkType)) + .toList(); + + String get xpub => hd.base58; + + @override + String get seed => mnemonic; + + bitcoin.NetworkType networkType; + + @override + BitcoinWalletKeys get keys => BitcoinWalletKeys( + wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey); + + final String _password; + List _unspent; + List _feeRates; + int _accountIndex; + Map> _scripthashesUpdateSubject; + bool _isTransactionUpdating; + + Future init() async { + await generateAddresses(); + address = addresses[_accountIndex].address; + await transactionHistory.init(); + } + + @action + Future nextAddress() async { + _accountIndex += 1; + + if (_accountIndex >= addresses.length) { + _accountIndex = 0; + } + + address = addresses[_accountIndex].address; + + await save(); + } + + Future generateAddresses() async { + if (addresses.length < 33) { + final addressesCount = 33 - addresses.length; + await generateNewAddresses(addressesCount, + startIndex: addresses.length, hd: hd); + } + } + + Future generateNewAddress( + {bool isHidden = false, bitcoin.HDWallet hd}) async { + _accountIndex += 1; + final _hd = hd ?? this.hd; + final address = BitcoinAddressRecord( + getAddress(index: _accountIndex, hd: _hd), + index: _accountIndex, + isHidden: isHidden); + addresses.add(address); + await save(); + return address; + } + + Future> generateNewAddresses(int count, + {int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async { + final list = []; + + for (var i = startIndex; i < count + startIndex; i++) { + final address = BitcoinAddressRecord(getAddress(index: i, hd: hd), + index: i, isHidden: isHidden); + list.add(address); + } + + addresses.addAll(list); + await save(); + return list; + } + + Future updateAddress(String address) async { + for (final addr in addresses) { + if (addr.address == address) { + await save(); + break; + } + } + } + + @action + @override + Future startSync() async { + try { + syncStatus = StartingSyncStatus(); + updateTransactions(); + _subscribeForUpdates(); + await _updateBalance(); + await _updateUnspent(); + _feeRates = await electrumClient.feeRates(); + + Timer.periodic(const Duration(minutes: 1), + (timer) async => _feeRates = await electrumClient.feeRates()); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + print(e.toString()); + syncStatus = FailedSyncStatus(); + } + } + + @action + @override + Future connectToNode({@required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + await electrumClient.connectToUri(node.uri); + electrumClient.onConnectionStatusChange = (bool isConnected) { + if (!isConnected) { + syncStatus = LostConnectionSyncStatus(); + } + }; + syncStatus = ConnectedSyncStatus(); + } catch (e) { + print(e.toString()); + syncStatus = FailedSyncStatus(); + } + } + + @override + Future createTransaction( + Object credentials) async { + const minAmount = 546; + final transactionCredentials = credentials as BitcoinTransactionCredentials; + final inputs = []; + final allAmountFee = + calculateEstimatedFee(transactionCredentials.priority, null); + final allAmount = balance.confirmed - allAmountFee; + var fee = 0; + final credentialsAmount = transactionCredentials.amount != null + ? stringDoubleToBitcoinAmount(transactionCredentials.amount) + : 0; + final amount = transactionCredentials.amount == null || + allAmount - credentialsAmount < minAmount + ? allAmount + : credentialsAmount; + final txb = bitcoin.TransactionBuilder(network: networkType); + final changeAddress = address; + var leftAmount = amount; + var totalInputAmount = 0; + + if (_unspent.isEmpty) { + await _updateUnspent(); + } + + for (final utx in _unspent) { + leftAmount = leftAmount - utx.value; + totalInputAmount += utx.value; + inputs.add(utx); + + if (leftAmount <= 0) { + break; + } + } + + if (inputs.isEmpty) { + throw BitcoinTransactionNoInputsException(); + } + + final totalAmount = amount + fee; + fee = transactionCredentials.amount != null + ? feeAmountForPriority(transactionCredentials.priority, inputs.length, + amount == allAmount ? 1 : 2) + : allAmountFee; + + if (totalAmount > balance.confirmed) { + throw BitcoinTransactionWrongBalanceException(); + } + + if (amount <= 0 || totalInputAmount < amount) { + throw BitcoinTransactionWrongBalanceException(); + } + + txb.setVersion(1); + + inputs.forEach((input) { + if (input.isP2wpkh) { + final p2wpkh = bitcoin + .P2WPKH( + data: generatePaymentData(hd: hd, index: input.address.index), + network: networkType) + .data; + + txb.addInput(input.hash, input.vout, null, p2wpkh.output); + } else { + txb.addInput(input.hash, input.vout); + } + }); + + txb.addOutput( + addressToOutputScript(transactionCredentials.address, networkType), + amount); + + final estimatedSize = estimatedTransactionSize(inputs.length, 2); + final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize; + final changeValue = totalInputAmount - amount - feeAmount; + + if (changeValue > minAmount) { + txb.addOutput(changeAddress, changeValue); + } + + for (var i = 0; i < inputs.length; i++) { + final input = inputs[i]; + final keyPair = generateKeyPair( + hd: hd, index: input.address.index, network: networkType); + final witnessValue = input.isP2wpkh ? input.value : null; + + txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue); + } + + return PendingBitcoinTransaction(txb.build(), type, + electrumClient: electrumClient, amount: amount, fee: fee) + ..addListener((transaction) async { + transactionHistory.addOne(transaction); + await _updateBalance(); + }); + } + + String toJSON() => json.encode({ + 'mnemonic': mnemonic, + 'account_index': _accountIndex.toString(), + 'addresses': addresses.map((addr) => addr.toJSON()).toList(), + 'balance': balance?.toJSON() + }); + + int feeRate(TransactionPriority priority) { + if (priority is BitcoinTransactionPriority) { + return _feeRates[priority.raw]; + } + + return 0; + } + + int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, + int outputsCount) => + feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); + + @override + int calculateEstimatedFee(TransactionPriority priority, int amount) { + if (priority is BitcoinTransactionPriority) { + int inputsCount = 0; + + if (amount != null) { + int totalValue = 0; + + for (final input in _unspent) { + if (totalValue >= amount) { + break; + } + + totalValue += input.value; + inputsCount += 1; + } + } else { + inputsCount = _unspent.length; + } + // If send all, then we have no change value + return feeAmountForPriority( + priority, inputsCount, amount != null ? 2 : 1); + } + + return 0; + } + + @override + Future save() async { + final path = await makePath(); + await write(path: path, password: _password, data: toJSON()); + await transactionHistory.save(); + } + + bitcoin.ECPair keyPairFor({@required int index}) => + generateKeyPair(hd: hd, index: index, network: networkType); + + @override + Future rescan({int height}) async => throw UnimplementedError(); + + @override + Future close() async { + try { + await electrumClient?.close(); + } catch (_) {} + } + + String getAddress({@required int index, @required bitcoin.HDWallet hd}) => ''; + + Future makePath() async => + pathForWallet(name: walletInfo.name, type: walletInfo.type); + + Future _updateUnspent() async { + final unspent = await Future.wait(addresses.map((address) => electrumClient + .getListUnspentWithAddress(address.address, networkType) + .then((unspent) => unspent + .map((unspent) => BitcoinUnspent.fromJSON(address, unspent))))); + _unspent = unspent.expand((e) => e).toList(); + } + + Future fetchTransactionInfo( + {@required String hash, @required int height}) async { + final tx = await electrumClient.getTransactionExpanded(hash: hash); + return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type, + height: height, addresses: addresses); + } + + @override + Future> fetchTransactions() async { + final histories = + scriptHashes.map((scriptHash) => electrumClient.getHistory(scriptHash)); + final _historiesWithDetails = await Future.wait(histories) + .then((histories) => histories.expand((i) => i).toList()) + .then((histories) => histories.map((tx) => fetchTransactionInfo( + hash: tx['tx_hash'] as String, height: tx['height'] as int))); + final historiesWithDetails = await Future.wait(_historiesWithDetails); + + return historiesWithDetails.fold>( + {}, (acc, tx) { + acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx; + return acc; + }); + } + + Future updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (e) { + print(e); + _isTransactionUpdating = false; + } + } + + void _subscribeForUpdates() { + scriptHashes.forEach((sh) async { + await _scripthashesUpdateSubject[sh]?.close(); + _scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh); + _scripthashesUpdateSubject[sh].listen((event) async { + try { + await _updateBalance(); + await _updateUnspent(); + await updateTransactions(); + } catch (e) { + print(e.toString()); + } + }); + }); + } + + Future _fetchBalances() async { + final balances = await Future.wait( + scriptHashes.map((sh) => electrumClient.getBalance(sh))); + final balance = balances.fold( + ElectrumBalance(confirmed: 0, unconfirmed: 0), + (ElectrumBalance acc, val) => ElectrumBalance( + confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0), + unconfirmed: + (val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0))); + + return balance; + } + + Future _updateBalance() async { + balance = await _fetchBalances(); + await save(); + } +} diff --git a/lib/bitcoin/electrum_wallet_snapshot.dart b/lib/bitcoin/electrum_wallet_snapshot.dart new file mode 100644 index 000000000..9347157ad --- /dev/null +++ b/lib/bitcoin/electrum_wallet_snapshot.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; +import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart'; +import 'package:cake_wallet/bitcoin/electrum_balance.dart'; +import 'package:cake_wallet/bitcoin/file.dart'; +import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; + +class ElectrumWallletSnapshot { + ElectrumWallletSnapshot(this.name, this.type, this.password); + + final String name; + final String password; + final WalletType type; + + String mnemonic; + List addresses; + ElectrumBalance balance; + int accountIndex; + + Future load() async { + try { + final path = await pathForWallet(name: name, type: type); + final jsonSource = await read(path: path, password: password); + final data = json.decode(jsonSource) as Map; + final addressesTmp = data['addresses'] as List ?? []; + mnemonic = data['mnemonic'] as String; + addresses = addressesTmp + .whereType() + .map((addr) => BitcoinAddressRecord.fromJSON(addr)) + .toList(); + balance = ElectrumBalance.fromJSON(data['balance'] as String) ?? + ElectrumBalance(confirmed: 0, unconfirmed: 0); + accountIndex = 0; + + try { + accountIndex = int.parse(data['account_index'] as String); + } catch (_) {} + } catch (e) { + print(e); + } + } +} diff --git a/lib/bitcoin/litecoin_network.dart b/lib/bitcoin/litecoin_network.dart new file mode 100644 index 000000000..d7ad2f837 --- /dev/null +++ b/lib/bitcoin/litecoin_network.dart @@ -0,0 +1,9 @@ +import 'package:bitcoin_flutter/bitcoin_flutter.dart'; + +final litecoinNetwork = NetworkType( + messagePrefix: '\x19Litecoin Signed Message:\n', + bech32: 'ltc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0); diff --git a/lib/bitcoin/litecoin_wallet.dart b/lib/bitcoin/litecoin_wallet.dart new file mode 100644 index 000000000..89902ad27 --- /dev/null +++ b/lib/bitcoin/litecoin_wallet.dart @@ -0,0 +1,88 @@ +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cake_wallet/entities/transaction_priority.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart'; +import 'package:cake_wallet/bitcoin/electrum_balance.dart'; +import 'package:cake_wallet/bitcoin/litecoin_network.dart'; +import 'package:cake_wallet/bitcoin/utils.dart'; + +part 'litecoin_wallet.g.dart'; + +class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet; + +abstract class LitecoinWalletBase extends ElectrumWallet with Store { + LitecoinWalletBase( + {@required String mnemonic, + @required String password, + @required WalletInfo walletInfo, + List initialAddresses, + ElectrumBalance initialBalance, + int accountIndex = 0}) + : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + networkType: litecoinNetwork, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + accountIndex: accountIndex); + + static Future open({ + @required String name, + @required WalletInfo walletInfo, + @required String password, + }) async { + final snp = ElectrumWallletSnapshot(name, walletInfo.type, password); + await snp.load(); + return LitecoinWallet( + mnemonic: snp.mnemonic, + password: password, + walletInfo: walletInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + accountIndex: snp.accountIndex); + } + + @override + String getAddress({@required int index, @required bitcoin.HDWallet hd}) => + generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); + + @override + Future generateAddresses() async { + if (addresses.length < 33) { + final addressesCount = 22 - addresses.length; + await generateNewAddresses(addressesCount, + hd: hd, startIndex: addresses.length); + + final changeRoot = bitcoin.HDWallet.fromSeed( + mnemonicToSeedBytes(mnemonic), + network: networkType) + .derivePath("m/0'/1"); + + await generateNewAddresses(11, + startIndex: 0, hd: changeRoot, isHidden: true); + } + } + + @override + int feeRate(TransactionPriority priority) { + if (priority is BitcoinTransactionPriority) { + switch (priority) { + case BitcoinTransactionPriority.slow: + return 1; + case BitcoinTransactionPriority.medium: + return 2; + case BitcoinTransactionPriority.fast: + return 3; + } + } + + return 0; + } +} diff --git a/lib/bitcoin/litecoin_wallet_service.dart b/lib/bitcoin/litecoin_wallet_service.dart new file mode 100644 index 000000000..04533640e --- /dev/null +++ b/lib/bitcoin/litecoin_wallet_service.dart @@ -0,0 +1,76 @@ +import 'dart:io'; +import 'package:hive/hive.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart'; +import 'package:cake_wallet/bitcoin/litecoin_wallet.dart'; +import 'package:cake_wallet/core/wallet_service.dart'; +import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:cake_wallet/core/wallet_base.dart'; + +class LitecoinWalletService extends WalletService< + BitcoinNewWalletCredentials, + BitcoinRestoreWalletFromSeedCredentials, + BitcoinRestoreWalletFromWIFCredentials> { + LitecoinWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + @override + WalletType getType() => WalletType.litecoin; + + @override + Future create(BitcoinNewWalletCredentials credentials) async { + final wallet = LitecoinWallet( + mnemonic: generateMnemonic(), + password: credentials.password, + walletInfo: credentials.walletInfo); + await wallet.save(); + await wallet.init(); + + 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()), + orElse: () => null); + final wallet = await LitecoinWalletBase.open( + password: password, name: name, walletInfo: walletInfo); + await wallet.init(); + return wallet; + } + + @override + Future remove(String wallet) async => + File(await pathForWalletDir(name: wallet, type: getType())) + .delete(recursive: true); + + @override + Future restoreFromKeys( + BitcoinRestoreWalletFromWIFCredentials credentials) async => + throw UnimplementedError(); + + @override + Future restoreFromSeed( + BitcoinRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw BitcoinMnemonicIsIncorrectException(); + } + + final wallet = LitecoinWallet( + password: credentials.password, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo); + await wallet.save(); + await wallet.init(); + return wallet; + } +} diff --git a/lib/bitcoin/pending_bitcoin_transaction.dart b/lib/bitcoin/pending_bitcoin_transaction.dart index edd5a0450..ec3e3a985 100644 --- a/lib/bitcoin/pending_bitcoin_transaction.dart +++ b/lib/bitcoin/pending_bitcoin_transaction.dart @@ -1,18 +1,22 @@ -import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; -import 'package:cake_wallet/entities/transaction_direction.dart'; import 'package:flutter/foundation.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cake_wallet/core/pending_transaction.dart'; import 'package:cake_wallet/bitcoin/electrum.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; +import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart'; +import 'package:cake_wallet/entities/transaction_direction.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; class PendingBitcoinTransaction with PendingTransaction { - PendingBitcoinTransaction(this._tx, - {@required this.eclient, @required this.amount, @required this.fee}) - : _listeners = []; + PendingBitcoinTransaction(this._tx, this.type, + {@required this.electrumClient, + @required this.amount, + @required this.fee}) + : _listeners = []; + final WalletType type; final bitcoin.Transaction _tx; - final ElectrumClient eclient; + final ElectrumClient electrumClient; final int amount; final int fee; @@ -25,24 +29,25 @@ class PendingBitcoinTransaction with PendingTransaction { @override String get feeFormatted => bitcoinAmountToString(amount: fee); - final List _listeners; + final List _listeners; @override Future commit() async { - await eclient.broadcastTransaction(transactionRaw: _tx.toHex()); + await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex()); _listeners?.forEach((listener) => listener(transactionInfo())); } void addListener( - void Function(BitcoinTransactionInfo transaction) listener) => + void Function(ElectrumTransactionInfo transaction) listener) => _listeners.add(listener); - BitcoinTransactionInfo transactionInfo() => BitcoinTransactionInfo( + ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, id: id, height: 0, amount: amount, direction: TransactionDirection.outgoing, date: DateTime.now(), isPending: true, - confirmations: 0); + confirmations: 0, + fee: fee); } diff --git a/lib/bitcoin/script_hash.dart b/lib/bitcoin/script_hash.dart index b252a0700..b1025f66b 100644 --- a/lib/bitcoin/script_hash.dart +++ b/lib/bitcoin/script_hash.dart @@ -1,18 +1,20 @@ +import 'package:flutter/foundation.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:crypto/crypto.dart'; -String scriptHash(String address) { - final outputScript = bitcoin.Address.addressToOutputScript(address); - final splitted = sha256.convert(outputScript).toString().split(''); +String scriptHash(String address, {@required bitcoin.NetworkType networkType}) { + final outputScript = + bitcoin.Address.addressToOutputScript(address, networkType); + final parts = sha256.convert(outputScript).toString().split(''); var res = ''; - for (var i = splitted.length - 1; i >= 0; i--) { - final char = splitted[i]; + for (var i = parts.length - 1; i >= 0; i--) { + final char = parts[i]; i--; - final nextChar = splitted[i]; + final nextChar = parts[i]; res += nextChar; res += char; } return res; -} \ No newline at end of file +} diff --git a/lib/bitcoin/utils.dart b/lib/bitcoin/utils.dart index 257c8b176..3a638555a 100644 --- a/lib/bitcoin/utils.dart +++ b/lib/bitcoin/utils.dart @@ -13,14 +13,43 @@ bitcoin.ECPair generateKeyPair( {@required bitcoin.HDWallet hd, @required int index, bitcoin.NetworkType network}) => - bitcoin.ECPair.fromWIF(hd.derive(index).wif, - network: network ?? bitcoin.bitcoin); + bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network); -String generateAddress({@required bitcoin.HDWallet hd, @required int index}) => +String generateP2WPKHAddress( + {@required bitcoin.HDWallet hd, + @required int index, + bitcoin.NetworkType networkType}) => bitcoin .P2WPKH( data: PaymentData( pubkey: - Uint8List.fromList(HEX.decode(hd.derive(index).pubKey)))) + Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))), + network: networkType) + .data + .address; + +String generateP2WPKHAddressByPath( + {@required bitcoin.HDWallet hd, + @required String path, + bitcoin.NetworkType networkType}) => + bitcoin + .P2WPKH( + data: PaymentData( + pubkey: + Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey))), + network: networkType) + .data + .address; + +String generateP2PKHAddress( + {@required bitcoin.HDWallet hd, + @required int index, + bitcoin.NetworkType networkType}) => + bitcoin + .P2PKH( + data: PaymentData( + pubkey: + Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))), + network: networkType) .data .address; diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 273a6ca14..f481d0b1d 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -72,7 +72,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.eth: return [42]; case CryptoCurrency.ltc: - return [34]; + return [34, 43]; case CryptoCurrency.nano: return [64, 65]; case CryptoCurrency.trx: diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index fa984f3dd..09edc6c1f 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -8,7 +8,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:cryptography/cryptography.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:archive/archive_io.dart'; -import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 1744f3df8..9bda4e7ad 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -3,7 +3,6 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; -import 'package:cake_wallet/entities/currency_formatter.dart'; const fiatApiAuthority = 'fiat-api.cakewallet.com'; const fiatApiPath = '/v1/rates'; @@ -15,9 +14,8 @@ Future _fetchPrice(Map args) async { try { final fiatStringified = fiat.toString(); - final uri = - Uri.https(fiatApiAuthority, fiatApiPath, - {'convert': fiatStringified}); + final uri = Uri.https(fiatApiAuthority, fiatApiPath, + {'convert': fiatStringified}); final response = await get(uri.toString()); if (response.statusCode != 200) { @@ -28,7 +26,7 @@ Future _fetchPrice(Map args) async { final data = responseJSON['data'] as List; for (final item in data) { - if (item['symbol'] == cryptoToString(crypto)) { + if (item['symbol'] == crypto.title) { price = item['quote'][fiatStringified]['price'] as double; break; } @@ -45,6 +43,7 @@ Future _fetchPriceAsync( compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto}); class FiatConversionService { - static Future fetchPrice(CryptoCurrency crypto, FiatCurrency fiat) async => + static Future fetchPrice( + CryptoCurrency crypto, FiatCurrency fiat) async => await _fetchPriceAsync(crypto, fiat); } diff --git a/lib/core/generate_wallet_password.dart b/lib/core/generate_wallet_password.dart index abf2e6dac..9f126d8c2 100644 --- a/lib/core/generate_wallet_password.dart +++ b/lib/core/generate_wallet_password.dart @@ -4,9 +4,9 @@ import 'package:cake_wallet/entities/wallet_type.dart'; String generateWalletPassword(WalletType type) { switch (type) { - case WalletType.bitcoin: - return generateKey(); - default: + case WalletType.monero: return Uuid().v4(); + default: + return generateKey(); } } diff --git a/lib/core/node_port_validator.dart b/lib/core/node_port_validator.dart index 16be00dde..89d4ec1da 100644 --- a/lib/core/node_port_validator.dart +++ b/lib/core/node_port_validator.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/validator.dart'; diff --git a/lib/core/transaction_history.dart b/lib/core/transaction_history.dart index dd91fb203..ee386e392 100644 --- a/lib/core/transaction_history.dart +++ b/lib/core/transaction_history.dart @@ -3,43 +3,50 @@ import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/transaction_info.dart'; abstract class TransactionHistoryBase { - TransactionHistoryBase() : _isUpdating = false; + TransactionHistoryBase(); + // : _isUpdating = false; @observable ObservableMap transactions; - bool _isUpdating; + Future save(); - @action - Future update() async { - if (_isUpdating) { - return; - } + void addOne(TransactionType transaction); - try { - _isUpdating = true; - final _transactions = await fetchTransactions(); - transactions.keys - .toSet() - .difference(_transactions.keys.toSet()) - .forEach((k) => transactions.remove(k)); - _transactions.forEach((key, value) => transactions[key] = value); - _isUpdating = false; - } catch (e) { - _isUpdating = false; - rethrow; - } - } + void addMany(Map transactions); - void updateAsync({void Function() onFinished}) { - fetchTransactionsAsync( - (transaction) => transactions[transaction.id] = transaction, - onFinished: onFinished); - } + // bool _isUpdating; - void fetchTransactionsAsync( - void Function(TransactionType transaction) onTransactionLoaded, - {void Function() onFinished}); + // @action + // Future update() async { + // if (_isUpdating) { + // return; + // } - Future> fetchTransactions(); + // try { + // _isUpdating = true; + // final _transactions = await fetchTransactions(); + // transactions.keys + // .toSet() + // .difference(_transactions.keys.toSet()) + // .forEach((k) => transactions.remove(k)); + // _transactions.forEach((key, value) => transactions[key] = value); + // _isUpdating = false; + // } catch (e) { + // _isUpdating = false; + // rethrow; + // } + // } + + // void updateAsync({void Function() onFinished}) { + // fetchTransactionsAsync( + // (transaction) => transactions[transaction.id] = transaction, + // onFinished: onFinished); + // } + + // void fetchTransactionsAsync( + // void Function(TransactionType transaction) onTransactionLoaded, + // {void Function() onFinished}); + + // Future> fetchTransactions(); } diff --git a/lib/core/wallet_base.dart b/lib/core/wallet_base.dart index 8369ed113..ced918342 100644 --- a/lib/core/wallet_base.dart +++ b/lib/core/wallet_base.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:flutter/foundation.dart'; import 'package:cake_wallet/entities/wallet_info.dart'; @@ -11,7 +12,10 @@ import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/entities/node.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; -abstract class WalletBase { +abstract class WalletBase< + BalanceType extends Balance, + HistoryType extends TransactionHistoryBase, + TransactionType extends TransactionInfo> { WalletBase(this.walletInfo); static String idFor(String name, WalletType type) => @@ -41,7 +45,7 @@ abstract class WalletBase { Object get keys; - TransactionHistoryBase transactionHistory; + HistoryType transactionHistory; Future connectToNode({@required Node node}); @@ -51,6 +55,12 @@ abstract class WalletBase { int calculateEstimatedFee(TransactionPriority priority, int amount); + // void fetchTransactionsAsync( + // void Function(TransactionType transaction) onTransactionLoaded, + // {void Function() onFinished}); + + Future> fetchTransactions(); + Future save(); Future rescan({int height}); diff --git a/lib/core/wallet_service.dart b/lib/core/wallet_service.dart index f26fc10c2..2d207c31e 100644 --- a/lib/core/wallet_service.dart +++ b/lib/core/wallet_service.dart @@ -1,8 +1,11 @@ import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_credentials.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; abstract class WalletService { + WalletType getType(); + Future create(N credentials); Future restoreFromSeed(RFS credentials); diff --git a/lib/di.dart b/lib/di.dart index 66ac22231..1885a54b8 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart'; +import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart'; import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/wallet_service.dart'; import 'package:cake_wallet/entities/biometric_auth.dart'; @@ -443,6 +444,8 @@ Future setup( getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource)); + getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource)); + getIt.registerFactoryParam( (WalletType param1, __) { switch (param1) { @@ -450,6 +453,8 @@ Future setup( return getIt.get(); case WalletType.bitcoin: return getIt.get(); + case WalletType.litecoin: + return getIt.get(); default: return null; } diff --git a/lib/entities/currency_for_wallet_type.dart b/lib/entities/currency_for_wallet_type.dart index 840e75dd1..7f13d12b0 100644 --- a/lib/entities/currency_for_wallet_type.dart +++ b/lib/entities/currency_for_wallet_type.dart @@ -7,7 +7,9 @@ CryptoCurrency currencyForWalletType(WalletType type) { return CryptoCurrency.btc; case WalletType.monero: return CryptoCurrency.xmr; + case WalletType.litecoin: + return CryptoCurrency.ltc; default: return null; } -} \ No newline at end of file +} diff --git a/lib/entities/currency_formatter.dart b/lib/entities/currency_formatter.dart deleted file mode 100644 index 40326a7e6..000000000 --- a/lib/entities/currency_formatter.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:cake_wallet/entities/crypto_currency.dart'; - -String cryptoToString(CryptoCurrency crypto) { - switch (crypto) { - case CryptoCurrency.xmr: - return 'XMR'; - case CryptoCurrency.btc: - return 'BTC'; - default: - return ''; - } -} \ No newline at end of file diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 1d8dd37f9..18e8a362a 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -1,11 +1,7 @@ import 'dart:io' show File, Platform; import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cake_wallet/core/generate_wallet_password.dart'; -import 'package:cake_wallet/core/key_service.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/pathForWallet.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; -import 'package:cake_wallet/monero/monero_wallet_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; @@ -24,7 +20,8 @@ import 'package:cake_wallet/exchange/trade.dart'; import 'package:encrypt/encrypt.dart' as encrypt; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; -const cakeWalletElectrumUri = 'electrum.cakewallet.com:50002'; +const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; +const cakeWalletLitecoinElectrumUri = '128.199.34.116:50002'; Future defaultSettingsMigration( {@required int version, @@ -68,6 +65,8 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); await changeBitcoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); + await changeLitecoinCurrentElectrumServerToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); break; case 2: @@ -97,6 +96,7 @@ Future defaultSettingsMigration( case 9: await generateBackupPassword(secureStorage); break; + case 10: await changeTransactionPriorityAndFeeRateKeys(sharedPreferences); break; @@ -110,7 +110,14 @@ Future defaultSettingsMigration( break; case 13: - await resetElectrumServer(nodes, sharedPreferences); + await resetBitcoinElectrumServer(nodes, sharedPreferences); + break; + + case 15: + await addLitecoinElectrumServerList(nodes: nodes); + await changeLitecoinCurrentElectrumServerToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + await checkCurrentNodes(nodes, sharedPreferences); break; default: @@ -160,12 +167,21 @@ Future changeMoneroCurrentNodeToDefault( } Node getBitcoinDefaultElectrumServer({@required Box nodes}) { - return nodes.values - .firstWhere((Node node) => node.uri == cakeWalletElectrumUri, orElse: () => null) ?? + return nodes.values.firstWhere( + (Node node) => node.uri == cakeWalletBitcoinElectrumUri, + orElse: () => null) ?? nodes.values.firstWhere((node) => node.type == WalletType.bitcoin, orElse: () => null); } +Node getLitecoinDefaultElectrumServer({@required Box nodes}) { + return nodes.values.firstWhere( + (Node node) => node.uri == cakeWalletLitecoinElectrumUri, + orElse: () => null) ?? + nodes.values.firstWhere((node) => node.type == WalletType.litecoin, + orElse: () => null); +} + Node getMoneroDefaultNode({@required Box nodes}) { final timeZone = DateTime.now().timeZoneOffset.inHours; var nodeUri = ''; @@ -192,6 +208,15 @@ Future changeBitcoinCurrentElectrumServerToDefault( await sharedPreferences.setInt('current_node_id_btc', serverId); } +Future changeLitecoinCurrentElectrumServerToDefault( + {@required SharedPreferences sharedPreferences, + @required Box nodes}) async { + final server = getLitecoinDefaultElectrumServer(nodes: nodes); + final serverId = server?.key as int ?? 0; + + await sharedPreferences.setInt('current_node_id_ltc', serverId); +} + Future replaceDefaultNode( {@required SharedPreferences sharedPreferences, @required Box nodes}) async { @@ -224,7 +249,12 @@ Future updateNodeTypes({@required Box nodes}) async { } Future addBitcoinElectrumServerList({@required Box nodes}) async { - final serverList = await loadElectrumServerList(); + final serverList = await loadBitcoinElectrumServerList(); + await nodes.addAll(serverList); +} + +Future addLitecoinElectrumServerList({@required Box nodes}) async { + final serverList = await loadLitecoinElectrumServerList(); await nodes.addAll(serverList); } @@ -284,57 +314,97 @@ Future changeTransactionPriorityAndFeeRateKeys( Future changeDefaultMoneroNode( Box nodeSource, SharedPreferences sharedPreferences) async { const cakeWalletMoneroNodeUriPattern = '.cakewallet.com'; - final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); - final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId); - final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern); + final currentMoneroNodeId = + sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); + final currentMoneroNode = + nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId); + final needToReplaceCurrentMoneroNode = + currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern); nodeSource.values.forEach((node) async { - if (node.type == WalletType.monero && node.uri.contains(cakeWalletMoneroNodeUriPattern)) { + if (node.type == WalletType.monero && + node.uri.contains(cakeWalletMoneroNodeUriPattern)) { await node.delete(); } }); - final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); + final newCakeWalletNode = + Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); await nodeSource.add(newCakeWalletNode); if (needToReplaceCurrentMoneroNode) { - await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int); + await sharedPreferences.setInt( + PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int); } } -Future checkCurrentNodes(Box nodeSource, SharedPreferences sharedPreferences) async { - final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); - final currentElectrumSeverId = await sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); - final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId, orElse: () => null); - final currentElectrumServer = nodeSource.values.firstWhere((node) => node.key == currentElectrumSeverId, orElse: () => null); +Future checkCurrentNodes( + Box nodeSource, SharedPreferences sharedPreferences) async { + final currentMoneroNodeId = + sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); + final currentBitcoinElectrumSeverId = + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + final currentLitecoinElectrumSeverId = sharedPreferences + .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final currentMoneroNode = nodeSource.values.firstWhere( + (node) => node.key == currentMoneroNodeId, + orElse: () => null); + final currentBitcoinElectrumServer = nodeSource.values.firstWhere( + (node) => node.key == currentBitcoinElectrumSeverId, + orElse: () => null); + final currentLitecoinElectrumServer = nodeSource.values.firstWhere( + (node) => node.key == currentLitecoinElectrumSeverId, + orElse: () => null); if (currentMoneroNode == null) { - final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); + final newCakeWalletNode = + Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); await nodeSource.add(newCakeWalletNode); - await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int); + await sharedPreferences.setInt( + PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int); } - if (currentElectrumServer == null) { - final cakeWalletElectrum = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin); + if (currentBitcoinElectrumServer == null) { + final cakeWalletElectrum = + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); await nodeSource.add(cakeWalletElectrum); - await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); + await sharedPreferences.setInt( + PreferencesKey.currentBitcoinElectrumSererIdKey, + cakeWalletElectrum.key as int); + } + + if (currentLitecoinElectrumServer == null) { + final cakeWalletElectrum = + Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin); + await nodeSource.add(cakeWalletElectrum); + await sharedPreferences.setInt( + PreferencesKey.currentLitecoinElectrumSererIdKey, + cakeWalletElectrum.key as int); } } - -Future resetElectrumServer(Box nodeSource, SharedPreferences sharedPreferences) async { - final currentElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); - final oldElectrumServer = nodeSource.values.firstWhere((node) => node.uri.contains('electrumx.cakewallet.com'), orElse: () => null); - var cakeWalletNode = nodeSource.values.firstWhere((node) => node.uri == cakeWalletElectrumUri, orElse: () => null); +Future resetBitcoinElectrumServer( + Box nodeSource, SharedPreferences sharedPreferences) async { + final currentElectrumSeverId = + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + final oldElectrumServer = nodeSource.values.firstWhere( + (node) => node.uri.contains('electrumx.cakewallet.com'), + orElse: () => null); + var cakeWalletNode = nodeSource.values.firstWhere( + (node) => node.uri == cakeWalletBitcoinElectrumUri, + orElse: () => null); if (cakeWalletNode == null) { - cakeWalletNode = Node(uri: cakeWalletElectrumUri, type: WalletType.bitcoin); + cakeWalletNode = + Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); await nodeSource.add(cakeWalletNode); } if (currentElectrumSeverId == oldElectrumServer?.key) { - await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int); + await sharedPreferences.setInt( + PreferencesKey.currentBitcoinElectrumSererIdKey, + cakeWalletNode.key as int); } await oldElectrumServer?.delete(); diff --git a/lib/entities/fs_migration.dart b/lib/entities/fs_migration.dart index 3e7513117..efaefdd64 100644 --- a/lib/entities/fs_migration.dart +++ b/lib/entities/fs_migration.dart @@ -1,7 +1,10 @@ import 'dart:io'; import 'dart:convert'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; -import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/encrypt.dart'; @@ -14,11 +17,6 @@ import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/trade.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:flutter/foundation.dart'; -import 'package:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; const reservedNames = ["flutter_assets", "wallets", "db"]; @@ -407,7 +405,7 @@ Future ios_migrate_address_book(Box contactSource) async { } final List addresses = - json.decode(addressBookJSON.readAsStringSync()) as List; + json.decode(addressBookJSON.readAsStringSync()) as List; final contacts = addresses.map((dynamic item) { final _item = item as Map; final type = _item["type"] as String; @@ -420,7 +418,7 @@ Future ios_migrate_address_book(Box contactSource) async { await contactSource.addAll(contacts); await prefs.setBool('ios_migration_address_book_completed', true); - } catch(e) { + } catch (e) { print(e.toString()); } } diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 118394e6f..a35ed47d4 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -20,9 +20,9 @@ Future> loadDefaultNodes() async { }).toList(); } -Future> loadElectrumServerList() async { +Future> loadBitcoinElectrumServerList() async { final serverListRaw = - await rootBundle.loadString('assets/electrum_server_list.yml'); + await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml'); final serverList = loadYaml(serverListRaw) as YamlList; return serverList.map((dynamic raw) { @@ -37,10 +37,29 @@ Future> loadElectrumServerList() async { }).toList(); } +Future> loadLitecoinElectrumServerList() async { + final serverListRaw = + await rootBundle.loadString('assets/litecoin_electrum_server_list.yml'); + final serverList = loadYaml(serverListRaw) as YamlList; + + return serverList.map((dynamic raw) { + if (raw is Map) { + final node = Node.fromMap(raw); + node?.type = WalletType.litecoin; + + return node; + } + + return null; + }).toList(); +} + Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); - final bitcoinElectrumServerList = await loadElectrumServerList(); - final nodes = moneroNodes + bitcoinElectrumServerList; + final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); + final litecoinElectrumServerList = await loadLitecoinElectrumServerList(); + final nodes = + moneroNodes + bitcoinElectrumServerList + litecoinElectrumServerList; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 15dbc2fb8..6d55748cc 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -1,8 +1,9 @@ class PreferencesKey { - static const currentWalletType ='current_wallet_type'; - static const currentWalletName ='current_wallet_name'; + static const currentWalletType = 'current_wallet_type'; + static const currentWalletName = 'current_wallet_name'; static const currentNodeIdKey = 'current_node_id'; static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc'; + static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc'; static const currentFiatCurrencyKey = 'current_fiat_currency'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; @@ -14,7 +15,8 @@ class PreferencesKey { static const displayActionListModeKey = 'display_list_mode'; static const currentPinLength = 'current_pin_length'; static const currentLanguageCode = 'language_code'; - static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version'; + static const currentDefaultSettingsMigrationVersion = + 'current_default_settings_migration_version'; static const moneroTransactionPriority = 'current_fee_priority_monero'; static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin'; -} \ No newline at end of file +} diff --git a/lib/entities/wallet_type.dart b/lib/entities/wallet_type.dart index f4375e43e..d59f336c0 100644 --- a/lib/entities/wallet_type.dart +++ b/lib/entities/wallet_type.dart @@ -3,7 +3,11 @@ import 'package:hive/hive.dart'; part 'wallet_type.g.dart'; -const walletTypes = [WalletType.monero, WalletType.bitcoin]; +const walletTypes = [ + WalletType.monero, + WalletType.bitcoin, + WalletType.litecoin +]; const walletTypeTypeId = 5; @HiveType(typeId: walletTypeTypeId) @@ -15,7 +19,10 @@ enum WalletType { none, @HiveField(2) - bitcoin + bitcoin, + + @HiveField(3) + litecoin } int serializeToInt(WalletType type) { @@ -24,6 +31,8 @@ int serializeToInt(WalletType type) { return 0; case WalletType.bitcoin: return 1; + case WalletType.litecoin: + return 2; default: return -1; } @@ -35,6 +44,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.monero; case 1: return WalletType.bitcoin; + case 2: + return WalletType.litecoin; default: return null; } @@ -46,6 +57,8 @@ String walletTypeToString(WalletType type) { return 'Monero'; case WalletType.bitcoin: return 'Bitcoin'; + case WalletType.litecoin: + return 'Litecoin'; default: return ''; } @@ -57,6 +70,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Monero'; case WalletType.bitcoin: return 'Bitcoin (Electrum)'; + case WalletType.litecoin: + return 'Litecoin'; default: return ''; } @@ -68,6 +83,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.xmr; case WalletType.bitcoin: return CryptoCurrency.btc; + case WalletType.litecoin: + return CryptoCurrency.ltc; default: return null; } diff --git a/lib/main.dart b/lib/main.dart index 48b54c416..1a1724e3e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -107,7 +107,7 @@ Future main() async { exchangeTemplates: exchangeTemplates, transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, - initialMigrationVersion: 13); + initialMigrationVersion: 15); runApp(App()); } catch (e) { runApp(MaterialApp( @@ -135,7 +135,7 @@ Future initialSetup( @required Box exchangeTemplates, @required Box transactionDescriptions, FlutterSecureStorage secureStorage, - int initialMigrationVersion = 13}) async { + int initialMigrationVersion = 15}) async { LanguageService.loadLocaleList(); await defaultSettingsMigration( secureStorage: secureStorage, diff --git a/lib/monero/monero_account_list.dart b/lib/monero/monero_account_list.dart index 9792d3c1d..9e3b14931 100644 --- a/lib/monero/monero_account_list.dart +++ b/lib/monero/monero_account_list.dart @@ -49,13 +49,13 @@ abstract class MoneroAccountListBase with Store { Future addAccount({String label}) async { await account_list.addAccount(label: label); - await update(); + update(); } Future setLabelAccount({int accountIndex, String label}) async { await account_list.setLabelForAccount( accountIndex: accountIndex, label: label); - await update(); + update(); } void refresh() { diff --git a/lib/monero/monero_transaction_history.dart b/lib/monero/monero_transaction_history.dart index 93c00f376..11eaff4b6 100644 --- a/lib/monero/monero_transaction_history.dart +++ b/lib/monero/monero_transaction_history.dart @@ -1,18 +1,10 @@ import 'dart:core'; import 'package:mobx/mobx.dart'; -import 'package:cw_monero/transaction_history.dart' - as monero_transaction_history; import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/monero/monero_transaction_info.dart'; part 'monero_transaction_history.g.dart'; -List _getAllTransactions(dynamic _) => - monero_transaction_history - .getAllTransations() - .map((row) => MoneroTransactionInfo.fromRow(row)) - .toList(); - class MoneroTransactionHistory = MoneroTransactionHistoryBase with _$MoneroTransactionHistory; @@ -23,30 +15,13 @@ abstract class MoneroTransactionHistoryBase } @override - Future> fetchTransactions() async { - monero_transaction_history.refreshTransactions(); - return _getAllTransactions(null).fold>( - {}, - (Map acc, MoneroTransactionInfo tx) { - acc[tx.id] = tx; - return acc; - }); - } + Future save() async {} @override - @action - void updateAsync({void Function() onFinished}) { - fetchTransactionsAsync( - (transaction) => transactions[transaction.id] = transaction, - onFinished: onFinished); - } + void addOne(MoneroTransactionInfo transaction) => + transactions[transaction.id] = transaction; @override - void fetchTransactionsAsync( - void Function(MoneroTransactionInfo transaction) onTransactionLoaded, - {void Function() onFinished}) async { - final transactions = await fetchTransactions(); - transactions.values.forEach((tx) => onTransactionLoaded(tx)); - onFinished?.call(); - } + void addMany(Map transactions) => + this.transactions.addAll(transactions); } diff --git a/lib/monero/monero_transaction_info.dart b/lib/monero/monero_transaction_info.dart index 71bc5957a..6f099fbfa 100644 --- a/lib/monero/monero_transaction_info.dart +++ b/lib/monero/monero_transaction_info.dart @@ -59,6 +59,7 @@ class MoneroTransactionInfo extends TransactionInfo { @override void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + @override String feeFormatted() => '${formatAmount(moneroAmountToString(amount: fee))} XMR'; } diff --git a/lib/monero/monero_wallet.dart b/lib/monero/monero_wallet.dart index ac7f0cd94..cca24a979 100644 --- a/lib/monero/monero_wallet.dart +++ b/lib/monero/monero_wallet.dart @@ -1,10 +1,12 @@ import 'dart:async'; - import 'package:cake_wallet/entities/transaction_priority.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart'; import 'package:cake_wallet/monero/monero_transaction_creation_exception.dart'; +import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; +import 'package:cw_monero/transaction_history.dart' + as monero_transaction_history; import 'package:cw_monero/wallet.dart'; import 'package:cw_monero/wallet.dart' as monero_wallet; import 'package:cw_monero/transaction_history.dart' as transaction_history; @@ -30,19 +32,21 @@ const moneroBlockSize = 1000; class MoneroWallet = MoneroWalletBase with _$MoneroWallet; -abstract class MoneroWalletBase extends WalletBase with Store { - MoneroWalletBase({String filename, WalletInfo walletInfo}) - : transactionHistory = MoneroTransactionHistory(), - accountList = MoneroAccountList(), +abstract class MoneroWalletBase extends WalletBase with Store { + MoneroWalletBase({WalletInfo walletInfo}) + : accountList = MoneroAccountList(), subaddressList = MoneroSubaddressList(), super(walletInfo) { - _filename = filename; + transactionHistory = MoneroTransactionHistory(); balance = MoneroBalance( fullBalance: monero_wallet.getFullBalance(accountIndex: 0), unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0)); _lastAutosaveTimestamp = 0; + _lastSaveTimestamp = 0; _isSavingAfterSync = false; _isSavingAfterNewTransaction = false; + _isTransactionUpdating = false; _onAccountChangeReaction = reaction((_) => account, (Account account) { balance = MoneroBalance( fullBalance: monero_wallet.getFullBalance(accountIndex: account.id), @@ -56,9 +60,6 @@ abstract class MoneroWalletBase extends WalletBase with Store { static const int _autoAfterSyncSaveInterval = 60000; - @override - final MoneroTransactionHistory transactionHistory; - @observable Account account; @@ -91,12 +92,13 @@ abstract class MoneroWalletBase extends WalletBase with Store { final MoneroAccountList accountList; - String _filename; SyncListener _listener; ReactionDisposer _onAccountChangeReaction; int _lastAutosaveTimestamp; bool _isSavingAfterSync; bool _isSavingAfterNewTransaction; + bool _isTransactionUpdating; + int _lastSaveTimestamp; Future init() async { accountList.update(); @@ -111,7 +113,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { monero_wallet.getUnlockedBalance(accountIndex: account.id)); address = subaddress.address; _setListeners(); - await transactionHistory.update(); + await updateTransactions(); if (walletInfo.isRecovery) { monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery); @@ -238,6 +240,13 @@ abstract class MoneroWalletBase extends WalletBase with Store { @override Future save() async { + final now = DateTime.now().millisecondsSinceEpoch; + + if (now - _lastSaveTimestamp < Duration(seconds: 10).inMilliseconds) { + return; + } + + _lastSaveTimestamp = now; await monero_wallet.store(); } @@ -264,6 +273,40 @@ abstract class MoneroWalletBase extends WalletBase with Store { await walletInfo.save(); } + @override + Future> fetchTransactions() async { + monero_transaction_history.refreshTransactions(); + return _getAllTransactions(null).fold>( + {}, + (Map acc, MoneroTransactionInfo tx) { + acc[tx.id] = tx; + return acc; + }); + } + + Future updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (e) { + print(e); + _isTransactionUpdating = false; + } + } + + List _getAllTransactions(dynamic _) => + monero_transaction_history + .getAllTransations() + .map((row) => MoneroTransactionInfo.fromRow(row)) + .toList(); + void _setListeners() { _listener?.stop(); _listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction); @@ -315,7 +358,7 @@ abstract class MoneroWalletBase extends WalletBase with Store { } Future _askForUpdateTransactionHistory() async => - await transactionHistory.update(); + await updateTransactions(); int _getFullBalance() => monero_wallet.getFullBalance(accountIndex: account.id); @@ -389,11 +432,12 @@ abstract class MoneroWalletBase extends WalletBase with Store { } } - void _onNewTransaction() { + void _onNewTransaction() async { try { - _askForUpdateTransactionHistory(); + await _askForUpdateTransactionHistory(); _askForUpdateBalance(); - Timer(Duration(seconds: 1), () => _afterNewTransactionSave()); + await Future.delayed(Duration(seconds: 1)); + await _afterNewTransactionSave(); } catch (e) { print(e.toString()); } diff --git a/lib/monero/monero_wallet_service.dart b/lib/monero/monero_wallet_service.dart index d964f0232..669c83593 100644 --- a/lib/monero/monero_wallet_service.dart +++ b/lib/monero/monero_wallet_service.dart @@ -68,18 +68,18 @@ class MoneroWalletService extends WalletService< static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync(); + @override + WalletType getType() => WalletType.monero; + @override Future create(MoneroNewWalletCredentials credentials) async { try { - final path = - await pathForWallet(name: credentials.name, type: WalletType.monero); + final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.createWallet( path: path, password: credentials.password, language: credentials.language); - final wallet = MoneroWallet( - filename: monero_wallet.getFilename(), - walletInfo: credentials.walletInfo); + final wallet = MoneroWallet(walletInfo: credentials.walletInfo); await wallet.init(); return wallet; @@ -93,7 +93,7 @@ class MoneroWalletService extends WalletService< @override Future isWalletExit(String name) async { try { - final path = await pathForWallet(name: name, type: WalletType.monero); + final path = await pathForWallet(name: name, type: getType()); return monero_wallet_manager.isWalletExist(path: path); } catch (e) { // TODO: Implement Exception for wallet list service. @@ -105,7 +105,7 @@ class MoneroWalletService extends WalletService< @override Future openWallet(String name, String password) async { try { - final path = await pathForWallet(name: name, type: WalletType.monero); + final path = await pathForWallet(name: name, type: getType()); if (walletFilesExist(path)) { await repairOldAndroidWallet(name); @@ -114,10 +114,9 @@ class MoneroWalletService extends WalletService< await monero_wallet_manager .openWalletAsync({'path': path, 'password': password}); final walletInfo = walletInfoSource.values.firstWhere( - (info) => info.id == WalletBase.idFor(name, WalletType.monero), + (info) => info.id == WalletBase.idFor(name, getType()), orElse: () => null); - final wallet = MoneroWallet( - filename: monero_wallet.getFilename(), walletInfo: walletInfo); + final wallet = MoneroWallet(walletInfo: walletInfo); final isValid = wallet.validate(); if (!isValid) { @@ -146,7 +145,7 @@ class MoneroWalletService extends WalletService< @override Future remove(String wallet) async { - final path = await pathForWalletDir(name: wallet, type: WalletType.monero); + final path = await pathForWalletDir(name: wallet, type: getType()); final file = Directory(path); final isExist = file.existsSync(); @@ -159,8 +158,7 @@ class MoneroWalletService extends WalletService< Future restoreFromKeys( MoneroRestoreWalletFromKeysCredentials credentials) async { try { - final path = - await pathForWallet(name: credentials.name, type: WalletType.monero); + final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.restoreFromKeys( path: path, password: credentials.password, @@ -169,9 +167,7 @@ class MoneroWalletService extends WalletService< address: credentials.address, viewKey: credentials.viewKey, spendKey: credentials.spendKey); - final wallet = MoneroWallet( - filename: monero_wallet.getFilename(), - walletInfo: credentials.walletInfo); + final wallet = MoneroWallet(walletInfo: credentials.walletInfo); await wallet.init(); return wallet; @@ -186,16 +182,13 @@ class MoneroWalletService extends WalletService< Future restoreFromSeed( MoneroRestoreWalletFromSeedCredentials credentials) async { try { - final path = - await pathForWallet(name: credentials.name, type: WalletType.monero); + final path = await pathForWallet(name: credentials.name, type: getType()); await monero_wallet_manager.restoreFromSeed( path: path, password: credentials.password, seed: credentials.mnemonic, restoreHeight: credentials.height); - final wallet = MoneroWallet( - filename: monero_wallet.getFilename(), - walletInfo: credentials.walletInfo); + final wallet = MoneroWallet(walletInfo: credentials.walletInfo); await wallet.init(); return wallet; @@ -221,7 +214,7 @@ class MoneroWalletService extends WalletService< } final newWalletDirPath = - await pathForWalletDir(name: name, type: WalletType.monero); + await pathForWalletDir(name: name, type: getType()); dir.listSync().forEach((f) { final file = File(f.path); diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 17e2ca4f9..428f2703e 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -1,4 +1,6 @@ +import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/di.dart'; @@ -18,9 +20,11 @@ ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction; void startCurrentWalletChangeReaction(AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) { _onCurrentWalletChangeReaction?.reaction?.dispose(); + _onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose(); - _onCurrentWalletChangeReaction = - reaction((_) => appStore.wallet, (WalletBase wallet) async { + _onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase< + Balance, TransactionHistoryBase, TransactionInfo> + wallet) async { try { final node = settingsStore.getCurrentNode(wallet.type); startWalletSyncStatusChangeReaction(wallet); @@ -45,7 +49,9 @@ void startCurrentWalletChangeReaction(AppStore appStore, }); _onCurrentWalletChangeFiatRateUpdateReaction = - reaction((_) => appStore.wallet, (WalletBase wallet) async { + reaction((_) => appStore.wallet, (WalletBase, TransactionInfo> + wallet) async { try { fiatConversionStore.prices[wallet.currency] = 0; fiatConversionStore.prices[wallet.currency] = diff --git a/lib/reactions/on_wallet_sync_status_change.dart b/lib/reactions/on_wallet_sync_status_change.dart index 8ad1c2e1b..f02dd0441 100644 --- a/lib/reactions/on_wallet_sync_status_change.dart +++ b/lib/reactions/on_wallet_sync_status_change.dart @@ -1,16 +1,21 @@ -import 'package:cake_wallet/entities/balance.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/entities/sync_status.dart'; ReactionDisposer _onWalletSyncStatusChangeReaction; -void startWalletSyncStatusChangeReaction(WalletBase wallet) { +void startWalletSyncStatusChangeReaction( + WalletBase, + TransactionInfo> + wallet) { _onWalletSyncStatusChangeReaction?.reaction?.dispose(); _onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async { - if (status is ConnectedSyncStatus) { - await wallet.startSync(); - } - }); -} \ No newline at end of file + if (status is ConnectedSyncStatus) { + await wallet.startSync(); + } + }); +} diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 5e45152ce..d9e34280a 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -21,6 +21,7 @@ class MenuWidget extends StatefulWidget { class MenuWidgetState extends State { Image moneroIcon; Image bitcoinIcon; + Image litecoinIcon; final largeScreen = 731; double menuWidth; @@ -76,6 +77,7 @@ class MenuWidgetState extends State { color: Theme.of(context).accentTextTheme.overline.decorationColor); bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png', color: Theme.of(context).accentTextTheme.overline.decorationColor); + litecoinIcon = Image.asset('assets/images/litecoin_menu.png'); return Row( mainAxisSize: MainAxisSize.max, @@ -238,6 +240,8 @@ class MenuWidgetState extends State { return moneroIcon; case WalletType.bitcoin: return bitcoinIcon; + case WalletType.litecoin: + return litecoinIcon; default: return null; } diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 3e74c3855..f808a237c 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -59,6 +59,8 @@ class WalletTypeFormState extends State { Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); + final litecoinIcon = + Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final walletTypeImage = Image.asset('assets/images/wallet_type.png'); final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png'); @@ -69,7 +71,7 @@ class WalletTypeFormState extends State { @override void initState() { - types = [WalletType.bitcoin, WalletType.monero]; + types = [WalletType.bitcoin, WalletType.monero, WalletType.litecoin]; super.initState(); } @@ -84,8 +86,7 @@ class WalletTypeFormState extends State { padding: EdgeInsets.only(left: 12, right: 12), child: AspectRatio( aspectRatio: aspectRatioImage, - child: - FittedBox(child: widget.walletImage, fit: BoxFit.fill)), + child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)), ), Padding( padding: EdgeInsets.only(top: 48), @@ -99,13 +100,13 @@ class WalletTypeFormState extends State { ), ), ...types.map((type) => Padding( - padding: EdgeInsets.only(top: 24), - child: SelectButton( - image: _iconFor(type), - text: walletTypeToDisplayName(type), - isSelected: selected == type, - onTap: () => setState(() => selected = type)), - )) + padding: EdgeInsets.only(top: 24), + child: SelectButton( + image: _iconFor(type), + text: walletTypeToDisplayName(type), + isSelected: selected == type, + onTap: () => setState(() => selected = type)), + )) ], ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), @@ -125,6 +126,8 @@ class WalletTypeFormState extends State { return moneroIcon; case WalletType.bitcoin: return bitcoinIcon; + case WalletType.litecoin: + return litecoinIcon; default: return null; } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 58ef36797..e7856d685 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -40,6 +40,8 @@ class WalletListBodyState extends State { Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); + final litecoinIcon = + Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar _progressBar; @@ -193,6 +195,8 @@ class WalletListBodyState extends State { return bitcoinIcon; case WalletType.monero: return moneroIcon; + case WalletType.litecoin: + return litecoinIcon; default: return null; } diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index 06e0de1b2..aee7610cd 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -1,6 +1,8 @@ -import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/store/wallet_list_store.dart'; import 'package:cake_wallet/store/authentication_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -20,7 +22,8 @@ abstract class AppStoreBase with Store { AuthenticationStore authenticationStore; @observable - WalletBase wallet; + WalletBase, TransactionInfo> + wallet; WalletListStore walletList; @@ -29,7 +32,10 @@ abstract class AppStoreBase with Store { NodeListStore nodeListStore; @action - void changeCurrentWallet(WalletBase wallet) { + void changeCurrentWallet( + WalletBase, + TransactionInfo> + wallet) { this.wallet?.close(); this.wallet = wallet; } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 6e16e2324..da2410378 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -149,7 +149,7 @@ abstract class SettingsStoreBase with Store { static Future load( {@required Box nodeSource, - @required bool isBitcoinBuyEnabled, + @required bool isBitcoinBuyEnabled, FiatCurrency initialFiatCurrency = FiatCurrency.usd, MoneroTransactionPriority initialMoneroTransactionPriority = MoneroTransactionPriority.slow, @@ -205,15 +205,19 @@ abstract class SettingsStoreBase with Store { final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = sharedPreferences .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + final litecoinElectrumServerId = sharedPreferences + .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final moneroNode = nodeSource.get(nodeId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); + final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final packageInfo = await PackageInfo.fromPlatform(); return SettingsStore( sharedPreferences: sharedPreferences, nodes: { WalletType.monero: moneroNode, - WalletType.bitcoin: bitcoinElectrumServer + WalletType.bitcoin: bitcoinElectrumServer, + WalletType.litecoin: litecoinElectrumServer }, appVersion: packageInfo.version, isBitcoinBuyEnabled: isBitcoinBuyEnabled, @@ -263,6 +267,10 @@ abstract class SettingsStoreBase with Store { await _sharedPreferences.setInt( PreferencesKey.currentBitcoinElectrumSererIdKey, node.key as int); break; + case WalletType.litecoin: + await _sharedPreferences.setInt( + PreferencesKey.currentLitecoinElectrumSererIdKey, node.key as int); + break; case WalletType.monero: await _sharedPreferences.setInt( PreferencesKey.currentNodeIdKey, node.key as int); diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index fb9b07a27..a0935a936 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -1,7 +1,9 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero_wallet.dart'; @@ -23,11 +25,7 @@ abstract class BalanceViewModelBase with Store { @required this.settingsStore, @required this.fiatConvertationStore}) { isReversing = false; - wallet ??= appStore.wallet; - - _reaction = reaction((_) => appStore.wallet, _onWalletChange); - final _wallet = wallet; if (_wallet is MoneroWallet) { @@ -38,6 +36,8 @@ abstract class BalanceViewModelBase with Store { balance = _wallet.balance; } + reaction((_) => appStore.wallet, _onWalletChange); + _onCurrentWalletChangeReaction = reaction((_) => wallet.balance, (dynamic balance) { if (balance is Balance) { @@ -59,7 +59,8 @@ abstract class BalanceViewModelBase with Store { Balance balance; @observable - WalletBase wallet; + WalletBase, TransactionInfo> + wallet; @computed double get price => fiatConvertationStore.prices[appStore.wallet.currency]; @@ -70,8 +71,8 @@ abstract class BalanceViewModelBase with Store { @computed BalanceDisplayMode get displayMode => isReversing ? savedDisplayMode == BalanceDisplayMode.hiddenBalance - ? BalanceDisplayMode.displayableBalance - : savedDisplayMode + ? BalanceDisplayMode.displayableBalance + : savedDisplayMode : savedDisplayMode; @computed @@ -153,14 +154,14 @@ abstract class BalanceViewModelBase with Store { CryptoCurrency get currency => appStore.wallet.currency; ReactionDisposer _onCurrentWalletChangeReaction; - ReactionDisposer _reaction; @action - void _onWalletChange(WalletBase wallet) { + void _onWalletChange( + WalletBase, + TransactionInfo> + wallet) { this.wallet = wallet; - balance = wallet.balance; - _onCurrentWalletChangeReaction?.reaction?.dispose(); _onCurrentWalletChangeReaction = reaction( (_) => wallet.balance, (Balance balance) => this.balance = balance); diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 66fc53269..7a8a55f00 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,23 +1,13 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/order.dart'; -import 'package:cake_wallet/entities/transaction_history.dart'; -import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/monero/account.dart'; import 'package:cake_wallet/monero/monero_balance.dart'; -import 'package:cake_wallet/monero/monero_transaction_history.dart'; import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:cake_wallet/monero/monero_wallet.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; -import 'package:cake_wallet/entities/crypto_currency.dart'; -import 'package:cake_wallet/entities/transaction_direction.dart'; import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; -import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/dashboard/orders_store.dart'; import 'package:cake_wallet/utils/mobx.dart'; @@ -27,12 +17,8 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; -import 'package:cake_wallet/view_model/dashboard/action_list_display_mode.dart'; import 'package:cake_wallet/view_model/wyre_view_model.dart'; -import 'package:crypto/crypto.dart'; -import 'package:flutter/services.dart'; import 'package:hive/hive.dart'; -import 'package:http/http.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/entities/sync_status.dart'; @@ -43,8 +29,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart'; import 'package:cake_wallet/store/dashboard/trade_filter_store.dart'; import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart'; import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:convert/convert.dart'; part 'dashboard_view_model.g.dart'; @@ -100,9 +84,6 @@ abstract class DashboardViewModelBase with Store { name = appStore.wallet?.name; wallet ??= appStore.wallet; type = wallet.type; - - _reaction = reaction((_) => appStore.wallet, _onWalletChange); - final _wallet = wallet; if (_wallet is MoneroWallet) { @@ -133,6 +114,8 @@ abstract class DashboardViewModelBase with Store { settingsStore: appStore.settingsStore))); } + reaction((_) => appStore.wallet, _onWalletChange); + connectMapToListWithTransform( appStore.wallet.transactionHistory.transactions, transactions, @@ -215,7 +198,8 @@ abstract class DashboardViewModelBase with Store { } @observable - WalletBase wallet; + WalletBase, TransactionInfo> + wallet; bool get hasRescan => wallet.type == WalletType.monero; @@ -241,8 +225,6 @@ abstract class DashboardViewModelBase with Store { bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; - ReactionDisposer _reaction; - ReactionDisposer _onMoneroAccountChangeReaction; ReactionDisposer _onMoneroBalanceChangeReaction; @@ -253,7 +235,10 @@ abstract class DashboardViewModelBase with Store { } @action - void _onWalletChange(WalletBase wallet) { + void _onWalletChange( + WalletBase, + TransactionInfo> + wallet) { this.wallet = wallet; type = wallet.type; name = wallet.name; @@ -286,17 +271,17 @@ abstract class DashboardViewModelBase with Store { connectMapToListWithTransform( appStore.wallet.transactionHistory.transactions, transactions, - (TransactionInfo val) => TransactionListItem( + (TransactionInfo val) => TransactionListItem( transaction: val, balanceViewModel: balanceViewModel, settingsStore: appStore.settingsStore), filter: (TransactionInfo tx) { - if (tx is MoneroTransactionInfo && wallet is MoneroWallet) { - return tx.accountIndex == wallet.account.id; - } + if (tx is MoneroTransactionInfo && wallet is MoneroWallet) { + return tx.accountIndex == wallet.account.id; + } - return true; - }); + return true; + }); } @action @@ -319,6 +304,4 @@ abstract class DashboardViewModelBase with Store { balanceViewModel: balanceViewModel, settingsStore: appStore.settingsStore))); } - - } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 83477544e..9974e3b11 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/mobx.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; +import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart'; import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:cake_wallet/monero/monero_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; @@ -42,7 +42,7 @@ class TransactionListItem extends ActionListItem with Keyable { transaction.changeFiatAmount(amount); } - if (transaction is BitcoinTransactionInfo) { + if (transaction is ElectrumTransactionInfo) { final amount = calculateFiatAmountRaw( cryptoAmount: bitcoinAmountToDouble(amount: transaction.amount), price: price); @@ -56,4 +56,4 @@ class TransactionListItem extends ActionListItem with Keyable { @override DateTime get date => transaction.date; -} \ No newline at end of file +} diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index fc860e011..026bec348 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -33,9 +33,7 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel; abstract class ExchangeViewModelBase with Store { ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore, this._settingsStore) { - providerList = [ - ChangeNowExchangeProvider() - ]; + providerList = [ChangeNowExchangeProvider()]; _initialPairBasedOnWallet(); isDepositAddressEnabled = !(depositCurrency == wallet.currency); @@ -57,9 +55,11 @@ abstract class ExchangeViewModelBase with Store { _onPairChange(); } }); - receiveCurrencies = CryptoCurrency.all.where((cryptoCurrency) => - (cryptoCurrency != CryptoCurrency.xlm)&& - (cryptoCurrency != CryptoCurrency.xrp)).toList(); + receiveCurrencies = CryptoCurrency.all + .where((cryptoCurrency) => + (cryptoCurrency != CryptoCurrency.xlm) && + (cryptoCurrency != CryptoCurrency.xrp)) + .toList(); _defineIsReceiveAmountEditable(); isFixedRateMode = false; isReceiveAmountEntered = false; @@ -218,8 +218,10 @@ abstract class ExchangeViewModelBase with Store { limitsState = LimitsIsLoading(); try { - limits = await provider.fetchLimits(from: depositCurrency, - to: receiveCurrency, isFixedRateMode: isFixedRateMode); + limits = await provider.fetchLimits( + from: depositCurrency, + to: receiveCurrency, + isFixedRateMode: isFixedRateMode); limitsState = LimitsLoadedSuccessfully(limits: limits); } catch (e) { limitsState = LimitsLoadedFailure(error: e.toString()); @@ -283,8 +285,8 @@ abstract class ExchangeViewModelBase with Store { } else { try { tradeState = TradeIsCreating(); - final trade = await provider.createTrade(request: request, - isFixedRateMode: isFixedRateMode); + final trade = await provider.createTrade( + request: request, isFixedRateMode: isFixedRateMode); trade.walletId = wallet.id; tradesStore.setTrade(trade); await trades.add(trade); @@ -320,7 +322,8 @@ abstract class ExchangeViewModelBase with Store { void calculateDepositAllAmount() { if (wallet is BitcoinWallet) { final availableBalance = wallet.balance.available; - final priority = _settingsStore.priority[wallet.type] as BitcoinTransactionPriority; + final priority = + _settingsStore.priority[wallet.type] as BitcoinTransactionPriority; final fee = wallet.calculateEstimatedFee(priority, null); if (availableBalance < fee || availableBalance == 0) { @@ -402,6 +405,10 @@ abstract class ExchangeViewModelBase with Store { depositCurrency = CryptoCurrency.btc; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.litecoin: + depositCurrency = CryptoCurrency.ltc; + receiveCurrency = CryptoCurrency.xmr; + break; default: break; } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 9cf800705..c680cc859 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -39,6 +39,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.monero: node = getMoneroDefaultNode(nodes: _nodeSource); break; + case WalletType.litecoin: + node = getLitecoinDefaultElectrumServer(nodes: _nodeSource); + break; default: break; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index b92672ff8..3cf940257 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; @@ -90,6 +91,9 @@ abstract class SendViewModelBase with Store { case WalletType.bitcoin: _amount = stringDoubleToBitcoinAmount(_cryptoAmount); break; + case WalletType.litecoin: + _amount = stringDoubleToBitcoinAmount(_cryptoAmount); + break; default: break; } @@ -102,7 +106,7 @@ abstract class SendViewModelBase with Store { final fee = _wallet.calculateEstimatedFee( _settingsStore.priority[_wallet.type], amount); - if (_wallet is BitcoinWallet) { + if (_wallet is ElectrumWallet) { return bitcoinAmountToDouble(amount: fee); } @@ -304,6 +308,12 @@ abstract class SendViewModelBase with Store { final amount = !sendAll ? _amount : null; final priority = _settingsStore.priority[_wallet.type]; + return BitcoinTransactionCredentials( + address, amount, priority as BitcoinTransactionPriority); + case WalletType.litecoin: + final amount = !sendAll ? _amount : null; + final priority = _settingsStore.priority[_wallet.type]; + return BitcoinTransactionCredentials( address, amount, priority as BitcoinTransactionPriority); case WalletType.monero: @@ -330,6 +340,9 @@ abstract class SendViewModelBase with Store { case WalletType.bitcoin: maximumFractionDigits = 8; break; + case WalletType.litecoin: + maximumFractionDigits = 8; + break; default: break; } @@ -357,7 +370,7 @@ abstract class SendViewModelBase with Store { final _priority = priority as TransactionPriority; final wallet = _wallet; - if (wallet is BitcoinWallet) { + if (wallet is ElectrumWallet) { final rate = wallet.feeRate(_priority); return '${priority.toString()} ($rate sat/byte)'; } diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index 71088d339..eb73b2dd6 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -1,10 +1,3 @@ -import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; -import 'package:cake_wallet/entities/balance.dart'; -import 'package:cake_wallet/entities/transaction_priority.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:cake_wallet/themes/theme_list.dart'; -import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:flutter/cupertino.dart'; import 'package:mobx/mobx.dart'; import 'package:package_info/package_info.dart'; @@ -25,6 +18,15 @@ import 'package:cake_wallet/view_model/settings/regular_list_item.dart'; import 'package:cake_wallet/view_model/settings/settings_list_item.dart'; import 'package:cake_wallet/view_model/settings/switcher_list_item.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; +import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; +import 'package:cake_wallet/entities/transaction_priority.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/themes/theme_list.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; part 'settings_view_model.g.dart'; @@ -36,13 +38,19 @@ List priorityForWalletType(WalletType type) { return MoneroTransactionPriority.all; case WalletType.bitcoin: return BitcoinTransactionPriority.all; + case WalletType.litecoin: + return BitcoinTransactionPriority.all; default: return []; } } abstract class SettingsViewModelBase with Store { - SettingsViewModelBase(this._settingsStore, WalletBase wallet) + SettingsViewModelBase( + this._settingsStore, + WalletBase, + TransactionInfo> + wallet) : itemHeaders = {}, _walletType = wallet.type, _biometricAuth = BiometricAuth() { @@ -77,7 +85,7 @@ abstract class SettingsViewModelBase with Store { displayItem: (dynamic priority) { final _priority = priority as TransactionPriority; - if (wallet is BitcoinWallet) { + if (wallet is ElectrumWallet) { final rate = wallet.feeRate(_priority); return '${priority.toString()} ($rate sat/byte)'; } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 67822e207..47b0957d7 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -1,5 +1,6 @@ -import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; +import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart'; import 'package:cake_wallet/entities/transaction_info.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/monero/monero_transaction_info.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; @@ -43,12 +44,6 @@ abstract class TransactionDetailsViewModelBase with Store { value: tx.amountFormatted()), StandartListItem( title: S.current.transaction_details_fee, value: tx.feeFormatted()), - BlockExplorerListItem( - title: "View in Block Explorer", - value: "View Transaction on XMRChain.net", - onTap: () { - launch("https://xmrchain.net/search?value=${tx.id}"); - }) ]; if (tx.key?.isNotEmpty ?? null) { @@ -59,7 +54,7 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } - if (tx is BitcoinTransactionInfo) { + if (tx is ElectrumTransactionInfo) { final _items = [ StandartListItem( title: S.current.transaction_details_transaction_id, value: tx.id), @@ -78,12 +73,6 @@ abstract class TransactionDetailsViewModelBase with Store { StandartListItem( title: S.current.transaction_details_fee, value: tx.feeFormatted()), - BlockExplorerListItem( - title: "View in Block Explorer", - value: "View Transaction on Blockchain.com", - onTap: () { - launch("https://www.blockchain.com/btc/tx/${tx.id}"); - }) ]; items.addAll(_items); @@ -101,6 +90,19 @@ abstract class TransactionDetailsViewModelBase with Store { } } + WalletType type; + + if (tx is MoneroTransactionInfo) { + type = WalletType.monero; + } else if (tx is ElectrumTransactionInfo) { + type = tx.type; + } + + items.add(BlockExplorerListItem( + title: "View in Block Explorer", + value: _explorerDescription(type), + onTap: () => launch(_explorerUrl(type, tx.id)))); + final description = transactionDescriptionBox.values.firstWhere( (val) => val.id == transactionInfo.id, orElse: () => TransactionDescription(id: transactionInfo.id)); @@ -125,4 +127,30 @@ abstract class TransactionDetailsViewModelBase with Store { final List items; bool showRecipientAddress; + + String _explorerUrl(WalletType type, String txId) { + switch (type) { + case WalletType.monero: + return 'https://xmrchain.net/search?value=${txId}'; + case WalletType.bitcoin: + return 'https://www.blockchain.com/btc/tx/${txId}'; + case WalletType.litecoin: + return 'https://blockchair.com/litecoin/transaction/${txId}'; + default: + return ''; + } + } + + String _explorerDescription(WalletType type) { + switch (type) { + case WalletType.monero: + return 'View Transaction on XMRChain.net'; + case WalletType.bitcoin: + return 'View Transaction on Blockchain.com'; + case WalletType.litecoin: + return 'View Transaction on Blockchair.com'; + default: + return ''; + } + } } diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index b06a00e99..b018c542d 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/wallet_base.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/monero/monero_wallet.dart'; @@ -62,7 +63,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { Future _createNew() async { final wallet = _wallet; - if (wallet is BitcoinWallet) { + if (wallet is ElectrumWallet) { await wallet.generateNewAddress(); } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 9f9531864..74f448185 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,4 +1,6 @@ +import 'package:cake_wallet/core/transaction_history.dart'; import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; @@ -56,12 +58,13 @@ class BitcoinURI extends PaymentURI { } abstract class WalletAddressListViewModelBase with Store { - WalletAddressListViewModelBase( - {@required AppStore appStore}) { + WalletAddressListViewModelBase({@required AppStore appStore}) { _appStore = appStore; _wallet = _appStore.wallet; hasAccounts = _wallet?.type == WalletType.monero; - _onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase wallet) { + _onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase< + Balance, TransactionHistoryBase, TransactionInfo> + wallet) { _wallet = wallet; hasAccounts = _wallet.type == WalletType.monero; }); @@ -119,9 +122,7 @@ abstract class WalletAddressListViewModelBase with Store { final isPrimary = addr == primaryAddress; return WalletAddressListItem( - isPrimary: isPrimary, - name: null, - address: addr.address); + isPrimary: isPrimary, name: null, address: addr.address); }); addressList.addAll(bitcoinAddresses); } @@ -147,7 +148,8 @@ abstract class WalletAddressListViewModelBase with Store { bool get hasAddressList => _wallet.type == WalletType.monero; @observable - WalletBase _wallet; + WalletBase, TransactionInfo> + _wallet; List _baseItems; @@ -155,7 +157,6 @@ abstract class WalletAddressListViewModelBase with Store { ReactionDisposer _onWalletChangeReaction; - @action void setAddress(WalletAddressListItem address) => _wallet.address = address.address; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 4a5315c9a..b56425ee9 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -2,7 +2,7 @@ import 'package:mobx/mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/monero/monero_wallet.dart'; -import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; part 'wallet_keys_view_model.g.dart'; @@ -24,12 +24,11 @@ abstract class WalletKeysViewModelBase with Store { title: S.current.view_key_public, value: keys.publicViewKey), StandartListItem( title: S.current.view_key_private, value: keys.privateViewKey), - StandartListItem( - title: S.current.wallet_seed, value: wallet.seed), + StandartListItem(title: S.current.wallet_seed, value: wallet.seed), ]); } - if (wallet is BitcoinWallet) { + if (wallet is ElectrumWallet) { final keys = wallet.keys; items.addAll([ diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index c21f1b8c1..0611786a8 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -37,6 +37,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, language: options as String); case WalletType.bitcoin: return BitcoinNewWalletCredentials(name: name); + case WalletType.litecoin: + return BitcoinNewWalletCredentials(name: name); default: return null; } diff --git a/pubspec.lock b/pubspec.lock index 75b67e638..3ff596074 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -67,10 +67,12 @@ packages: bech32: dependency: transitive description: - name: bech32 - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2" + path: "." + ref: cake + resolved-ref: "02fef082f20af13de00b4e64efb93a2c1e5e1cf2" + url: "git@github.com:cake-tech/bech32.git" + source: git + version: "0.2.0" bip32: dependency: transitive description: @@ -88,9 +90,11 @@ packages: bitcoin_flutter: dependency: "direct main" description: - name: bitcoin_flutter - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: cake + resolved-ref: b3ab2926c665f0e68b74a4a5f31059f7fcd817b7 + url: "git@github.com:cake-tech/bitcoin_flutter.git" + source: git version: "2.0.2" boolean_selector: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index e93b34fa6..abb78776a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ description: Cake Wallet. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 4.1.4+43 +publish_to: none environment: sdk: ">=2.7.0 <3.0.0" @@ -65,7 +66,10 @@ dependencies: crypto: ^2.1.5 password: ^1.0.0 basic_utils: ^2.0.3 - bitcoin_flutter: ^2.0.0 + bitcoin_flutter: + git: + url: git@github.com:cake-tech/bitcoin_flutter.git + ref: cake get_it: ^6.0.0 connectivity: ^3.0.3 keyboard_actions: ^3.3.0 @@ -105,7 +109,8 @@ flutter: assets: - assets/images/ - assets/node_list.yml - - assets/electrum_server_list.yml + - assets/bitcoin_electrum_server_list.yml + - assets/litecoin_electrum_server_list.yml - assets/text/ - assets/faq/ From 595636c24d8d8ac3b370c49f22b64d6b38c2e0be Mon Sep 17 00:00:00 2001 From: M Date: Fri, 7 May 2021 21:59:03 +0300 Subject: [PATCH 2/8] Fixes for LTC. --- assets/images/litecoin_menu.png | Bin 15547 -> 15485 bytes lib/bitcoin/electrum_transaction_info.dart | 4 ++-- lib/core/seed_validator.dart | 2 ++ lib/view_model/wallet_restore_view_model.dart | 3 +++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/assets/images/litecoin_menu.png b/assets/images/litecoin_menu.png index 5003adc1cc14498388e3f8465dc488bfeae0dba5..d39aff717db2dd848b6d76d72b45ce9cb45e0b8d 100644 GIT binary patch delta 14503 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 delta 14556 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 diff --git a/lib/bitcoin/electrum_transaction_info.dart b/lib/bitcoin/electrum_transaction_info.dart index 7aeebad1b..47005edc7 100644 --- a/lib/bitcoin/electrum_transaction_info.dart +++ b/lib/bitcoin/electrum_transaction_info.dart @@ -138,11 +138,11 @@ class ElectrumTransactionInfo extends TransactionInfo { @override String amountFormatted() => - '${formatAmount(bitcoinAmountToString(amount: amount))} BTC'; + '${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}'; @override String feeFormatted() => fee != null - ? '${formatAmount(bitcoinAmountToString(amount: fee))} BTC' + ? '${formatAmount(bitcoinAmountToString(amount: fee))} ${walletTypeToCryptoCurrency(type).title}' : ''; @override diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 94ada7481..fd2e4851b 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -24,6 +24,8 @@ class SeedValidator extends Validator { switch (type) { case WalletType.bitcoin: return getBitcoinWordList(language); + case WalletType.litecoin: + return getBitcoinWordList(language); case WalletType.monero: return getMoneroWordList(language); default: diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index b015535ae..4fe81c7d0 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -62,6 +62,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.bitcoin: return BitcoinRestoreWalletFromSeedCredentials( name: name, mnemonic: seed, password: password); + case WalletType.litecoin: + return BitcoinRestoreWalletFromSeedCredentials( + name: name, mnemonic: seed, password: password); default: break; } From 85094b646f8bd8fc291ff7b9c5dbcb14224c5038 Mon Sep 17 00:00:00 2001 From: M Date: Fri, 7 May 2021 22:00:04 +0300 Subject: [PATCH 3/8] Changed version to 4.2.0 --- ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a60dfb75f..4dc5104c4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -358,7 +358,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 35; + CURRENT_PROJECT_VERSION = 36; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -375,7 +375,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.1.6; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -501,7 +501,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 35; + CURRENT_PROJECT_VERSION = 36; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -518,7 +518,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.1.6; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -536,7 +536,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 35; + CURRENT_PROJECT_VERSION = 36; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -553,7 +553,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 4.1.6; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/pubspec.yaml b/pubspec.yaml index b2aa7582d..13c73338a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Cake Wallet. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 4.1.6+45 +version: 4.2.0+46 environment: sdk: ">=2.7.0 <3.0.0" From 9f9ec91eba356bc4267cb7aadce52f385dcad812 Mon Sep 17 00:00:00 2001 From: M Date: Sat, 8 May 2021 14:22:01 +0300 Subject: [PATCH 4/8] Updated monero lib wrapper target config. --- cw_monero/ios/cw_monero.podspec | 14 ++++++-------- ios/Podfile.lock | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cw_monero/ios/cw_monero.podspec b/cw_monero/ios/cw_monero.podspec index 78416a9e7..ad8a94d04 100644 --- a/cw_monero/ios/cw_monero.podspec +++ b/cw_monero/ios/cw_monero.podspec @@ -5,20 +5,18 @@ Pod::Spec.new do |s| s.name = 'cw_monero' s.version = '0.0.2' - s.summary = 'A new flutter plugin project.' - s.description = <<-DESC -A new flutter plugin project. - DESC - s.homepage = 'http://example.com' + s.summary = 'CW Monero' + s.description = 'Cake Wallet wrapper over Monero project.' + s.homepage = 'http://cakewallet.com' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'CakeWallet' => 'support@cakewallet.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h, Classes/*.h, External/ios/libs/monero/include/src/**/*.h, External/ios/libs/monero/include/contrib/**/*.h, External/ios/libs/monero/include/External/ios/**/*.h' s.dependency 'Flutter' s.platform = :ios, '9.0' s.swift_version = '4.0' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64' } + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64', 'ENABLE_BITCODE' => 'NO' } s.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/Classes/*.h" } s.subspec 'OpenSSL' do |openssl| @@ -53,4 +51,4 @@ A new flutter plugin project. lmdb.vendored_libraries = 'External/ios/libs/lmdb/liblmdb.a' lmdb.libraries = 'lmdb' end -end \ No newline at end of file +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 03302eab3..879041cac 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -157,7 +157,7 @@ SPEC CHECKSUMS: barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060 - cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649 + cw_monero: 78f369253cc913efc23db9cf6be81a11eaf40fe1 devicelocale: b22617f40038496deffba44747101255cee005b0 DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 From 1cb27d9da3edb507c8b42d0e6fb0997846d9a09f Mon Sep 17 00:00:00 2001 From: M Date: Mon, 10 May 2021 19:00:20 +0300 Subject: [PATCH 5/8] Fixes for LTC electrum nodes available. --- lib/bitcoin/electrum.dart | 23 +--------- lib/entities/default_settings_migration.dart | 10 ++--- lib/entities/node.dart | 46 +++++++++++++++----- lib/main.dart | 2 +- lib/monero/monero_wallet.dart | 2 +- lib/reactions/check_connection.dart | 27 +++++++----- lib/src/screens/nodes/nodes_list_page.dart | 7 +-- 7 files changed, 64 insertions(+), 53 deletions(-) diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index 64569e416..71813ac3f 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -8,16 +8,6 @@ import 'package:cake_wallet/bitcoin/script_hash.dart'; import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; -class UriParseException implements Exception { - UriParseException(this.uri); - - final String uri; - - @override - String toString() => - 'Cannot parse host and port from uri. Invalid uri format. Uri: $uri'; -} - String jsonrpcparams(List params) { final _params = params?.map((val) => '"${val.toString()}"')?.join(','); return '[$_params]'; @@ -54,17 +44,8 @@ class ElectrumClient { Timer _aliveTimer; String unterminatedString; - Future connectToUri(String uri) async { - final splittedUri = uri.split(':'); - - if (splittedUri.length != 2) { - throw UriParseException(uri); - } - - final host = splittedUri.first; - final port = int.parse(splittedUri.last); - await connect(host: host, port: port); - } + Future connectToUri(Uri uri) async => + await connect(host: uri.host, port: uri.port); Future connect({@required String host, @required int port}) async { try { diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 18e8a362a..e8edd28aa 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -149,7 +149,7 @@ Future replaceNodesMigration({@required Box nodes}) async { final nodeToReplace = replaceNodes[node.uri]; if (nodeToReplace != null) { - node.uri = nodeToReplace.uri; + node.uriRaw = nodeToReplace.uriRaw; node.login = nodeToReplace.login; node.password = nodeToReplace.password; await node.save(); @@ -319,11 +319,11 @@ Future changeDefaultMoneroNode( final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId); final needToReplaceCurrentMoneroNode = - currentMoneroNode.uri.contains(cakeWalletMoneroNodeUriPattern); + currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern); nodeSource.values.forEach((node) async { if (node.type == WalletType.monero && - node.uri.contains(cakeWalletMoneroNodeUriPattern)) { + node.uri.toString().contains(cakeWalletMoneroNodeUriPattern)) { await node.delete(); } }); @@ -389,10 +389,10 @@ Future resetBitcoinElectrumServer( final currentElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final oldElectrumServer = nodeSource.values.firstWhere( - (node) => node.uri.contains('electrumx.cakewallet.com'), + (node) => node.uri.toString().contains('electrumx.cakewallet.com'), orElse: () => null); var cakeWalletNode = nodeSource.values.firstWhere( - (node) => node.uri == cakeWalletBitcoinElectrumUri, + (node) => node.uri.toString() == cakeWalletBitcoinElectrumUri, orElse: () => null); if (cakeWalletNode == null) { diff --git a/lib/entities/node.dart b/lib/entities/node.dart index 2636f9601..fe2823352 100644 --- a/lib/entities/node.dart +++ b/lib/entities/node.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cake_wallet/utils/mobx.dart'; import 'package:flutter/foundation.dart'; import 'dart:convert'; @@ -8,19 +10,23 @@ import 'package:cake_wallet/entities/digest_request.dart'; part 'node.g.dart'; +Uri createUriFromElectrumAddress(String address) => + Uri.tryParse('tcp://$address'); + @HiveType(typeId: Node.typeId) class Node extends HiveObject with Keyable { Node( - {@required this.uri, + {@required String uri, @required WalletType type, this.login, this.password, this.useSSL}) { + uriRaw = uri; this.type = type; } Node.fromMap(Map map) - : uri = map['uri'] as String ?? '', + : uriRaw = map['uri'] as String ?? '', login = map['login'] as String, password = map['password'] as String, typeRaw = map['typeRaw'] as int, @@ -30,7 +36,7 @@ class Node extends HiveObject with Keyable { static const boxName = 'Nodes'; @HiveField(0) - String uri; + String uriRaw; @HiveField(1) String login; @@ -46,6 +52,19 @@ class Node extends HiveObject with Keyable { bool get isSSL => useSSL ?? false; + Uri get uri { + switch (type) { + case WalletType.monero: + return Uri.http(uriRaw, ''); + case WalletType.bitcoin: + return createUriFromElectrumAddress(uriRaw); + case WalletType.litecoin: + return createUriFromElectrumAddress(uriRaw); + default: + return null; + } + } + @override dynamic get keyIndex { _keyIndex ??= key; @@ -64,7 +83,9 @@ class Node extends HiveObject with Keyable { case WalletType.monero: return requestMoneroNode(); case WalletType.bitcoin: - return requestBitcoinElectrumServer(); + return requestElectrumServer(); + case WalletType.litecoin: + return requestElectrumServer(); default: return false; } @@ -80,15 +101,15 @@ class Node extends HiveObject with Keyable { if (login != null && password != null) { final digestRequest = DigestRequest(); final response = await digestRequest.request( - uri: uri, login: login, password: password); + uri: uri.toString(), login: login, password: password); resBody = response.data as Map; } else { - final url = Uri.http(uri, '/json_rpc'); + final rpcUri = Uri.http(uri.toString(), '/json_rpc'); final headers = {'Content-type': 'application/json'}; final body = json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'}); final response = - await http.post(url.toString(), headers: headers, body: body); + await http.post(rpcUri.toString(), headers: headers, body: body); resBody = json.decode(response.body) as Map; } @@ -98,8 +119,13 @@ class Node extends HiveObject with Keyable { } } - Future requestBitcoinElectrumServer() async { - // FIXME: IMPLEMENT ME - return true; + Future requestElectrumServer() async { + try { + await SecureSocket.connect(uri.host, uri.port, + timeout: Duration(seconds: 5), onBadCertificate: (_) => true); + return true; + } catch (_) { + return false; + } } } diff --git a/lib/main.dart b/lib/main.dart index 1a1724e3e..3871140ea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -74,7 +74,7 @@ Future main() async { if (!Hive.isAdapterRegistered(Order.typeId)) { Hive.registerAdapter(OrderAdapter()); } - + final secureStorage = FlutterSecureStorage(); final transactionDescriptionsBoxKey = await getEncryptionKey( secureStorage: secureStorage, forKey: TransactionDescription.boxKey); diff --git a/lib/monero/monero_wallet.dart b/lib/monero/monero_wallet.dart index 6d7d41c4b..92742273c 100644 --- a/lib/monero/monero_wallet.dart +++ b/lib/monero/monero_wallet.dart @@ -152,7 +152,7 @@ abstract class MoneroWalletBase extends WalletBase From b096123ea163be80edf209b3a21222030b579a6c Mon Sep 17 00:00:00 2001 From: M Date: Mon, 10 May 2021 20:01:54 +0300 Subject: [PATCH 6/8] Fixed mnemonic generation call for litecoin service. --- lib/bitcoin/litecoin_wallet_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bitcoin/litecoin_wallet_service.dart b/lib/bitcoin/litecoin_wallet_service.dart index 04533640e..053fd785f 100644 --- a/lib/bitcoin/litecoin_wallet_service.dart +++ b/lib/bitcoin/litecoin_wallet_service.dart @@ -24,7 +24,7 @@ class LitecoinWalletService extends WalletService< @override Future create(BitcoinNewWalletCredentials credentials) async { final wallet = LitecoinWallet( - mnemonic: generateMnemonic(), + mnemonic: await generateMnemonic(), password: credentials.password, walletInfo: credentials.walletInfo); await wallet.save(); From a439560d4d54648219e0ee16fdb45717be7e057b Mon Sep 17 00:00:00 2001 From: M Date: Tue, 11 May 2021 16:52:48 +0300 Subject: [PATCH 7/8] Fixes for LTC and added banner for old bitcoin electrum wallets. --- ios/Runner.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 8 +- lib/bitcoin/bitcoin_transaction_priority.dart | 54 +++++++++ lib/bitcoin/litecoin_wallet.dart | 8 +- lib/main.dart | 2 +- lib/src/screens/dashboard/dashboard_page.dart | 109 +++++++++++------- .../dashboard/dashboard_view_model.dart | 4 + lib/view_model/send/send_view_model.dart | 2 +- .../settings/settings_view_model.dart | 4 +- .../wallet_address_list_view_model.dart | 11 +- pubspec.yaml | 2 +- res/values/strings_de.arb | 5 +- res/values/strings_en.arb | 5 +- res/values/strings_es.arb | 5 +- res/values/strings_hi.arb | 5 +- res/values/strings_hr.arb | 7 +- res/values/strings_it.arb | 7 +- res/values/strings_ja.arb | 5 +- res/values/strings_ko.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_uk.arb | 5 +- res/values/strings_zh.arb | 5 +- 25 files changed, 205 insertions(+), 79 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9deb7b43a..f51e0e3fe 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -362,7 +362,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 38; + CURRENT_PROJECT_VERSION = 39; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -505,7 +505,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 38; + CURRENT_PROJECT_VERSION = 39; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -540,7 +540,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 38; + CURRENT_PROJECT_VERSION = 39; DEVELOPMENT_TEAM = 32J6BB6VUS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..fb2dffc49 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + - - 'sat'; + @override String toString() { var label = ''; @@ -46,4 +48,56 @@ class BitcoinTransactionPriority extends TransactionPriority { return label; } + + String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)'; +} + +class LitecoinTransactionPriority extends BitcoinTransactionPriority { + const LitecoinTransactionPriority({String title, int raw}) + : super(title: title, raw: raw); + + static const List all = [fast, medium, slow]; + static const LitecoinTransactionPriority slow = + LitecoinTransactionPriority(title: 'Slow', raw: 0); + static const LitecoinTransactionPriority medium = + LitecoinTransactionPriority(title: 'Medium', raw: 1); + static const LitecoinTransactionPriority fast = + LitecoinTransactionPriority(title: 'Fast', raw: 2); + + static LitecoinTransactionPriority deserialize({int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + return null; + } + } + + @override + String get units => 'Latoshi'; + + @override + String toString() { + var label = ''; + + switch (this) { + case LitecoinTransactionPriority.slow: + label = S.current.transaction_priority_slow; + break; + case LitecoinTransactionPriority.medium: + label = S.current.transaction_priority_medium; + break; + case LitecoinTransactionPriority.fast: + label = S.current.transaction_priority_fast; + break; + default: + break; + } + + return label; + } } diff --git a/lib/bitcoin/litecoin_wallet.dart b/lib/bitcoin/litecoin_wallet.dart index 89902ad27..209d1904e 100644 --- a/lib/bitcoin/litecoin_wallet.dart +++ b/lib/bitcoin/litecoin_wallet.dart @@ -72,13 +72,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @override int feeRate(TransactionPriority priority) { - if (priority is BitcoinTransactionPriority) { + if (priority is LitecoinTransactionPriority) { switch (priority) { - case BitcoinTransactionPriority.slow: + case LitecoinTransactionPriority.slow: return 1; - case BitcoinTransactionPriority.medium: + case LitecoinTransactionPriority.medium: return 2; - case BitcoinTransactionPriority.fast: + case LitecoinTransactionPriority.fast: return 3; } } diff --git a/lib/main.dart b/lib/main.dart index 3871140ea..1a1724e3e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -74,7 +74,7 @@ Future main() async { if (!Hive.isAdapterRegistered(Order.typeId)) { Hive.registerAdapter(OrderAdapter()); } - + final secureStorage = FlutterSecureStorage(); final transactionDescriptionsBoxKey = await getEncryptionKey( secureStorage: secureStorage, forKey: TransactionDescription.boxKey); diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 400b34060..46a4185ce 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -16,6 +16,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; @@ -26,8 +27,8 @@ class DashboardPage extends BasePage { }); @override - Color get backgroundLightColor => currentTheme.type == ThemeType.bright - ? Colors.transparent : Colors.white; + Color get backgroundLightColor => + currentTheme.type == ThemeType.bright ? Colors.transparent : Colors.white; @override Color get backgroundDarkColor => Colors.transparent; @@ -56,9 +57,8 @@ class DashboardPage extends BasePage { @override Widget trailing(BuildContext context) { - final menuButton = - Image.asset('assets/images/menu.png', - color: Theme.of(context).accentTextTheme.display3.backgroundColor); + final menuButton = Image.asset('assets/images/menu.png', + color: Theme.of(context).accentTextTheme.display3.backgroundColor); return Container( alignment: Alignment.centerRight, @@ -81,15 +81,18 @@ class DashboardPage extends BasePage { @override Widget body(BuildContext context) { final sendImage = Image.asset('assets/images/upload.png', - height: 22.24, width: 24, + height: 22.24, + width: 24, color: Theme.of(context).accentTextTheme.display3.backgroundColor); final exchangeImage = Image.asset('assets/images/transfer.png', - height: 24.27, width: 22.25, + height: 24.27, + width: 22.25, color: Theme.of(context).accentTextTheme.display3.backgroundColor); final buyImage = Image.asset('assets/images/coins.png', - height: 22.24, width: 24, + height: 22.24, + width: 24, color: Theme.of(context).accentTextTheme.display3.backgroundColor); - _setEffects(); + _setEffects(context); return SafeArea( child: Column( @@ -111,7 +114,9 @@ class DashboardPage extends BasePage { dotWidth: 6.0, dotHeight: 6.0, dotColor: Theme.of(context).indicatorColor, - activeDotColor: Theme.of(context).accentTextTheme.display1 + activeDotColor: Theme.of(context) + .accentTextTheme + .display1 .backgroundColor), )), Container( @@ -129,25 +134,27 @@ class DashboardPage extends BasePage { route: Routes.exchange), Observer( builder: (_) => Stack( - clipBehavior: Clip.none, - alignment: Alignment.topCenter, - children: [ - if (walletViewModel.isRunningWebView) Positioned( - top: -5, - child: SpinKitRing( - color: Theme.of(context).buttonColor, - lineWidth: 3, - size: 70.0, - ), - ), - ActionButton( - image: buyImage, - title: S.of(context).buy, - onClick: walletViewModel.isRunningWebView - ? null - : () async => await _onClickBuyButton(context)) - ], - )), + clipBehavior: Clip.none, + alignment: Alignment.topCenter, + children: [ + if (walletViewModel.isRunningWebView) + Positioned( + top: -5, + child: SpinKitRing( + color: Theme.of(context).buttonColor, + lineWidth: 3, + size: 70.0, + ), + ), + ActionButton( + image: buyImage, + title: S.of(context).buy, + onClick: walletViewModel.isRunningWebView + ? null + : () async => + await _onClickBuyButton(context)) + ], + )), ], ), ) @@ -155,7 +162,7 @@ class DashboardPage extends BasePage { )); } - void _setEffects() { + void _setEffects(BuildContext context) { if (_isEffectsInstalled) { return; } @@ -164,14 +171,42 @@ class DashboardPage extends BasePage { pages.add(BalancePage(dashboardViewModel: walletViewModel)); pages.add(TransactionsPage(dashboardViewModel: walletViewModel)); + autorun((_) async { + if (!walletViewModel.isOutdatedElectrumWallet) { + return; + } + + await Future.delayed(Duration(seconds: 1)); + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).pre_seed_title, + alertContent: + S.of(context).outdated_electrum_wallet_desceription, + buttonText: S.of(context).understand, + buttonAction: () => Navigator.of(context).pop()); + }); + }); + _isEffectsInstalled = true; } - Future _onClickBuyButton(BuildContext context) async { + Future _onClickBuyButton(BuildContext context) async { final walletType = walletViewModel.type; switch (walletType) { - case WalletType.monero: + case WalletType.bitcoin: + try { + walletViewModel.isRunningWebView = true; + final url = await walletViewModel.wyreViewModel.wyreUrl; + await Navigator.of(context).pushNamed(Routes.wyre, arguments: url); + walletViewModel.isRunningWebView = false; + } catch (_) { + walletViewModel.isRunningWebView = false; + } + break; + default: await showPopUp( context: context, builder: (BuildContext context) { @@ -182,16 +217,6 @@ class DashboardPage extends BasePage { buttonAction: () => Navigator.of(context).pop()); }); break; - default: - try { - walletViewModel.isRunningWebView = true; - final url = await walletViewModel.wyreViewModel.wyreUrl; - await Navigator.of(context).pushNamed(Routes.wyre, arguments: url); - walletViewModel.isRunningWebView = false; - } catch(_) { - walletViewModel.isRunningWebView = false; - } - break; } } } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 7a8a55f00..551769808 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -234,6 +234,10 @@ abstract class DashboardViewModelBase with Store { await wallet.connectToNode(node: node); } + @computed + bool get isOutdatedElectrumWallet => + wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; + @action void _onWalletChange( WalletBase, diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 3cf940257..689032223 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -372,7 +372,7 @@ abstract class SendViewModelBase with Store { if (wallet is ElectrumWallet) { final rate = wallet.feeRate(_priority); - return '${priority.toString()} ($rate sat/byte)'; + return '${priority.labelWithRate(rate)}'; } return priority.toString(); diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index eb73b2dd6..57de6d7d9 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -39,7 +39,7 @@ List priorityForWalletType(WalletType type) { case WalletType.bitcoin: return BitcoinTransactionPriority.all; case WalletType.litecoin: - return BitcoinTransactionPriority.all; + return LitecoinTransactionPriority.all; default: return []; } @@ -87,7 +87,7 @@ abstract class SettingsViewModelBase with Store { if (wallet is ElectrumWallet) { final rate = wallet.feeRate(_priority); - return '${priority.toString()} ($rate sat/byte)'; + return '${priority.labelWithRate(rate)}'; } return priority.toString(); diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 74f448185..b15443222 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,7 +1,3 @@ -import 'package:cake_wallet/core/transaction_history.dart'; -import 'package:cake_wallet/entities/balance.dart'; -import 'package:cake_wallet/entities/transaction_info.dart'; -import 'package:cake_wallet/store/app_store.dart'; import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; @@ -12,6 +8,11 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_h import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; +import 'package:cake_wallet/bitcoin/electrum_wallet.dart'; +import 'package:cake_wallet/core/transaction_history.dart'; +import 'package:cake_wallet/entities/balance.dart'; +import 'package:cake_wallet/entities/transaction_info.dart'; +import 'package:cake_wallet/store/app_store.dart'; part 'wallet_address_list_view_model.g.dart'; @@ -175,7 +176,7 @@ abstract class WalletAddressListViewModelBase with Store { void nextAddress() { final wallet = _wallet; - if (wallet is BitcoinWallet) { + if (wallet is ElectrumWallet) { wallet.nextAddress(); } } diff --git a/pubspec.yaml b/pubspec.yaml index ee63a7b68..27fb93b0d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Cake Wallet. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 4.2.0+48 +version: 4.2.0+49 environment: sdk: ">=2.7.0 <3.0.0" diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index c9836f13b..6b9822ba7 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -471,5 +471,8 @@ "submit_request" : "Einen Antrag stellen", - "buy_alert_content" : "Derzeit unterstützen wir nur den Kauf von Bitcoin. Um Bitcoin zu kaufen, erstellen Sie bitte Ihre Bitcoin-Brieftasche oder wechseln Sie zu dieser" + "buy_alert_content" : "Derzeit unterstützen wir nur den Kauf von Bitcoin. Um Bitcoin zu kaufen, erstellen Sie bitte Ihre Bitcoin-Brieftasche oder wechseln Sie zu dieser", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2364bca10..38e769d0a 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -471,5 +471,8 @@ "submit_request" : "submit a request", - "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet" + "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a57886fe8..dd4707ddf 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -471,5 +471,8 @@ "submit_request" : "presentar una solicitud", - "buy_alert_content" : "Actualmente solo apoyamos la compra de Bitcoin. Para comprar Bitcoin, cree o cambie a su billetera Bitcoin" + "buy_alert_content" : "Actualmente solo apoyamos la compra de Bitcoin. Para comprar Bitcoin, cree o cambie a su billetera Bitcoin", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 852e249c9..4bcb4e9fa 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -471,5 +471,8 @@ "submit_request" : "एक अनुरोध सबमिट करें", - "buy_alert_content" : "वर्तमान में हम केवल बिटकॉइन की खरीद का समर्थन करते हैं। बिटकॉइन खरीदने के लिए, कृपया अपना बिटकॉइन वॉलेट बनाएं या स्विच करें" + "buy_alert_content" : "वर्तमान में हम केवल बिटकॉइन की खरीद का समर्थन करते हैं। बिटकॉइन खरीदने के लिए, कृपया अपना बिटकॉइन वॉलेट बनाएं या स्विच करें", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index a27384dde..ae1b4d8ec 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -469,5 +469,10 @@ "unconfirmed" : "Nepotvrđeno", "displayable" : "Dostupno za prikaz", - "submit_request" : "podnesi zahtjev" + "submit_request" : "podnesi zahtjev", + + "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 21f61ec6f..32e59cd3b 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -469,5 +469,10 @@ "unconfirmed" : "Non confermato", "displayable" : "Visualizzabile", - "submit_request" : "invia una richiesta" + "submit_request" : "invia una richiesta", + + "buy_alert_content" : "Currently we only support the purchase of Bitcoin. To buy Bitcoin, please create or switch to your Bitcoin wallet", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 0df4e9dda..af8b3e405 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -471,5 +471,8 @@ "submit_request" : "リクエストを送信する", - "buy_alert_content" : "現在、ビットコインの購入のみをサポートしています。 ビットコインを購入するには、ビットコインウォレットを作成するか切り替えてください" + "buy_alert_content" : "現在、ビットコインの購入のみをサポートしています。 ビットコインを購入するには、ビットコインウォレットを作成するか切り替えてください", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index ade3cc847..b5347e1b9 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -471,5 +471,8 @@ "submit_request" : "요청을 제출", - "buy_alert_content" : "현재 우리는 비트 코인 구매 만 지원합니다. 비트 코인을 구매하려면 비트 코인 지갑을 생성하거나 전환하십시오" + "buy_alert_content" : "현재 우리는 비트 코인 구매 만 지원합니다. 비트 코인을 구매하려면 비트 코인 지갑을 생성하거나 전환하십시오", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 8f50f7102..d9530aaa5 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -471,5 +471,8 @@ "submit_request" : "een verzoek indienen", - "buy_alert_content" : "Momenteel ondersteunen we alleen de aankoop van Bitcoin. Om Bitcoin te kopen, moet u uw Bitcoin-portemonnee aanmaken of naar uw Bitcoin-portemonnee overschakelen" + "buy_alert_content" : "Momenteel ondersteunen we alleen de aankoop van Bitcoin. Om Bitcoin te kopen, moet u uw Bitcoin-portemonnee aanmaken of naar uw Bitcoin-portemonnee overschakelen", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 84a8ba338..53787f1d6 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -471,5 +471,8 @@ "submit_request" : "złożyć wniosek", - "buy_alert_content" : "Obecnie obsługujemy tylko zakup Bitcoinów. Aby kupić Bitcoin, utwórz lub przełącz się na swój portfel Bitcoin" + "buy_alert_content" : "Obecnie obsługujemy tylko zakup Bitcoinów. Aby kupić Bitcoin, utwórz lub przełącz się na swój portfel Bitcoin", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 18a5ad9b5..ec7e72f06 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -471,5 +471,8 @@ "submit_request" : "enviar um pedido", - "buy_alert_content" : "Atualmente, apoiamos apenas a compra de Bitcoin. Para comprar Bitcoin, crie ou mude para sua carteira Bitcoin" + "buy_alert_content" : "Atualmente, apoiamos apenas a compra de Bitcoin. Para comprar Bitcoin, crie ou mude para sua carteira Bitcoin", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 711d4fa07..b1b085a35 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -471,5 +471,8 @@ "submit_request" : "отправить запрос", - "buy_alert_content" : "В настоящее время мы поддерживаем только покупку Bitcoin. Чтобы купить Bitcoin, создайте или переключитесь на ваш Bitcoin кошелек" + "buy_alert_content" : "В настоящее время мы поддерживаем только покупку Bitcoin. Чтобы купить Bitcoin, создайте или переключитесь на ваш Bitcoin кошелек", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 46c82da58..92ccf7f05 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -471,5 +471,8 @@ "submit_request" : "надіслати запит", - "buy_alert_content" : "На даний час ми підтримуємо тільки покупку Bitcoin. Щоб купити Bitcoin, будь ласка, створіть або переключіться на ваш Bitcoin гаманець" + "buy_alert_content" : "На даний час ми підтримуємо тільки покупку Bitcoin. Щоб купити Bitcoin, будь ласка, створіть або переключіться на ваш Bitcoin гаманець", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index cff7833b1..1ee4e3c26 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -468,5 +468,8 @@ "unconfirmed" : "未经证实", "displayable" : "可显示", "submit_request" : "提交请求", - "buy_alert_content" : "目前,我們僅支持購買比特幣。 要購買比特幣,請創建或切換到您的比特幣錢包" + "buy_alert_content" : "目前,我們僅支持購買比特幣。 要購買比特幣,請創建或切換到您的比特幣錢包", + + "outdated_electrum_wallet_desceription" : "New Bitcoin wallets created in Cake now have the 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-seed wallet and stop using wallets with the 12-word seed. Please do this immediately to secure your funds.", + "understand" : "I undersand" } \ No newline at end of file From f4c57e22f234e23a44f19cdc3ac933001ca23dcd Mon Sep 17 00:00:00 2001 From: M Date: Tue, 11 May 2021 18:37:26 +0300 Subject: [PATCH 8/8] Minor bug fixes. --- .../AppIcon.appiconset/Contents.json | 84 +++++++++--------- .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 137383 bytes .../AppIcon.appiconset/app_icon_120.png | Bin 0 -> 11862 bytes .../AppIcon.appiconset/app_icon_180.png | Bin 0 -> 17028 bytes .../AppIcon.appiconset/cake_xmr_1024.png | Bin 23662 -> 0 bytes .../AppIcon.appiconset/cake_xmr_120.png | Bin 10387 -> 0 bytes .../AppIcon.appiconset/cake_xmr_180.png | Bin 14781 -> 0 bytes lib/di.dart | 10 +-- lib/entities/calculate_fiat_amount.dart | 5 +- lib/entities/wyre_service.dart | 43 +++++---- .../dashboard/dashboard_view_model.dart | 9 +- 11 files changed, 75 insertions(+), 76 deletions(-) create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/cake_xmr_1024.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/cake_xmr_120.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/cake_xmr_180.png diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 6613d9583..6d834d4ee 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -2,100 +2,100 @@ "images" : [ { "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" + "scale" : "3x", + "size" : "29x29" }, { "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" + "scale" : "3x", + "size" : "40x40" }, { - "size" : "60x60", + "filename" : "app_icon_120.png", "idiom" : "iphone", - "filename" : "cake_xmr_120.png", - "scale" : "2x" + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", + "filename" : "app_icon_180.png", "idiom" : "iphone", - "filename" : "cake_xmr_180.png", - "scale" : "3x" + "scale" : "3x", + "size" : "60x60" }, { "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" + "scale" : "1x", + "size" : "20x20" }, { "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" + "scale" : "1x", + "size" : "29x29" }, { "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" + "scale" : "1x", + "size" : "40x40" }, { "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" + "scale" : "1x", + "size" : "76x76" }, { "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" + "scale" : "2x", + "size" : "76x76" }, { "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" + "scale" : "2x", + "size" : "83.5x83.5" }, { - "size" : "1024x1024", + "filename" : "app_icon_1024.png", "idiom" : "ios-marketing", - "filename" : "cake_xmr_1024.png", - "scale" : "1x" + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..2681576272a661d03806405e7c47c5b94d1dc64f GIT binary patch literal 137383 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

E-WNTMqO)V8J>Ji67foy=H_6Y~4;2dP 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 K=d#Wzp$P!T=Y>W9 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/cake_xmr_1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/cake_xmr_1024.png deleted file mode 100644 index 4540659cf4334bbd9511de2e5be7c6faf36a5138..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23662 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 diff --git a/lib/di.dart b/lib/di.dart index 1885a54b8..cae3a068f 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -538,8 +538,7 @@ Future setup( TradeDetailsPage(getIt.get(param1: trade))); getIt.registerFactory(() { - final wallet = getIt.get().wallet; - return WyreService(wallet: wallet); + return WyreService(appStore: getIt.get()); }); getIt.registerFactory(() { @@ -551,10 +550,9 @@ Future setup( WyrePage(getIt.get(), ordersStore: getIt.get(), url: url)); - getIt.registerFactoryParam( - (order, _) => OrderDetailsViewModel( - wyreViewModel: getIt.get(), - orderForDetails: order)); + getIt.registerFactoryParam((order, _) => + OrderDetailsViewModel( + wyreViewModel: getIt.get(), orderForDetails: order)); getIt.registerFactoryParam((Order order, _) => OrderDetailsPage(getIt.get(param1: order))); diff --git a/lib/entities/calculate_fiat_amount.dart b/lib/entities/calculate_fiat_amount.dart index 6302c23a0..407030d7f 100644 --- a/lib/entities/calculate_fiat_amount.dart +++ b/lib/entities/calculate_fiat_amount.dart @@ -4,11 +4,12 @@ String calculateFiatAmount({double price, String cryptoAmount}) { } final _amount = double.parse(cryptoAmount); - final result = price * _amount; + final _result = price * _amount; + final result = _result < 0 ? _result * -1 : _result; if (result == 0.0) { return '0.00'; } return result > 0.01 ? result.toStringAsFixed(2) : '< 0.01'; -} \ No newline at end of file +} diff --git a/lib/entities/wyre_service.dart b/lib/entities/wyre_service.dart index b83bfce68..63346f0e4 100644 --- a/lib/entities/wyre_service.dart +++ b/lib/entities/wyre_service.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/entities/wyre_exception.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -9,15 +9,9 @@ import 'package:cake_wallet/entities/order.dart'; import 'package:cake_wallet/entities/wallet_type.dart'; class WyreService { - WyreService({ - @required this.wallet, - this.isTestEnvironment = false}) { - baseApiUrl = isTestEnvironment - ? _baseTestApiUrl - : _baseProductApiUrl; - trackUrl = isTestEnvironment - ? _trackTestUrl - : _trackProductUrl; + WyreService({@required this.appStore, this.isTestEnvironment = false}) { + baseApiUrl = isTestEnvironment ? _baseTestApiUrl : _baseProductApiUrl; + trackUrl = isTestEnvironment ? _trackTestUrl : _trackProductUrl; } static const _baseTestApiUrl = 'https://api.testwyre.com'; @@ -31,24 +25,28 @@ class WyreService { static const _trackSuffix = '/track'; final bool isTestEnvironment; - final WalletBase wallet; + final AppStore appStore; - WalletType get walletType => wallet.type; - String get walletAddress => wallet.address; - String get walletId => wallet.id; + WalletType get walletType => appStore.wallet.type; + String get walletAddress => appStore.wallet.address; + String get walletId => appStore.wallet.id; String baseApiUrl; String trackUrl; Future getWyreUrl() async { final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); - final url = baseApiUrl + _ordersSuffix + _reserveSuffix + - _timeStampSuffix + timestamp; + final url = baseApiUrl + + _ordersSuffix + + _reserveSuffix + + _timeStampSuffix + + timestamp; final secretKey = secrets.wyreSecretKey; final accountId = secrets.wyreAccountId; final body = { 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'dest': + walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, 'referrerAccountId': accountId, 'lockFields': ['destCurrency', 'dest'] }; @@ -79,7 +77,7 @@ class WyreService { } final orderResponseJSON = - json.decode(orderResponse.body) as Map; + json.decode(orderResponse.body) as Map; final transferId = orderResponseJSON['transferId'] as String; final from = orderResponseJSON['sourceCurrency'] as String; final to = orderResponseJSON['destCurrency'] as String; @@ -87,7 +85,7 @@ class WyreService { final state = TradeState.deserialize(raw: status.toLowerCase()); final createdAtRaw = orderResponseJSON['createdAt'] as int; final createdAt = - DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal(); + DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal(); final transferUrl = baseApiUrl + _transferSuffix + transferId + _trackSuffix; @@ -98,7 +96,7 @@ class WyreService { } final transferResponseJSON = - json.decode(transferResponse.body) as Map; + json.decode(transferResponse.body) as Map; final amount = transferResponseJSON['destAmount'] as double; return Order( @@ -110,7 +108,6 @@ class WyreService { createdAt: createdAt, amount: amount.toString(), receiveAddress: walletAddress, - walletId: walletId - ); + walletId: walletId); } -} \ No newline at end of file +} diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 551769808..97ed234e6 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -84,6 +84,8 @@ abstract class DashboardViewModelBase with Store { name = appStore.wallet?.name; wallet ??= appStore.wallet; type = wallet.type; + isOutdatedElectrumWallet = + wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; final _wallet = wallet; if (_wallet is MoneroWallet) { @@ -234,9 +236,8 @@ abstract class DashboardViewModelBase with Store { await wallet.connectToNode(node: node); } - @computed - bool get isOutdatedElectrumWallet => - wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; + @observable + bool isOutdatedElectrumWallet; @action void _onWalletChange( @@ -246,6 +247,8 @@ abstract class DashboardViewModelBase with Store { this.wallet = wallet; type = wallet.type; name = wallet.name; + isOutdatedElectrumWallet = + wallet.type == WalletType.bitcoin && wallet.seed.split(' ').length < 24; if (wallet is MoneroWallet) { subname = wallet.account?.label;