diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 246188448..a75de4d4d 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -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; diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 15e0e02d7..0c3343856 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -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; } } } diff --git a/cw_bitcoin/lib/lightning/lightning_wallet.dart b/cw_bitcoin/lib/lightning/lightning_wallet.dart index 8f713b21a..12a28776b 100644 --- a/cw_bitcoin/lib/lightning/lightning_wallet.dart +++ b/cw_bitcoin/lib/lightning/lightning_wallet.dart @@ -137,17 +137,24 @@ class LightningWallet { .lightningAddress; } - Future getBolt11Invoice(BigInt? amount, String description) async { - final response = await sdk.receivePayment( - request: ReceivePaymentRequest( - paymentMethod: ReceivePaymentMethod.bolt11Invoice( - description: description, - amountSats: amount, + Future 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 isCompatible(String input) async { @@ -162,12 +169,14 @@ class LightningWallet { } Future 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) onCreateDepositTransactionEvent, + required Function(List) 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 = {}; + + 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}, + ); + } } diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index d056ad0c5..dfa999932 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -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 { diff --git a/lib/entities/lnurlpay_record.dart b/lib/entities/lnurlpay_record.dart index c6e9788a5..35fec0d51 100644 --- a/lib/entities/lnurlpay_record.dart +++ b/lib/entities/lnurlpay_record.dart @@ -12,7 +12,8 @@ class LNUrlPayRecord { final String name; final String address; - static Future checkWellKnownUsername(String username, CryptoCurrency currency) async { + static Future 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; diff --git a/lib/new-ui/pages/send_page.dart b/lib/new-ui/pages/send_page.dart index 90ec32796..b27bf8293 100644 --- a/lib/new-ui/pages/send_page.dart +++ b/lib/new-ui/pages/send_page.dart @@ -691,12 +691,15 @@ class _NewSendPageState extends State { void _handleSend() async { //TODO refactor this action. code was copied over from old ui. i don't like it. - for(int i=0; i { keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')) + FilteringTextInputFormatter.allow(RegExp(r'^\d*[.,]?\d*$')) ], onChanged: state.didChange, controller: widget.amountController, diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 9711fdf17..9bee1704e 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -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 _addDeviceInfo(File file) async { diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 4737c1d5b..f01448c0e 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -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." -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index f3f732c23..832ea23ac 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -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." -} \ No newline at end of file +}