feat: add lightning preimage to transaction details
What changed, and why it matters
This commit adds a new piece of information—the Lightning Network payment preimage—to the transaction details screen. The preimage is a proof that a Lightning payment completed, and it is already stored in the wallet's existing payment data. The change simply displays it to the user, similar to showing a receipt number. There is no indication it introduces a security vulnerability.
No security action required. This is a feature enhancement. Routine code review and testing are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extends LightningWallet._paymentToElectrumTransactionInfo to extract payment.preimage from PaymentDetails_Lightning.htlcDetails and include it in additionalInfo. It then adds a new TxDetailRowDefinition in TransactionDetailsViewModelBase that renders this preimage when present. A minor cleanup removes a debug printV call and refactors transactionAsset to return CryptoCurrency.btcln for Lightning transactions. No cryptographic operations, network calls, or permission changes are introduced.
Changed components
cw_bitcoin/lib/lightning/lightning_wallet.dartlib/view_model/transaction_details_view_model.dartres/values/strings_en.arbInspect captured patch +23 / −11
diff --git a/cw_bitcoin/lib/lightning/lightning_wallet.dart b/cw_bitcoin/lib/lightning/lightning_wallet.dart
index d93a84b5..42dda7cb 100644
--- a/cw_bitcoin/lib/lightning/lightning_wallet.dart
+++ b/cw_bitcoin/lib/lightning/lightning_wallet.dart
@@ -441,6 +441,11 @@ class LightningWallet {
direction = TransactionDirection.incoming;
}
+ String? preimage;
+ if (payment.details != null && payment.details is PaymentDetails_Lightning) {
+ preimage = (payment.details as PaymentDetails_Lightning).htlcDetails.preimage;
+ }
+
return ElectrumTransactionInfo(
WalletType.bitcoin,
id: payment.id,
@@ -450,7 +455,10 @@ class LightningWallet {
fee: Money(payment.fees, currency),
date: DateTime.fromMillisecondsSinceEpoch(payment.timestamp.toInt() * 1000),
confirmations: payment.status == PaymentStatus.pending ? 0 : 10,
- additionalInfo: {"isLightning": true},
+ additionalInfo: {
+ "isLightning": true,
+ if (preimage != null) "preimage": preimage,
+ },
);
}
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index 7199ed1a..0cc1bdb8 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -9,7 +9,6 @@ import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
-import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -50,10 +49,9 @@ String _moneroRecipientAddressForDisplay(String raw, WalletType walletType) {
return match?.group(0) ?? raw.trim();
}
-bool isLightning(TransactionInfo tx) {
- printV(tx.additionalInfo);
- return (tx.additionalInfo["isLightning"] as bool?) ?? false;
-}
+bool isLightning(TransactionInfo tx) => (tx.additionalInfo["isLightning"] as bool?) ?? false;
+
+bool hasLightningPreimage(TransactionInfo tx) => (tx.additionalInfo["preimage"] as String?) != null;
class TxDetailRowDefinition {
final String keyString;
@@ -188,6 +186,13 @@ class TxDetailRowDefinition {
},
applicable: (vm) => vm.wallet.type == WalletType.monero),
+ TxDetailRowDefinition(
+ keyString: "standard_list_item_lightning_preimage",
+ title: S.current.transaction_preimage,
+ valueGetter: (vm) => vm.transactionInfo.additionalInfo['preimage'] as String? ?? "",
+ applicable: (vm) =>
+ hasLightningPreimage(vm.transactionInfo) && isLightning(vm.transactionInfo),
+ ),
TxDetailRowDefinition(
keyString: "standard_list_item_transaction_confirmed_key",
@@ -316,10 +321,9 @@ abstract class TransactionDetailsViewModelBase with Store {
TransactionPriority? transactionPriority;
CryptoCurrency get transactionAsset {
+ if (isEVMCompatibleChain(wallet.type)) return evm!.assetOfTransaction(wallet, transactionInfo);
- if (isEVMCompatibleChain(wallet.type)) {
- return evm!.assetOfTransaction(wallet, transactionInfo);
- }
+ if (isLightning(transactionInfo)) return CryptoCurrency.btcln;
return switch (wallet.type) {
WalletType.solana => solana!.assetOfTransaction(wallet, transactionInfo),
@@ -329,7 +333,6 @@ abstract class TransactionDetailsViewModelBase with Store {
};
}
-
// TODO integrate these getters with the TransactionInfo object
String get formattedPendingStatus {
switch (wallet.type) {
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index 1debd0a8..1523c53e 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -1253,6 +1253,7 @@
"transaction_error": "Transaction Error",
"transaction_key": "Transaction Key",
"transaction_memo_optional": "Transaction Memo (optional)",
+ "transaction_preimage": "Preimage",
"transaction_priority_fast": "Fast",
"transaction_priority_fastest": "Fastest",
"transaction_priority_medium": "Medium",
@@ -1452,4 +1453,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
+}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.