#nullable enable using System; using System.Collections.Generic; using System.Linq; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Models.WalletViewModels; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Newtonsoft.Json.Linq; namespace BTCPayServer.Services.Labels; public class LabelService { private readonly LinkGenerator _linkGenerator; public LabelService(LinkGenerator linkGenerator) { _linkGenerator = linkGenerator; } public IEnumerable CreateTransactionTagModels(WalletTransactionInfo? transactionInfo, HttpRequest req) { if (transactionInfo is null) return Array.Empty(); string PayoutTooltip(IGrouping? payoutsByPullPaymentId = null) { if (payoutsByPullPaymentId is null) { return "Paid a payout"; } if (payoutsByPullPaymentId.Count() == 1) { var pp = payoutsByPullPaymentId.Key; var payout = payoutsByPullPaymentId.First(); return !string.IsNullOrEmpty(pp) ? $"Paid a payout ({payout}) of a pull payment ({pp})" : $"Paid a payout {payout}"; } else { var pp = payoutsByPullPaymentId.Key; return !string.IsNullOrEmpty(pp) ? $"Paid {payoutsByPullPaymentId.Count()} payouts of a pull payment ({pp})" : $"Paid {payoutsByPullPaymentId.Count()} payouts"; } } var models = new Dictionary(); foreach (var tag in transactionInfo.Attachments) { if (models.ContainsKey(tag.Type)) continue; if (!transactionInfo.LabelColors.TryGetValue(tag.Type, out var color)) continue; var model = new TransactionTagModel { Text = tag.Type, Color = color, TextColor = ColorPalette.Default.TextColor(color) }; models.Add(tag.Type, model); if (tag.Type == WalletObjectData.Types.Payout) { var payoutsByPullPaymentId = transactionInfo.Attachments.Where(t => t.Type == "payout") .GroupBy(t => t.Data?["pullPaymentId"]?.Value() ?? "", k => k.Id).ToList(); model.Tooltip = payoutsByPullPaymentId.Count switch { 0 => PayoutTooltip(), 1 => PayoutTooltip(payoutsByPullPaymentId.First()), _ => string.Join(", ", payoutsByPullPaymentId.Select(PayoutTooltip)) }; model.Link = _linkGenerator.PayoutLink(transactionInfo.WalletId.ToString(), null, PayoutState.Completed, req.Scheme, req.Host, req.PathBase); } else if (tag.Type == WalletObjectData.Types.Payjoin) { model.Tooltip = "This UTXO was part of a PayJoin transaction."; } else if (tag.Type == WalletObjectData.Types.Invoice) { model.Tooltip = $"Received through an invoice {tag.Id}"; model.Link = string.IsNullOrEmpty(tag.Id) ? null : _linkGenerator.InvoiceLink(tag.Id, req.Scheme, req.Host, req.PathBase); } else if (tag.Type == WalletObjectData.Types.PaymentRequest) { model.Tooltip = $"Received through a payment request {tag.Id}"; model.Link = _linkGenerator.PaymentRequestLink(tag.Id, req.Scheme, req.Host, req.PathBase); } else if (tag.Type == WalletObjectData.Types.App) { model.Tooltip = $"Received through an app {tag.Id}"; model.Link = _linkGenerator.AppLink(tag.Id, req.Scheme, req.Host, req.PathBase); } else if (tag.Type == WalletObjectData.Types.PayjoinExposed) { if (tag.Id.Length != 0) { model.Tooltip = $"This UTXO was exposed through a PayJoin proposal for an invoice ({tag.Id})"; model.Link = _linkGenerator.InvoiceLink(tag.Id, req.Scheme, req.Host, req.PathBase); } else { model.Tooltip = $"This UTXO was exposed through a PayJoin proposal"; } } else if (tag.Type == WalletObjectData.Types.Payjoin) { model.Tooltip = $"This UTXO was part of a PayJoin transaction."; } else { model.Tooltip = tag.Data?.TryGetValue("tooltip", StringComparison.InvariantCultureIgnoreCase, out var tooltip) is true ? tooltip.ToString() : tag.Id; if (tag.Data?.TryGetValue("link", StringComparison.InvariantCultureIgnoreCase, out var link) is true) { model.Link = link.ToString(); } } } foreach (var label in transactionInfo.LabelColors) models.TryAdd(label.Key, new TransactionTagModel { Text = label.Key, Color = label.Value, TextColor = ColorPalette.Default.TextColor(label.Value) }); return models.Values.OrderBy(v => v.Text); } }