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/
./install_ndk.sh
source ./app_env.sh cakewallet
chmod +x pubspec_gen.sh
./app_config.sh
- name: Cache Externals
@ -92,6 +93,7 @@ jobs:
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
@ -128,6 +130,7 @@ jobs:
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
@ -140,18 +143,18 @@ jobs:
cd /opt/android/cake_wallet
flutter build apk --release
# - name: Push to App Center
# run: |
# echo 'Installing App Center CLI tools'
# npm install -g appcenter-cli
# echo "Publishing test to App Center"
# appcenter distribute release \
# --group "Testers" \
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
# --release-notes ${GITHUB_HEAD_REF} \
# --app Cake-Labs/Cake-Wallet \
# --token ${{ secrets.APP_CENTER_TOKEN }} \
# --quiet
# - name: Push to App Center
# run: |
# echo 'Installing App Center CLI tools'
# npm install -g appcenter-cli
# echo "Publishing test to App Center"
# appcenter distribute release \
# --group "Testers" \
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
# --release-notes ${GITHUB_HEAD_REF} \
# --app Cake-Labs/Cake-Wallet \
# --token ${{ secrets.APP_CENTER_TOKEN }} \
# --quiet
- name: Rename apk file
run: |

1
.gitignore vendored
View file

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

View file

@ -52,6 +52,15 @@
<data android:scheme="litecoin" />
<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>
</activity>
<meta-data

View file

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=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 WalletConnect to connect your ETH wallet with your favorite dApp
Support getting Addresses from ENS and Mastodon
Add BitcoinCash (BCH)
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_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -1,5 +1,4 @@
import 'package:cw_core/transaction_priority.dart';
//import 'package:cake_wallet/generated/i18n.dart';
class BitcoinTransactionPriority extends TransactionPriority {
const BitcoinTransactionPriority({required String title, required int raw})
@ -100,4 +99,55 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority {
return label;
}
}
class BitcoinCashTransactionPriority extends BitcoinTransactionPriority {
const BitcoinCashTransactionPriority({required String title, required int raw})
: super(title: title, raw: raw);
static const List<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_core/unspent_transaction_output.dart';
class BitcoinUnspent {
BitcoinUnspent(this.address, this.hash, this.value, this.vout)
: isSending = true,
isFrozen = false,
note = '';
class BitcoinUnspent extends Unspent {
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
: bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(
BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
json['tx_pos'] as int);
final BitcoinAddressRecord address;
final String hash;
final int value;
final int vout;
bool get isP2wpkh =>
address.address.startsWith('bc') || address.address.startsWith('ltc');
bool isSending;
bool isFrozen;
String note;
final BitcoinAddressRecord bitcoinAddressRecord;
}

View file

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

View file

@ -2,8 +2,10 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:mobx/mobx.dart';
@ -34,38 +36,42 @@ import 'package:cw_bitcoin/electrum.dart';
import 'package:hex/hex.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:collection/collection.dart';
import 'package:bip32/bip32.dart';
part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
abstract class ElectrumWalletBase
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
with Store {
ElectrumWalletBase(
{required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required this.networkType,
required this.mnemonic,
required Uint8List seedBytes,
required this.encryptionFileUtils,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
CryptoCurrency? currency})
: hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath("m/0'/0"),
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required this.networkType,
required this.mnemonic,
required Uint8List seedBytes,
required this.encryptionFileUtils,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
CryptoCurrency? currency})
: hd = currency == CryptoCurrency.bch
? bitcoinCashHDWallet(seedBytes)
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = <int>[],
_isTransactionUpdating = false,
unspentCoins = [],
_scripthashesUpdateSubject = {},
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(
currency != null
? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0,
frozen: 0)}
: {}),
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
? {
currency:
initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
}
: {}),
this.unspentCoinsInfo = unspentCoinsInfo,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
@ -77,6 +83,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
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) =>
inputsCount * 146 + outputsCounts * 33 + 8;
@ -103,9 +113,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
.toList();
List<String> get publicScriptHashes => walletAddresses.addresses
.where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, networkType: networkType))
.toList();
.where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, networkType: networkType))
.toList();
String get xpub => hd.base58!;
@ -118,8 +128,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
bitcoin.NetworkType networkType;
@override
BitcoinWalletKeys get keys => BitcoinWalletKeys(
wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
BitcoinWalletKeys get keys =>
BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
String _password;
List<BitcoinUnspent> unspentCoins;
@ -147,8 +157,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
await updateBalance();
_feeRates = await electrumClient.feeRates();
Timer.periodic(const Duration(minutes: 1),
(timer) async => _feeRates = await electrumClient.feeRates());
Timer.periodic(
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
syncStatus = SyncedSyncStatus();
} catch (e, stacktrace) {
@ -177,8 +187,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}
@override
Future<PendingBitcoinTransaction> createTransaction(
Object credentials) async {
Future<PendingTransaction> createTransaction(Object credentials) async {
const minAmount = 546;
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
@ -212,13 +221,11 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
var fee = 0;
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll
|| item.formattedCryptoAmount! <= 0)) {
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
throw BitcoinTransactionWrongBalanceException(currency);
}
credentialsAmount = outputs.fold(0, (acc, value) =>
acc + value.formattedCryptoAmount!);
credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
if (allAmount - credentialsAmount < minAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
@ -235,9 +242,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}
} else {
final output = outputs.first;
credentialsAmount = !output.sendAll
? output.formattedCryptoAmount!
: 0;
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
if (credentialsAmount > allAmount) {
throw BitcoinTransactionWrongBalanceException(currency);
@ -299,8 +304,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final p2wpkh = bitcoin
.P2WPKH(
data: generatePaymentData(
hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.address.index),
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.bitcoinAddressRecord.index),
network: networkType)
.data;
@ -311,19 +316,12 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
});
outputs.forEach((item) {
final outputAmount = hasMultiDestination
? item.formattedCryptoAmount
: amount;
final outputAddress = item.isParsedAddress
? item.extractedAddress!
: item.address;
txb.addOutput(
addressToOutputScript(outputAddress, networkType),
outputAmount!);
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
});
final estimatedSize =
estimatedTransactionSize(inputs.length, outputs.length + 1);
final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
var feeAmount = 0;
if (transactionCredentials.feeRate != null) {
@ -341,8 +339,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
final keyPair = generateKeyPair(
hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.address.index,
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: input.bitcoinAddressRecord.index,
network: networkType);
final witnessValue = input.isP2wpkh ? input.value : null;
@ -358,12 +356,12 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}
String toJSON() => json.encode({
'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON()
});
'mnemonic': mnemonic,
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance[currency]?.toJSON()
});
int feeRate(TransactionPriority priority) {
try {
@ -372,34 +370,29 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}
return 0;
} catch(_) {
} catch (_) {
return 0;
}
}
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
int outputsCount) =>
int feeAmountForPriority(
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
int feeAmountWithFeeRate(int feeRate, int inputsCount,
int outputsCount) =>
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
@override
int calculateEstimatedFee(TransactionPriority? priority, int? amount,
{int? outputsCount}) {
int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
if (priority is BitcoinTransactionPriority) {
return calculateEstimatedFeeWithFeeRate(
feeRate(priority),
amount,
outputsCount: outputsCount);
return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
outputsCount: outputsCount);
}
return 0;
}
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount,
{int? outputsCount}) {
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
int inputsCount = 0;
if (amount != null) {
@ -428,8 +421,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
// If send all, then we have no change value
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
return feeAmountWithFeeRate(
feeRate, inputsCount, _outputsCount);
return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
}
@override
@ -444,8 +436,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
final currentWalletFile = File(currentWalletPath);
final currentDirPath =
await pathForWalletDir(name: walletInfo.name, type: type);
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
// Copies current wallet files into new wallet name's dir and files
@ -482,21 +473,20 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
} catch (_) {}
}
Future<String> makePath() async =>
pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> updateUnspent() async {
final unspent = await Future.wait(walletAddresses
.addresses.map((address) => electrumClient
.getListUnspentWithAddress(address.address, networkType)
.then((unspent) => unspent
.map((unspent) {
try {
return BitcoinUnspent.fromJSON(address, unspent);
} catch(_) {
return null;
}
}).whereNotNull())));
.map((unspent) {
try {
return BitcoinUnspent.fromJSON(address, unspent);
} catch(_) {
return null;
}
}).whereNotNull())));
unspentCoins = unspent.expand((e) => e).toList();
if (unspentCoinsInfo.isEmpty) {
@ -506,8 +496,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) && element.hash.contains(coin.hash));
final coinInfoList = unspentCoinsInfo.values
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
@ -526,14 +516,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
final newInfo = UnspentCoinsInfo(
walletId: id,
hash: coin.hash,
isFrozen: coin.isFrozen,
isSending: coin.isSending,
noteRaw: coin.note,
address: coin.address.address,
value: coin.value,
vout: coin.vout,
walletId: id,
hash: coin.hash,
isFrozen: coin.isFrozen,
isSending: coin.isSending,
noteRaw: coin.note,
address: coin.bitcoinAddressRecord.address,
value: coin.value,
vout: coin.vout,
);
await unspentCoinsInfo.add(newInfo);
@ -542,8 +532,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<void> _refreshUnspentCoinsInfo() async {
try {
final List<dynamic> keys = <dynamic>[];
final currentWalletUnspentCoins = unspentCoinsInfo.values
.where((element) => element.walletId.contains(id));
final currentWalletUnspentCoins =
unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
if (currentWalletUnspentCoins.isNotEmpty) {
currentWalletUnspentCoins.forEach((element) {
@ -579,27 +569,19 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
ins.add(tx);
}
return ElectrumTransactionBundle(
original,
ins: ins,
time: time,
confirmations: confirmations);
return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations);
}
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
{required String hash, required int height}) async {
try {
final tx = await getTransactionExpanded(hash: hash, height: height);
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
return ElectrumTransactionInfo.fromElectrumBundle(
tx,
walletInfo.type,
networkType,
addresses: addresses,
height: height);
} catch(_) {
return null;
}
try {
final tx = await getTransactionExpanded(hash: hash, height: height);
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
addresses: addresses, height: height);
} catch (_) {
return null;
}
}
@override
@ -610,10 +592,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
final sh = scriptHash(addressRecord.address, networkType: networkType);
addressHashes[sh] = addressRecord;
});
final histories =
addressHashes.keys.map((scriptHash) => electrumClient
.getHistory(scriptHash)
.then((history) => {scriptHash: history}));
final histories = addressHashes.keys.map((scriptHash) =>
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
final historyResults = await Future.wait(histories);
historyResults.forEach((history) {
history.entries.forEach((historyItem) {
@ -624,19 +604,16 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}
});
});
final historiesWithDetails = await Future.wait(
normalizedHistories
.map((transaction) {
try {
return fetchTransactionInfo(
hash: transaction['tx_hash'] as String,
height: transaction['height'] as int);
} catch(_) {
return Future.value(null);
}
}));
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
<String, ElectrumTransactionInfo>{}, (acc, tx) {
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
try {
return fetchTransactionInfo(
hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
} catch (_) {
return Future.value(null);
}
}));
return historiesWithDetails
.fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
if (tx == null) {
return acc;
}
@ -688,9 +665,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
Future<ElectrumBalance> _fetchBalances() async {
final addresses = walletAddresses.addresses.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i];
final addressRecord = addresses[i] ;
final sh = scriptHash(addressRecord.address, networkType: networkType);
final balanceFuture = electrumClient.getBalance(sh);
balanceFutures.add(balanceFuture);
@ -699,8 +675,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
var totalFrozen = 0;
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash && info.isFrozen && element.address.address == info.address
&& element.value == info.value) {
if (element.hash == info.hash &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
}
});
@ -723,8 +701,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
}
}
return ElectrumBalance(confirmed: totalConfirmed, unconfirmed: totalUnconfirmed,
frozen: totalFrozen);
return ElectrumBalance(
confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
}
Future<void> updateBalance() async {
@ -735,9 +713,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
String getChangeAddress() {
const minCountOfHiddenAddresses = 5;
final random = Random();
var addresses = walletAddresses.addresses
.where((addr) => addr.isHidden)
.toList();
var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList();
if (addresses.length < minCountOfHiddenAddresses) {
addresses = walletAddresses.addresses.toList();

View file

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

View file

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

View file

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

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:
return _moneroAmountToString(amount);
case CryptoCurrency.btc:
case CryptoCurrency.bch:
return _bitcoinAmountToString(amount);
case CryptoCurrency.xhv:
case CryptoCurrency.xag:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -100,6 +100,66 @@
<string>litecoin-wallet</string>
</array>
</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>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>

View file

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

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";
case CryptoCurrency.xmr:
return "XMR_MONERO";
case CryptoCurrency.bch:
return "BCH_BITCOINCASH";
case CryptoCurrency.nano:
return "XNO_NANO";
default:

View file

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

View file

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

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_invoice_info.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
@ -838,6 +839,9 @@ Future<void> setup({
case WalletType.ethereum:
return ethereum!.createEthereumWalletService(
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource,
SettingsStoreBase.walletPasswordDirectInput);
case WalletType.nano:
return nano!.createNanoWalletService(
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);

View file

@ -27,6 +27,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
@ -82,7 +83,10 @@ Future<void> defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeBitcoinCashCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 2:
@ -167,6 +171,11 @@ Future<void> defaultSettingsMigration(
await changeNanoCurrentPowNodeToDefault(
sharedPreferences: sharedPreferences, nodes: powNodes);
break;
case 23:
await addBitcoinCashElectrumServerList(nodes: nodes);
await changeBitcoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
default:
break;
@ -324,6 +333,12 @@ Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
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}) {
final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = '';
@ -359,6 +374,15 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
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(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
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 {
final nodeList = await loadDefaultHavenNodes();
for (var node in nodeList) {
@ -498,27 +531,34 @@ Future<void> checkCurrentNodes(
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentLitecoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
final currentLitecoinElectrumSeverId = sharedPreferences
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences
.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences
.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentNanoNodeId = sharedPreferences
.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences
.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentBitcoinCashNodeId = sharedPreferences
.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentEthereumNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentBitcoinCashNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
@ -566,6 +606,13 @@ Future<void> checkCurrentNodes(
}
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
}
if (currentBitcoinCashNodeServer == null) {
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
}
}
Future<void> resetBitcoinElectrumServer(

View file

@ -52,8 +52,7 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.nano:
case WalletType.banano:
case WalletType.bitcoinCash:
switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
@ -66,6 +65,8 @@ class MainActions {
break;
}
break;
case WalletType.nano:
case WalletType.banano:
case WalletType.monero:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
@ -123,6 +124,7 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.bitcoinCash:
if (viewModel.isEnabledSellAction) {
final moonPaySellProvider = MoonPaySellProvider();
final uri = await moonPaySellProvider.requestUrl(

View file

@ -84,6 +84,23 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
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 {
final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList;
@ -116,10 +133,11 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
return nodes;
}
Future resetToDefault(Box<Node> nodeSource) async {
Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList();
final havenNodes = await loadDefaultHavenNodes();
final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes();
@ -129,13 +147,14 @@ Future resetToDefault(Box<Node> nodeSource) async {
litecoinElectrumServerList +
havenNodes +
ethereumNodes +
bitcoinCashElectrumServerList +
nanoNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);
}
Future resetPowToDefault(Box<Node> powNodeSource) async {
Future<void> resetPowToDefault(Box<Node> powNodeSource) async {
final nanoPowNodes = await loadDefaultNanoPowNodes();
final nodes = nanoPowNodes;
await powNodeSource.clear();

View file

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

View file

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

View file

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

View file

@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
static const trocador =
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: '');
static const exolix =
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) {
@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
case 5:
return trocador;
case 6:
return exolix;
case 7:
return all;
default:
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 settling = TradeState(raw: 'settling', title: 'Settlement in progress');
static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
static const wait = TradeState(raw: 'wait', title: 'Waiting');
static const overdue = TradeState(raw: 'overdue', title: 'Overdue');
static const refund = TradeState(raw: 'refund', title: 'Refund');
static const refunded = TradeState(raw: 'refunded', title: 'Refunded');
static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation');
static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed');
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
static const sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success');
static TradeState deserialize({required String raw}) {
switch (raw) {
case 'pending':
@ -77,6 +86,24 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
return failed;
case 'completed':
return completed;
case 'wait':
return wait;
case 'overdue':
return overdue;
case 'refund':
return refund;
case 'refunded':
return refunded;
case 'confirmation':
return confirmation;
case 'confirmed':
return confirmed;
case 'exchanging':
return exchanging;
case 'sending':
return sending;
case 'success':
return success;
default:
throw Exception('Unexpected token: $raw in TradeState deserialize');
}

View file

@ -101,10 +101,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
if (!CakeHive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(DerivationTypeAdapter());
}
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter());
}
@ -136,7 +136,8 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.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>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
@ -163,7 +164,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 22);
initialMigrationVersion: 23);
}
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 havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/nano_icon.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;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:

View file

@ -194,7 +194,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
contractAddress: _contractAddressController.text,
decimal: int.parse(_tokenDecimalController.text),
));
Navigator.pop(context);
if (context.mounted) {
Navigator.pop(context);
}
}
},
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.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
this.bananoIcon = Image.asset('assets/images/nano_icon.png');
this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png');
final largeScreen = 731;
@ -50,10 +51,10 @@ class MenuWidgetState extends State<MenuWidget> {
Image litecoinIcon;
Image havenIcon;
Image ethereumIcon;
Image bitcoinCashIcon;
Image nanoIcon;
Image bananoIcon;
@override
void initState() {
menuWidth = 0;
@ -212,6 +213,8 @@ class MenuWidgetState extends State<MenuWidget> {
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:

View file

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

View file

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

View file

@ -73,6 +73,7 @@ class PreSeedPage extends BasePage {
case WalletType.monero:
return 25;
case WalletType.ethereum:
case WalletType.bitcoinCash:
return 12;
default:
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/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@ -79,6 +81,9 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
itemBuilder: (_, int index) {
return Observer(builder: (_) {
final item = unspentCoinsListViewModel.items[index];
final address = unspentCoinsListViewModel.wallet.type == WalletType.bitcoinCash
? bitcoinCash!.getCashAddrFormat(item.address)
: item.address;
return GestureDetector(
onTap: () =>
@ -88,7 +93,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
child: UnspentCoinsListItem(
note: item.note,
amount: item.amount,
address: item.address,
address: address,
isSending: item.isSending,
isFrozen: 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/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
@ -53,6 +52,7 @@ class WalletListBodyState extends State<WalletListBody> {
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final scrollController = ScrollController();
final double tileHeight = 60;
@ -69,169 +69,178 @@ class WalletListBodyState extends State<WalletListBody> {
return Container(
padding: EdgeInsets.only(top: 16),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 20),
content: Container(
child: Observer(
builder: (_) => ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (_, index) =>
Divider(color: Theme.of(context).colorScheme.background, height: 32),
itemCount: widget.walletListViewModel.wallets.length,
itemBuilder: (__, index) {
final wallet = widget.walletListViewModel.wallets[index];
final currentColor = wallet.isCurrent
? Theme.of(context)
.extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor
: Theme.of(context).colorScheme.background;
final row = GestureDetector(
onTap: () => wallet.isCurrent ? null : _loadWallet(wallet),
child: Container(
height: tileHeight,
width: double.infinity,
child: Row(
children: <Widget>[
Container(
height: tileHeight,
width: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(4), bottomRight: Radius.circular(4)),
color: currentColor),
),
Expanded(
child: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 20, right: 20),
color: Theme.of(context).colorScheme.background,
alignment: Alignment.centerLeft,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <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,
),
),
),
],
child: Column(
children: [
Expanded(
child: Container(
child: Observer(
builder: (_) => ListView.separated(
physics: const BouncingScrollPhysics(),
separatorBuilder: (_, index) =>
Divider(color: Theme.of(context).colorScheme.background, height: 32),
itemCount: widget.walletListViewModel.wallets.length,
itemBuilder: (__, index) {
final wallet = widget.walletListViewModel.wallets[index];
final currentColor = wallet.isCurrent
? Theme.of(context)
.extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor
: Theme.of(context).colorScheme.background;
final row = GestureDetector(
onTap: () => wallet.isCurrent ? null : _loadWallet(wallet),
child: Container(
height: tileHeight,
width: double.infinity,
child: Row(
children: <Widget>[
Container(
height: tileHeight,
width: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(4),
bottomRight: Radius.circular(4)),
color: currentColor),
),
),
),
],
),
),
);
return wallet.isCurrent
? row
: Row(
children: [
Expanded(child: row),
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit,
arguments: [widget.walletListViewModel, wallet]),
child: Container(
padding: EdgeInsets.only(right: 20),
child: Center(
child: Container(
height: 40,
width: 44,
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsBackgroundColor,
),
child: Icon(
Icons.edit,
size: 14,
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
),
Expanded(
child: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 20, right: 20),
color: Theme.of(context).colorScheme.background,
alignment: Alignment.centerLeft,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <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,
),
),
),
],
),
),
),
),
],
);
},
],
),
),
);
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,
),
),
),
),
),
],
);
},
),
),
),
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24, right: 24, left: 24),
bottomSection: Column(
children: <Widget>[
PrimaryImageButton(
onPressed: () {
//TODO(David): Find a way to optimize this
if (isSingleCoin) {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
);
}
} else {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.newWalletType,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.newWalletType);
}
}
},
image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: <Widget>[
PrimaryImageButton(
onPressed: () {
//TODO(David): Find a way to optimize this
if (isSingleCoin) {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(
Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType,
);
}
} else {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.newWalletType,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.newWalletType);
}
}
},
image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
SizedBox(height: 10.0),
PrimaryImageButton(
onPressed: () {
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
widget.authService.authenticateAction(
context,
route: Routes.restoreOptions,
arguments: false,
conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
);
} else {
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
}
},
image: restoreWalletImage,
text: S.of(context).wallet_list_restore_wallet,
color: Theme.of(context).cardColor,
textColor: Theme.of(context).extension<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;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.bitcoinCash:
return bitcoinCashIcon;
case WalletType.nano:
return nanoIcon;
default:

View file

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

View file

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
@ -86,7 +87,8 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialHavenTransactionPriority,
TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority})
TransactionPriority? initialEthereumTransactionPriority,
TransactionPriority? initialBitcoinCashTransactionPriority})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(powNodes),
_sharedPreferences = sharedPreferences,
@ -147,6 +149,10 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.ethereum] = initialEthereumTransactionPriority;
}
if (initialBitcoinCashTransactionPriority != null) {
priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
}
reaction(
(_) => fiatCurrency,
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
@ -175,6 +181,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.ethereum:
key = PreferencesKey.ethereumTransactionPriority;
break;
case WalletType.bitcoinCash:
key = PreferencesKey.bitcoinCashTransactionPriority;
break;
default:
key = null;
}
@ -528,12 +537,13 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!);
TransactionPriority? bitcoinTransactionPriority =
bitcoin?.deserializeBitcoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!);
bitcoin?.deserializeBitcoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!);
TransactionPriority? havenTransactionPriority;
TransactionPriority? litecoinTransactionPriority;
TransactionPriority? ethereumTransactionPriority;
TransactionPriority? bitcoinCashTransactionPriority;
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@ -547,12 +557,17 @@ abstract class SettingsStoreBase with Store {
ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
}
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@ -562,7 +577,8 @@ abstract class SettingsStoreBase with Store {
final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false;
final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false;
final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
final defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(
PreferencesKey.defaultBuyProvider) ?? 0];
final currentFiatApiMode = FiatApiMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ??
FiatApiMode.enabled.raw);
@ -581,7 +597,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
false;
final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
false;
final shouldRequireTOTP2FAForAddingContacts =
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
@ -589,7 +605,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
false;
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
false;
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
@ -614,7 +630,7 @@ abstract class SettingsStoreBase with Store {
? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
: defaultPinCodeTimeOutDuration;
final sortBalanceBy =
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
final pinNativeTokenAtTop =
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
@ -628,9 +644,11 @@ abstract class SettingsStoreBase with Store {
await LanguageService.localeDetection();
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final litecoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final bitcoinCashElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
@ -640,13 +658,14 @@ abstract class SettingsStoreBase with Store {
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
final autoGenerateSubaddressStatus = generateSubaddresses != null
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
@ -675,70 +694,76 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode;
}
if (bitcoinCashElectrumServer != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
}
if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode;
}
if (nanoPowNode != null) {
powNodes[WalletType.nano] = nanoPowNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
});
final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
});
final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
return SettingsStore(
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialDefaultBuyProvider: defaultBuyProvider,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets:
shouldRequireTOTP2FAForSendsToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup);
}
return SettingsStore(
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialDefaultBuyProvider: defaultBuyProvider,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets:
shouldRequireTOTP2FAForSendsToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup);
}
Future<void> reload({required Box<Node> nodeSource}) async {
final sharedPreferences = await getIt.getAsync<SharedPreferences>();
@ -747,30 +772,35 @@ abstract class SettingsStoreBase with Store {
raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
priority[WalletType.monero] = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
priority[WalletType.monero]!;
priority[WalletType.bitcoin] = bitcoin?.deserializeBitcoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!) ??
priority[WalletType.bitcoin]!;
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
priority[WalletType.haven] = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
raw: sharedPreferences.getInt(PreferencesKey.havenTransactionPriority)!) ??
priority[WalletType.haven]!;
}
if (sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority) != null) {
priority[WalletType.litecoin] = bitcoin?.deserializeLitecoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
priority[WalletType.litecoin]!;
}
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
priority[WalletType.ethereum]!;
}
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ??
priority[WalletType.bitcoinCash]!;
}
final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
autoGenerateSubaddressStatus = generateSubaddresses != null
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
@ -788,7 +818,8 @@ abstract class SettingsStoreBase with Store {
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
defaultBuyProvider = BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
defaultBuyProvider =
BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0];
allowBiometricalAuthentication =
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
allowBiometricalAuthentication;
@ -805,7 +836,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
false;
shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
false;
shouldRequireTOTP2FAForAddingContacts =
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
@ -813,7 +844,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
false;
shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
false;
shouldShowMarketPlaceInDashboard =
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
@ -849,9 +880,11 @@ abstract class SettingsStoreBase with Store {
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final litecoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final bitcoinCashElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
@ -861,6 +894,7 @@ abstract class SettingsStoreBase with Store {
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
if (moneroNode != null) {
@ -883,6 +917,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.ethereum] = ethereumNode;
}
if (bitcoinCashNode != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashNode;
}
if (nanoNode != null) {
nodes[WalletType.nano] = nanoNode;
}
@ -907,6 +945,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.ethereum:
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
break;
case WalletType.bitcoinCash:
await _sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
break;
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
break;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,16 +2,21 @@ import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/exolix/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
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_request.dart';
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -150,6 +155,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
TrocadorExchangeProvider(useTorOnly: _useTorOnly),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
];
@observable
@ -262,8 +268,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
bool get hasAllAmount =>
(wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) &&
depositCurrency == wallet.currency;
(wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash) &&
depositCurrency == wallet.currency;
bool get isMoneroWallet => wallet.type == WalletType.monero;
@ -275,7 +283,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
case WalletType.bitcoin:
return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow();
case WalletType.litecoin:
return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow();
return transactionPriority ==
bitcoin!.getLitecoinTransactionPrioritySlow();
case WalletType.ethereum:
return transactionPriority ==
ethereum!.getEthereumTransactionPrioritySlow();
case WalletType.bitcoinCash:
return transactionPriority ==
bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
default:
return false;
}
@ -546,6 +561,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
if (provider is ExolixExchangeProvider) {
request = ExolixRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = isFixedRateMode ? receiveAmount : depositAmount;
}
amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully) {
@ -605,7 +631,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@action
void calculateDepositAllAmount() {
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) {
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) {
final availableBalance = wallet.balance[wallet.currency]!.available;
final priority = _settingsStore.priority[wallet.type]!;
final fee = wallet.calculateEstimatedFee(priority, null);
@ -680,6 +706,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.ltc;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.bitcoinCash:
depositCurrency = CryptoCurrency.bch;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.haven:
depositCurrency = CryptoCurrency.xhv;
receiveCurrency = CryptoCurrency.btc;
@ -775,6 +805,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
case WalletType.litecoin:
_settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium();
break;
case WalletType.ethereum:
_settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
break;
case WalletType.bitcoinCash:
_settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
break;
default:
break;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
import 'package:cw_core/wallet_type.dart';
@ -19,12 +20,14 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
UnspentCoinsDetailsViewModelBase(
{required this.unspentCoinsItem, required this.unspentCoinsListViewModel})
: items = <TransactionDetailsListItem>[],
_type = unspentCoinsListViewModel.wallet.type,
isFrozen = unspentCoinsItem.isFrozen,
note = unspentCoinsItem.note {
items = [
StandartListItem(title: S.current.transaction_details_amount, value: unspentCoinsItem.amount),
StandartListItem(title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash),
StandartListItem(title: S.current.widgets_address, value: unspentCoinsItem.address),
StandartListItem(
title: S.current.transaction_details_transaction_id, value: unspentCoinsItem.hash),
StandartListItem(title: S.current.widgets_address, value: formattedAddress),
TextFieldListItem(
title: S.current.note_tap_to_change,
value: note,
@ -46,14 +49,13 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
})
];
if ([WalletType.bitcoin, WalletType.litecoin].contains(unspentCoinsListViewModel.wallet.type)) {
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(_type)) {
items.add(BlockExplorerListItem(
title: S.current.view_in_block_explorer,
value: _explorerDescription(unspentCoinsListViewModel.wallet.type),
value: _explorerDescription(_type),
onTap: () {
try {
final url = Uri.parse(
_explorerUrl(unspentCoinsListViewModel.wallet.type, unspentCoinsItem.hash));
final url = Uri.parse(_explorerUrl(_type, unspentCoinsItem.hash));
return launchUrl(url);
} catch (e) {}
},
@ -67,6 +69,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
return 'https://ordinals.com/tx/${txId}';
case WalletType.litecoin:
return 'https://litecoin.earlyordies.com/tx/${txId}';
case WalletType.bitcoinCash:
return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
default:
return '';
}
@ -78,6 +82,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'Ordinals.com';
case WalletType.litecoin:
return S.current.view_transaction_on + 'Earlyordies.com';
case WalletType.bitcoinCash:
return S.current.view_transaction_on + 'Blockchair.com';
default:
return '';
}
@ -91,5 +97,10 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
final UnspentCoinsItem unspentCoinsItem;
final UnspentCoinsListViewModel unspentCoinsListViewModel;
final WalletType _type;
List<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:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/unspent_transaction_output.dart';
import 'package:cw_core/unspent_transaction_output.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
@ -24,11 +25,11 @@ abstract class UnspentCoinsListViewModelBase with Store {
final Box<UnspentCoinsInfo> _unspentCoinsInfo;
@computed
ObservableList<UnspentCoinsItem> get items =>
ObservableList.of(_getUnspents().map((elem) {
ObservableList<UnspentCoinsItem> get items => ObservableList.of(_getUnspents().map((elem) {
final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}';
final info = getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
return UnspentCoinsItem(
address: elem.address,
@ -39,13 +40,13 @@ abstract class UnspentCoinsListViewModelBase with Store {
isSending: info?.isSending ?? true,
amountRaw: elem.value,
vout: elem.vout,
keyImage: elem.keyImage
);
keyImage: elem.keyImage);
}));
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
try {
final info = getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
final info =
getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
if (info == null) {
final newInfo = UnspentCoinsInfo(
walletId: wallet.id,
@ -56,8 +57,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
isFrozen: item.isFrozen,
isSending: item.isSending,
noteRaw: item.note,
keyImage: item.keyImage
);
keyImage: item.keyImage);
await _unspentCoinsInfo.add(newInfo);
_updateUnspents();
@ -76,37 +76,34 @@ abstract class UnspentCoinsListViewModelBase with Store {
}
}
UnspentCoinsInfo? getUnspentCoinInfo(String hash, String address, int value, int vout, String? keyImage) {
UnspentCoinsInfo? getUnspentCoinInfo(
String hash, String address, int value, int vout, String? keyImage) {
return _unspentCoinsInfo.values.firstWhereOrNull((element) =>
element.walletId == wallet.id &&
element.hash == hash &&
element.address == address &&
element.value == value &&
element.vout == vout &&
element.keyImage == keyImage
);
element.keyImage == keyImage);
}
String formatAmountToString(int fullBalance) {
if (wallet.type == WalletType.monero)
return monero!.formatterMoneroAmountToString(amount: fullBalance);
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance);
return '';
}
void _updateUnspents() {
if (wallet.type == WalletType.monero)
return monero!.updateUnspents(wallet);
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet);
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
return bitcoin!.updateUnspents(wallet);
}
List<Unspent> _getUnspents() {
if (wallet.type == WalletType.monero)
return monero!.getUnspents(wallet);
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type))
if (wallet.type == WalletType.monero) return monero!.getUnspents(wallet);
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
return bitcoin!.getUnspents(wallet);
return List.empty();
}

View file

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

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

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