mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Handle network errors, improve Lightning setup, and add deposit events (#3127)
* fix: send all for lightning * fix: adjust crypto amount parsing logic for send page outputs * feat: add event handling for deposit transactions in Bitcoin and Lightning wallets - Introduced `onCreateDepositTransactionEvent` and `onUpdateDepositTransactionEvent` callbacks to manage deposit transaction updates. - Updated event listener logic to handle unclaimed and claimed deposit events. - Added support for processing deposit transaction info with `ElectrumTransactionInfo`. * fix: handle network errors on username validation and improve Lightning address setup - Added network error handling for username validation with a fallback error. - Enhanced Lightning address setup logic with error handling and a fail-safe mechanism to disable Lightning usage if the setup fails. * fix: enhance error handling for Lightning and update validation patterns * fix: remove redundant raw string mark from amount validation pattern * fix: normalize crypto amount input by replacing commas with dots
This commit is contained in:
@@ -112,10 +112,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
);
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
lightningWallet = null;
|
||||
}
|
||||
} else {
|
||||
lightningWallet = null;
|
||||
}
|
||||
|
||||
payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this);
|
||||
@@ -153,6 +150,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
lnurlDomain: "cake.cash",
|
||||
cachedAddress: cachedLightningAddress,
|
||||
);
|
||||
walletAddresses.setLightningAddress(walletInfo.name);
|
||||
}
|
||||
} else {
|
||||
lightningWallet = null;
|
||||
@@ -367,6 +365,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
await fetchBalances();
|
||||
}
|
||||
},
|
||||
onCreateDepositTransactionEvent: (txs) async {
|
||||
if (txs.isNotEmpty) {
|
||||
transactionHistory.addMany(txs);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
},
|
||||
onUpdateDepositTransactionEvent: (txs) async {
|
||||
if (txs.isNotEmpty) {
|
||||
txs.forEach((tx) => transactionHistory.transactions.remove(tx.id));
|
||||
await transactionHistory.save();
|
||||
}
|
||||
},
|
||||
onBalanceChangedEvent: fetchBalances,
|
||||
);
|
||||
}
|
||||
@@ -390,7 +400,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
return super.fetchTransactions();
|
||||
}
|
||||
|
||||
late LightningWallet? lightningWallet;
|
||||
LightningWallet? lightningWallet;
|
||||
|
||||
late final PayjoinManager payjoinManager;
|
||||
|
||||
@@ -479,10 +489,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
final isLNCompatible = await lightningWallet?.isCompatible(lnAddr);
|
||||
if ((credentials.coinTypeToSpendFrom == UnspentCoinType.lightning && lightningWallet != null) ||
|
||||
isLNCompatible == true) {
|
||||
|
||||
BigInt amount;
|
||||
if (credentials.outputs.first.sendAll) {
|
||||
amount = (await lightningWallet!.getBalance()) - BigInt.from(10);
|
||||
amount = await lightningWallet!.getBalance();
|
||||
} else {
|
||||
amount = parseFixed(
|
||||
credentials.outputs.first.cryptoAmount?.isNotEmpty == true
|
||||
@@ -491,8 +500,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
8);
|
||||
}
|
||||
|
||||
return lightningWallet!.createTransaction(lnAddr,
|
||||
amount > BigInt.zero ? amount : null, credentials.priority);
|
||||
return lightningWallet!.createTransaction(lnAddr, amount > BigInt.zero ? amount : null,
|
||||
credentials.priority, credentials.outputs.first.sendAll);
|
||||
}
|
||||
|
||||
final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction;
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:breez_sdk_spark_flutter/breez_sdk_spark.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_bitcoin/lightning/lightning_addres_type.dart';
|
||||
@@ -807,10 +808,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
printV(username);
|
||||
rethrow;
|
||||
}
|
||||
} catch (e) {
|
||||
// make sure lightningAddress gets initialized anyway
|
||||
} on SdkError_NetworkError catch (_) {
|
||||
} on SdkError_SparkError catch (e) {
|
||||
if (!e.field0.contains("dns")) rethrow;
|
||||
} finally {
|
||||
lightningAddress ??= lightningWallet!.cachedAddress;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,17 +137,24 @@ class LightningWallet {
|
||||
.lightningAddress;
|
||||
}
|
||||
|
||||
Future<String> getBolt11Invoice(BigInt? amount, String description) async {
|
||||
final response = await sdk.receivePayment(
|
||||
request: ReceivePaymentRequest(
|
||||
paymentMethod: ReceivePaymentMethod.bolt11Invoice(
|
||||
description: description,
|
||||
amountSats: amount,
|
||||
Future<String?> getBolt11Invoice(BigInt? amount, String description) async {
|
||||
try {
|
||||
final response = await sdk.receivePayment(
|
||||
request: ReceivePaymentRequest(
|
||||
paymentMethod: ReceivePaymentMethod.bolt11Invoice(
|
||||
description: description,
|
||||
amountSats: amount,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
return response.paymentRequest;
|
||||
return response.paymentRequest;
|
||||
} on SdkError_NetworkError catch (_) {
|
||||
return null;
|
||||
} on SdkError_SparkError catch (e) {
|
||||
if (!e.field0.contains("dns")) rethrow;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> isCompatible(String input) async {
|
||||
@@ -162,12 +169,14 @@ class LightningWallet {
|
||||
}
|
||||
|
||||
Future<PendingLightningTransaction> createTransaction(
|
||||
String address, BigInt? amountSats, BitcoinTransactionPriority? priority) async {
|
||||
String address, BigInt? amountSats, BitcoinTransactionPriority? priority, bool feesIncluded) async {
|
||||
final inputType = await sdk.parse(input: address);
|
||||
|
||||
final feePolicy = feesIncluded ? FeePolicy.feesIncluded : FeePolicy.feesExcluded;
|
||||
|
||||
if (inputType is InputType_Bolt11Invoice) {
|
||||
final request = PrepareSendPaymentRequest(
|
||||
paymentRequest: inputType.field0.invoice.bolt11, amount: amountSats);
|
||||
paymentRequest: inputType.field0.invoice.bolt11, amount: amountSats, feePolicy: feePolicy);
|
||||
final prepareResponse = await sdk.prepareSendPayment(request: request);
|
||||
|
||||
final paymentMethod = prepareResponse.paymentMethod;
|
||||
@@ -177,7 +186,7 @@ class LightningWallet {
|
||||
|
||||
return PendingLightningTransaction(
|
||||
id: paymentMethod.invoiceDetails.paymentHash,
|
||||
amount: amountSats?.toInt() ??
|
||||
amount: request.amount?.toInt() ?? amountSats?.toInt() ??
|
||||
((paymentMethod.invoiceDetails.amountMsat?.toInt() ?? 0) / 1000).round(),
|
||||
fee: lightningFeeSats.toInt() + (sparkTransferFeeSats?.toInt() ?? 0),
|
||||
commitOverride: () async {
|
||||
@@ -203,12 +212,14 @@ class LightningWallet {
|
||||
amountSats: amountSats!,
|
||||
payRequest: inputType.field0.payRequest,
|
||||
validateSuccessActionUrl: optionalValidateSuccessActionUrl,
|
||||
feePolicy: feePolicy,
|
||||
);
|
||||
} else {
|
||||
request = PrepareLnurlPayRequest(
|
||||
amountSats: amountSats!,
|
||||
payRequest: (inputType as InputType_LnurlPay).field0,
|
||||
validateSuccessActionUrl: optionalValidateSuccessActionUrl,
|
||||
feePolicy: feePolicy,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -218,7 +229,7 @@ class LightningWallet {
|
||||
|
||||
return PendingLightningTransaction(
|
||||
id: prepareResponse.invoiceDetails.paymentHash,
|
||||
amount: ((prepareResponse.invoiceDetails.amountMsat?.toInt() ?? 0) / 1000).round(),
|
||||
amount: prepareResponse.amountSats.toInt(),
|
||||
fee: feeSats.toInt(),
|
||||
commitOverride: () async {
|
||||
final res =
|
||||
@@ -227,8 +238,11 @@ class LightningWallet {
|
||||
},
|
||||
);
|
||||
} else if (inputType is InputType_BitcoinAddress) {
|
||||
final request =
|
||||
PrepareSendPaymentRequest(paymentRequest: inputType.field0.address, amount: amountSats);
|
||||
final request = PrepareSendPaymentRequest(
|
||||
paymentRequest: inputType.field0.address,
|
||||
amount: amountSats,
|
||||
feePolicy: feePolicy,
|
||||
);
|
||||
final prepareResponse = await sdk.prepareSendPayment(request: request);
|
||||
|
||||
final paymentMethod = prepareResponse.paymentMethod;
|
||||
@@ -355,8 +369,12 @@ class LightningWallet {
|
||||
return response.txHex;
|
||||
}
|
||||
|
||||
void setEventListener(
|
||||
{required Function(ElectrumTransactionInfo) onTransactionEvent, required Function onBalanceChangedEvent}) {
|
||||
void setEventListener({
|
||||
required Function(ElectrumTransactionInfo) onTransactionEvent,
|
||||
required Function onBalanceChangedEvent,
|
||||
required Function(Map<String, ElectrumTransactionInfo>) onCreateDepositTransactionEvent,
|
||||
required Function(List<ElectrumTransactionInfo>) onUpdateDepositTransactionEvent,
|
||||
}) {
|
||||
_eventSubscription = _eventStream?.listen((sdkEvent) {
|
||||
if (sdkEvent is SdkEvent_PaymentSucceeded) {
|
||||
onTransactionEvent(_getElectrumTransactionInfoFromPayment(sdkEvent.payment));
|
||||
@@ -364,6 +382,16 @@ class LightningWallet {
|
||||
onTransactionEvent(_getElectrumTransactionInfoFromPayment(sdkEvent.payment));
|
||||
} else if (sdkEvent is SdkEvent_ClaimedDeposits) {
|
||||
onBalanceChangedEvent();
|
||||
onUpdateDepositTransactionEvent(
|
||||
sdkEvent.claimedDeposits.map(_getElectrumTransactionInfoFromDepositInfo).toList());
|
||||
} else if (sdkEvent is SdkEvent_UnclaimedDeposits) {
|
||||
final unclaimedDeposits = <String, ElectrumTransactionInfo>{};
|
||||
|
||||
for (final deposit in sdkEvent.unclaimedDeposits) {
|
||||
unclaimedDeposits[deposit.txid] = _getElectrumTransactionInfoFromDepositInfo(deposit);
|
||||
}
|
||||
|
||||
onCreateDepositTransactionEvent(unclaimedDeposits);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -390,4 +418,18 @@ class LightningWallet {
|
||||
additionalInfo: {"isLightning": true},
|
||||
);
|
||||
}
|
||||
|
||||
ElectrumTransactionInfo _getElectrumTransactionInfoFromDepositInfo(DepositInfo deposit) {
|
||||
return ElectrumTransactionInfo(
|
||||
WalletType.bitcoin,
|
||||
id: deposit.txid,
|
||||
amount: deposit.amountSats.toInt(),
|
||||
direction: TransactionDirection.incoming,
|
||||
isPending: true,
|
||||
fee: 0,
|
||||
date: DateTime.now(),
|
||||
confirmations: 0,
|
||||
additionalInfo: {"isLightning": true, "isSparkDeposit": true},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class SymbolsAmountValidator extends TextValidator {
|
||||
maxLength: 0,
|
||||
);
|
||||
|
||||
static String _pattern() => '^([0-9]+([.\,][0-9]+)?|[.\,][0-9]+)\$';
|
||||
static String _pattern() => r'^(?:\d+(?:[.\,]\d+)?|[.\,]\d+)$';
|
||||
}
|
||||
|
||||
class DecimalAmountValidator extends TextValidator {
|
||||
|
||||
@@ -12,7 +12,8 @@ class LNUrlPayRecord {
|
||||
final String name;
|
||||
final String address;
|
||||
|
||||
static Future<String?> checkWellKnownUsername(String username, CryptoCurrency currency) async {
|
||||
static Future<String?> checkWellKnownUsername(String username, CryptoCurrency currency,
|
||||
{bool allowRethrow = false}) async {
|
||||
if (![CryptoCurrency.btc, CryptoCurrency.btcln].contains(currency)) return null;
|
||||
|
||||
// split the string by the @ symbol:
|
||||
@@ -40,6 +41,7 @@ class LNUrlPayRecord {
|
||||
return LNURL.encode(expectedUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
if (allowRethrow) rethrow;
|
||||
printV("error checking well-known username: $e");
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -691,12 +691,15 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
void _handleSend() async {
|
||||
//TODO refactor this action. code was copied over from old ui. i don't like it.
|
||||
|
||||
for(int i=0; i<widget.sendViewModel.outputs.length; i++) {
|
||||
if(i < _amountControllers.length) {
|
||||
if(_fiatInputMode) {
|
||||
for (var i = 0; i < widget.sendViewModel.outputs.length; i++) {
|
||||
if (i < _amountControllers.length && !widget.sendViewModel.outputs[i].sendAll) {
|
||||
if (_fiatInputMode) {
|
||||
widget.sendViewModel.outputs[i].setFiatAmount(_amountControllers[i].text);
|
||||
} else {
|
||||
widget.sendViewModel.outputs[i].cryptoAmount = _amountControllers[i].text;
|
||||
final amount = widget.sendViewModel.amountParsingProxy.getCanonicalCryptoAmount(
|
||||
_amountControllers[i].text.replaceAll(",", "."),
|
||||
widget.sendViewModel.selectedCryptoCurrency);
|
||||
widget.sendViewModel.outputs[i].setCryptoAmount(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +81,15 @@ class LightningUsernameBloc extends Bloc<LightningUsernameEvent, LightningUserna
|
||||
return UsernameError(S.current.already_your_username, isInfo: true);
|
||||
}
|
||||
|
||||
if ((await LNUrlPayRecord.checkWellKnownUsername(
|
||||
"${username}${usernameSuffix}", CryptoCurrency.btc)) !=
|
||||
null) {
|
||||
return UsernameError(S.current.username_not_available);
|
||||
try {
|
||||
if ((await LNUrlPayRecord.checkWellKnownUsername(
|
||||
"${username}${usernameSuffix}", CryptoCurrency.btc,
|
||||
allowRethrow: true)) !=
|
||||
null) {
|
||||
return UsernameError(S.current.username_not_available);
|
||||
}
|
||||
} catch (e) {
|
||||
return UsernameError(S.current.username_check_your_network);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class NewSendAmountInput extends StatefulWidget {
|
||||
const NewSendAmountInput(
|
||||
@@ -66,7 +65,7 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$'))
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d*[.,]?\d*$'))
|
||||
],
|
||||
onChanged: state.didChange,
|
||||
controller: widget.amountController,
|
||||
|
||||
@@ -301,7 +301,8 @@ class ExceptionHandler {
|
||||
"_QueuedFuture.execute (package:universal_ble/src/queue.dart:65)",
|
||||
"reown_core/relay_client/websocket/websocket_handler.dart",
|
||||
"Image upload failed due to loss of GPU access",
|
||||
'transport error',
|
||||
"transport error",
|
||||
"SdkError.sparkError(field0: Operator RPC error: Connection error: status: Unavailable, message: \"dns error\", details: []"
|
||||
];
|
||||
|
||||
static Future<void> _addDeviceInfo(File file) async {
|
||||
|
||||
@@ -1273,6 +1273,7 @@
|
||||
"username_length_error": "Benutzernamen müssen 6–32 alphanumerische Zeichen enthalten.",
|
||||
"username_length_error_1_64": "Benutzernamen müssen 1–64 alphanumerische Zeichen enthalten.",
|
||||
"username_not_available": "Dieser Benutzername ist nicht verfügbar.",
|
||||
"username_check_your_network": "Benutzername konnte nicht geprüft werden. Bitte überprüfen Sie Ihre Netzwerkverbindung.",
|
||||
"value": "Wert",
|
||||
"value_type": "Werttyp",
|
||||
"variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
|
||||
@@ -1373,4 +1374,4 @@
|
||||
"zcash_card_missing_funds": "Fehlende Gelder?",
|
||||
"zcash_card_scan": "Scannen",
|
||||
"zcash_card_warning": "Schließen Sie die App nicht, bis der Vorgang abgeschlossen ist. Andernfalls muss der Vorgang von Grund auf neu gestartet werden."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,6 +1272,7 @@
|
||||
"username_length_error": "Usernames must contain 6-32 alphanumeric characters.",
|
||||
"username_length_error_1_64": "Usernames must contain 1-64 alphanumeric characters.",
|
||||
"username_not_available": "This username is not available.",
|
||||
"username_check_your_network": "Unable to check username. Please check your network connection.",
|
||||
"value": "Value",
|
||||
"value_type": "Value Type",
|
||||
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
|
||||
@@ -1371,4 +1372,4 @@
|
||||
"zcash_card_missing_funds": "Missing funds?",
|
||||
"zcash_card_scan": "Scan",
|
||||
"zcash_card_warning": "Do not close the app until the procedure completes, if you do so this process will need to restart from scratch."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user