Show payment request title in wallet transaction tooltip (#6982)
What changed, and why it matters
This commit is a small UI improvement: when you hover over a wallet transaction that came from a payment request, the tooltip now shows the payment request's human-readable title instead of its internal ID number. It also fetches all related payment requests in one database query rather than one by one. There is no security fix or vulnerability here.
No security action required; review as normal feature/UX code.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change adds a PaymentRequestRepository dependency to TransactionLabelMarkerHostedService so that, when attaching payment-request labels to wallet transactions, it can batch-lookup titles and store them in the attachment’s JObject data. LabelService then reads tag.Data[‘title’] to render a friendlier tooltip. The commit is purely cosmetic/performance-related.
Changed components
BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.csBTCPayServer/Services/Attachment.csBTCPayServer/Services/Labels/LabelService.csInspect captured patch +29 / −6
diff --git a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
index c727e19..4b4b561 100644
--- a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
+++ b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs
@@ -24,12 +24,14 @@ namespace BTCPayServer.HostedServices
{
private readonly PaymentMethodHandlerDictionary _handlers;
private readonly WalletRepository _walletRepository;
+ private readonly PaymentRequestRepository _paymentRequestRepository;
- public TransactionLabelMarkerHostedService(PaymentMethodHandlerDictionary handlers, EventAggregator eventAggregator, WalletRepository walletRepository, Logs logs) :
+ public TransactionLabelMarkerHostedService(PaymentMethodHandlerDictionary handlers, EventAggregator eventAggregator, WalletRepository walletRepository, PaymentRequestRepository paymentRequestRepository, Logs logs) :
base(eventAggregator, logs)
{
_handlers = handlers;
_walletRepository = walletRepository;
+ _paymentRequestRepository = paymentRequestRepository;
}
protected override void SubscribeToEvents()
@@ -141,8 +143,8 @@ namespace BTCPayServer.HostedServices
// if we the tx is matching some known address and utxo, we link them to this tx
{
if (walletObjectData.Value.Type is WalletObjectData.Types.Utxo or WalletObjectData.Types.Address)
- links.Add(
- WalletRepository.NewWalletObjectLinkData(txWalletObject, walletObjectData.Key));
+ links.Add(
+ WalletRepository.NewWalletObjectLinkData(txWalletObject, walletObjectData.Key));
}
// if the object is an address, we also link its labels (the ones added in the wallet receive page)
{
@@ -154,7 +156,7 @@ namespace BTCPayServer.HostedServices
new WalletObjectId(wid, data.Type, data.Id));
foreach (var label in labels)
{
- links.Add(WalletRepository.NewWalletObjectLinkData(label, txWalletObject));
+ links.Add(WalletRepository.NewWalletObjectLinkData(label, txWalletObject));
var attachments = neighbours.Where(data => data.Type == label.Id);
foreach (var attachment in attachments)
{
@@ -179,7 +181,23 @@ namespace BTCPayServer.HostedServices
{
Attachment.Invoice(invoiceEvent.Invoice.Id)
};
- labels.AddRange(PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice).Select(Attachment.PaymentRequest));
+
+ var paymentRequestIds = PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice).ToArray();
+ if (paymentRequestIds.Length > 0)
+ {
+ var paymentRequests = await _paymentRequestRepository.FindPaymentRequests(
+ new PaymentRequestQuery { Ids = paymentRequestIds });
+ var paymentRequestsById = paymentRequests.ToDictionary(pr => pr.Id);
+
+ foreach (var prId in paymentRequestIds)
+ {
+ var data = paymentRequestsById.TryGetValue(prId, out var pr) && pr.Title is { } title
+ ? new JObject { ["title"] = title }
+ : null;
+ labels.Add(Attachment.PaymentRequest(prId, data));
+ }
+ }
+
labels.AddRange(AppService.GetAppInternalTags(invoiceEvent.Invoice).Select(Attachment.App));
await _walletRepository.AddWalletTransactionAttachment(walletId, transactionId, labels);
diff --git a/BTCPayServer/Services/Attachment.cs b/BTCPayServer/Services/Attachment.cs
index 3db9b20..6035ed1 100644
--- a/BTCPayServer/Services/Attachment.cs
+++ b/BTCPayServer/Services/Attachment.cs
@@ -32,6 +32,10 @@ namespace BTCPayServer.Services
{
return new Attachment(WalletObjectData.Types.PaymentRequest, paymentRequestId);
}
+ public static Attachment PaymentRequest(string paymentRequestId, JObject? data)
+ {
+ return new Attachment(WalletObjectData.Types.PaymentRequest, paymentRequestId, data);
+ }
public static Attachment App(string appId)
{
return new Attachment(WalletObjectData.Types.App, appId);
diff --git a/BTCPayServer/Services/Labels/LabelService.cs b/BTCPayServer/Services/Labels/LabelService.cs
index 243f31f..27503d7 100644
--- a/BTCPayServer/Services/Labels/LabelService.cs
+++ b/BTCPayServer/Services/Labels/LabelService.cs
@@ -109,7 +109,8 @@ public class LabelService
}
else if (tag.Type == WalletObjectData.Types.PaymentRequest)
{
- model.Tooltip = $"Received through a payment request {tag.Id}";
+ var title = tag.Data?["title"]?.ToString() ?? tag.Id;
+ model.Tooltip = $"Payment request: {title}";
model.Link = _linkGenerator.PaymentRequestLink(tag.Id, req.Scheme, req.Host, req.PathBase);
}
else if (tag.Type == WalletObjectData.Types.App)
Why this scored 19/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.