-
Notifications
You must be signed in to change notification settings - Fork 5
fix: prevent cleared Bolt11 invoices from appearing in transaction history #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b399c86
5646db6
ca65890
52471dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import '../services/invoice_service.dart'; | |
| import '../services/yadio_service.dart'; | ||
| import '../services/transaction_detector.dart'; | ||
| import '../services/nfc_charge_service.dart'; | ||
| import '../services/cleared_invoice_store.dart'; | ||
| import '../models/lightning_invoice.dart'; | ||
| import '../models/wallet_info.dart'; | ||
| import '../l10n/generated/app_localizations.dart'; | ||
|
|
@@ -28,6 +29,7 @@ class ReceiveScreen extends StatefulWidget { | |
| } | ||
|
|
||
| class _ReceiveScreenState extends State<ReceiveScreen> { | ||
|
|
||
| final _amountController = TextEditingController(); | ||
| final _noteController = TextEditingController(); | ||
| String _selectedCurrency = 'sats'; | ||
|
|
@@ -125,6 +127,11 @@ class _ReceiveScreenState extends State<ReceiveScreen> { | |
| _yadioService.dispose(); | ||
| _invoicePaymentTimer?.cancel(); | ||
| _invoicePaymentTimeoutTimer?.cancel(); | ||
| if (_generatedInvoice != null) { | ||
| final hash = _generatedInvoice!.paymentHash; | ||
| ClearedInvoiceStore.instance.add(hash); | ||
| unawaited(_tryCancelInvoiceOnServer(hash)); | ||
| } | ||
| super.dispose(); | ||
| } | ||
|
|
||
|
|
@@ -783,6 +790,13 @@ class _ReceiveScreenState extends State<ReceiveScreen> { | |
| void _clearInvoice() { | ||
| _invoicePaymentTimer?.cancel(); | ||
| _invoicePaymentTimeoutTimer?.cancel(); | ||
|
|
||
| if (_generatedInvoice != null) { | ||
| final hash = _generatedInvoice!.paymentHash; | ||
| ClearedInvoiceStore.instance.add(hash); | ||
| unawaited(_tryCancelInvoiceOnServer(hash)); | ||
| } | ||
|
|
||
| setState(() { | ||
| _generatedInvoice = null; | ||
| }); | ||
|
|
@@ -792,6 +806,36 @@ class _ReceiveScreenState extends State<ReceiveScreen> { | |
| ); | ||
| } | ||
|
|
||
| void _discardInvoice() { | ||
| _invoicePaymentTimer?.cancel(); | ||
| _invoicePaymentTimeoutTimer?.cancel(); | ||
| if (_generatedInvoice != null) { | ||
| final hash = _generatedInvoice!.paymentHash; | ||
| ClearedInvoiceStore.instance.add(hash); | ||
| unawaited(_tryCancelInvoiceOnServer(hash)); | ||
| } | ||
| setState(() { | ||
| _generatedInvoice = null; | ||
| }); | ||
| } | ||
|
|
||
| Future<void> _tryCancelInvoiceOnServer(String paymentHash) async { | ||
| try { | ||
| final walletProvider = context.read<WalletProvider>(); | ||
| final authProvider = context.read<AuthProvider>(); | ||
| final serverUrl = authProvider.sessionData?.serverUrl; | ||
| final wallet = walletProvider.primaryWallet; | ||
|
|
||
| if (serverUrl == null || wallet == null) return; | ||
|
|
||
| await _invoiceService.cancelInvoice( | ||
| serverUrl: serverUrl, | ||
| adminKey: wallet.inKey, | ||
| paymentHash: paymentHash, | ||
| ); | ||
| } catch (_) {} | ||
| } | ||
|
Comment on lines
+822
to
+837
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect InvoiceService.cancelInvoice to confirm which key the endpoint expects
fd -t f 'invoice_service.dart' | xargs catRepository: lachispame/lachispa Length of output: 50375 🏁 Script executed: rg -A 20 'Future.*cancelInvoice' lib/services/Repository: lachispame/lachispa Length of output: 1376 🏁 Script executed: rg -A 40 'Future<bool> cancelInvoice' lib/services/invoice_service.dartRepository: lachispame/lachispa Length of output: 1355 🏁 Script executed: rg -B 5 -A 15 '_tryCancelInvoiceOnServer' lib/screens/9receive_screen.dartRepository: lachispame/lachispa Length of output: 2176 🏁 Script executed: fd -t f 'wallet' lib/models/ | xargs grep -l 'class.*Wallet' | head -1 | xargs grep -A 30 'class.*Wallet' | grep -E '(inKey|adminKey)'Repository: lachispame/lachispa Length of output: 313 Pass
Change line 833 to: The wallet model already provides both keys separately (see line 144: 🤖 Prompt for AI Agents |
||
|
|
||
| void _showCopySheet(LNAddress? defaultAddress) { | ||
| final hasInvoice = _generatedInvoice != null; | ||
| final lnurl = defaultAddress?.lnurl; | ||
|
|
@@ -1444,9 +1488,7 @@ class _ReceiveScreenState extends State<ReceiveScreen> { | |
| _invoicePaymentTimeoutTimer = Timer(const Duration(minutes: 10), () { | ||
| _invoicePaymentTimer?.cancel(); | ||
| if (!mounted) return; | ||
| setState(() { | ||
| _generatedInvoice = null; | ||
| }); | ||
| _discardInvoice(); | ||
| _showInfoSnackBar( | ||
| AppLocalizations.of(context)!.invoice_monitoring_timeout_message, | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import 'package:shared_preferences/shared_preferences.dart'; | ||
| import 'dart:convert'; | ||
|
|
||
| class ClearedInvoiceStore { | ||
| static final ClearedInvoiceStore _instance = ClearedInvoiceStore._(); | ||
| static ClearedInvoiceStore get instance => _instance; | ||
| ClearedInvoiceStore._(); | ||
|
|
||
| static const String _storageKey = 'cleared_invoice_hashes'; | ||
| Set<String> _hashes = {}; | ||
|
|
||
| Future<void> load() async { | ||
| try { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| final stored = prefs.getString(_storageKey); | ||
| if (stored != null && stored.isNotEmpty) { | ||
| final List<dynamic> decoded = jsonDecode(stored); | ||
| _hashes = decoded.cast<String>().toSet(); | ||
| } | ||
| } catch (_) { | ||
| _hashes = {}; | ||
| } | ||
| } | ||
|
|
||
| Future<void> add(String hash) async { | ||
| _hashes.add(hash); | ||
| await _persist(); | ||
| } | ||
|
|
||
| bool contains(String? hash) => hash != null && _hashes.contains(hash); | ||
|
|
||
| Set<String> get all => Set.unmodifiable(_hashes); | ||
|
|
||
| Future<void> _persist() async { | ||
| try { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| await prefs.setString(_storageKey, jsonEncode(_hashes.toList())); | ||
| } catch (_) {} | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: lachispame/lachispa
Length of output: 1204
🏁 Script executed:
Repository: lachispame/lachispa
Length of output: 217
🏁 Script executed:
Repository: lachispame/lachispa
Length of output: 129
🏁 Script executed:
Repository: lachispame/lachispa
Length of output: 1042
Wrap
ClearedInvoiceStore.instance.add(hash)withunawaited()at all three call sites.ClearedInvoiceStore.add()returnsFuture<void>and performs async SharedPreferences I/O. The current code at lines 132, 796, and 814 drops the returnedFuturewithoutunawaited(), which violates theunawaited_futureslint and silently discards persistence errors. Wrap each call inunawaited(...)for consistency with the adjacent_tryCancelInvoiceOnServercall.♻️ Proposed change (apply at all three sites)
Also applies to: 796, 814
🤖 Prompt for AI Agents