From 9afb8c230f21eface442b9755119718bca472f16 Mon Sep 17 00:00:00 2001 From: JanekGOOGLE140 <109436710+JanekGOOGLE140@users.noreply.github.com> Date: Tue, 10 Oct 2023 23:59:10 +0200 Subject: [PATCH 1/6] Update of strings_pl.arb (#1121) * Update strings_pl.arb * Update strings_pl.arb --- res/values/strings_pl.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 6e8e863a7..f243a2343 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -292,7 +292,7 @@ "available_balance": "Dostępne środki", "hidden_balance": "Ukryte saldo", "sync_status_syncronizing": "SYNCHRONIZACJA", - "sync_status_syncronized": "SYNCHRONIZOWANIE", + "sync_status_syncronized": "ZSYNCHRONIZOWANO", "sync_status_not_connected": "NIE POŁĄCZONY", "sync_status_starting_sync": "ROZPOCZĘCIE SYNCHRONIZACJI", "sync_status_failed_connect": "POŁĄCZENIE NIEUDANE", From a035872fc99c3f2e8eac4438ef0628fb3644f995 Mon Sep 17 00:00:00 2001 From: Procyon Lotor <110021993+ProcyonLotor123@users.noreply.github.com> Date: Thu, 12 Oct 2023 03:20:19 +0300 Subject: [PATCH 2/6] Exolix integration: bugfix apiToken on fetchRates (#1120) * Add Exolix exchange integration * update tx payload * remove import * Improve mapping * Additional fixes * fix apiBaseUrl * Update trade_details_view_model.dart * Update exolix_exchange_provider.dart * Fix status URL * Fix fetch rates API error handling update limits API to use a valid amount and validate on success status code * bugfix added apiToken for fetchRate --------- Co-authored-by: Justin Ehrenhofer Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build.yml | 1 + assets/images/exolix.png | Bin 0 -> 1203 bytes .../exchange_provider_description.dart | 7 +- .../exolix/exolix_exchange_provider.dart | 295 ++++++++++++++++++ lib/exchange/exolix/exolix_request.dart | 20 ++ lib/exchange/trade_state.dart | 27 ++ .../screens/dashboard/widgets/trade_row.dart | 3 + lib/store/dashboard/trade_filter_store.dart | 18 +- .../dashboard/dashboard_view_model.dart | 5 + .../exchange/exchange_trade_view_model.dart | 4 + .../exchange/exchange_view_model.dart | 14 + lib/view_model/support_view_model.dart | 5 + lib/view_model/trade_details_view_model.dart | 10 + tool/utils/secret_key.dart | 1 + 14 files changed, 405 insertions(+), 5 deletions(-) create mode 100644 assets/images/exolix.png create mode 100644 lib/exchange/exolix/exolix_exchange_provider.dart create mode 100644 lib/exchange/exolix/exolix_request.dart diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 6b12911b6..af03c5e30 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -128,6 +128,7 @@ jobs: echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart diff --git a/assets/images/exolix.png b/assets/images/exolix.png new file mode 100644 index 0000000000000000000000000000000000000000..29e5f2db1d659dc3ca64858d800287c29b0a49fa GIT binary patch literal 1203 zcmV;k1WfyhP)Px(ZAnByRA@upT3t+3M-={MH7sigx|iVHa%-9*rD;eiDv(r5E2W5NYg6q{jN0l$ z+cY7PkiIm<#56^T51O>5sXnx%8dB;{OlVT-Pg|@HO{iU3kPSPJ+FaGHkbfx&^k z^U0W+*a2}bR6Yd3mjrNH!J)t?D0U3qQ4d+sXF8S$NDGx?AlPViQUkrCey{p204jnj zL?B02KBK@FA`^(p5}?87RU3KrI0+OuB{?t%z{Lw}Q!e25y4*nbs2PqafgF#gl+rnW znE_45_k=jgeEqloPK2G1Ku)f*jXLn%bQs5rNkZ7}8&I2#jWP5vwM_4iM4r#J+(13SXI#i^9@dtI5a2YU(uNmhna7;QxP2mD@jUbK>q zKq}(AsKv4JvG;_IfFvtIx1e2Ptm^x{YQ*lyACj#62;gn2pefa(IPSlrzNO2B?5;lB zx_!mZizPA!tl6s8U+PSlsD*by+X0qU5`u$vSFs=~3#Tf|?ZTk^%@542F2Ng5=2<3jsJjPET~|%@St>s0RWmgLcHJj-wyt&6a5@~OM#KvE zM#oTk>MXY&b0aeOlKO*Ni$icOA-MM18V*UlNPgSTLOL`=%ZVf_tpMH;(&P|cEz9_( zY#E2-gh<8N4%{4Occ{>4C2m>q8NFeJpeW#3RgC_QOA)oUmsX-^ezqx0bI%QYex=J) zE>=K$V<*f1i+62D{-#%Pvgmz;#>cg}C^KCF-w0xpk)nvSnD z%usM#BM}bb?u-mvtgeh}Ad4VR3;KO5kbMG_V#Bo-9d6(03;45kf8?{e!UEJi`^*#) zRll9X^#>0vuRc?>O`x)^3G)jVO)=uvjXN=PxhL}Zh3ZO385xoPTW;J!!@pN-8E>1w z6P3&H!q@fjjUd2@!Y#TgN$=}5TB>m`7({Wqb-$0E(DDuthXZeR9ZtA{!TZB#EBPQI zQzM01@^42+sqOSoJs97cZtz$#+g zV@bn%SiH5GTfV)b4#RzSx#a}}TZL1J%v?9to9bh|=%%LT743IaSe&ytDEO_oI%QkfGIfCwHv4qWNGF>{&^PuspT8b41Ke23l zSdm2~7M<)4-Gj!|jVMLQ-jB81#`d5T7eBTA_=E0AsS%i-n!jzn1^}#=pgjR*Pg#Yr zN&CXqr)3ePQ%{CSH#56U!b+I8_%}Z`CA*$vtl7DczP<+U={jod Rkx~Ev002ovPDHLkV1hCPI;#Kx literal 0 HcmV?d00001 diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index e545f69ce..151d018e0 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< static const trocador = ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); - static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: ''); + static const exolix = + ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png'); + + static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: ''); static ExchangeProviderDescription deserialize({required int raw}) { switch (raw) { @@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem with Serializable< case 5: return trocador; case 6: + return exolix; + case 7: return all; default: throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); diff --git a/lib/exchange/exolix/exolix_exchange_provider.dart b/lib/exchange/exolix/exolix_exchange_provider.dart new file mode 100644 index 000000000..01b8ecc2b --- /dev/null +++ b/lib/exchange/exolix/exolix_exchange_provider.dart @@ -0,0 +1,295 @@ +import 'dart:convert'; +import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:http/http.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/exchange_pair.dart'; +import 'package:cake_wallet/exchange/exchange_provider.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; + +class ExolixExchangeProvider extends ExchangeProvider { + ExolixExchangeProvider() : super(pairList: _supportedPairs()); + + static final apiKey = secrets.exolixApiKey; + static const apiBaseUrl = 'exolix.com'; + static const transactionsPath = '/api/v2/transactions'; + static const ratePath = '/api/v2/rate'; + + static const List _notSupported = [ + CryptoCurrency.usdt, + CryptoCurrency.xhv, + CryptoCurrency.btt, + CryptoCurrency.firo, + CryptoCurrency.zaddr, + CryptoCurrency.xvg, + CryptoCurrency.kmd, + CryptoCurrency.paxg, + CryptoCurrency.rune, + CryptoCurrency.scrt, + CryptoCurrency.btcln, + CryptoCurrency.cro, + CryptoCurrency.ftm, + CryptoCurrency.frax, + CryptoCurrency.gusd, + CryptoCurrency.gtc, + CryptoCurrency.weth, + ]; + + static List _supportedPairs() { + final supportedCurrencies = + CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList(); + + return supportedCurrencies + .map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true))) + .expand((i) => i) + .toList(); + } + + @override + String get title => 'Exolix'; + + @override + bool get isAvailable => true; + + @override + bool get isEnabled => true; + + @override + bool get supportsFixedRate => true; + + @override + ExchangeProviderDescription get description => ExchangeProviderDescription.exolix; + + @override + Future checkIsAvailable() async => true; + + static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float'; + + @override + Future fetchLimits( + {required CryptoCurrency from, + required CryptoCurrency to, + required bool isFixedRateMode}) async { + final params = { + 'rateType': getRateType(isFixedRateMode), + 'amount': '1', + }; + if (isFixedRateMode) { + params['coinFrom'] = _normalizeCurrency(to); + params['coinTo'] = _normalizeCurrency(from); + params['networkFrom'] = _networkFor(to); + params['networkTo'] = _networkFor(from); + } else { + params['coinFrom'] = _normalizeCurrency(from); + params['coinTo'] = _normalizeCurrency(to); + params['networkFrom'] = _networkFor(from); + params['networkTo'] = _networkFor(to); + } + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); + + if (response.statusCode != 200) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + return Limits(min: responseJSON['minAmount'] as double?); + } + + @override + Future createTrade({required TradeRequest request, required bool isFixedRateMode}) async { + final _request = request as ExolixRequest; + + final headers = {'Content-Type': 'application/json'}; + final body = { + 'coinFrom': _normalizeCurrency(_request.from), + 'coinTo': _normalizeCurrency(_request.to), + 'networkFrom': _networkFor(_request.from), + 'networkTo': _networkFor(_request.to), + 'withdrawalAddress': _request.address, + 'refundAddress': _request.refundAddress, + 'rateType': getRateType(isFixedRateMode), + 'apiToken': apiKey, + }; + + if (isFixedRateMode) { + body['withdrawalAmount'] = _request.toAmount; + } else { + body['amount'] = _request.fromAmount; + } + + final uri = Uri.https(apiBaseUrl, transactionsPath); + final response = await post(uri, headers: headers, body: json.encode(body)); + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final errors = responseJSON['errors'] as Map; + final errorMessage = errors.values.join(', '); + throw Exception(errorMessage); + } + + if (response.statusCode != 200 && response.statusCode != 201) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + final id = responseJSON['id'] as String; + final inputAddress = responseJSON['depositAddress'] as String; + final refundAddress = responseJSON['refundAddress'] as String?; + final extraId = responseJSON['depositExtraId'] as String?; + final payoutAddress = responseJSON['withdrawalAddress'] as String; + final amount = responseJSON['amount'].toString(); + + return Trade( + id: id, + from: _request.from, + to: _request.to, + provider: description, + inputAddress: inputAddress, + refundAddress: refundAddress, + extraId: extraId, + createdAt: DateTime.now(), + amount: amount, + state: TradeState.created, + payoutAddress: payoutAddress); + } + + @override + Future findTradeById({required String id}) async { + final findTradeByIdPath = transactionsPath + '/$id'; + final uri = Uri.https(apiBaseUrl, findTradeByIdPath); + final response = await get(uri); + + if (response.statusCode == 404) { + throw TradeNotFoundException(id, provider: description); + } + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final errors = responseJSON['errors'] as Map; + final errorMessage = errors.values.join(', '); + + throw TradeNotFoundException(id, provider: description, description: errorMessage); + } + + if (response.statusCode != 200) { + throw Exception('Unexpected http status: ${response.statusCode}'); + } + + final responseJSON = json.decode(response.body) as Map; + final coinFrom = responseJSON['coinFrom']['coinCode'] as String; + final from = CryptoCurrency.fromString(coinFrom); + final coinTo = responseJSON['coinTo']['coinCode'] as String; + final to = CryptoCurrency.fromString(coinTo); + final inputAddress = responseJSON['depositAddress'] as String; + final amount = responseJSON['amount'].toString(); + final status = responseJSON['status'] as String; + final state = TradeState.deserialize(raw: _prepareStatus(status)); + final extraId = responseJSON['depositExtraId'] as String?; + final outputTransaction = responseJSON['hashOut']['hash'] as String?; + final payoutAddress = responseJSON['withdrawalAddress'] as String; + + return Trade( + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: amount, + state: state, + extraId: extraId, + outputTransaction: outputTransaction, + payoutAddress: payoutAddress); + } + + @override + Future fetchRate( + {required CryptoCurrency from, + required CryptoCurrency to, + required double amount, + required bool isFixedRateMode, + required bool isReceiveAmount}) async { + try { + if (amount == 0) { + return 0.0; + } + + final params = { + 'coinFrom': _normalizeCurrency(from), + 'coinTo': _normalizeCurrency(to), + 'networkFrom': _networkFor(from), + 'networkTo': _networkFor(to), + 'rateType': getRateType(isFixedRateMode), + 'apiToken': apiKey, + }; + + if (isReceiveAmount) { + params['withdrawalAmount'] = amount.toString(); + } else { + params['amount'] = amount.toString(); + } + + final uri = Uri.https(apiBaseUrl, ratePath, params); + final response = await get(uri); + final responseJSON = json.decode(response.body) as Map; + + if (response.statusCode != 200) { + final message = responseJSON['message'] as String?; + throw Exception(message); + } + + final rate = responseJSON['rate'] as double; + + return rate; + } catch (e) { + print(e.toString()); + return 0.0; + } + } + + String _prepareStatus(String status) { + switch (status) { + case 'deleted': + case 'error': + return 'overdue'; + default: + return status; + } + } + + String _networkFor(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.arb: + return 'ARBITRUM'; + default: + return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title; + } + } + + String _normalizeCurrency(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.nano: + return 'XNO'; + case CryptoCurrency.bttc: + return 'BTT'; + case CryptoCurrency.zec: + return 'ZEC'; + default: + return currency.title; + } + } + + String _normalizeTag(String tag) { + switch (tag) { + case 'POLY': + return 'Polygon'; + default: + return tag; + } + } +} diff --git a/lib/exchange/exolix/exolix_request.dart b/lib/exchange/exolix/exolix_request.dart new file mode 100644 index 000000000..e97ffa386 --- /dev/null +++ b/lib/exchange/exolix/exolix_request.dart @@ -0,0 +1,20 @@ +import 'package:flutter/foundation.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; + +class ExolixRequest extends TradeRequest { + ExolixRequest( + {required this.from, + required this.to, + required this.address, + required this.fromAmount, + required this.toAmount, + required this.refundAddress}); + + CryptoCurrency from; + CryptoCurrency to; + String address; + String fromAmount; + String toAmount; + String refundAddress; +} diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index 98737339c..ebf74ce7a 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -35,6 +35,15 @@ class TradeState extends EnumerableItem with Serializable { static const completed = TradeState(raw: 'completed', title: 'Completed'); static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); + static const wait = TradeState(raw: 'wait', title: 'Waiting'); + static const overdue = TradeState(raw: 'overdue', title: 'Overdue'); + static const refund = TradeState(raw: 'refund', title: 'Refund'); + static const refunded = TradeState(raw: 'refunded', title: 'Refunded'); + static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation'); + static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed'); + static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); + static const sending = TradeState(raw: 'sending', title: 'Sending'); + static const success = TradeState(raw: 'success', title: 'Success'); static TradeState deserialize({required String raw}) { switch (raw) { case 'pending': @@ -77,6 +86,24 @@ class TradeState extends EnumerableItem with Serializable { return failed; case 'completed': return completed; + case 'wait': + return wait; + case 'overdue': + return overdue; + case 'refund': + return refund; + case 'refunded': + return refunded; + case 'confirmation': + return confirmation; + case 'confirmed': + return confirmed; + case 'exchanging': + return exchanging; + case 'sending': + return sending; + case 'success': + return success; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); } diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index a42593f24..7f570b98e 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget { borderRadius: BorderRadius.circular(50), child: Image.asset('assets/images/trocador.png', width: 36, height: 36)); break; + case ExchangeProviderDescription.exolix: + image = Image.asset('assets/images/exolix.png', width: 36, height: 36); + break; default: image = null; } diff --git a/lib/store/dashboard/trade_filter_store.dart b/lib/store/dashboard/trade_filter_store.dart index c772a35d6..799e8b951 100644 --- a/lib/store/dashboard/trade_filter_store.dart +++ b/lib/store/dashboard/trade_filter_store.dart @@ -13,7 +13,8 @@ abstract class TradeFilterStoreBase with Store { displaySideShift = true, displayMorphToken = true, displaySimpleSwap = true, - displayTrocador = true; + displayTrocador = true, + displayExolix = true; @observable bool displayXMRTO; @@ -33,8 +34,11 @@ abstract class TradeFilterStoreBase with Store { @observable bool displayTrocador; + @observable + bool displayExolix; + @computed - bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador; + bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix; @action void toggleDisplayExchange(ExchangeProviderDescription provider) { @@ -56,7 +60,10 @@ abstract class TradeFilterStoreBase with Store { break; case ExchangeProviderDescription.trocador: displayTrocador = !displayTrocador; - break; + break; + case ExchangeProviderDescription.exolix: + displayExolix = !displayExolix; + break; case ExchangeProviderDescription.all: if (displayAllTrades) { displayChangeNow = false; @@ -65,6 +72,7 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = false; displaySimpleSwap = false; displayTrocador = false; + displayExolix = false; } else { displayChangeNow = true; displaySideShift = true; @@ -72,6 +80,7 @@ abstract class TradeFilterStoreBase with Store { displayMorphToken = true; displaySimpleSwap = true; displayTrocador = true; + displayExolix = true; } break; } @@ -98,7 +107,8 @@ abstract class TradeFilterStoreBase with Store { ||(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) - ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)) + ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) + ||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix)) .toList() : _trades; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index f6eb7f244..03f0aa9a8 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -98,6 +98,11 @@ abstract class DashboardViewModelBase with Store { caption: ExchangeProviderDescription.trocador.title, onChanged: () => tradeFilterStore .toggleDisplayExchange(ExchangeProviderDescription.trocador)), + FilterItem( + value: () => tradeFilterStore.displayExolix, + caption: ExchangeProviderDescription.exolix.title, + onChanged: () => tradeFilterStore + .toggleDisplayExchange(ExchangeProviderDescription.exolix)), ] }, subname = '', diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index cfabd994f..346844171 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; @@ -53,6 +54,9 @@ abstract class ExchangeTradeViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; + case ExchangeProviderDescription.exolix: + _provider = ExolixExchangeProvider(); + break; } _updateItems(); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index b438e9b74..47b408bc2 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -6,6 +6,8 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -150,6 +152,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider(useTorOnly: _useTorOnly), + ExolixExchangeProvider(), ]; @observable @@ -546,6 +549,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with amount = isFixedRateMode ? receiveAmount : depositAmount; } + if (provider is ExolixExchangeProvider) { + request = ExolixRequest( + from: depositCurrency, + to: receiveCurrency, + fromAmount: depositAmount.replaceAll(',', '.'), + toAmount: receiveAmount.replaceAll(',', '.'), + refundAddress: depositAddress, + address: receiveAddress); + amount = isFixedRateMode ? receiveAmount : depositAmount; + } + amount = amount.replaceAll(',', '.'); if (limitsState is LimitsLoadedSuccessfully) { diff --git a/lib/view_model/support_view_model.dart b/lib/view_model/support_view_model.dart index d3b14c59b..ccef76154 100644 --- a/lib/view_model/support_view_model.dart +++ b/lib/view_model/support_view_model.dart @@ -53,6 +53,11 @@ abstract class SupportViewModelBase with Store { icon: 'assets/images/simpleSwap.png', linkTitle: 'support@simpleswap.io', link: 'mailto:support@simpleswap.io'), + LinkListItem( + title: 'Exolix', + icon: 'assets/images/exolix.png', + linkTitle: 'support@exolix.com', + link: 'mailto:support@exolix.com'), if (!isMoneroOnly) ... [ LinkListItem( title: 'Wyre', diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index c0b1ac461..393629237 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; @@ -54,6 +55,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.trocador: _provider = TrocadorExchangeProvider(); break; + case ExchangeProviderDescription.exolix: + _provider = ExolixExchangeProvider(); + break; } _updateItems(); @@ -157,6 +161,12 @@ abstract class TradeDetailsViewModelBase with Store { items.add(StandartListItem( title: '${trade.providerName} ${S.current.password}', value: trade.password ?? '')); } + + if (trade.provider == ExchangeProviderDescription.exolix) { + final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}'; + items.add( + TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL))); + } } void _launchUrl(String url) { diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index fc2cc7fa8..a8c6a6166 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -32,6 +32,7 @@ class SecretKey { SecretKey('fiatApiKey', () => ''), SecretKey('payfuraApiKey', () => ''), SecretKey('chatwootWebsiteToken', () => ''), + SecretKey('exolixApiKey', () => ''), SecretKey('robinhoodApplicationId', () => ''), SecretKey('robinhoodCIdApiSecret', () => ''), SecretKey('walletConnectProjectId', () => ''), From acb05178713e2d4b0bad2ac84f410fb69e2e5f67 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Wed, 11 Oct 2023 20:32:11 -0400 Subject: [PATCH 3/6] fix , bug and swapping for nano (#1123) --- cw_nano/lib/nano_wallet.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index da50f4ebb..bf7cc6bca 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -169,8 +169,8 @@ abstract class NanoWalletBase if (txOut.sendAll) { amt = balance[currency]?.currentBalance ?? BigInt.zero; } else { - amt = BigInt.tryParse( - NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ?? + amt = BigInt.tryParse(NanoUtil.getAmountAsRaw( + txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ?? BigInt.zero; } @@ -182,7 +182,9 @@ abstract class NanoWalletBase final block = await _client.constructSendBlock( amountRaw: amt.toString(), - destinationAddress: txOut.extractedAddress ?? txOut.address, + destinationAddress: credentials.outputs.first.isParsedAddress + ? credentials.outputs.first.extractedAddress! + : credentials.outputs.first.address, privateKey: _privateKey!, balanceAfterTx: runningBalance, previousHash: previousHash, From 66301ff2478c1879859b5a20d4deff0559f7af25 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 13 Oct 2023 01:50:16 +0300 Subject: [PATCH 4/6] CW-432-Add-Bitcoin-Cash-BCH (#1041) * initial commit * creating and restoring a wallet * [skip ci] add transaction priority * fix send and unspent screen * fix transaction priority type * replace Unspend with BitcoinUnspent * add transaction creation * fix transaction details screen * minor fix * fix create side wallet * basic transaction creation flow * fix fiat amount calculation * edit wallet * minor fix * fix address book parsing * merge commit fixes * minor fixes * Update gradle.properties * fix bch unspent coins * minor fix * fix BitcoinCashTransactionPriority * Fetch tags first before switching to one of them * Update build_haven.sh * Update build_haven.sh * Update build_haven.sh * Update build_haven.sh * update transaction build function * Update build_haven.sh * add ability to rename and delete * fix address format * Update pubspec.lock * Revert "fix address format" This reverts commit 1549bf4d8c3bdb0addbd6e3c5f049ebc3799ff8f. * fix address format for exange * restore from qr * Update configure.dart * [skip ci] minor fix * fix default fee rate * Update onramper_buy_provider.dart * Update wallet_address_list_view_model.dart * PR comments fixes * Update exchange_view_model.dart * fix merge conflict * Update address_validator.dart * merge fixes * update initialMigrationVersion * move cw_bitbox to Cake tech * PR fixes * PR fixes * Fix configure.dart brackets * update the new version text after macos * dummy change to run workflow * Fix Nano restore from QR issue Fix Conflicts with main * PR fixes * Update app_config.sh --------- Co-authored-by: Omar Hatem --- .github/workflows/pr_test_build.yml | 26 +- .gitignore | 1 + android/gradle.properties | 2 +- assets/bitcoin_cash_electrum_server_list.yml | 3 + configure_cake_wallet_android.sh | 1 + .../lib/bitcoin_transaction_priority.dart | 52 ++- cw_bitcoin/lib/bitcoin_unspent.dart | 23 +- cw_bitcoin/lib/bitcoin_wallet_addresses.dart | 45 ++- cw_bitcoin/lib/electrum_wallet.dart | 251 +++++++-------- cw_bitcoin/lib/electrum_wallet_addresses.dart | 17 +- cw_bitcoin/pubspec.lock | 9 + cw_bitcoin/pubspec.yaml | 4 + cw_bitcoin_cash/.gitignore | 30 ++ cw_bitcoin_cash/.metadata | 10 + cw_bitcoin_cash/CHANGELOG.md | 3 + cw_bitcoin_cash/LICENSE | 1 + cw_bitcoin_cash/README.md | 39 +++ cw_bitcoin_cash/analysis_options.yaml | 4 + cw_bitcoin_cash/lib/cw_bitcoin_cash.dart | 9 + .../lib/src/bitcoin_cash_address_utils.dart | 5 + .../lib/src/bitcoin_cash_base.dart | 7 + .../lib/src/bitcoin_cash_wallet.dart | 297 ++++++++++++++++++ .../src/bitcoin_cash_wallet_addresses.dart | 34 ++ ...coin_cash_wallet_creation_credentials.dart | 26 ++ .../lib/src/bitcoin_cash_wallet_service.dart | 107 +++++++ ..._cash_mnemonic_is_incorrect_exception.dart | 5 + .../lib/src/exceptions/exceptions.dart | 1 + cw_bitcoin_cash/lib/src/mnemonic.dart | 11 + .../src/pending_bitcoin_cash_transaction.dart | 62 ++++ .../.plugin_symlinks/path_provider_linux | 1 + .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 23 ++ .../Flutter/GeneratedPluginRegistrant.swift | 12 + .../ephemeral/Flutter-Generated.xcconfig | 11 + .../ephemeral/flutter_export_environment.sh | 12 + cw_bitcoin_cash/pubspec.yaml | 76 +++++ .../test/cw_bitcoin_cash_test.dart | 12 + .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 23 ++ cw_core/lib/amount_converter.dart | 1 + cw_core/lib/currency_for_wallet_type.dart | 2 + cw_core/lib/node.dart | 4 + .../lib}/unspent_transaction_output.dart | 0 cw_core/lib/wallet_type.dart | 15 + lib/bitcoin/cw_bitcoin.dart | 12 +- lib/bitcoin_cash/cw_bitcoin_cash.dart | 45 +++ lib/buy/onramper/onramper_buy_provider.dart | 2 + lib/core/address_validator.dart | 9 + lib/core/seed_validator.dart | 2 + lib/di.dart | 3 + lib/entities/default_settings_migration.dart | 87 +++-- lib/entities/main_actions.dart | 2 + lib/entities/node_list.dart | 23 +- lib/entities/preferences_key.dart | 2 + lib/entities/priority_for_wallet_type.dart | 3 + lib/ethereum/cw_ethereum.dart | 3 + lib/main.dart | 2 +- .../desktop_wallet_selection_dropdown.dart | 3 + .../dashboard/widgets/menu_widget.dart | 7 +- lib/src/screens/seed/pre_seed_page.dart | 1 + .../unspent_coins_list_page.dart | 7 +- .../screens/wallet_list/wallet_list_page.dart | 3 + lib/store/settings_store.dart | 195 +++++++----- .../dashboard/transaction_list_item.dart | 1 + .../exchange/exchange_view_model.dart | 29 +- .../node_list/node_list_view_model.dart | 3 + .../restore/restore_from_qr_vm.dart | 4 + .../restore/wallet_restore_from_qr_code.dart | 6 + lib/view_model/send/output.dart | 10 +- lib/view_model/send/send_view_model.dart | 46 +-- .../settings/other_settings_view_model.dart | 4 +- .../transaction_details_view_model.dart | 4 + .../unspent_coins_details_view_model.dart | 25 +- .../unspent_coins_list_view_model.dart | 37 +-- ...let_address_edit_or_create_view_model.dart | 3 +- .../wallet_address_list_view_model.dart | 26 +- lib/view_model/wallet_keys_view_model.dart | 6 +- lib/view_model/wallet_new_vm.dart | 5 +- lib/view_model/wallet_restore_view_model.dart | 19 +- model_generator.sh | 1 + pubspec_base.yaml | 1 + scripts/android/app_config.sh | 2 +- scripts/android/pubspec_gen.sh | 2 +- scripts/ios/app_config.sh | 4 +- scripts/macos/app_config.sh | 2 +- tool/configure.dart | 121 +++++-- 88 files changed, 1685 insertions(+), 416 deletions(-) create mode 100644 assets/bitcoin_cash_electrum_server_list.yml create mode 100644 cw_bitcoin_cash/.gitignore create mode 100644 cw_bitcoin_cash/.metadata create mode 100644 cw_bitcoin_cash/CHANGELOG.md create mode 100644 cw_bitcoin_cash/LICENSE create mode 100644 cw_bitcoin_cash/README.md create mode 100644 cw_bitcoin_cash/analysis_options.yaml create mode 100644 cw_bitcoin_cash/lib/cw_bitcoin_cash.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart create mode 100644 cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart create mode 100644 cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart create mode 100644 cw_bitcoin_cash/lib/src/exceptions/exceptions.dart create mode 100644 cw_bitcoin_cash/lib/src/mnemonic.dart create mode 100644 cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart create mode 120000 cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux create mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc create mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h create mode 100644 cw_bitcoin_cash/linux/flutter/generated_plugins.cmake create mode 100644 cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig create mode 100644 cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh create mode 100644 cw_bitcoin_cash/pubspec.yaml create mode 100644 cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart create mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc create mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h create mode 100644 cw_bitcoin_cash/windows/flutter/generated_plugins.cmake rename {lib/entities => cw_core/lib}/unspent_transaction_output.dart (100%) create mode 100644 lib/bitcoin_cash/cw_bitcoin_cash.dart mode change 100755 => 100644 model_generator.sh mode change 100755 => 100644 scripts/ios/app_config.sh diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index af03c5e30..5434429b2 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -42,6 +42,7 @@ jobs: cd cake_wallet/scripts/android/ ./install_ndk.sh source ./app_env.sh cakewallet + chmod +x pubspec_gen.sh ./app_config.sh - name: Cache Externals @@ -92,6 +93,7 @@ jobs: cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs @@ -141,18 +143,18 @@ jobs: cd /opt/android/cake_wallet flutter build apk --release - # - name: Push to App Center - # run: | - # echo 'Installing App Center CLI tools' - # npm install -g appcenter-cli - # echo "Publishing test to App Center" - # appcenter distribute release \ - # --group "Testers" \ - # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ - # --release-notes ${GITHUB_HEAD_REF} \ - # --app Cake-Labs/Cake-Wallet \ - # --token ${{ secrets.APP_CENTER_TOKEN }} \ - # --quiet +# - name: Push to App Center +# run: | +# echo 'Installing App Center CLI tools' +# npm install -g appcenter-cli +# echo "Publishing test to App Center" +# appcenter distribute release \ +# --group "Testers" \ +# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ +# --release-notes ${GITHUB_HEAD_REF} \ +# --app Cake-Labs/Cake-Wallet \ +# --token ${{ secrets.APP_CENTER_TOKEN }} \ +# --quiet - name: Rename apk file run: | diff --git a/.gitignore b/.gitignore index e8fb0048c..c735d4058 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart lib/monero/monero.dart lib/haven/haven.dart lib/ethereum/ethereum.dart +lib/bitcoin_cash/bitcoin_cash.dart lib/nano/nano.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png diff --git a/android/gradle.properties b/android/gradle.properties index a5965ab8d..38c8d4544 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.enableR8=true android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true diff --git a/assets/bitcoin_cash_electrum_server_list.yml b/assets/bitcoin_cash_electrum_server_list.yml new file mode 100644 index 000000000..d76668169 --- /dev/null +++ b/assets/bitcoin_cash_electrum_server_list.yml @@ -0,0 +1,3 @@ +- + uri: bitcoincash.stackwallet.com:50002 + is_default: true \ No newline at end of file diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet_android.sh index 792159f29..b8aa433de 100755 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet_android.sh @@ -8,4 +8,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs diff --git a/cw_bitcoin/lib/bitcoin_transaction_priority.dart b/cw_bitcoin/lib/bitcoin_transaction_priority.dart index d82ea429e..10953a2e0 100644 --- a/cw_bitcoin/lib/bitcoin_transaction_priority.dart +++ b/cw_bitcoin/lib/bitcoin_transaction_priority.dart @@ -1,5 +1,4 @@ import 'package:cw_core/transaction_priority.dart'; -//import 'package:cake_wallet/generated/i18n.dart'; class BitcoinTransactionPriority extends TransactionPriority { const BitcoinTransactionPriority({required String title, required int raw}) @@ -100,4 +99,55 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority { return label; } + } +class BitcoinCashTransactionPriority extends BitcoinTransactionPriority { + const BitcoinCashTransactionPriority({required String title, required int raw}) + : super(title: title, raw: raw); + + static const List all = [fast, medium, slow]; + static const BitcoinCashTransactionPriority slow = + BitcoinCashTransactionPriority(title: 'Slow', raw: 0); + static const BitcoinCashTransactionPriority medium = + BitcoinCashTransactionPriority(title: 'Medium', raw: 1); + static const BitcoinCashTransactionPriority fast = + BitcoinCashTransactionPriority(title: 'Fast', raw: 2); + + static BitcoinCashTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception('Unexpected token: $raw for BitcoinCashTransactionPriority deserialize'); + } + } + + @override + String get units => 'Satoshi'; + + @override + String toString() { + var label = ''; + + switch (this) { + case BitcoinCashTransactionPriority.slow: + label = 'Slow'; // S.current.transaction_priority_slow; + break; + case BitcoinCashTransactionPriority.medium: + label = 'Medium'; // S.current.transaction_priority_medium; + break; + case BitcoinCashTransactionPriority.fast: + label = 'Fast'; // S.current.transaction_priority_fast; + break; + default: + break; + } + + return label; + } +} + diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index e5a0e8cac..9c198c27c 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,24 +1,15 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; -class BitcoinUnspent { - BitcoinUnspent(this.address, this.hash, this.value, this.vout) - : isSending = true, - isFrozen = false, - note = ''; +class BitcoinUnspent extends Unspent { + BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout) + : bitcoinAddressRecord = addressRecord, + super(addressRecord.address, hash, value, vout, null); factory BitcoinUnspent.fromJSON( - BitcoinAddressRecord address, Map json) => + BitcoinAddressRecord address, Map json) => BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int); - final BitcoinAddressRecord address; - final String hash; - final int value; - final int vout; - - bool get isP2wpkh => - address.address.startsWith('bc') || address.address.startsWith('ltc'); - bool isSending; - bool isFrozen; - String note; + final BitcoinAddressRecord bitcoinAddressRecord; } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index de3fdfbca..36d37127d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -1,39 +1,34 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; -import 'package:cw_bitcoin/electrum.dart'; -import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; part 'bitcoin_wallet_addresses.g.dart'; -class BitcoinWalletAddresses = BitcoinWalletAddressesBase - with _$BitcoinWalletAddresses; +class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses; -abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses - with Store { - BitcoinWalletAddressesBase( - WalletInfo walletInfo, +abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store { + BitcoinWalletAddressesBase(WalletInfo walletInfo, {required bitcoin.HDWallet mainHd, - required bitcoin.HDWallet sideHd, - required bitcoin.NetworkType networkType, - required ElectrumClient electrumClient, - List? initialAddresses, - int initialRegularAddressIndex = 0, - int initialChangeAddressIndex = 0}) - : super( - walletInfo, - initialAddresses: initialAddresses, - initialRegularAddressIndex: initialRegularAddressIndex, - initialChangeAddressIndex: initialChangeAddressIndex, - mainHd: mainHd, - sideHd: sideHd, - electrumClient: electrumClient, - networkType: networkType); + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); @override String getAddress({required int index, required bitcoin.HDWallet hd}) => generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); -} \ No newline at end of file +} diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 1c0a1e4e3..804b53379 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:mobx/mobx.dart'; @@ -34,45 +36,52 @@ import 'package:cw_bitcoin/electrum.dart'; import 'package:hex/hex.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:collection/collection.dart'; +import 'package:bip32/bip32.dart'; part 'electrum_wallet.g.dart'; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; -abstract class ElectrumWalletBase extends WalletBase with Store { +abstract class ElectrumWalletBase + extends WalletBase + with Store { ElectrumWalletBase( {required String password, - required WalletInfo walletInfo, - required Box unspentCoinsInfo, - required this.networkType, - required this.mnemonic, - required Uint8List seedBytes, - List? initialAddresses, - ElectrumClient? electrumClient, - ElectrumBalance? initialBalance, - CryptoCurrency? currency}) - : hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) - .derivePath("m/0'/0"), + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required this.networkType, + required this.mnemonic, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumClient? electrumClient, + ElectrumBalance? initialBalance, + CryptoCurrency? currency}) + : hd = currency == CryptoCurrency.bch + ? bitcoinCashHDWallet(seedBytes) + : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"), syncStatus = NotConnectedSyncStatus(), _password = password, _feeRates = [], _isTransactionUpdating = false, unspentCoins = [], _scripthashesUpdateSubject = {}, - balance = ObservableMap.of( - currency != null - ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, - frozen: 0)} - : {}), + balance = ObservableMap.of(currency != null + ? { + currency: + initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0) + } + : {}), this.unspentCoinsInfo = unspentCoinsInfo, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; - transactionHistory = - ElectrumTransactionHistory(walletInfo: walletInfo, password: password); + transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); } + static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) => + bitcoin.HDWallet.fromSeed(seedBytes) + .derivePath("m/44'/145'/0'/0"); + static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 146 + outputsCounts * 33 + 8; @@ -98,9 +107,9 @@ abstract class ElectrumWalletBase extends WalletBase get publicScriptHashes => walletAddresses.addresses - .where((addr) => !addr.isHidden) - .map((addr) => scriptHash(addr.address, networkType: networkType)) - .toList(); + .where((addr) => !addr.isHidden) + .map((addr) => scriptHash(addr.address, networkType: networkType)) + .toList(); String get xpub => hd.base58!; @@ -110,8 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase BitcoinWalletKeys( - wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); + BitcoinWalletKeys get keys => + BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); String _password; List unspentCoins; @@ -139,8 +148,8 @@ abstract class ElectrumWalletBase extends WalletBase _feeRates = await electrumClient.feeRates()); + Timer.periodic( + const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); syncStatus = SyncedSyncStatus(); } catch (e, stacktrace) { @@ -169,8 +178,7 @@ abstract class ElectrumWalletBase extends WalletBase createTransaction( - Object credentials) async { + Future createTransaction(Object credentials) async { const minAmount = 546; final transactionCredentials = credentials as BitcoinTransactionCredentials; final inputs = []; @@ -204,13 +212,11 @@ abstract class ElectrumWalletBase extends WalletBase item.sendAll - || item.formattedCryptoAmount! <= 0)) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { throw BitcoinTransactionWrongBalanceException(currency); } - credentialsAmount = outputs.fold(0, (acc, value) => - acc + value.formattedCryptoAmount!); + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); if (allAmount - credentialsAmount < minAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -227,9 +233,7 @@ abstract class ElectrumWalletBase extends WalletBase allAmount) { throw BitcoinTransactionWrongBalanceException(currency); @@ -291,8 +295,8 @@ abstract class ElectrumWalletBase extends WalletBase json.encode({ - 'mnemonic': mnemonic, - 'account_index': walletAddresses.currentReceiveAddressIndex.toString(), - 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), - 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), - 'balance': balance[currency]?.toJSON() - }); + 'mnemonic': mnemonic, + 'account_index': walletAddresses.currentReceiveAddressIndex.toString(), + 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), + 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), + 'balance': balance[currency]?.toJSON() + }); int feeRate(TransactionPriority priority) { try { @@ -364,34 +361,29 @@ abstract class ElectrumWalletBase extends WalletBase + int feeAmountForPriority( + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); - int feeAmountWithFeeRate(int feeRate, int inputsCount, - int outputsCount) => + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => feeRate * estimatedTransactionSize(inputsCount, outputsCount); @override - int calculateEstimatedFee(TransactionPriority? priority, int? amount, - {int? outputsCount}) { + int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) { if (priority is BitcoinTransactionPriority) { - return calculateEstimatedFeeWithFeeRate( - feeRate(priority), - amount, - outputsCount: outputsCount); + return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount, + outputsCount: outputsCount); } return 0; } - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, - {int? outputsCount}) { + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { int inputsCount = 0; if (amount != null) { @@ -420,8 +412,7 @@ abstract class ElectrumWalletBase extends WalletBase makePath() async => - pathForWallet(name: walletInfo.name, type: walletInfo.type); + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future updateUnspent() async { final unspent = await Future.wait(walletAddresses .addresses.map((address) => electrumClient .getListUnspentWithAddress(address.address, networkType) .then((unspent) => unspent - .map((unspent) { - try { - return BitcoinUnspent.fromJSON(address, unspent); - } catch(_) { - return null; - } - }).whereNotNull()))); + .map((unspent) { + try { + return BitcoinUnspent.fromJSON(address, unspent); + } catch(_) { + return null; + } + }).whereNotNull()))); unspentCoins = unspent.expand((e) => e).toList(); if (unspentCoinsInfo.isEmpty) { @@ -498,8 +487,8 @@ abstract class ElectrumWalletBase extends WalletBase - element.walletId.contains(id) && element.hash.contains(coin.hash)); + final coinInfoList = unspentCoinsInfo.values + .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash)); if (coinInfoList.isNotEmpty) { final coinInfo = coinInfoList.first; @@ -518,14 +507,14 @@ abstract class ElectrumWalletBase extends WalletBase _addCoinInfo(BitcoinUnspent coin) async { final newInfo = UnspentCoinsInfo( - walletId: id, - hash: coin.hash, - isFrozen: coin.isFrozen, - isSending: coin.isSending, - noteRaw: coin.note, - address: coin.address.address, - value: coin.value, - vout: coin.vout, + walletId: id, + hash: coin.hash, + isFrozen: coin.isFrozen, + isSending: coin.isSending, + noteRaw: coin.note, + address: coin.bitcoinAddressRecord.address, + value: coin.value, + vout: coin.vout, ); await unspentCoinsInfo.add(newInfo); @@ -534,8 +523,8 @@ abstract class ElectrumWalletBase extends WalletBase _refreshUnspentCoinsInfo() async { try { final List keys = []; - final currentWalletUnspentCoins = unspentCoinsInfo.values - .where((element) => element.walletId.contains(id)); + final currentWalletUnspentCoins = + unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); if (currentWalletUnspentCoins.isNotEmpty) { currentWalletUnspentCoins.forEach((element) { @@ -571,27 +560,19 @@ abstract class ElectrumWalletBase extends WalletBase fetchTransactionInfo( {required String hash, required int height}) async { - try { - final tx = await getTransactionExpanded(hash: hash, height: height); - final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); - return ElectrumTransactionInfo.fromElectrumBundle( - tx, - walletInfo.type, - networkType, - addresses: addresses, - height: height); - } catch(_) { - return null; - } + try { + final tx = await getTransactionExpanded(hash: hash, height: height); + final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); + return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType, + addresses: addresses, height: height); + } catch (_) { + return null; + } } @override @@ -602,10 +583,8 @@ abstract class ElectrumWalletBase extends WalletBase electrumClient - .getHistory(scriptHash) - .then((history) => {scriptHash: history})); + final histories = addressHashes.keys.map((scriptHash) => + electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); final historyResults = await Future.wait(histories); historyResults.forEach((history) { history.entries.forEach((historyItem) { @@ -616,19 +595,16 @@ abstract class ElectrumWalletBase extends WalletBase>( - {}, (acc, tx) { + final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) { + try { + return fetchTransactionInfo( + hash: transaction['tx_hash'] as String, height: transaction['height'] as int); + } catch (_) { + return Future.value(null); + } + })); + return historiesWithDetails + .fold>({}, (acc, tx) { if (tx == null) { return acc; } @@ -680,9 +656,8 @@ abstract class ElectrumWalletBase extends WalletBase _fetchBalances() async { final addresses = walletAddresses.addresses.toList(); final balanceFutures = >>[]; - for (var i = 0; i < addresses.length; i++) { - final addressRecord = addresses[i]; + final addressRecord = addresses[i] ; final sh = scriptHash(addressRecord.address, networkType: networkType); final balanceFuture = electrumClient.getBalance(sh); balanceFutures.add(balanceFuture); @@ -691,8 +666,10 @@ abstract class ElectrumWalletBase extends WalletBase updateBalance() async { @@ -727,9 +704,7 @@ abstract class ElectrumWalletBase extends WalletBase addr.isHidden) - .toList(); + var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList(); if (addresses.length < minCountOfHiddenAddresses) { addresses = walletAddresses.addresses.toList(); diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 741c2fe1c..ab99a875c 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,9 +1,11 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:bitbox/bitbox.dart' as bitbox; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; part 'electrum_wallet_addresses.g.dart'; @@ -38,6 +40,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { static const defaultChangeAddressesCount = 17; static const gap = 20; + static String toCashAddr(String address) => bitbox.Address.toCashAddress(address); + final ObservableList addresses; final ObservableList receiveAddresses; final ObservableList changeAddresses; @@ -50,10 +54,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @computed String get address { if (receiveAddresses.isEmpty) { - return generateNewAddress().address; + final address = generateNewAddress().address; + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address; } + final receiveAddress = receiveAddresses.first.address; - return receiveAddresses.first.address; + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress; } @override @@ -105,10 +111,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future getChangeAddress() async { updateChangeAddresses(); - + if (changeAddresses.isEmpty) { - final newAddresses = await _createNewAddresses( - gap, + final newAddresses = await _createNewAddresses(gap, hd: sideHd, startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 @@ -179,7 +184,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } else { addrs = await _createNewAddresses( isHidden - ? defaultChangeAddressesCount + ? defaultChangeAddressesCount : defaultReceiveAddressesCount, startIndex: 0, hd: hd, diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 843daa771..eb75227b6 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -66,6 +66,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + bitbox: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 + url: "https://github.com/cake-tech/bitbox-flutter.git" + source: git + version: "1.0.1" bitcoin_flutter: dependency: "direct main" description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index dae0af39b..693d5af7a 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -23,6 +23,10 @@ dependencies: git: url: https://github.com/cake-tech/bitcoin_flutter.git ref: cake-update-v3 + bitbox: + git: + url: https://github.com/cake-tech/bitbox-flutter.git + ref: master rxdart: ^0.27.5 unorm_dart: ^0.2.0 cryptography: ^2.0.5 diff --git a/cw_bitcoin_cash/.gitignore b/cw_bitcoin_cash/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/cw_bitcoin_cash/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/cw_bitcoin_cash/.metadata b/cw_bitcoin_cash/.metadata new file mode 100644 index 000000000..4161da6ea --- /dev/null +++ b/cw_bitcoin_cash/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b06b8b2710955028a6b562f5aa6fe62941d6febf + channel: stable + +project_type: package diff --git a/cw_bitcoin_cash/CHANGELOG.md b/cw_bitcoin_cash/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_bitcoin_cash/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_bitcoin_cash/LICENSE b/cw_bitcoin_cash/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_bitcoin_cash/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_bitcoin_cash/README.md b/cw_bitcoin_cash/README.md new file mode 100644 index 000000000..02fe8ecab --- /dev/null +++ b/cw_bitcoin_cash/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/cw_bitcoin_cash/analysis_options.yaml b/cw_bitcoin_cash/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_bitcoin_cash/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_bitcoin_cash/lib/cw_bitcoin_cash.dart b/cw_bitcoin_cash/lib/cw_bitcoin_cash.dart new file mode 100644 index 000000000..732474ac4 --- /dev/null +++ b/cw_bitcoin_cash/lib/cw_bitcoin_cash.dart @@ -0,0 +1,9 @@ +library cw_bitcoin_cash; + +export 'src/bitcoin_cash_base.dart'; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart new file mode 100644 index 000000000..ca47ea9f6 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart @@ -0,0 +1,5 @@ +import 'package:bitbox/bitbox.dart' as bitbox; + +class AddressUtils { + static String getCashAddrFormat(String address) => bitbox.Address.toCashAddress(address); +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart new file mode 100644 index 000000000..4699b1649 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart @@ -0,0 +1,7 @@ +export 'bitcoin_cash_wallet.dart'; +export 'bitcoin_cash_wallet_addresses.dart'; +export 'bitcoin_cash_wallet_creation_credentials.dart'; +export 'bitcoin_cash_wallet_service.dart'; +export 'exceptions/exceptions.dart'; +export 'mnemonic.dart'; +export 'bitcoin_cash_address_utils.dart'; diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart new file mode 100644 index 000000000..b4924e2db --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -0,0 +1,297 @@ +import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; + +import 'bitcoin_cash_base.dart'; + +part 'bitcoin_cash_wallet.g.dart'; + +class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet; + +abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { + BitcoinCashWalletBase( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: bitcoin.bitcoin, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.bch) { + walletAddresses = BitcoinCashWalletAddresses(walletInfo, + electrumClient: electrumClient, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: bitcoin.HDWallet.fromSeed(seedBytes) + .derivePath("m/44'/145'/0'/1"), + networkType: networkType); + } + + + static Future create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + List? initialAddresses, + ElectrumBalance? initialBalance, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) async { + return BitcoinCashWallet( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: await Mnemonic.toSeed(mnemonic), + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex); + } + + static Future open({ + required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + }) async { + final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password); + return BitcoinCashWallet( + mnemonic: snp.mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: snp.balance, + seedBytes: await Mnemonic.toSeed(snp.mnemonic), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex); + } + + @override + Future createTransaction(Object credentials) async { + const minAmount = 546; + final transactionCredentials = credentials as BitcoinTransactionCredentials; + final inputs = []; + final outputs = transactionCredentials.outputs; + final hasMultiDestination = outputs.length > 1; + + var allInputsAmount = 0; + + if (unspentCoins.isEmpty) await updateUnspent(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + allInputsAmount += utx.value; + inputs.add(utx); + } + } + + if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); + + final allAmountFee = transactionCredentials.feeRate != null + ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length) + : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length); + + final allAmount = allInputsAmount - allAmountFee; + + var credentialsAmount = 0; + var amount = 0; + var fee = 0; + + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!); + + if (allAmount - credentialsAmount < minAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + amount = credentialsAmount; + + if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount, + outputsCount: outputs.length + 1); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount, + outputsCount: outputs.length + 1); + } + } else { + final output = outputs.first; + credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0; + + if (credentialsAmount > allAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + amount = output.sendAll || allAmount - credentialsAmount < minAmount + ? allAmount + : credentialsAmount; + + if (output.sendAll || amount == allAmount) { + fee = allAmountFee; + } else if (transactionCredentials.feeRate != null) { + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount); + } else { + fee = calculateEstimatedFee(transactionCredentials.priority, amount); + } + } + + if (fee == 0) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + final totalAmount = amount + fee; + + if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + final txb = bitbox.Bitbox.transactionBuilder(testnet: false); + + final changeAddress = await walletAddresses.getChangeAddress(); + var leftAmount = totalAmount; + var totalInputAmount = 0; + + inputs.clear(); + + for (final utx in unspentCoins) { + if (utx.isSending) { + leftAmount = leftAmount - utx.value; + totalInputAmount += utx.value; + inputs.add(utx); + + if (leftAmount <= 0) { + break; + } + } + } + + if (inputs.isEmpty) throw BitcoinTransactionNoInputsException(); + + if (amount <= 0 || totalInputAmount < totalAmount) { + throw BitcoinTransactionWrongBalanceException(currency); + } + + inputs.forEach((input) { + txb.addInput(input.hash, input.vout); + }); + + outputs.forEach((item) { + final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; + final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + txb.addOutput(outputAddress, outputAmount!); + }); + + final estimatedSize = bitbox.BitcoinCash.getByteCount(inputs.length, outputs.length + 1); + + var feeAmount = 0; + + if (transactionCredentials.feeRate != null) { + feeAmount = transactionCredentials.feeRate! * estimatedSize; + } else { + feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize; + } + + final changeValue = totalInputAmount - amount - feeAmount; + + if (changeValue > minAmount) { + txb.addOutput(changeAddress, changeValue); + } + + for (var i = 0; i < inputs.length; i++) { + final input = inputs[i]; + final keyPair = generateKeyPair( + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, + index: input.bitcoinAddressRecord.index); + txb.sign(i, keyPair, input.value); + } + + // Build the transaction + final tx = txb.build(); + + return PendingBitcoinCashTransaction(tx, type, + electrumClient: electrumClient, amount: amount, fee: fee); + } + + bitbox.ECPair generateKeyPair( + {required bitcoin.HDWallet hd, + required int index}) => + bitbox.ECPair.fromWIF(hd.derive(index).wif!); + + @override + int feeAmountForPriority( + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) => + feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); + + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) => + feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount); + + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) { + int inputsCount = 0; + int totalValue = 0; + + for (final input in unspentCoins) { + if (input.isSending) { + inputsCount++; + totalValue += input.value; + } + if (amount != null && totalValue >= amount) { + break; + } + } + + if (amount != null && totalValue < amount) return 0; + + final _outputsCount = outputsCount ?? (amount != null ? 2 : 1); + + return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount); + } + + @override + int feeRate(TransactionPriority priority) { + if (priority is BitcoinCashTransactionPriority) { + switch (priority) { + case BitcoinCashTransactionPriority.slow: + return 1; + case BitcoinCashTransactionPriority.medium: + return 5; + case BitcoinCashTransactionPriority.fast: + return 10; + } + } + + return 0; + } +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart new file mode 100644 index 000000000..1709c4d8f --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -0,0 +1,34 @@ +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/electrum.dart'; +import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:mobx/mobx.dart'; + +part 'bitcoin_cash_wallet_addresses.g.dart'; + +class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses; + +abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store { + BitcoinCashWalletAddressesBase(WalletInfo walletInfo, + {required bitcoin.HDWallet mainHd, + required bitcoin.HDWallet sideHd, + required bitcoin.NetworkType networkType, + required ElectrumClient electrumClient, + List? initialAddresses, + int initialRegularAddressIndex = 0, + int initialChangeAddressIndex = 0}) + : super(walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: mainHd, + sideHd: sideHd, + electrumClient: electrumClient, + networkType: networkType); + + @override + String getAddress({required int index, required bitcoin.HDWallet hd}) => + generateP2PKHAddress(hd: hd, index: index, networkType: networkType); +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart new file mode 100644 index 000000000..72caa6c58 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart @@ -0,0 +1,26 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; + +class BitcoinCashNewWalletCredentials extends WalletCredentials { + BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); +} + +class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials { + BitcoinCashRestoreWalletFromSeedCredentials( + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String mnemonic; +} + +class BitcoinCashRestoreWalletFromWIFCredentials extends WalletCredentials { + BitcoinCashRestoreWalletFromWIFCredentials( + {required String name, required String password, required this.wif, WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String wif; +} diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart new file mode 100644 index 000000000..fa572b38b --- /dev/null +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart @@ -0,0 +1,107 @@ +import 'dart:io'; + +import 'package:bip39/bip39.dart'; +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:collection/collection.dart'; +import 'package:hive/hive.dart'; + +class BitcoinCashWalletService extends WalletService { + BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); + + final Box walletInfoSource; + final Box unspentCoinsInfoSource; + + @override + WalletType getType() => WalletType.bitcoinCash; + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future create( + credentials) async { + final wallet = await BitcoinCashWalletBase.create( + mnemonic: await Mnemonic.generate(), + password: credentials.password!, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.save(); + await wallet.init(); + return wallet; + } + + @override + Future openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; + final wallet = await BitcoinCashWalletBase.open( + password: password, name: name, walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.init(); + return wallet; + } + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())) + .delete(recursive: true); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWallet = await BitcoinCashWalletBase.open( + password: password, + name: currentName, + walletInfo: currentWalletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future + restoreFromKeys(credentials) { + // TODO: implement restoreFromKeys + throw UnimplementedError('restoreFromKeys() is not implemented'); + } + + @override + Future restoreFromSeed( + BitcoinCashRestoreWalletFromSeedCredentials credentials) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw BitcoinCashMnemonicIsIncorrectException(); + } + + final wallet = await BitcoinCashWalletBase.create( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource); + await wallet.save(); + await wallet.init(); + return wallet; + } +} diff --git a/cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart b/cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart new file mode 100644 index 000000000..7cce59085 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart @@ -0,0 +1,5 @@ +class BitcoinCashMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Bitcoin Cash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_bitcoin_cash/lib/src/exceptions/exceptions.dart b/cw_bitcoin_cash/lib/src/exceptions/exceptions.dart new file mode 100644 index 000000000..746e3248a --- /dev/null +++ b/cw_bitcoin_cash/lib/src/exceptions/exceptions.dart @@ -0,0 +1 @@ +export 'bitcoin_cash_mnemonic_is_incorrect_exception.dart'; \ No newline at end of file diff --git a/cw_bitcoin_cash/lib/src/mnemonic.dart b/cw_bitcoin_cash/lib/src/mnemonic.dart new file mode 100644 index 000000000..b1f1ee984 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/mnemonic.dart @@ -0,0 +1,11 @@ +import 'dart:typed_data'; + +import 'package:bip39/bip39.dart' as bip39; + +class Mnemonic { + /// Generate bip39 mnemonic + static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength); + + /// Create root seed from mnemonic + static Uint8List toSeed(String mnemonic) => bip39.mnemonicToSeed(mnemonic); +} diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart new file mode 100644 index 000000000..d5ac36ce2 --- /dev/null +++ b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart @@ -0,0 +1,62 @@ +import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart'; +import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_bitcoin/electrum.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/electrum_transaction_info.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_type.dart'; + +class PendingBitcoinCashTransaction with PendingTransaction { + PendingBitcoinCashTransaction(this._tx, this.type, + {required this.electrumClient, + required this.amount, + required this.fee}) + : _listeners = []; + + final WalletType type; + final bitbox.Transaction _tx; + final ElectrumClient electrumClient; + final int amount; + final int fee; + + @override + String get id => _tx.getId(); + + @override + String get hex => _tx.toHex(); + + @override + String get amountFormatted => bitcoinAmountToString(amount: amount); + + @override + String get feeFormatted => bitcoinAmountToString(amount: fee); + + final List _listeners; + + @override + Future commit() async { + final result = + await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex()); + + if (result.isEmpty) { + throw BitcoinCommitTransactionException(); + } + + _listeners?.forEach((listener) => listener(transactionInfo())); + } + + void addListener( + void Function(ElectrumTransactionInfo transaction) listener) => + _listeners.add(listener); + + ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, + id: id, + height: 0, + amount: amount, + direction: TransactionDirection.outgoing, + date: DateTime.now(), + isPending: true, + confirmations: 0, + fee: fee); +} diff --git a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux new file mode 120000 index 000000000..7d9244fbe --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux @@ -0,0 +1 @@ +C:/Users/borod/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.0/ \ No newline at end of file diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..e71a16d23 --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake b/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..2e1de87a7 --- /dev/null +++ b/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift b/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..e777c67df --- /dev/null +++ b/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) +} diff --git a/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new file mode 100644 index 000000000..2f46994d3 --- /dev/null +++ b/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -0,0 +1,11 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=C:\Users\borod\flutter +FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=0.0.1 +FLUTTER_BUILD_NUMBER=0.0.1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh b/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh new file mode 100644 index 000000000..2a3bcca5a --- /dev/null +++ b/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=C:\Users\borod\flutter" +export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=0.0.1" +export "FLUTTER_BUILD_NUMBER=0.0.1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml new file mode 100644 index 000000000..30ed49e80 --- /dev/null +++ b/cw_bitcoin_cash/pubspec.yaml @@ -0,0 +1,76 @@ +name: cw_bitcoin_cash +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=2.19.0 <3.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + bip39: ^1.0.6 + bip32: ^2.0.0 + path_provider: ^2.0.11 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 + cw_core: + path: ../cw_core + cw_bitcoin: + path: ../cw_bitcoin + bitcoin_flutter: + git: + url: https://github.com/cake-tech/bitcoin_flutter.git + ref: cake-update-v3 + bitbox: + git: + url: https://github.com/cake-tech/bitbox-flutter.git + ref: master + + + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + +# To add assets to your package, add an assets section, like this: +# assets: +# - images/a_dot_burr.jpeg +# - images/a_dot_ham.jpeg +# +# For details regarding assets in packages, see +# https://flutter.dev/assets-and-images/#from-packages +# +# An image asset can refer to one or more resolution-specific "variants", see +# https://flutter.dev/assets-and-images/#resolution-aware + +# To add custom fonts to your package, add a fonts section here, +# in this "flutter" section. Each entry in this list should have a +# "family" key with the font family name, and a "fonts" key with a +# list giving the asset and other descriptors for the font. For +# example: +# fonts: +# - family: Schyler +# fonts: +# - asset: fonts/Schyler-Regular.ttf +# - asset: fonts/Schyler-Italic.ttf +# style: italic +# - family: Trajan Pro +# fonts: +# - asset: fonts/TrajanPro.ttf +# - asset: fonts/TrajanPro_Bold.ttf +# weight: 700 +# + diff --git a/cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart b/cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart new file mode 100644 index 000000000..f06646a8f --- /dev/null +++ b/cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake b/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..b93c4c30c --- /dev/null +++ b/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index a11907ef2..6fd43dd82 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -80,6 +80,7 @@ class AmountConverter { case CryptoCurrency.xmr: return _moneroAmountToString(amount); case CryptoCurrency.btc: + case CryptoCurrency.bch: return _bitcoinAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 2db858b30..4c330b073 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -13,6 +13,8 @@ CryptoCurrency currencyForWalletType(WalletType type) { return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.bitcoinCash: + return CryptoCurrency.bch; case WalletType.nano: return CryptoCurrency.nano; case WalletType.banano: diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index a07030d64..dc3620ec2 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -78,6 +78,8 @@ class Node extends HiveObject with Keyable { return Uri.http(uriRaw, ''); case WalletType.ethereum: return Uri.https(uriRaw, ''); + case WalletType.bitcoinCash: + return createUriFromElectrumAddress(uriRaw); case WalletType.nano: case WalletType.banano: if (isSSL) { @@ -138,6 +140,8 @@ class Node extends HiveObject with Keyable { return requestMoneroNode(); case WalletType.ethereum: return requestElectrumServer(); + case WalletType.bitcoinCash: + return requestElectrumServer(); case WalletType.nano: case WalletType.banano: return requestNanoNode(); diff --git a/lib/entities/unspent_transaction_output.dart b/cw_core/lib/unspent_transaction_output.dart similarity index 100% rename from lib/entities/unspent_transaction_output.dart rename to cw_core/lib/unspent_transaction_output.dart diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 0125facaf..debf92e11 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -10,6 +10,7 @@ const walletTypes = [ WalletType.litecoin, WalletType.haven, WalletType.ethereum, + WalletType.bitcoinCash, WalletType.nano, WalletType.banano, ]; @@ -39,6 +40,10 @@ enum WalletType { @HiveField(7) banano, + + @HiveField(8) + bitcoinCash, + } int serializeToInt(WalletType type) { @@ -57,6 +62,8 @@ int serializeToInt(WalletType type) { return 5; case WalletType.banano: return 6; + case WalletType.bitcoinCash: + return 7; default: return -1; } @@ -78,6 +85,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.nano; case 6: return WalletType.banano; + case 7: + return WalletType.bitcoinCash; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -95,6 +104,8 @@ String walletTypeToString(WalletType type) { return 'Haven'; case WalletType.ethereum: return 'Ethereum'; + case WalletType.bitcoinCash: + return 'Bitcoin Cash'; case WalletType.nano: return 'Nano'; case WalletType.banano: @@ -116,6 +127,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Haven (XHV)'; case WalletType.ethereum: return 'Ethereum (ETH)'; + case WalletType.bitcoinCash: + return 'Bitcoin Cash (BCH)'; case WalletType.nano: return 'Nano (XNO)'; case WalletType.banano: @@ -137,6 +150,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.bitcoinCash: + return CryptoCurrency.bch; case WalletType.nano: return CryptoCurrency.nano; case WalletType.banano: diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 353458937..dd713fd15 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -44,6 +44,7 @@ class CWBitcoin extends Bitcoin { List getTransactionPriorities() => BitcoinTransactionPriority.all; + @override List getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all; @@ -121,16 +122,9 @@ class CWBitcoin extends Bitcoin { => (priority as BitcoinTransactionPriority).labelWithRate(rate); @override - List getUnspents(Object wallet) { + List getUnspents(Object wallet) { final bitcoinWallet = wallet as ElectrumWallet; - return bitcoinWallet.unspentCoins - .map((BitcoinUnspent bitcoinUnspent) => Unspent( - bitcoinUnspent.address.address, - bitcoinUnspent.hash, - bitcoinUnspent.value, - bitcoinUnspent.vout, - null)) - .toList(); + return bitcoinWallet.unspentCoins; } void updateUnspents(Object wallet) async { diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart new file mode 100644 index 000000000..7dbb8614f --- /dev/null +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -0,0 +1,45 @@ +part of 'bitcoin_cash.dart'; + +class CWBitcoinCash extends BitcoinCash { + @override + String getMnemonic(int? strength) => Mnemonic.generate(); + + @override + Uint8List getSeedFromMnemonic(String seed) => Mnemonic.toSeed(seed); + + @override + String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address); + + @override + WalletService createBitcoinCashWalletService( + Box walletInfoSource, Box unspentCoinSource) { + return BitcoinCashWalletService(walletInfoSource, unspentCoinSource); + } + + @override + WalletCredentials createBitcoinCashNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + }) => + BitcoinCashNewWalletCredentials(name: name, walletInfo: walletInfo); + + @override + WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials( + {required String name, required String mnemonic, required String password}) => + BitcoinCashRestoreWalletFromSeedCredentials( + name: name, mnemonic: mnemonic, password: password); + + @override + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) => + BitcoinCashTransactionPriority.deserialize(raw: raw); + + @override + TransactionPriority getDefaultTransactionPriority() => BitcoinCashTransactionPriority.medium; + + @override + List getTransactionPriorities() => BitcoinCashTransactionPriority.all; + + @override + TransactionPriority getBitcoinCashTransactionPrioritySlow() => + BitcoinCashTransactionPriority.slow; +} diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 91309a2ca..872fcebf5 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -27,6 +27,8 @@ class OnRamperBuyProvider { return "LTC_LITECOIN"; case CryptoCurrency.xmr: return "XMR_MONERO"; + case CryptoCurrency.bch: + return "BCH_BITCOINCASH"; case CryptoCurrency.nano: return "XNO_NANO"; default: diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index d039a40a7..fcb881943 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -88,7 +88,9 @@ class AddressValidator extends TextValidator { case CryptoCurrency.dai: case CryptoCurrency.dash: case CryptoCurrency.eos: + return '[0-9a-zA-Z]'; case CryptoCurrency.bch: + return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q[0-9a-zA-Z]{42}\$|^bitcoincash:q[0-9a-zA-Z]{41}\$|^bitcoincash:q[0-9a-zA-Z]{42}\$'; case CryptoCurrency.bnb: return '[0-9a-zA-Z]'; case CryptoCurrency.ltc: @@ -172,7 +174,9 @@ class AddressValidator extends TextValidator { case CryptoCurrency.steth: case CryptoCurrency.shib: case CryptoCurrency.avaxc: + return [42]; case CryptoCurrency.bch: + return [42, 43, 44, 54, 55]; case CryptoCurrency.bnb: return [42]; case CryptoCurrency.ltc: @@ -271,6 +275,11 @@ class AddressValidator extends TextValidator { return 'nano_[0-9a-zA-Z]{60}'; case CryptoCurrency.banano: return 'ban_[0-9a-zA-Z]{60}'; + case CryptoCurrency.bch: + return 'bitcoincash:q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)' + '|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)' + '|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)' + '|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'; default: return null; } diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 1c6e7cd20..95ccf89ac 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -29,6 +29,8 @@ class SeedValidator extends Validator { return haven!.getMoneroWordList(language); case WalletType.ethereum: return ethereum!.getEthereumWordList(language); + case WalletType.bitcoinCash: + return getBitcoinWordList(language); case WalletType.nano: case WalletType.banano: return nano!.getNanoWordList(language); diff --git a/lib/di.dart b/lib/di.dart index b871e6a6b..1576c378b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; @@ -820,6 +821,8 @@ Future setup({ return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource); case WalletType.ethereum: return ethereum!.createEthereumWalletService(_walletInfoSource); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!); case WalletType.nano: return nano!.createNanoWalletService(_walletInfoSource); default: diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 690a5e9eb..94283302d 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -26,6 +26,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; +const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; @@ -81,7 +82,10 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); await changeLitecoinCurrentElectrumServerToDefault( sharedPreferences: sharedPreferences, nodes: nodes); - await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); + await changeHavenCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + await changeBitcoinCashCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); break; case 2: @@ -166,6 +170,11 @@ Future defaultSettingsMigration( await changeNanoCurrentPowNodeToDefault( sharedPreferences: sharedPreferences, nodes: powNodes); break; + case 23: + await addBitcoinCashElectrumServerList(nodes: nodes); + await changeBitcoinCurrentElectrumServerToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + break; default: break; @@ -323,6 +332,12 @@ Node? getNanoDefaultPowNode({required Box nodes}) { nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano)); } +Node? getBitcoinCashDefaultElectrumServer({required Box nodes}) { + return nodes.values.firstWhereOrNull( + (Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri) + ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash); +} + Node getMoneroDefaultNode({required Box nodes}) { final timeZone = DateTime.now().timeZoneOffset.inHours; var nodeUri = ''; @@ -358,6 +373,15 @@ Future changeLitecoinCurrentElectrumServerToDefault( await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId); } +Future changeBitcoinCashCurrentNodeToDefault( + {required SharedPreferences sharedPreferences, + required Box nodes}) async { + final server = getBitcoinCashDefaultElectrumServer(nodes: nodes); + final serverId = server?.key as int ?? 0; + + await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, serverId); +} + Future changeHavenCurrentNodeToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final node = getHavenDefaultNode(nodes: nodes); @@ -411,6 +435,15 @@ Future addLitecoinElectrumServerList({required Box nodes}) async { } } +Future addBitcoinCashElectrumServerList({required Box nodes}) async { + final serverList = await loadBitcoinCashElectrumServerList(); + for (var node in serverList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } +} + Future addHavenNodeList({required Box nodes}) async { final nodeList = await loadDefaultHavenNodes(); for (var node in nodeList) { @@ -497,27 +530,34 @@ Future checkCurrentNodes( final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentBitcoinElectrumSeverId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); - final currentLitecoinElectrumSeverId = - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); - final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); - final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); - final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); - final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); - final currentMoneroNode = - nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId); - final currentBitcoinElectrumServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId); - final currentLitecoinElectrumServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId); - final currentHavenNodeServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId); - final currentEthereumNodeServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); + final currentLitecoinElectrumSeverId = sharedPreferences + .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final currentHavenNodeId = sharedPreferences + .getInt(PreferencesKey.currentHavenNodeIdKey); + final currentEthereumNodeId = sharedPreferences + .getInt(PreferencesKey.currentEthereumNodeIdKey); + final currentNanoNodeId = sharedPreferences + .getInt(PreferencesKey.currentNanoNodeIdKey); + final currentNanoPowNodeId = sharedPreferences + .getInt(PreferencesKey.currentNanoPowNodeIdKey); + final currentBitcoinCashNodeId = sharedPreferences + .getInt(PreferencesKey.currentBitcoinCashNodeIdKey); + final currentMoneroNode = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentMoneroNodeId); + final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentBitcoinElectrumSeverId); + final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentLitecoinElectrumSeverId); + final currentHavenNodeServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentHavenNodeId); + final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentEthereumNodeId); final currentNanoNodeServer = - nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); + nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); final currentNanoPowNodeServer = - powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId); - + powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId); + final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull( + (node) => node.key == currentBitcoinCashNodeId); if (currentMoneroNode == null) { final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); await nodeSource.add(newCakeWalletNode); @@ -565,6 +605,13 @@ Future checkCurrentNodes( } await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int); } + + if (currentBitcoinCashNodeServer == null) { + final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash); + await nodeSource.add(node); + await sharedPreferences.setInt( + PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 48beab32b..d2c67ef95 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -52,6 +52,7 @@ class MainActions { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.bitcoinCash: case WalletType.nano: case WalletType.banano: switch (defaultBuyProvider) { @@ -123,6 +124,7 @@ class MainActions { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.bitcoinCash: if (viewModel.isEnabledSellAction) { final moonPaySellProvider = MoonPaySellProvider(); final uri = await moonPaySellProvider.requestUrl( diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 0641c0846..53facf18c 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -84,6 +84,23 @@ Future> loadDefaultEthereumNodes() async { return nodes; } +Future> loadBitcoinCashElectrumServerList() async { + final serverListRaw = + await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml'); + final loadedServerList = loadYaml(serverListRaw) as YamlList; + final serverList = []; + + for (final raw in loadedServerList) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + node.type = WalletType.bitcoinCash; + serverList.add(node); + } + } + + return serverList; +} + Future> loadDefaultNanoNodes() async { final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml'); final loadedNodes = loadYaml(nodesRaw) as YamlList; @@ -116,10 +133,11 @@ Future> loadDefaultNanoPowNodes() async { return nodes; } -Future resetToDefault(Box nodeSource) async { +Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); final litecoinElectrumServerList = await loadLitecoinElectrumServerList(); + final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList(); final havenNodes = await loadDefaultHavenNodes(); final ethereumNodes = await loadDefaultEthereumNodes(); final nanoNodes = await loadDefaultNanoNodes(); @@ -129,13 +147,14 @@ Future resetToDefault(Box nodeSource) async { litecoinElectrumServerList + havenNodes + ethereumNodes + + bitcoinCashElectrumServerList + nanoNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); } -Future resetPowToDefault(Box powNodeSource) async { +Future resetPowToDefault(Box powNodeSource) async { final nanoPowNodes = await loadDefaultNanoPowNodes(); final nodes = nanoPowNodes; await powNodeSource.clear(); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index ce2db7cdf..cbd12d777 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -11,6 +11,7 @@ class PreferencesKey { static const currentBananoNodeIdKey = 'current_node_id_banano'; static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow'; static const currentFiatCurrencyKey = 'current_fiat_currency'; + static const currentBitcoinCashNodeIdKey = 'current_node_id_bch'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const shouldSaveRecipientAddressKey = 'save_recipient_address'; @@ -36,6 +37,7 @@ class PreferencesKey { static const havenTransactionPriority = 'current_fee_priority_haven'; static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; + static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 378cf0ea2..bf6f8157d 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -17,6 +18,8 @@ List priorityForWalletType(WalletType type) { return haven!.getTransactionPriorities(); case WalletType.ethereum: return ethereum!.getTransactionPriorities(); + case WalletType.bitcoinCash: + return bitcoinCash!.getTransactionPriorities(); // no such thing for nano/banano: case WalletType.nano: case WalletType.banano: diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 602db2d49..abafc2f26 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -50,6 +50,9 @@ class CWEthereum extends Ethereum { @override TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium; + @override + TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow; + @override List getTransactionPriorities() => EthereumTransactionPriority.all; diff --git a/lib/main.dart b/lib/main.dart index c50c16f61..2d76196c3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -159,7 +159,7 @@ Future initializeAppConfigs() async { transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 22); + initialMigrationVersion: 23); } Future initialSetup( diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index b22acdc8b..1aa7f6c4a 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State { this.havenIcon = Image.asset('assets/images/haven_menu.png'), this.ethereumIcon = Image.asset('assets/images/eth_icon.png'), this.nanoIcon = Image.asset('assets/images/nano_icon.png'), - this.bananoIcon = Image.asset('assets/images/nano_icon.png'); + this.bananoIcon = Image.asset('assets/images/nano_icon.png'), + this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'); final largeScreen = 731; @@ -50,10 +51,10 @@ class MenuWidgetState extends State { Image litecoinIcon; Image havenIcon; Image ethereumIcon; + Image bitcoinCashIcon; Image nanoIcon; Image bananoIcon; - @override void initState() { menuWidth = 0; @@ -212,6 +213,8 @@ class MenuWidgetState extends State { return havenIcon; case WalletType.ethereum: return ethereumIcon; + case WalletType.bitcoinCash: + return bitcoinCashIcon; case WalletType.nano: return nanoIcon; case WalletType.banano: diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index a73e7bbff..7e6bfb1de 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -73,6 +73,7 @@ class PreSeedPage extends BasePage { case WalletType.monero: return 25; case WalletType.ethereum: + case WalletType.bitcoinCash: return 12; default: return 24; diff --git a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart index 1c1fbfa5d..1a173f62a 100644 --- a/lib/src/screens/unspent_coins/unspent_coins_list_page.dart +++ b/lib/src/screens/unspent_coins/unspent_coins_list_page.dart @@ -1,8 +1,10 @@ +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -79,6 +81,9 @@ class UnspentCoinsListFormState extends State { itemBuilder: (_, int index) { return Observer(builder: (_) { final item = unspentCoinsListViewModel.items[index]; + final address = unspentCoinsListViewModel.wallet.type == WalletType.bitcoinCash + ? bitcoinCash!.getCashAddrFormat(item.address) + : item.address; return GestureDetector( onTap: () => @@ -88,7 +93,7 @@ class UnspentCoinsListFormState extends State { child: UnspentCoinsListItem( note: item.note, amount: item.amount, - address: item.address, + address: address, isSending: item.isSending, isFrozen: item.isFrozen, onCheckBoxTap: item.isFrozen diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index a0b44f375..11b394460 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -48,6 +48,7 @@ class WalletListBodyState extends State { final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); + final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24); final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; @@ -243,6 +244,8 @@ class WalletListBodyState extends State { return havenIcon; case WalletType.ethereum: return ethereumIcon; + case WalletType.bitcoinCash: + return bitcoinCashIcon; case WalletType.nano: return nanoIcon; default: diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 939d724d6..a2e2570e0 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; @@ -85,7 +86,8 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialLitecoinTransactionPriority, - TransactionPriority? initialEthereumTransactionPriority}) + TransactionPriority? initialEthereumTransactionPriority, + TransactionPriority? initialBitcoinCashTransactionPriority}) : nodes = ObservableMap.of(nodes), powNodes = ObservableMap.of(powNodes), _sharedPreferences = sharedPreferences, @@ -146,6 +148,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.ethereum] = initialEthereumTransactionPriority; } + if (initialBitcoinCashTransactionPriority != null) { + priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; + } + reaction( (_) => fiatCurrency, (FiatCurrency fiatCurrency) => sharedPreferences.setString( @@ -174,6 +180,9 @@ abstract class SettingsStoreBase with Store { case WalletType.ethereum: key = PreferencesKey.ethereumTransactionPriority; break; + case WalletType.bitcoinCash: + key = PreferencesKey.bitcoinCashTransactionPriority; + break; default: key = null; } @@ -526,12 +535,13 @@ abstract class SettingsStoreBase with Store { TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority( raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!); TransactionPriority? bitcoinTransactionPriority = - bitcoin?.deserializeBitcoinTransactionPriority( - sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!); + bitcoin?.deserializeBitcoinTransactionPriority( + sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!); TransactionPriority? havenTransactionPriority; TransactionPriority? litecoinTransactionPriority; TransactionPriority? ethereumTransactionPriority; + TransactionPriority? bitcoinCashTransactionPriority; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { havenTransactionPriority = monero?.deserializeMoneroTransactionPriority( @@ -545,12 +555,17 @@ abstract class SettingsStoreBase with Store { ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { + bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority( + sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); + } moneroTransactionPriority ??= monero?.getDefaultTransactionPriority(); bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority(); havenTransactionPriority ??= monero?.getDefaultTransactionPriority(); litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium(); ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority(); + bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); @@ -560,7 +575,8 @@ abstract class SettingsStoreBase with Store { final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false; final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false; - final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; + final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt( + PreferencesKey.defaultBuyProvider) ?? 0]; final currentFiatApiMode = FiatApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw); @@ -579,7 +595,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? false; final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? false; final shouldRequireTOTP2FAForAddingContacts = sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; @@ -587,7 +603,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? false; final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? false; final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false; final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? ''; @@ -612,7 +628,7 @@ abstract class SettingsStoreBase with Store { ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) : defaultPinCodeTimeOutDuration; final sortBalanceBy = - SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; + SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; final pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; @@ -626,9 +642,11 @@ abstract class SettingsStoreBase with Store { await LanguageService.localeDetection(); final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final litecoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final bitcoinCashElectrumServerId = + sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); @@ -638,13 +656,14 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); final nanoPowNode = powNodeSource.get(nanoPowNodeId); final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; final generateSubaddresses = - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); + sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); final autoGenerateSubaddressStatus = generateSubaddresses != null ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) @@ -672,70 +691,76 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (bitcoinCashElectrumServer != null) { + nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; + } + if (nanoNode != null) { nodes[WalletType.nano] = nanoNode; } + if (nanoPowNode != null) { powNodes[WalletType.nano] = nanoPowNode; } - final savedSyncMode = SyncMode.all.firstWhere((element) { - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); - }); - final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; + final savedSyncMode = SyncMode.all.firstWhere((element) { + return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); + }); + final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; - return SettingsStore( - sharedPreferences: sharedPreferences, - initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, - nodes: nodes, - powNodes: powNodes, - appVersion: packageInfo.version, - deviceName: deviceName, - isBitcoinBuyEnabled: isBitcoinBuyEnabled, - initialFiatCurrency: currentFiatCurrency, - initialBalanceDisplayMode: currentBalanceDisplayMode, - initialSaveRecipientAddress: shouldSaveRecipientAddress, - initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, - initialAppSecure: isAppSecure, - initialDisableBuy: disableBuy, - initialDisableSell: disableSell, - initialDefaultBuyProvider: defaultBuyProvider, - initialFiatMode: currentFiatApiMode, - initialAllowBiometricalAuthentication: allowBiometricalAuthentication, - initialCake2FAPresetOptions: selectedCake2FAPreset, - initialUseTOTP2FA: useTOTP2FA, - initialTotpSecretKey: totpSecretKey, - initialFailedTokenTrial: tokenTrialNumber, - initialExchangeStatus: exchangeStatus, - initialTheme: savedTheme, - actionlistDisplayMode: actionListDisplayMode, - initialPinLength: pinLength, - pinTimeOutDuration: pinCodeTimeOutDuration, - initialLanguageCode: savedLanguageCode, - sortBalanceBy: sortBalanceBy, - pinNativeTokenAtTop: pinNativeTokenAtTop, - useEtherscan: useEtherscan, - initialMoneroTransactionPriority: moneroTransactionPriority, - initialBitcoinTransactionPriority: bitcoinTransactionPriority, - initialHavenTransactionPriority: havenTransactionPriority, - initialLitecoinTransactionPriority: litecoinTransactionPriority, - initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, - initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, - initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, - initialShouldRequireTOTP2FAForSendsToInternalWallets: - shouldRequireTOTP2FAForSendsToInternalWallets, - initialShouldRequireTOTP2FAForExchangesToInternalWallets: - shouldRequireTOTP2FAForExchangesToInternalWallets, - initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, - initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, - initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: - shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - initialEthereumTransactionPriority: ethereumTransactionPriority, - backgroundTasks: backgroundTasks, - initialSyncMode: savedSyncMode, - initialSyncAll: savedSyncAll, - shouldShowYatPopup: shouldShowYatPopup); - } + return SettingsStore( + sharedPreferences: sharedPreferences, + initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, + nodes: nodes, + powNodes: powNodes, + appVersion: packageInfo.version, + deviceName: deviceName, + isBitcoinBuyEnabled: isBitcoinBuyEnabled, + initialFiatCurrency: currentFiatCurrency, + initialBalanceDisplayMode: currentBalanceDisplayMode, + initialSaveRecipientAddress: shouldSaveRecipientAddress, + initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, + initialAppSecure: isAppSecure, + initialDisableBuy: disableBuy, + initialDisableSell: disableSell, + initialDefaultBuyProvider: defaultBuyProvider, + initialFiatMode: currentFiatApiMode, + initialAllowBiometricalAuthentication: allowBiometricalAuthentication, + initialCake2FAPresetOptions: selectedCake2FAPreset, + initialUseTOTP2FA: useTOTP2FA, + initialTotpSecretKey: totpSecretKey, + initialFailedTokenTrial: tokenTrialNumber, + initialExchangeStatus: exchangeStatus, + initialTheme: savedTheme, + actionlistDisplayMode: actionListDisplayMode, + initialPinLength: pinLength, + pinTimeOutDuration: pinCodeTimeOutDuration, + initialLanguageCode: savedLanguageCode, + sortBalanceBy: sortBalanceBy, + pinNativeTokenAtTop: pinNativeTokenAtTop, + useEtherscan: useEtherscan, + initialMoneroTransactionPriority: moneroTransactionPriority, + initialBitcoinTransactionPriority: bitcoinTransactionPriority, + initialHavenTransactionPriority: havenTransactionPriority, + initialLitecoinTransactionPriority: litecoinTransactionPriority, + initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority, + initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, + initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, + initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, + initialShouldRequireTOTP2FAForSendsToInternalWallets: + shouldRequireTOTP2FAForSendsToInternalWallets, + initialShouldRequireTOTP2FAForExchangesToInternalWallets: + shouldRequireTOTP2FAForExchangesToInternalWallets, + initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, + initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, + initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: + shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + initialEthereumTransactionPriority: ethereumTransactionPriority, + backgroundTasks: backgroundTasks, + initialSyncMode: savedSyncMode, + initialSyncAll: savedSyncAll, + shouldShowYatPopup: shouldShowYatPopup); + } Future reload({required Box nodeSource}) async { final sharedPreferences = await getIt.getAsync(); @@ -744,30 +769,35 @@ abstract class SettingsStoreBase with Store { raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!); priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority( - raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? + raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? priority[WalletType.monero]!; priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority( - sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? + sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? priority[WalletType.bitcoin]!; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority( - raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ?? + raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ?? priority[WalletType.haven]!; } if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) { priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority( - sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ?? + sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ?? priority[WalletType.litecoin]!; } if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority( - sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? priority[WalletType.ethereum]!; } + if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { + priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority( + sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ?? + priority[WalletType.bitcoinCash]!; + } final generateSubaddresses = - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); + sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); autoGenerateSubaddressStatus = generateSubaddresses != null ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) @@ -785,7 +815,8 @@ abstract class SettingsStoreBase with Store { isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; - defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; + defaultBuyProvider = + BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; allowBiometricalAuthentication = sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; @@ -802,7 +833,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? false; shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? false; shouldRequireTOTP2FAForAddingContacts = sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; @@ -810,7 +841,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? false; shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? false; shouldShowMarketPlaceInDashboard = sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? @@ -846,9 +877,11 @@ abstract class SettingsStoreBase with Store { final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); final litecoinElectrumServerId = - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); + final bitcoinCashElectrumServerId = + sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); @@ -858,6 +891,7 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); if (moneroNode != null) { @@ -880,6 +914,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (bitcoinCashNode != null) { + nodes[WalletType.bitcoinCash] = bitcoinCashNode; + } + if (nanoNode != null) { nodes[WalletType.nano] = nanoNode; } @@ -904,6 +942,9 @@ abstract class SettingsStoreBase with Store { case WalletType.ethereum: await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int); break; + case WalletType.bitcoinCash: + await _sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); + break; case WalletType.nano: await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int); break; diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index e7e640afc..4e1c1aae0 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -72,6 +72,7 @@ class TransactionListItem extends ActionListItem with Keyable { break; case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.bitcoinCash: amount = calculateFiatAmountRaw( cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount), price: price); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 47b408bc2..7bc7927df 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -2,10 +2,12 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; @@ -265,8 +267,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } bool get hasAllAmount => - (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) && - depositCurrency == wallet.currency; + (wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash) && + depositCurrency == wallet.currency; bool get isMoneroWallet => wallet.type == WalletType.monero; @@ -278,7 +282,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.bitcoin: return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow(); case WalletType.litecoin: - return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow(); + return transactionPriority == + bitcoin!.getLitecoinTransactionPrioritySlow(); + case WalletType.ethereum: + return transactionPriority == + ethereum!.getEthereumTransactionPrioritySlow(); + case WalletType.bitcoinCash: + return transactionPriority == + bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); default: return false; } @@ -619,7 +630,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @action void calculateDepositAllAmount() { - if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { + if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) { final availableBalance = wallet.balance[wallet.currency]!.available; final priority = _settingsStore.priority[wallet.type]!; final fee = wallet.calculateEstimatedFee(priority, null); @@ -694,6 +705,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.ltc; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.bitcoinCash: + depositCurrency = CryptoCurrency.bch; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.haven: depositCurrency = CryptoCurrency.xhv; receiveCurrency = CryptoCurrency.btc; @@ -789,6 +804,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.litecoin: _settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium(); break; + case WalletType.ethereum: + _settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority(); + break; + case WalletType.bitcoinCash: + _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority(); + break; default: break; } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index fb1198c41..ae0edba30 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.ethereum: node = getEthereumDefaultNode(nodes: _nodeSource)!; break; + case WalletType.bitcoinCash: + node = getBitcoinCashDefaultElectrumServer(nodes: _nodeSource)!; + break; case WalletType.nano: node = getNanoDefaultNode(nodes: _nodeSource)!; break; diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index 39a7b682f..2bc595469 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; @@ -84,6 +85,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store case WalletType.litecoin: return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromSeedCredentials( name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index e9aed55c6..dd138b66c 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -72,8 +72,13 @@ class WalletRestoreFromQRCode { case 'litecoin': case 'litecoin-wallet': return WalletType.litecoin; + case 'bitcoincash': + case 'bitcoinCash-wallet': + return WalletType.bitcoinCash; case 'ethereum-wallet': return WalletType.ethereum; + case 'nano-wallet': + return WalletType.nano; default: throw Exception('Unexpected wallet type: ${scheme.toString()}'); } @@ -107,6 +112,7 @@ class WalletRestoreFromQRCode { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.ethereum: + case WalletType.bitcoinCash: RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b'); diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 8008812ba..2e696e16f 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -81,10 +81,8 @@ abstract class OutputBase with Store { _amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount); break; case WalletType.bitcoin: - _amount = - bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); - break; case WalletType.litecoin: + case WalletType.bitcoinCash: _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); break; @@ -116,7 +114,8 @@ abstract class OutputBase with Store { _settingsStore.priority[_wallet.type]!, formattedCryptoAmount); if (_wallet.type == WalletType.bitcoin || - _wallet.type == WalletType.litecoin) { + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.bitcoinCash) { return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); } @@ -234,6 +233,9 @@ abstract class OutputBase with Store { case WalletType.litecoin: maximumFractionDigits = 8; break; + case WalletType.bitcoinCash: + maximumFractionDigits = 8; + break; case WalletType.haven: maximumFractionDigits = 12; break; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 2ba6dc784..719298675 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -185,12 +185,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get hasCoinControl => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.monero; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.monero || + wallet.type == WalletType.bitcoinCash; @computed bool get isElectrumWallet => - wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; @computed bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; @@ -345,41 +348,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor _settingsStore.priority[wallet.type] = priority; Object _credentials() { + final priority = _settingsStore.priority[wallet.type]; + + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); + switch (wallet.type) { case WalletType.bitcoin: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - - return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); case WalletType.litecoin: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - + case WalletType.bitcoinCash: return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); + case WalletType.monero: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - return monero! .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority); + case WalletType.haven: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - return haven!.createHavenTransactionCreationCredentials( outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title); + case WalletType.ethereum: final priority = _settingsStore.priority[wallet.type]; @@ -390,9 +376,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return ethereum!.createEthereumTransactionCredentials(outputs, priority: priority, currency: selectedCryptoCurrency); case WalletType.nano: - return nano!.createNanoTransactionCredentials( - outputs, - ); + return nano!.createNanoTransactionCredentials(outputs); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 587e8723b..b4ca46f70 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -63,7 +63,9 @@ abstract class OtherSettingsViewModelBase with Store { String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; - if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin) { + if (_wallet.type == WalletType.bitcoin || + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.bitcoinCash) { final rate = bitcoin!.getFeeRate(_wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index fd001125f..a8c892284 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -39,6 +39,7 @@ abstract class TransactionDetailsViewModelBase with Store { break; case WalletType.bitcoin: case WalletType.litecoin: + case WalletType.bitcoinCash: _addElectrumListItems(tx, dateFormat); break; case WalletType.haven: @@ -115,6 +116,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://mempool.space/tx/${txId}'; case WalletType.litecoin: return 'https://blockchair.com/litecoin/transaction/${txId}'; + case WalletType.bitcoinCash: + return 'https://blockchair.com/bitcoin-cash/transaction/${txId}'; case WalletType.haven: return 'https://explorer.havenprotocol.org/search?value=${txId}'; case WalletType.ethereum: @@ -135,6 +138,7 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.bitcoin: return S.current.view_transaction_on + 'mempool.space'; case WalletType.litecoin: + case WalletType.bitcoinCash: return S.current.view_transaction_on + 'Blockchair.com'; case WalletType.haven: return S.current.view_transaction_on + 'explorer.havenprotocol.org'; diff --git a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart index 992991147..4da43c241 100644 --- a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart @@ -1,9 +1,10 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; import 'package:cw_core/wallet_type.dart'; @@ -19,12 +20,14 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { UnspentCoinsDetailsViewModelBase( {required this.unspentCoinsItem, required this.unspentCoinsListViewModel}) : items = [], + _type = unspentCoinsListViewModel.wallet.type, isFrozen = unspentCoinsItem.isFrozen, note = unspentCoinsItem.note { items = [ StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount), - StandartListItem(title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash), - StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address), + StandartListItem( + title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash), + StandartListItem(title: S.current.widgets_address, value: formattedAddress), TextFieldListItem( title: S.current.note_tap_to_change, value: note, @@ -46,14 +49,13 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { }) ]; - if ([WalletType.bitcoin, WalletType.litecoin].contains(unspentCoinsListViewModel.wallet.type)) { + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(_type)) { items.add(BlockExplorerListItem( title: S.current.view_in_block_explorer, - value: _explorerDescription(unspentCoinsListViewModel.wallet.type), + value: _explorerDescription(_type), onTap: () { try { - final url = Uri.parse( - _explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash)); + final url = Uri.parse(_explorerUrl(_type, unspentCoinsItem.hash)); return launchUrl(url); } catch (e) {} }, @@ -67,6 +69,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { return 'https://ordinals.com/tx/${txId}'; case WalletType.litecoin: return 'https://litecoin.earlyordies.com/tx/${txId}'; + case WalletType.bitcoinCash: + return 'https://blockchair.com/bitcoin-cash/transaction/${txId}'; default: return ''; } @@ -78,6 +82,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { return S.current.view_transaction_on + 'Ordinals.com'; case WalletType.litecoin: return S.current.view_transaction_on + 'Earlyordies.com'; + case WalletType.bitcoinCash: + return S.current.view_transaction_on + 'Blockchair.com'; default: return ''; } @@ -91,5 +97,10 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { final UnspentCoinsItem unspentCoinsItem; final UnspentCoinsListViewModel unspentCoinsListViewModel; + final WalletType _type; List items; + + String get formattedAddress => WalletType.bitcoinCash == _type + ? bitcoinCash!.getCashAddrFormat(unspentCoinsItem.address) + : unspentCoinsItem.address; } diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index 657a0cb74..709c50562 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -1,9 +1,10 @@ import 'package:collection/collection.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/entities/unspent_transaction_output.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; @@ -24,11 +25,11 @@ abstract class UnspentCoinsListViewModelBase with Store { final Box _unspentCoinsInfo; @computed - ObservableList get items => - ObservableList.of(_getUnspents().map((elem) { + ObservableList get items => ObservableList.of(_getUnspents().map((elem) { final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}'; - final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); + final info = + getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); return UnspentCoinsItem( address: elem.address, @@ -39,13 +40,13 @@ abstract class UnspentCoinsListViewModelBase with Store { isSending: info?.isSending ?? true, amountRaw: elem.value, vout: elem.vout, - keyImage: elem.keyImage - ); + keyImage: elem.keyImage); })); Future saveUnspentCoinInfo(UnspentCoinsItem item) async { try { - final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage); + final info = + getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage); if (info == null) { final newInfo = UnspentCoinsInfo( walletId: wallet.id, @@ -56,8 +57,7 @@ abstract class UnspentCoinsListViewModelBase with Store { isFrozen: item.isFrozen, isSending: item.isSending, noteRaw: item.note, - keyImage: item.keyImage - ); + keyImage: item.keyImage); await _unspentCoinsInfo.add(newInfo); _updateUnspents(); @@ -76,37 +76,34 @@ abstract class UnspentCoinsListViewModelBase with Store { } } - UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout, String? keyImage) { + UnspentCoinsInfo? getUnspentCoinInfo( + String hash, String address, int value, int vout, String? keyImage) { return _unspentCoinsInfo.values.firstWhereOrNull((element) => element.walletId == wallet.id && element.hash == hash && element.address == address && element.value == value && element.vout == vout && - element.keyImage == keyImage - ); + element.keyImage == keyImage); } String formatAmountToString(int fullBalance) { if (wallet.type == WalletType.monero) return monero!.formatterMoneroAmountToString(amount: fullBalance); - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance); return ''; } - void _updateUnspents() { - if (wallet.type == WalletType.monero) - return monero!.updateUnspents(wallet); - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) + if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet); + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) return bitcoin!.updateUnspents(wallet); } List _getUnspents() { - if (wallet.type == WalletType.monero) - return monero!.getUnspents(wallet); - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) + if (wallet.type == WalletType.monero) return monero!.getUnspents(wallet); + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) return bitcoin!.getUnspents(wallet); return List.empty(); } diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index a4eb3d386..9e2aa7187 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -66,7 +66,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { final wallet = _wallet; if (wallet.type == WalletType.bitcoin - || wallet.type == WalletType.litecoin) { + || wallet.type == WalletType.litecoin + || wallet.type == WalletType.bitcoinCash) { await bitcoin!.generateNewAddress(wallet); await wallet.save(); } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index a24e1635f..4d5eefdb7 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -107,6 +107,23 @@ class EthereumURI extends PaymentURI { } } +class BitcoinCashURI extends PaymentURI { + BitcoinCashURI({required String amount, required String address}) + : super(amount: amount, address: address); + @override + String toString() { + var base = address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } + } + + + class NanoURI extends PaymentURI { NanoURI({required String amount, required String address}) : super(amount: amount, address: address); @@ -114,7 +131,6 @@ class NanoURI extends PaymentURI { @override String toString() { var base = 'nano:' + address; - if (amount.isNotEmpty) { base += '?amount=${amount.replaceAll(',', '.')}'; } @@ -192,6 +208,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return EthereumURI(amount: amount, address: address.address); } + if (wallet.type == WalletType.bitcoinCash) { + return BitcoinCashURI(amount: amount, address: address.address); + } + if (wallet.type == WalletType.nano) { return NanoURI(amount: amount, address: address.address); } @@ -280,7 +300,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo @computed bool get showElectrumAddressDisclaimer => - wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; + wallet.type == WalletType.bitcoin || + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; List _baseItems; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index e2938c74e..e106126bc 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -19,6 +19,7 @@ abstract class WalletKeysViewModelBase with Store { WalletKeysViewModelBase(this._appStore) : title = _appStore.wallet!.type == WalletType.bitcoin || _appStore.wallet!.type == WalletType.litecoin || + _appStore.wallet!.type == WalletType.bitcoinCash || _appStore.wallet!.type == WalletType.ethereum ? S.current.wallet_seed : S.current.wallet_keys, @@ -91,7 +92,8 @@ abstract class WalletKeysViewModelBase with Store { } if (_appStore.wallet!.type == WalletType.bitcoin || - _appStore.wallet!.type == WalletType.litecoin) { + _appStore.wallet!.type == WalletType.litecoin || + _appStore.wallet!.type == WalletType.bitcoinCash) { items.addAll([ StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); @@ -145,6 +147,8 @@ abstract class WalletKeysViewModelBase with Store { return 'haven-wallet'; case WalletType.ethereum: return 'ethereum-wallet'; + case WalletType.bitcoinCash: + return 'bitcoinCash-wallet'; case WalletType.nano: return 'nano-wallet'; case WalletType.banano: diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 04da7190e..9b1f0834d 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -46,10 +47,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, language: options as String); case WalletType.ethereum: return ethereum!.createEthereumNewWalletCredentials(name: name); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name); case WalletType.nano: return nano!.createNanoNewWalletCredentials(name: name); default: - throw Exception('Unexpected type: ${type.toString()}');; + throw Exception('Unexpected type: ${type.toString()}'); } } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 4eb69e48f..058948c2f 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -92,14 +93,20 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, height: height, mnemonic: seed, password: password); case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromSeedCredentials( - name: name, mnemonic: seed, password: password); + name: name, + mnemonic: seed, + password: password); + case WalletType.bitcoinCash: + return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password); case WalletType.nano: return nano!.createNanoRestoreWalletFromSeedCredentials( name: name, mnemonic: seed, password: password, - derivationType: derivationType, - ); + derivationType: derivationType); default: break; } @@ -145,8 +152,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, password: password, seedKey: options['private_key'] as String, - derivationType: options["derivationType"] as DerivationType, - ); + derivationType: options["derivationType"] as DerivationType); default: break; } @@ -167,8 +173,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { return nanoUtil!.compareDerivationMethods( mnemonic: mnemonic, privateKey: seedKey, - node: node, - ); + node: node); default: break; } diff --git a/model_generator.sh b/model_generator.sh old mode 100755 new mode 100644 index 0e4345c25..50cb3d353 --- a/model_generator.sh +++ b/model_generator.sh @@ -4,4 +4,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 1e5a2df26..6f8d51615 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -129,6 +129,7 @@ flutter: - assets/bitcoin_electrum_server_list.yml - assets/litecoin_electrum_server_list.yml - assets/ethereum_server_list.yml + - assets/bitcoin_cash_electrum_server_list.yml - assets/nano_node_list.yml - assets/nano_pow_node_list.yml - assets/text/ diff --git a/scripts/android/app_config.sh b/scripts/android/app_config.sh index 01edb14a4..e2cbd72da 100755 --- a/scripts/android/app_config.sh +++ b/scripts/android/app_config.sh @@ -9,4 +9,4 @@ fi ./app_icon.sh ./pubspec_gen.sh ./manifest.sh -./inject_app_details.sh \ No newline at end of file +./inject_app_details.sh diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index c74108bf1..dd9852072 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" ;; $HAVEN) CONFIG_ARGS="--haven" diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh old mode 100755 new mode 100644 index e62b06548..8d999f594 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -28,9 +28,11 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" ;; $HAVEN) + + CONFIG_ARGS="--haven" ;; esac diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 2af101485..48b680330 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -23,7 +23,7 @@ CONFIG_ARGS="" case $APP_MACOS_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --nano";; #--haven + CONFIG_ARGS="--monero --bitcoin --ethereum --nano --bitcoinCash";; #--haven esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index f7b9dc126..534aeef57 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -4,6 +4,7 @@ const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart'; const moneroOutputPath = 'lib/monero/monero.dart'; const havenOutputPath = 'lib/haven/haven.dart'; const ethereumOutputPath = 'lib/ethereum/ethereum.dart'; +const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart'; const nanoOutputPath = 'lib/nano/nano.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; @@ -15,6 +16,7 @@ Future main(List args) async { final hasMonero = args.contains('${prefix}monero'); final hasHaven = args.contains('${prefix}haven'); final hasEthereum = args.contains('${prefix}ethereum'); + final hasBitcoinCash = args.contains('${prefix}bitcoinCash'); final hasNano = args.contains('${prefix}nano'); final hasBanano = args.contains('${prefix}banano'); @@ -22,6 +24,7 @@ Future main(List args) async { await generateMonero(hasMonero); await generateHaven(hasHaven); await generateEthereum(hasEthereum); + await generateBitcoinCash(hasBitcoinCash); await generateNano(hasNano); // await generateBanano(hasEthereum); @@ -32,6 +35,7 @@ Future main(List args) async { hasEthereum: hasEthereum, hasNano: hasNano, hasBanano: hasBanano, + hasBitcoinCash: hasBitcoinCash, ); await generateWalletTypes( hasMonero: hasMonero, @@ -40,13 +44,13 @@ Future main(List args) async { hasEthereum: hasEthereum, hasNano: hasNano, hasBanano: hasBanano, + hasBitcoinCash: hasBitcoinCash, ); } Future generateBitcoin(bool hasImplementation) async { final outputFile = File(bitcoinOutputPath); const bitcoinCommonHeaders = """ -import 'package:cake_wallet/entities/unspent_transaction_output.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -60,7 +64,6 @@ import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; -import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet_service.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; @@ -80,8 +83,8 @@ abstract class Bitcoin { Map getWalletKeys(Object wallet); List getTransactionPriorities(); List getLitecoinTransactionPriorities(); - TransactionPriority deserializeBitcoinTransactionPriority(int raw); - TransactionPriority deserializeLitecoinTransactionPriority(int raw); + TransactionPriority deserializeBitcoinTransactionPriority(int raw); + TransactionPriority deserializeLitecoinTransactionPriority(int raw); int getFeeRate(Object wallet, TransactionPriority priority); Future generateNewAddress(Object wallet); Object createBitcoinTransactionCredentials(List outputs, {required TransactionPriority priority, int? feeRate}); @@ -95,7 +98,7 @@ abstract class Bitcoin { int formatterStringDoubleToBitcoinAmount(String amount); String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate); - List getUnspents(Object wallet); + List getUnspents(Object wallet); void updateUnspents(Object wallet); WalletService createBitcoinWalletService(Box walletInfoSource, Box unspentCoinSource); WalletService createLitecoinWalletService(Box walletInfoSource, Box unspentCoinSource); @@ -126,7 +129,7 @@ abstract class Bitcoin { Future generateMonero(bool hasImplementation) async { final outputFile = File(moneroOutputPath); const moneroCommonHeaders = """ -import 'package:cake_wallet/entities/unspent_transaction_output.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_monero/monero_unspent.dart'; import 'package:mobx/mobx.dart'; @@ -521,6 +524,7 @@ abstract class Ethereum { String getPrivateKey(WalletBase wallet); String getPublicKey(WalletBase wallet); TransactionPriority getDefaultTransactionPriority(); + TransactionPriority getEthereumTransactionPrioritySlow(); List getTransactionPriorities(); TransactionPriority deserializeEthereumTransactionPriority(int raw); @@ -568,6 +572,67 @@ abstract class Ethereum { await outputFile.writeAsString(output); } +Future generateBitcoinCash(bool hasImplementation) async { + final outputFile = File(bitcoinCashOutputPath); + const bitcoinCashCommonHeaders = """ +import 'dart:typed_data'; + +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart'; +"""; + const bitcoinCashCWHeaders = """ +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart'; +"""; + const bitcoinCashCwPart = "part 'cw_bitcoin_cash.dart';"; + const bitcoinCashContent = """ +abstract class BitcoinCash { + String getMnemonic(int? strength); + + Uint8List getSeedFromMnemonic(String seed); + + String getCashAddrFormat(String address); + + WalletService createBitcoinCashWalletService( + Box walletInfoSource, Box unspentCoinSource); + + WalletCredentials createBitcoinCashNewWalletCredentials( + {required String name, WalletInfo? walletInfo}); + + WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials( + {required String name, required String mnemonic, required String password}); + + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw); + + TransactionPriority getDefaultTransactionPriority(); + + List getTransactionPriorities(); + + TransactionPriority getBitcoinCashTransactionPrioritySlow(); +} + """; + + const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n'; + const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n'; + + final output = '$bitcoinCashCommonHeaders\n' + + (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') + + (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') + + (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) + + '\n' + + bitcoinCashContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generateNano(bool hasImplementation) async { final outputFile = File(nanoOutputPath); const nanoCommonHeaders = """ @@ -710,14 +775,14 @@ abstract class NanoUtil { await outputFile.writeAsString(output); } -Future generatePubspec({ - required bool hasMonero, - required bool hasBitcoin, - required bool hasHaven, - required bool hasEthereum, - required bool hasNano, - required bool hasBanano, -}) async { +Future generatePubspec( + {required bool hasMonero, + required bool hasBitcoin, + required bool hasHaven, + required bool hasEthereum, + required bool hasNano, + required bool hasBanano, + required bool hasBitcoinCash}) async { const cwCore = """ cw_core: path: ./cw_core @@ -742,6 +807,10 @@ Future generatePubspec({ cw_ethereum: path: ./cw_ethereum """; + const cwBitcoinCash = """ + cw_bitcoin_cash: + path: ./cw_bitcoin_cash + """; const cwNano = """ cw_nano: path: ./cw_nano @@ -776,6 +845,10 @@ Future generatePubspec({ output += '\n$cwBanano'; } + if (hasBitcoinCash) { + output += '\n$cwBitcoinCash'; + } + if (hasHaven && !hasMonero) { output += '\n$cwSharedExternal\n$cwHaven'; } else if (hasHaven) { @@ -794,14 +867,14 @@ Future generatePubspec({ await outputFile.writeAsString(outputContent); } -Future generateWalletTypes({ - required bool hasMonero, - required bool hasBitcoin, - required bool hasHaven, - required bool hasEthereum, - required bool hasNano, - required bool hasBanano, -}) async { +Future generateWalletTypes( + {required bool hasMonero, + required bool hasBitcoin, + required bool hasHaven, + required bool hasEthereum, + required bool hasNano, + required bool hasBanano, + required bool hasBitcoinCash}) async { final walletTypesFile = File(walletTypesPath); if (walletTypesFile.existsSync()) { @@ -828,6 +901,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.litecoin,\n'; } + if (hasBitcoinCash) { + outputContent += '\tWalletType.bitcoinCash,\n'; + } + if (hasNano) { outputContent += '\tWalletType.nano,\n'; } From 426ac99e34334e25c29c25325b74fa21ef1a4c2e Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Fri, 13 Oct 2023 14:49:00 +0300 Subject: [PATCH 5/6] Generic Fixes (#1122) * Fix Hive issue * Disable RobinHood for Nano * Validate context is still mounted [skip ci] * Disable Exolix for new exchanges Remove duplicate ethereum case * add nano/banano to manifest/info.plist * fix qr code issues for nano * Add Nano-wallet to restore form qr Add iOS keychain accessibility config * support app links for ethereum and nano [skip ci] * catch exceptions from gas price and estimated gas * Add bitcoin cash to app links Fix restore from QR for bitcoin cash * Fixate bottom buttons for create/restore wallet in wallet list page --------- Co-authored-by: fosse --- android/app/src/main/AndroidManifestBase.xml | 9 + cw_ethereum/lib/ethereum_client.dart | 16 +- ios/Runner/InfoBase.plist | 60 ++++ lib/entities/main_actions.dart | 4 +- lib/main.dart | 11 +- .../screens/dashboard/edit_token_page.dart | 4 +- .../exchange_trade/exchange_trade_page.dart | 8 +- .../screens/wallet_list/wallet_list_page.dart | 322 +++++++++--------- lib/utils/exception_handler.dart | 2 +- lib/utils/feature_flag.dart | 1 + lib/utils/payment_request.dart | 10 +- .../dashboard/transaction_list_item.dart | 7 - .../exchange/exchange_view_model.dart | 3 +- .../restore/wallet_restore_from_qr_code.dart | 4 +- lib/view_model/wallet_keys_view_model.dart | 2 +- 15 files changed, 275 insertions(+), 188 deletions(-) diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index f22ba9c4f..77a555db8 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -52,6 +52,15 @@ + + + + + + + + + getGasUnitPrice() async { - final gasPrice = await _client!.getGasPrice(); - return gasPrice.getInWei.toInt(); + try { + final gasPrice = await _client!.getGasPrice(); + return gasPrice.getInWei.toInt(); + } catch (_) { + return 0; + } } Future getEstimatedGas() async { - final estimatedGas = await _client!.estimateGas(); - return estimatedGas.toInt(); + try { + final estimatedGas = await _client!.estimateGas(); + return estimatedGas.toInt(); + } catch (_) { + return 0; + } } Future signTransaction({ diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index e4d07c717..6cea7a730 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -100,6 +100,66 @@ litecoin-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + ethereum + CFBundleURLSchemes + + ethereum + + + + CFBundleTypeRole + Viewer + CFBundleURLName + ethereum-wallet + CFBundleURLSchemes + + ethereum-wallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + nano + CFBundleURLSchemes + + nano + + + + CFBundleTypeRole + Viewer + CFBundleURLName + nano-wallet + CFBundleURLSchemes + + nano-wallet + + + + CFBundleTypeRole + Editor + CFBundleURLName + bitcoincash + CFBundleURLSchemes + + bitcoincash + + + + CFBundleTypeRole + Viewer + CFBundleURLName + bitcoincash-wallet + CFBundleURLSchemes + + bitcoincash-wallet + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index d2c67ef95..46865cbcc 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -53,8 +53,6 @@ class MainActions { case WalletType.litecoin: case WalletType.ethereum: case WalletType.bitcoinCash: - case WalletType.nano: - case WalletType.banano: switch (defaultBuyProvider) { case BuyProviderType.AskEachTime: Navigator.pushNamed(context, Routes.buy); @@ -67,6 +65,8 @@ class MainActions { break; } break; + case WalletType.nano: + case WalletType.banano: case WalletType.monero: await getIt.get().launchProvider(context); break; diff --git a/lib/main.dart b/lib/main.dart index 2d76196c3..98ba1e195 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -97,10 +97,10 @@ Future initializeAppConfigs() async { CakeHive.registerAdapter(WalletInfoAdapter()); } - if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) { + if (!CakeHive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) { CakeHive.registerAdapter(DerivationTypeAdapter()); } - + if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) { CakeHive.registerAdapter(WalletTypeAdapter()); } @@ -125,14 +125,17 @@ Future initializeAppConfigs() async { CakeHive.registerAdapter(AnonpayInvoiceInfoAdapter()); } - final secureStorage = FlutterSecureStorage(); + final secureStorage = FlutterSecureStorage( + iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), + ); final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey); final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey); final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey); final contacts = await CakeHive.openBox(Contact.boxName); final nodes = await CakeHive.openBox(Node.boxName); - final powNodes = await CakeHive.openBox(Node.boxName + "pow");// must be different from Node.boxName + final powNodes = + await CakeHive.openBox(Node.boxName + "pow"); // must be different from Node.boxName final transactionDescriptions = await CakeHive.openBox( TransactionDescription.boxName, encryptionKey: transactionDescriptionsBoxKey); diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index 50bcb24e1..df6d3bd09 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -194,7 +194,9 @@ class _EditTokenPageBodyState extends State { contractAddress: _contractAddressController.text, decimal: int.parse(_tokenDecimalController.text), )); - Navigator.pop(context); + if (context.mounted) { + Navigator.pop(context); + } } }, text: S.of(context).save, diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index dbf6676a1..22606c21e 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -5,7 +5,6 @@ import 'package:cake_wallet/utils/request_review_handler.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/execution_state.dart'; @@ -26,16 +25,15 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; void showInformation( ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { - final fetchingLabel = S.current.fetching; final trade = exchangeTradeViewModel.trade; final walletName = exchangeTradeViewModel.wallet.name; final information = exchangeTradeViewModel.isSendable ? S.current.exchange_result_confirm( - trade.amount ?? fetchingLabel, trade.from.toString(), walletName) + + trade.amount, trade.from.toString(), walletName) + exchangeTradeViewModel.extraInfo : S.current.exchange_result_description( - trade.amount ?? fetchingLabel, trade.from.toString()) + + trade.amount, trade.from.toString()) + exchangeTradeViewModel.extraInfo; showPopUp( @@ -177,7 +175,7 @@ class ExchangeTradeState extends State { ), itemBuilder: (context, index) { final item = widget.exchangeTradeViewModel.items[index]; - final value = item.data ?? fetchingLabel; + final value = item.data; final content = ListRow( title: item.title, diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 11b394460..cce4a2581 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -13,7 +13,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; @@ -65,169 +64,178 @@ class WalletListBodyState extends State { return Container( padding: EdgeInsets.only(top: 16), - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(bottom: 20), - content: Container( - child: Observer( - builder: (_) => ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - separatorBuilder: (_, index) => - Divider(color: Theme.of(context).colorScheme.background, height: 32), - itemCount: widget.walletListViewModel.wallets.length, - itemBuilder: (__, index) { - final wallet = widget.walletListViewModel.wallets[index]; - final currentColor = wallet.isCurrent - ? Theme.of(context) - .extension()! - .createNewWalletButtonBackgroundColor - : Theme.of(context).colorScheme.background; - final row = GestureDetector( - onTap: () => wallet.isCurrent ? null : _loadWallet(wallet), - child: Container( - height: tileHeight, - width: double.infinity, - child: Row( - children: [ - Container( - height: tileHeight, - width: 4, - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(4), bottomRight: Radius.circular(4)), - color: currentColor), - ), - Expanded( - child: Container( - height: tileHeight, - padding: EdgeInsets.only(left: 20, right: 20), - color: Theme.of(context).colorScheme.background, - alignment: Alignment.centerLeft, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon, - SizedBox(width: 10), - Flexible( - child: Text( - wallet.name, - maxLines: null, - softWrap: true, - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor, - ), - ), - ), - ], + child: Column( + children: [ + Expanded( + child: Container( + child: Observer( + builder: (_) => ListView.separated( + physics: const BouncingScrollPhysics(), + separatorBuilder: (_, index) => + Divider(color: Theme.of(context).colorScheme.background, height: 32), + itemCount: widget.walletListViewModel.wallets.length, + itemBuilder: (__, index) { + final wallet = widget.walletListViewModel.wallets[index]; + final currentColor = wallet.isCurrent + ? Theme.of(context) + .extension()! + .createNewWalletButtonBackgroundColor + : Theme.of(context).colorScheme.background; + final row = GestureDetector( + onTap: () => wallet.isCurrent ? null : _loadWallet(wallet), + child: Container( + height: tileHeight, + width: double.infinity, + child: Row( + children: [ + Container( + height: tileHeight, + width: 4, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4)), + color: currentColor), ), - ), - ), - ], - ), - ), - ); - - return wallet.isCurrent - ? row - : Row( - children: [ - Expanded(child: row), - GestureDetector( - onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit, - arguments: [widget.walletListViewModel, wallet]), - child: Container( - padding: EdgeInsets.only(right: 20), - child: Center( - child: Container( - height: 40, - width: 44, - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context) - .extension()! - .iconsBackgroundColor, - ), - child: Icon( - Icons.edit, - size: 14, - color: - Theme.of(context).extension()!.iconsColor, - ), + Expanded( + child: Container( + height: tileHeight, + padding: EdgeInsets.only(left: 20, right: 20), + color: Theme.of(context).colorScheme.background, + alignment: Alignment.centerLeft, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + wallet.isEnabled + ? _imageFor(type: wallet.type) + : nonWalletTypeIcon, + SizedBox(width: 10), + Flexible( + child: Text( + wallet.name, + maxLines: null, + softWrap: true, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension()! + .titleColor, + ), + ), + ), + ], ), ), ), - ), - ], - ); - }, + ], + ), + ), + ); + + return wallet.isCurrent + ? row + : Row( + children: [ + Expanded(child: row), + GestureDetector( + onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit, + arguments: [widget.walletListViewModel, wallet]), + child: Container( + padding: EdgeInsets.only(right: 20), + child: Center( + child: Container( + height: 40, + width: 44, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context) + .extension()! + .iconsBackgroundColor, + ), + child: Icon( + Icons.edit, + size: 14, + color: Theme.of(context) + .extension()! + .iconsColor, + ), + ), + ), + ), + ), + ], + ); + }, + ), + ), ), ), - ), - bottomSectionPadding: EdgeInsets.only(bottom: 24, right: 24, left: 24), - bottomSection: Column( - children: [ - PrimaryImageButton( - onPressed: () { - //TODO(David): Find a way to optimize this - if (isSingleCoin) { - if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { - widget.authService.authenticateAction( - context, - route: Routes.newWallet, - arguments: widget.walletListViewModel.currentWalletType, - conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, - ); - } else { - Navigator.of(context).pushNamed( - Routes.newWallet, - arguments: widget.walletListViewModel.currentWalletType, - ); - } - } else { - if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { - widget.authService.authenticateAction( - context, - route: Routes.newWalletType, - conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, - ); - } else { - Navigator.of(context).pushNamed(Routes.newWalletType); - } - } - }, - image: newWalletImage, - text: S.of(context).wallet_list_create_new_wallet, - color: Theme.of(context).primaryColor, - textColor: Colors.white, + Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + PrimaryImageButton( + onPressed: () { + //TODO(David): Find a way to optimize this + if (isSingleCoin) { + if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { + widget.authService.authenticateAction( + context, + route: Routes.newWallet, + arguments: widget.walletListViewModel.currentWalletType, + conditionToDetermineIfToUse2FA: + widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, + ); + } else { + Navigator.of(context).pushNamed( + Routes.newWallet, + arguments: widget.walletListViewModel.currentWalletType, + ); + } + } else { + if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { + widget.authService.authenticateAction( + context, + route: Routes.newWalletType, + conditionToDetermineIfToUse2FA: + widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, + ); + } else { + Navigator.of(context).pushNamed(Routes.newWalletType); + } + } + }, + image: newWalletImage, + text: S.of(context).wallet_list_create_new_wallet, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + ), + SizedBox(height: 10.0), + PrimaryImageButton( + onPressed: () { + if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { + widget.authService.authenticateAction( + context, + route: Routes.restoreOptions, + arguments: false, + conditionToDetermineIfToUse2FA: + widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, + ); + } else { + Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); + } + }, + image: restoreWalletImage, + text: S.of(context).wallet_list_restore_wallet, + color: Theme.of(context).cardColor, + textColor: Theme.of(context).extension()!.buttonTextColor, + ) + ], ), - SizedBox(height: 10.0), - PrimaryImageButton( - onPressed: () { - if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { - widget.authService.authenticateAction( - context, - route: Routes.restoreOptions, - arguments: false, - conditionToDetermineIfToUse2FA: - widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, - ); - } else { - Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false); - } - }, - image: restoreWalletImage, - text: S.of(context).wallet_list_restore_wallet, - color: Theme.of(context).cardColor, - textColor: Theme.of(context).extension()!.buttonTextColor, - ) - ], - ), + ), + ], ), ); } diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index b9c659872..bea43a6c6 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -161,7 +161,7 @@ class ExceptionHandler { "Handshake error in client", "Error while launching http", "OS Error: Network is unreachable", - "ClientException: Write failed, uri=https:", + "ClientException: Write failed, uri=http", ]; static Future _addDeviceInfo(File file) async { diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 2b0d5a2b9..628023f85 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -1,3 +1,4 @@ class FeatureFlag { static const bool isCakePayEnabled = false; + static const bool isExolixEnabled = false; } \ No newline at end of file diff --git a/lib/utils/payment_request.dart b/lib/utils/payment_request.dart index 6f82c3b94..00093b413 100644 --- a/lib/utils/payment_request.dart +++ b/lib/utils/payment_request.dart @@ -17,10 +17,12 @@ class PaymentRequest { } if (nano != null) { - if (address.contains("nano")) { - amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano); - } else if (address.contains("ban")) { - amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano); + if (amount.isNotEmpty) { + if (address.contains("nano")) { + amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano); + } else if (address.contains("ban")) { + amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano); + } } } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 4e1c1aae0..fd7971001 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -97,13 +97,6 @@ class TransactionListItem extends ActionListItem with Keyable { nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), price: price); break; - case WalletType.ethereum: - final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction); - final price = balanceViewModel.fiatConvertationStore.prices[asset]; - amount = calculateFiatAmountRaw( - cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction), - price: price); - break; default: break; } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 7bc7927df..f7db98245 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -16,6 +16,7 @@ import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dar import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_request.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -154,7 +155,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with SideShiftExchangeProvider(), SimpleSwapExchangeProvider(), TrocadorExchangeProvider(useTorOnly: _useTorOnly), - ExolixExchangeProvider(), + if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), ]; @observable diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index dd138b66c..bc100f9fe 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -73,10 +73,12 @@ class WalletRestoreFromQRCode { case 'litecoin-wallet': return WalletType.litecoin; case 'bitcoincash': - case 'bitcoinCash-wallet': + case 'bitcoincash-wallet': return WalletType.bitcoinCash; + case 'ethereum': case 'ethereum-wallet': return WalletType.ethereum; + case 'nano': case 'nano-wallet': return WalletType.nano; default: diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index e106126bc..81f521d27 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -148,7 +148,7 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.ethereum: return 'ethereum-wallet'; case WalletType.bitcoinCash: - return 'bitcoinCash-wallet'; + return 'bitcoincash-wallet'; case WalletType.nano: return 'nano-wallet'; case WalletType.banano: From 25149823251024eb8f21f87312d339639dfc2da2 Mon Sep 17 00:00:00 2001 From: fosse Date: Mon, 16 Oct 2023 09:40:02 -0400 Subject: [PATCH 6/6] fix nano sending, update restore page wording, and other minor fixes --- .../lib/nano_wallet_creation_credentials.dart | 13 ++++++----- lib/entities/parse_address_from_domain.dart | 2 +- lib/nano/cw_nano.dart | 18 ++++++++++++--- .../wallet_restore_from_keys_form.dart | 9 +++++++- lib/src/widgets/seed_widget.dart | 3 ++- lib/view_model/send/send_view_model.dart | 23 ++++++++----------- res/values/strings_ar.arb | 6 +++-- res/values/strings_bg.arb | 6 +++-- res/values/strings_cs.arb | 6 +++-- res/values/strings_de.arb | 6 +++-- res/values/strings_en.arb | 6 +++-- res/values/strings_es.arb | 6 +++-- res/values/strings_fr.arb | 6 +++-- res/values/strings_ha.arb | 6 +++-- res/values/strings_hi.arb | 6 +++-- res/values/strings_hr.arb | 6 +++-- res/values/strings_id.arb | 6 +++-- res/values/strings_it.arb | 6 +++-- res/values/strings_ja.arb | 6 +++-- res/values/strings_ko.arb | 6 +++-- res/values/strings_my.arb | 6 +++-- res/values/strings_nl.arb | 6 +++-- res/values/strings_pl.arb | 6 +++-- res/values/strings_pt.arb | 6 +++-- res/values/strings_ru.arb | 6 +++-- res/values/strings_th.arb | 6 +++-- res/values/strings_tl.arb | 6 +++-- res/values/strings_tr.arb | 6 +++-- res/values/strings_uk.arb | 6 +++-- res/values/strings_ur.arb | 6 +++-- res/values/strings_yo.arb | 6 +++-- res/values/strings_zh.arb | 6 +++-- 32 files changed, 146 insertions(+), 78 deletions(-) diff --git a/cw_nano/lib/nano_wallet_creation_credentials.dart b/cw_nano/lib/nano_wallet_creation_credentials.dart index 84531e24a..3616fcf44 100644 --- a/cw_nano/lib/nano_wallet_creation_credentials.dart +++ b/cw_nano/lib/nano_wallet_creation_credentials.dart @@ -10,13 +10,11 @@ class NanoRestoreWalletFromSeedCredentials extends WalletCredentials { NanoRestoreWalletFromSeedCredentials({ required String name, required this.mnemonic, - int height = 0, String? password, DerivationType? derivationType, }) : super( name: name, password: password, - height: height, derivationType: derivationType, ); @@ -33,9 +31,12 @@ class NanoRestoreWalletFromKeysCredentials extends WalletCredentials { required String name, required String password, required this.seedKey, - this.derivationType, - }) : super(name: name, password: password); + DerivationType? derivationType, + }) : super( + name: name, + password: password, + derivationType: derivationType, + ); final String seedKey; - final DerivationType? derivationType; -} \ No newline at end of file +} diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 14bfe2f7f..4dd7d34e7 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -46,7 +46,7 @@ class AddressResolver { } final match = RegExp(addressPattern).firstMatch(raw); - return match?.group(0)?.replaceAll(RegExp('[^0-9a-zA-Z]'), ''); + return match?.group(0)?.replaceAll(RegExp('[^0-9a-zA-Z_]'), ''); } Future resolve(String text, String ticker) async { diff --git a/lib/nano/cw_nano.dart b/lib/nano/cw_nano.dart index cd0f0ca8a..778b2584a 100644 --- a/lib/nano/cw_nano.dart +++ b/lib/nano/cw_nano.dart @@ -406,6 +406,13 @@ class CWNanoUtil extends NanoUtil { late String publicAddress; if (seedKey != null) { + if (seedKey.length == 64) { + try { + mnemonic = nanoUtil!.seedToMnemonic(seedKey); + } catch (e) { + print("not a valid 'nano' seed key"); + } + } if (derivationType == DerivationType.bip39) { publicAddress = await hdSeedToAddress(seedKey, 0); } else if (derivationType == DerivationType.nano) { @@ -429,7 +436,8 @@ class CWNanoUtil extends NanoUtil { AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress); if (accountInfo == null) { - accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0); + accountInfo = AccountInfoResponse( + frontier: "", balance: "0", representative: "", confirmationHeight: 0); } accountInfo.address = publicAddress; return accountInfo; @@ -449,7 +457,11 @@ class CWNanoUtil extends NanoUtil { if (seedKey?.length == 128) { return [DerivationType.bip39]; } else if (seedKey?.length == 64) { - return [DerivationType.nano]; + try { + mnemonic = nanoUtil!.seedToMnemonic(seedKey!); + } catch (e) { + print("not a valid 'nano' seed key"); + } } late String publicAddressStandard; @@ -503,7 +515,7 @@ class CWNanoUtil extends NanoUtil { // we don't know for sure: return [DerivationType.nano, DerivationType.bip39]; } catch (e) { - return [DerivationType.unknown]; + return [DerivationType.nano, DerivationType.bip39]; } } } diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index e42488183..2c0dfa53f 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -124,9 +124,16 @@ class WalletRestoreFromKeysFromState extends State { Widget _restoreFromKeysFormFields() { if (widget.displayPrivateKeyField) { + // the term "private key" isn't actually what we're accepting here, and it's confusing to + // users of the nano community, what this form actually accepts (when importing for nano) is a nano seed in it's hex form, referred to in code as a "seed key" + // so we should change the placeholder text to reflect this + // supporting actual nano private keys is possible, but it's super niche in the nano community / they're not really used + + bool nanoBased = widget.walletRestoreViewModel.type == WalletType.nano || + widget.walletRestoreViewModel.type == WalletType.banano; return AddressTextField( controller: privateKeyController, - placeholder: S.of(context).private_key, + placeholder: nanoBased ? S.of(context).seed_key : S.of(context).private_key, options: [AddressTextFieldOption.paste], buttonColor: Theme.of(context).hintColor, onPushPasteButton: (_) { diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart index 7015e0acf..bf9a85b32 100644 --- a/lib/src/widgets/seed_widget.dart +++ b/lib/src/widgets/seed_widget.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; @@ -75,7 +76,7 @@ class SeedWidgetState extends State { Positioned( top: 10, left: 0, - child: Text('Enter your seed', + child: Text(S.of(context).enter_seed_phrase, style: TextStyle( fontSize: 16.0, color: Theme.of(context).hintColor))), Padding( diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 719298675..9d0eee052 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -185,15 +185,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed bool get hasCoinControl => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.monero || - wallet.type == WalletType.bitcoinCash; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.monero || + wallet.type == WalletType.bitcoinCash; @computed bool get isElectrumWallet => wallet.type == WalletType.bitcoin || - wallet.type == WalletType.litecoin || - wallet.type == WalletType.bitcoinCash; + wallet.type == WalletType.litecoin || + wallet.type == WalletType.bitcoinCash; @computed bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; @@ -281,7 +281,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor List conditionsList = []; for (var output in outputs) { - final show = checkThroughChecksToDisplayTOTP(output.extractedAddress); conditionsList.add(show); } @@ -350,29 +349,25 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor Object _credentials() { final priority = _settingsStore.priority[wallet.type]; - if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); - switch (wallet.type) { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); case WalletType.monero: + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); return monero! .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority); case WalletType.haven: + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); return haven!.createHavenTransactionCreationCredentials( outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title); case WalletType.ethereum: - final priority = _settingsStore.priority[wallet.type]; - - if (priority == null) { - throw Exception('Priority is null for wallet type: ${wallet.type}'); - } - + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}'); return ethereum!.createEthereumTransactionCredentials(outputs, priority: priority, currency: selectedCryptoCurrency); case WalletType.nano: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index a730a9dd8..fcb1b090c 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -717,5 +717,7 @@ "message": "ﺔﻟﺎﺳﺭ", "do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.", "totp_auth_url": "TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ", - "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ" -} + "awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ", + "seed_key": "مفتاح البذور", + "enter_seed_phrase": "أدخل عبارة البذور الخاصة بك" +} \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 212f7f2a8..d85b30118 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -713,5 +713,7 @@ "message": "Съобщение", "do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката." -} + "awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката.", + "seed_key": "Ключ за семена", + "enter_seed_phrase": "Въведете вашата фраза за семена" +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 3042cadfe..998f0b915 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -713,5 +713,7 @@ "message": "Zpráva", "do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.", "totp_auth_url": "URL AUTH TOTP", - "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování." -} + "awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování.", + "seed_key": "Klíč semen", + "enter_seed_phrase": "Zadejte svou frázi semen" +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 9971aff0e..82575200f 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -721,5 +721,7 @@ "message": "Nachricht", "do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.", "totp_auth_url": "TOTP-Auth-URL", - "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat." -} + "awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat.", + "seed_key": "Samenschlüssel", + "enter_seed_phrase": "Geben Sie Ihre Samenphrase ein" +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b981c0177..f30d224c0 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -722,5 +722,7 @@ "message": "Message", "do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Kindly wait for the dApp to finish processing." -} + "awaitDAppProcessing": "Kindly wait for the dApp to finish processing.", + "seed_key": "Seed key", + "enter_seed_phrase": "Enter your seed phrase" +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 01bd3207e..049a83e40 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -721,5 +721,7 @@ "message": "Mensaje", "do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.", "totp_auth_url": "URL de autenticación TOTP", - "awaitDAppProcessing": "Espere a que la dApp termine de procesarse." -} + "awaitDAppProcessing": "Espere a que la dApp termine de procesarse.", + "seed_key": "Llave de semilla", + "enter_seed_phrase": "Ingrese su frase de semillas" +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f171cbb68..e6516fb87 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -721,5 +721,7 @@ "message": "Message", "do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.", "totp_auth_url": "URL D'AUTORISATION TOTP", - "awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement." -} + "awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement.", + "seed_key": "Clé de graines", + "enter_seed_phrase": "Entrez votre phrase de semence" +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 917cad8a9..7d8e8969c 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -699,5 +699,7 @@ "message": "Sako", "do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki." -} + "awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki.", + "seed_key": "Maɓallin iri", + "enter_seed_phrase": "Shigar da Sert Sentarku" +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 104c871bf..4a9e6b284 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -721,5 +721,7 @@ "message": "संदेश", "do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।", "totp_auth_url": "TOTP प्रामाणिक यूआरएल", - "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।" -} + "awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।", + "seed_key": "बीज कुंजी", + "enter_seed_phrase": "अपना बीज वाक्यांश दर्ज करें" +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 93ab87319..17391a6d7 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -719,5 +719,7 @@ "message": "Poruka", "do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu." -} + "awaitDAppProcessing": "Molimo pričekajte da dApp završi obradu.", + "seed_key": "Sjemenski ključ", + "enter_seed_phrase": "Unesite svoju sjemensku frazu" +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index f6b20c213..5330ec96c 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -709,5 +709,7 @@ "message": "Pesan", "do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.", "totp_auth_url": "URL Otentikasi TOTP", - "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan." -} + "awaitDAppProcessing": "Mohon tunggu hingga dApp menyelesaikan pemrosesan.", + "seed_key": "Kunci benih", + "enter_seed_phrase": "Masukkan frasa benih Anda" +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 946deb91c..d0c5a1a87 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -721,5 +721,7 @@ "message": "Messaggio", "do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.", "totp_auth_url": "URL DI AUT. TOTP", - "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione." -} + "awaitDAppProcessing": "Attendi gentilmente che la dApp termini l'elaborazione.", + "seed_key": "Chiave di semi", + "enter_seed_phrase": "Inserisci la tua frase di semi" +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index f74f853d4..1d8012237 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -721,5 +721,7 @@ "message": "メッセージ", "do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。", "totp_auth_url": "TOTP認証URL", - "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。" -} + "awaitDAppProcessing": "dAppの処理が完了するまでお待ちください。", + "seed_key": "シードキー", + "enter_seed_phrase": "シードフレーズを入力してください" +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 5762822e1..df5d6f8ea 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -719,5 +719,7 @@ "message": "메시지", "do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.", "totp_auth_url": "TOTP 인증 URL", - "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요." -} + "awaitDAppProcessing": "dApp이 처리를 마칠 때까지 기다려주세요.", + "seed_key": "시드 키", + "enter_seed_phrase": "시드 문구를 입력하십시오" +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 33ec70494..e2c4a55f9 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -719,5 +719,7 @@ "message": "မက်ဆေ့ချ်", "do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။" -} + "awaitDAppProcessing": "ကျေးဇူးပြု၍ dApp ကို စီမံလုပ်ဆောင်ခြင်း အပြီးသတ်ရန် စောင့်ပါ။", + "seed_key": "မျိုးစေ့သော့", + "enter_seed_phrase": "သင့်ရဲ့မျိုးစေ့စကားစုကိုရိုက်ထည့်ပါ" +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 767959855..fd95f5299 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -721,5 +721,7 @@ "message": "Bericht", "do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.", "totp_auth_url": "TOTP AUTH-URL", - "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken." -} + "awaitDAppProcessing": "Wacht tot de dApp klaar is met verwerken.", + "seed_key": "Zaadsleutel", + "enter_seed_phrase": "Voer uw zaadzin in" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index f243a2343..561d0a731 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -721,5 +721,7 @@ "message": "Wiadomość", "do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.", "totp_auth_url": "Adres URL TOTP AUTH", - "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie." -} + "awaitDAppProcessing": "Poczekaj, aż dApp zakończy przetwarzanie.", + "seed_key": "Klucz nasion", + "enter_seed_phrase": "Wprowadź swoją frazę nasienną" +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 49a446f92..21378e9ba 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -720,5 +720,7 @@ "message": "Mensagem", "do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.", "totp_auth_url": "URL de autenticação TOTP", - "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento." -} + "awaitDAppProcessing": "Aguarde até que o dApp termine o processamento.", + "seed_key": "Chave de semente", + "enter_seed_phrase": "Digite sua frase de semente" +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 305ce68f2..e12b010dd 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -721,5 +721,7 @@ "message": "Сообщение", "do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.", "totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ", - "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку." -} + "awaitDAppProcessing": "Пожалуйста, подождите, пока dApp завершит обработку.", + "seed_key": "Ключ семян", + "enter_seed_phrase": "Введите свою семенную фразу" +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 95ba4ef6f..7ac83fc00 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -719,5 +719,7 @@ "message": "ข้อความ", "do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม", "totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP", - "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น" -} + "awaitDAppProcessing": "โปรดรอให้ dApp ประมวลผลเสร็จสิ้น", + "seed_key": "คีย์เมล็ดพันธุ์", + "enter_seed_phrase": "ป้อนวลีเมล็ดพันธุ์ของคุณ" +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 8072eee61..9981469a8 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -716,5 +716,7 @@ "message": "Mensahe", "do_not_have_enough_gas_asset": "Wala kang sapat na ${currency} para gumawa ng transaksyon sa kasalukuyang kundisyon ng network ng blockchain. Kailangan mo ng higit pang ${currency} upang magbayad ng mga bayarin sa network ng blockchain, kahit na nagpapadala ka ng ibang asset.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp." -} + "awaitDAppProcessing": "Pakihintay na matapos ang pagproseso ng dApp.", + "seed_key": "Seed Key", + "enter_seed_phrase": "Ipasok ang iyong pariralang binhi" +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 596b6b000..6c838b036 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -719,5 +719,7 @@ "message": "İleti", "do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.", "totp_auth_url": "TOTP YETKİ URL'si", - "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin." -} + "awaitDAppProcessing": "Lütfen dApp'in işlemeyi bitirmesini bekleyin.", + "seed_key": "Tohum", + "enter_seed_phrase": "Tohum ifadenizi girin" +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index bdc950faa..b7720fb99 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -721,5 +721,7 @@ "message": "повідомлення", "do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку." -} + "awaitDAppProcessing": "Зачекайте, доки dApp завершить обробку.", + "seed_key": "Насіннєвий ключ", + "enter_seed_phrase": "Введіть свою насіннєву фразу" +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 73c528b69..f7cbdd5e4 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -713,5 +713,7 @@ "message": "ﻡﺎﻐﯿﭘ", "do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ" -} + "awaitDAppProcessing": "۔ﮟﯾﺮﮐ ﺭﺎﻈﺘﻧﺍ ﺎﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﮓﻨﺴﯿﺳﻭﺮﭘ ﮯﮐ dApp ﻡﺮﮐ ﮦﺍﺮﺑ", + "seed_key": "بیج کی کلید", + "enter_seed_phrase": "اپنے بیج کا جملہ درج کریں" +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index f885c2493..a5e3ca263 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -715,5 +715,7 @@ "message": "Ifiranṣẹ", "do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.", "totp_auth_url": "TOTP AUTH URL", - "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ." -} + "awaitDAppProcessing": "Fi inurere duro fun dApp lati pari sisẹ.", + "seed_key": "Bọtini Ose", + "enter_seed_phrase": "Tẹ ọrọ-iru irugbin rẹ" +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 91642ec73..a2ea6b73f 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -720,5 +720,7 @@ "message": "信息", "do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。", "totp_auth_url": "TOTP 授权 URL", - "awaitDAppProcessing": "请等待 dApp 处理完成。" -} + "awaitDAppProcessing": "请等待 dApp 处理完成。", + "seed_key": "种子钥匙", + "enter_seed_phrase": "输入您的种子短语" +} \ No newline at end of file