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 <omarh.ismail1@gmail.com>

Serhii committed Oct 13, 2023 at 01:50 UTC 66301ff2478c1879859b5a20d4deff0559f7af25
88 files changed +1686 -417
.github/workflows/pr_test_build.yml
+14 -12
@@ -42,6 +42,7 @@ jobs:
42 cd cake_wallet/scripts/android/
43 ./install_ndk.sh
44 source ./app_env.sh cakewallet
45 + chmod +x pubspec_gen.sh
46 ./app_config.sh
47
48 - name: Cache Externals
@@ -92,6 +93,7 @@ jobs:
93 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
94 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
95 cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
96 + cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
97 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
98 flutter packages pub run build_runner build --delete-conflicting-outputs
99
@@ -141,18 +143,18 @@ jobs:
143 cd /opt/android/cake_wallet
144 flutter build apk --release
145
144 - # - name: Push to App Center
145 - # run: |
146 - # echo 'Installing App Center CLI tools'
147 - # npm install -g appcenter-cli
148 - # echo "Publishing test to App Center"
149 - # appcenter distribute release \
150 - # --group "Testers" \
151 - # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
152 - # --release-notes ${GITHUB_HEAD_REF} \
153 - # --app Cake-Labs/Cake-Wallet \
154 - # --token ${{ secrets.APP_CENTER_TOKEN }} \
155 - # --quiet
146 +# - name: Push to App Center
147 +# run: |
148 +# echo 'Installing App Center CLI tools'
149 +# npm install -g appcenter-cli
150 +# echo "Publishing test to App Center"
151 +# appcenter distribute release \
152 +# --group "Testers" \
153 +# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
154 +# --release-notes ${GITHUB_HEAD_REF} \
155 +# --app Cake-Labs/Cake-Wallet \
156 +# --token ${{ secrets.APP_CENTER_TOKEN }} \
157 +# --quiet
158
159 - name: Rename apk file
160 run: |
.gitignore
+1
@@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
124 lib/monero/monero.dart
125 lib/haven/haven.dart
126 lib/ethereum/ethereum.dart
127 +lib/bitcoin_cash/bitcoin_cash.dart
128 lib/nano/nano.dart
129
130 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
android/gradle.properties
+1 -1
@@ -1,4 +1,4 @@
1 org.gradle.jvmargs=-Xmx1536M
2 android.enableR8=true
3 android.useAndroidX=true
4 -android.enableJetifier=true
\ No newline at end of file
4 +android.enableJetifier=true
assets/bitcoin_cash_electrum_server_list.yml new
+3
@@ -0,0 +1,3 @@
1 +-
2 + uri: bitcoincash.stackwallet.com:50002
3 + is_default: true
\ No newline at end of file
configure_cake_wallet_android.sh
+1
@@ -8,4 +8,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build
8 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
9 cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
10 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
11 +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
12 flutter packages pub run build_runner build --delete-conflicting-outputs
cw_bitcoin/lib/bitcoin_transaction_priority.dart
+51 -1
@@ -1,5 +1,4 @@
1 import 'package:cw_core/transaction_priority.dart';
2 -//import 'package:cake_wallet/generated/i18n.dart';
2
3 class BitcoinTransactionPriority extends TransactionPriority {
4 const BitcoinTransactionPriority({required String title, required int raw})
@@ -98,6 +97,57 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority {
97 break;
98 }
99
100 + return label;
101 + }
102 +
103 +}
104 +class BitcoinCashTransactionPriority extends BitcoinTransactionPriority {
105 + const BitcoinCashTransactionPriority({required String title, required int raw})
106 + : super(title: title, raw: raw);
107 +
108 + static const List<BitcoinCashTransactionPriority> all = [fast, medium, slow];
109 + static const BitcoinCashTransactionPriority slow =
110 + BitcoinCashTransactionPriority(title: 'Slow', raw: 0);
111 + static const BitcoinCashTransactionPriority medium =
112 + BitcoinCashTransactionPriority(title: 'Medium', raw: 1);
113 + static const BitcoinCashTransactionPriority fast =
114 + BitcoinCashTransactionPriority(title: 'Fast', raw: 2);
115 +
116 + static BitcoinCashTransactionPriority deserialize({required int raw}) {
117 + switch (raw) {
118 + case 0:
119 + return slow;
120 + case 1:
121 + return medium;
122 + case 2:
123 + return fast;
124 + default:
125 + throw Exception('Unexpected token: $raw for BitcoinCashTransactionPriority deserialize');
126 + }
127 + }
128 +
129 + @override
130 + String get units => 'Satoshi';
131 +
132 + @override
133 + String toString() {
134 + var label = '';
135 +
136 + switch (this) {
137 + case BitcoinCashTransactionPriority.slow:
138 + label = 'Slow'; // S.current.transaction_priority_slow;
139 + break;
140 + case BitcoinCashTransactionPriority.medium:
141 + label = 'Medium'; // S.current.transaction_priority_medium;
142 + break;
143 + case BitcoinCashTransactionPriority.fast:
144 + label = 'Fast'; // S.current.transaction_priority_fast;
145 + break;
146 + default:
147 + break;
148 + }
149 +
150 return label;
151 }
152 }
153 +
cw_bitcoin/lib/bitcoin_unspent.dart
+7 -16
@@ -1,24 +1,15 @@
1 import 'package:cw_bitcoin/bitcoin_address_record.dart';
2 +import 'package:cw_core/unspent_transaction_output.dart';
3
3 -class BitcoinUnspent {
4 - BitcoinUnspent(this.address, this.hash, this.value, this.vout)
5 - : isSending = true,
6 - isFrozen = false,
7 - note = '';
4 +class BitcoinUnspent extends Unspent {
5 + BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
6 + : bitcoinAddressRecord = addressRecord,
7 + super(addressRecord.address, hash, value, vout, null);
8
9 factory BitcoinUnspent.fromJSON(
10 - BitcoinAddressRecord address, Map<String, dynamic> json) =>
10 + BitcoinAddressRecord address, Map<String, dynamic> json) =>
11 BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
12 json['tx_pos'] as int);
13
14 - final BitcoinAddressRecord address;
15 - final String hash;
16 - final int value;
17 - final int vout;
18 -
19 - bool get isP2wpkh =>
20 - address.address.startsWith('bc') || address.address.startsWith('ltc');
21 - bool isSending;
22 - bool isFrozen;
23 - String note;
14 + final BitcoinAddressRecord bitcoinAddressRecord;
15 }
cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+20 -25
@@ -1,39 +1,34 @@
1 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 -import 'package:cw_bitcoin/electrum.dart';
3 -import 'package:cw_bitcoin/utils.dart';
2 import 'package:cw_bitcoin/bitcoin_address_record.dart';
3 +import 'package:cw_bitcoin/electrum.dart';
4 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
5 +import 'package:cw_bitcoin/utils.dart';
6 import 'package:cw_core/wallet_info.dart';
7 -import 'package:flutter/foundation.dart';
7 import 'package:mobx/mobx.dart';
8
9 part 'bitcoin_wallet_addresses.g.dart';
10
12 -class BitcoinWalletAddresses = BitcoinWalletAddressesBase
13 - with _$BitcoinWalletAddresses;
11 +class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
12
15 -abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
16 - with Store {
17 - BitcoinWalletAddressesBase(
18 - WalletInfo walletInfo,
13 +abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
14 + BitcoinWalletAddressesBase(WalletInfo walletInfo,
15 {required bitcoin.HDWallet mainHd,
20 - required bitcoin.HDWallet sideHd,
21 - required bitcoin.NetworkType networkType,
22 - required ElectrumClient electrumClient,
23 - List<BitcoinAddressRecord>? initialAddresses,
24 - int initialRegularAddressIndex = 0,
25 - int initialChangeAddressIndex = 0})
26 - : super(
27 - walletInfo,
28 - initialAddresses: initialAddresses,
29 - initialRegularAddressIndex: initialRegularAddressIndex,
30 - initialChangeAddressIndex: initialChangeAddressIndex,
31 - mainHd: mainHd,
32 - sideHd: sideHd,
33 - electrumClient: electrumClient,
34 - networkType: networkType);
16 + required bitcoin.HDWallet sideHd,
17 + required bitcoin.NetworkType networkType,
18 + required ElectrumClient electrumClient,
19 + List<BitcoinAddressRecord>? initialAddresses,
20 + int initialRegularAddressIndex = 0,
21 + int initialChangeAddressIndex = 0})
22 + : super(walletInfo,
23 + initialAddresses: initialAddresses,
24 + initialRegularAddressIndex: initialRegularAddressIndex,
25 + initialChangeAddressIndex: initialChangeAddressIndex,
26 + mainHd: mainHd,
27 + sideHd: sideHd,
28 + electrumClient: electrumClient,
29 + networkType: networkType);
30
31 @override
32 String getAddress({required int index, required bitcoin.HDWallet hd}) =>
33 generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
39 -}
\ No newline at end of file
34 +}
cw_bitcoin/lib/electrum_wallet.dart
+113 -138
@@ -2,7 +2,9 @@ import 'dart:async';
2 import 'dart:convert';
3 import 'dart:io';
4 import 'dart:math';
5 +import 'package:cw_core/pending_transaction.dart';
6 import 'package:cw_core/unspent_coins_info.dart';
7 +import 'package:cw_core/wallet_type.dart';
8 import 'package:hive/hive.dart';
9 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
10 import 'package:mobx/mobx.dart';
@@ -34,45 +36,52 @@ import 'package:cw_bitcoin/electrum.dart';
36 import 'package:hex/hex.dart';
37 import 'package:cw_core/crypto_currency.dart';
38 import 'package:collection/collection.dart';
39 +import 'package:bip32/bip32.dart';
40
41 part 'electrum_wallet.g.dart';
42
43 class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
44
42 -abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
43 - ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
45 +abstract class ElectrumWalletBase
46 + extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
47 + with Store {
48 ElectrumWalletBase(
49 {required String password,
46 - required WalletInfo walletInfo,
47 - required Box<UnspentCoinsInfo> unspentCoinsInfo,
48 - required this.networkType,
49 - required this.mnemonic,
50 - required Uint8List seedBytes,
51 - List<BitcoinAddressRecord>? initialAddresses,
52 - ElectrumClient? electrumClient,
53 - ElectrumBalance? initialBalance,
54 - CryptoCurrency? currency})
55 - : hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
56 - .derivePath("m/0'/0"),
50 + required WalletInfo walletInfo,
51 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
52 + required this.networkType,
53 + required this.mnemonic,
54 + required Uint8List seedBytes,
55 + List<BitcoinAddressRecord>? initialAddresses,
56 + ElectrumClient? electrumClient,
57 + ElectrumBalance? initialBalance,
58 + CryptoCurrency? currency})
59 + : hd = currency == CryptoCurrency.bch
60 + ? bitcoinCashHDWallet(seedBytes)
61 + : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
62 syncStatus = NotConnectedSyncStatus(),
63 _password = password,
64 _feeRates = <int>[],
65 _isTransactionUpdating = false,
66 unspentCoins = [],
67 _scripthashesUpdateSubject = {},
63 - balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(
64 - currency != null
65 - ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0,
66 - frozen: 0)}
67 - : {}),
68 + balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
69 + ? {
70 + currency:
71 + initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
72 + }
73 + : {}),
74 this.unspentCoinsInfo = unspentCoinsInfo,
75 super(walletInfo) {
76 this.electrumClient = electrumClient ?? ElectrumClient();
77 this.walletInfo = walletInfo;
72 - transactionHistory =
73 - ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
78 + transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
79 }
80
81 + static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
82 + bitcoin.HDWallet.fromSeed(seedBytes)
83 + .derivePath("m/44'/145'/0'/0");
84 +
85 static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
86 inputsCount * 146 + outputsCounts * 33 + 8;
87
@@ -98,9 +107,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
107 .toList();
108
109 List<String> get publicScriptHashes => walletAddresses.addresses
101 - .where((addr) => !addr.isHidden)
102 - .map((addr) => scriptHash(addr.address, networkType: networkType))
103 - .toList();
110 + .where((addr) => !addr.isHidden)
111 + .map((addr) => scriptHash(addr.address, networkType: networkType))
112 + .toList();
113
114 String get xpub => hd.base58!;
115
@@ -110,8 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
119 bitcoin.NetworkType networkType;
120
121 @override
113 - BitcoinWalletKeys get keys => BitcoinWalletKeys(
114 - wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
122 + BitcoinWalletKeys get keys =>
123 + BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
124
125 String _password;
126 List<BitcoinUnspent> unspentCoins;
@@ -139,8 +148,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
148 await updateBalance();
149 _feeRates = await electrumClient.feeRates();
150
142 - Timer.periodic(const Duration(minutes: 1),
143 - (timer) async => _feeRates = await electrumClient.feeRates());
151 + Timer.periodic(
152 + const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
153
154 syncStatus = SyncedSyncStatus();
155 } catch (e, stacktrace) {
@@ -169,8 +178,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
178 }
179
180 @override
172 - Future<PendingBitcoinTransaction> createTransaction(
173 - Object credentials) async {
181 + Future<PendingTransaction> createTransaction(Object credentials) async {
182 const minAmount = 546;
183 final transactionCredentials = credentials as BitcoinTransactionCredentials;
184 final inputs = <BitcoinUnspent>[];
@@ -204,13 +212,11 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
212 var fee = 0;
213
214 if (hasMultiDestination) {
207 - if (outputs.any((item) => item.sendAll
208 - || item.formattedCryptoAmount! <= 0)) {
215 + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
216 throw BitcoinTransactionWrongBalanceException(currency);
217 }
218
212 - credentialsAmount = outputs.fold(0, (acc, value) =>
213 - acc + value.formattedCryptoAmount!);
219 + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
220
221 if (allAmount - credentialsAmount < minAmount) {
222 throw BitcoinTransactionWrongBalanceException(currency);
@@ -227,9 +233,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
233 }
234 } else {
235 final output = outputs.first;
230 - credentialsAmount = !output.sendAll
231 - ? output.formattedCryptoAmount!
232 - : 0;
236 + credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
237
238 if (credentialsAmount > allAmount) {
239 throw BitcoinTransactionWrongBalanceException(currency);
@@ -291,8 +295,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
295 final p2wpkh = bitcoin
296 .P2WPKH(
297 data: generatePaymentData(
294 - hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
295 - index: input.address.index),
298 + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
299 + index: input.bitcoinAddressRecord.index),
300 network: networkType)
301 .data;
302
@@ -303,19 +307,12 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
307 });
308
309 outputs.forEach((item) {
306 - final outputAmount = hasMultiDestination
307 - ? item.formattedCryptoAmount
308 - : amount;
309 - final outputAddress = item.isParsedAddress
310 - ? item.extractedAddress!
311 - : item.address;
312 - txb.addOutput(
313 - addressToOutputScript(outputAddress, networkType),
314 - outputAmount!);
310 + final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
311 + final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
312 + txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
313 });
314
317 - final estimatedSize =
318 - estimatedTransactionSize(inputs.length, outputs.length + 1);
315 + final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
316 var feeAmount = 0;
317
318 if (transactionCredentials.feeRate != null) {
@@ -333,8 +330,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
330 for (var i = 0; i < inputs.length; i++) {
331 final input = inputs[i];
332 final keyPair = generateKeyPair(
336 - hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
337 - index: input.address.index,
333 + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
334 + index: input.bitcoinAddressRecord.index,
335 network: networkType);
336 final witnessValue = input.isP2wpkh ? input.value : null;
337
@@ -350,12 +347,12 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
347 }
348
349 String toJSON() => json.encode({
353 - 'mnemonic': mnemonic,
354 - 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
355 - 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
356 - 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
357 - 'balance': balance[currency]?.toJSON()
358 - });
350 + 'mnemonic': mnemonic,
351 + 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
352 + 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
353 + 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
354 + 'balance': balance[currency]?.toJSON()
355 + });
356
357 int feeRate(TransactionPriority priority) {
358 try {
@@ -364,34 +361,29 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
361 }
362
363 return 0;
367 - } catch(_) {
364 + } catch (_) {
365 return 0;
366 }
367 }
368
372 - int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
373 - int outputsCount) =>
369 + int feeAmountForPriority(
370 + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
371 feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
372
376 - int feeAmountWithFeeRate(int feeRate, int inputsCount,
377 - int outputsCount) =>
373 + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
374 feeRate * estimatedTransactionSize(inputsCount, outputsCount);
375
376 @override
381 - int calculateEstimatedFee(TransactionPriority? priority, int? amount,
382 - {int? outputsCount}) {
377 + int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
378 if (priority is BitcoinTransactionPriority) {
384 - return calculateEstimatedFeeWithFeeRate(
385 - feeRate(priority),
386 - amount,
387 - outputsCount: outputsCount);
379 + return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
380 + outputsCount: outputsCount);
381 }
382
383 return 0;
384 }
385
393 - int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount,
394 - {int? outputsCount}) {
386 + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
387 int inputsCount = 0;
388
389 if (amount != null) {
@@ -420,8 +412,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
412 // If send all, then we have no change value
413 final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
414
423 - return feeAmountWithFeeRate(
424 - feeRate, inputsCount, _outputsCount);
415 + return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
416 }
417
418 @override
@@ -436,8 +427,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
427 final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
428 final currentWalletFile = File(currentWalletPath);
429
439 - final currentDirPath =
440 - await pathForWalletDir(name: walletInfo.name, type: type);
430 + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
431 final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
432
433 // Copies current wallet files into new wallet name's dir and files
@@ -474,21 +464,20 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
464 } catch (_) {}
465 }
466
477 - Future<String> makePath() async =>
478 - pathForWallet(name: walletInfo.name, type: walletInfo.type);
467 + Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
468
469 Future<void> updateUnspent() async {
470 final unspent = await Future.wait(walletAddresses
471 .addresses.map((address) => electrumClient
472 .getListUnspentWithAddress(address.address, networkType)
473 .then((unspent) => unspent
485 - .map((unspent) {
486 - try {
487 - return BitcoinUnspent.fromJSON(address, unspent);
488 - } catch(_) {
489 - return null;
490 - }
491 - }).whereNotNull())));
474 + .map((unspent) {
475 + try {
476 + return BitcoinUnspent.fromJSON(address, unspent);
477 + } catch(_) {
478 + return null;
479 + }
480 + }).whereNotNull())));
481 unspentCoins = unspent.expand((e) => e).toList();
482
483 if (unspentCoinsInfo.isEmpty) {
@@ -498,8 +487,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
487
488 if (unspentCoins.isNotEmpty) {
489 unspentCoins.forEach((coin) {
501 - final coinInfoList = unspentCoinsInfo.values.where((element) =>
502 - element.walletId.contains(id) && element.hash.contains(coin.hash));
490 + final coinInfoList = unspentCoinsInfo.values
491 + .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
492
493 if (coinInfoList.isNotEmpty) {
494 final coinInfo = coinInfoList.first;
@@ -518,14 +507,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
507
508 Future<void> _addCoinInfo(BitcoinUnspent coin) async {
509 final newInfo = UnspentCoinsInfo(
521 - walletId: id,
522 - hash: coin.hash,
523 - isFrozen: coin.isFrozen,
524 - isSending: coin.isSending,
525 - noteRaw: coin.note,
526 - address: coin.address.address,
527 - value: coin.value,
528 - vout: coin.vout,
510 + walletId: id,
511 + hash: coin.hash,
512 + isFrozen: coin.isFrozen,
513 + isSending: coin.isSending,
514 + noteRaw: coin.note,
515 + address: coin.bitcoinAddressRecord.address,
516 + value: coin.value,
517 + vout: coin.vout,
518 );
519
520 await unspentCoinsInfo.add(newInfo);
@@ -534,8 +523,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
523 Future<void> _refreshUnspentCoinsInfo() async {
524 try {
525 final List<dynamic> keys = <dynamic>[];
537 - final currentWalletUnspentCoins = unspentCoinsInfo.values
538 - .where((element) => element.walletId.contains(id));
526 + final currentWalletUnspentCoins =
527 + unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
528
529 if (currentWalletUnspentCoins.isNotEmpty) {
530 currentWalletUnspentCoins.forEach((element) {
@@ -571,27 +560,19 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
560 ins.add(tx);
561 }
562
574 - return ElectrumTransactionBundle(
575 - original,
576 - ins: ins,
577 - time: time,
578 - confirmations: confirmations);
563 + return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations);
564 }
565
566 Future<ElectrumTransactionInfo?> fetchTransactionInfo(
567 {required String hash, required int height}) async {
583 - try {
584 - final tx = await getTransactionExpanded(hash: hash, height: height);
585 - final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
586 - return ElectrumTransactionInfo.fromElectrumBundle(
587 - tx,
588 - walletInfo.type,
589 - networkType,
590 - addresses: addresses,
591 - height: height);
592 - } catch(_) {
593 - return null;
594 - }
568 + try {
569 + final tx = await getTransactionExpanded(hash: hash, height: height);
570 + final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
571 + return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
572 + addresses: addresses, height: height);
573 + } catch (_) {
574 + return null;
575 + }
576 }
577
578 @override
@@ -602,10 +583,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
583 final sh = scriptHash(addressRecord.address, networkType: networkType);
584 addressHashes[sh] = addressRecord;
585 });
605 - final histories =
606 - addressHashes.keys.map((scriptHash) => electrumClient
607 - .getHistory(scriptHash)
608 - .then((history) => {scriptHash: history}));
586 + final histories = addressHashes.keys.map((scriptHash) =>
587 + electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
588 final historyResults = await Future.wait(histories);
589 historyResults.forEach((history) {
590 history.entries.forEach((historyItem) {
@@ -616,19 +595,16 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
595 }
596 });
597 });
619 - final historiesWithDetails = await Future.wait(
620 - normalizedHistories
621 - .map((transaction) {
622 - try {
623 - return fetchTransactionInfo(
624 - hash: transaction['tx_hash'] as String,
625 - height: transaction['height'] as int);
626 - } catch(_) {
627 - return Future.value(null);
628 - }
629 - }));
630 - return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
631 - <String, ElectrumTransactionInfo>{}, (acc, tx) {
598 + final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
599 + try {
600 + return fetchTransactionInfo(
601 + hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
602 + } catch (_) {
603 + return Future.value(null);
604 + }
605 + }));
606 + return historiesWithDetails
607 + .fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
608 if (tx == null) {
609 return acc;
610 }
@@ -680,9 +656,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
656 Future<ElectrumBalance> _fetchBalances() async {
657 final addresses = walletAddresses.addresses.toList();
658 final balanceFutures = <Future<Map<String, dynamic>>>[];
683 -
659 for (var i = 0; i < addresses.length; i++) {
685 - final addressRecord = addresses[i];
660 + final addressRecord = addresses[i] ;
661 final sh = scriptHash(addressRecord.address, networkType: networkType);
662 final balanceFuture = electrumClient.getBalance(sh);
663 balanceFutures.add(balanceFuture);
@@ -691,8 +666,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
666 var totalFrozen = 0;
667 unspentCoinsInfo.values.forEach((info) {
668 unspentCoins.forEach((element) {
694 - if (element.hash == info.hash && info.isFrozen && element.address.address == info.address
695 - && element.value == info.value) {
669 + if (element.hash == info.hash &&
670 + info.isFrozen &&
671 + element.bitcoinAddressRecord.address == info.address &&
672 + element.value == info.value) {
673 totalFrozen += element.value;
674 }
675 });
@@ -715,8 +692,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
692 }
693 }
694
718 - return ElectrumBalance(confirmed: totalConfirmed, unconfirmed: totalUnconfirmed,
719 - frozen: totalFrozen);
695 + return ElectrumBalance(
696 + confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
697 }
698
699 Future<void> updateBalance() async {
@@ -727,9 +704,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
704 String getChangeAddress() {
705 const minCountOfHiddenAddresses = 5;
706 final random = Random();
730 - var addresses = walletAddresses.addresses
731 - .where((addr) => addr.isHidden)
732 - .toList();
707 + var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList();
708
709 if (addresses.length < minCountOfHiddenAddresses) {
710 addresses = walletAddresses.addresses.toList();
cw_bitcoin/lib/electrum_wallet_addresses.dart
+11 -6
@@ -1,9 +1,11 @@
1 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 +import 'package:bitbox/bitbox.dart' as bitbox;
3 import 'package:cw_bitcoin/bitcoin_address_record.dart';
4 import 'package:cw_bitcoin/electrum.dart';
5 import 'package:cw_bitcoin/script_hash.dart';
6 import 'package:cw_core/wallet_addresses.dart';
7 import 'package:cw_core/wallet_info.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 import 'package:mobx/mobx.dart';
10
11 part 'electrum_wallet_addresses.g.dart';
@@ -38,6 +40,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
40 static const defaultChangeAddressesCount = 17;
41 static const gap = 20;
42
43 + static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
44 +
45 final ObservableList<BitcoinAddressRecord> addresses;
46 final ObservableList<BitcoinAddressRecord> receiveAddresses;
47 final ObservableList<BitcoinAddressRecord> changeAddresses;
@@ -50,10 +54,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
54 @computed
55 String get address {
56 if (receiveAddresses.isEmpty) {
53 - return generateNewAddress().address;
57 + final address = generateNewAddress().address;
58 + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address;
59 }
60 + final receiveAddress = receiveAddresses.first.address;
61
56 - return receiveAddresses.first.address;
62 + return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
63 }
64
65 @override
@@ -105,10 +111,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
111 @action
112 Future<String> getChangeAddress() async {
113 updateChangeAddresses();
108 -
114 +
115 if (changeAddresses.isEmpty) {
110 - final newAddresses = await _createNewAddresses(
111 - gap,
116 + final newAddresses = await _createNewAddresses(gap,
117 hd: sideHd,
118 startIndex: totalCountOfChangeAddresses > 0
119 ? totalCountOfChangeAddresses - 1
@@ -179,7 +184,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
184 } else {
185 addrs = await _createNewAddresses(
186 isHidden
182 - ? defaultChangeAddressesCount
187 + ? defaultChangeAddressesCount
188 : defaultReceiveAddressesCount,
189 startIndex: 0,
190 hd: hd,
cw_bitcoin/pubspec.lock
+9
@@ -66,6 +66,15 @@ packages:
66 url: "https://pub.dev"
67 source: hosted
68 version: "1.0.6"
69 + bitbox:
70 + dependency: "direct main"
71 + description:
72 + path: "."
73 + ref: master
74 + resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
75 + url: "https://github.com/cake-tech/bitbox-flutter.git"
76 + source: git
77 + version: "1.0.1"
78 bitcoin_flutter:
79 dependency: "direct main"
80 description:
cw_bitcoin/pubspec.yaml
+4
@@ -23,6 +23,10 @@ dependencies:
23 git:
24 url: https://github.com/cake-tech/bitcoin_flutter.git
25 ref: cake-update-v3
26 + bitbox:
27 + git:
28 + url: https://github.com/cake-tech/bitbox-flutter.git
29 + ref: master
30 rxdart: ^0.27.5
31 unorm_dart: ^0.2.0
32 cryptography: ^2.0.5
cw_bitcoin_cash/.gitignore new
+30
@@ -0,0 +1,30 @@
1 +# Miscellaneous
2 +*.class
3 +*.log
4 +*.pyc
5 +*.swp
6 +.DS_Store
7 +.atom/
8 +.buildlog/
9 +.history
10 +.svn/
11 +migrate_working_dir/
12 +
13 +# IntelliJ related
14 +*.iml
15 +*.ipr
16 +*.iws
17 +.idea/
18 +
19 +# The .vscode folder contains launch configuration and tasks you configure in
20 +# VS Code which you may wish to be included in version control, so this line
21 +# is commented out by default.
22 +#.vscode/
23 +
24 +# Flutter/Dart/Pub related
25 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 +/pubspec.lock
27 +**/doc/api/
28 +.dart_tool/
29 +.packages
30 +build/
cw_bitcoin_cash/.metadata new
+10
@@ -0,0 +1,10 @@
1 +# This file tracks properties of this Flutter project.
2 +# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 +#
4 +# This file should be version controlled and should not be manually edited.
5 +
6 +version:
7 + revision: b06b8b2710955028a6b562f5aa6fe62941d6febf
8 + channel: stable
9 +
10 +project_type: package
cw_bitcoin_cash/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 0.0.1
2 +
3 +* TODO: Describe initial release.
cw_bitcoin_cash/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_bitcoin_cash/README.md new
+39
@@ -0,0 +1,39 @@
1 +<!--
2 +This README describes the package. If you publish this package to pub.dev,
3 +this README's contents appear on the landing page for your package.
4 +
5 +For information about how to write a good package README, see the guide for
6 +[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
7 +
8 +For general information about developing packages, see the Dart guide for
9 +[creating packages](https://dart.dev/guides/libraries/create-library-packages)
10 +and the Flutter guide for
11 +[developing packages and plugins](https://flutter.dev/developing-packages).
12 +-->
13 +
14 +TODO: Put a short description of the package here that helps potential users
15 +know whether this package might be useful for them.
16 +
17 +## Features
18 +
19 +TODO: List what your package can do. Maybe include images, gifs, or videos.
20 +
21 +## Getting started
22 +
23 +TODO: List prerequisites and provide or point to information on how to
24 +start using the package.
25 +
26 +## Usage
27 +
28 +TODO: Include short and useful examples for package users. Add longer examples
29 +to `/example` folder.
30 +
31 +```dart
32 +const like = 'sample';
33 +```
34 +
35 +## Additional information
36 +
37 +TODO: Tell users more about the package: where to find more information, how to
38 +contribute to the package, how to file issues, what response they can expect
39 +from the package authors, and more.
cw_bitcoin_cash/analysis_options.yaml new
+4
@@ -0,0 +1,4 @@
1 +include: package:flutter_lints/flutter.yaml
2 +
3 +# Additional information about this file can be found at
4 +# https://dart.dev/guides/language/analysis-options
cw_bitcoin_cash/lib/cw_bitcoin_cash.dart new
+9
@@ -0,0 +1,9 @@
1 +library cw_bitcoin_cash;
2 +
3 +export 'src/bitcoin_cash_base.dart';
4 +
5 +/// A Calculator.
6 +class Calculator {
7 + /// Returns [value] plus 1.
8 + int addOne(int value) => value + 1;
9 +}
cw_bitcoin_cash/lib/src/bitcoin_cash_address_utils.dart new
+5
@@ -0,0 +1,5 @@
1 +import 'package:bitbox/bitbox.dart' as bitbox;
2 +
3 +class AddressUtils {
4 + static String getCashAddrFormat(String address) => bitbox.Address.toCashAddress(address);
5 +}
cw_bitcoin_cash/lib/src/bitcoin_cash_base.dart new
+7
@@ -0,0 +1,7 @@
1 +export 'bitcoin_cash_wallet.dart';
2 +export 'bitcoin_cash_wallet_addresses.dart';
3 +export 'bitcoin_cash_wallet_creation_credentials.dart';
4 +export 'bitcoin_cash_wallet_service.dart';
5 +export 'exceptions/exceptions.dart';
6 +export 'mnemonic.dart';
7 +export 'bitcoin_cash_address_utils.dart';
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart new
+297
@@ -0,0 +1,297 @@
1 +import 'package:bitbox/bitbox.dart' as bitbox;
2 +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
3 +import 'package:cw_bitcoin/bitcoin_address_record.dart';
4 +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
5 +import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
6 +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
7 +import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
8 +import 'package:cw_bitcoin/bitcoin_unspent.dart';
9 +import 'package:cw_bitcoin/electrum_balance.dart';
10 +import 'package:cw_bitcoin/electrum_wallet.dart';
11 +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
12 +import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart';
13 +import 'package:cw_core/crypto_currency.dart';
14 +import 'package:cw_core/transaction_priority.dart';
15 +import 'package:cw_core/unspent_coins_info.dart';
16 +import 'package:cw_core/wallet_info.dart';
17 +import 'package:flutter/foundation.dart';
18 +import 'package:hive/hive.dart';
19 +import 'package:mobx/mobx.dart';
20 +
21 +import 'bitcoin_cash_base.dart';
22 +
23 +part 'bitcoin_cash_wallet.g.dart';
24 +
25 +class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
26 +
27 +abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
28 + BitcoinCashWalletBase(
29 + {required String mnemonic,
30 + required String password,
31 + required WalletInfo walletInfo,
32 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
33 + required Uint8List seedBytes,
34 + List<BitcoinAddressRecord>? initialAddresses,
35 + ElectrumBalance? initialBalance,
36 + int initialRegularAddressIndex = 0,
37 + int initialChangeAddressIndex = 0})
38 + : super(
39 + mnemonic: mnemonic,
40 + password: password,
41 + walletInfo: walletInfo,
42 + unspentCoinsInfo: unspentCoinsInfo,
43 + networkType: bitcoin.bitcoin,
44 + initialAddresses: initialAddresses,
45 + initialBalance: initialBalance,
46 + seedBytes: seedBytes,
47 + currency: CryptoCurrency.bch) {
48 + walletAddresses = BitcoinCashWalletAddresses(walletInfo,
49 + electrumClient: electrumClient,
50 + initialAddresses: initialAddresses,
51 + initialRegularAddressIndex: initialRegularAddressIndex,
52 + initialChangeAddressIndex: initialChangeAddressIndex,
53 + mainHd: hd,
54 + sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
55 + .derivePath("m/44'/145'/0'/1"),
56 + networkType: networkType);
57 + }
58 +
59 +
60 + static Future<BitcoinCashWallet> create(
61 + {required String mnemonic,
62 + required String password,
63 + required WalletInfo walletInfo,
64 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
65 + List<BitcoinAddressRecord>? initialAddresses,
66 + ElectrumBalance? initialBalance,
67 + int initialRegularAddressIndex = 0,
68 + int initialChangeAddressIndex = 0}) async {
69 + return BitcoinCashWallet(
70 + mnemonic: mnemonic,
71 + password: password,
72 + walletInfo: walletInfo,
73 + unspentCoinsInfo: unspentCoinsInfo,
74 + initialAddresses: initialAddresses,
75 + initialBalance: initialBalance,
76 + seedBytes: await Mnemonic.toSeed(mnemonic),
77 + initialRegularAddressIndex: initialRegularAddressIndex,
78 + initialChangeAddressIndex: initialChangeAddressIndex);
79 + }
80 +
81 + static Future<BitcoinCashWallet> open({
82 + required String name,
83 + required WalletInfo walletInfo,
84 + required Box<UnspentCoinsInfo> unspentCoinsInfo,
85 + required String password,
86 + }) async {
87 + final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
88 + return BitcoinCashWallet(
89 + mnemonic: snp.mnemonic,
90 + password: password,
91 + walletInfo: walletInfo,
92 + unspentCoinsInfo: unspentCoinsInfo,
93 + initialAddresses: snp.addresses,
94 + initialBalance: snp.balance,
95 + seedBytes: await Mnemonic.toSeed(snp.mnemonic),
96 + initialRegularAddressIndex: snp.regularAddressIndex,
97 + initialChangeAddressIndex: snp.changeAddressIndex);
98 + }
99 +
100 + @override
101 + Future<PendingBitcoinCashTransaction> createTransaction(Object credentials) async {
102 + const minAmount = 546;
103 + final transactionCredentials = credentials as BitcoinTransactionCredentials;
104 + final inputs = <BitcoinUnspent>[];
105 + final outputs = transactionCredentials.outputs;
106 + final hasMultiDestination = outputs.length > 1;
107 +
108 + var allInputsAmount = 0;
109 +
110 + if (unspentCoins.isEmpty) await updateUnspent();
111 +
112 + for (final utx in unspentCoins) {
113 + if (utx.isSending) {
114 + allInputsAmount += utx.value;
115 + inputs.add(utx);
116 + }
117 + }
118 +
119 + if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
120 +
121 + final allAmountFee = transactionCredentials.feeRate != null
122 + ? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
123 + : feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
124 +
125 + final allAmount = allInputsAmount - allAmountFee;
126 +
127 + var credentialsAmount = 0;
128 + var amount = 0;
129 + var fee = 0;
130 +
131 + if (hasMultiDestination) {
132 + if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
133 + throw BitcoinTransactionWrongBalanceException(currency);
134 + }
135 +
136 + credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
137 +
138 + if (allAmount - credentialsAmount < minAmount) {
139 + throw BitcoinTransactionWrongBalanceException(currency);
140 + }
141 +
142 + amount = credentialsAmount;
143 +
144 + if (transactionCredentials.feeRate != null) {
145 + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
146 + outputsCount: outputs.length + 1);
147 + } else {
148 + fee = calculateEstimatedFee(transactionCredentials.priority, amount,
149 + outputsCount: outputs.length + 1);
150 + }
151 + } else {
152 + final output = outputs.first;
153 + credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
154 +
155 + if (credentialsAmount > allAmount) {
156 + throw BitcoinTransactionWrongBalanceException(currency);
157 + }
158 +
159 + amount = output.sendAll || allAmount - credentialsAmount < minAmount
160 + ? allAmount
161 + : credentialsAmount;
162 +
163 + if (output.sendAll || amount == allAmount) {
164 + fee = allAmountFee;
165 + } else if (transactionCredentials.feeRate != null) {
166 + fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
167 + } else {
168 + fee = calculateEstimatedFee(transactionCredentials.priority, amount);
169 + }
170 + }
171 +
172 + if (fee == 0) {
173 + throw BitcoinTransactionWrongBalanceException(currency);
174 + }
175 +
176 + final totalAmount = amount + fee;
177 +
178 + if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
179 + throw BitcoinTransactionWrongBalanceException(currency);
180 + }
181 + final txb = bitbox.Bitbox.transactionBuilder(testnet: false);
182 +
183 + final changeAddress = await walletAddresses.getChangeAddress();
184 + var leftAmount = totalAmount;
185 + var totalInputAmount = 0;
186 +
187 + inputs.clear();
188 +
189 + for (final utx in unspentCoins) {
190 + if (utx.isSending) {
191 + leftAmount = leftAmount - utx.value;
192 + totalInputAmount += utx.value;
193 + inputs.add(utx);
194 +
195 + if (leftAmount <= 0) {
196 + break;
197 + }
198 + }
199 + }
200 +
201 + if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
202 +
203 + if (amount <= 0 || totalInputAmount < totalAmount) {
204 + throw BitcoinTransactionWrongBalanceException(currency);
205 + }
206 +
207 + inputs.forEach((input) {
208 + txb.addInput(input.hash, input.vout);
209 + });
210 +
211 + outputs.forEach((item) {
212 + final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
213 + final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
214 + txb.addOutput(outputAddress, outputAmount!);
215 + });
216 +
217 + final estimatedSize = bitbox.BitcoinCash.getByteCount(inputs.length, outputs.length + 1);
218 +
219 + var feeAmount = 0;
220 +
221 + if (transactionCredentials.feeRate != null) {
222 + feeAmount = transactionCredentials.feeRate! * estimatedSize;
223 + } else {
224 + feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
225 + }
226 +
227 + final changeValue = totalInputAmount - amount - feeAmount;
228 +
229 + if (changeValue > minAmount) {
230 + txb.addOutput(changeAddress, changeValue);
231 + }
232 +
233 + for (var i = 0; i < inputs.length; i++) {
234 + final input = inputs[i];
235 + final keyPair = generateKeyPair(
236 + hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
237 + index: input.bitcoinAddressRecord.index);
238 + txb.sign(i, keyPair, input.value);
239 + }
240 +
241 + // Build the transaction
242 + final tx = txb.build();
243 +
244 + return PendingBitcoinCashTransaction(tx, type,
245 + electrumClient: electrumClient, amount: amount, fee: fee);
246 + }
247 +
248 + bitbox.ECPair generateKeyPair(
249 + {required bitcoin.HDWallet hd,
250 + required int index}) =>
251 + bitbox.ECPair.fromWIF(hd.derive(index).wif!);
252 +
253 + @override
254 + int feeAmountForPriority(
255 + BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
256 + feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
257 +
258 + int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
259 + feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
260 +
261 + int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
262 + int inputsCount = 0;
263 + int totalValue = 0;
264 +
265 + for (final input in unspentCoins) {
266 + if (input.isSending) {
267 + inputsCount++;
268 + totalValue += input.value;
269 + }
270 + if (amount != null && totalValue >= amount) {
271 + break;
272 + }
273 + }
274 +
275 + if (amount != null && totalValue < amount) return 0;
276 +
277 + final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
278 +
279 + return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
280 + }
281 +
282 + @override
283 + int feeRate(TransactionPriority priority) {
284 + if (priority is BitcoinCashTransactionPriority) {
285 + switch (priority) {
286 + case BitcoinCashTransactionPriority.slow:
287 + return 1;
288 + case BitcoinCashTransactionPriority.medium:
289 + return 5;
290 + case BitcoinCashTransactionPriority.fast:
291 + return 10;
292 + }
293 + }
294 +
295 + return 0;
296 + }
297 +}
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart new
+34
@@ -0,0 +1,34 @@
1 +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 +import 'package:cw_bitcoin/bitcoin_address_record.dart';
3 +import 'package:cw_bitcoin/electrum.dart';
4 +import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
5 +import 'package:cw_bitcoin/utils.dart';
6 +import 'package:cw_core/wallet_info.dart';
7 +import 'package:mobx/mobx.dart';
8 +
9 +part 'bitcoin_cash_wallet_addresses.g.dart';
10 +
11 +class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
12 +
13 +abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
14 + BitcoinCashWalletAddressesBase(WalletInfo walletInfo,
15 + {required bitcoin.HDWallet mainHd,
16 + required bitcoin.HDWallet sideHd,
17 + required bitcoin.NetworkType networkType,
18 + required ElectrumClient electrumClient,
19 + List<BitcoinAddressRecord>? initialAddresses,
20 + int initialRegularAddressIndex = 0,
21 + int initialChangeAddressIndex = 0})
22 + : super(walletInfo,
23 + initialAddresses: initialAddresses,
24 + initialRegularAddressIndex: initialRegularAddressIndex,
25 + initialChangeAddressIndex: initialChangeAddressIndex,
26 + mainHd: mainHd,
27 + sideHd: sideHd,
28 + electrumClient: electrumClient,
29 + networkType: networkType);
30 +
31 + @override
32 + String getAddress({required int index, required bitcoin.HDWallet hd}) =>
33 + generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
34 +}
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_creation_credentials.dart new
+26
@@ -0,0 +1,26 @@
1 +import 'package:cw_core/wallet_credentials.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +
4 +class BitcoinCashNewWalletCredentials extends WalletCredentials {
5 + BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo})
6 + : super(name: name, walletInfo: walletInfo);
7 +}
8 +
9 +class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials {
10 + BitcoinCashRestoreWalletFromSeedCredentials(
11 + {required String name,
12 + required String password,
13 + required this.mnemonic,
14 + WalletInfo? walletInfo})
15 + : super(name: name, password: password, walletInfo: walletInfo);
16 +
17 + final String mnemonic;
18 +}
19 +
20 +class BitcoinCashRestoreWalletFromWIFCredentials extends WalletCredentials {
21 + BitcoinCashRestoreWalletFromWIFCredentials(
22 + {required String name, required String password, required this.wif, WalletInfo? walletInfo})
23 + : super(name: name, password: password, walletInfo: walletInfo);
24 +
25 + final String wif;
26 +}
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart new
+107
@@ -0,0 +1,107 @@
1 +import 'dart:io';
2 +
3 +import 'package:bip39/bip39.dart';
4 +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
5 +import 'package:cw_core/balance.dart';
6 +import 'package:cw_core/pathForWallet.dart';
7 +import 'package:cw_core/transaction_history.dart';
8 +import 'package:cw_core/transaction_info.dart';
9 +import 'package:cw_core/unspent_coins_info.dart';
10 +import 'package:cw_core/wallet_base.dart';
11 +import 'package:cw_core/wallet_info.dart';
12 +import 'package:cw_core/wallet_service.dart';
13 +import 'package:cw_core/wallet_type.dart';
14 +import 'package:collection/collection.dart';
15 +import 'package:hive/hive.dart';
16 +
17 +class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
18 + BitcoinCashRestoreWalletFromSeedCredentials,
19 + BitcoinCashRestoreWalletFromWIFCredentials> {
20 + BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
21 +
22 + final Box<WalletInfo> walletInfoSource;
23 + final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
24 +
25 + @override
26 + WalletType getType() => WalletType.bitcoinCash;
27 +
28 + @override
29 + Future<bool> isWalletExit(String name) async =>
30 + File(await pathForWallet(name: name, type: getType())).existsSync();
31 +
32 + @override
33 + Future<BitcoinCashWallet> create(
34 + credentials) async {
35 + final wallet = await BitcoinCashWalletBase.create(
36 + mnemonic: await Mnemonic.generate(),
37 + password: credentials.password!,
38 + walletInfo: credentials.walletInfo!,
39 + unspentCoinsInfo: unspentCoinsInfoSource);
40 + await wallet.save();
41 + await wallet.init();
42 + return wallet;
43 + }
44 +
45 + @override
46 + Future<BitcoinCashWallet> openWallet(String name, String password) async {
47 + final walletInfo = walletInfoSource.values.firstWhereOrNull(
48 + (info) => info.id == WalletBase.idFor(name, getType()))!;
49 + final wallet = await BitcoinCashWalletBase.open(
50 + password: password, name: name, walletInfo: walletInfo,
51 + unspentCoinsInfo: unspentCoinsInfoSource);
52 + await wallet.init();
53 + return wallet;
54 + }
55 +
56 + @override
57 + Future<void> remove(String wallet) async {
58 + File(await pathForWalletDir(name: wallet, type: getType()))
59 + .delete(recursive: true);
60 + final walletInfo = walletInfoSource.values.firstWhereOrNull(
61 + (info) => info.id == WalletBase.idFor(wallet, getType()))!;
62 + await walletInfoSource.delete(walletInfo.key);
63 + }
64 +
65 + @override
66 + Future<void> rename(String currentName, String password, String newName) async {
67 + final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
68 + (info) => info.id == WalletBase.idFor(currentName, getType()))!;
69 + final currentWallet = await BitcoinCashWalletBase.open(
70 + password: password,
71 + name: currentName,
72 + walletInfo: currentWalletInfo,
73 + unspentCoinsInfo: unspentCoinsInfoSource);
74 +
75 + await currentWallet.renameWalletFiles(newName);
76 +
77 + final newWalletInfo = currentWalletInfo;
78 + newWalletInfo.id = WalletBase.idFor(newName, getType());
79 + newWalletInfo.name = newName;
80 +
81 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
82 + }
83 +
84 + @override
85 + Future<BitcoinCashWallet>
86 + restoreFromKeys(credentials) {
87 + // TODO: implement restoreFromKeys
88 + throw UnimplementedError('restoreFromKeys() is not implemented');
89 + }
90 +
91 + @override
92 + Future<BitcoinCashWallet> restoreFromSeed(
93 + BitcoinCashRestoreWalletFromSeedCredentials credentials) async {
94 + if (!validateMnemonic(credentials.mnemonic)) {
95 + throw BitcoinCashMnemonicIsIncorrectException();
96 + }
97 +
98 + final wallet = await BitcoinCashWalletBase.create(
99 + password: credentials.password!,
100 + mnemonic: credentials.mnemonic,
101 + walletInfo: credentials.walletInfo!,
102 + unspentCoinsInfo: unspentCoinsInfoSource);
103 + await wallet.save();
104 + await wallet.init();
105 + return wallet;
106 + }
107 +}
cw_bitcoin_cash/lib/src/exceptions/bitcoin_cash_mnemonic_is_incorrect_exception.dart new
+5
@@ -0,0 +1,5 @@
1 +class BitcoinCashMnemonicIsIncorrectException implements Exception {
2 + @override
3 + String toString() =>
4 + 'Bitcoin Cash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 +}
cw_bitcoin_cash/lib/src/exceptions/exceptions.dart new
+1
@@ -0,0 +1 @@
1 +export 'bitcoin_cash_mnemonic_is_incorrect_exception.dart';
\ No newline at end of file
cw_bitcoin_cash/lib/src/mnemonic.dart new
+11
@@ -0,0 +1,11 @@
1 +import 'dart:typed_data';
2 +
3 +import 'package:bip39/bip39.dart' as bip39;
4 +
5 +class Mnemonic {
6 + /// Generate bip39 mnemonic
7 + static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
8 +
9 + /// Create root seed from mnemonic
10 + static Uint8List toSeed(String mnemonic) => bip39.mnemonicToSeed(mnemonic);
11 +}
cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart new
+62
@@ -0,0 +1,62 @@
1 +import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
2 +import 'package:bitbox/bitbox.dart' as bitbox;
3 +import 'package:cw_core/pending_transaction.dart';
4 +import 'package:cw_bitcoin/electrum.dart';
5 +import 'package:cw_bitcoin/bitcoin_amount_format.dart';
6 +import 'package:cw_bitcoin/electrum_transaction_info.dart';
7 +import 'package:cw_core/transaction_direction.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 +
10 +class PendingBitcoinCashTransaction with PendingTransaction {
11 + PendingBitcoinCashTransaction(this._tx, this.type,
12 + {required this.electrumClient,
13 + required this.amount,
14 + required this.fee})
15 + : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
16 +
17 + final WalletType type;
18 + final bitbox.Transaction _tx;
19 + final ElectrumClient electrumClient;
20 + final int amount;
21 + final int fee;
22 +
23 + @override
24 + String get id => _tx.getId();
25 +
26 + @override
27 + String get hex => _tx.toHex();
28 +
29 + @override
30 + String get amountFormatted => bitcoinAmountToString(amount: amount);
31 +
32 + @override
33 + String get feeFormatted => bitcoinAmountToString(amount: fee);
34 +
35 + final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
36 +
37 + @override
38 + Future<void> commit() async {
39 + final result =
40 + await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
41 +
42 + if (result.isEmpty) {
43 + throw BitcoinCommitTransactionException();
44 + }
45 +
46 + _listeners?.forEach((listener) => listener(transactionInfo()));
47 + }
48 +
49 + void addListener(
50 + void Function(ElectrumTransactionInfo transaction) listener) =>
51 + _listeners.add(listener);
52 +
53 + ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
54 + id: id,
55 + height: 0,
56 + amount: amount,
57 + direction: TransactionDirection.outgoing,
58 + date: DateTime.now(),
59 + isPending: true,
60 + confirmations: 0,
61 + fee: fee);
62 +}
cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux new
+1
@@ -0,0 +1 @@
1 +C:/Users/borod/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.0/
\ No newline at end of file
cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc new
+11
@@ -0,0 +1,11 @@
1 +//
2 +// Generated file. Do not edit.
3 +//
4 +
5 +// clang-format off
6 +
7 +#include "generated_plugin_registrant.h"
8 +
9 +
10 +void fl_register_plugins(FlPluginRegistry* registry) {
11 +}
cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h new
+15
@@ -0,0 +1,15 @@
1 +//
2 +// Generated file. Do not edit.
3 +//
4 +
5 +// clang-format off
6 +
7 +#ifndef GENERATED_PLUGIN_REGISTRANT_
8 +#define GENERATED_PLUGIN_REGISTRANT_
9 +
10 +#include <flutter_linux/flutter_linux.h>
11 +
12 +// Registers Flutter plugins.
13 +void fl_register_plugins(FlPluginRegistry* registry);
14 +
15 +#endif // GENERATED_PLUGIN_REGISTRANT_
cw_bitcoin_cash/linux/flutter/generated_plugins.cmake new
+23
@@ -0,0 +1,23 @@
1 +#
2 +# Generated file, do not edit.
3 +#
4 +
5 +list(APPEND FLUTTER_PLUGIN_LIST
6 +)
7 +
8 +list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 +)
10 +
11 +set(PLUGIN_BUNDLED_LIBRARIES)
12 +
13 +foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
15 + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 + list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
17 + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 +endforeach(plugin)
19 +
20 +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
22 + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 +endforeach(ffi_plugin)
cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift new
+12
@@ -0,0 +1,12 @@
1 +//
2 +// Generated file. Do not edit.
3 +//
4 +
5 +import FlutterMacOS
6 +import Foundation
7 +
8 +import path_provider_foundation
9 +
10 +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
11 + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
12 +}
cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new
+11
@@ -0,0 +1,11 @@
1 +// This is a generated file; do not edit or check into version control.
2 +FLUTTER_ROOT=C:\Users\borod\flutter
3 +FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash
4 +COCOAPODS_PARALLEL_CODE_SIGN=true
5 +FLUTTER_BUILD_DIR=build
6 +FLUTTER_BUILD_NAME=0.0.1
7 +FLUTTER_BUILD_NUMBER=0.0.1
8 +DART_OBFUSCATION=false
9 +TRACK_WIDGET_CREATION=true
10 +TREE_SHAKE_ICONS=false
11 +PACKAGE_CONFIG=.dart_tool/package_config.json
cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh new
+12
@@ -0,0 +1,12 @@
1 +#!/bin/sh
2 +# This is a generated file; do not edit or check into version control.
3 +export "FLUTTER_ROOT=C:\Users\borod\flutter"
4 +export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash"
5 +export "COCOAPODS_PARALLEL_CODE_SIGN=true"
6 +export "FLUTTER_BUILD_DIR=build"
7 +export "FLUTTER_BUILD_NAME=0.0.1"
8 +export "FLUTTER_BUILD_NUMBER=0.0.1"
9 +export "DART_OBFUSCATION=false"
10 +export "TRACK_WIDGET_CREATION=true"
11 +export "TREE_SHAKE_ICONS=false"
12 +export "PACKAGE_CONFIG=.dart_tool/package_config.json"
cw_bitcoin_cash/pubspec.yaml new
+76
@@ -0,0 +1,76 @@
1 +name: cw_bitcoin_cash
2 +description: A new Flutter package project.
3 +version: 0.0.1
4 +publish_to: none
5 +author: Cake Wallet
6 +homepage: https://cakewallet.com
7 +
8 +environment:
9 + sdk: '>=2.19.0 <3.0.0'
10 + flutter: ">=1.17.0"
11 +
12 +dependencies:
13 + flutter:
14 + sdk: flutter
15 + bip39: ^1.0.6
16 + bip32: ^2.0.0
17 + path_provider: ^2.0.11
18 + mobx: ^2.0.7+4
19 + flutter_mobx: ^2.0.6+1
20 + cw_core:
21 + path: ../cw_core
22 + cw_bitcoin:
23 + path: ../cw_bitcoin
24 + bitcoin_flutter:
25 + git:
26 + url: https://github.com/cake-tech/bitcoin_flutter.git
27 + ref: cake-update-v3
28 + bitbox:
29 + git:
30 + url: https://github.com/cake-tech/bitbox-flutter.git
31 + ref: master
32 +
33 +
34 +
35 +dev_dependencies:
36 + flutter_test:
37 + sdk: flutter
38 + build_runner: ^2.1.11
39 + mobx_codegen: ^2.0.7
40 + hive_generator: ^1.1.3
41 +
42 +# For information on the generic Dart part of this file, see the
43 +# following page: https://dart.dev/tools/pub/pubspec
44 +
45 +# The following section is specific to Flutter packages.
46 +flutter:
47 +
48 +# To add assets to your package, add an assets section, like this:
49 +# assets:
50 +# - images/a_dot_burr.jpeg
51 +# - images/a_dot_ham.jpeg
52 +#
53 +# For details regarding assets in packages, see
54 +# https://flutter.dev/assets-and-images/#from-packages
55 +#
56 +# An image asset can refer to one or more resolution-specific "variants", see
57 +# https://flutter.dev/assets-and-images/#resolution-aware
58 +
59 +# To add custom fonts to your package, add a fonts section here,
60 +# in this "flutter" section. Each entry in this list should have a
61 +# "family" key with the font family name, and a "fonts" key with a
62 +# list giving the asset and other descriptors for the font. For
63 +# example:
64 +# fonts:
65 +# - family: Schyler
66 +# fonts:
67 +# - asset: fonts/Schyler-Regular.ttf
68 +# - asset: fonts/Schyler-Italic.ttf
69 +# style: italic
70 +# - family: Trajan Pro
71 +# fonts:
72 +# - asset: fonts/TrajanPro.ttf
73 +# - asset: fonts/TrajanPro_Bold.ttf
74 +# weight: 700
75 +#
76 +
cw_bitcoin_cash/test/cw_bitcoin_cash_test.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:flutter_test/flutter_test.dart';
2 +
3 +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
4 +
5 +void main() {
6 + test('adds one to input values', () {
7 + final calculator = Calculator();
8 + expect(calculator.addOne(2), 3);
9 + expect(calculator.addOne(-7), -6);
10 + expect(calculator.addOne(0), 1);
11 + });
12 +}
cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc new
+11
@@ -0,0 +1,11 @@
1 +//
2 +// Generated file. Do not edit.
3 +//
4 +
5 +// clang-format off
6 +
7 +#include "generated_plugin_registrant.h"
8 +
9 +
10 +void RegisterPlugins(flutter::PluginRegistry* registry) {
11 +}
cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h new
+15
@@ -0,0 +1,15 @@
1 +//
2 +// Generated file. Do not edit.
3 +//
4 +
5 +// clang-format off
6 +
7 +#ifndef GENERATED_PLUGIN_REGISTRANT_
8 +#define GENERATED_PLUGIN_REGISTRANT_
9 +
10 +#include <flutter/plugin_registry.h>
11 +
12 +// Registers Flutter plugins.
13 +void RegisterPlugins(flutter::PluginRegistry* registry);
14 +
15 +#endif // GENERATED_PLUGIN_REGISTRANT_
cw_bitcoin_cash/windows/flutter/generated_plugins.cmake new
+23
@@ -0,0 +1,23 @@
1 +#
2 +# Generated file, do not edit.
3 +#
4 +
5 +list(APPEND FLUTTER_PLUGIN_LIST
6 +)
7 +
8 +list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 +)
10 +
11 +set(PLUGIN_BUNDLED_LIBRARIES)
12 +
13 +foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
15 + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 + list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
17 + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 +endforeach(plugin)
19 +
20 +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
22 + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 +endforeach(ffi_plugin)
cw_core/lib/amount_converter.dart
+1
@@ -80,6 +80,7 @@ class AmountConverter {
80 case CryptoCurrency.xmr:
81 return _moneroAmountToString(amount);
82 case CryptoCurrency.btc:
83 + case CryptoCurrency.bch:
84 return _bitcoinAmountToString(amount);
85 case CryptoCurrency.xhv:
86 case CryptoCurrency.xag:
cw_core/lib/currency_for_wallet_type.dart
+2
@@ -13,6 +13,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
13 return CryptoCurrency.xhv;
14 case WalletType.ethereum:
15 return CryptoCurrency.eth;
16 + case WalletType.bitcoinCash:
17 + return CryptoCurrency.bch;
18 case WalletType.nano:
19 return CryptoCurrency.nano;
20 case WalletType.banano:
cw_core/lib/node.dart
+4
@@ -78,6 +78,8 @@ class Node extends HiveObject with Keyable {
78 return Uri.http(uriRaw, '');
79 case WalletType.ethereum:
80 return Uri.https(uriRaw, '');
81 + case WalletType.bitcoinCash:
82 + return createUriFromElectrumAddress(uriRaw);
83 case WalletType.nano:
84 case WalletType.banano:
85 if (isSSL) {
@@ -138,6 +140,8 @@ class Node extends HiveObject with Keyable {
140 return requestMoneroNode();
141 case WalletType.ethereum:
142 return requestElectrumServer();
143 + case WalletType.bitcoinCash:
144 + return requestElectrumServer();
145 case WalletType.nano:
146 case WalletType.banano:
147 return requestNanoNode();
cw_core/lib/unspent_transaction_output.dart renamed
cw_core/lib/wallet_type.dart
+15
@@ -10,6 +10,7 @@ const walletTypes = [
10 WalletType.litecoin,
11 WalletType.haven,
12 WalletType.ethereum,
13 + WalletType.bitcoinCash,
14 WalletType.nano,
15 WalletType.banano,
16 ];
@@ -39,6 +40,10 @@ enum WalletType {
40
41 @HiveField(7)
42 banano,
43 +
44 + @HiveField(8)
45 + bitcoinCash,
46 +
47 }
48
49 int serializeToInt(WalletType type) {
@@ -57,6 +62,8 @@ int serializeToInt(WalletType type) {
62 return 5;
63 case WalletType.banano:
64 return 6;
65 + case WalletType.bitcoinCash:
66 + return 7;
67 default:
68 return -1;
69 }
@@ -78,6 +85,8 @@ WalletType deserializeFromInt(int raw) {
85 return WalletType.nano;
86 case 6:
87 return WalletType.banano;
88 + case 7:
89 + return WalletType.bitcoinCash;
90 default:
91 throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
92 }
@@ -95,6 +104,8 @@ String walletTypeToString(WalletType type) {
104 return 'Haven';
105 case WalletType.ethereum:
106 return 'Ethereum';
107 + case WalletType.bitcoinCash:
108 + return 'Bitcoin Cash';
109 case WalletType.nano:
110 return 'Nano';
111 case WalletType.banano:
@@ -116,6 +127,8 @@ String walletTypeToDisplayName(WalletType type) {
127 return 'Haven (XHV)';
128 case WalletType.ethereum:
129 return 'Ethereum (ETH)';
130 + case WalletType.bitcoinCash:
131 + return 'Bitcoin Cash (BCH)';
132 case WalletType.nano:
133 return 'Nano (XNO)';
134 case WalletType.banano:
@@ -137,6 +150,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
150 return CryptoCurrency.xhv;
151 case WalletType.ethereum:
152 return CryptoCurrency.eth;
153 + case WalletType.bitcoinCash:
154 + return CryptoCurrency.bch;
155 case WalletType.nano:
156 return CryptoCurrency.nano;
157 case WalletType.banano:
lib/bitcoin/cw_bitcoin.dart
+3 -9
@@ -44,6 +44,7 @@ class CWBitcoin extends Bitcoin {
44 List<TransactionPriority> getTransactionPriorities()
45 => BitcoinTransactionPriority.all;
46
47 + @override
48 List<TransactionPriority> getLitecoinTransactionPriorities()
49 => LitecoinTransactionPriority.all;
50
@@ -121,16 +122,9 @@ class CWBitcoin extends Bitcoin {
122 => (priority as BitcoinTransactionPriority).labelWithRate(rate);
123
124 @override
124 - List<Unspent> getUnspents(Object wallet) {
125 + List<BitcoinUnspent> getUnspents(Object wallet) {
126 final bitcoinWallet = wallet as ElectrumWallet;
126 - return bitcoinWallet.unspentCoins
127 - .map((BitcoinUnspent bitcoinUnspent) => Unspent(
128 - bitcoinUnspent.address.address,
129 - bitcoinUnspent.hash,
130 - bitcoinUnspent.value,
131 - bitcoinUnspent.vout,
132 - null))
133 - .toList();
127 + return bitcoinWallet.unspentCoins;
128 }
129
130 void updateUnspents(Object wallet) async {
lib/bitcoin_cash/cw_bitcoin_cash.dart new
+45
@@ -0,0 +1,45 @@
1 +part of 'bitcoin_cash.dart';
2 +
3 +class CWBitcoinCash extends BitcoinCash {
4 + @override
5 + String getMnemonic(int? strength) => Mnemonic.generate();
6 +
7 + @override
8 + Uint8List getSeedFromMnemonic(String seed) => Mnemonic.toSeed(seed);
9 +
10 + @override
11 + String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address);
12 +
13 + @override
14 + WalletService createBitcoinCashWalletService(
15 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
16 + return BitcoinCashWalletService(walletInfoSource, unspentCoinSource);
17 + }
18 +
19 + @override
20 + WalletCredentials createBitcoinCashNewWalletCredentials({
21 + required String name,
22 + WalletInfo? walletInfo,
23 + }) =>
24 + BitcoinCashNewWalletCredentials(name: name, walletInfo: walletInfo);
25 +
26 + @override
27 + WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
28 + {required String name, required String mnemonic, required String password}) =>
29 + BitcoinCashRestoreWalletFromSeedCredentials(
30 + name: name, mnemonic: mnemonic, password: password);
31 +
32 + @override
33 + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>
34 + BitcoinCashTransactionPriority.deserialize(raw: raw);
35 +
36 + @override
37 + TransactionPriority getDefaultTransactionPriority() => BitcoinCashTransactionPriority.medium;
38 +
39 + @override
40 + List<TransactionPriority> getTransactionPriorities() => BitcoinCashTransactionPriority.all;
41 +
42 + @override
43 + TransactionPriority getBitcoinCashTransactionPrioritySlow() =>
44 + BitcoinCashTransactionPriority.slow;
45 +}
lib/buy/onramper/onramper_buy_provider.dart
+2
@@ -27,6 +27,8 @@ class OnRamperBuyProvider {
27 return "LTC_LITECOIN";
28 case CryptoCurrency.xmr:
29 return "XMR_MONERO";
30 + case CryptoCurrency.bch:
31 + return "BCH_BITCOINCASH";
32 case CryptoCurrency.nano:
33 return "XNO_NANO";
34 default:
lib/core/address_validator.dart
+9
@@ -88,7 +88,9 @@ class AddressValidator extends TextValidator {
88 case CryptoCurrency.dai:
89 case CryptoCurrency.dash:
90 case CryptoCurrency.eos:
91 + return '[0-9a-zA-Z]';
92 case CryptoCurrency.bch:
93 + 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}\$';
94 case CryptoCurrency.bnb:
95 return '[0-9a-zA-Z]';
96 case CryptoCurrency.ltc:
@@ -172,7 +174,9 @@ class AddressValidator extends TextValidator {
174 case CryptoCurrency.steth:
175 case CryptoCurrency.shib:
176 case CryptoCurrency.avaxc:
177 + return [42];
178 case CryptoCurrency.bch:
179 + return [42, 43, 44, 54, 55];
180 case CryptoCurrency.bnb:
181 return [42];
182 case CryptoCurrency.ltc:
@@ -271,6 +275,11 @@ class AddressValidator extends TextValidator {
275 return 'nano_[0-9a-zA-Z]{60}';
276 case CryptoCurrency.banano:
277 return 'ban_[0-9a-zA-Z]{60}';
278 + case CryptoCurrency.bch:
279 + return 'bitcoincash:q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
280 + '|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'
281 + '|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
282 + '|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
283 default:
284 return null;
285 }
lib/core/seed_validator.dart
+2
@@ -29,6 +29,8 @@ class SeedValidator extends Validator<MnemonicItem> {
29 return haven!.getMoneroWordList(language);
30 case WalletType.ethereum:
31 return ethereum!.getEthereumWordList(language);
32 + case WalletType.bitcoinCash:
33 + return getBitcoinWordList(language);
34 case WalletType.nano:
35 case WalletType.banano:
36 return nano!.getNanoWordList(language);
lib/di.dart
+3
@@ -2,6 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart';
2 import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
3 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
4 import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
5 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
6 import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
7 import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
8 import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
@@ -820,6 +821,8 @@ Future<void> setup({
821 return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
822 case WalletType.ethereum:
823 return ethereum!.createEthereumWalletService(_walletInfoSource);
824 + case WalletType.bitcoinCash:
825 + return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
826 case WalletType.nano:
827 return nano!.createNanoWalletService(_walletInfoSource);
828 default:
lib/entities/default_settings_migration.dart
+67 -20
@@ -26,6 +26,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
26 const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
27 const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
28 const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
29 +const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
30 const nanoDefaultNodeUri = 'rpc.nano.to';
31 const nanoDefaultPowNodeUri = 'rpc.nano.to';
32
@@ -81,7 +82,10 @@ Future<void> defaultSettingsMigration(
82 sharedPreferences: sharedPreferences, nodes: nodes);
83 await changeLitecoinCurrentElectrumServerToDefault(
84 sharedPreferences: sharedPreferences, nodes: nodes);
84 - await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
85 + await changeHavenCurrentNodeToDefault(
86 + sharedPreferences: sharedPreferences, nodes: nodes);
87 + await changeBitcoinCashCurrentNodeToDefault(
88 + sharedPreferences: sharedPreferences, nodes: nodes);
89
90 break;
91 case 2:
@@ -166,6 +170,11 @@ Future<void> defaultSettingsMigration(
170 await changeNanoCurrentPowNodeToDefault(
171 sharedPreferences: sharedPreferences, nodes: powNodes);
172 break;
173 + case 23:
174 + await addBitcoinCashElectrumServerList(nodes: nodes);
175 + await changeBitcoinCurrentElectrumServerToDefault(
176 + sharedPreferences: sharedPreferences, nodes: nodes);
177 + break;
178
179 default:
180 break;
@@ -323,6 +332,12 @@ Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
332 nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
333 }
334
335 +Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
336 + return nodes.values.firstWhereOrNull(
337 + (Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri)
338 + ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
339 +}
340 +
341 Node getMoneroDefaultNode({required Box<Node> nodes}) {
342 final timeZone = DateTime.now().timeZoneOffset.inHours;
343 var nodeUri = '';
@@ -358,6 +373,15 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
373 await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId);
374 }
375
376 +Future<void> changeBitcoinCashCurrentNodeToDefault(
377 + {required SharedPreferences sharedPreferences,
378 + required Box<Node> nodes}) async {
379 + final server = getBitcoinCashDefaultElectrumServer(nodes: nodes);
380 + final serverId = server?.key as int ?? 0;
381 +
382 + await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, serverId);
383 +}
384 +
385 Future<void> changeHavenCurrentNodeToDefault(
386 {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
387 final node = getHavenDefaultNode(nodes: nodes);
@@ -411,6 +435,15 @@ Future<void> addLitecoinElectrumServerList({required Box<Node> nodes}) async {
435 }
436 }
437
438 +Future<void> addBitcoinCashElectrumServerList({required Box<Node> nodes}) async {
439 + final serverList = await loadBitcoinCashElectrumServerList();
440 + for (var node in serverList) {
441 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
442 + await nodes.add(node);
443 + }
444 + }
445 +}
446 +
447 Future<void> addHavenNodeList({required Box<Node> nodes}) async {
448 final nodeList = await loadDefaultHavenNodes();
449 for (var node in nodeList) {
@@ -497,27 +530,34 @@ Future<void> checkCurrentNodes(
530 final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
531 final currentBitcoinElectrumSeverId =
532 sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
500 - final currentLitecoinElectrumSeverId =
501 - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
502 - final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
503 - final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
504 - final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
505 - final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
506 - final currentMoneroNode =
507 - nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
508 - final currentBitcoinElectrumServer =
509 - nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
510 - final currentLitecoinElectrumServer =
511 - nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
512 - final currentHavenNodeServer =
513 - nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
514 - final currentEthereumNodeServer =
515 - nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
533 + final currentLitecoinElectrumSeverId = sharedPreferences
534 + .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
535 + final currentHavenNodeId = sharedPreferences
536 + .getInt(PreferencesKey.currentHavenNodeIdKey);
537 + final currentEthereumNodeId = sharedPreferences
538 + .getInt(PreferencesKey.currentEthereumNodeIdKey);
539 + final currentNanoNodeId = sharedPreferences
540 + .getInt(PreferencesKey.currentNanoNodeIdKey);
541 + final currentNanoPowNodeId = sharedPreferences
542 + .getInt(PreferencesKey.currentNanoPowNodeIdKey);
543 + final currentBitcoinCashNodeId = sharedPreferences
544 + .getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
545 + final currentMoneroNode = nodeSource.values.firstWhereOrNull(
546 + (node) => node.key == currentMoneroNodeId);
547 + final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
548 + (node) => node.key == currentBitcoinElectrumSeverId);
549 + final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull(
550 + (node) => node.key == currentLitecoinElectrumSeverId);
551 + final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
552 + (node) => node.key == currentHavenNodeId);
553 + final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
554 + (node) => node.key == currentEthereumNodeId);
555 final currentNanoNodeServer =
517 - nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
556 + nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
557 final currentNanoPowNodeServer =
519 - powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
520 -
558 + powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
559 + final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull(
560 + (node) => node.key == currentBitcoinCashNodeId);
561 if (currentMoneroNode == null) {
562 final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
563 await nodeSource.add(newCakeWalletNode);
@@ -565,6 +605,13 @@ Future<void> checkCurrentNodes(
605 }
606 await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
607 }
608 +
609 + if (currentBitcoinCashNodeServer == null) {
610 + final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash);
611 + await nodeSource.add(node);
612 + await sharedPreferences.setInt(
613 + PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
614 + }
615 }
616
617 Future<void> resetBitcoinElectrumServer(
lib/entities/main_actions.dart
+2
@@ -52,6 +52,7 @@ class MainActions {
52 case WalletType.bitcoin:
53 case WalletType.litecoin:
54 case WalletType.ethereum:
55 + case WalletType.bitcoinCash:
56 case WalletType.nano:
57 case WalletType.banano:
58 switch (defaultBuyProvider) {
@@ -123,6 +124,7 @@ class MainActions {
124 case WalletType.bitcoin:
125 case WalletType.litecoin:
126 case WalletType.ethereum:
127 + case WalletType.bitcoinCash:
128 if (viewModel.isEnabledSellAction) {
129 final moonPaySellProvider = MoonPaySellProvider();
130 final uri = await moonPaySellProvider.requestUrl(
lib/entities/node_list.dart
+21 -2
@@ -84,6 +84,23 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
84 return nodes;
85 }
86
87 +Future<List<Node>> loadBitcoinCashElectrumServerList() async {
88 + final serverListRaw =
89 + await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml');
90 + final loadedServerList = loadYaml(serverListRaw) as YamlList;
91 + final serverList = <Node>[];
92 +
93 + for (final raw in loadedServerList) {
94 + if (raw is Map) {
95 + final node = Node.fromMap(Map<String, Object>.from(raw));
96 + node.type = WalletType.bitcoinCash;
97 + serverList.add(node);
98 + }
99 + }
100 +
101 + return serverList;
102 +}
103 +
104 Future<List<Node>> loadDefaultNanoNodes() async {
105 final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
106 final loadedNodes = loadYaml(nodesRaw) as YamlList;
@@ -116,10 +133,11 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
133 return nodes;
134 }
135
119 -Future resetToDefault(Box<Node> nodeSource) async {
136 +Future<void> resetToDefault(Box<Node> nodeSource) async {
137 final moneroNodes = await loadDefaultNodes();
138 final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
139 final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
140 + final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList();
141 final havenNodes = await loadDefaultHavenNodes();
142 final ethereumNodes = await loadDefaultEthereumNodes();
143 final nanoNodes = await loadDefaultNanoNodes();
@@ -129,13 +147,14 @@ Future resetToDefault(Box<Node> nodeSource) async {
147 litecoinElectrumServerList +
148 havenNodes +
149 ethereumNodes +
150 + bitcoinCashElectrumServerList +
151 nanoNodes;
152
153 await nodeSource.clear();
154 await nodeSource.addAll(nodes);
155 }
156
138 -Future resetPowToDefault(Box<Node> powNodeSource) async {
157 +Future<void> resetPowToDefault(Box<Node> powNodeSource) async {
158 final nanoPowNodes = await loadDefaultNanoPowNodes();
159 final nodes = nanoPowNodes;
160 await powNodeSource.clear();
lib/entities/preferences_key.dart
+2
@@ -11,6 +11,7 @@ class PreferencesKey {
11 static const currentBananoNodeIdKey = 'current_node_id_banano';
12 static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
13 static const currentFiatCurrencyKey = 'current_fiat_currency';
14 + static const currentBitcoinCashNodeIdKey = 'current_node_id_bch';
15 static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
16 static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
17 static const shouldSaveRecipientAddressKey = 'save_recipient_address';
@@ -36,6 +37,7 @@ class PreferencesKey {
37 static const havenTransactionPriority = 'current_fee_priority_haven';
38 static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
39 static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
40 + static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
41 static const shouldShowReceiveWarning = 'should_show_receive_warning';
42 static const shouldShowYatPopup = 'should_show_yat_popup';
43 static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
lib/entities/priority_for_wallet_type.dart
+3
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/haven/haven.dart';
5 import 'package:cake_wallet/monero/monero.dart';
@@ -17,6 +18,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
18 return haven!.getTransactionPriorities();
19 case WalletType.ethereum:
20 return ethereum!.getTransactionPriorities();
21 + case WalletType.bitcoinCash:
22 + return bitcoinCash!.getTransactionPriorities();
23 // no such thing for nano/banano:
24 case WalletType.nano:
25 case WalletType.banano:
lib/ethereum/cw_ethereum.dart
+3
@@ -50,6 +50,9 @@ class CWEthereum extends Ethereum {
50 @override
51 TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
52
53 + @override
54 + TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow;
55 +
56 @override
57 List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;
58
lib/main.dart
+1 -1
@@ -159,7 +159,7 @@ Future<void> initializeAppConfigs() async {
159 transactionDescriptions: transactionDescriptions,
160 secureStorage: secureStorage,
161 anonpayInvoiceInfo: anonpayInvoiceInfo,
162 - initialMigrationVersion: 22);
162 + initialMigrationVersion: 23);
163 }
164
165 Future<void> initialSetup(
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+3
@@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
33 final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
34 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
35 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
36 + final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
37 final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
38 final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
39 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
@@ -143,6 +144,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
144 return havenIcon;
145 case WalletType.ethereum:
146 return ethereumIcon;
147 + case WalletType.bitcoinCash:
148 + return bitcoinCashIcon;
149 case WalletType.nano:
150 return nanoIcon;
151 case WalletType.banano:
lib/src/screens/dashboard/widgets/menu_widget.dart
+5 -2
@@ -31,7 +31,8 @@ class MenuWidgetState extends State<MenuWidget> {
31 this.havenIcon = Image.asset('assets/images/haven_menu.png'),
32 this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
33 this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
34 - this.bananoIcon = Image.asset('assets/images/nano_icon.png');
34 + this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
35 + this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png');
36
37
38 final largeScreen = 731;
@@ -50,10 +51,10 @@ class MenuWidgetState extends State<MenuWidget> {
51 Image litecoinIcon;
52 Image havenIcon;
53 Image ethereumIcon;
54 + Image bitcoinCashIcon;
55 Image nanoIcon;
56 Image bananoIcon;
57
56 -
58 @override
59 void initState() {
60 menuWidth = 0;
@@ -212,6 +213,8 @@ class MenuWidgetState extends State<MenuWidget> {
213 return havenIcon;
214 case WalletType.ethereum:
215 return ethereumIcon;
216 + case WalletType.bitcoinCash:
217 + return bitcoinCashIcon;
218 case WalletType.nano:
219 return nanoIcon;
220 case WalletType.banano:
lib/src/screens/seed/pre_seed_page.dart
+1
@@ -73,6 +73,7 @@ class PreSeedPage extends BasePage {
73 case WalletType.monero:
74 return 25;
75 case WalletType.ethereum:
76 + case WalletType.bitcoinCash:
77 return 12;
78 default:
79 return 24;
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
+6 -1
@@ -1,8 +1,10 @@
1 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
2 import 'package:cake_wallet/routes.dart';
3 import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
4 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
5 import 'package:cake_wallet/utils/show_pop_up.dart';
6 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
7 +import 'package:cw_core/wallet_type.dart';
8 import 'package:flutter/material.dart';
9 import 'package:flutter/cupertino.dart';
10 import 'package:cake_wallet/src/screens/base_page.dart';
@@ -79,6 +81,9 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
81 itemBuilder: (_, int index) {
82 return Observer(builder: (_) {
83 final item = unspentCoinsListViewModel.items[index];
84 + final address = unspentCoinsListViewModel.wallet.type == WalletType.bitcoinCash
85 + ? bitcoinCash!.getCashAddrFormat(item.address)
86 + : item.address;
87
88 return GestureDetector(
89 onTap: () =>
@@ -88,7 +93,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
93 child: UnspentCoinsListItem(
94 note: item.note,
95 amount: item.amount,
91 - address: item.address,
96 + address: address,
97 isSending: item.isSending,
98 isFrozen: item.isFrozen,
99 onCheckBoxTap: item.isFrozen
lib/src/screens/wallet_list/wallet_list_page.dart
+3
@@ -48,6 +48,7 @@ class WalletListBodyState extends State<WalletListBody> {
48 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
49 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
50 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
51 + final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
52 final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
53 final scrollController = ScrollController();
54 final double tileHeight = 60;
@@ -243,6 +244,8 @@ class WalletListBodyState extends State<WalletListBody> {
244 return havenIcon;
245 case WalletType.ethereum:
246 return ethereumIcon;
247 + case WalletType.bitcoinCash:
248 + return bitcoinCashIcon;
249 case WalletType.nano:
250 return nanoIcon;
251 default:
lib/store/settings_store.dart
+119 -78
@@ -1,6 +1,7 @@
1 import 'dart:io';
2
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
5 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
6 import 'package:cake_wallet/entities/buy_provider_types.dart';
7 import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
@@ -85,7 +86,8 @@ abstract class SettingsStoreBase with Store {
86 TransactionPriority? initialMoneroTransactionPriority,
87 TransactionPriority? initialHavenTransactionPriority,
88 TransactionPriority? initialLitecoinTransactionPriority,
88 - TransactionPriority? initialEthereumTransactionPriority})
89 + TransactionPriority? initialEthereumTransactionPriority,
90 + TransactionPriority? initialBitcoinCashTransactionPriority})
91 : nodes = ObservableMap<WalletType, Node>.of(nodes),
92 powNodes = ObservableMap<WalletType, Node>.of(powNodes),
93 _sharedPreferences = sharedPreferences,
@@ -146,6 +148,10 @@ abstract class SettingsStoreBase with Store {
148 priority[WalletType.ethereum] = initialEthereumTransactionPriority;
149 }
150
151 + if (initialBitcoinCashTransactionPriority != null) {
152 + priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
153 + }
154 +
155 reaction(
156 (_) => fiatCurrency,
157 (FiatCurrency fiatCurrency) => sharedPreferences.setString(
@@ -174,6 +180,9 @@ abstract class SettingsStoreBase with Store {
180 case WalletType.ethereum:
181 key = PreferencesKey.ethereumTransactionPriority;
182 break;
183 + case WalletType.bitcoinCash:
184 + key = PreferencesKey.bitcoinCashTransactionPriority;
185 + break;
186 default:
187 key = null;
188 }
@@ -526,12 +535,13 @@ abstract class SettingsStoreBase with Store {
535 TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority(
536 raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!);
537 TransactionPriority? bitcoinTransactionPriority =
529 - bitcoin?.deserializeBitcoinTransactionPriority(
530 - sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!);
538 + bitcoin?.deserializeBitcoinTransactionPriority(
539 + sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!);
540
541 TransactionPriority? havenTransactionPriority;
542 TransactionPriority? litecoinTransactionPriority;
543 TransactionPriority? ethereumTransactionPriority;
544 + TransactionPriority? bitcoinCashTransactionPriority;
545
546 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
547 havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@@ -545,12 +555,17 @@ abstract class SettingsStoreBase with Store {
555 ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
556 sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
557 }
558 + if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
559 + bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
560 + sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
561 + }
562
563 moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
564 bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
565 havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
566 litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
567 ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
568 + bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
569
570 final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
571 raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@@ -560,7 +575,8 @@ abstract class SettingsStoreBase with Store {
575 final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
576 final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false;
577 final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
563 - final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
578 + final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(
579 + PreferencesKey.defaultBuyProvider) ?? 0];
580 final currentFiatApiMode = FiatApiMode.deserialize(
581 raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
582 FiatApiMode.enabled.raw);
@@ -579,7 +595,7 @@ abstract class SettingsStoreBase with Store {
595 sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
596 false;
597 final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
582 - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
598 + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
599 false;
600 final shouldRequireTOTP2FAForAddingContacts =
601 sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
@@ -587,7 +603,7 @@ abstract class SettingsStoreBase with Store {
603 sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
604 false;
605 final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
590 - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
606 + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
607 false;
608 final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
609 final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
@@ -612,7 +628,7 @@ abstract class SettingsStoreBase with Store {
628 ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
629 : defaultPinCodeTimeOutDuration;
630 final sortBalanceBy =
615 - SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
631 + SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
632 final pinNativeTokenAtTop =
633 sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
634 final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
@@ -626,9 +642,11 @@ abstract class SettingsStoreBase with Store {
642 await LanguageService.localeDetection();
643 final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
644 final bitcoinElectrumServerId =
629 - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
645 + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
646 final litecoinElectrumServerId =
631 - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
647 + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
648 + final bitcoinCashElectrumServerId =
649 + sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
650 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
651 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
652 final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
@@ -638,13 +656,14 @@ abstract class SettingsStoreBase with Store {
656 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
657 final havenNode = nodeSource.get(havenNodeId);
658 final ethereumNode = nodeSource.get(ethereumNodeId);
659 + final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
660 final nanoNode = nodeSource.get(nanoNodeId);
661 final nanoPowNode = powNodeSource.get(nanoPowNodeId);
662 final packageInfo = await PackageInfo.fromPlatform();
663 final deviceName = await _getDeviceName() ?? '';
664 final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
665 final generateSubaddresses =
647 - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
666 + sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
667
668 final autoGenerateSubaddressStatus = generateSubaddresses != null
669 ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
@@ -672,70 +691,76 @@ abstract class SettingsStoreBase with Store {
691 nodes[WalletType.ethereum] = ethereumNode;
692 }
693
694 + if (bitcoinCashElectrumServer != null) {
695 + nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
696 + }
697 +
698 if (nanoNode != null) {
699 nodes[WalletType.nano] = nanoNode;
700 }
701 +
702 if (nanoPowNode != null) {
703 powNodes[WalletType.nano] = nanoPowNode;
704 }
705
682 - final savedSyncMode = SyncMode.all.firstWhere((element) {
683 - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
684 - });
685 - final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
686 -
687 - return SettingsStore(
688 - sharedPreferences: sharedPreferences,
689 - initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
690 - nodes: nodes,
691 - powNodes: powNodes,
692 - appVersion: packageInfo.version,
693 - deviceName: deviceName,
694 - isBitcoinBuyEnabled: isBitcoinBuyEnabled,
695 - initialFiatCurrency: currentFiatCurrency,
696 - initialBalanceDisplayMode: currentBalanceDisplayMode,
697 - initialSaveRecipientAddress: shouldSaveRecipientAddress,
698 - initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
699 - initialAppSecure: isAppSecure,
700 - initialDisableBuy: disableBuy,
701 - initialDisableSell: disableSell,
702 - initialDefaultBuyProvider: defaultBuyProvider,
703 - initialFiatMode: currentFiatApiMode,
704 - initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
705 - initialCake2FAPresetOptions: selectedCake2FAPreset,
706 - initialUseTOTP2FA: useTOTP2FA,
707 - initialTotpSecretKey: totpSecretKey,
708 - initialFailedTokenTrial: tokenTrialNumber,
709 - initialExchangeStatus: exchangeStatus,
710 - initialTheme: savedTheme,
711 - actionlistDisplayMode: actionListDisplayMode,
712 - initialPinLength: pinLength,
713 - pinTimeOutDuration: pinCodeTimeOutDuration,
714 - initialLanguageCode: savedLanguageCode,
715 - sortBalanceBy: sortBalanceBy,
716 - pinNativeTokenAtTop: pinNativeTokenAtTop,
717 - useEtherscan: useEtherscan,
718 - initialMoneroTransactionPriority: moneroTransactionPriority,
719 - initialBitcoinTransactionPriority: bitcoinTransactionPriority,
720 - initialHavenTransactionPriority: havenTransactionPriority,
721 - initialLitecoinTransactionPriority: litecoinTransactionPriority,
722 - initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
723 - initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
724 - initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
725 - initialShouldRequireTOTP2FAForSendsToInternalWallets:
726 - shouldRequireTOTP2FAForSendsToInternalWallets,
727 - initialShouldRequireTOTP2FAForExchangesToInternalWallets:
728 - shouldRequireTOTP2FAForExchangesToInternalWallets,
729 - initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
730 - initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
731 - initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
732 - shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
733 - initialEthereumTransactionPriority: ethereumTransactionPriority,
734 - backgroundTasks: backgroundTasks,
735 - initialSyncMode: savedSyncMode,
736 - initialSyncAll: savedSyncAll,
737 - shouldShowYatPopup: shouldShowYatPopup);
738 - }
706 + final savedSyncMode = SyncMode.all.firstWhere((element) {
707 + return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
708 + });
709 + final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
710 +
711 + return SettingsStore(
712 + sharedPreferences: sharedPreferences,
713 + initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
714 + nodes: nodes,
715 + powNodes: powNodes,
716 + appVersion: packageInfo.version,
717 + deviceName: deviceName,
718 + isBitcoinBuyEnabled: isBitcoinBuyEnabled,
719 + initialFiatCurrency: currentFiatCurrency,
720 + initialBalanceDisplayMode: currentBalanceDisplayMode,
721 + initialSaveRecipientAddress: shouldSaveRecipientAddress,
722 + initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
723 + initialAppSecure: isAppSecure,
724 + initialDisableBuy: disableBuy,
725 + initialDisableSell: disableSell,
726 + initialDefaultBuyProvider: defaultBuyProvider,
727 + initialFiatMode: currentFiatApiMode,
728 + initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
729 + initialCake2FAPresetOptions: selectedCake2FAPreset,
730 + initialUseTOTP2FA: useTOTP2FA,
731 + initialTotpSecretKey: totpSecretKey,
732 + initialFailedTokenTrial: tokenTrialNumber,
733 + initialExchangeStatus: exchangeStatus,
734 + initialTheme: savedTheme,
735 + actionlistDisplayMode: actionListDisplayMode,
736 + initialPinLength: pinLength,
737 + pinTimeOutDuration: pinCodeTimeOutDuration,
738 + initialLanguageCode: savedLanguageCode,
739 + sortBalanceBy: sortBalanceBy,
740 + pinNativeTokenAtTop: pinNativeTokenAtTop,
741 + useEtherscan: useEtherscan,
742 + initialMoneroTransactionPriority: moneroTransactionPriority,
743 + initialBitcoinTransactionPriority: bitcoinTransactionPriority,
744 + initialHavenTransactionPriority: havenTransactionPriority,
745 + initialLitecoinTransactionPriority: litecoinTransactionPriority,
746 + initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
747 + initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
748 + initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
749 + initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
750 + initialShouldRequireTOTP2FAForSendsToInternalWallets:
751 + shouldRequireTOTP2FAForSendsToInternalWallets,
752 + initialShouldRequireTOTP2FAForExchangesToInternalWallets:
753 + shouldRequireTOTP2FAForExchangesToInternalWallets,
754 + initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
755 + initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
756 + initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
757 + shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
758 + initialEthereumTransactionPriority: ethereumTransactionPriority,
759 + backgroundTasks: backgroundTasks,
760 + initialSyncMode: savedSyncMode,
761 + initialSyncAll: savedSyncAll,
762 + shouldShowYatPopup: shouldShowYatPopup);
763 + }
764
765 Future<void> reload({required Box<Node> nodeSource}) async {
766 final sharedPreferences = await getIt.getAsync<SharedPreferences>();
@@ -744,30 +769,35 @@ abstract class SettingsStoreBase with Store {
769 raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
770
771 priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority(
747 - raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
772 + raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
773 priority[WalletType.monero]!;
774 priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority(
750 - sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
775 + sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
776 priority[WalletType.bitcoin]!;
777
778 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
779 priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority(
755 - raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
780 + raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
781 priority[WalletType.haven]!;
782 }
783 if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) {
784 priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority(
760 - sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
785 + sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
786 priority[WalletType.litecoin]!;
787 }
788 if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
789 priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority(
765 - sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
790 + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
791 priority[WalletType.ethereum]!;
792 }
793 + if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
794 + priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
795 + sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ??
796 + priority[WalletType.bitcoinCash]!;
797 + }
798
799 final generateSubaddresses =
770 - sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
800 + sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
801
802 autoGenerateSubaddressStatus = generateSubaddresses != null
803 ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
@@ -785,7 +815,8 @@ abstract class SettingsStoreBase with Store {
815 isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
816 disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
817 disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
788 - defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
818 + defaultBuyProvider =
819 + BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
820 allowBiometricalAuthentication =
821 sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
822 allowBiometricalAuthentication;
@@ -802,7 +833,7 @@ abstract class SettingsStoreBase with Store {
833 sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
834 false;
835 shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
805 - .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
836 + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
837 false;
838 shouldRequireTOTP2FAForAddingContacts =
839 sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
@@ -810,7 +841,7 @@ abstract class SettingsStoreBase with Store {
841 sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
842 false;
843 shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
813 - .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
844 + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
845 false;
846 shouldShowMarketPlaceInDashboard =
847 sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
@@ -846,9 +877,11 @@ abstract class SettingsStoreBase with Store {
877
878 final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
879 final bitcoinElectrumServerId =
849 - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
880 + sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
881 final litecoinElectrumServerId =
851 - sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
882 + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
883 + final bitcoinCashElectrumServerId =
884 + sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
885 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
886 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
887 final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
@@ -858,6 +891,7 @@ abstract class SettingsStoreBase with Store {
891 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
892 final havenNode = nodeSource.get(havenNodeId);
893 final ethereumNode = nodeSource.get(ethereumNodeId);
894 + final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
895 final nanoNode = nodeSource.get(nanoNodeId);
896
897 if (moneroNode != null) {
@@ -880,6 +914,10 @@ abstract class SettingsStoreBase with Store {
914 nodes[WalletType.ethereum] = ethereumNode;
915 }
916
917 + if (bitcoinCashNode != null) {
918 + nodes[WalletType.bitcoinCash] = bitcoinCashNode;
919 + }
920 +
921 if (nanoNode != null) {
922 nodes[WalletType.nano] = nanoNode;
923 }
@@ -904,6 +942,9 @@ abstract class SettingsStoreBase with Store {
942 case WalletType.ethereum:
943 await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
944 break;
945 + case WalletType.bitcoinCash:
946 + await _sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
947 + break;
948 case WalletType.nano:
949 await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
950 break;
lib/view_model/dashboard/transaction_list_item.dart
+1
@@ -72,6 +72,7 @@ class TransactionListItem extends ActionListItem with Keyable {
72 break;
73 case WalletType.bitcoin:
74 case WalletType.litecoin:
75 + case WalletType.bitcoinCash:
76 amount = calculateFiatAmountRaw(
77 cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
78 price: price);
lib/view_model/exchange/exchange_view_model.dart
+25 -4
@@ -2,10 +2,12 @@ import 'dart:async';
2 import 'dart:collection';
3 import 'dart:convert';
4
5 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
6 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
7 import 'package:cake_wallet/entities/exchange_api_mode.dart';
8 import 'package:cake_wallet/entities/preferences_key.dart';
9 import 'package:cake_wallet/entities/wallet_contact.dart';
10 +import 'package:cake_wallet/ethereum/ethereum.dart';
11 import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
12 import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
13 import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
@@ -265,8 +267,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
267 }
268
269 bool get hasAllAmount =>
268 - (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) &&
269 - depositCurrency == wallet.currency;
270 + (wallet.type == WalletType.bitcoin ||
271 + wallet.type == WalletType.litecoin ||
272 + wallet.type == WalletType.bitcoinCash) &&
273 + depositCurrency == wallet.currency;
274
275 bool get isMoneroWallet => wallet.type == WalletType.monero;
276
@@ -278,7 +282,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
282 case WalletType.bitcoin:
283 return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow();
284 case WalletType.litecoin:
281 - return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow();
285 + return transactionPriority ==
286 + bitcoin!.getLitecoinTransactionPrioritySlow();
287 + case WalletType.ethereum:
288 + return transactionPriority ==
289 + ethereum!.getEthereumTransactionPrioritySlow();
290 + case WalletType.bitcoinCash:
291 + return transactionPriority ==
292 + bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
293 default:
294 return false;
295 }
@@ -619,7 +630,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
630
631 @action
632 void calculateDepositAllAmount() {
622 - if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) {
633 + if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) {
634 final availableBalance = wallet.balance[wallet.currency]!.available;
635 final priority = _settingsStore.priority[wallet.type]!;
636 final fee = wallet.calculateEstimatedFee(priority, null);
@@ -694,6 +705,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
705 depositCurrency = CryptoCurrency.ltc;
706 receiveCurrency = CryptoCurrency.xmr;
707 break;
708 + case WalletType.bitcoinCash:
709 + depositCurrency = CryptoCurrency.bch;
710 + receiveCurrency = CryptoCurrency.xmr;
711 + break;
712 case WalletType.haven:
713 depositCurrency = CryptoCurrency.xhv;
714 receiveCurrency = CryptoCurrency.btc;
@@ -789,6 +804,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
804 case WalletType.litecoin:
805 _settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium();
806 break;
807 + case WalletType.ethereum:
808 + _settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
809 + break;
810 + case WalletType.bitcoinCash:
811 + _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
812 + break;
813 default:
814 break;
815 }
lib/view_model/node_list/node_list_view_model.dart
+3
@@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store {
66 case WalletType.ethereum:
67 node = getEthereumDefaultNode(nodes: _nodeSource)!;
68 break;
69 + case WalletType.bitcoinCash:
70 + node = getBitcoinCashDefaultElectrumServer(nodes: _nodeSource)!;
71 + break;
72 case WalletType.nano:
73 node = getNanoDefaultNode(nodes: _nodeSource)!;
74 break;
lib/view_model/restore/restore_from_qr_vm.dart
+4
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/view_model/restore/restore_mode.dart';
5 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
@@ -84,6 +85,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
85 case WalletType.litecoin:
86 return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
87 name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
88 + case WalletType.bitcoinCash:
89 + return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
90 + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
91 case WalletType.ethereum:
92 return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
93 name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
lib/view_model/restore/wallet_restore_from_qr_code.dart
+6
@@ -72,8 +72,13 @@ class WalletRestoreFromQRCode {
72 case 'litecoin':
73 case 'litecoin-wallet':
74 return WalletType.litecoin;
75 + case 'bitcoincash':
76 + case 'bitcoinCash-wallet':
77 + return WalletType.bitcoinCash;
78 case 'ethereum-wallet':
79 return WalletType.ethereum;
80 + case 'nano-wallet':
81 + return WalletType.nano;
82 default:
83 throw Exception('Unexpected wallet type: ${scheme.toString()}');
84 }
@@ -107,6 +112,7 @@ class WalletRestoreFromQRCode {
112 case WalletType.bitcoin:
113 case WalletType.litecoin:
114 case WalletType.ethereum:
115 + case WalletType.bitcoinCash:
116 RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b');
117 RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b');
118 RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b');
lib/view_model/send/output.dart
+6 -4
@@ -81,10 +81,8 @@ abstract class OutputBase with Store {
81 _amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
82 break;
83 case WalletType.bitcoin:
84 - _amount =
85 - bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
86 - break;
84 case WalletType.litecoin:
85 + case WalletType.bitcoinCash:
86 _amount =
87 bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
88 break;
@@ -116,7 +114,8 @@ abstract class OutputBase with Store {
114 _settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
115
116 if (_wallet.type == WalletType.bitcoin ||
119 - _wallet.type == WalletType.litecoin) {
117 + _wallet.type == WalletType.litecoin ||
118 + _wallet.type == WalletType.bitcoinCash) {
119 return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
120 }
121
@@ -234,6 +233,9 @@ abstract class OutputBase with Store {
233 case WalletType.litecoin:
234 maximumFractionDigits = 8;
235 break;
236 + case WalletType.bitcoinCash:
237 + maximumFractionDigits = 8;
238 + break;
239 case WalletType.haven:
240 maximumFractionDigits = 12;
241 break;
lib/view_model/send/send_view_model.dart
+15 -31
@@ -185,12 +185,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
185 @computed
186 bool get hasCoinControl =>
187 wallet.type == WalletType.bitcoin ||
188 - wallet.type == WalletType.litecoin ||
189 - wallet.type == WalletType.monero;
188 + wallet.type == WalletType.litecoin ||
189 + wallet.type == WalletType.monero ||
190 + wallet.type == WalletType.bitcoinCash;
191
192 @computed
193 bool get isElectrumWallet =>
193 - wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin;
194 + wallet.type == WalletType.bitcoin ||
195 + wallet.type == WalletType.litecoin ||
196 + wallet.type == WalletType.bitcoinCash;
197
198 @computed
199 bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
@@ -345,41 +348,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
348 _settingsStore.priority[wallet.type] = priority;
349
350 Object _credentials() {
348 - switch (wallet.type) {
349 - case WalletType.bitcoin:
350 - final priority = _settingsStore.priority[wallet.type];
351 + final priority = _settingsStore.priority[wallet.type];
352
352 - if (priority == null) {
353 - throw Exception('Priority is null for wallet type: ${wallet.type}');
354 - }
353 + if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}');
354
356 - return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority);
355 + switch (wallet.type) {
356 + case WalletType.bitcoin:
357 case WalletType.litecoin:
358 - final priority = _settingsStore.priority[wallet.type];
359 -
360 - if (priority == null) {
361 - throw Exception('Priority is null for wallet type: ${wallet.type}');
362 - }
363 -
358 + case WalletType.bitcoinCash:
359 return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority);
365 - case WalletType.monero:
366 - final priority = _settingsStore.priority[wallet.type];
367 -
368 - if (priority == null) {
369 - throw Exception('Priority is null for wallet type: ${wallet.type}');
370 - }
360
361 + case WalletType.monero:
362 return monero!
363 .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority);
374 - case WalletType.haven:
375 - final priority = _settingsStore.priority[wallet.type];
376 -
377 - if (priority == null) {
378 - throw Exception('Priority is null for wallet type: ${wallet.type}');
379 - }
364
365 + case WalletType.haven:
366 return haven!.createHavenTransactionCreationCredentials(
367 outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
368 +
369 case WalletType.ethereum:
370 final priority = _settingsStore.priority[wallet.type];
371
@@ -390,9 +376,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
376 return ethereum!.createEthereumTransactionCredentials(outputs,
377 priority: priority, currency: selectedCryptoCurrency);
378 case WalletType.nano:
393 - return nano!.createNanoTransactionCredentials(
394 - outputs,
395 - );
379 + return nano!.createNanoTransactionCredentials(outputs);
380 default:
381 throw Exception('Unexpected wallet type: ${wallet.type}');
382 }
lib/view_model/settings/other_settings_view_model.dart
+3 -1
@@ -63,7 +63,9 @@ abstract class OtherSettingsViewModelBase with Store {
63 String getDisplayPriority(dynamic priority) {
64 final _priority = priority as TransactionPriority;
65
66 - if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin) {
66 + if (_wallet.type == WalletType.bitcoin ||
67 + _wallet.type == WalletType.litecoin ||
68 + _wallet.type == WalletType.bitcoinCash) {
69 final rate = bitcoin!.getFeeRate(_wallet, _priority);
70 return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
71 }
lib/view_model/transaction_details_view_model.dart
+4
@@ -39,6 +39,7 @@ abstract class TransactionDetailsViewModelBase with Store {
39 break;
40 case WalletType.bitcoin:
41 case WalletType.litecoin:
42 + case WalletType.bitcoinCash:
43 _addElectrumListItems(tx, dateFormat);
44 break;
45 case WalletType.haven:
@@ -115,6 +116,8 @@ abstract class TransactionDetailsViewModelBase with Store {
116 return 'https://mempool.space/tx/${txId}';
117 case WalletType.litecoin:
118 return 'https://blockchair.com/litecoin/transaction/${txId}';
119 + case WalletType.bitcoinCash:
120 + return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
121 case WalletType.haven:
122 return 'https://explorer.havenprotocol.org/search?value=${txId}';
123 case WalletType.ethereum:
@@ -135,6 +138,7 @@ abstract class TransactionDetailsViewModelBase with Store {
138 case WalletType.bitcoin:
139 return S.current.view_transaction_on + 'mempool.space';
140 case WalletType.litecoin:
141 + case WalletType.bitcoinCash:
142 return S.current.view_transaction_on + 'Blockchair.com';
143 case WalletType.haven:
144 return S.current.view_transaction_on + 'explorer.havenprotocol.org';
lib/view_model/unspent_coins/unspent_coins_details_view_model.dart
+18 -7
@@ -1,9 +1,10 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
4 import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
5 import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
6 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
7 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
6 -import 'package:cake_wallet/generated/i18n.dart';
8 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
9 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
10 import 'package:cw_core/wallet_type.dart';
@@ -19,12 +20,14 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
20 UnspentCoinsDetailsViewModelBase(
21 {required this.unspentCoinsItem, required this.unspentCoinsListViewModel})
22 : items = <TransactionDetailsListItem>[],
23 + _type = unspentCoinsListViewModel.wallet.type,
24 isFrozen = unspentCoinsItem.isFrozen,
25 note = unspentCoinsItem.note {
26 items = [
27 StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount),
26 - StandartListItem(title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash),
27 - StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address),
28 + StandartListItem(
29 + title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash),
30 + StandartListItem(title: S.current.widgets_address, value: formattedAddress),
31 TextFieldListItem(
32 title: S.current.note_tap_to_change,
33 value: note,
@@ -46,14 +49,13 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
49 })
50 ];
51
49 - if ([WalletType.bitcoin, WalletType.litecoin].contains(unspentCoinsListViewModel.wallet.type)) {
52 + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(_type)) {
53 items.add(BlockExplorerListItem(
54 title: S.current.view_in_block_explorer,
52 - value: _explorerDescription(unspentCoinsListViewModel.wallet.type),
55 + value: _explorerDescription(_type),
56 onTap: () {
57 try {
55 - final url = Uri.parse(
56 - _explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash));
58 + final url = Uri.parse(_explorerUrl(_type, unspentCoinsItem.hash));
59 return launchUrl(url);
60 } catch (e) {}
61 },
@@ -67,6 +69,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
69 return 'https://ordinals.com/tx/${txId}';
70 case WalletType.litecoin:
71 return 'https://litecoin.earlyordies.com/tx/${txId}';
72 + case WalletType.bitcoinCash:
73 + return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
74 default:
75 return '';
76 }
@@ -78,6 +82,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
82 return S.current.view_transaction_on + 'Ordinals.com';
83 case WalletType.litecoin:
84 return S.current.view_transaction_on + 'Earlyordies.com';
85 + case WalletType.bitcoinCash:
86 + return S.current.view_transaction_on + 'Blockchair.com';
87 default:
88 return '';
89 }
@@ -91,5 +97,10 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
97
98 final UnspentCoinsItem unspentCoinsItem;
99 final UnspentCoinsListViewModel unspentCoinsListViewModel;
100 + final WalletType _type;
101 List<TransactionDetailsListItem> items;
102 +
103 + String get formattedAddress => WalletType.bitcoinCash == _type
104 + ? bitcoinCash!.getCashAddrFormat(unspentCoinsItem.address)
105 + : unspentCoinsItem.address;
106 }
lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
+17 -20
@@ -1,9 +1,10 @@
1 import 'package:collection/collection.dart';
2 import 'package:cake_wallet/bitcoin/bitcoin.dart';
3 -import 'package:cake_wallet/entities/unspent_transaction_output.dart';
3 +import 'package:cw_core/unspent_transaction_output.dart';
4 import 'package:cake_wallet/monero/monero.dart';
5 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
6 import 'package:cw_core/unspent_coins_info.dart';
7 +import 'package:cw_core/wallet_addresses.dart';
8 import 'package:cw_core/wallet_base.dart';
9 import 'package:cw_core/wallet_type.dart';
10 import 'package:hive/hive.dart';
@@ -24,11 +25,11 @@ abstract class UnspentCoinsListViewModelBase with Store {
25 final Box<UnspentCoinsInfo> _unspentCoinsInfo;
26
27 @computed
27 - ObservableList<UnspentCoinsItem> get items =>
28 - ObservableList.of(_getUnspents().map((elem) {
28 + ObservableList<UnspentCoinsItem> get items => ObservableList.of(_getUnspents().map((elem) {
29 final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}';
30
31 - final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
31 + final info =
32 + getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
33
34 return UnspentCoinsItem(
35 address: elem.address,
@@ -39,13 +40,13 @@ abstract class UnspentCoinsListViewModelBase with Store {
40 isSending: info?.isSending ?? true,
41 amountRaw: elem.value,
42 vout: elem.vout,
42 - keyImage: elem.keyImage
43 - );
43 + keyImage: elem.keyImage);
44 }));
45
46 Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
47 try {
48 - final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
48 + final info =
49 + getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
50 if (info == null) {
51 final newInfo = UnspentCoinsInfo(
52 walletId: wallet.id,
@@ -56,8 +57,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
57 isFrozen: item.isFrozen,
58 isSending: item.isSending,
59 noteRaw: item.note,
59 - keyImage: item.keyImage
60 - );
60 + keyImage: item.keyImage);
61
62 await _unspentCoinsInfo.add(newInfo);
63 _updateUnspents();
@@ -76,37 +76,34 @@ abstract class UnspentCoinsListViewModelBase with Store {
76 }
77 }
78
79 - UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout, String? keyImage) {
79 + UnspentCoinsInfo? getUnspentCoinInfo(
80 + String hash, String address, int value, int vout, String? keyImage) {
81 return _unspentCoinsInfo.values.firstWhereOrNull((element) =>
82 element.walletId == wallet.id &&
83 element.hash == hash &&
84 element.address == address &&
85 element.value == value &&
86 element.vout == vout &&
86 - element.keyImage == keyImage
87 - );
87 + element.keyImage == keyImage);
88 }
89
90 String formatAmountToString(int fullBalance) {
91 if (wallet.type == WalletType.monero)
92 return monero!.formatterMoneroAmountToString(amount: fullBalance);
93 - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
93 + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
94 return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance);
95 return '';
96 }
97
98 -
98 void _updateUnspents() {
100 - if (wallet.type == WalletType.monero)
101 - return monero!.updateUnspents(wallet);
102 - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
99 + if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet);
100 + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
101 return bitcoin!.updateUnspents(wallet);
102 }
103
104 List<Unspent> _getUnspents() {
107 - if (wallet.type == WalletType.monero)
108 - return monero!.getUnspents(wallet);
109 - if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
105 + if (wallet.type == WalletType.monero) return monero!.getUnspents(wallet);
106 + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
107 return bitcoin!.getUnspents(wallet);
108 return List.empty();
109 }
lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart
+2 -1
@@ -66,7 +66,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
66 final wallet = _wallet;
67
68 if (wallet.type == WalletType.bitcoin
69 - || wallet.type == WalletType.litecoin) {
69 + || wallet.type == WalletType.litecoin
70 + || wallet.type == WalletType.bitcoinCash) {
71 await bitcoin!.generateNewAddress(wallet);
72 await wallet.save();
73 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+24 -2
@@ -107,6 +107,23 @@ class EthereumURI extends PaymentURI {
107 }
108 }
109
110 +class BitcoinCashURI extends PaymentURI {
111 + BitcoinCashURI({required String amount, required String address})
112 + : super(amount: amount, address: address);
113 + @override
114 + String toString() {
115 + var base = address;
116 +
117 + if (amount.isNotEmpty) {
118 + base += '?amount=${amount.replaceAll(',', '.')}';
119 + }
120 +
121 + return base;
122 + }
123 + }
124 +
125 +
126 +
127 class NanoURI extends PaymentURI {
128 NanoURI({required String amount, required String address})
129 : super(amount: amount, address: address);
@@ -114,7 +131,6 @@ class NanoURI extends PaymentURI {
131 @override
132 String toString() {
133 var base = 'nano:' + address;
117 -
134 if (amount.isNotEmpty) {
135 base += '?amount=${amount.replaceAll(',', '.')}';
136 }
@@ -192,6 +208,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
208 return EthereumURI(amount: amount, address: address.address);
209 }
210
211 + if (wallet.type == WalletType.bitcoinCash) {
212 + return BitcoinCashURI(amount: amount, address: address.address);
213 + }
214 +
215 if (wallet.type == WalletType.nano) {
216 return NanoURI(amount: amount, address: address.address);
217 }
@@ -280,7 +300,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
300
301 @computed
302 bool get showElectrumAddressDisclaimer =>
283 - wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin;
303 + wallet.type == WalletType.bitcoin ||
304 + wallet.type == WalletType.litecoin ||
305 + wallet.type == WalletType.bitcoinCash;
306
307 List<ListItem> _baseItems;
308
lib/view_model/wallet_keys_view_model.dart
+5 -1
@@ -19,6 +19,7 @@ abstract class WalletKeysViewModelBase with Store {
19 WalletKeysViewModelBase(this._appStore)
20 : title = _appStore.wallet!.type == WalletType.bitcoin ||
21 _appStore.wallet!.type == WalletType.litecoin ||
22 + _appStore.wallet!.type == WalletType.bitcoinCash ||
23 _appStore.wallet!.type == WalletType.ethereum
24 ? S.current.wallet_seed
25 : S.current.wallet_keys,
@@ -91,7 +92,8 @@ abstract class WalletKeysViewModelBase with Store {
92 }
93
94 if (_appStore.wallet!.type == WalletType.bitcoin ||
94 - _appStore.wallet!.type == WalletType.litecoin) {
95 + _appStore.wallet!.type == WalletType.litecoin ||
96 + _appStore.wallet!.type == WalletType.bitcoinCash) {
97 items.addAll([
98 StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
99 ]);
@@ -145,6 +147,8 @@ abstract class WalletKeysViewModelBase with Store {
147 return 'haven-wallet';
148 case WalletType.ethereum:
149 return 'ethereum-wallet';
150 + case WalletType.bitcoinCash:
151 + return 'bitcoinCash-wallet';
152 case WalletType.nano:
153 return 'nano-wallet';
154 case WalletType.banano:
lib/view_model/wallet_new_vm.dart
+4 -1
@@ -1,6 +1,7 @@
1 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
2 import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:flutter/foundation.dart';
4 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
5 import 'package:hive/hive.dart';
6 import 'package:mobx/mobx.dart';
7 import 'package:cake_wallet/monero/monero.dart';
@@ -46,10 +47,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
47 name: name, language: options as String);
48 case WalletType.ethereum:
49 return ethereum!.createEthereumNewWalletCredentials(name: name);
50 + case WalletType.bitcoinCash:
51 + return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name);
52 case WalletType.nano:
53 return nano!.createNanoNewWalletCredentials(name: name);
54 default:
52 - throw Exception('Unexpected type: ${type.toString()}');;
55 + throw Exception('Unexpected type: ${type.toString()}');
56 }
57 }
58
lib/view_model/wallet_restore_view_model.dart
+12 -7
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/di.dart';
3 import 'package:cake_wallet/nano/nano.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
6 import 'package:hive/hive.dart';
7 import 'package:mobx/mobx.dart';
8 import 'package:cake_wallet/store/app_store.dart';
@@ -92,14 +93,20 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
93 name: name, height: height, mnemonic: seed, password: password);
94 case WalletType.ethereum:
95 return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
95 - name: name, mnemonic: seed, password: password);
96 + name: name,
97 + mnemonic: seed,
98 + password: password);
99 + case WalletType.bitcoinCash:
100 + return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
101 + name: name,
102 + mnemonic: seed,
103 + password: password);
104 case WalletType.nano:
105 return nano!.createNanoRestoreWalletFromSeedCredentials(
106 name: name,
107 mnemonic: seed,
108 password: password,
101 - derivationType: derivationType,
102 - );
109 + derivationType: derivationType);
110 default:
111 break;
112 }
@@ -145,8 +152,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
152 name: name,
153 password: password,
154 seedKey: options['private_key'] as String,
148 - derivationType: options["derivationType"] as DerivationType,
149 - );
155 + derivationType: options["derivationType"] as DerivationType);
156 default:
157 break;
158 }
@@ -167,8 +173,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
173 return nanoUtil!.compareDerivationMethods(
174 mnemonic: mnemonic,
175 privateKey: seedKey,
170 - node: node,
171 - );
176 + node: node);
177 default:
178 break;
179 }
model_generator.sh
+1
@@ -4,4 +4,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build
4 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
5 cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
6 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
7 +cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
8 flutter packages pub run build_runner build --delete-conflicting-outputs
\ No newline at end of file
pubspec_base.yaml
+1
@@ -129,6 +129,7 @@ flutter:
129 - assets/bitcoin_electrum_server_list.yml
130 - assets/litecoin_electrum_server_list.yml
131 - assets/ethereum_server_list.yml
132 + - assets/bitcoin_cash_electrum_server_list.yml
133 - assets/nano_node_list.yml
134 - assets/nano_pow_node_list.yml
135 - assets/text/
scripts/android/app_config.sh
+1 -1
@@ -9,4 +9,4 @@ fi
9 ./app_icon.sh
10 ./pubspec_gen.sh
11 ./manifest.sh
12 -./inject_app_details.sh
\ No newline at end of file
12 +./inject_app_details.sh
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano"
13 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash"
14 ;;
15 $HAVEN)
16 CONFIG_ARGS="--haven"
scripts/ios/app_config.sh
+3 -1
@@ -28,9 +28,11 @@ case $APP_IOS_TYPE in
28 CONFIG_ARGS="--monero"
29 ;;
30 $CAKEWALLET)
31 - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano"
31 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash"
32 ;;
33 $HAVEN)
34 +
35 +
36 CONFIG_ARGS="--haven"
37 ;;
38 esac
scripts/macos/app_config.sh
+1 -1
@@ -23,7 +23,7 @@ CONFIG_ARGS=""
23
24 case $APP_MACOS_TYPE in
25 $CAKEWALLET)
26 - CONFIG_ARGS="--monero --bitcoin --ethereum --nano";; #--haven
26 + CONFIG_ARGS="--monero --bitcoin --ethereum --nano --bitcoinCash";; #--haven
27 esac
28
29 cp -rf pubspec_description.yaml pubspec.yaml
tool/configure.dart
+99 -22
@@ -4,6 +4,7 @@ const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart';
4 const moneroOutputPath = 'lib/monero/monero.dart';
5 const havenOutputPath = 'lib/haven/haven.dart';
6 const ethereumOutputPath = 'lib/ethereum/ethereum.dart';
7 +const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart';
8 const nanoOutputPath = 'lib/nano/nano.dart';
9 const walletTypesPath = 'lib/wallet_types.g.dart';
10 const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -15,6 +16,7 @@ Future<void> main(List<String> args) async {
16 final hasMonero = args.contains('${prefix}monero');
17 final hasHaven = args.contains('${prefix}haven');
18 final hasEthereum = args.contains('${prefix}ethereum');
19 + final hasBitcoinCash = args.contains('${prefix}bitcoinCash');
20 final hasNano = args.contains('${prefix}nano');
21 final hasBanano = args.contains('${prefix}banano');
22
@@ -22,6 +24,7 @@ Future<void> main(List<String> args) async {
24 await generateMonero(hasMonero);
25 await generateHaven(hasHaven);
26 await generateEthereum(hasEthereum);
27 + await generateBitcoinCash(hasBitcoinCash);
28 await generateNano(hasNano);
29 // await generateBanano(hasEthereum);
30
@@ -32,6 +35,7 @@ Future<void> main(List<String> args) async {
35 hasEthereum: hasEthereum,
36 hasNano: hasNano,
37 hasBanano: hasBanano,
38 + hasBitcoinCash: hasBitcoinCash,
39 );
40 await generateWalletTypes(
41 hasMonero: hasMonero,
@@ -40,13 +44,13 @@ Future<void> main(List<String> args) async {
44 hasEthereum: hasEthereum,
45 hasNano: hasNano,
46 hasBanano: hasBanano,
47 + hasBitcoinCash: hasBitcoinCash,
48 );
49 }
50
51 Future<void> generateBitcoin(bool hasImplementation) async {
52 final outputFile = File(bitcoinOutputPath);
53 const bitcoinCommonHeaders = """
49 -import 'package:cake_wallet/entities/unspent_transaction_output.dart';
54 import 'package:cw_core/wallet_credentials.dart';
55 import 'package:cw_core/wallet_info.dart';
56 import 'package:cw_core/transaction_priority.dart';
@@ -60,7 +64,6 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
64 import 'package:cw_bitcoin/bitcoin_unspent.dart';
65 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
66 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
63 -import 'package:cw_bitcoin/bitcoin_wallet.dart';
67 import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
68 import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
69 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
@@ -80,8 +83,8 @@ abstract class Bitcoin {
83 Map<String, String> getWalletKeys(Object wallet);
84 List<TransactionPriority> getTransactionPriorities();
85 List<TransactionPriority> getLitecoinTransactionPriorities();
83 - TransactionPriority deserializeBitcoinTransactionPriority(int raw);
84 - TransactionPriority deserializeLitecoinTransactionPriority(int raw);
86 + TransactionPriority deserializeBitcoinTransactionPriority(int raw);
87 + TransactionPriority deserializeLitecoinTransactionPriority(int raw);
88 int getFeeRate(Object wallet, TransactionPriority priority);
89 Future<void> generateNewAddress(Object wallet);
90 Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
@@ -95,7 +98,7 @@ abstract class Bitcoin {
98 int formatterStringDoubleToBitcoinAmount(String amount);
99 String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate);
100
98 - List<Unspent> getUnspents(Object wallet);
101 + List<BitcoinUnspent> getUnspents(Object wallet);
102 void updateUnspents(Object wallet);
103 WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
104 WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
@@ -126,7 +129,7 @@ abstract class Bitcoin {
129 Future<void> generateMonero(bool hasImplementation) async {
130 final outputFile = File(moneroOutputPath);
131 const moneroCommonHeaders = """
129 -import 'package:cake_wallet/entities/unspent_transaction_output.dart';
132 +import 'package:cw_core/unspent_transaction_output.dart';
133 import 'package:cw_core/unspent_coins_info.dart';
134 import 'package:cw_monero/monero_unspent.dart';
135 import 'package:mobx/mobx.dart';
@@ -521,6 +524,7 @@ abstract class Ethereum {
524 String getPrivateKey(WalletBase wallet);
525 String getPublicKey(WalletBase wallet);
526 TransactionPriority getDefaultTransactionPriority();
527 + TransactionPriority getEthereumTransactionPrioritySlow();
528 List<TransactionPriority> getTransactionPriorities();
529 TransactionPriority deserializeEthereumTransactionPriority(int raw);
530
@@ -568,6 +572,67 @@ abstract class Ethereum {
572 await outputFile.writeAsString(output);
573 }
574
575 +Future<void> generateBitcoinCash(bool hasImplementation) async {
576 + final outputFile = File(bitcoinCashOutputPath);
577 + const bitcoinCashCommonHeaders = """
578 +import 'dart:typed_data';
579 +
580 +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
581 +import 'package:cw_core/transaction_priority.dart';
582 +import 'package:cw_core/unspent_coins_info.dart';
583 +import 'package:cw_core/wallet_credentials.dart';
584 +import 'package:cw_core/wallet_info.dart';
585 +import 'package:cw_core/wallet_service.dart';
586 +import 'package:hive/hive.dart';
587 +""";
588 + const bitcoinCashCWHeaders = """
589 +import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
590 +""";
591 + const bitcoinCashCwPart = "part 'cw_bitcoin_cash.dart';";
592 + const bitcoinCashContent = """
593 +abstract class BitcoinCash {
594 + String getMnemonic(int? strength);
595 +
596 + Uint8List getSeedFromMnemonic(String seed);
597 +
598 + String getCashAddrFormat(String address);
599 +
600 + WalletService createBitcoinCashWalletService(
601 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
602 +
603 + WalletCredentials createBitcoinCashNewWalletCredentials(
604 + {required String name, WalletInfo? walletInfo});
605 +
606 + WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
607 + {required String name, required String mnemonic, required String password});
608 +
609 + TransactionPriority deserializeBitcoinCashTransactionPriority(int raw);
610 +
611 + TransactionPriority getDefaultTransactionPriority();
612 +
613 + List<TransactionPriority> getTransactionPriorities();
614 +
615 + TransactionPriority getBitcoinCashTransactionPrioritySlow();
616 +}
617 + """;
618 +
619 + const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n';
620 + const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
621 +
622 + final output = '$bitcoinCashCommonHeaders\n' +
623 + (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') +
624 + (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') +
625 + (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) +
626 + '\n' +
627 + bitcoinCashContent;
628 +
629 + if (outputFile.existsSync()) {
630 + await outputFile.delete();
631 + }
632 +
633 + await outputFile.writeAsString(output);
634 +}
635 +
636 Future<void> generateNano(bool hasImplementation) async {
637 final outputFile = File(nanoOutputPath);
638 const nanoCommonHeaders = """
@@ -710,14 +775,14 @@ abstract class NanoUtil {
775 await outputFile.writeAsString(output);
776 }
777
713 -Future<void> generatePubspec({
714 - required bool hasMonero,
715 - required bool hasBitcoin,
716 - required bool hasHaven,
717 - required bool hasEthereum,
718 - required bool hasNano,
719 - required bool hasBanano,
720 -}) async {
778 +Future<void> generatePubspec(
779 + {required bool hasMonero,
780 + required bool hasBitcoin,
781 + required bool hasHaven,
782 + required bool hasEthereum,
783 + required bool hasNano,
784 + required bool hasBanano,
785 + required bool hasBitcoinCash}) async {
786 const cwCore = """
787 cw_core:
788 path: ./cw_core
@@ -742,6 +807,10 @@ Future<void> generatePubspec({
807 cw_ethereum:
808 path: ./cw_ethereum
809 """;
810 + const cwBitcoinCash = """
811 + cw_bitcoin_cash:
812 + path: ./cw_bitcoin_cash
813 + """;
814 const cwNano = """
815 cw_nano:
816 path: ./cw_nano
@@ -776,6 +845,10 @@ Future<void> generatePubspec({
845 output += '\n$cwBanano';
846 }
847
848 + if (hasBitcoinCash) {
849 + output += '\n$cwBitcoinCash';
850 + }
851 +
852 if (hasHaven && !hasMonero) {
853 output += '\n$cwSharedExternal\n$cwHaven';
854 } else if (hasHaven) {
@@ -794,14 +867,14 @@ Future<void> generatePubspec({
867 await outputFile.writeAsString(outputContent);
868 }
869
797 -Future<void> generateWalletTypes({
798 - required bool hasMonero,
799 - required bool hasBitcoin,
800 - required bool hasHaven,
801 - required bool hasEthereum,
802 - required bool hasNano,
803 - required bool hasBanano,
804 -}) async {
870 +Future<void> generateWalletTypes(
871 + {required bool hasMonero,
872 + required bool hasBitcoin,
873 + required bool hasHaven,
874 + required bool hasEthereum,
875 + required bool hasNano,
876 + required bool hasBanano,
877 + required bool hasBitcoinCash}) async {
878 final walletTypesFile = File(walletTypesPath);
879
880 if (walletTypesFile.existsSync()) {
@@ -828,6 +901,10 @@ Future<void> generateWalletTypes({
901 outputContent += '\tWalletType.litecoin,\n';
902 }
903
904 + if (hasBitcoinCash) {
905 + outputContent += '\tWalletType.bitcoinCash,\n';
906 + }
907 +
908 if (hasNano) {
909 outputContent += '\tWalletType.nano,\n';
910 }