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 1549bf4d8c.

* 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>
This commit is contained in:
Serhii 2023-10-13 01:50:16 +03:00 committed by GitHub
parent acb0517871
commit 66301ff247
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 1685 additions and 416 deletions

View file

@ -42,6 +42,7 @@ jobs:
cd cake_wallet/scripts/android/ cd cake_wallet/scripts/android/
./install_ndk.sh ./install_ndk.sh
source ./app_env.sh cakewallet source ./app_env.sh cakewallet
chmod +x pubspec_gen.sh
./app_config.sh ./app_config.sh
- name: Cache Externals - 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_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_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_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 .. 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 flutter packages pub run build_runner build --delete-conflicting-outputs
@ -141,18 +143,18 @@ jobs:
cd /opt/android/cake_wallet cd /opt/android/cake_wallet
flutter build apk --release flutter build apk --release
# - name: Push to App Center # - name: Push to App Center
# run: | # run: |
# echo 'Installing App Center CLI tools' # echo 'Installing App Center CLI tools'
# npm install -g appcenter-cli # npm install -g appcenter-cli
# echo "Publishing test to App Center" # echo "Publishing test to App Center"
# appcenter distribute release \ # appcenter distribute release \
# --group "Testers" \ # --group "Testers" \
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
# --release-notes ${GITHUB_HEAD_REF} \ # --release-notes ${GITHUB_HEAD_REF} \
# --app Cake-Labs/Cake-Wallet \ # --app Cake-Labs/Cake-Wallet \
# --token ${{ secrets.APP_CENTER_TOKEN }} \ # --token ${{ secrets.APP_CENTER_TOKEN }} \
# --quiet # --quiet
- name: Rename apk file - name: Rename apk file
run: | run: |

1
.gitignore vendored
View file

@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
lib/monero/monero.dart lib/monero/monero.dart
lib/haven/haven.dart lib/haven/haven.dart
lib/ethereum/ethereum.dart lib/ethereum/ethereum.dart
lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart lib/nano/nano.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png

View file

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.enableR8=true android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

View file

@ -0,0 +1,3 @@
-
uri: bitcoincash.stackwallet.com:50002
is_default: true

View file

@ -8,4 +8,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_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_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -1,5 +1,4 @@
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
//import 'package:cake_wallet/generated/i18n.dart';
class BitcoinTransactionPriority extends TransactionPriority { class BitcoinTransactionPriority extends TransactionPriority {
const BitcoinTransactionPriority({required String title, required int raw}) const BitcoinTransactionPriority({required String title, required int raw})
@ -100,4 +99,55 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority {
return label; return label;
} }
} }
class BitcoinCashTransactionPriority extends BitcoinTransactionPriority {
const BitcoinCashTransactionPriority({required String title, required int raw})
: super(title: title, raw: raw);
static const List<BitcoinCashTransactionPriority> 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;
}
}

View file

@ -1,24 +1,15 @@
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart';
class BitcoinUnspent { class BitcoinUnspent extends Unspent {
BitcoinUnspent(this.address, this.hash, this.value, this.vout) BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
: isSending = true, : bitcoinAddressRecord = addressRecord,
isFrozen = false, super(addressRecord.address, hash, value, vout, null);
note = '';
factory BitcoinUnspent.fromJSON( factory BitcoinUnspent.fromJSON(
BitcoinAddressRecord address, Map<String, dynamic> json) => BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int, BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
json['tx_pos'] as int); json['tx_pos'] as int);
final BitcoinAddressRecord address; final BitcoinAddressRecord bitcoinAddressRecord;
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;
} }

View file

@ -1,39 +1,34 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; 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/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'bitcoin_wallet_addresses.g.dart'; part 'bitcoin_wallet_addresses.g.dart';
class BitcoinWalletAddresses = BitcoinWalletAddressesBase class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
with _$BitcoinWalletAddresses;
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
with Store { BitcoinWalletAddressesBase(WalletInfo walletInfo,
BitcoinWalletAddressesBase(
WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd, {required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd, required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType, required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient, required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0, int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) int initialChangeAddressIndex = 0})
: super( : super(walletInfo,
walletInfo, initialAddresses: initialAddresses,
initialAddresses: initialAddresses, initialRegularAddressIndex: initialRegularAddressIndex,
initialRegularAddressIndex: initialRegularAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, mainHd: mainHd,
mainHd: mainHd, sideHd: sideHd,
sideHd: sideHd, electrumClient: electrumClient,
electrumClient: electrumClient, networkType: networkType);
networkType: networkType);
@override @override
String getAddress({required int index, required bitcoin.HDWallet hd}) => String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType); generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
} }

View file

@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -34,45 +36,52 @@ import 'package:cw_bitcoin/electrum.dart';
import 'package:hex/hex.dart'; import 'package:hex/hex.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:bip32/bip32.dart';
part 'electrum_wallet.g.dart'; part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance, abstract class ElectrumWalletBase
ElectrumTransactionHistory, ElectrumTransactionInfo> with Store { extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
with Store {
ElectrumWalletBase( ElectrumWalletBase(
{required String password, {required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required this.networkType, required this.networkType,
required this.mnemonic, required this.mnemonic,
required Uint8List seedBytes, required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient, ElectrumClient? electrumClient,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
CryptoCurrency? currency}) CryptoCurrency? currency})
: hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : hd = currency == CryptoCurrency.bch
.derivePath("m/0'/0"), ? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_feeRates = <int>[], _feeRates = <int>[],
_isTransactionUpdating = false, _isTransactionUpdating = false,
unspentCoins = [], unspentCoins = [],
_scripthashesUpdateSubject = {}, _scripthashesUpdateSubject = {},
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of( balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
currency != null ? {
? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, currency:
frozen: 0)} initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
: {}), }
: {}),
this.unspentCoinsInfo = unspentCoinsInfo, this.unspentCoinsInfo = unspentCoinsInfo,
super(walletInfo) { super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient(); this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo; this.walletInfo = walletInfo;
transactionHistory = transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
} }
static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
bitcoin.HDWallet.fromSeed(seedBytes)
.derivePath("m/44'/145'/0'/0");
static int estimatedTransactionSize(int inputsCount, int outputsCounts) => static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 146 + outputsCounts * 33 + 8; inputsCount * 146 + outputsCounts * 33 + 8;
@ -98,9 +107,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
.toList(); .toList();
List<String> get publicScriptHashes => walletAddresses.addresses List<String> get publicScriptHashes => walletAddresses.addresses
.where((addr) => !addr.isHidden) .where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, networkType: networkType)) .map((addr) => scriptHash(addr.address, networkType: networkType))
.toList(); .toList();
String get xpub => hd.base58!; String get xpub => hd.base58!;
@ -110,8 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
bitcoin.NetworkType networkType; bitcoin.NetworkType networkType;
@override @override
BitcoinWalletKeys get keys => BitcoinWalletKeys( BitcoinWalletKeys get keys =>
wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!); BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
String _password; String _password;
List<BitcoinUnspent> unspentCoins; List<BitcoinUnspent> unspentCoins;
@ -139,8 +148,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
await updateBalance(); await updateBalance();
_feeRates = await electrumClient.feeRates(); _feeRates = await electrumClient.feeRates();
Timer.periodic(const Duration(minutes: 1), Timer.periodic(
(timer) async => _feeRates = await electrumClient.feeRates()); const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
syncStatus = SyncedSyncStatus(); syncStatus = SyncedSyncStatus();
} catch (e, stacktrace) { } catch (e, stacktrace) {
@ -169,8 +178,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
@override @override
Future<PendingBitcoinTransaction> createTransaction( Future<PendingTransaction> createTransaction(Object credentials) async {
Object credentials) async {
const minAmount = 546; const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials; final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[]; final inputs = <BitcoinUnspent>[];
@ -204,13 +212,11 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
var fee = 0; var fee = 0;
if (hasMultiDestination) { if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
|| item.formattedCryptoAmount! <= 0)) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
} }
credentialsAmount = outputs.fold(0, (acc, value) => credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
acc + value.formattedCryptoAmount!);
if (allAmount - credentialsAmount < minAmount) { if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
@ -227,9 +233,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
} else { } else {
final output = outputs.first; final output = outputs.first;
credentialsAmount = !output.sendAll credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
? output.formattedCryptoAmount!
: 0;
if (credentialsAmount > allAmount) { if (credentialsAmount > allAmount) {
throw BitcoinTransactionWrongBalanceException(currency); throw BitcoinTransactionWrongBalanceException(currency);
@ -291,8 +295,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final p2wpkh = bitcoin final p2wpkh = bitcoin
.P2WPKH( .P2WPKH(
data: generatePaymentData( data: generatePaymentData(
hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.address.index), index: input.bitcoinAddressRecord.index),
network: networkType) network: networkType)
.data; .data;
@ -303,19 +307,12 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}); });
outputs.forEach((item) { outputs.forEach((item) {
final outputAmount = hasMultiDestination final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
? item.formattedCryptoAmount final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
: amount; txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
final outputAddress = item.isParsedAddress
? item.extractedAddress!
: item.address;
txb.addOutput(
addressToOutputScript(outputAddress, networkType),
outputAmount!);
}); });
final estimatedSize = final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
estimatedTransactionSize(inputs.length, outputs.length + 1);
var feeAmount = 0; var feeAmount = 0;
if (transactionCredentials.feeRate != null) { if (transactionCredentials.feeRate != null) {
@ -333,8 +330,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
for (var i = 0; i < inputs.length; i++) { for (var i = 0; i < inputs.length; i++) {
final input = inputs[i]; final input = inputs[i];
final keyPair = generateKeyPair( final keyPair = generateKeyPair(
hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.address.index, index: input.bitcoinAddressRecord.index,
network: networkType); network: networkType);
final witnessValue = input.isP2wpkh ? input.value : null; final witnessValue = input.isP2wpkh ? input.value : null;
@ -350,12 +347,12 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
String toJSON() => json.encode({ String toJSON() => json.encode({
'mnemonic': mnemonic, 'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(), 'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(), 'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(), 'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON() 'balance': balance[currency]?.toJSON()
}); });
int feeRate(TransactionPriority priority) { int feeRate(TransactionPriority priority) {
try { try {
@ -364,34 +361,29 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
return 0; return 0;
} catch(_) { } catch (_) {
return 0; return 0;
} }
} }
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int feeAmountForPriority(
int outputsCount) => BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount); feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
int feeAmountWithFeeRate(int feeRate, int inputsCount, int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
int outputsCount) =>
feeRate * estimatedTransactionSize(inputsCount, outputsCount); feeRate * estimatedTransactionSize(inputsCount, outputsCount);
@override @override
int calculateEstimatedFee(TransactionPriority? priority, int? amount, int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
{int? outputsCount}) {
if (priority is BitcoinTransactionPriority) { if (priority is BitcoinTransactionPriority) {
return calculateEstimatedFeeWithFeeRate( return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
feeRate(priority), outputsCount: outputsCount);
amount,
outputsCount: outputsCount);
} }
return 0; return 0;
} }
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
{int? outputsCount}) {
int inputsCount = 0; int inputsCount = 0;
if (amount != null) { if (amount != null) {
@ -420,8 +412,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
// If send all, then we have no change value // If send all, then we have no change value
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1); final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
return feeAmountWithFeeRate( return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
feeRate, inputsCount, _outputsCount);
} }
@override @override
@ -436,8 +427,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
final currentWalletFile = File(currentWalletPath); final currentWalletFile = File(currentWalletPath);
final currentDirPath = final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
await pathForWalletDir(name: walletInfo.name, type: type);
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName'); final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
// Copies current wallet files into new wallet name's dir and files // Copies current wallet files into new wallet name's dir and files
@ -474,21 +464,20 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} catch (_) {} } catch (_) {}
} }
Future<String> makePath() async => Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> updateUnspent() async { Future<void> updateUnspent() async {
final unspent = await Future.wait(walletAddresses final unspent = await Future.wait(walletAddresses
.addresses.map((address) => electrumClient .addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType) .getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent .then((unspent) => unspent
.map((unspent) { .map((unspent) {
try { try {
return BitcoinUnspent.fromJSON(address, unspent); return BitcoinUnspent.fromJSON(address, unspent);
} catch(_) { } catch(_) {
return null; return null;
} }
}).whereNotNull()))); }).whereNotNull())));
unspentCoins = unspent.expand((e) => e).toList(); unspentCoins = unspent.expand((e) => e).toList();
if (unspentCoinsInfo.isEmpty) { if (unspentCoinsInfo.isEmpty) {
@ -498,8 +487,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
if (unspentCoins.isNotEmpty) { if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) { unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) => final coinInfoList = unspentCoinsInfo.values
element.walletId.contains(id) && element.hash.contains(coin.hash)); .where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
if (coinInfoList.isNotEmpty) { if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first; final coinInfo = coinInfoList.first;
@ -518,14 +507,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<void> _addCoinInfo(BitcoinUnspent coin) async { Future<void> _addCoinInfo(BitcoinUnspent coin) async {
final newInfo = UnspentCoinsInfo( final newInfo = UnspentCoinsInfo(
walletId: id, walletId: id,
hash: coin.hash, hash: coin.hash,
isFrozen: coin.isFrozen, isFrozen: coin.isFrozen,
isSending: coin.isSending, isSending: coin.isSending,
noteRaw: coin.note, noteRaw: coin.note,
address: coin.address.address, address: coin.bitcoinAddressRecord.address,
value: coin.value, value: coin.value,
vout: coin.vout, vout: coin.vout,
); );
await unspentCoinsInfo.add(newInfo); await unspentCoinsInfo.add(newInfo);
@ -534,8 +523,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<void> _refreshUnspentCoinsInfo() async { Future<void> _refreshUnspentCoinsInfo() async {
try { try {
final List<dynamic> keys = <dynamic>[]; final List<dynamic> keys = <dynamic>[];
final currentWalletUnspentCoins = unspentCoinsInfo.values final currentWalletUnspentCoins =
.where((element) => element.walletId.contains(id)); unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
if (currentWalletUnspentCoins.isNotEmpty) { if (currentWalletUnspentCoins.isNotEmpty) {
currentWalletUnspentCoins.forEach((element) { currentWalletUnspentCoins.forEach((element) {
@ -571,27 +560,19 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
ins.add(tx); ins.add(tx);
} }
return ElectrumTransactionBundle( return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations);
original,
ins: ins,
time: time,
confirmations: confirmations);
} }
Future<ElectrumTransactionInfo?> fetchTransactionInfo( Future<ElectrumTransactionInfo?> fetchTransactionInfo(
{required String hash, required int height}) async { {required String hash, required int height}) async {
try { try {
final tx = await getTransactionExpanded(hash: hash, height: height); final tx = await getTransactionExpanded(hash: hash, height: height);
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
return ElectrumTransactionInfo.fromElectrumBundle( return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
tx, addresses: addresses, height: height);
walletInfo.type, } catch (_) {
networkType, return null;
addresses: addresses, }
height: height);
} catch(_) {
return null;
}
} }
@override @override
@ -602,10 +583,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final sh = scriptHash(addressRecord.address, networkType: networkType); final sh = scriptHash(addressRecord.address, networkType: networkType);
addressHashes[sh] = addressRecord; addressHashes[sh] = addressRecord;
}); });
final histories = final histories = addressHashes.keys.map((scriptHash) =>
addressHashes.keys.map((scriptHash) => electrumClient electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
.getHistory(scriptHash)
.then((history) => {scriptHash: history}));
final historyResults = await Future.wait(histories); final historyResults = await Future.wait(histories);
historyResults.forEach((history) { historyResults.forEach((history) {
history.entries.forEach((historyItem) { history.entries.forEach((historyItem) {
@ -616,19 +595,16 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
}); });
}); });
final historiesWithDetails = await Future.wait( final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
normalizedHistories try {
.map((transaction) { return fetchTransactionInfo(
try { hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
return fetchTransactionInfo( } catch (_) {
hash: transaction['tx_hash'] as String, return Future.value(null);
height: transaction['height'] as int); }
} catch(_) { }));
return Future.value(null); return historiesWithDetails
} .fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
}));
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
<String, ElectrumTransactionInfo>{}, (acc, tx) {
if (tx == null) { if (tx == null) {
return acc; return acc;
} }
@ -680,9 +656,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<ElectrumBalance> _fetchBalances() async { Future<ElectrumBalance> _fetchBalances() async {
final addresses = walletAddresses.addresses.toList(); final addresses = walletAddresses.addresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[]; final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) { for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i]; final addressRecord = addresses[i] ;
final sh = scriptHash(addressRecord.address, networkType: networkType); final sh = scriptHash(addressRecord.address, networkType: networkType);
final balanceFuture = electrumClient.getBalance(sh); final balanceFuture = electrumClient.getBalance(sh);
balanceFutures.add(balanceFuture); balanceFutures.add(balanceFuture);
@ -691,8 +666,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
var totalFrozen = 0; var totalFrozen = 0;
unspentCoinsInfo.values.forEach((info) { unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) { unspentCoins.forEach((element) {
if (element.hash == info.hash && info.isFrozen && element.address.address == info.address if (element.hash == info.hash &&
&& element.value == info.value) { info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value; totalFrozen += element.value;
} }
}); });
@ -715,8 +692,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} }
} }
return ElectrumBalance(confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, return ElectrumBalance(
frozen: totalFrozen); confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
} }
Future<void> updateBalance() async { Future<void> updateBalance() async {
@ -727,9 +704,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
String getChangeAddress() { String getChangeAddress() {
const minCountOfHiddenAddresses = 5; const minCountOfHiddenAddresses = 5;
final random = Random(); final random = Random();
var addresses = walletAddresses.addresses var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList();
.where((addr) => addr.isHidden)
.toList();
if (addresses.length < minCountOfHiddenAddresses) { if (addresses.length < minCountOfHiddenAddresses) {
addresses = walletAddresses.addresses.toList(); addresses = walletAddresses.addresses.toList();

View file

@ -1,9 +1,11 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart'; import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'electrum_wallet_addresses.g.dart'; part 'electrum_wallet_addresses.g.dart';
@ -38,6 +40,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const defaultChangeAddressesCount = 17; static const defaultChangeAddressesCount = 17;
static const gap = 20; static const gap = 20;
static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
final ObservableList<BitcoinAddressRecord> addresses; final ObservableList<BitcoinAddressRecord> addresses;
final ObservableList<BitcoinAddressRecord> receiveAddresses; final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses; final ObservableList<BitcoinAddressRecord> changeAddresses;
@ -50,10 +54,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@computed @computed
String get address { String get address {
if (receiveAddresses.isEmpty) { if (receiveAddresses.isEmpty) {
return generateNewAddress().address; final address = generateNewAddress().address;
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address;
} }
final receiveAddress = receiveAddresses.first.address;
return receiveAddresses.first.address; return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
} }
@override @override
@ -105,10 +111,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
Future<String> getChangeAddress() async { Future<String> getChangeAddress() async {
updateChangeAddresses(); updateChangeAddresses();
if (changeAddresses.isEmpty) { if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses( final newAddresses = await _createNewAddresses(gap,
gap,
hd: sideHd, hd: sideHd,
startIndex: totalCountOfChangeAddresses > 0 startIndex: totalCountOfChangeAddresses > 0
? totalCountOfChangeAddresses - 1 ? totalCountOfChangeAddresses - 1
@ -179,7 +184,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} else { } else {
addrs = await _createNewAddresses( addrs = await _createNewAddresses(
isHidden isHidden
? defaultChangeAddressesCount ? defaultChangeAddressesCount
: defaultReceiveAddressesCount, : defaultReceiveAddressesCount,
startIndex: 0, startIndex: 0,
hd: hd, hd: hd,

View file

@ -66,6 +66,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.6"
bitbox:
dependency: "direct main"
description:
path: "."
ref: master
resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
url: "https://github.com/cake-tech/bitbox-flutter.git"
source: git
version: "1.0.1"
bitcoin_flutter: bitcoin_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -23,6 +23,10 @@ dependencies:
git: git:
url: https://github.com/cake-tech/bitcoin_flutter.git url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3 ref: cake-update-v3
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git
ref: master
rxdart: ^0.27.5 rxdart: ^0.27.5
unorm_dart: ^0.2.0 unorm_dart: ^0.2.0
cryptography: ^2.0.5 cryptography: ^2.0.5

30
cw_bitcoin_cash/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

10
cw_bitcoin_cash/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: b06b8b2710955028a6b562f5aa6fe62941d6febf
channel: stable
project_type: package

View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
cw_bitcoin_cash/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

39
cw_bitcoin_cash/README.md Normal file
View file

@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,9 @@
library cw_bitcoin_cash;
export 'src/bitcoin_cash_base.dart';
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

View file

@ -0,0 +1,5 @@
import 'package:bitbox/bitbox.dart' as bitbox;
class AddressUtils {
static String getCashAddrFormat(String address) => bitbox.Address.toCashAddress(address);
}

View file

@ -0,0 +1,7 @@
export 'bitcoin_cash_wallet.dart';
export 'bitcoin_cash_wallet_addresses.dart';
export 'bitcoin_cash_wallet_creation_credentials.dart';
export 'bitcoin_cash_wallet_service.dart';
export 'exceptions/exceptions.dart';
export 'mnemonic.dart';
export 'bitcoin_cash_address_utils.dart';

View file

@ -0,0 +1,297 @@
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'bitcoin_cash_base.dart';
part 'bitcoin_cash_wallet.g.dart';
class BitcoinCashWallet = BitcoinCashWalletBase with _$BitcoinCashWallet;
abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
BitcoinCashWalletBase(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.bch) {
walletAddresses = BitcoinCashWalletAddresses(walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes)
.derivePath("m/44'/145'/0'/1"),
networkType: networkType);
}
static Future<BitcoinCashWallet> create(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) async {
return BitcoinCashWallet(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await Mnemonic.toSeed(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex);
}
static Future<BitcoinCashWallet> open({
required String name,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
return BitcoinCashWallet(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
}
@override
Future<PendingBitcoinCashTransaction> createTransaction(Object credentials) async {
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final outputs = transactionCredentials.outputs;
final hasMultiDestination = outputs.length > 1;
var allInputsAmount = 0;
if (unspentCoins.isEmpty) await updateUnspent();
for (final utx in unspentCoins) {
if (utx.isSending) {
allInputsAmount += utx.value;
inputs.add(utx);
}
}
if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
final allAmountFee = transactionCredentials.feeRate != null
? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
: feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
final allAmount = allInputsAmount - allAmountFee;
var credentialsAmount = 0;
var amount = 0;
var fee = 0;
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
throw BitcoinTransactionWrongBalanceException(currency);
}
credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
amount = credentialsAmount;
if (transactionCredentials.feeRate != null) {
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
outputsCount: outputs.length + 1);
} else {
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
outputsCount: outputs.length + 1);
}
} else {
final output = outputs.first;
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
if (credentialsAmount > allAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
amount = output.sendAll || allAmount - credentialsAmount < minAmount
? allAmount
: credentialsAmount;
if (output.sendAll || amount == allAmount) {
fee = allAmountFee;
} else if (transactionCredentials.feeRate != null) {
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
} else {
fee = calculateEstimatedFee(transactionCredentials.priority, amount);
}
}
if (fee == 0) {
throw BitcoinTransactionWrongBalanceException(currency);
}
final totalAmount = amount + fee;
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
final txb = bitbox.Bitbox.transactionBuilder(testnet: false);
final changeAddress = await walletAddresses.getChangeAddress();
var leftAmount = totalAmount;
var totalInputAmount = 0;
inputs.clear();
for (final utx in unspentCoins) {
if (utx.isSending) {
leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) {
break;
}
}
}
if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
if (amount <= 0 || totalInputAmount < totalAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
}
inputs.forEach((input) {
txb.addInput(input.hash, input.vout);
});
outputs.forEach((item) {
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
txb.addOutput(outputAddress, outputAmount!);
});
final estimatedSize = bitbox.BitcoinCash.getByteCount(inputs.length, outputs.length + 1);
var feeAmount = 0;
if (transactionCredentials.feeRate != null) {
feeAmount = transactionCredentials.feeRate! * estimatedSize;
} else {
feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
}
final changeValue = totalInputAmount - amount - feeAmount;
if (changeValue > minAmount) {
txb.addOutput(changeAddress, changeValue);
}
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
final keyPair = generateKeyPair(
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.bitcoinAddressRecord.index);
txb.sign(i, keyPair, input.value);
}
// Build the transaction
final tx = txb.build();
return PendingBitcoinCashTransaction(tx, type,
electrumClient: electrumClient, amount: amount, fee: fee);
}
bitbox.ECPair generateKeyPair(
{required bitcoin.HDWallet hd,
required int index}) =>
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
@override
int feeAmountForPriority(
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
int inputsCount = 0;
int totalValue = 0;
for (final input in unspentCoins) {
if (input.isSending) {
inputsCount++;
totalValue += input.value;
}
if (amount != null && totalValue >= amount) {
break;
}
}
if (amount != null && totalValue < amount) return 0;
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
}
@override
int feeRate(TransactionPriority priority) {
if (priority is BitcoinCashTransactionPriority) {
switch (priority) {
case BitcoinCashTransactionPriority.slow:
return 1;
case BitcoinCashTransactionPriority.medium:
return 5;
case BitcoinCashTransactionPriority.fast:
return 10;
}
}
return 0;
}
}

View file

@ -0,0 +1,34 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
part 'bitcoin_cash_wallet_addresses.g.dart';
class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$BitcoinCashWalletAddresses;
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
BitcoinCashWalletAddressesBase(WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
}

View file

@ -0,0 +1,26 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class BitcoinCashNewWalletCredentials extends WalletCredentials {
BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo})
: super(name: name, walletInfo: walletInfo);
}
class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials {
BitcoinCashRestoreWalletFromSeedCredentials(
{required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic;
}
class BitcoinCashRestoreWalletFromWIFCredentials extends WalletCredentials {
BitcoinCashRestoreWalletFromWIFCredentials(
{required String name, required String password, required this.wif, WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String wif;
}

View file

@ -0,0 +1,107 @@
import 'dart:io';
import 'package:bip39/bip39.dart';
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
BitcoinCashRestoreWalletFromSeedCredentials,
BitcoinCashRestoreWalletFromWIFCredentials> {
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
@override
WalletType getType() => WalletType.bitcoinCash;
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<BitcoinCashWallet> create(
credentials) async {
final wallet = await BitcoinCashWalletBase.create(
mnemonic: await Mnemonic.generate(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
await wallet.init();
return wallet;
}
@override
Future<BitcoinCashWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))!;
final wallet = await BitcoinCashWalletBase.open(
password: password, name: name, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
return wallet;
}
@override
Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType()))
.delete(recursive: true);
final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinCashWalletBase.open(
password: password,
name: currentName,
walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
await currentWallet.renameWalletFiles(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<BitcoinCashWallet>
restoreFromKeys(credentials) {
// TODO: implement restoreFromKeys
throw UnimplementedError('restoreFromKeys() is not implemented');
}
@override
Future<BitcoinCashWallet> restoreFromSeed(
BitcoinCashRestoreWalletFromSeedCredentials credentials) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinCashMnemonicIsIncorrectException();
}
final wallet = await BitcoinCashWalletBase.create(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.save();
await wallet.init();
return wallet;
}
}

View file

@ -0,0 +1,5 @@
class BitcoinCashMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Bitcoin Cash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}

View file

@ -0,0 +1 @@
export 'bitcoin_cash_mnemonic_is_incorrect_exception.dart';

View file

@ -0,0 +1,11 @@
import 'dart:typed_data';
import 'package:bip39/bip39.dart' as bip39;
class Mnemonic {
/// Generate bip39 mnemonic
static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
/// Create root seed from mnemonic
static Uint8List toSeed(String mnemonic) => bip39.mnemonicToSeed(mnemonic);
}

View file

@ -0,0 +1,62 @@
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/wallet_type.dart';
class PendingBitcoinCashTransaction with PendingTransaction {
PendingBitcoinCashTransaction(this._tx, this.type,
{required this.electrumClient,
required this.amount,
required this.fee})
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
final bitbox.Transaction _tx;
final ElectrumClient electrumClient;
final int amount;
final int fee;
@override
String get id => _tx.getId();
@override
String get hex => _tx.toHex();
@override
String get amountFormatted => bitcoinAmountToString(amount: amount);
@override
String get feeFormatted => bitcoinAmountToString(amount: fee);
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
@override
Future<void> commit() async {
final result =
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
if (result.isEmpty) {
throw BitcoinCommitTransactionException();
}
_listeners?.forEach((listener) => listener(transactionInfo()));
}
void addListener(
void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener);
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
id: id,
height: 0,
amount: amount,
direction: TransactionDirection.outgoing,
date: DateTime.now(),
isPending: true,
confirmations: 0,
fee: fee);
}

View file

@ -0,0 +1 @@
C:/Users/borod/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.0/

View file

@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}

View file

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View file

@ -0,0 +1,12 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}

View file

@ -0,0 +1,11 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=C:\Users\borod\flutter
FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=0.0.1
FLUTTER_BUILD_NUMBER=0.0.1
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View file

@ -0,0 +1,12 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=C:\Users\borod\flutter"
export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=0.0.1"
export "FLUTTER_BUILD_NUMBER=0.0.1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View file

@ -0,0 +1,76 @@
name: cw_bitcoin_cash
description: A new Flutter package project.
version: 0.0.1
publish_to: none
author: Cake Wallet
homepage: https://cakewallet.com
environment:
sdk: '>=2.19.0 <3.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
bip39: ^1.0.6
bip32: ^2.0.0
path_provider: ^2.0.11
mobx: ^2.0.7+4
flutter_mobx: ^2.0.6+1
cw_core:
path: ../cw_core
cw_bitcoin:
path: ../cw_bitcoin
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git
ref: master
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#

View file

@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}

View file

@ -0,0 +1,11 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}

View file

@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View file

@ -80,6 +80,7 @@ class AmountConverter {
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
return _moneroAmountToString(amount); return _moneroAmountToString(amount);
case CryptoCurrency.btc: case CryptoCurrency.btc:
case CryptoCurrency.bch:
return _bitcoinAmountToString(amount); return _bitcoinAmountToString(amount);
case CryptoCurrency.xhv: case CryptoCurrency.xhv:
case CryptoCurrency.xag: case CryptoCurrency.xag:

View file

@ -13,6 +13,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.xhv; return CryptoCurrency.xhv;
case WalletType.ethereum: case WalletType.ethereum:
return CryptoCurrency.eth; return CryptoCurrency.eth;
case WalletType.bitcoinCash:
return CryptoCurrency.bch;
case WalletType.nano: case WalletType.nano:
return CryptoCurrency.nano; return CryptoCurrency.nano;
case WalletType.banano: case WalletType.banano:

View file

@ -78,6 +78,8 @@ class Node extends HiveObject with Keyable {
return Uri.http(uriRaw, ''); return Uri.http(uriRaw, '');
case WalletType.ethereum: case WalletType.ethereum:
return Uri.https(uriRaw, ''); return Uri.https(uriRaw, '');
case WalletType.bitcoinCash:
return createUriFromElectrumAddress(uriRaw);
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
if (isSSL) { if (isSSL) {
@ -138,6 +140,8 @@ class Node extends HiveObject with Keyable {
return requestMoneroNode(); return requestMoneroNode();
case WalletType.ethereum: case WalletType.ethereum:
return requestElectrumServer(); return requestElectrumServer();
case WalletType.bitcoinCash:
return requestElectrumServer();
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
return requestNanoNode(); return requestNanoNode();

View file

@ -10,6 +10,7 @@ const walletTypes = [
WalletType.litecoin, WalletType.litecoin,
WalletType.haven, WalletType.haven,
WalletType.ethereum, WalletType.ethereum,
WalletType.bitcoinCash,
WalletType.nano, WalletType.nano,
WalletType.banano, WalletType.banano,
]; ];
@ -39,6 +40,10 @@ enum WalletType {
@HiveField(7) @HiveField(7)
banano, banano,
@HiveField(8)
bitcoinCash,
} }
int serializeToInt(WalletType type) { int serializeToInt(WalletType type) {
@ -57,6 +62,8 @@ int serializeToInt(WalletType type) {
return 5; return 5;
case WalletType.banano: case WalletType.banano:
return 6; return 6;
case WalletType.bitcoinCash:
return 7;
default: default:
return -1; return -1;
} }
@ -78,6 +85,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.nano; return WalletType.nano;
case 6: case 6:
return WalletType.banano; return WalletType.banano;
case 7:
return WalletType.bitcoinCash;
default: default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
} }
@ -95,6 +104,8 @@ String walletTypeToString(WalletType type) {
return 'Haven'; return 'Haven';
case WalletType.ethereum: case WalletType.ethereum:
return 'Ethereum'; return 'Ethereum';
case WalletType.bitcoinCash:
return 'Bitcoin Cash';
case WalletType.nano: case WalletType.nano:
return 'Nano'; return 'Nano';
case WalletType.banano: case WalletType.banano:
@ -116,6 +127,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Haven (XHV)'; return 'Haven (XHV)';
case WalletType.ethereum: case WalletType.ethereum:
return 'Ethereum (ETH)'; return 'Ethereum (ETH)';
case WalletType.bitcoinCash:
return 'Bitcoin Cash (BCH)';
case WalletType.nano: case WalletType.nano:
return 'Nano (XNO)'; return 'Nano (XNO)';
case WalletType.banano: case WalletType.banano:
@ -137,6 +150,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.xhv; return CryptoCurrency.xhv;
case WalletType.ethereum: case WalletType.ethereum:
return CryptoCurrency.eth; return CryptoCurrency.eth;
case WalletType.bitcoinCash:
return CryptoCurrency.bch;
case WalletType.nano: case WalletType.nano:
return CryptoCurrency.nano; return CryptoCurrency.nano;
case WalletType.banano: case WalletType.banano:

View file

@ -44,6 +44,7 @@ class CWBitcoin extends Bitcoin {
List<TransactionPriority> getTransactionPriorities() List<TransactionPriority> getTransactionPriorities()
=> BitcoinTransactionPriority.all; => BitcoinTransactionPriority.all;
@override
List<TransactionPriority> getLitecoinTransactionPriorities() List<TransactionPriority> getLitecoinTransactionPriorities()
=> LitecoinTransactionPriority.all; => LitecoinTransactionPriority.all;
@ -121,16 +122,9 @@ class CWBitcoin extends Bitcoin {
=> (priority as BitcoinTransactionPriority).labelWithRate(rate); => (priority as BitcoinTransactionPriority).labelWithRate(rate);
@override @override
List<Unspent> getUnspents(Object wallet) { List<BitcoinUnspent> getUnspents(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.unspentCoins return bitcoinWallet.unspentCoins;
.map((BitcoinUnspent bitcoinUnspent) => Unspent(
bitcoinUnspent.address.address,
bitcoinUnspent.hash,
bitcoinUnspent.value,
bitcoinUnspent.vout,
null))
.toList();
} }
void updateUnspents(Object wallet) async { void updateUnspents(Object wallet) async {

View file

@ -0,0 +1,45 @@
part of 'bitcoin_cash.dart';
class CWBitcoinCash extends BitcoinCash {
@override
String getMnemonic(int? strength) => Mnemonic.generate();
@override
Uint8List getSeedFromMnemonic(String seed) => Mnemonic.toSeed(seed);
@override
String getCashAddrFormat(String address) => AddressUtils.getCashAddrFormat(address);
@override
WalletService createBitcoinCashWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
return BitcoinCashWalletService(walletInfoSource, unspentCoinSource);
}
@override
WalletCredentials createBitcoinCashNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
}) =>
BitcoinCashNewWalletCredentials(name: name, walletInfo: walletInfo);
@override
WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
{required String name, required String mnemonic, required String password}) =>
BitcoinCashRestoreWalletFromSeedCredentials(
name: name, mnemonic: mnemonic, password: password);
@override
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>
BitcoinCashTransactionPriority.deserialize(raw: raw);
@override
TransactionPriority getDefaultTransactionPriority() => BitcoinCashTransactionPriority.medium;
@override
List<TransactionPriority> getTransactionPriorities() => BitcoinCashTransactionPriority.all;
@override
TransactionPriority getBitcoinCashTransactionPrioritySlow() =>
BitcoinCashTransactionPriority.slow;
}

View file

@ -27,6 +27,8 @@ class OnRamperBuyProvider {
return "LTC_LITECOIN"; return "LTC_LITECOIN";
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
return "XMR_MONERO"; return "XMR_MONERO";
case CryptoCurrency.bch:
return "BCH_BITCOINCASH";
case CryptoCurrency.nano: case CryptoCurrency.nano:
return "XNO_NANO"; return "XNO_NANO";
default: default:

View file

@ -88,7 +88,9 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.dai: case CryptoCurrency.dai:
case CryptoCurrency.dash: case CryptoCurrency.dash:
case CryptoCurrency.eos: case CryptoCurrency.eos:
return '[0-9a-zA-Z]';
case CryptoCurrency.bch: case CryptoCurrency.bch:
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q[0-9a-zA-Z]{42}\$|^bitcoincash:q[0-9a-zA-Z]{41}\$|^bitcoincash:q[0-9a-zA-Z]{42}\$';
case CryptoCurrency.bnb: case CryptoCurrency.bnb:
return '[0-9a-zA-Z]'; return '[0-9a-zA-Z]';
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
@ -172,7 +174,9 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.steth: case CryptoCurrency.steth:
case CryptoCurrency.shib: case CryptoCurrency.shib:
case CryptoCurrency.avaxc: case CryptoCurrency.avaxc:
return [42];
case CryptoCurrency.bch: case CryptoCurrency.bch:
return [42, 43, 44, 54, 55];
case CryptoCurrency.bnb: case CryptoCurrency.bnb:
return [42]; return [42];
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
@ -271,6 +275,11 @@ class AddressValidator extends TextValidator {
return 'nano_[0-9a-zA-Z]{60}'; return 'nano_[0-9a-zA-Z]{60}';
case CryptoCurrency.banano: case CryptoCurrency.banano:
return 'ban_[0-9a-zA-Z]{60}'; return 'ban_[0-9a-zA-Z]{60}';
case CryptoCurrency.bch:
return 'bitcoincash:q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
'|bitcoincash:q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{41}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
default: default:
return null; return null;
} }

View file

@ -29,6 +29,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return haven!.getMoneroWordList(language); return haven!.getMoneroWordList(language);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.getEthereumWordList(language); return ethereum!.getEthereumWordList(language);
case WalletType.bitcoinCash:
return getBitcoinWordList(language);
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
return nano!.getNanoWordList(language); return nano!.getNanoWordList(language);

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
@ -820,6 +821,8 @@ Future<void> setup({
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource); return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumWalletService(_walletInfoSource); return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource); return nano!.createNanoWalletService(_walletInfoSource);
default: default:

View file

@ -26,6 +26,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to';
@ -81,7 +82,10 @@ Future<void> defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault( await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); await changeHavenCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeBitcoinCashCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 2: case 2:
@ -166,6 +170,11 @@ Future<void> defaultSettingsMigration(
await changeNanoCurrentPowNodeToDefault( await changeNanoCurrentPowNodeToDefault(
sharedPreferences: sharedPreferences, nodes: powNodes); sharedPreferences: sharedPreferences, nodes: powNodes);
break; break;
case 23:
await addBitcoinCashElectrumServerList(nodes: nodes);
await changeBitcoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
default: default:
break; break;
@ -323,6 +332,12 @@ Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano)); nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
} }
Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
}
Node getMoneroDefaultNode({required Box<Node> nodes}) { Node getMoneroDefaultNode({required Box<Node> nodes}) {
final timeZone = DateTime.now().timeZoneOffset.inHours; final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = ''; var nodeUri = '';
@ -358,6 +373,15 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId); await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId);
} }
Future<void> changeBitcoinCashCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
final server = getBitcoinCashDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, serverId);
}
Future<void> changeHavenCurrentNodeToDefault( Future<void> changeHavenCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async { {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getHavenDefaultNode(nodes: nodes); final node = getHavenDefaultNode(nodes: nodes);
@ -411,6 +435,15 @@ Future<void> addLitecoinElectrumServerList({required Box<Node> nodes}) async {
} }
} }
Future<void> addBitcoinCashElectrumServerList({required Box<Node> nodes}) async {
final serverList = await loadBitcoinCashElectrumServerList();
for (var node in serverList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> addHavenNodeList({required Box<Node> nodes}) async { Future<void> addHavenNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultHavenNodes(); final nodeList = await loadDefaultHavenNodes();
for (var node in nodeList) { for (var node in nodeList) {
@ -497,27 +530,34 @@ Future<void> checkCurrentNodes(
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentBitcoinElectrumSeverId = final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentLitecoinElectrumSeverId = final currentLitecoinElectrumSeverId = sharedPreferences
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final currentHavenNodeId = sharedPreferences
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); .getInt(PreferencesKey.currentHavenNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final currentEthereumNodeId = sharedPreferences
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); .getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentMoneroNode = final currentNanoNodeId = sharedPreferences
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId); .getInt(PreferencesKey.currentNanoNodeIdKey);
final currentBitcoinElectrumServer = final currentNanoPowNodeId = sharedPreferences
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId); .getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentLitecoinElectrumServer = final currentBitcoinCashNodeId = sharedPreferences
nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId); .getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final currentHavenNodeServer = final currentMoneroNode = nodeSource.values.firstWhereOrNull(
nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId); (node) => node.key == currentMoneroNodeId);
final currentEthereumNodeServer = final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); (node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentEthereumNodeId);
final currentNanoNodeServer = final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer = final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId); powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentBitcoinCashNodeId);
if (currentMoneroNode == null) { if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode); await nodeSource.add(newCakeWalletNode);
@ -565,6 +605,13 @@ Future<void> checkCurrentNodes(
} }
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int); await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
} }
if (currentBitcoinCashNodeServer == null) {
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
}
} }
Future<void> resetBitcoinElectrumServer( Future<void> resetBitcoinElectrumServer(

View file

@ -52,6 +52,7 @@ class MainActions {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.bitcoinCash:
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:
switch (defaultBuyProvider) { switch (defaultBuyProvider) {
@ -123,6 +124,7 @@ class MainActions {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.bitcoinCash:
if (viewModel.isEnabledSellAction) { if (viewModel.isEnabledSellAction) {
final moonPaySellProvider = MoonPaySellProvider(); final moonPaySellProvider = MoonPaySellProvider();
final uri = await moonPaySellProvider.requestUrl( final uri = await moonPaySellProvider.requestUrl(

View file

@ -84,6 +84,23 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
return nodes; return nodes;
} }
Future<List<Node>> loadBitcoinCashElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
for (final raw in loadedServerList) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.bitcoinCash;
serverList.add(node);
}
}
return serverList;
}
Future<List<Node>> loadDefaultNanoNodes() async { Future<List<Node>> loadDefaultNanoNodes() async {
final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml'); final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList; final loadedNodes = loadYaml(nodesRaw) as YamlList;
@ -116,10 +133,11 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
return nodes; return nodes;
} }
Future resetToDefault(Box<Node> nodeSource) async { Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes(); final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList(); final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList();
final havenNodes = await loadDefaultHavenNodes(); final havenNodes = await loadDefaultHavenNodes();
final ethereumNodes = await loadDefaultEthereumNodes(); final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes(); final nanoNodes = await loadDefaultNanoNodes();
@ -129,13 +147,14 @@ Future resetToDefault(Box<Node> nodeSource) async {
litecoinElectrumServerList + litecoinElectrumServerList +
havenNodes + havenNodes +
ethereumNodes + ethereumNodes +
bitcoinCashElectrumServerList +
nanoNodes; nanoNodes;
await nodeSource.clear(); await nodeSource.clear();
await nodeSource.addAll(nodes); await nodeSource.addAll(nodes);
} }
Future resetPowToDefault(Box<Node> powNodeSource) async { Future<void> resetPowToDefault(Box<Node> powNodeSource) async {
final nanoPowNodes = await loadDefaultNanoPowNodes(); final nanoPowNodes = await loadDefaultNanoPowNodes();
final nodes = nanoPowNodes; final nodes = nanoPowNodes;
await powNodeSource.clear(); await powNodeSource.clear();

View file

@ -11,6 +11,7 @@ class PreferencesKey {
static const currentBananoNodeIdKey = 'current_node_id_banano'; static const currentBananoNodeIdKey = 'current_node_id_banano';
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow'; static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
static const currentFiatCurrencyKey = 'current_fiat_currency'; static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentBitcoinCashNodeIdKey = 'current_node_id_bch';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const shouldSaveRecipientAddressKey = 'save_recipient_address';
@ -36,6 +37,7 @@ class PreferencesKey {
static const havenTransactionPriority = 'current_fee_priority_haven'; static const havenTransactionPriority = 'current_fee_priority_haven';
static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup'; static const shouldShowYatPopup = 'should_show_yat_popup';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
@ -17,6 +18,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return haven!.getTransactionPriorities(); return haven!.getTransactionPriorities();
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.getTransactionPriorities(); return ethereum!.getTransactionPriorities();
case WalletType.bitcoinCash:
return bitcoinCash!.getTransactionPriorities();
// no such thing for nano/banano: // no such thing for nano/banano:
case WalletType.nano: case WalletType.nano:
case WalletType.banano: case WalletType.banano:

View file

@ -50,6 +50,9 @@ class CWEthereum extends Ethereum {
@override @override
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium; TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
@override
TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow;
@override @override
List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all; List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;

View file

@ -159,7 +159,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 22); initialMigrationVersion: 23);
} }
Future<void> initialSetup( Future<void> initialSetup(

View file

@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24); final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24); final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
@ -143,6 +144,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return havenIcon; return havenIcon;
case WalletType.ethereum: case WalletType.ethereum:
return ethereumIcon; return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano: case WalletType.nano:
return nanoIcon; return nanoIcon;
case WalletType.banano: case WalletType.banano:

View file

@ -31,7 +31,8 @@ class MenuWidgetState extends State<MenuWidget> {
this.havenIcon = Image.asset('assets/images/haven_menu.png'), this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'), this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
this.nanoIcon = Image.asset('assets/images/nano_icon.png'), this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
this.bananoIcon = Image.asset('assets/images/nano_icon.png'); this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png');
final largeScreen = 731; final largeScreen = 731;
@ -50,10 +51,10 @@ class MenuWidgetState extends State<MenuWidget> {
Image litecoinIcon; Image litecoinIcon;
Image havenIcon; Image havenIcon;
Image ethereumIcon; Image ethereumIcon;
Image bitcoinCashIcon;
Image nanoIcon; Image nanoIcon;
Image bananoIcon; Image bananoIcon;
@override @override
void initState() { void initState() {
menuWidth = 0; menuWidth = 0;
@ -212,6 +213,8 @@ class MenuWidgetState extends State<MenuWidget> {
return havenIcon; return havenIcon;
case WalletType.ethereum: case WalletType.ethereum:
return ethereumIcon; return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano: case WalletType.nano:
return nanoIcon; return nanoIcon;
case WalletType.banano: case WalletType.banano:

View file

@ -73,6 +73,7 @@ class PreSeedPage extends BasePage {
case WalletType.monero: case WalletType.monero:
return 25; return 25;
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.bitcoinCash:
return 12; return 12;
default: default:
return 24; return 24;

View file

@ -1,8 +1,10 @@
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart'; import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
@ -79,6 +81,9 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
itemBuilder: (_, int index) { itemBuilder: (_, int index) {
return Observer(builder: (_) { return Observer(builder: (_) {
final item = unspentCoinsListViewModel.items[index]; final item = unspentCoinsListViewModel.items[index];
final address = unspentCoinsListViewModel.wallet.type == WalletType.bitcoinCash
? bitcoinCash!.getCashAddrFormat(item.address)
: item.address;
return GestureDetector( return GestureDetector(
onTap: () => onTap: () =>
@ -88,7 +93,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
child: UnspentCoinsListItem( child: UnspentCoinsListItem(
note: item.note, note: item.note,
amount: item.amount, amount: item.amount,
address: item.address, address: address,
isSending: item.isSending, isSending: item.isSending,
isFrozen: item.isFrozen, isFrozen: item.isFrozen,
onCheckBoxTap: item.isFrozen onCheckBoxTap: item.isFrozen

View file

@ -48,6 +48,7 @@ class WalletListBodyState extends State<WalletListBody> {
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24); final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final scrollController = ScrollController(); final scrollController = ScrollController();
final double tileHeight = 60; final double tileHeight = 60;
@ -243,6 +244,8 @@ class WalletListBodyState extends State<WalletListBody> {
return havenIcon; return havenIcon;
case WalletType.ethereum: case WalletType.ethereum:
return ethereumIcon; return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano: case WalletType.nano:
return nanoIcon; return nanoIcon;
default: default:

View file

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
@ -85,7 +86,8 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialHavenTransactionPriority,
TransactionPriority? initialLitecoinTransactionPriority, TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority}) TransactionPriority? initialEthereumTransactionPriority,
TransactionPriority? initialBitcoinCashTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes), : nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(powNodes), powNodes = ObservableMap<WalletType, Node>.of(powNodes),
_sharedPreferences = sharedPreferences, _sharedPreferences = sharedPreferences,
@ -146,6 +148,10 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.ethereum] = initialEthereumTransactionPriority; priority[WalletType.ethereum] = initialEthereumTransactionPriority;
} }
if (initialBitcoinCashTransactionPriority != null) {
priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
}
reaction( reaction(
(_) => fiatCurrency, (_) => fiatCurrency,
(FiatCurrency fiatCurrency) => sharedPreferences.setString( (FiatCurrency fiatCurrency) => sharedPreferences.setString(
@ -174,6 +180,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
key = PreferencesKey.ethereumTransactionPriority; key = PreferencesKey.ethereumTransactionPriority;
break; break;
case WalletType.bitcoinCash:
key = PreferencesKey.bitcoinCashTransactionPriority;
break;
default: default:
key = null; key = null;
} }
@ -526,12 +535,13 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority( TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!); raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!);
TransactionPriority? bitcoinTransactionPriority = TransactionPriority? bitcoinTransactionPriority =
bitcoin?.deserializeBitcoinTransactionPriority( bitcoin?.deserializeBitcoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!); sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!);
TransactionPriority? havenTransactionPriority; TransactionPriority? havenTransactionPriority;
TransactionPriority? litecoinTransactionPriority; TransactionPriority? litecoinTransactionPriority;
TransactionPriority? ethereumTransactionPriority; TransactionPriority? ethereumTransactionPriority;
TransactionPriority? bitcoinCashTransactionPriority;
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority( havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@ -545,12 +555,17 @@ abstract class SettingsStoreBase with Store {
ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!); sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
} }
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
}
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority(); moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority(); bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
havenTransactionPriority ??= monero?.getDefaultTransactionPriority(); havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium(); litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority(); ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@ -560,7 +575,8 @@ abstract class SettingsStoreBase with Store {
final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false; final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false;
final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false; final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(
PreferencesKey.defaultBuyProvider) ?? 0];
final currentFiatApiMode = FiatApiMode.deserialize( final currentFiatApiMode = FiatApiMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
FiatApiMode.enabled.raw); FiatApiMode.enabled.raw);
@ -579,7 +595,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
false; false;
final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
false; false;
final shouldRequireTOTP2FAForAddingContacts = final shouldRequireTOTP2FAForAddingContacts =
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
@ -587,7 +603,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
false; false;
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
false; false;
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false; final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? ''; final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
@ -612,7 +628,7 @@ abstract class SettingsStoreBase with Store {
? PinCodeRequiredDuration.deserialize(raw: timeOutDuration) ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
: defaultPinCodeTimeOutDuration; : defaultPinCodeTimeOutDuration;
final sortBalanceBy = final sortBalanceBy =
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0]; SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
final pinNativeTokenAtTop = final pinNativeTokenAtTop =
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
@ -626,9 +642,11 @@ abstract class SettingsStoreBase with Store {
await LanguageService.localeDetection(); await LanguageService.localeDetection();
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId = final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final litecoinElectrumServerId = final litecoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final bitcoinCashElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
@ -638,13 +656,14 @@ abstract class SettingsStoreBase with Store {
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId); final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId); final ethereumNode = nodeSource.get(ethereumNodeId);
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId); final nanoNode = nodeSource.get(nanoNodeId);
final nanoPowNode = powNodeSource.get(nanoPowNodeId); final nanoPowNode = powNodeSource.get(nanoPowNodeId);
final packageInfo = await PackageInfo.fromPlatform(); final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? ''; final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final generateSubaddresses = final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
final autoGenerateSubaddressStatus = generateSubaddresses != null final autoGenerateSubaddressStatus = generateSubaddresses != null
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
@ -672,70 +691,76 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode; nodes[WalletType.ethereum] = ethereumNode;
} }
if (bitcoinCashElectrumServer != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
}
if (nanoNode != null) { if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode; nodes[WalletType.nano] = nanoNode;
} }
if (nanoPowNode != null) { if (nanoPowNode != null) {
powNodes[WalletType.nano] = nanoPowNode; powNodes[WalletType.nano] = nanoPowNode;
} }
final savedSyncMode = SyncMode.all.firstWhere((element) { final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
}); });
final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
return SettingsStore( return SettingsStore(
sharedPreferences: sharedPreferences, sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes, nodes: nodes,
powNodes: powNodes, powNodes: powNodes,
appVersion: packageInfo.version, appVersion: packageInfo.version,
deviceName: deviceName, deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled, isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency, initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode, initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress, initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialAppSecure: isAppSecure, initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy, initialDisableBuy: disableBuy,
initialDisableSell: disableSell, initialDisableSell: disableSell,
initialDefaultBuyProvider: defaultBuyProvider, initialDefaultBuyProvider: defaultBuyProvider,
initialFiatMode: currentFiatApiMode, initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset, initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA, initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey, initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber, initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus, initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme, initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode, actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength, initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration, pinTimeOutDuration: pinCodeTimeOutDuration,
initialLanguageCode: savedLanguageCode, initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy, sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop, pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan, useEtherscan: useEtherscan,
initialMoneroTransactionPriority: moneroTransactionPriority, initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority, initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets: initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
shouldRequireTOTP2FAForSendsToInternalWallets, initialShouldRequireTOTP2FAForSendsToInternalWallets:
initialShouldRequireTOTP2FAForExchangesToInternalWallets: shouldRequireTOTP2FAForSendsToInternalWallets,
shouldRequireTOTP2FAForExchangesToInternalWallets, initialShouldRequireTOTP2FAForExchangesToInternalWallets:
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
shouldRequireTOTP2FAForAllSecurityAndBackupSettings, initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
initialEthereumTransactionPriority: ethereumTransactionPriority, shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
backgroundTasks: backgroundTasks, initialEthereumTransactionPriority: ethereumTransactionPriority,
initialSyncMode: savedSyncMode, backgroundTasks: backgroundTasks,
initialSyncAll: savedSyncAll, initialSyncMode: savedSyncMode,
shouldShowYatPopup: shouldShowYatPopup); initialSyncAll: savedSyncAll,
} shouldShowYatPopup: shouldShowYatPopup);
}
Future<void> reload({required Box<Node> nodeSource}) async { Future<void> reload({required Box<Node> nodeSource}) async {
final sharedPreferences = await getIt.getAsync<SharedPreferences>(); final sharedPreferences = await getIt.getAsync<SharedPreferences>();
@ -744,30 +769,35 @@ abstract class SettingsStoreBase with Store {
raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!); raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority( priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
priority[WalletType.monero]!; priority[WalletType.monero]!;
priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority( priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ?? sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
priority[WalletType.bitcoin]!; priority[WalletType.bitcoin]!;
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority( priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ?? raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
priority[WalletType.haven]!; priority[WalletType.haven]!;
} }
if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) { if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) {
priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority( priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ?? sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
priority[WalletType.litecoin]!; priority[WalletType.litecoin]!;
} }
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority( priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
priority[WalletType.ethereum]!; priority[WalletType.ethereum]!;
} }
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ??
priority[WalletType.bitcoinCash]!;
}
final generateSubaddresses = final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
autoGenerateSubaddressStatus = generateSubaddresses != null autoGenerateSubaddressStatus = generateSubaddresses != null
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses) ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
@ -785,7 +815,8 @@ abstract class SettingsStoreBase with Store {
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; defaultBuyProvider =
BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
allowBiometricalAuthentication = allowBiometricalAuthentication =
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
allowBiometricalAuthentication; allowBiometricalAuthentication;
@ -802,7 +833,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ?? sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
false; false;
shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ?? .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
false; false;
shouldRequireTOTP2FAForAddingContacts = shouldRequireTOTP2FAForAddingContacts =
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false; sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
@ -810,7 +841,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ?? sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
false; false;
shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ?? .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
false; false;
shouldShowMarketPlaceInDashboard = shouldShowMarketPlaceInDashboard =
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
@ -846,9 +877,11 @@ abstract class SettingsStoreBase with Store {
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId = final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final litecoinElectrumServerId = final litecoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final bitcoinCashElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
@ -858,6 +891,7 @@ abstract class SettingsStoreBase with Store {
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId); final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId); final ethereumNode = nodeSource.get(ethereumNodeId);
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId); final nanoNode = nodeSource.get(nanoNodeId);
if (moneroNode != null) { if (moneroNode != null) {
@ -880,6 +914,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode; nodes[WalletType.ethereum] = ethereumNode;
} }
if (bitcoinCashNode != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashNode;
}
if (nanoNode != null) { if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode; nodes[WalletType.nano] = nanoNode;
} }
@ -904,6 +942,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int); await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
break; break;
case WalletType.bitcoinCash:
await _sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
break;
case WalletType.nano: case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int); await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
break; break;

View file

@ -72,6 +72,7 @@ class TransactionListItem extends ActionListItem with Keyable {
break; break;
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash:
amount = calculateFiatAmountRaw( amount = calculateFiatAmountRaw(
cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount), cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
price: price); price: price);

View file

@ -2,10 +2,12 @@ import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/exolix/exolix_request.dart'; import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
@ -265,8 +267,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
} }
bool get hasAllAmount => bool get hasAllAmount =>
(wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) && (wallet.type == WalletType.bitcoin ||
depositCurrency == wallet.currency; wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash) &&
depositCurrency == wallet.currency;
bool get isMoneroWallet => wallet.type == WalletType.monero; bool get isMoneroWallet => wallet.type == WalletType.monero;
@ -278,7 +282,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
case WalletType.bitcoin: case WalletType.bitcoin:
return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow(); return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow();
case WalletType.litecoin: case WalletType.litecoin:
return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow(); return transactionPriority ==
bitcoin!.getLitecoinTransactionPrioritySlow();
case WalletType.ethereum:
return transactionPriority ==
ethereum!.getEthereumTransactionPrioritySlow();
case WalletType.bitcoinCash:
return transactionPriority ==
bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
default: default:
return false; return false;
} }
@ -619,7 +630,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action @action
void calculateDepositAllAmount() { void calculateDepositAllAmount() {
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) {
final availableBalance = wallet.balance[wallet.currency]!.available; final availableBalance = wallet.balance[wallet.currency]!.available;
final priority = _settingsStore.priority[wallet.type]!; final priority = _settingsStore.priority[wallet.type]!;
final fee = wallet.calculateEstimatedFee(priority, null); final fee = wallet.calculateEstimatedFee(priority, null);
@ -694,6 +705,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.ltc; depositCurrency = CryptoCurrency.ltc;
receiveCurrency = CryptoCurrency.xmr; receiveCurrency = CryptoCurrency.xmr;
break; break;
case WalletType.bitcoinCash:
depositCurrency = CryptoCurrency.bch;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.haven: case WalletType.haven:
depositCurrency = CryptoCurrency.xhv; depositCurrency = CryptoCurrency.xhv;
receiveCurrency = CryptoCurrency.btc; receiveCurrency = CryptoCurrency.btc;
@ -789,6 +804,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
case WalletType.litecoin: case WalletType.litecoin:
_settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium(); _settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium();
break; break;
case WalletType.ethereum:
_settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
break;
case WalletType.bitcoinCash:
_settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
break;
default: default:
break; break;
} }

View file

@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
node = getEthereumDefaultNode(nodes: _nodeSource)!; node = getEthereumDefaultNode(nodes: _nodeSource)!;
break; break;
case WalletType.bitcoinCash:
node = getBitcoinCashDefaultElectrumServer(nodes: _nodeSource)!;
break;
case WalletType.nano: case WalletType.nano:
node = getNanoDefaultNode(nodes: _nodeSource)!; node = getNanoDefaultNode(nodes: _nodeSource)!;
break; break;

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
@ -84,6 +85,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials( return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);

View file

@ -72,8 +72,13 @@ class WalletRestoreFromQRCode {
case 'litecoin': case 'litecoin':
case 'litecoin-wallet': case 'litecoin-wallet':
return WalletType.litecoin; return WalletType.litecoin;
case 'bitcoincash':
case 'bitcoinCash-wallet':
return WalletType.bitcoinCash;
case 'ethereum-wallet': case 'ethereum-wallet':
return WalletType.ethereum; return WalletType.ethereum;
case 'nano-wallet':
return WalletType.nano;
default: default:
throw Exception('Unexpected wallet type: ${scheme.toString()}'); throw Exception('Unexpected wallet type: ${scheme.toString()}');
} }
@ -107,6 +112,7 @@ class WalletRestoreFromQRCode {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.bitcoinCash:
RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b'); RegExp regex24 = RegExp(r'\b(\S+\b\s+){23}\S+\b');
RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b'); RegExp regex18 = RegExp(r'\b(\S+\b\s+){17}\S+\b');
RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b'); RegExp regex12 = RegExp(r'\b(\S+\b\s+){11}\S+\b');

View file

@ -81,10 +81,8 @@ abstract class OutputBase with Store {
_amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount); _amount = monero!.formatterMoneroParseAmount(amount: _cryptoAmount);
break; break;
case WalletType.bitcoin: case WalletType.bitcoin:
_amount =
bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
break;
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash:
_amount = _amount =
bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
break; break;
@ -116,7 +114,8 @@ abstract class OutputBase with Store {
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount); _settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
if (_wallet.type == WalletType.bitcoin || if (_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.litecoin) { _wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash) {
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
} }
@ -234,6 +233,9 @@ abstract class OutputBase with Store {
case WalletType.litecoin: case WalletType.litecoin:
maximumFractionDigits = 8; maximumFractionDigits = 8;
break; break;
case WalletType.bitcoinCash:
maximumFractionDigits = 8;
break;
case WalletType.haven: case WalletType.haven:
maximumFractionDigits = 12; maximumFractionDigits = 12;
break; break;

View file

@ -185,12 +185,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed @computed
bool get hasCoinControl => bool get hasCoinControl =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin || wallet.type == WalletType.litecoin ||
wallet.type == WalletType.monero; wallet.type == WalletType.monero ||
wallet.type == WalletType.bitcoinCash;
@computed @computed
bool get isElectrumWallet => bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash;
@computed @computed
bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano; bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
@ -345,41 +348,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
_settingsStore.priority[wallet.type] = priority; _settingsStore.priority[wallet.type] = priority;
Object _credentials() { Object _credentials() {
final priority = _settingsStore.priority[wallet.type];
if (priority == null) throw Exception('Priority is null for wallet type: ${wallet.type}');
switch (wallet.type) { switch (wallet.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
final priority = _settingsStore.priority[wallet.type];
if (priority == null) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority);
case WalletType.litecoin: case WalletType.litecoin:
final priority = _settingsStore.priority[wallet.type]; case WalletType.bitcoinCash:
if (priority == null) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority); return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority);
case WalletType.monero: case WalletType.monero:
final priority = _settingsStore.priority[wallet.type];
if (priority == null) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
return monero! return monero!
.createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority); .createMoneroTransactionCreationCredentials(outputs: outputs, priority: priority);
case WalletType.haven: case WalletType.haven:
final priority = _settingsStore.priority[wallet.type];
if (priority == null) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
return haven!.createHavenTransactionCreationCredentials( return haven!.createHavenTransactionCreationCredentials(
outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title); outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
case WalletType.ethereum: case WalletType.ethereum:
final priority = _settingsStore.priority[wallet.type]; final priority = _settingsStore.priority[wallet.type];
@ -390,9 +376,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return ethereum!.createEthereumTransactionCredentials(outputs, return ethereum!.createEthereumTransactionCredentials(outputs,
priority: priority, currency: selectedCryptoCurrency); priority: priority, currency: selectedCryptoCurrency);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoTransactionCredentials( return nano!.createNanoTransactionCredentials(outputs);
outputs,
);
default: default:
throw Exception('Unexpected wallet type: ${wallet.type}'); throw Exception('Unexpected wallet type: ${wallet.type}');
} }

View file

@ -63,7 +63,9 @@ abstract class OtherSettingsViewModelBase with Store {
String getDisplayPriority(dynamic priority) { String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority; final _priority = priority as TransactionPriority;
if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin) { if (_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash) {
final rate = bitcoin!.getFeeRate(_wallet, _priority); final rate = bitcoin!.getFeeRate(_wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
} }

View file

@ -39,6 +39,7 @@ abstract class TransactionDetailsViewModelBase with Store {
break; break;
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash:
_addElectrumListItems(tx, dateFormat); _addElectrumListItems(tx, dateFormat);
break; break;
case WalletType.haven: case WalletType.haven:
@ -115,6 +116,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://mempool.space/tx/${txId}'; return 'https://mempool.space/tx/${txId}';
case WalletType.litecoin: case WalletType.litecoin:
return 'https://blockchair.com/litecoin/transaction/${txId}'; return 'https://blockchair.com/litecoin/transaction/${txId}';
case WalletType.bitcoinCash:
return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
case WalletType.haven: case WalletType.haven:
return 'https://explorer.havenprotocol.org/search?value=${txId}'; return 'https://explorer.havenprotocol.org/search?value=${txId}';
case WalletType.ethereum: case WalletType.ethereum:
@ -135,6 +138,7 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.bitcoin: case WalletType.bitcoin:
return S.current.view_transaction_on + 'mempool.space'; return S.current.view_transaction_on + 'mempool.space';
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash:
return S.current.view_transaction_on + 'Blockchair.com'; return S.current.view_transaction_on + 'Blockchair.com';
case WalletType.haven: case WalletType.haven:
return S.current.view_transaction_on + 'explorer.havenprotocol.org'; return S.current.view_transaction_on + 'explorer.havenprotocol.org';

View file

@ -1,9 +1,10 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -19,12 +20,14 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
UnspentCoinsDetailsViewModelBase( UnspentCoinsDetailsViewModelBase(
{required this.unspentCoinsItem, required this.unspentCoinsListViewModel}) {required this.unspentCoinsItem, required this.unspentCoinsListViewModel})
: items = <TransactionDetailsListItem>[], : items = <TransactionDetailsListItem>[],
_type = unspentCoinsListViewModel.wallet.type,
isFrozen = unspentCoinsItem.isFrozen, isFrozen = unspentCoinsItem.isFrozen,
note = unspentCoinsItem.note { note = unspentCoinsItem.note {
items = [ items = [
StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount), StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount),
StandartListItem(title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash), StandartListItem(
StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address), title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash),
StandartListItem(title: S.current.widgets_address, value: formattedAddress),
TextFieldListItem( TextFieldListItem(
title: S.current.note_tap_to_change, title: S.current.note_tap_to_change,
value: note, value: note,
@ -46,14 +49,13 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
}) })
]; ];
if ([WalletType.bitcoin, WalletType.litecoin].contains(unspentCoinsListViewModel.wallet.type)) { if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(_type)) {
items.add(BlockExplorerListItem( items.add(BlockExplorerListItem(
title: S.current.view_in_block_explorer, title: S.current.view_in_block_explorer,
value: _explorerDescription(unspentCoinsListViewModel.wallet.type), value: _explorerDescription(_type),
onTap: () { onTap: () {
try { try {
final url = Uri.parse( final url = Uri.parse(_explorerUrl(_type, unspentCoinsItem.hash));
_explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash));
return launchUrl(url); return launchUrl(url);
} catch (e) {} } catch (e) {}
}, },
@ -67,6 +69,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
return 'https://ordinals.com/tx/${txId}'; return 'https://ordinals.com/tx/${txId}';
case WalletType.litecoin: case WalletType.litecoin:
return 'https://litecoin.earlyordies.com/tx/${txId}'; return 'https://litecoin.earlyordies.com/tx/${txId}';
case WalletType.bitcoinCash:
return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
default: default:
return ''; return '';
} }
@ -78,6 +82,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'Ordinals.com'; return S.current.view_transaction_on + 'Ordinals.com';
case WalletType.litecoin: case WalletType.litecoin:
return S.current.view_transaction_on + 'Earlyordies.com'; return S.current.view_transaction_on + 'Earlyordies.com';
case WalletType.bitcoinCash:
return S.current.view_transaction_on + 'Blockchair.com';
default: default:
return ''; return '';
} }
@ -91,5 +97,10 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
final UnspentCoinsItem unspentCoinsItem; final UnspentCoinsItem unspentCoinsItem;
final UnspentCoinsListViewModel unspentCoinsListViewModel; final UnspentCoinsListViewModel unspentCoinsListViewModel;
final WalletType _type;
List<TransactionDetailsListItem> items; List<TransactionDetailsListItem> items;
String get formattedAddress => WalletType.bitcoinCash == _type
? bitcoinCash!.getCashAddrFormat(unspentCoinsItem.address)
: unspentCoinsItem.address;
} }

View file

@ -1,9 +1,10 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/unspent_transaction_output.dart'; import 'package:cw_core/unspent_transaction_output.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -24,11 +25,11 @@ abstract class UnspentCoinsListViewModelBase with Store {
final Box<UnspentCoinsInfo> _unspentCoinsInfo; final Box<UnspentCoinsInfo> _unspentCoinsInfo;
@computed @computed
ObservableList<UnspentCoinsItem> get items => ObservableList<UnspentCoinsItem> get items => ObservableList.of(_getUnspents().map((elem) {
ObservableList.of(_getUnspents().map((elem) {
final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}'; final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}';
final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
return UnspentCoinsItem( return UnspentCoinsItem(
address: elem.address, address: elem.address,
@ -39,13 +40,13 @@ abstract class UnspentCoinsListViewModelBase with Store {
isSending: info?.isSending ?? true, isSending: info?.isSending ?? true,
amountRaw: elem.value, amountRaw: elem.value,
vout: elem.vout, vout: elem.vout,
keyImage: elem.keyImage keyImage: elem.keyImage);
);
})); }));
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async { Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
try { try {
final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage); final info =
getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
if (info == null) { if (info == null) {
final newInfo = UnspentCoinsInfo( final newInfo = UnspentCoinsInfo(
walletId: wallet.id, walletId: wallet.id,
@ -56,8 +57,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
isFrozen: item.isFrozen, isFrozen: item.isFrozen,
isSending: item.isSending, isSending: item.isSending,
noteRaw: item.note, noteRaw: item.note,
keyImage: item.keyImage keyImage: item.keyImage);
);
await _unspentCoinsInfo.add(newInfo); await _unspentCoinsInfo.add(newInfo);
_updateUnspents(); _updateUnspents();
@ -76,37 +76,34 @@ abstract class UnspentCoinsListViewModelBase with Store {
} }
} }
UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout, String? keyImage) { UnspentCoinsInfo? getUnspentCoinInfo(
String hash, String address, int value, int vout, String? keyImage) {
return _unspentCoinsInfo.values.firstWhereOrNull((element) => return _unspentCoinsInfo.values.firstWhereOrNull((element) =>
element.walletId == wallet.id && element.walletId == wallet.id &&
element.hash == hash && element.hash == hash &&
element.address == address && element.address == address &&
element.value == value && element.value == value &&
element.vout == vout && element.vout == vout &&
element.keyImage == keyImage element.keyImage == keyImage);
);
} }
String formatAmountToString(int fullBalance) { String formatAmountToString(int fullBalance) {
if (wallet.type == WalletType.monero) if (wallet.type == WalletType.monero)
return monero!.formatterMoneroAmountToString(amount: fullBalance); return monero!.formatterMoneroAmountToString(amount: fullBalance);
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance); return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance);
return ''; return '';
} }
void _updateUnspents() { void _updateUnspents() {
if (wallet.type == WalletType.monero) if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet);
return monero!.updateUnspents(wallet); if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
return bitcoin!.updateUnspents(wallet); return bitcoin!.updateUnspents(wallet);
} }
List<Unspent> _getUnspents() { List<Unspent> _getUnspents() {
if (wallet.type == WalletType.monero) if (wallet.type == WalletType.monero) return monero!.getUnspents(wallet);
return monero!.getUnspents(wallet); if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
return bitcoin!.getUnspents(wallet); return bitcoin!.getUnspents(wallet);
return List.empty(); return List.empty();
} }

View file

@ -66,7 +66,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
final wallet = _wallet; final wallet = _wallet;
if (wallet.type == WalletType.bitcoin if (wallet.type == WalletType.bitcoin
|| wallet.type == WalletType.litecoin) { || wallet.type == WalletType.litecoin
|| wallet.type == WalletType.bitcoinCash) {
await bitcoin!.generateNewAddress(wallet); await bitcoin!.generateNewAddress(wallet);
await wallet.save(); await wallet.save();
} }

View file

@ -107,6 +107,23 @@ class EthereumURI extends PaymentURI {
} }
} }
class BitcoinCashURI extends PaymentURI {
BitcoinCashURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
class NanoURI extends PaymentURI { class NanoURI extends PaymentURI {
NanoURI({required String amount, required String address}) NanoURI({required String amount, required String address})
: super(amount: amount, address: address); : super(amount: amount, address: address);
@ -114,7 +131,6 @@ class NanoURI extends PaymentURI {
@override @override
String toString() { String toString() {
var base = 'nano:' + address; var base = 'nano:' + address;
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
} }
@ -192,6 +208,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return EthereumURI(amount: amount, address: address.address); return EthereumURI(amount: amount, address: address.address);
} }
if (wallet.type == WalletType.bitcoinCash) {
return BitcoinCashURI(amount: amount, address: address.address);
}
if (wallet.type == WalletType.nano) { if (wallet.type == WalletType.nano) {
return NanoURI(amount: amount, address: address.address); return NanoURI(amount: amount, address: address.address);
} }
@ -280,7 +300,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@computed @computed
bool get showElectrumAddressDisclaimer => bool get showElectrumAddressDisclaimer =>
wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin; wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash;
List<ListItem> _baseItems; List<ListItem> _baseItems;

View file

@ -19,6 +19,7 @@ abstract class WalletKeysViewModelBase with Store {
WalletKeysViewModelBase(this._appStore) WalletKeysViewModelBase(this._appStore)
: title = _appStore.wallet!.type == WalletType.bitcoin || : title = _appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin || _appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.bitcoinCash ||
_appStore.wallet!.type == WalletType.ethereum _appStore.wallet!.type == WalletType.ethereum
? S.current.wallet_seed ? S.current.wallet_seed
: S.current.wallet_keys, : S.current.wallet_keys,
@ -91,7 +92,8 @@ abstract class WalletKeysViewModelBase with Store {
} }
if (_appStore.wallet!.type == WalletType.bitcoin || if (_appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin) { _appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.bitcoinCash) {
items.addAll([ items.addAll([
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
]); ]);
@ -145,6 +147,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'haven-wallet'; return 'haven-wallet';
case WalletType.ethereum: case WalletType.ethereum:
return 'ethereum-wallet'; return 'ethereum-wallet';
case WalletType.bitcoinCash:
return 'bitcoinCash-wallet';
case WalletType.nano: case WalletType.nano:
return 'nano-wallet'; return 'nano-wallet';
case WalletType.banano: case WalletType.banano:

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.dart';
@ -46,10 +47,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
name: name, language: options as String); name: name, language: options as String);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(name: name); return ethereum!.createEthereumNewWalletCredentials(name: name);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoNewWalletCredentials(name: name); return nano!.createNanoNewWalletCredentials(name: name);
default: default:
throw Exception('Unexpected type: ${type.toString()}');; throw Exception('Unexpected type: ${type.toString()}');
} }
} }

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
@ -92,14 +93,20 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
name: name, height: height, mnemonic: seed, password: password); name: name, height: height, mnemonic: seed, password: password);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials( return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name, mnemonic: seed, password: password); name: name,
mnemonic: seed,
password: password);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoRestoreWalletFromSeedCredentials( return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name, name: name,
mnemonic: seed, mnemonic: seed,
password: password, password: password,
derivationType: derivationType, derivationType: derivationType);
);
default: default:
break; break;
} }
@ -145,8 +152,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
name: name, name: name,
password: password, password: password,
seedKey: options['private_key'] as String, seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType, derivationType: options["derivationType"] as DerivationType);
);
default: default:
break; break;
} }
@ -167,8 +173,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
return nanoUtil!.compareDerivationMethods( return nanoUtil!.compareDerivationMethods(
mnemonic: mnemonic, mnemonic: mnemonic,
privateKey: seedKey, privateKey: seedKey,
node: node, node: node);
);
default: default:
break; break;
} }

1
model_generator.sh Executable file → Normal file
View file

@ -4,4 +4,5 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_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_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -129,6 +129,7 @@ flutter:
- assets/bitcoin_electrum_server_list.yml - assets/bitcoin_electrum_server_list.yml
- assets/litecoin_electrum_server_list.yml - assets/litecoin_electrum_server_list.yml
- assets/ethereum_server_list.yml - assets/ethereum_server_list.yml
- assets/bitcoin_cash_electrum_server_list.yml
- assets/nano_node_list.yml - assets/nano_node_list.yml
- assets/nano_pow_node_list.yml - assets/nano_pow_node_list.yml
- assets/text/ - assets/text/

View file

@ -9,4 +9,4 @@ fi
./app_icon.sh ./app_icon.sh
./pubspec_gen.sh ./pubspec_gen.sh
./manifest.sh ./manifest.sh
./inject_app_details.sh ./inject_app_details.sh

View file

@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
CONFIG_ARGS="--monero" CONFIG_ARGS="--monero"
;; ;;
$CAKEWALLET) $CAKEWALLET)
CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano" CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash"
;; ;;
$HAVEN) $HAVEN)
CONFIG_ARGS="--haven" CONFIG_ARGS="--haven"

4
scripts/ios/app_config.sh Executable file → Normal file
View file

@ -28,9 +28,11 @@ case $APP_IOS_TYPE in
CONFIG_ARGS="--monero" CONFIG_ARGS="--monero"
;; ;;
$CAKEWALLET) $CAKEWALLET)
CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano" CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash"
;; ;;
$HAVEN) $HAVEN)
CONFIG_ARGS="--haven" CONFIG_ARGS="--haven"
;; ;;
esac esac

View file

@ -23,7 +23,7 @@ CONFIG_ARGS=""
case $APP_MACOS_TYPE in case $APP_MACOS_TYPE in
$CAKEWALLET) $CAKEWALLET)
CONFIG_ARGS="--monero --bitcoin --ethereum --nano";; #--haven CONFIG_ARGS="--monero --bitcoin --ethereum --nano --bitcoinCash";; #--haven
esac esac
cp -rf pubspec_description.yaml pubspec.yaml cp -rf pubspec_description.yaml pubspec.yaml

View file

@ -4,6 +4,7 @@ const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart';
const moneroOutputPath = 'lib/monero/monero.dart'; const moneroOutputPath = 'lib/monero/monero.dart';
const havenOutputPath = 'lib/haven/haven.dart'; const havenOutputPath = 'lib/haven/haven.dart';
const ethereumOutputPath = 'lib/ethereum/ethereum.dart'; const ethereumOutputPath = 'lib/ethereum/ethereum.dart';
const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart';
const nanoOutputPath = 'lib/nano/nano.dart'; const nanoOutputPath = 'lib/nano/nano.dart';
const walletTypesPath = 'lib/wallet_types.g.dart'; const walletTypesPath = 'lib/wallet_types.g.dart';
const pubspecDefaultPath = 'pubspec_default.yaml'; const pubspecDefaultPath = 'pubspec_default.yaml';
@ -15,6 +16,7 @@ Future<void> main(List<String> args) async {
final hasMonero = args.contains('${prefix}monero'); final hasMonero = args.contains('${prefix}monero');
final hasHaven = args.contains('${prefix}haven'); final hasHaven = args.contains('${prefix}haven');
final hasEthereum = args.contains('${prefix}ethereum'); final hasEthereum = args.contains('${prefix}ethereum');
final hasBitcoinCash = args.contains('${prefix}bitcoinCash');
final hasNano = args.contains('${prefix}nano'); final hasNano = args.contains('${prefix}nano');
final hasBanano = args.contains('${prefix}banano'); final hasBanano = args.contains('${prefix}banano');
@ -22,6 +24,7 @@ Future<void> main(List<String> args) async {
await generateMonero(hasMonero); await generateMonero(hasMonero);
await generateHaven(hasHaven); await generateHaven(hasHaven);
await generateEthereum(hasEthereum); await generateEthereum(hasEthereum);
await generateBitcoinCash(hasBitcoinCash);
await generateNano(hasNano); await generateNano(hasNano);
// await generateBanano(hasEthereum); // await generateBanano(hasEthereum);
@ -32,6 +35,7 @@ Future<void> main(List<String> args) async {
hasEthereum: hasEthereum, hasEthereum: hasEthereum,
hasNano: hasNano, hasNano: hasNano,
hasBanano: hasBanano, hasBanano: hasBanano,
hasBitcoinCash: hasBitcoinCash,
); );
await generateWalletTypes( await generateWalletTypes(
hasMonero: hasMonero, hasMonero: hasMonero,
@ -40,13 +44,13 @@ Future<void> main(List<String> args) async {
hasEthereum: hasEthereum, hasEthereum: hasEthereum,
hasNano: hasNano, hasNano: hasNano,
hasBanano: hasBanano, hasBanano: hasBanano,
hasBitcoinCash: hasBitcoinCash,
); );
} }
Future<void> generateBitcoin(bool hasImplementation) async { Future<void> generateBitcoin(bool hasImplementation) async {
final outputFile = File(bitcoinOutputPath); final outputFile = File(bitcoinOutputPath);
const bitcoinCommonHeaders = """ const bitcoinCommonHeaders = """
import 'package:cake_wallet/entities/unspent_transaction_output.dart';
import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
@ -60,7 +64,6 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/bitcoin_wallet_service.dart'; import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
@ -80,8 +83,8 @@ abstract class Bitcoin {
Map<String, String> getWalletKeys(Object wallet); Map<String, String> getWalletKeys(Object wallet);
List<TransactionPriority> getTransactionPriorities(); List<TransactionPriority> getTransactionPriorities();
List<TransactionPriority> getLitecoinTransactionPriorities(); List<TransactionPriority> getLitecoinTransactionPriorities();
TransactionPriority deserializeBitcoinTransactionPriority(int raw); TransactionPriority deserializeBitcoinTransactionPriority(int raw);
TransactionPriority deserializeLitecoinTransactionPriority(int raw); TransactionPriority deserializeLitecoinTransactionPriority(int raw);
int getFeeRate(Object wallet, TransactionPriority priority); int getFeeRate(Object wallet, TransactionPriority priority);
Future<void> generateNewAddress(Object wallet); Future<void> generateNewAddress(Object wallet);
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate}); Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
@ -95,7 +98,7 @@ abstract class Bitcoin {
int formatterStringDoubleToBitcoinAmount(String amount); int formatterStringDoubleToBitcoinAmount(String amount);
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate); String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate);
List<Unspent> getUnspents(Object wallet); List<BitcoinUnspent> getUnspents(Object wallet);
void updateUnspents(Object wallet); void updateUnspents(Object wallet);
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource); WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource); WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
@ -126,7 +129,7 @@ abstract class Bitcoin {
Future<void> generateMonero(bool hasImplementation) async { Future<void> generateMonero(bool hasImplementation) async {
final outputFile = File(moneroOutputPath); final outputFile = File(moneroOutputPath);
const moneroCommonHeaders = """ const moneroCommonHeaders = """
import 'package:cake_wallet/entities/unspent_transaction_output.dart'; import 'package:cw_core/unspent_transaction_output.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_monero/monero_unspent.dart'; import 'package:cw_monero/monero_unspent.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -521,6 +524,7 @@ abstract class Ethereum {
String getPrivateKey(WalletBase wallet); String getPrivateKey(WalletBase wallet);
String getPublicKey(WalletBase wallet); String getPublicKey(WalletBase wallet);
TransactionPriority getDefaultTransactionPriority(); TransactionPriority getDefaultTransactionPriority();
TransactionPriority getEthereumTransactionPrioritySlow();
List<TransactionPriority> getTransactionPriorities(); List<TransactionPriority> getTransactionPriorities();
TransactionPriority deserializeEthereumTransactionPriority(int raw); TransactionPriority deserializeEthereumTransactionPriority(int raw);
@ -568,6 +572,67 @@ abstract class Ethereum {
await outputFile.writeAsString(output); await outputFile.writeAsString(output);
} }
Future<void> generateBitcoinCash(bool hasImplementation) async {
final outputFile = File(bitcoinCashOutputPath);
const bitcoinCashCommonHeaders = """
import 'dart:typed_data';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart';
""";
const bitcoinCashCWHeaders = """
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
""";
const bitcoinCashCwPart = "part 'cw_bitcoin_cash.dart';";
const bitcoinCashContent = """
abstract class BitcoinCash {
String getMnemonic(int? strength);
Uint8List getSeedFromMnemonic(String seed);
String getCashAddrFormat(String address);
WalletService createBitcoinCashWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
WalletCredentials createBitcoinCashNewWalletCredentials(
{required String name, WalletInfo? walletInfo});
WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
{required String name, required String mnemonic, required String password});
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw);
TransactionPriority getDefaultTransactionPriority();
List<TransactionPriority> getTransactionPriorities();
TransactionPriority getBitcoinCashTransactionPrioritySlow();
}
""";
const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n';
const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
final output = '$bitcoinCashCommonHeaders\n' +
(hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') +
(hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') +
(hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) +
'\n' +
bitcoinCashContent;
if (outputFile.existsSync()) {
await outputFile.delete();
}
await outputFile.writeAsString(output);
}
Future<void> generateNano(bool hasImplementation) async { Future<void> generateNano(bool hasImplementation) async {
final outputFile = File(nanoOutputPath); final outputFile = File(nanoOutputPath);
const nanoCommonHeaders = """ const nanoCommonHeaders = """
@ -710,14 +775,14 @@ abstract class NanoUtil {
await outputFile.writeAsString(output); await outputFile.writeAsString(output);
} }
Future<void> generatePubspec({ Future<void> generatePubspec(
required bool hasMonero, {required bool hasMonero,
required bool hasBitcoin, required bool hasBitcoin,
required bool hasHaven, required bool hasHaven,
required bool hasEthereum, required bool hasEthereum,
required bool hasNano, required bool hasNano,
required bool hasBanano, required bool hasBanano,
}) async { required bool hasBitcoinCash}) async {
const cwCore = """ const cwCore = """
cw_core: cw_core:
path: ./cw_core path: ./cw_core
@ -742,6 +807,10 @@ Future<void> generatePubspec({
cw_ethereum: cw_ethereum:
path: ./cw_ethereum path: ./cw_ethereum
"""; """;
const cwBitcoinCash = """
cw_bitcoin_cash:
path: ./cw_bitcoin_cash
""";
const cwNano = """ const cwNano = """
cw_nano: cw_nano:
path: ./cw_nano path: ./cw_nano
@ -776,6 +845,10 @@ Future<void> generatePubspec({
output += '\n$cwBanano'; output += '\n$cwBanano';
} }
if (hasBitcoinCash) {
output += '\n$cwBitcoinCash';
}
if (hasHaven && !hasMonero) { if (hasHaven && !hasMonero) {
output += '\n$cwSharedExternal\n$cwHaven'; output += '\n$cwSharedExternal\n$cwHaven';
} else if (hasHaven) { } else if (hasHaven) {
@ -794,14 +867,14 @@ Future<void> generatePubspec({
await outputFile.writeAsString(outputContent); await outputFile.writeAsString(outputContent);
} }
Future<void> generateWalletTypes({ Future<void> generateWalletTypes(
required bool hasMonero, {required bool hasMonero,
required bool hasBitcoin, required bool hasBitcoin,
required bool hasHaven, required bool hasHaven,
required bool hasEthereum, required bool hasEthereum,
required bool hasNano, required bool hasNano,
required bool hasBanano, required bool hasBanano,
}) async { required bool hasBitcoinCash}) async {
final walletTypesFile = File(walletTypesPath); final walletTypesFile = File(walletTypesPath);
if (walletTypesFile.existsSync()) { if (walletTypesFile.existsSync()) {
@ -828,6 +901,10 @@ Future<void> generateWalletTypes({
outputContent += '\tWalletType.litecoin,\n'; outputContent += '\tWalletType.litecoin,\n';
} }
if (hasBitcoinCash) {
outputContent += '\tWalletType.bitcoinCash,\n';
}
if (hasNano) { if (hasNano) {
outputContent += '\tWalletType.nano,\n'; outputContent += '\tWalletType.nano,\n';
} }