mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-25 05:58:42 +01:00
Cw 598 fixes for electrum based wallets (#1344)
* fix: address book addresses, bch builder, exchange all fee estimation, bch coin control * feat: new error framework for Electrum messages * build: cw_bitcoin.dart * feat: error improvements, localization, fix exchange amount mismatch * chore: misc comment & print [skip ci] * feat: refactor & simplify sendAll vs regular tx estimation and creation - Since there were so many conditions inside a single function to alter its behavior if sendAll or not, it is easier and more readable to have separate sendAll and estimateTx functions that behave separately * fix: wrong LTC dust * feat: fee rate confirmation * fix: wrong createTrade value when isSendAll is enabled * fix bitcoin cash address parsing [skip ci] * fix: form no amount validator, address book with multiple entries, exchange all below min error * fix: improve string, fix sending with dust inputs at the top * fix: two change outputs when re-estimating * fix: sendAll with a little dust adds fees * chore: sanity check [skip ci] * fix: if the fee is higher than estimated * Minor enhancement [skip ci] --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
+24
-17
@@ -86,7 +86,7 @@ class CWBitcoin extends Bitcoin {
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: feeRate);
|
||||
@@ -123,23 +123,30 @@ class CWBitcoin extends Bitcoin {
|
||||
|
||||
@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();
|
||||
try {
|
||||
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);
|
||||
final sk = ECPrivate.random();
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
|
||||
if (wallet.type == WalletType.bitcoinCash) {
|
||||
final p2pkhAddr = sk.getPublic().toP2pkhAddress();
|
||||
final estimatedTx = await electrumWallet.estimateSendAllTx(
|
||||
[BitcoinOutput(address: p2pkhAddr, value: BigInt.zero)],
|
||||
getFeeRate(wallet, priority as BitcoinCashTransactionPriority),
|
||||
);
|
||||
|
||||
return estimatedTx.amount;
|
||||
}
|
||||
|
||||
final p2shAddr = sk.getPublic().toP2pkhInP2sh();
|
||||
final estimatedTx = await electrumWallet.estimateSendAllTx(
|
||||
[BitcoinOutput(address: p2shAddr, value: BigInt.zero)],
|
||||
getFeeRate(
|
||||
wallet,
|
||||
wallet.type == WalletType.litecoin
|
||||
? priority as LitecoinTransactionPriority
|
||||
: priority as BitcoinTransactionPriority,
|
||||
),
|
||||
);
|
||||
|
||||
return estimatedTx.amount;
|
||||
} catch (_) {
|
||||
|
||||
@@ -34,6 +34,10 @@ class AmountValidator extends TextValidator {
|
||||
late final DecimalAmountValidator decimalAmountValidator;
|
||||
|
||||
String? call(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return S.current.error_text_amount;
|
||||
}
|
||||
|
||||
//* Validate for Text(length, symbols, decimals etc)
|
||||
|
||||
final textValidation = symbolsAmountValidator(value) ?? decimalAmountValidator(value);
|
||||
|
||||
@@ -133,7 +133,11 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
Future<Trade> createTrade({
|
||||
required TradeRequest request,
|
||||
required bool isFixedRateMode,
|
||||
required bool isSendAll,
|
||||
}) async {
|
||||
final distributionPath = await DistributionInfo.instance.getDistributionPath();
|
||||
final formattedAppVersion = int.tryParse(_settingsStore.appVersion.replaceAll('.', '')) ?? 0;
|
||||
final payload = {
|
||||
@@ -202,7 +206,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||
createdAt: DateTime.now(),
|
||||
amount: responseJSON['fromAmount']?.toString() ?? request.fromAmount,
|
||||
state: TradeState.created,
|
||||
payoutAddress: payoutAddress);
|
||||
payoutAddress: payoutAddress,
|
||||
isSendAll: isSendAll);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -28,7 +28,8 @@ abstract class ExchangeProvider {
|
||||
Future<Limits> fetchLimits(
|
||||
{required CryptoCurrency from, required CryptoCurrency to, required bool isFixedRateMode});
|
||||
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode});
|
||||
Future<Trade> createTrade(
|
||||
{required TradeRequest request, required bool isFixedRateMode, required bool isSendAll});
|
||||
|
||||
Future<Trade> findTradeById({required String id});
|
||||
|
||||
|
||||
@@ -130,7 +130,11 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
Future<Trade> createTrade({
|
||||
required TradeRequest request,
|
||||
required bool isFixedRateMode,
|
||||
required bool isSendAll,
|
||||
}) async {
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
final body = {
|
||||
'coinFrom': _normalizeCurrency(request.fromCurrency),
|
||||
@@ -180,7 +184,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
createdAt: DateTime.now(),
|
||||
amount: amount,
|
||||
state: TradeState.created,
|
||||
payoutAddress: payoutAddress);
|
||||
payoutAddress: payoutAddress,
|
||||
isSendAll: isSendAll);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -144,7 +144,11 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
Future<Trade> createTrade({
|
||||
required TradeRequest request,
|
||||
required bool isFixedRateMode,
|
||||
required bool isSendAll,
|
||||
}) async {
|
||||
String url = '';
|
||||
final body = {
|
||||
'affiliateId': affiliateId,
|
||||
@@ -197,6 +201,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
||||
amount: depositAmount ?? request.fromAmount,
|
||||
payoutAddress: settleAddress,
|
||||
createdAt: DateTime.now(),
|
||||
isSendAll: isSendAll,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,11 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
Future<Trade> createTrade({
|
||||
required TradeRequest request,
|
||||
required bool isFixedRateMode,
|
||||
required bool isSendAll,
|
||||
}) async {
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
final params = {'api_key': apiKey};
|
||||
final body = <String, dynamic>{
|
||||
@@ -162,6 +166,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||
amount: request.fromAmount,
|
||||
payoutAddress: payoutAddress,
|
||||
createdAt: DateTime.now(),
|
||||
isSendAll: isSendAll,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@ class ThorChainExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
Future<Trade> createTrade({
|
||||
required TradeRequest request,
|
||||
required bool isFixedRateMode,
|
||||
required bool isSendAll,
|
||||
}) async {
|
||||
String formattedToAddress = request.toAddress.startsWith('bitcoincash:')
|
||||
? request.toAddress.replaceFirst('bitcoincash:', '')
|
||||
: request.toAddress;
|
||||
@@ -142,7 +146,8 @@ class ThorChainExchangeProvider extends ExchangeProvider {
|
||||
amount: request.fromAmount,
|
||||
state: TradeState.notFound,
|
||||
payoutAddress: request.toAddress,
|
||||
memo: memo);
|
||||
memo: memo,
|
||||
isSendAll: isSendAll);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -177,10 +182,12 @@ class ThorChainExchangeProvider extends ExchangeProvider {
|
||||
final parts = memo?.split(':') ?? [];
|
||||
|
||||
final String toChain = parts.length > 1 ? parts[1].split('.')[0] : '';
|
||||
final String toAsset = parts.length > 1 && parts[1].split('.').length > 1 ? parts[1].split('.')[1].split('-')[0] : '';
|
||||
final String toAsset = parts.length > 1 && parts[1].split('.').length > 1
|
||||
? parts[1].split('.')[1].split('-')[0]
|
||||
: '';
|
||||
|
||||
final formattedToChain = CryptoCurrency.fromString(toChain);
|
||||
final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency:formattedToChain);
|
||||
final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency: formattedToChain);
|
||||
|
||||
final plannedOutTxs = responseJSON['planned_out_txs'] as List<dynamic>?;
|
||||
final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false;
|
||||
|
||||
@@ -13,7 +13,8 @@ import 'package:http/http.dart';
|
||||
|
||||
class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}})
|
||||
: _lastUsedRateId = '', _provider = [],
|
||||
: _lastUsedRateId = '',
|
||||
_provider = [],
|
||||
super(pairList: supportedPairs(_notSupported));
|
||||
|
||||
bool useTorOnly;
|
||||
@@ -23,7 +24,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
'Swapter',
|
||||
'StealthEx',
|
||||
'Simpleswap',
|
||||
'Swapuz'
|
||||
'Swapuz',
|
||||
'ChangeNow',
|
||||
'Changehero',
|
||||
'FixedFloat',
|
||||
@@ -144,8 +145,11 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
|
||||
Future<Trade> createTrade({
|
||||
required TradeRequest request,
|
||||
required bool isFixedRateMode,
|
||||
required bool isSendAll,
|
||||
}) async {
|
||||
final params = {
|
||||
'api_key': apiKey,
|
||||
'ticker_from': _normalizeCurrency(request.fromCurrency),
|
||||
@@ -172,7 +176,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
params['id'] = _lastUsedRateId;
|
||||
}
|
||||
|
||||
|
||||
String firstAvailableProvider = '';
|
||||
|
||||
for (var provider in _provider) {
|
||||
@@ -225,7 +228,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
||||
providerName: providerName,
|
||||
createdAt: DateTime.tryParse(date)?.toLocal(),
|
||||
amount: responseJSON['amount_from']?.toString() ?? request.fromAmount,
|
||||
payoutAddress: payoutAddress);
|
||||
payoutAddress: payoutAddress,
|
||||
isSendAll: isSendAll);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -31,6 +31,7 @@ class Trade extends HiveObject {
|
||||
this.memo,
|
||||
this.txId,
|
||||
this.isRefund,
|
||||
this.isSendAll,
|
||||
}) {
|
||||
if (provider != null) providerRaw = provider.raw;
|
||||
|
||||
@@ -117,6 +118,9 @@ class Trade extends HiveObject {
|
||||
@HiveField(20)
|
||||
bool? isRefund;
|
||||
|
||||
@HiveField(21)
|
||||
bool? isSendAll;
|
||||
|
||||
static Trade fromMap(Map<String, Object?> map) {
|
||||
return Trade(
|
||||
id: map['id'] as String,
|
||||
@@ -130,8 +134,8 @@ class Trade extends HiveObject {
|
||||
fromWalletAddress: map['from_wallet_address'] as String?,
|
||||
memo: map['memo'] as String?,
|
||||
txId: map['tx_id'] as String?,
|
||||
isRefund: map['isRefund'] as bool?
|
||||
);
|
||||
isRefund: map['isRefund'] as bool?,
|
||||
isSendAll: map['isSendAll'] as bool?);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
@@ -146,7 +150,8 @@ class Trade extends HiveObject {
|
||||
'from_wallet_address': fromWalletAddress,
|
||||
'memo': memo,
|
||||
'tx_id': txId,
|
||||
'isRefund': isRefund
|
||||
'isRefund': isRefund,
|
||||
'isSendAll': isSendAll,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,13 @@ class ExchangePage extends BasePage {
|
||||
StandardCheckbox(
|
||||
value: exchangeViewModel.isFixedRateMode,
|
||||
caption: S.of(context).fixed_rate,
|
||||
onChanged: (value) => exchangeViewModel.isFixedRateMode = value,
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
exchangeViewModel.enableFixedRateMode();
|
||||
} else {
|
||||
exchangeViewModel.isFixedRateMode = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
@@ -528,7 +534,7 @@ class ExchangePage extends BasePage {
|
||||
|
||||
_receiveAmountFocus.addListener(() {
|
||||
if (_receiveAmountFocus.hasFocus) {
|
||||
exchangeViewModel.isFixedRateMode = true;
|
||||
exchangeViewModel.enableFixedRateMode();
|
||||
}
|
||||
// exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
|
||||
});
|
||||
|
||||
@@ -262,6 +262,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: widget.exchangeTradeViewModel.sendViewModel
|
||||
.pendingTransaction!.feeFormatted,
|
||||
feeRate: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeRate,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
|
||||
@@ -426,6 +426,7 @@ class SendPage extends BasePage {
|
||||
fee: isEVMCompatibleChain(sendViewModel.walletType)
|
||||
? S.of(_dialogContext).send_estimated_fee
|
||||
: S.of(_dialogContext).send_fee,
|
||||
feeRate: sendViewModel.pendingTransaction!.feeRate,
|
||||
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: sendViewModel.outputs,
|
||||
|
||||
@@ -16,6 +16,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
required this.fee,
|
||||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs,
|
||||
@@ -36,6 +37,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final String fee;
|
||||
final String? feeRate;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
@@ -90,6 +92,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
|
||||
amountValue: amountValue,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
fee: fee,
|
||||
feeRate: feeRate,
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs);
|
||||
@@ -103,6 +106,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
required this.fee,
|
||||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs});
|
||||
@@ -113,6 +117,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final String fee;
|
||||
final String? feeRate;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
@@ -125,6 +130,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
|
||||
amountValue: amountValue,
|
||||
fiatAmountValue: fiatAmountValue,
|
||||
fee: fee,
|
||||
feeRate: feeRate,
|
||||
feeValue: feeValue,
|
||||
feeFiatAmount: feeFiatAmount,
|
||||
outputs: outputs);
|
||||
@@ -138,6 +144,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
required this.amountValue,
|
||||
required this.fiatAmountValue,
|
||||
required this.fee,
|
||||
this.feeRate,
|
||||
required this.feeValue,
|
||||
required this.feeFiatAmount,
|
||||
required this.outputs})
|
||||
@@ -153,6 +160,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
final String amountValue;
|
||||
final String fiatAmountValue;
|
||||
final String fee;
|
||||
final String? feeRate;
|
||||
final String feeValue;
|
||||
final String feeFiatAmount;
|
||||
final List<Output> outputs;
|
||||
@@ -183,7 +191,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
|
||||
return Stack(alignment: Alignment.center, clipBehavior: Clip.none, children: [
|
||||
Container(
|
||||
height: 200,
|
||||
height: feeRate != null ? 250 : 200,
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Column(
|
||||
@@ -311,6 +319,36 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
|
||||
)
|
||||
],
|
||||
)),
|
||||
if (feeRate != null && feeRate!.isNotEmpty)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.current.send_estimated_fee,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"$feeRate sat/byte",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 16),
|
||||
child: Column(
|
||||
|
||||
@@ -46,9 +46,6 @@ 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: () => Navigator.of(context).pushNamed(Routes.unspentCoinsDetails,
|
||||
@@ -56,7 +53,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
child: UnspentCoinsListItem(
|
||||
note: item.note,
|
||||
amount: item.amount,
|
||||
address: address,
|
||||
address: item.address,
|
||||
isSending: item.isSending,
|
||||
isFrozen: item.isFrozen,
|
||||
isChange: item.isChange,
|
||||
|
||||
@@ -46,6 +46,8 @@ abstract class ContactListViewModelBase with Store {
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
));
|
||||
// Only one contact address per wallet
|
||||
return;
|
||||
});
|
||||
} else if (info.address != null) {
|
||||
walletContacts.add(WalletContact(
|
||||
|
||||
@@ -105,6 +105,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
output.address = trade.inputAddress ?? '';
|
||||
output.setCryptoAmount(trade.amount);
|
||||
if (_provider is ThorChainExchangeProvider) output.memo = trade.memo;
|
||||
if (trade.isSendAll == true) output.sendAll = true;
|
||||
sendViewModel.selectedCryptoCurrency = trade.from;
|
||||
final pendingTransaction = await sendViewModel.createTransaction(provider: _provider);
|
||||
if (_provider is ThorChainExchangeProvider) {
|
||||
@@ -116,6 +117,8 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
@action
|
||||
Future<void> _updateTrade() async {
|
||||
try {
|
||||
final agreedAmount = tradesStore.trade!.amount;
|
||||
final isSendAll = tradesStore.trade!.isSendAll;
|
||||
final updatedTrade = await _provider!.findTradeById(id: trade.id);
|
||||
|
||||
if (updatedTrade.createdAt == null && trade.createdAt != null)
|
||||
@@ -124,6 +127,8 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
if (updatedTrade.amount.isEmpty) updatedTrade.amount = trade.amount;
|
||||
|
||||
trade = updatedTrade;
|
||||
trade.amount = agreedAmount;
|
||||
trade.isSendAll = isSendAll;
|
||||
|
||||
_updateItems();
|
||||
} catch (e) {
|
||||
@@ -137,9 +142,9 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
|
||||
items.clear();
|
||||
|
||||
if(trade.provider != ExchangeProviderDescription.thorChain)
|
||||
items.add(ExchangeTradeItem(
|
||||
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
|
||||
if (trade.provider != ExchangeProviderDescription.thorChain)
|
||||
items.add(ExchangeTradeItem(
|
||||
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
|
||||
|
||||
if (trade.extraId != null) {
|
||||
final title = trade.from == CryptoCurrency.xrp
|
||||
|
||||
@@ -470,6 +470,18 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
|
||||
@action
|
||||
Future<void> createTrade() async {
|
||||
if (isSendAllEnabled) {
|
||||
await calculateDepositAllAmount();
|
||||
final amount = double.tryParse(depositAmount);
|
||||
|
||||
if (limits.min != null && amount != null && amount < limits.min!) {
|
||||
tradeState = TradeIsCreatedFailure(
|
||||
title: S.current.trade_not_created,
|
||||
error: S.current.amount_is_below_minimum_limit(limits.min!.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (var provider in _sortedAvailableProviders.values) {
|
||||
if (!(await provider.checkIsAvailable())) continue;
|
||||
@@ -496,8 +508,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
else {
|
||||
try {
|
||||
tradeState = TradeIsCreating();
|
||||
final trade =
|
||||
await provider.createTrade(request: request, isFixedRateMode: isFixedRateMode);
|
||||
final trade = await provider.createTrade(
|
||||
request: request,
|
||||
isFixedRateMode: isFixedRateMode,
|
||||
isSendAll: isSendAllEnabled,
|
||||
);
|
||||
trade.walletId = wallet.id;
|
||||
trade.fromWalletAddress = wallet.walletAddresses.address;
|
||||
|
||||
@@ -551,25 +566,24 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
@action
|
||||
void enableSendAllAmount() {
|
||||
isSendAllEnabled = true;
|
||||
isFixedRateMode = false;
|
||||
calculateDepositAllAmount();
|
||||
}
|
||||
|
||||
@action
|
||||
void enableFixedRateMode() {
|
||||
isSendAllEnabled = false;
|
||||
isFixedRateMode = true;
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
if (availableBalance < fee || availableBalance == 0) return;
|
||||
|
||||
final amount = availableBalance - fee;
|
||||
changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
|
||||
} else if (wallet.type == WalletType.bitcoin) {
|
||||
if (wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.bitcoinCash) {
|
||||
final priority = _settingsStore.priority[wallet.type]!;
|
||||
|
||||
final amount = await bitcoin!.estimateFakeSendAllTxAmount(
|
||||
wallet, bitcoin!.deserializeBitcoinTransactionPriority(priority.raw));
|
||||
final amount = await bitcoin!.estimateFakeSendAllTxAmount(wallet, priority);
|
||||
|
||||
changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
|
||||
@@ -309,9 +310,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
state = ExecutedSuccessfullyState();
|
||||
return pendingTransaction;
|
||||
} catch (e) {
|
||||
print('Failed with ${e.toString()}');
|
||||
state = FailureState(e.toString());
|
||||
return null;
|
||||
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,8 +352,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
state = TransactionCommitted();
|
||||
} catch (e) {
|
||||
String translatedError = translateErrorMessage(e.toString(), wallet.type, wallet.currency);
|
||||
state = FailureState(translatedError);
|
||||
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,11 +427,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
}
|
||||
}
|
||||
|
||||
ContactRecord? newContactAddress () {
|
||||
|
||||
ContactRecord? newContactAddress() {
|
||||
final Set<String> contactAddresses =
|
||||
Set.from(contactListViewModel.contacts.map((contact) => contact.address))
|
||||
..addAll(contactListViewModel.walletContacts.map((contact) => contact.address));
|
||||
Set.from(contactListViewModel.contacts.map((contact) => contact.address))
|
||||
..addAll(contactListViewModel.walletContacts.map((contact) => contact.address));
|
||||
|
||||
for (var output in outputs) {
|
||||
String address;
|
||||
@@ -444,7 +441,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
}
|
||||
|
||||
if (address.isNotEmpty && !contactAddresses.contains(address)) {
|
||||
|
||||
return ContactRecord(
|
||||
contactListViewModel.contactSource,
|
||||
Contact(
|
||||
@@ -458,22 +454,59 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
}
|
||||
|
||||
String translateErrorMessage(
|
||||
String error,
|
||||
Object error,
|
||||
WalletType walletType,
|
||||
CryptoCurrency currency,
|
||||
) {
|
||||
String errorMessage = error.toString();
|
||||
|
||||
if (walletType == WalletType.ethereum ||
|
||||
walletType == WalletType.polygon ||
|
||||
walletType == WalletType.solana ||
|
||||
walletType == WalletType.haven) {
|
||||
if (error.contains('gas required exceeds allowance') ||
|
||||
error.contains('insufficient funds')) {
|
||||
if (errorMessage.contains('gas required exceeds allowance') ||
|
||||
errorMessage.contains('insufficient funds')) {
|
||||
return S.current.do_not_have_enough_gas_asset(currency.toString());
|
||||
}
|
||||
|
||||
return error;
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
return error;
|
||||
if (walletType == WalletType.bitcoin ||
|
||||
walletType == WalletType.litecoin ||
|
||||
walletType == WalletType.bitcoinCash) {
|
||||
if (error is TransactionWrongBalanceException) {
|
||||
return S.current.tx_wrong_balance_exception(currency.toString());
|
||||
}
|
||||
if (error is TransactionNoInputsException) {
|
||||
return S.current.tx_not_enough_inputs_exception;
|
||||
}
|
||||
if (error is TransactionNoFeeException) {
|
||||
return S.current.tx_zero_fee_exception;
|
||||
}
|
||||
if (error is TransactionNoDustException) {
|
||||
return S.current.tx_no_dust_exception;
|
||||
}
|
||||
if (error is TransactionCommitFailed) {
|
||||
return S.current.tx_commit_failed;
|
||||
}
|
||||
if (error is TransactionCommitFailedDustChange) {
|
||||
return S.current.tx_rejected_dust_change;
|
||||
}
|
||||
if (error is TransactionCommitFailedDustOutput) {
|
||||
return S.current.tx_rejected_dust_output;
|
||||
}
|
||||
if (error is TransactionCommitFailedDustOutputSendAll) {
|
||||
return S.current.tx_rejected_dust_output_send_all;
|
||||
}
|
||||
if (error is TransactionCommitFailedVoutNegative) {
|
||||
return S.current.tx_rejected_vout_negative;
|
||||
}
|
||||
if (error is TransactionNoDustOnChangeException) {
|
||||
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
|
||||
}
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,5 @@ abstract class UnspentCoinsDetailsViewModelBase with Store {
|
||||
final WalletType _type;
|
||||
List<TransactionDetailsListItem> items;
|
||||
|
||||
String get formattedAddress => WalletType.bitcoinCash == _type
|
||||
? bitcoinCash!.getCashAddrFormat(unspentCoinsItem.address)
|
||||
: unspentCoinsItem.address;
|
||||
String get formattedAddress => unspentCoinsItem.address;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user