Merge branch 'v4.10.1_v1.7.1' of https://github.com/cake-tech/cake_wallet into cw_linux_direct_input_password

 Conflicts:
	cw_bitcoin/lib/electrum_wallet.dart
	lib/di.dart
	lib/main.dart
	lib/view_model/wallet_new_vm.dart
	model_generator.sh
	tool/configure.dart
This commit is contained in:
OmarHatem 2023-10-13 17:29:46 +03:00
commit 9101165921
115 changed files with 2382 additions and 622 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
@ -128,6 +130,7 @@ jobs:
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
@ -140,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

@ -52,6 +52,15 @@
<data android:scheme="litecoin" /> <data android:scheme="litecoin" />
<data android:scheme="litecoin-wallet" /> <data android:scheme="litecoin-wallet" />
<data android:scheme="litecoin_wallet" /> <data android:scheme="litecoin_wallet" />
<data android:scheme="ethereum" />
<data android:scheme="ethereum-wallet" />
<data android:scheme="ethereum_wallet" />
<data android:scheme="nano" />
<data android:scheme="nano-wallet" />
<data android:scheme="nano_wallet" />
<data android:scheme="bitcoincash" />
<data android:scheme="bitcoincash-wallet" />
<data android:scheme="bitcoincash_wallet" />
</intent-filter> </intent-filter>
</activity> </activity>
<meta-data <meta-data

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

BIN
assets/images/exolix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,5 +1,3 @@
Add Nano wallet Add BitcoinCash (BCH)
Add WalletConnect to connect your ETH wallet with your favorite dApp
Support getting Addresses from ENS and Mastodon
Bug fixes Bug fixes
Minor enhancements Accessibility enhancements

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,8 +2,10 @@ 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/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.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,38 +36,42 @@ 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,
required this.encryptionFileUtils, required this.encryptionFileUtils,
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();
@ -77,6 +83,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
encryptionFileUtils: encryptionFileUtils); encryptionFileUtils: encryptionFileUtils);
} }
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;
@ -103,9 +113,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!;
@ -118,8 +128,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;
@ -147,8 +157,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) {
@ -177,8 +187,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>[];
@ -212,13 +221,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);
@ -235,9 +242,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);
@ -299,8 +304,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;
@ -311,19 +316,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) {
@ -341,8 +339,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;
@ -358,12 +356,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 {
@ -372,34 +370,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) {
@ -428,8 +421,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
@ -444,8 +436,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
@ -482,21 +473,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) {
@ -506,8 +496,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;
@ -526,14 +516,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);
@ -542,8 +532,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) {
@ -579,27 +569,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
@ -610,10 +592,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) {
@ -624,19 +604,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;
} }
@ -688,9 +665,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);
@ -699,8 +675,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;
} }
}); });
@ -723,8 +701,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 {
@ -735,9 +713,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

@ -42,13 +42,21 @@ class EthereumClient {
await _client!.getBalance(address); await _client!.getBalance(address);
Future<int> getGasUnitPrice() async { Future<int> getGasUnitPrice() async {
final gasPrice = await _client!.getGasPrice(); try {
return gasPrice.getInWei.toInt(); final gasPrice = await _client!.getGasPrice();
return gasPrice.getInWei.toInt();
} catch (_) {
return 0;
}
} }
Future<int> getEstimatedGas() async { Future<int> getEstimatedGas() async {
final estimatedGas = await _client!.estimateGas(); try {
return estimatedGas.toInt(); final estimatedGas = await _client!.estimateGas();
return estimatedGas.toInt();
} catch (_) {
return 0;
}
} }
Future<PendingEthereumTransaction> signTransaction({ Future<PendingEthereumTransaction> signTransaction({

View file

@ -180,8 +180,8 @@ abstract class NanoWalletBase
if (txOut.sendAll) { if (txOut.sendAll) {
amt = balance[currency]?.currentBalance ?? BigInt.zero; amt = balance[currency]?.currentBalance ?? BigInt.zero;
} else { } else {
amt = BigInt.tryParse( amt = BigInt.tryParse(NanoUtil.getAmountAsRaw(
NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ?? txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ??
BigInt.zero; BigInt.zero;
} }
@ -193,7 +193,9 @@ abstract class NanoWalletBase
final block = await _client.constructSendBlock( final block = await _client.constructSendBlock(
amountRaw: amt.toString(), amountRaw: amt.toString(),
destinationAddress: txOut.extractedAddress ?? txOut.address, destinationAddress: credentials.outputs.first.isParsedAddress
? credentials.outputs.first.extractedAddress!
: credentials.outputs.first.address,
privateKey: _privateKey!, privateKey: _privateKey!,
balanceAfterTx: runningBalance, balanceAfterTx: runningBalance,
previousHash: previousHash, previousHash: previousHash,

View file

@ -272,7 +272,7 @@ SPEC CHECKSUMS:
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920 file_picker: ce3938a0df3cc1ef404671531facef740d03f920
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d

View file

@ -100,6 +100,66 @@
<string>litecoin-wallet</string> <string>litecoin-wallet</string>
</array> </array>
</dict> </dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>ethereum</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ethereum</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>ethereum-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ethereum-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>nano</string>
<key>CFBundleURLSchemes</key>
<array>
<string>nano</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>nano-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>nano-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>bitcoincash</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoincash</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>bitcoincash-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoincash-wallet</string>
</array>
</dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>

View file

@ -45,6 +45,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;
@ -122,16 +123,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

@ -6,6 +6,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';
@ -838,6 +839,9 @@ Future<void> setup({
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.createEthereumWalletService( return ethereum!.createEthereumWalletService(
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource,
SettingsStoreBase.walletPasswordDirectInput);
case WalletType.nano: case WalletType.nano:
return nano!.createNanoWalletService( return nano!.createNanoWalletService(
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);

View file

@ -27,6 +27,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';
@ -82,7 +83,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:
@ -167,6 +171,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;
@ -324,6 +333,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 = '';
@ -359,6 +374,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);
@ -412,6 +436,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) {
@ -498,27 +531,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);
@ -566,6 +606,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,8 +52,7 @@ class MainActions {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.nano: case WalletType.bitcoinCash:
case WalletType.banano:
switch (defaultBuyProvider) { switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime: case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy); Navigator.pushNamed(context, Routes.buy);
@ -66,6 +65,8 @@ class MainActions {
break; break;
} }
break; break;
case WalletType.nano:
case WalletType.banano:
case WalletType.monero: case WalletType.monero:
await getIt.get<OnRamperBuyProvider>().launchProvider(context); await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break; break;
@ -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

@ -51,6 +51,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

@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
static const trocador = static const trocador =
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png'); ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: ''); static const exolix =
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
static ExchangeProviderDescription deserialize({required int raw}) { static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) { switch (raw) {
@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
case 5: case 5:
return trocador; return trocador;
case 6: case 6:
return exolix;
case 7:
return all; return all;
default: default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');

View file

@ -0,0 +1,295 @@
import 'dart:convert';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
class ExolixExchangeProvider extends ExchangeProvider {
ExolixExchangeProvider() : super(pairList: _supportedPairs());
static final apiKey = secrets.exolixApiKey;
static const apiBaseUrl = 'exolix.com';
static const transactionsPath = '/api/v2/transactions';
static const ratePath = '/api/v2/rate';
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.usdt,
CryptoCurrency.xhv,
CryptoCurrency.btt,
CryptoCurrency.firo,
CryptoCurrency.zaddr,
CryptoCurrency.xvg,
CryptoCurrency.kmd,
CryptoCurrency.paxg,
CryptoCurrency.rune,
CryptoCurrency.scrt,
CryptoCurrency.btcln,
CryptoCurrency.cro,
CryptoCurrency.ftm,
CryptoCurrency.frax,
CryptoCurrency.gusd,
CryptoCurrency.gtc,
CryptoCurrency.weth,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies =
CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
return supportedCurrencies
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList();
}
@override
String get title => 'Exolix';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.exolix;
@override
Future<bool> checkIsAvailable() async => true;
static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float';
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final params = <String, String>{
'rateType': getRateType(isFixedRateMode),
'amount': '1',
};
if (isFixedRateMode) {
params['coinFrom'] = _normalizeCurrency(to);
params['coinTo'] = _normalizeCurrency(from);
params['networkFrom'] = _networkFor(to);
params['networkTo'] = _networkFor(from);
} else {
params['coinFrom'] = _normalizeCurrency(from);
params['coinTo'] = _normalizeCurrency(to);
params['networkFrom'] = _networkFor(from);
params['networkTo'] = _networkFor(to);
}
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return Limits(min: responseJSON['minAmount'] as double?);
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as ExolixRequest;
final headers = {'Content-Type': 'application/json'};
final body = <String, dynamic>{
'coinFrom': _normalizeCurrency(_request.from),
'coinTo': _normalizeCurrency(_request.to),
'networkFrom': _networkFor(_request.from),
'networkTo': _networkFor(_request.to),
'withdrawalAddress': _request.address,
'refundAddress': _request.refundAddress,
'rateType': getRateType(isFixedRateMode),
'apiToken': apiKey,
};
if (isFixedRateMode) {
body['withdrawalAmount'] = _request.toAmount;
} else {
body['amount'] = _request.fromAmount;
}
final uri = Uri.https(apiBaseUrl, transactionsPath);
final response = await post(uri, headers: headers, body: json.encode(body));
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final errors = responseJSON['errors'] as Map<String, String>;
final errorMessage = errors.values.join(', ');
throw Exception(errorMessage);
}
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
final inputAddress = responseJSON['depositAddress'] as String;
final refundAddress = responseJSON['refundAddress'] as String?;
final extraId = responseJSON['depositExtraId'] as String?;
final payoutAddress = responseJSON['withdrawalAddress'] as String;
final amount = responseJSON['amount'].toString();
return Trade(
id: id,
from: _request.from,
to: _request.to,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
extraId: extraId,
createdAt: DateTime.now(),
amount: amount,
state: TradeState.created,
payoutAddress: payoutAddress);
}
@override
Future<Trade> findTradeById({required String id}) async {
final findTradeByIdPath = transactionsPath + '/$id';
final uri = Uri.https(apiBaseUrl, findTradeByIdPath);
final response = await get(uri);
if (response.statusCode == 404) {
throw TradeNotFoundException(id, provider: description);
}
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final errors = responseJSON['errors'] as Map<String, String>;
final errorMessage = errors.values.join(', ');
throw TradeNotFoundException(id, provider: description, description: errorMessage);
}
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final coinFrom = responseJSON['coinFrom']['coinCode'] as String;
final from = CryptoCurrency.fromString(coinFrom);
final coinTo = responseJSON['coinTo']['coinCode'] as String;
final to = CryptoCurrency.fromString(coinTo);
final inputAddress = responseJSON['depositAddress'] as String;
final amount = responseJSON['amount'].toString();
final status = responseJSON['status'] as String;
final state = TradeState.deserialize(raw: _prepareStatus(status));
final extraId = responseJSON['depositExtraId'] as String?;
final outputTransaction = responseJSON['hashOut']['hash'] as String?;
final payoutAddress = responseJSON['withdrawalAddress'] as String;
return Trade(
id: id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: amount,
state: state,
extraId: extraId,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
final params = <String, String>{
'coinFrom': _normalizeCurrency(from),
'coinTo': _normalizeCurrency(to),
'networkFrom': _networkFor(from),
'networkTo': _networkFor(to),
'rateType': getRateType(isFixedRateMode),
'apiToken': apiKey,
};
if (isReceiveAmount) {
params['withdrawalAmount'] = amount.toString();
} else {
params['amount'] = amount.toString();
}
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200) {
final message = responseJSON['message'] as String?;
throw Exception(message);
}
final rate = responseJSON['rate'] as double;
return rate;
} catch (e) {
print(e.toString());
return 0.0;
}
}
String _prepareStatus(String status) {
switch (status) {
case 'deleted':
case 'error':
return 'overdue';
default:
return status;
}
}
String _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.arb:
return 'ARBITRUM';
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title;
}
}
String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.nano:
return 'XNO';
case CryptoCurrency.bttc:
return 'BTT';
case CryptoCurrency.zec:
return 'ZEC';
default:
return currency.title;
}
}
String _normalizeTag(String tag) {
switch (tag) {
case 'POLY':
return 'Polygon';
default:
return tag;
}
}
}

View file

@ -0,0 +1,20 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
class ExolixRequest extends TradeRequest {
ExolixRequest(
{required this.from,
required this.to,
required this.address,
required this.fromAmount,
required this.toAmount,
required this.refundAddress});
CryptoCurrency from;
CryptoCurrency to;
String address;
String fromAmount;
String toAmount;
String refundAddress;
}

View file

@ -35,6 +35,15 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const completed = TradeState(raw: 'completed', title: 'Completed'); static const completed = TradeState(raw: 'completed', title: 'Completed');
static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); static const settling = TradeState(raw: 'settling', title: 'Settlement in progress');
static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
static const wait = TradeState(raw: 'wait', title: 'Waiting');
static const overdue = TradeState(raw: 'overdue', title: 'Overdue');
static const refund = TradeState(raw: 'refund', title: 'Refund');
static const refunded = TradeState(raw: 'refunded', title: 'Refunded');
static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation');
static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed');
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
static const sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success');
static TradeState deserialize({required String raw}) { static TradeState deserialize({required String raw}) {
switch (raw) { switch (raw) {
case 'pending': case 'pending':
@ -77,6 +86,24 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
return failed; return failed;
case 'completed': case 'completed':
return completed; return completed;
case 'wait':
return wait;
case 'overdue':
return overdue;
case 'refund':
return refund;
case 'refunded':
return refunded;
case 'confirmation':
return confirmation;
case 'confirmed':
return confirmed;
case 'exchanging':
return exchanging;
case 'sending':
return sending;
case 'success':
return success;
default: default:
throw Exception('Unexpected token: $raw in TradeState deserialize'); throw Exception('Unexpected token: $raw in TradeState deserialize');
} }

View file

@ -101,10 +101,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(WalletInfoAdapter()); CakeHive.registerAdapter(WalletInfoAdapter());
} }
if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) { if (!CakeHive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(DerivationTypeAdapter()); CakeHive.registerAdapter(DerivationTypeAdapter());
} }
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) { if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter()); CakeHive.registerAdapter(WalletTypeAdapter());
} }
@ -136,7 +136,8 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey); final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.boxName); final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
final nodes = await CakeHive.openBox<Node>(Node.boxName); final nodes = await CakeHive.openBox<Node>(Node.boxName);
final powNodes = await CakeHive.openBox<Node>(Node.boxName + "pow");// must be different from Node.boxName final powNodes =
await CakeHive.openBox<Node>(Node.boxName + "pow"); // must be different from Node.boxName
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>( final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName, TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey); encryptionKey: transactionDescriptionsBoxKey);
@ -163,7 +164,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

@ -36,6 +36,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);
@ -146,6 +147,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

@ -194,7 +194,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
contractAddress: _contractAddressController.text, contractAddress: _contractAddressController.text,
decimal: int.parse(_tokenDecimalController.text), decimal: int.parse(_tokenDecimalController.text),
)); ));
Navigator.pop(context); if (context.mounted) {
Navigator.pop(context);
}
} }
}, },
text: S.of(context).save, text: S.of(context).save,

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

@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget {
borderRadius: BorderRadius.circular(50), borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/trocador.png', width: 36, height: 36)); child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
break; break;
case ExchangeProviderDescription.exolix:
image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
break;
default: default:
image = null; image = null;
} }

View file

@ -5,7 +5,6 @@ import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
@ -26,16 +25,15 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
void showInformation( void showInformation(
ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) {
final fetchingLabel = S.current.fetching;
final trade = exchangeTradeViewModel.trade; final trade = exchangeTradeViewModel.trade;
final walletName = exchangeTradeViewModel.wallet.name; final walletName = exchangeTradeViewModel.wallet.name;
final information = exchangeTradeViewModel.isSendable final information = exchangeTradeViewModel.isSendable
? S.current.exchange_result_confirm( ? S.current.exchange_result_confirm(
trade.amount ?? fetchingLabel, trade.from.toString(), walletName) + trade.amount, trade.from.toString(), walletName) +
exchangeTradeViewModel.extraInfo exchangeTradeViewModel.extraInfo
: S.current.exchange_result_description( : S.current.exchange_result_description(
trade.amount ?? fetchingLabel, trade.from.toString()) + trade.amount, trade.from.toString()) +
exchangeTradeViewModel.extraInfo; exchangeTradeViewModel.extraInfo;
showPopUp<void>( showPopUp<void>(
@ -177,7 +175,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = widget.exchangeTradeViewModel.items[index]; final item = widget.exchangeTradeViewModel.items[index];
final value = item.data ?? fetchingLabel; final value = item.data;
final content = ListRow( final content = ListRow(
title: item.title, title: item.title,

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

@ -18,7 +18,6 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
@ -53,6 +52,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;
@ -69,169 +69,178 @@ class WalletListBodyState extends State<WalletListBody> {
return Container( return Container(
padding: EdgeInsets.only(top: 16), padding: EdgeInsets.only(top: 16),
child: ScrollableWithBottomSection( child: Column(
contentPadding: EdgeInsets.only(bottom: 20), children: [
content: Container( Expanded(
child: Observer( child: Container(
builder: (_) => ListView.separated( child: Observer(
shrinkWrap: true, builder: (_) => ListView.separated(
physics: const NeverScrollableScrollPhysics(), physics: const BouncingScrollPhysics(),
separatorBuilder: (_, index) => separatorBuilder: (_, index) =>
Divider(color: Theme.of(context).colorScheme.background, height: 32), Divider(color: Theme.of(context).colorScheme.background, height: 32),
itemCount: widget.walletListViewModel.wallets.length, itemCount: widget.walletListViewModel.wallets.length,
itemBuilder: (__, index) { itemBuilder: (__, index) {
final wallet = widget.walletListViewModel.wallets[index]; final wallet = widget.walletListViewModel.wallets[index];
final currentColor = wallet.isCurrent final currentColor = wallet.isCurrent
? Theme.of(context) ? Theme.of(context)
.extension<WalletListTheme>()! .extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor .createNewWalletButtonBackgroundColor
: Theme.of(context).colorScheme.background; : Theme.of(context).colorScheme.background;
final row = GestureDetector( final row = GestureDetector(
onTap: () => wallet.isCurrent ? null : _loadWallet(wallet), onTap: () => wallet.isCurrent ? null : _loadWallet(wallet),
child: Container( child: Container(
height: tileHeight, height: tileHeight,
width: double.infinity, width: double.infinity,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Container( Container(
height: tileHeight, height: tileHeight,
width: 4, width: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(4), bottomRight: Radius.circular(4)), topRight: Radius.circular(4),
color: currentColor), bottomRight: Radius.circular(4)),
), color: currentColor),
Expanded(
child: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 20, right: 20),
color: Theme.of(context).colorScheme.background,
alignment: Alignment.centerLeft,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon,
SizedBox(width: 10),
Flexible(
child: Text(
wallet.name,
maxLines: null,
softWrap: true,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
],
), ),
), Expanded(
), child: Container(
], height: tileHeight,
), padding: EdgeInsets.only(left: 20, right: 20),
), color: Theme.of(context).colorScheme.background,
); alignment: Alignment.centerLeft,
child: Row(
return wallet.isCurrent crossAxisAlignment: CrossAxisAlignment.center,
? row children: <Widget>[
: Row( wallet.isEnabled
children: [ ? _imageFor(type: wallet.type)
Expanded(child: row), : nonWalletTypeIcon,
GestureDetector( SizedBox(width: 10),
onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit, Flexible(
arguments: [widget.walletListViewModel, wallet]), child: Text(
child: Container( wallet.name,
padding: EdgeInsets.only(right: 20), maxLines: null,
child: Center( softWrap: true,
child: Container( style: TextStyle(
height: 40, fontSize: 22,
width: 44, fontWeight: FontWeight.w500,
padding: EdgeInsets.all(10), color: Theme.of(context)
decoration: BoxDecoration( .extension<CakeTextTheme>()!
shape: BoxShape.circle, .titleColor,
color: Theme.of(context) ),
.extension<ReceivePageTheme>()! ),
.iconsBackgroundColor, ),
), ],
child: Icon(
Icons.edit,
size: 14,
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
),
), ),
), ),
), ),
), ],
], ),
); ),
}, );
return wallet.isCurrent
? row
: Row(
children: [
Expanded(child: row),
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit,
arguments: [widget.walletListViewModel, wallet]),
child: Container(
padding: EdgeInsets.only(right: 20),
child: Center(
child: Container(
height: 40,
width: 44,
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsBackgroundColor,
),
child: Icon(
Icons.edit,
size: 14,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsColor,
),
),
),
),
),
],
);
},
),
),
), ),
), ),
), Padding(
bottomSectionPadding: EdgeInsets.only(bottom: 24, right: 24, left: 24), padding: const EdgeInsets.all(24),
bottomSection: Column( child: Column(
children: <Widget>[ children: <Widget>[
PrimaryImageButton( PrimaryImageButton(
onPressed: () { onPressed: () {
//TODO(David): Find a way to optimize this //TODO(David): Find a way to optimize this
if (isSingleCoin) { if (isSingleCoin) {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction( widget.authService.authenticateAction(
context, context,
route: Routes.newWallet, route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType, arguments: widget.walletListViewModel.currentWalletType,
conditionToDetermineIfToUse2FA: conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
); );
} else { } else {
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
Routes.newWallet, Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType, arguments: widget.walletListViewModel.currentWalletType,
); );
} }
} else { } else {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction( widget.authService.authenticateAction(
context, context,
route: Routes.newWalletType, route: Routes.newWalletType,
conditionToDetermineIfToUse2FA: conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
); );
} else { } else {
Navigator.of(context).pushNamed(Routes.newWalletType); Navigator.of(context).pushNamed(Routes.newWalletType);
} }
} }
}, },
image: newWalletImage, image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet, text: S.of(context).wallet_list_create_new_wallet,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
textColor: Colors.white, textColor: Colors.white,
),
SizedBox(height: 10.0),
PrimaryImageButton(
onPressed: () {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.restoreOptions,
arguments: false,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
}
},
image: restoreWalletImage,
text: S.of(context).wallet_list_restore_wallet,
color: Theme.of(context).cardColor,
textColor: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
)
],
), ),
SizedBox(height: 10.0), ),
PrimaryImageButton( ],
onPressed: () {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.restoreOptions,
arguments: false,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
}
},
image: restoreWalletImage,
text: S.of(context).wallet_list_restore_wallet,
color: Theme.of(context).cardColor,
textColor: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
)
],
),
), ),
); );
} }
@ -248,6 +257,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

@ -13,7 +13,8 @@ abstract class TradeFilterStoreBase with Store {
displaySideShift = true, displaySideShift = true,
displayMorphToken = true, displayMorphToken = true,
displaySimpleSwap = true, displaySimpleSwap = true,
displayTrocador = true; displayTrocador = true,
displayExolix = true;
@observable @observable
bool displayXMRTO; bool displayXMRTO;
@ -33,8 +34,11 @@ abstract class TradeFilterStoreBase with Store {
@observable @observable
bool displayTrocador; bool displayTrocador;
@observable
bool displayExolix;
@computed @computed
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador; bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix;
@action @action
void toggleDisplayExchange(ExchangeProviderDescription provider) { void toggleDisplayExchange(ExchangeProviderDescription provider) {
@ -56,7 +60,10 @@ abstract class TradeFilterStoreBase with Store {
break; break;
case ExchangeProviderDescription.trocador: case ExchangeProviderDescription.trocador:
displayTrocador = !displayTrocador; displayTrocador = !displayTrocador;
break; break;
case ExchangeProviderDescription.exolix:
displayExolix = !displayExolix;
break;
case ExchangeProviderDescription.all: case ExchangeProviderDescription.all:
if (displayAllTrades) { if (displayAllTrades) {
displayChangeNow = false; displayChangeNow = false;
@ -65,6 +72,7 @@ abstract class TradeFilterStoreBase with Store {
displayMorphToken = false; displayMorphToken = false;
displaySimpleSwap = false; displaySimpleSwap = false;
displayTrocador = false; displayTrocador = false;
displayExolix = false;
} else { } else {
displayChangeNow = true; displayChangeNow = true;
displaySideShift = true; displaySideShift = true;
@ -72,6 +80,7 @@ abstract class TradeFilterStoreBase with Store {
displayMorphToken = true; displayMorphToken = true;
displaySimpleSwap = true; displaySimpleSwap = true;
displayTrocador = true; displayTrocador = true;
displayExolix = true;
} }
break; break;
} }
@ -98,7 +107,8 @@ abstract class TradeFilterStoreBase with Store {
||(displaySimpleSwap && ||(displaySimpleSwap &&
item.trade.provider == item.trade.provider ==
ExchangeProviderDescription.simpleSwap) ExchangeProviderDescription.simpleSwap)
||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)) ||(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador)
||(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix))
.toList() .toList()
: _trades; : _trades;
} }

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';
@ -86,7 +87,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,
@ -147,6 +149,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(
@ -175,6 +181,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;
} }
@ -528,12 +537,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(
@ -547,12 +557,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)!);
@ -562,7 +577,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);
@ -581,7 +597,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;
@ -589,7 +605,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) ?? '';
@ -614,7 +630,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;
@ -628,9 +644,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);
@ -640,13 +658,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)
@ -675,70 +694,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>();
@ -747,30 +772,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)
@ -788,7 +818,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;
@ -805,7 +836,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;
@ -813,7 +844,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) ??
@ -849,9 +880,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);
@ -861,6 +894,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) {
@ -883,6 +917,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;
} }
@ -907,6 +945,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

@ -162,7 +162,7 @@ class ExceptionHandler {
"Handshake error in client", "Handshake error in client",
"Error while launching http", "Error while launching http",
"OS Error: Network is unreachable", "OS Error: Network is unreachable",
"ClientException: Write failed, uri=https:", "ClientException: Write failed, uri=http",
]; ];
static Future<void> _addDeviceInfo(File file) async { static Future<void> _addDeviceInfo(File file) async {

View file

@ -1,3 +1,4 @@
class FeatureFlag { class FeatureFlag {
static const bool isCakePayEnabled = false; static const bool isCakePayEnabled = false;
static const bool isExolixEnabled = false;
} }

View file

@ -17,10 +17,12 @@ class PaymentRequest {
} }
if (nano != null) { if (nano != null) {
if (address.contains("nano")) { if (amount.isNotEmpty) {
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano); if (address.contains("nano")) {
} else if (address.contains("ban")) { amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano);
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano); } else if (address.contains("ban")) {
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano);
}
} }
} }

View file

@ -98,6 +98,11 @@ abstract class DashboardViewModelBase with Store {
caption: ExchangeProviderDescription.trocador.title, caption: ExchangeProviderDescription.trocador.title,
onChanged: () => tradeFilterStore onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.trocador)), .toggleDisplayExchange(ExchangeProviderDescription.trocador)),
FilterItem(
value: () => tradeFilterStore.displayExolix,
caption: ExchangeProviderDescription.exolix.title,
onChanged: () => tradeFilterStore
.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
] ]
}, },
subname = '', subname = '',

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);
@ -96,13 +97,6 @@ class TransactionListItem extends ActionListItem with Keyable {
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
price: price); price: price);
break; break;
case WalletType.ethereum:
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
price: price);
break;
default: default:
break; break;
} }

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
@ -53,6 +54,9 @@ abstract class ExchangeTradeViewModelBase with Store {
case ExchangeProviderDescription.trocador: case ExchangeProviderDescription.trocador:
_provider = TrocadorExchangeProvider(); _provider = TrocadorExchangeProvider();
break; break;
case ExchangeProviderDescription.exolix:
_provider = ExolixExchangeProvider();
break;
} }
_updateItems(); _updateItems();

View file

@ -2,16 +2,21 @@ 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_request.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trocador/trocador_request.dart'; import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
@ -150,6 +155,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
SideShiftExchangeProvider(), SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(), SimpleSwapExchangeProvider(),
TrocadorExchangeProvider(useTorOnly: _useTorOnly), TrocadorExchangeProvider(useTorOnly: _useTorOnly),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
]; ];
@observable @observable
@ -262,8 +268,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;
@ -275,7 +283,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;
} }
@ -546,6 +561,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
amount = isFixedRateMode ? receiveAmount : depositAmount; amount = isFixedRateMode ? receiveAmount : depositAmount;
} }
if (provider is ExolixExchangeProvider) {
request = ExolixRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
amount = amount.replaceAll(',', '.'); amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully) { if (limitsState is LimitsLoadedSuccessfully) {
@ -605,7 +631,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);
@ -680,6 +706,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;
@ -775,6 +805,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,15 @@ 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':
case 'ethereum-wallet': case 'ethereum-wallet':
return WalletType.ethereum; return WalletType.ethereum;
case 'nano':
case 'nano-wallet':
return WalletType.nano;
default: default:
throw Exception('Unexpected wallet type: ${scheme.toString()}'); throw Exception('Unexpected wallet type: ${scheme.toString()}');
} }
@ -107,6 +114,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

@ -53,6 +53,11 @@ abstract class SupportViewModelBase with Store {
icon: 'assets/images/simpleSwap.png', icon: 'assets/images/simpleSwap.png',
linkTitle: 'support@simpleswap.io', linkTitle: 'support@simpleswap.io',
link: 'mailto:support@simpleswap.io'), link: 'mailto:support@simpleswap.io'),
LinkListItem(
title: 'Exolix',
icon: 'assets/images/exolix.png',
linkTitle: 'support@exolix.com',
link: 'mailto:support@exolix.com'),
if (!isMoneroOnly) ... [ if (!isMoneroOnly) ... [
LinkListItem( LinkListItem(
title: 'Wyre', title: 'Wyre',

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart'; import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
@ -54,6 +55,9 @@ abstract class TradeDetailsViewModelBase with Store {
case ExchangeProviderDescription.trocador: case ExchangeProviderDescription.trocador:
_provider = TrocadorExchangeProvider(); _provider = TrocadorExchangeProvider();
break; break;
case ExchangeProviderDescription.exolix:
_provider = ExolixExchangeProvider();
break;
} }
_updateItems(); _updateItems();
@ -157,6 +161,12 @@ abstract class TradeDetailsViewModelBase with Store {
items.add(StandartListItem( items.add(StandartListItem(
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? '')); title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
} }
if (trade.provider == ExchangeProviderDescription.exolix) {
final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}';
items.add(
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
}
} }
void _launchUrl(String url) { void _launchUrl(String url) {

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;

Some files were not shown because too many files have changed in this diff Show more