diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml
index ae9328626..9ab942921 100644
--- a/.github/workflows/pr_test_build.yml
+++ b/.github/workflows/pr_test_build.yml
@@ -42,6 +42,7 @@ jobs:
cd cake_wallet/scripts/android/
./install_ndk.sh
source ./app_env.sh cakewallet
+ chmod +x pubspec_gen.sh
./app_config.sh
- name: Cache Externals
@@ -92,6 +93,7 @@ jobs:
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
+ cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
@@ -128,6 +130,7 @@ jobs:
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
+ echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
@@ -140,18 +143,18 @@ jobs:
cd /opt/android/cake_wallet
flutter build apk --release
- # - name: Push to App Center
- # run: |
- # echo 'Installing App Center CLI tools'
- # npm install -g appcenter-cli
- # echo "Publishing test to App Center"
- # appcenter distribute release \
- # --group "Testers" \
- # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
- # --release-notes ${GITHUB_HEAD_REF} \
- # --app Cake-Labs/Cake-Wallet \
- # --token ${{ secrets.APP_CENTER_TOKEN }} \
- # --quiet
+# - name: Push to App Center
+# run: |
+# echo 'Installing App Center CLI tools'
+# npm install -g appcenter-cli
+# echo "Publishing test to App Center"
+# appcenter distribute release \
+# --group "Testers" \
+# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
+# --release-notes ${GITHUB_HEAD_REF} \
+# --app Cake-Labs/Cake-Wallet \
+# --token ${{ secrets.APP_CENTER_TOKEN }} \
+# --quiet
- name: Rename apk file
run: |
diff --git a/.gitignore b/.gitignore
index bb0b4705c..af089f55a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
lib/monero/monero.dart
lib/haven/haven.dart
lib/ethereum/ethereum.dart
+lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index f22ba9c4f..77a555db8 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -52,6 +52,15 @@
+
+
+
+
+
+
+
+
+
all = [fast, medium, slow];
+ static const BitcoinCashTransactionPriority slow =
+ BitcoinCashTransactionPriority(title: 'Slow', raw: 0);
+ static const BitcoinCashTransactionPriority medium =
+ BitcoinCashTransactionPriority(title: 'Medium', raw: 1);
+ static const BitcoinCashTransactionPriority fast =
+ BitcoinCashTransactionPriority(title: 'Fast', raw: 2);
+
+ static BitcoinCashTransactionPriority deserialize({required int raw}) {
+ switch (raw) {
+ case 0:
+ return slow;
+ case 1:
+ return medium;
+ case 2:
+ return fast;
+ default:
+ throw Exception('Unexpected token: $raw for BitcoinCashTransactionPriority deserialize');
+ }
+ }
+
+ @override
+ String get units => 'Satoshi';
+
+ @override
+ String toString() {
+ var label = '';
+
+ switch (this) {
+ case BitcoinCashTransactionPriority.slow:
+ label = 'Slow'; // S.current.transaction_priority_slow;
+ break;
+ case BitcoinCashTransactionPriority.medium:
+ label = 'Medium'; // S.current.transaction_priority_medium;
+ break;
+ case BitcoinCashTransactionPriority.fast:
+ label = 'Fast'; // S.current.transaction_priority_fast;
+ break;
+ default:
+ break;
+ }
+
+ return label;
+ }
+}
+
diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart
index e5a0e8cac..9c198c27c 100644
--- a/cw_bitcoin/lib/bitcoin_unspent.dart
+++ b/cw_bitcoin/lib/bitcoin_unspent.dart
@@ -1,24 +1,15 @@
import 'package:cw_bitcoin/bitcoin_address_record.dart';
+import 'package:cw_core/unspent_transaction_output.dart';
-class BitcoinUnspent {
- BitcoinUnspent(this.address, this.hash, this.value, this.vout)
- : isSending = true,
- isFrozen = false,
- note = '';
+class BitcoinUnspent extends Unspent {
+ BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
+ : bitcoinAddressRecord = addressRecord,
+ super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(
- BitcoinAddressRecord address, Map json) =>
+ BitcoinAddressRecord address, Map json) =>
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
json['tx_pos'] as int);
- final BitcoinAddressRecord address;
- final String hash;
- final int value;
- final int vout;
-
- bool get isP2wpkh =>
- address.address.startsWith('bc') || address.address.startsWith('ltc');
- bool isSending;
- bool isFrozen;
- String note;
+ final BitcoinAddressRecord bitcoinAddressRecord;
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
index de3fdfbca..36d37127d 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
@@ -1,39 +1,34 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
-import 'package:cw_bitcoin/electrum.dart';
-import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
+import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
+import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart';
-import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
part 'bitcoin_wallet_addresses.g.dart';
-class BitcoinWalletAddresses = BitcoinWalletAddressesBase
- with _$BitcoinWalletAddresses;
+class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
-abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
- with Store {
- BitcoinWalletAddressesBase(
- WalletInfo walletInfo,
+abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
+ BitcoinWalletAddressesBase(WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd,
- required bitcoin.HDWallet sideHd,
- required bitcoin.NetworkType networkType,
- required ElectrumClient electrumClient,
- List? initialAddresses,
- int initialRegularAddressIndex = 0,
- int initialChangeAddressIndex = 0})
- : super(
- walletInfo,
- initialAddresses: initialAddresses,
- initialRegularAddressIndex: initialRegularAddressIndex,
- initialChangeAddressIndex: initialChangeAddressIndex,
- mainHd: mainHd,
- sideHd: sideHd,
- electrumClient: electrumClient,
- networkType: networkType);
+ required bitcoin.HDWallet sideHd,
+ required bitcoin.NetworkType networkType,
+ required ElectrumClient electrumClient,
+ List? initialAddresses,
+ int initialRegularAddressIndex = 0,
+ int initialChangeAddressIndex = 0})
+ : super(walletInfo,
+ initialAddresses: initialAddresses,
+ initialRegularAddressIndex: initialRegularAddressIndex,
+ initialChangeAddressIndex: initialChangeAddressIndex,
+ mainHd: mainHd,
+ sideHd: sideHd,
+ electrumClient: electrumClient,
+ networkType: networkType);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
-}
\ No newline at end of file
+}
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 6123def64..9bf87a5f2 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -2,8 +2,10 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
+import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/unspent_coins_info.dart';
+import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:mobx/mobx.dart';
@@ -34,38 +36,42 @@ import 'package:cw_bitcoin/electrum.dart';
import 'package:hex/hex.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:collection/collection.dart';
+import 'package:bip32/bip32.dart';
part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
-abstract class ElectrumWalletBase extends WalletBase with Store {
+abstract class ElectrumWalletBase
+ extends WalletBase
+ with Store {
ElectrumWalletBase(
{required String password,
- required WalletInfo walletInfo,
- required Box unspentCoinsInfo,
- required this.networkType,
- required this.mnemonic,
- required Uint8List seedBytes,
- required this.encryptionFileUtils,
- List? initialAddresses,
- ElectrumClient? electrumClient,
- ElectrumBalance? initialBalance,
- CryptoCurrency? currency})
- : hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
- .derivePath("m/0'/0"),
+ required WalletInfo walletInfo,
+ required Box unspentCoinsInfo,
+ required this.networkType,
+ required this.mnemonic,
+ required Uint8List seedBytes,
+ required this.encryptionFileUtils,
+ List? initialAddresses,
+ ElectrumClient? electrumClient,
+ ElectrumBalance? initialBalance,
+ CryptoCurrency? currency})
+ : hd = currency == CryptoCurrency.bch
+ ? bitcoinCashHDWallet(seedBytes)
+ : bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = [],
_isTransactionUpdating = false,
unspentCoins = [],
_scripthashesUpdateSubject = {},
- balance = ObservableMap.of(
- currency != null
- ? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0,
- frozen: 0)}
- : {}),
+ balance = ObservableMap.of(currency != null
+ ? {
+ currency:
+ initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
+ }
+ : {}),
this.unspentCoinsInfo = unspentCoinsInfo,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
@@ -77,6 +83,10 @@ abstract class ElectrumWalletBase extends WalletBase
+ bitcoin.HDWallet.fromSeed(seedBytes)
+ .derivePath("m/44'/145'/0'/0");
+
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 146 + outputsCounts * 33 + 8;
@@ -103,9 +113,9 @@ abstract class ElectrumWalletBase extends WalletBase get publicScriptHashes => walletAddresses.addresses
- .where((addr) => !addr.isHidden)
- .map((addr) => scriptHash(addr.address, networkType: networkType))
- .toList();
+ .where((addr) => !addr.isHidden)
+ .map((addr) => scriptHash(addr.address, networkType: networkType))
+ .toList();
String get xpub => hd.base58!;
@@ -118,8 +128,8 @@ abstract class ElectrumWalletBase extends WalletBase BitcoinWalletKeys(
- wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
+ BitcoinWalletKeys get keys =>
+ BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
String _password;
List unspentCoins;
@@ -147,8 +157,8 @@ abstract class ElectrumWalletBase extends WalletBase _feeRates = await electrumClient.feeRates());
+ Timer.periodic(
+ const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
syncStatus = SyncedSyncStatus();
} catch (e, stacktrace) {
@@ -177,8 +187,7 @@ abstract class ElectrumWalletBase extends WalletBase createTransaction(
- Object credentials) async {
+ Future createTransaction(Object credentials) async {
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = [];
@@ -212,13 +221,11 @@ abstract class ElectrumWalletBase extends WalletBase item.sendAll
- || item.formattedCryptoAmount! <= 0)) {
+ if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
throw BitcoinTransactionWrongBalanceException(currency);
}
- credentialsAmount = outputs.fold(0, (acc, value) =>
- acc + value.formattedCryptoAmount!);
+ credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
@@ -235,9 +242,7 @@ abstract class ElectrumWalletBase extends WalletBase allAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
@@ -299,8 +304,8 @@ abstract class ElectrumWalletBase extends WalletBase json.encode({
- 'mnemonic': mnemonic,
- 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
- 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
- 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
- 'balance': balance[currency]?.toJSON()
- });
+ 'mnemonic': mnemonic,
+ 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
+ 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
+ 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
+ 'balance': balance[currency]?.toJSON()
+ });
int feeRate(TransactionPriority priority) {
try {
@@ -372,34 +370,29 @@ abstract class ElectrumWalletBase extends WalletBase
+ int feeAmountForPriority(
+ BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
- int feeAmountWithFeeRate(int feeRate, int inputsCount,
- int outputsCount) =>
+ int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
@override
- int calculateEstimatedFee(TransactionPriority? priority, int? amount,
- {int? outputsCount}) {
+ int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
if (priority is BitcoinTransactionPriority) {
- return calculateEstimatedFeeWithFeeRate(
- feeRate(priority),
- amount,
- outputsCount: outputsCount);
+ return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
+ outputsCount: outputsCount);
}
return 0;
}
- int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount,
- {int? outputsCount}) {
+ int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
int inputsCount = 0;
if (amount != null) {
@@ -428,8 +421,7 @@ abstract class ElectrumWalletBase extends WalletBase makePath() async =>
- pathForWallet(name: walletInfo.name, type: walletInfo.type);
+ Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future updateUnspent() async {
final unspent = await Future.wait(walletAddresses
.addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent
- .map((unspent) {
- try {
- return BitcoinUnspent.fromJSON(address, unspent);
- } catch(_) {
- return null;
- }
- }).whereNotNull())));
+ .map((unspent) {
+ try {
+ return BitcoinUnspent.fromJSON(address, unspent);
+ } catch(_) {
+ return null;
+ }
+ }).whereNotNull())));
unspentCoins = unspent.expand((e) => e).toList();
if (unspentCoinsInfo.isEmpty) {
@@ -506,8 +496,8 @@ abstract class ElectrumWalletBase extends WalletBase
- element.walletId.contains(id) && element.hash.contains(coin.hash));
+ final coinInfoList = unspentCoinsInfo.values
+ .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
@@ -526,14 +516,14 @@ abstract class ElectrumWalletBase extends WalletBase _addCoinInfo(BitcoinUnspent coin) async {
final newInfo = UnspentCoinsInfo(
- walletId: id,
- hash: coin.hash,
- isFrozen: coin.isFrozen,
- isSending: coin.isSending,
- noteRaw: coin.note,
- address: coin.address.address,
- value: coin.value,
- vout: coin.vout,
+ walletId: id,
+ hash: coin.hash,
+ isFrozen: coin.isFrozen,
+ isSending: coin.isSending,
+ noteRaw: coin.note,
+ address: coin.bitcoinAddressRecord.address,
+ value: coin.value,
+ vout: coin.vout,
);
await unspentCoinsInfo.add(newInfo);
@@ -542,8 +532,8 @@ abstract class ElectrumWalletBase extends WalletBase _refreshUnspentCoinsInfo() async {
try {
final List keys = [];
- final currentWalletUnspentCoins = unspentCoinsInfo.values
- .where((element) => element.walletId.contains(id));
+ final currentWalletUnspentCoins =
+ unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
if (currentWalletUnspentCoins.isNotEmpty) {
currentWalletUnspentCoins.forEach((element) {
@@ -579,27 +569,19 @@ abstract class ElectrumWalletBase extends WalletBase fetchTransactionInfo(
{required String hash, required int height}) async {
- try {
- final tx = await getTransactionExpanded(hash: hash, height: height);
- final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
- return ElectrumTransactionInfo.fromElectrumBundle(
- tx,
- walletInfo.type,
- networkType,
- addresses: addresses,
- height: height);
- } catch(_) {
- return null;
- }
+ try {
+ final tx = await getTransactionExpanded(hash: hash, height: height);
+ final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
+ return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
+ addresses: addresses, height: height);
+ } catch (_) {
+ return null;
+ }
}
@override
@@ -610,10 +592,8 @@ abstract class ElectrumWalletBase extends WalletBase electrumClient
- .getHistory(scriptHash)
- .then((history) => {scriptHash: history}));
+ final histories = addressHashes.keys.map((scriptHash) =>
+ electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
final historyResults = await Future.wait(histories);
historyResults.forEach((history) {
history.entries.forEach((historyItem) {
@@ -624,19 +604,16 @@ abstract class ElectrumWalletBase extends WalletBase>(
- {}, (acc, tx) {
+ final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
+ try {
+ return fetchTransactionInfo(
+ hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
+ } catch (_) {
+ return Future.value(null);
+ }
+ }));
+ return historiesWithDetails
+ .fold