Litcoin bitcoin cash fix (#1339)

* Make address to output script a single entry point
Fix network type for bitcoin cash

* Add MoonPay to sell polygon

* Normalize currency for moonpay widget

* Minor fix

* fix: litecoin & bch address types

* fix: remove print

* fix: network decode location

* fix: missing place additional network type

* fix: wrong initial address page type

* fix: initial address generation

* fix: btc exchange sending all, bch without change addresses

* Minor fixes

* Update app versions [skip ci]

---------

Co-authored-by: Rafael Saes <git@rafael.saes.dev>
This commit is contained in:
Omar Hatem 2024-03-21 04:51:57 +02:00 committed by GitHub
parent a0c0ede099
commit 5a7a0e01a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 497 additions and 447 deletions

View file

@ -1,5 +1 @@
Monero enhancements
Bitcoin support different address types (Taproot, Segwit P2WPKH/P2WSH, Legacy)
In-App live status page for the app services
Add Exolix exchange provider
Bug fixes and enhancements

View file

@ -3,6 +3,9 @@ import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
List<int> addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
try {
if (network == bitcoin.BitcoinCashNetwork.mainnet) {
return bitcoin.BitcoinCashAddress(address).baseAddress.toScriptPubKey().toBytes();
}
return bitcoin.addressToOutputScript(address: address, network: network);
} catch (err) {
print(err);

View file

@ -1,5 +1,4 @@
import 'dart:convert';
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/script_hash.dart' as sh;
@ -20,10 +19,9 @@ class BitcoinAddressRecord {
_balance = balance,
_name = name,
_isUsed = isUsed,
scriptHash =
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
scriptHash = scriptHash ?? sh.scriptHash(address, network: network);
factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork? network) {
factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork network) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(
@ -39,9 +37,7 @@ class BitcoinAddressRecord {
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SegwitAddresType.p2wpkh,
scriptHash: decoded['scriptHash'] as String?,
network: (decoded['network'] as String?) == null
? network
: BasedUtxoNetwork.fromName(decoded['network'] as String),
network: network,
);
}
@ -56,7 +52,7 @@ class BitcoinAddressRecord {
String _name;
bool _isUsed;
String? scriptHash;
BasedUtxoNetwork? network;
BasedUtxoNetwork network;
int get txCount => _txCount;
@ -76,8 +72,6 @@ class BitcoinAddressRecord {
@override
int get hashCode => address.hashCode;
String get cashAddr => bitbox.Address.toCashAddress(address);
BitcoinAddressType type;
String updateScriptHash(BasedUtxoNetwork network) {
@ -95,6 +89,5 @@ class BitcoinAddressRecord {
'balance': balance,
'type': type.toString(),
'scriptHash': scriptHash,
'network': network?.value,
});
}

View file

@ -92,8 +92,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
final network = walletInfo.network != null
? BasedUtxoNetwork.fromName(walletInfo.network!)
: BitcoinNetwork.mainnet;
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
return BitcoinWallet(
mnemonic: snp.mnemonic,
@ -106,7 +108,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
networkParam: snp.network,
networkParam: network,
);
}
}

View file

@ -75,11 +75,7 @@ abstract class ElectrumWalletBase
}
: {}),
this.unspentCoinsInfo = unspentCoinsInfo,
this.network = networkType == bitcoin.bitcoin
? BitcoinNetwork.mainnet
: networkType == litecoinNetwork
? LitecoinNetwork.mainnet
: BitcoinNetwork.testnet,
this.network = _getNetwork(networkType, currency),
this.isTestnet = networkType == bitcoin.testnet,
super(walletInfo) {
this.electrumClient = electrumClient ?? ElectrumClient();
@ -192,12 +188,13 @@ abstract class ElectrumWalletBase
}
}
Future<EstimatedTxResult> _estimateTxFeeAndInputsToUse(
Future<EstimatedTxResult> estimateTxFeeAndInputsToUse(
int credentialsAmount,
bool sendAll,
List<BitcoinBaseAddress> outputAddresses,
List<BitcoinOutput> outputs,
BitcoinTransactionCredentials transactionCredentials,
int? feeRate,
BitcoinTransactionPriority? priority,
{int? inputsCount}) async {
final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = [];
@ -212,7 +209,7 @@ abstract class ElectrumWalletBase
allInputsAmount += utx.value;
leftAmount = leftAmount - utx.value;
final address = _addressTypeFromStr(utx.address, network);
final address = addressTypeFromStr(utx.address, network);
final privkey = generateECPrivate(
hd: utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: utx.bitcoinAddressRecord.index,
@ -249,7 +246,7 @@ abstract class ElectrumWalletBase
if (!sendAll) {
if (changeValue > 0) {
final changeAddress = await walletAddresses.getChangeAddress();
final address = _addressTypeFromStr(changeAddress, network);
final address = addressTypeFromStr(changeAddress, network);
outputAddresses.add(address);
outputs.add(BitcoinOutput(address: address, value: BigInt.from(changeValue)));
}
@ -258,9 +255,9 @@ abstract class ElectrumWalletBase
final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
utxos: utxos, outputs: outputs, network: network);
final fee = transactionCredentials.feeRate != null
? feeAmountWithFeeRate(transactionCredentials.feeRate!, 0, 0, size: estimatedSize)
: feeAmountForPriority(transactionCredentials.priority!, 0, 0, size: estimatedSize);
int fee = feeRate != null
? feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize)
: feeAmountForPriority(priority!, 0, 0, size: estimatedSize);
if (fee == 0) {
throw BitcoinTransactionWrongBalanceException(currency);
@ -297,8 +294,8 @@ abstract class ElectrumWalletBase
outputs.removeLast();
}
return _estimateTxFeeAndInputsToUse(
credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials,
return estimateTxFeeAndInputsToUse(
credentialsAmount, sendAll, outputAddresses, outputs, feeRate, priority,
inputsCount: utxos.length + 1);
}
}
@ -319,7 +316,7 @@ abstract class ElectrumWalletBase
for (final out in transactionCredentials.outputs) {
final outputAddress = out.isParsedAddress ? out.extractedAddress! : out.address;
final address = _addressTypeFromStr(outputAddress, network);
final address = addressTypeFromStr(outputAddress, network);
outputAddresses.add(address);
@ -344,8 +341,14 @@ abstract class ElectrumWalletBase
}
}
final estimatedTx = await _estimateTxFeeAndInputsToUse(
credentialsAmount, sendAll, outputAddresses, outputs, transactionCredentials);
final estimatedTx = await estimateTxFeeAndInputsToUse(
credentialsAmount,
sendAll,
outputAddresses,
outputs,
transactionCredentials.feeRate,
transactionCredentials.priority,
);
final txb = BitcoinTransactionBuilder(
utxos: estimatedTx.utxos,
@ -391,7 +394,6 @@ abstract class ElectrumWalletBase
? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet',
});
int feeRate(TransactionPriority priority) {
@ -852,6 +854,22 @@ abstract class ElectrumWalletBase
final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message));
}
static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) {
if (networkType == bitcoin.bitcoin && currency == CryptoCurrency.bch) {
return BitcoinCashNetwork.mainnet;
}
if (networkType == litecoinNetwork) {
return LitecoinNetwork.mainnet;
}
if (networkType == bitcoin.testnet) {
return BitcoinNetwork.testnet;
}
return BitcoinNetwork.mainnet;
}
}
class EstimateTxParams {
@ -879,7 +897,7 @@ class EstimatedTxResult {
final int amount;
}
BitcoinBaseAddress _addressTypeFromStr(String address, BasedUtxoNetwork network) {
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
if (P2pkhAddress.regex.hasMatch(address)) {
return P2pkhAddress.fromAddress(address: address, network: network);
} else if (P2shAddress.regex.hasMatch(address)) {

View file

@ -1,6 +1,5 @@
import 'package:bitcoin_base/bitcoin_base.dart';
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_core/wallet_addresses.dart';
@ -30,6 +29,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
BitcoinAddressType? initialAddressPageType,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType =
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
@ -41,9 +41,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
.toSet()),
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
_addressPageType = walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddresType.p2wpkh,
_addressPageType = initialAddressPageType ??
(walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddresType.p2wpkh),
super(walletInfo) {
updateAddressesByMatch();
}
@ -52,10 +53,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const defaultChangeAddressesCount = 17;
static const gap = 20;
static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
final ObservableList<BitcoinAddressRecord> _addresses;
// Matched by addressPageType
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
@ -67,7 +64,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final bitcoin.HDWallet sideHd;
@observable
BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
late BitcoinAddressType _addressPageType;
@computed
BitcoinAddressType get addressPageType => _addressPageType;
@ -97,7 +94,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
}
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
return receiveAddress;
}
@observable
@ -105,9 +102,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override
set address(String addr) {
if (addr.startsWith('bitcoincash:')) {
addr = toLegacy(addr);
}
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
previousAddressRecord = addressRecord;
@ -155,11 +149,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override
Future<void> init() async {
await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
if (walletInfo.type == WalletType.bitcoinCash) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) {
await _generateInitialAddresses();
} else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
}
updateAddressesByMatch();
updateReceiveAddresses();
updateChangeAddresses();
@ -229,9 +229,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
void updateAddress(String address, String label) {
if (address.startsWith('bitcoincash:')) {
address = toLegacy(address);
}
final addressRecord =
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
addressRecord.setNewName(label);
@ -261,7 +258,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressRecord.isHidden &&
!addressRecord.isUsed &&
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
addressRecord.type == SegwitAddresType.p2wpkh);
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
changeAddresses.addAll(newAddresses);
}

View file

@ -17,14 +17,12 @@ class ElectrumWalletSnapshot {
required this.regularAddressIndex,
required this.changeAddressIndex,
required this.addressPageType,
required this.network,
});
final String name;
final String password;
final WalletType type;
final String addressPageType;
final BasedUtxoNetwork network;
final String? addressPageType;
String mnemonic;
List<BitcoinAddressRecord> addresses;
@ -32,7 +30,8 @@ class ElectrumWalletSnapshot {
Map<String, int> regularAddressIndex;
Map<String, int> changeAddressIndex;
static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password, BasedUtxoNetwork? network) async {
static Future<ElectrumWalletSnapshot> load(
String name, WalletType type, String password, BasedUtxoNetwork network) async {
final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
@ -71,8 +70,7 @@ class ElectrumWalletSnapshot {
balance: balance,
regularAddressIndex: regularAddressIndexByType,
changeAddressIndex: changeAddressIndexByType,
addressPageType: data['address_page_type'] as String? ?? SegwitAddresType.p2wpkh.toString(),
network: data['network_type'] == 'testnet' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet,
addressPageType: data['address_page_type'] as String?,
);
}
}

View file

@ -1,8 +1,9 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/address_to_output_script.dart';
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
String scriptHash(String address, {required BasedUtxoNetwork network}) {
final outputScript = addressToOutputScript(address: address, network: network);
String scriptHash(String address, {required bitcoin.BasedUtxoNetwork network}) {
final outputScript = addressToOutputScript(address, network);
final parts = sha256.convert(outputScript).toString().split('');
var res = '';

View file

@ -79,11 +79,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: cake-update-v1
resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa"
ref: cake-update-v2
resolved-ref: "3fd81d238b990bb767fc7a4fdd5053a22a142e2e"
url: "https://github.com/cake-tech/bitcoin_base.git"
source: git
version: "4.0.0"
version: "4.2.0"
bitcoin_flutter:
dependency: "direct main"
description:
@ -97,10 +97,10 @@ packages:
dependency: "direct main"
description:
name: blockchain_utils
sha256: "9701dfaa74caad4daae1785f1ec4445cf7fb94e45620bc3a4aca1b9b281dc6c9"
sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
version: "2.1.1"
boolean_selector:
dependency: transitive
description:

View file

@ -33,8 +33,8 @@ dependencies:
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v1
blockchain_utils: ^1.6.0
ref: cake-update-v2
blockchain_utils: ^2.1.1
dev_dependencies:
flutter_test:

View file

@ -34,7 +34,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
BitcoinAddressType? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
@ -58,6 +58,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
network: network,
initialAddressPageType: addressPageType,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
@ -84,7 +85,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
seedBytes: await Mnemonic.toSeed(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
addressPageType: P2pkhAddressType.p2pkh,
);
}
@ -101,12 +102,31 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses,
initialAddresses: snp.addresses.map((addr) {
try {
BitcoinCashAddress(addr.address);
return BitcoinAddressRecord(
addr.address,
index: addr.index,
isHidden: addr.isHidden,
type: P2pkhAddressType.p2pkh,
network: BitcoinCashNetwork.mainnet,
);
} catch (_) {
return BitcoinAddressRecord(
AddressUtils.getCashAddrFormat(addr.address),
index: addr.index,
isHidden: addr.isHidden,
type: P2pkhAddressType.p2pkh,
network: BitcoinCashNetwork.mainnet,
);
}
}).toList(),
initialBalance: snp.balance,
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType,
addressPageType: P2pkhAddressType.p2pkh,
);
}

View file

@ -19,6 +19,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,
super.initialAddressPageType,
}) : super(walletInfo);
@override

View file

@ -32,7 +32,7 @@ dependencies:
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v1
ref: cake-update-v2

View file

@ -277,7 +277,7 @@ SPEC CHECKSUMS:
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
local_auth_ios: 1ba1475238daa33a6ffa2a29242558437be435ac
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
@ -302,4 +302,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: fcb1b8418441a35b438585c9dd8374e722e6c6ca
COCOAPODS: 1.12.1
COCOAPODS: 1.15.2

View file

@ -113,13 +113,35 @@ class CWBitcoin extends Bitcoin {
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: electrumWallet.type == WalletType.bitcoinCash ? addr.cashAddr : addr.address,
address: addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
Future<int> estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority) async {
final electrumWallet = wallet as ElectrumWallet;
final sk = ECPrivate.random();
final p2shAddr = sk.getPublic().toP2pkhInP2sh();
final p2wpkhAddr = sk.getPublic().toP2wpkhAddress();
final estimatedTx = await electrumWallet.estimateTxFeeAndInputsToUse(
0,
true,
// Deposit address + change address
[p2shAddr, p2wpkhAddr],
[
BitcoinOutput(address: p2shAddr, value: BigInt.zero),
BitcoinOutput(address: p2wpkhAddr, value: BigInt.zero)
],
null,
priority as BitcoinTransactionPriority);
return estimatedTx.amount;
}
@override
String getAddress(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;

View file

@ -81,7 +81,7 @@ class MoonPaySellProvider extends BuyProvider {
'',
<String, dynamic>{
'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
'defaultBaseCurrencyCode': _normalizeCurrency(currency),
'refundWalletAddress': refundWalletAddress,
}..addAll(customParams),
);
@ -134,6 +134,14 @@ class MoonPaySellProvider extends BuyProvider {
);
}
}
String _normalizeCurrency(CryptoCurrency currency) {
if (currency == CryptoCurrency.maticpoly) {
return "MATIC_POLYGON";
}
return currency.toString().toLowerCase();
}
}
class MoonPayBuyProvider extends BuyProvider {

View file

@ -274,7 +274,7 @@ class AddressValidator extends TextValidator {
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type
case CryptoCurrency.ltc:
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'

View file

@ -89,7 +89,12 @@ class ProvidersHelper {
case WalletType.bitcoinCash:
return [ProviderType.askEachTime, ProviderType.moonpaySell];
case WalletType.polygon:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.dfx];
return [
ProviderType.askEachTime,
ProviderType.onramper,
ProviderType.moonpaySell,
ProviderType.dfx,
];
case WalletType.solana:
return [
ProviderType.askEachTime,

View file

@ -384,7 +384,7 @@ class ExchangePage extends BasePage {
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
if (depositKey.currentState!.amountController.text != amount) {
if (depositKey.currentState!.amountController.text != amount && amount != S.of(context).all) {
depositKey.currentState!.amountController.text = amount;
}
});
@ -467,7 +467,9 @@ class ExchangePage extends BasePage {
.addListener(() => exchangeViewModel.depositAddress = depositAddressController.text);
depositAmountController.addListener(() {
if (depositAmountController.text != exchangeViewModel.depositAmount) {
if (depositAmountController.text != exchangeViewModel.depositAmount &&
depositAmountController.text != S.of(context).all) {
exchangeViewModel.isSendAllEnabled = false;
_depositAmountDebounce.run(() {
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
exchangeViewModel.isReceiveAmountEntered = false;
@ -589,8 +591,9 @@ class ExchangePage extends BasePage {
onDispose: disposeBestRateSync,
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel.calculateDepositAllAmount()
? () => exchangeViewModel.enableSendAllAmount()
: null,
isAllAmountEnabled: exchangeViewModel.isSendAllEnabled,
amountFocusNode: _depositAmountFocus,
addressFocusNode: _depositAddressFocus,
key: depositKey,
@ -626,8 +629,10 @@ class ExchangePage extends BasePage {
},
imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent,
addressButtonsColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderTopPanelColor,
addressButtonsColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor:
Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderTopPanelColor,
currencyValueValidator: (value) {
return !exchangeViewModel.isFixedRateMode
? AmountValidator(
@ -673,8 +678,10 @@ class ExchangePage extends BasePage {
exchangeViewModel.changeReceiveCurrency(currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: Colors.transparent,
addressButtonsColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderBottomPanelColor,
addressButtonsColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor:
Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderBottomPanelColor,
currencyValueValidator: (value) {
return exchangeViewModel.isFixedRateMode
? AmountValidator(

View file

@ -56,17 +56,14 @@ class ExchangeTemplatePage extends BasePage {
height: 8,
);
final depositWalletName =
exchangeViewModel.depositCurrency == CryptoCurrency.xmr
final depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
final receiveWalletName =
exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
final receiveWalletName = exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
WidgetsBinding.instance
.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
return KeyboardActions(
disableScroll: true,
@ -76,128 +73,125 @@ class ExchangeTemplatePage extends BasePage {
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _depositAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()]),
focusNode: _depositAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]),
KeyboardActionsItem(
focusNode: _receiveAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()])
focusNode: _receiveAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()])
]),
child: Container(
color: Theme.of(context).colorScheme.background,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
padding: EdgeInsets.only(bottom: 32),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)
),
gradient: LinearGradient(
colors: [
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor,
Theme.of(context).extension<ExchangePageTheme>()!.secondGradientBottomPanelColor,
],
stops: [0.35, 1.0],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
),
child: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)
color: Theme.of(context).colorScheme.background,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
padding: EdgeInsets.only(bottom: 32),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context)
.extension<ExchangePageTheme>()!
.firstGradientBottomPanelColor,
Theme.of(context)
.extension<ExchangePageTheme>()!
.secondGradientBottomPanelColor,
], stops: [
0.35,
1.0
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
child: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context)
.extension<ExchangePageTheme>()!
.firstGradientTopPanelColor,
Theme.of(context)
.extension<ExchangePageTheme>()!
.secondGradientTopPanelColor,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
amountFocusNode: _depositAmountFocus,
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency: exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName ?? '',
initialAddress: exchangeViewModel.depositCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
isAmountEstimated: false,
hasRefundAddress: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeDepositCurrency(currency: currency),
imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent,
addressButtonsColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldButtonColor,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderBottomPanelColor,
currencyValueValidator:
AmountValidator(currency: exchangeViewModel.depositCurrency),
//addressTextFieldValidator: AddressValidator(
// type: exchangeViewModel.depositCurrency),
),
),
),
gradient: LinearGradient(
colors: [
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientTopPanelColor,
Theme.of(context).extension<ExchangePageTheme>()!.secondGradientTopPanelColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
),
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
amountFocusNode: _depositAmountFocus,
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency:
exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName ?? '',
initialAddress: exchangeViewModel
.depositCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeViewModel
.isDepositAddressEnabled,
isAmountEstimated: false,
hasRefundAddress: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeDepositCurrency(
currency: currency),
imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
Theme.of(context).extension<ExchangePageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderBottomPanelColor,
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.depositCurrency),
//addressTextFieldValidator: AddressValidator(
// type: exchangeViewModel.depositCurrency),
),
),
Padding(
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
amountFocusNode: _receiveAmountFocus,
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency: exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName ?? '',
initialAddress: exchangeViewModel.receiveCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: false,
isAmountEstimated: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: exchangeViewModel.receiveCurrencies,
onCurrencySelected: (currency) => exchangeViewModel
.changeReceiveCurrency(currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: Colors.transparent,
addressButtonsColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldButtonColor,
borderColor: Theme.of(context)
.extension<ExchangePageTheme>()!
.textFieldBorderBottomPanelColor,
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.receiveCurrency),
//addressTextFieldValidator: AddressValidator(
// type: exchangeViewModel.receiveCurrency),
)),
)
],
),
Padding(
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
amountFocusNode: _receiveAmountFocus,
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency:
exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName ?? '',
initialAddress:
exchangeViewModel.receiveCurrency ==
exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.walletAddresses.address
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: false,
isAmountEstimated: true,
isMoneroWallet: exchangeViewModel.isMoneroWallet,
currencies: exchangeViewModel.receiveCurrencies,
onCurrencySelected: (currency) =>
exchangeViewModel.changeReceiveCurrency(
currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
Theme.of(context).extension<ExchangePageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<ExchangePageTheme>()!.textFieldBorderBottomPanelColor,
currencyValueValidator: AmountValidator(
currency: exchangeViewModel.receiveCurrency),
//addressTextFieldValidator: AddressValidator(
// type: exchangeViewModel.receiveCurrency),
)),
)
],
),
),
),
),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(children: <Widget>[
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Observer(
@ -217,36 +211,31 @@ class ExchangeTemplatePage extends BasePage {
),
),
PrimaryButton(
onPressed: () {
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
exchangeViewModel.addTemplate(
amount: exchangeViewModel.depositAmount,
depositCurrency:
exchangeViewModel.depositCurrency.name,
depositCurrencyTitle: exchangeViewModel
.depositCurrency.title + ' ${exchangeViewModel.depositCurrency.tag ?? ''}',
receiveCurrency:
exchangeViewModel.receiveCurrency.name,
receiveCurrencyTitle: exchangeViewModel
.receiveCurrency.title + ' ${exchangeViewModel.receiveCurrency.tag ?? ''}',
provider: exchangeViewModel.provider.toString(),
depositAddress: exchangeViewModel.depositAddress,
receiveAddress: exchangeViewModel.receiveAddress);
exchangeViewModel.updateTemplate();
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).primaryColor,
textColor: Colors.white),
]),
))
)
);
onPressed: () {
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
exchangeViewModel.addTemplate(
amount: exchangeViewModel.depositAmount,
depositCurrency: exchangeViewModel.depositCurrency.name,
depositCurrencyTitle: exchangeViewModel.depositCurrency.title +
' ${exchangeViewModel.depositCurrency.tag ?? ''}',
receiveCurrency: exchangeViewModel.receiveCurrency.name,
receiveCurrencyTitle: exchangeViewModel.receiveCurrency.title +
' ${exchangeViewModel.receiveCurrency.tag ?? ''}',
provider: exchangeViewModel.provider.toString(),
depositAddress: exchangeViewModel.depositAddress,
receiveAddress: exchangeViewModel.receiveAddress);
exchangeViewModel.updateTemplate();
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Theme.of(context).primaryColor,
textColor: Colors.white),
]),
))));
}
void _setReactions(
BuildContext context, ExchangeViewModel exchangeViewModel) {
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
if (_isReactionsSet) {
return;
}
@ -272,33 +261,27 @@ class ExchangeTemplatePage extends BasePage {
// key.currentState.changeLimits(min: min, max: max);
// }
_onCurrencyChange(
exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
_onCurrencyChange(
exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
_onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
_onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
reaction(
(_) => exchangeViewModel.wallet.name,
(String _) => _onWalletNameChange(
exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
(_) => exchangeViewModel.wallet.name,
(String _) =>
_onWalletNameChange(exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
reaction(
(_) => exchangeViewModel.wallet.name,
(String _) => _onWalletNameChange(
exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
(_) => exchangeViewModel.wallet.name,
(String _) =>
_onWalletNameChange(exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
reaction(
(_) => exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, exchangeViewModel, receiveKey));
reaction((_) => exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, receiveKey));
reaction(
(_) => exchangeViewModel.depositCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => exchangeViewModel.depositCurrency,
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
if (depositKey.currentState!.amountController.text != amount) {
if (depositKey.currentState!.amountController.text != amount && amount != S.of(context).all) {
depositKey.currentState!.amountController.text = amount;
}
});
@ -309,10 +292,9 @@ class ExchangeTemplatePage extends BasePage {
}
});
reaction((_) => exchangeViewModel.isDepositAddressEnabled,
(bool isEnabled) {
depositKey.currentState!.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
depositKey.currentState!.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => exchangeViewModel.receiveAmount, (String amount) {
if (receiveKey.currentState!.amountController.text != amount) {
@ -353,30 +335,28 @@ class ExchangeTemplatePage extends BasePage {
receiveKey.currentState.changeLimits(min: null, max: null);
});*/
depositAddressController.addListener(
() => exchangeViewModel.depositAddress = depositAddressController.text);
depositAddressController
.addListener(() => exchangeViewModel.depositAddress = depositAddressController.text);
depositAmountController.addListener(() {
if (depositAmountController.text != exchangeViewModel.depositAmount) {
exchangeViewModel.changeDepositAmount(
amount: depositAmountController.text);
if (depositAmountController.text != exchangeViewModel.depositAmount &&
exchangeViewModel.depositAmount != S.of(context).all) {
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
exchangeViewModel.isReceiveAmountEntered = false;
}
});
receiveAddressController.addListener(
() => exchangeViewModel.receiveAddress = receiveAddressController.text);
receiveAddressController
.addListener(() => exchangeViewModel.receiveAddress = receiveAddressController.text);
receiveAmountController.addListener(() {
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
exchangeViewModel.changeReceiveAmount(
amount: receiveAmountController.text);
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
exchangeViewModel.isReceiveAmountEntered = true;
}
});
reaction((_) => exchangeViewModel.wallet.walletAddresses.address,
(String address) {
reaction((_) => exchangeViewModel.wallet.walletAddresses.address, (String address) {
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
depositKey.currentState!.changeAddress(address: address);
}
@ -389,29 +369,26 @@ class ExchangeTemplatePage extends BasePage {
_isReactionsSet = true;
}
void _onCurrencyChange(CryptoCurrency currency,
ExchangeViewModel exchangeViewModel, GlobalKey<ExchangeCardState> key) {
void _onCurrencyChange(CryptoCurrency currency, ExchangeViewModel exchangeViewModel,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
key.currentState!.changeSelectedCurrency(currency);
key.currentState!.changeWalletName(
isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
key.currentState!.changeAddress(
address: isCurrentTypeWallet
? exchangeViewModel.wallet.walletAddresses.address : '');
address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.address : '');
key.currentState!.changeAmount(amount: '');
}
void _onWalletNameChange(ExchangeViewModel exchangeViewModel,
CryptoCurrency currency, GlobalKey<ExchangeCardState> key) {
void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
if (isCurrentTypeWallet) {
key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
key.currentState!.addressController.text =
exchangeViewModel.wallet.walletAddresses.address;
key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.address;
} else if (key.currentState!.addressController.text ==
exchangeViewModel.wallet.walletAddresses.address) {
key.currentState!.changeWalletName('');

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
import 'package:cake_wallet/routes.dart';
@ -37,6 +38,7 @@ class ExchangeCard extends StatefulWidget {
this.addressButtonsColor = Colors.transparent,
this.borderColor = Colors.transparent,
this.hasAllAmount = false,
this.isAllAmountEnabled = false,
this.amountFocusNode,
this.addressFocusNode,
this.allAmount,
@ -62,9 +64,11 @@ class ExchangeCard extends StatefulWidget {
final Color borderColor;
final FormFieldValidator<String>? currencyValueValidator;
final FormFieldValidator<String>? addressTextFieldValidator;
final FormFieldValidator<String> allAmountValidator = AllAmountValidator();
final FocusNode? amountFocusNode;
final FocusNode? addressFocusNode;
final bool hasAllAmount;
final bool isAllAmountEnabled;
final VoidCallback? allAmount;
final void Function(BuildContext context)? onPushPasteButton;
final void Function(BuildContext context)? onPushAddressBookButton;
@ -76,15 +80,15 @@ class ExchangeCard extends StatefulWidget {
class ExchangeCardState extends State<ExchangeCard> {
ExchangeCardState()
: _title = '',
_min = '',
_max = '',
_isAmountEditable = false,
_isAddressEditable = false,
_walletName = '',
_selectedCurrency = CryptoCurrency.btc,
_isAmountEstimated = false,
_isMoneroWallet = false;
: _title = '',
_min = '',
_max = '',
_isAmountEditable = false,
_isAddressEditable = false,
_walletName = '',
_selectedCurrency = CryptoCurrency.btc,
_isAmountEstimated = false,
_isMoneroWallet = false;
final addressController = TextEditingController();
final amountController = TextEditingController();
@ -160,6 +164,12 @@ class ExchangeCardState extends State<ExchangeCard> {
@override
Widget build(BuildContext context) {
if (widget.isAllAmountEnabled) {
WidgetsBinding.instance.addPostFrameCallback((_) {
amountController.text = S.of(context).all;
});
}
final copyImage = Image.asset('assets/images/copy_content.png',
height: 16,
width: 16,
@ -168,8 +178,7 @@ class ExchangeCardState extends State<ExchangeCard> {
return Container(
width: double.infinity,
color: Colors.transparent,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <
Widget>[
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
@ -202,40 +211,38 @@ class ExchangeCardState extends State<ExchangeCard> {
),
Text(_selectedCurrency.toString(),
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white))
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white))
]),
),
),
_selectedCurrency.tag != null ? Padding(
padding: const EdgeInsets.only(right:3.0),
child: Container(
height: 32,
decoration: BoxDecoration(
color: widget.addressButtonsColor ??
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderRadius:
BorderRadius.all(Radius.circular(6))),
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(_selectedCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor)),
if (_selectedCurrency.tag != null)
Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Container(
height: 32,
decoration: BoxDecoration(
color: widget.addressButtonsColor ??
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(_selectedCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor)),
),
),
),
),
) : Container(),
Padding(
padding: const EdgeInsets.only(right: 4.0),
child: Text(':',
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white)),
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)),
),
Expanded(
child: Row(
@ -249,26 +256,27 @@ class ExchangeCardState extends State<ExchangeCard> {
controller: amountController,
enabled: _isAmountEditable,
textAlign: TextAlign.left,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(
RegExp('[\\-|\\ ]'))
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
],
hintText: '0.0000',
borderColor: Colors.transparent,
//widget.borderColor,
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white),
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor),
validator: _isAmountEditable
? widget.currencyValueValidator
: null),
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.hintTextColor),
validator: widget.hasAllAmount
? widget.allAmountValidator
: _isAmountEditable
? widget.currencyValueValidator
: null),
),
),
if (widget.hasAllAmount)
@ -276,9 +284,10 @@ class ExchangeCardState extends State<ExchangeCard> {
height: 32,
width: 32,
decoration: BoxDecoration(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderRadius:
BorderRadius.all(Radius.circular(6))),
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonColor,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: InkWell(
onTap: () => widget.allAmount?.call(),
child: Center(
@ -287,7 +296,9 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor)),
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor)),
),
),
)
@ -296,39 +307,30 @@ class ExchangeCardState extends State<ExchangeCard> {
),
],
)),
Divider(
height: 1,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
Divider(height: 1, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
Padding(
padding: EdgeInsets.only(top: 5),
child: Container(
height: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_min != null
? Text(
S
.of(context)
.min_value(_min ?? '', _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor),
)
: Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(),
_max != null
? Text(
S
.of(context)
.max_value(_max ?? '', _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor))
: Offstage(),
])),
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
_min != null
? Text(
S.of(context).min_value(_min ?? '', _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor),
)
: Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(),
_max != null
? Text(S.of(context).max_value(_max ?? '', _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor))
: Offstage(),
])),
),
!_isAddressEditable && widget.hasRefundAddress
? Padding(
@ -343,7 +345,7 @@ class ExchangeCardState extends State<ExchangeCard> {
: Offstage(),
_isAddressEditable
? FocusTraversalOrder(
order: NumericFocusOrder(2),
order: NumericFocusOrder(2),
child: Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
@ -352,27 +354,23 @@ class ExchangeCardState extends State<ExchangeCard> {
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
addressController.text = paymentRequest.address;
if (amountController.text.isNotEmpty) {
_showAmountPopup(context, paymentRequest);
return;
}
widget.amountFocusNode?.requestFocus();
amountController.text = paymentRequest.amount;
amountController.text = paymentRequest.amount;
},
placeholder: widget.hasRefundAddress
? S.of(context).refund_address
: null,
placeholder: widget.hasRefundAddress ? S.of(context).refund_address : null,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook,
],
isBorderExist: false,
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white),
textStyle:
TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
@ -381,27 +379,22 @@ class ExchangeCardState extends State<ExchangeCard> {
validator: widget.addressTextFieldValidator,
onPushPasteButton: widget.onPushPasteButton,
onPushAddressBookButton: widget.onPushAddressBookButton,
selectedCurrency: _selectedCurrency
),
selectedCurrency: _selectedCurrency),
),
)
)
: Padding(
padding: EdgeInsets.only(top: 10),
child: Builder(
builder: (context) => Stack(children: <Widget>[
FocusTraversalOrder(
order: NumericFocusOrder(3),
child: BaseTextFormField(
controller: addressController,
borderColor: Colors.transparent,
suffixIcon:
SizedBox(width: _isMoneroWallet ? 80 : 36),
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white),
validator: widget.addressTextFieldValidator),
FocusTraversalOrder(
order: NumericFocusOrder(3),
child: BaseTextFormField(
controller: addressController,
borderColor: Colors.transparent,
suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36),
textStyle: TextStyle(
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: widget.addressTextFieldValidator),
),
Positioned(
top: 2,
@ -421,33 +414,28 @@ class ExchangeCardState extends State<ExchangeCard> {
child: InkWell(
onTap: () async {
final contact =
await Navigator.of(context)
.pushNamed(
await Navigator.of(context).pushNamed(
Routes.pickerAddressBook,
arguments: widget.initialCurrency,
);
if (contact is ContactBase &&
contact.address != null) {
if (contact is ContactBase) {
setState(() =>
addressController.text =
contact.address);
widget.onPushAddressBookButton
?.call(context);
addressController.text = contact.address);
widget.onPushAddressBookButton?.call(context);
}
},
child: Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: widget
.addressButtonsColor,
color: widget.addressButtonsColor,
borderRadius:
BorderRadius.all(
Radius.circular(
6))),
BorderRadius.all(Radius.circular(6))),
child: Image.asset(
'assets/images/open_book.png',
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor,
)),
),
)),
@ -462,18 +450,13 @@ class ExchangeCardState extends State<ExchangeCard> {
label: S.of(context).copy_address,
child: InkWell(
onTap: () {
Clipboard.setData(ClipboardData(
text: addressController
.text));
Clipboard.setData(
ClipboardData(text: addressController.text));
showBar<void>(
context,
S
.of(context)
.copied_to_clipboard);
context, S.of(context).copied_to_clipboard);
},
child: Container(
padding: EdgeInsets.fromLTRB(
8, 8, 0, 8),
padding: EdgeInsets.fromLTRB(8, 8, 0, 8),
color: Colors.transparent,
child: copyImage),
),
@ -514,7 +497,6 @@ class ExchangeCardState extends State<ExchangeCard> {
Navigator.of(context).pop();
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
}
);
});
}
}

View file

@ -1,7 +1,5 @@
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -11,7 +9,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/src/screens/send/widgets/send_template_card.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
@ -97,8 +94,13 @@ class SendTemplatePage extends BasePage {
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).extension<SendPageTheme>()!.indicatorDotColor,
activeDotColor: Theme.of(context).extension<DashboardPageTheme>()!.indicatorDotTheme.activeIndicatorColor))
dotColor: Theme.of(context)
.extension<SendPageTheme>()!
.indicatorDotColor,
activeDotColor: Theme.of(context)
.extension<DashboardPageTheme>()!
.indicatorDotTheme
.activeIndicatorColor))
: Offstage();
},
),

View file

@ -80,15 +80,17 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
if (initialPaymentRequest != null &&
sendViewModel.walletCurrencyName != initialPaymentRequest!.scheme.toLowerCase()) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).unmatched_currencies,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
if (context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).unmatched_currencies,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
});
}
}
@ -321,7 +323,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator,
),
if (!sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL)
if (!sendViewModel.isBatchSending &&
sendViewModel.shouldDisplaySendALL)
Positioned(
top: 2,
right: 0,

View file

@ -59,6 +59,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
this.sharedPreferences,
this.contactListViewModel,
) : _cryptoNumberFormat = NumberFormat(),
isSendAllEnabled = false,
isFixedRateMode = false,
isReceiveAmountEntered = false,
depositAmount = '',
@ -145,8 +146,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
ChangeNowExchangeProvider(settingsStore: _settingsStore),
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
TrocadorExchangeProvider(useTorOnly: _useTorOnly,
providerStates: _settingsStore.trocadorProviderStates),
TrocadorExchangeProvider(
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
];
@ -208,6 +209,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@observable
bool isFixedRateMode;
@observable
bool isSendAllEnabled;
@observable
Limits limits;
@ -533,10 +537,14 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
@action
void calculateDepositAllAmount() {
if (wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash) {
void enableSendAllAmount() {
isSendAllEnabled = true;
calculateDepositAllAmount();
}
@action
Future<void> calculateDepositAllAmount() async {
if (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);
@ -545,6 +553,13 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
final amount = availableBalance - fee;
changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
} else if (wallet.type == WalletType.bitcoin) {
final priority = _settingsStore.priority[wallet.type]!;
final amount = await bitcoin!.estimateFakeSendAllTxAmount(
wallet, bitcoin!.deserializeBitcoinTransactionPriority(priority.raw));
changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
}
}

View file

@ -125,4 +125,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb
COCOAPODS: 1.12.1
COCOAPODS: 1.15.2

View file

@ -110,7 +110,7 @@ dependencies:
bitcoin_base:
git:
url: https://github.com/cake-tech/bitcoin_base.git
ref: cake-update-v1
ref: cake-update-v2
dev_dependencies:
flutter_test:

View file

@ -22,8 +22,8 @@ MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.15.0"
CAKEWALLET_BUILD_NUMBER=198
CAKEWALLET_VERSION="4.15.1"
CAKEWALLET_BUILD_NUMBER=199
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"

View file

@ -18,8 +18,8 @@ MONERO_COM_BUILD_NUMBER=77
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.15.0"
CAKEWALLET_BUILD_NUMBER=217
CAKEWALLET_VERSION="4.15.1"
CAKEWALLET_BUILD_NUMBER=218
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven"

View file

@ -21,8 +21,8 @@ MONERO_COM_BUILD_NUMBER=10
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="1.8.0"
CAKEWALLET_BUILD_NUMBER=57
CAKEWALLET_VERSION="1.8.1"
CAKEWALLET_BUILD_NUMBER=58
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then

View file

@ -70,7 +70,6 @@ import 'package:cw_core/output_info.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:bitcoin_base/bitcoin_base.dart';""";
const bitcoinCWHeaders = """
@ -127,6 +126,7 @@ abstract class Bitcoin {
List<String> getAddresses(Object wallet);
String getAddress(Object wallet);
Future<int> estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority);
List<ElectrumSubAddress> getSubAddresses(Object wallet);
String formatterBitcoinAmountToString({required int amount});