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:
Rafael
2024-03-29 20:51:34 +02:00
committed by GitHub
co-authored by OmarHatem
parent 78685b74f0
commit fd9018bcc4
60 changed files with 984 additions and 431 deletions
+24 -17
View File
@@ -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 (_) {
+4
View File
@@ -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
+2 -1
View File
@@ -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
+8 -3
View File
@@ -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,
};
}
+8 -2
View File
@@ -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 {
+1
View File
@@ -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));
}
+48 -15
View File
@@ -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;
}