Merge remote-tracking branch 'origin/electrum-sp-refactors' into electrum-sp-refactors

This commit is contained in:
Rafael Saes 2024-11-07 13:01:57 -03:00
commit a169db7e51
9 changed files with 109 additions and 134 deletions

View file

@ -439,8 +439,8 @@ abstract class ElectrumWalletBase
TxCreateUtxoDetails _createUTXOS({
required bool sendAll,
required int credentialsAmount,
required bool paysToSilentPayment,
int credentialsAmount = 0,
int? inputsCount,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) {
@ -574,13 +574,11 @@ abstract class ElectrumWalletBase
List<BitcoinOutput> outputs,
int feeRate, {
String? memo,
int credentialsAmount = 0,
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
final utxoDetails = _createUTXOS(
sendAll: true,
credentialsAmount: credentialsAmount,
paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
@ -603,23 +601,11 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee);
}
if (amount <= 0) {
throw BitcoinTransactionWrongBalanceException();
}
// Attempting to send less than the dust limit
if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException();
}
if (credentialsAmount > 0) {
final amountLeftForFee = amount - credentialsAmount;
if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) {
amount -= amountLeftForFee;
fee += amountLeftForFee;
}
}
if (outputs.length == 1) {
outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount));
}
@ -649,6 +635,11 @@ abstract class ElectrumWalletBase
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
// Attempting to send less than the dust limit
if (_isBelowDust(credentialsAmount)) {
throw BitcoinTransactionNoDustException();
}
final utxoDetails = _createUTXOS(
sendAll: false,
credentialsAmount: credentialsAmount,
@ -726,7 +717,43 @@ abstract class ElectrumWalletBase
final lastOutput = updatedOutputs.last;
final amountLeftForChange = amountLeftForChangeAndFee - fee;
if (!_isBelowDust(amountLeftForChange)) {
if (_isBelowDust(amountLeftForChange)) {
// If has change that is lower than dust, will end up with tx rejected by network rules
// so remove the change amount
updatedOutputs.removeLast();
outputs.removeLast();
if (amountLeftForChange < 0) {
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
} else {
throw BitcoinTransactionWrongBalanceException();
}
}
return EstimatedTxResult(
utxos: utxoDetails.utxos,
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
publicKeys: utxoDetails.publicKeys,
fee: fee,
amount: amount,
hasChange: false,
isSendAll: spendingAllCoins,
memo: memo,
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
spendsSilentPayment: utxoDetails.spendsSilentPayment,
);
} else {
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
address: lastOutput.address,
@ -740,75 +767,6 @@ abstract class ElectrumWalletBase
isSilentPayment: lastOutput.isSilentPayment,
isChange: true,
);
} else {
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
updatedOutputs.removeLast();
outputs.removeLast();
// Still has inputs to spend before failing
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
hasSilentPayment: hasSilentPayment,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
final estimatedSendAll = await estimateSendAllTx(
updatedOutputs,
feeRate,
memo: memo,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
if (estimatedSendAll.amount == credentialsAmount) {
return estimatedSendAll;
}
// Estimate to user how much is needed to send to cover the fee
final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1;
throw BitcoinTransactionNoDustOnChangeException(
BitcoinAmountUtils.bitcoinAmountToString(amount: maxAmountWithReturningChange),
BitcoinAmountUtils.bitcoinAmountToString(amount: estimatedSendAll.amount),
);
}
// Attempting to send less than the dust limit
if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException();
}
final totalAmount = amount + fee;
if (totalAmount > (balance[currency]!.confirmed + balance[currency]!.secondConfirmed)) {
throw BitcoinTransactionWrongBalanceException();
}
if (totalAmount > utxoDetails.allInputsAmount) {
if (spendingAllCoins) {
throw BitcoinTransactionWrongBalanceException();
} else {
updatedOutputs.removeLast();
outputs.removeLast();
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
}
return EstimatedTxResult(
utxos: utxoDetails.utxos,
@ -817,12 +775,14 @@ abstract class ElectrumWalletBase
fee: fee,
amount: amount,
hasChange: true,
isSendAll: false,
isSendAll: spendingAllCoins,
memo: memo,
spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
spendsSilentPayment: utxoDetails.spendsSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
}
Future<int> calcFee({
required List<UtxoWithAddress> utxos,
@ -895,15 +855,20 @@ abstract class ElectrumWalletBase
: feeRate(transactionCredentials.priority!);
EstimatedTxResult estimatedTx;
final updatedOutputs =
outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
final updatedOutputs = outputs
.map((e) => BitcoinOutput(
address: e.address,
value: e.value,
isSilentPayment: e.isSilentPayment,
isChange: e.isChange,
))
.toList();
if (sendAll) {
estimatedTx = await estimateSendAllTx(
updatedOutputs,
feeRateInt,
memo: memo,
credentialsAmount: credentialsAmount,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);

View file

@ -109,12 +109,7 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.1.1):
- Flutter
- Protobuf (3.27.2)
- ReachabilitySwift (5.2.3)
- reactive_ble_mobile (0.0.1):
- Flutter
- Protobuf (~> 3.5)
- SwiftProtobuf (~> 1.0)
- SDWebImage (5.19.4):
- SDWebImage/Core (= 5.19.4)
- SDWebImage/Core (5.19.4)
@ -132,6 +127,9 @@ PODS:
- Toast (4.1.1)
- uni_links (0.0.1):
- Flutter
- universal_ble (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
- wakelock_plus (0.0.1):
@ -161,12 +159,12 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- reactive_ble_mobile (from `.symlinks/plugins/reactive_ble_mobile/ios`)
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sp_scanner (from `.symlinks/plugins/sp_scanner/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- workmanager (from `.symlinks/plugins/workmanager/ios`)
@ -178,7 +176,6 @@ SPEC REPOS:
- DKPhotoGallery
- MTBBarcodeScanner
- OrderedSet
- Protobuf
- ReachabilitySwift
- SDWebImage
- SwiftProtobuf
@ -226,8 +223,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
reactive_ble_mobile:
:path: ".symlinks/plugins/reactive_ble_mobile/ios"
sensitive_clipboard:
:path: ".symlinks/plugins/sensitive_clipboard/ios"
share_plus:
@ -238,6 +233,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sp_scanner/ios"
uni_links:
:path: ".symlinks/plugins/uni_links/ios"
universal_ble:
:path: ".symlinks/plugins/universal_ble/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock_plus:
@ -271,9 +268,7 @@ SPEC CHECKSUMS:
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
@ -283,6 +278,7 @@ SPEC CHECKSUMS:
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6

View file

@ -106,8 +106,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.wow:
pattern = '[0-9a-zA-Z]+';
case CryptoCurrency.bch:
pattern =
'(?!bitcoincash:)[0-9a-zA-Z]*|(?!bitcoincash:)q|p[0-9a-zA-Z]{41}|(?!bitcoincash:)q|p[0-9a-zA-Z]{42}|bitcoincash:q|p[0-9a-zA-Z]{41}|bitcoincash:q|p[0-9a-zA-Z]{42}';
pattern = '^(bitcoincash:)?(q|p)[0-9a-zA-Z]{41,42}';
case CryptoCurrency.bnb:
pattern = '[0-9a-zA-Z]+';
case CryptoCurrency.hbar:

View file

@ -141,8 +141,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
'coinTo': _normalizeCurrency(request.toCurrency),
'networkFrom': _networkFor(request.fromCurrency),
'networkTo': _networkFor(request.toCurrency),
'withdrawalAddress': request.toAddress,
'refundAddress': request.refundAddress,
'withdrawalAddress': _normalizeAddress(request.toAddress),
'refundAddress': _normalizeAddress(request.refundAddress),
'rateType': _getRateType(isFixedRateMode),
'apiToken': apiKey,
};
@ -275,4 +275,7 @@ class ExolixExchangeProvider extends ExchangeProvider {
return tag;
}
}
String _normalizeAddress(String address) =>
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}

View file

@ -129,8 +129,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
"currency_to": _normalizeCurrency(request.toCurrency),
"amount": request.fromAmount,
"fixed": isFixedRateMode,
"user_refund_address": request.refundAddress,
"address_to": request.toAddress
"user_refund_address": _normalizeAddress(request.refundAddress),
"address_to": _normalizeAddress(request.toAddress)
};
final uri = Uri.https(apiAuthority, createExchangePath, params);
@ -243,4 +243,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
return currency.title.toLowerCase();
}
}
String _normalizeAddress(String address) =>
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}

View file

@ -129,8 +129,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
if (isFixedRateMode) 'rate_id': rateId,
'amount':
isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount),
'address': request.toAddress,
'refund_address': request.refundAddress,
'address': _normalizeAddress(request.toAddress),
'refund_address': _normalizeAddress(request.refundAddress),
'additional_fee_percent': _additionalFeePercent,
};
@ -296,4 +296,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
return currency.tag!.toLowerCase();
}
String _normalizeAddress(String address) =>
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}

View file

@ -116,9 +116,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
required bool isFixedRateMode,
required bool isSendAll,
}) async {
String formattedToAddress = request.toAddress.startsWith('bitcoincash:')
? request.toAddress.replaceFirst('bitcoincash:', '')
: request.toAddress;
final formattedFromAmount = double.parse(request.fromAmount);
@ -126,11 +124,11 @@ class ThorChainExchangeProvider extends ExchangeProvider {
'from_asset': _normalizeCurrency(request.fromCurrency),
'to_asset': _normalizeCurrency(request.toCurrency),
'amount': _doubleToThorChainString(formattedFromAmount),
'destination': formattedToAddress,
'destination': _normalizeAddress(request.toAddress),
'affiliate': _affiliateName,
'affiliate_bps': _affiliateBps,
'refund_address':
isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '',
isRefundAddressSupported.contains(request.fromCurrency) ? _normalizeAddress(request.refundAddress) : '',
};
final responseJSON = await _getSwapQuote(params);
@ -288,4 +286,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
return currentState;
}
String _normalizeAddress(String address) =>
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
}

View file

@ -99,18 +99,23 @@ abstract class ContactListViewModelBase with Store {
Future<void> delete(ContactRecord contact) async => contact.original.delete();
@computed
List<ContactRecord> get contactsToShow =>
contacts.where((element) => _isValidForCurrency(element)).toList();
ObservableList<ContactRecord> get contactsToShow =>
ObservableList.of(contacts.where((element) => _isValidForCurrency(element)));
@computed
List<WalletContact> get walletContactsToShow =>
walletContacts.where((element) => _isValidForCurrency(element)).toList();
bool _isValidForCurrency(ContactBase element) {
if (element.name.contains('Silent Payments')) return false;
if (element.name.contains('MWEB')) return false;
return _currency == null ||
element.type == _currency ||
element.type.title == _currency!.tag ||
element.type.tag == _currency!.tag;
(element.type.tag != null &&
_currency?.tag != null &&
element.type.tag == _currency?.tag) ||
_currency?.toString() == element.type.tag ||
_currency?.tag == element.type.toString();
}
}

View file

@ -482,18 +482,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
nano!.updateTransactions(wallet);
}
if (pendingTransaction!.id.isNotEmpty) {
final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
_settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription(
id: descriptionKey,
recipientAddress: address,
transactionNote: note))
transactionNote: note,
))
: await transactionDescriptionBox.add(TransactionDescription(
id: descriptionKey,
transactionNote: note));
transactionNote: note,
));
}
state = TransactionCommitted();