diff --git a/BTCPayServer/Controllers/UIWalletsController.PSBT.cs b/BTCPayServer/Controllers/UIWalletsController.PSBT.cs index e54a1f0e8..882d080f5 100644 --- a/BTCPayServer/Controllers/UIWalletsController.PSBT.cs +++ b/BTCPayServer/Controllers/UIWalletsController.PSBT.cs @@ -459,14 +459,17 @@ namespace BTCPayServer.Controllers } } if (ix is null) continue; + + var labels = _labelService.CreateTransactionTagModels(ix, Request); var input = vm.Inputs.First(model => model.Index == inputToObject.Key); - input.Labels = ix.LabelColors; + input.Labels = labels; } foreach (var outputToObject in outputToObjects) { if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix)) continue; + var labels = _labelService.CreateTransactionTagModels(ix, Request); var destination = vm.Destinations.First(model => model.Destination == outputToObject.Key); - destination.Labels = ix.LabelColors; + destination.Labels = labels; } } diff --git a/BTCPayServer/Controllers/UIWalletsController.cs b/BTCPayServer/Controllers/UIWalletsController.cs index 9ab56b12c..090d17a1c 100644 --- a/BTCPayServer/Controllers/UIWalletsController.cs +++ b/BTCPayServer/Controllers/UIWalletsController.cs @@ -453,7 +453,7 @@ namespace BTCPayServer.Controllers { if (!string.IsNullOrEmpty(link)) { - LoadFromBIP21(model, link, network); + await LoadFromBIP21(walletId, model, link, network); } } } @@ -568,7 +568,7 @@ namespace BTCPayServer.Controllers if (!string.IsNullOrEmpty(bip21)) { vm.Outputs?.Clear(); - LoadFromBIP21(vm, bip21, network); + await LoadFromBIP21(walletId, vm, bip21, network); } decimal transactionAmountSum = 0; @@ -586,7 +586,7 @@ namespace BTCPayServer.Controllers .GetUnspentCoins(schemeSettings.AccountDerivation, false, cancellation); var walletTransactionsInfoAsync = await this.WalletRepository.GetWalletTransactionsInfo(walletId, - utxos.SelectMany(u => GetWalletObjectsQuery.Get(u)).Distinct().ToArray()); + utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray()); vm.InputsAvailable = utxos.Select(coin => { walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info1); @@ -863,8 +863,10 @@ namespace BTCPayServer.Controllers } - private void LoadFromBIP21(WalletSendModel vm, string bip21, BTCPayNetwork network) + private async Task LoadFromBIP21(WalletId walletId, WalletSendModel vm, string bip21, + BTCPayNetwork network) { + BitcoinAddress? address = null; vm.Outputs ??= new(); try { @@ -879,6 +881,7 @@ namespace BTCPayServer.Controllers ? uriBuilder.UnknownParameters["payout"] : null }); + address = uriBuilder.Address; if (!string.IsNullOrEmpty(uriBuilder.Label) || !string.IsNullOrEmpty(uriBuilder.Message)) { TempData.SetStatusMessageModel(new StatusMessageModel() @@ -896,9 +899,10 @@ namespace BTCPayServer.Controllers { try { + address = BitcoinAddress.Create(bip21, network.NBitcoinNetwork); vm.Outputs.Add(new WalletSendModel.TransactionOutput() { - DestinationAddress = BitcoinAddress.Create(bip21, network.NBitcoinNetwork).ToString() + DestinationAddress = address.ToString() } ); } @@ -913,6 +917,11 @@ namespace BTCPayServer.Controllers } ModelState.Clear(); + if (address is not null) + { + var addressLabels = await WalletRepository.GetWalletLabels(new WalletObjectId(walletId, WalletObjectData.Types.Address, address.ToString())); + vm.Outputs.Last().Labels = addressLabels.Select(tuple => tuple.Label).ToArray(); + } } private IActionResult ViewVault(WalletId walletId, WalletPSBTViewModel vm) diff --git a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs index 959dac081..d5e224aa5 100644 --- a/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs +++ b/BTCPayServer/Data/Payouts/BitcoinLike/BitcoinLikePayoutHandler.cs @@ -64,12 +64,19 @@ public class BitcoinLikePayoutHandler : IPayoutHandler _btcPayNetworkProvider.GetNetwork(paymentMethod.CryptoCode)?.ReadonlyWallet is false; } - public async Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination) + public async Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData) { - var network = _btcPayNetworkProvider.GetNetwork(paymentMethodId.CryptoCode); + var network = _btcPayNetworkProvider.GetNetwork(claimRequest.PaymentMethodId.CryptoCode); var explorerClient = _explorerClientProvider.GetExplorerClient(network); - if (claimDestination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination) + if (claimRequest.Destination is IBitcoinLikeClaimDestination bitcoinLikeClaimDestination) + { + await explorerClient.TrackAsync(TrackedSource.Create(bitcoinLikeClaimDestination.Address)); + await WalletRepository.AddWalletTransactionAttachment( + new WalletId(claimRequest.StoreId, claimRequest.PaymentMethodId.CryptoCode), + bitcoinLikeClaimDestination.Address.ToString(), + Attachment.Payout(payoutData.PullPaymentDataId, payoutData.Id), WalletObjectData.Types.Address); + } } public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken) diff --git a/BTCPayServer/Data/Payouts/IPayoutHandler.cs b/BTCPayServer/Data/Payouts/IPayoutHandler.cs index ebee5903d..33f5c09f5 100644 --- a/BTCPayServer/Data/Payouts/IPayoutHandler.cs +++ b/BTCPayServer/Data/Payouts/IPayoutHandler.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client.Models; using BTCPayServer.Data; +using BTCPayServer.HostedServices; using BTCPayServer.Payments; using Microsoft.AspNetCore.Mvc; using PayoutData = BTCPayServer.Data.PayoutData; @@ -14,7 +15,7 @@ using StoreData = BTCPayServer.Data.StoreData; public interface IPayoutHandler { public bool CanHandle(PaymentMethodId paymentMethod); - public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination); + public Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData); //Allows payout handler to parse payout destinations on its own public Task<(IClaimDestination destination, string error)> ParseClaimDestination(PaymentMethodId paymentMethodId, string destination, CancellationToken cancellationToken); public (bool valid, string? error) ValidateClaimDestination(IClaimDestination claimDestination, PullPaymentBlob? pullPaymentBlob); diff --git a/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs index 789c63390..7a5198b81 100644 --- a/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs +++ b/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client.Models; +using BTCPayServer.HostedServices; using BTCPayServer.Lightning; using BTCPayServer.Payments; using BTCPayServer.Payments.Lightning; @@ -45,7 +46,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike _btcPayNetworkProvider.GetNetwork(paymentMethod.CryptoCode)?.SupportLightning is true; } - public Task TrackClaim(PaymentMethodId paymentMethodId, IClaimDestination claimDestination) + public Task TrackClaim(ClaimRequest claimRequest, PayoutData payoutData) { return Task.CompletedTask; } diff --git a/BTCPayServer/HostedServices/PullPaymentHostedService.cs b/BTCPayServer/HostedServices/PullPaymentHostedService.cs index 54bc8ae3c..06bdb33fd 100644 --- a/BTCPayServer/HostedServices/PullPaymentHostedService.cs +++ b/BTCPayServer/HostedServices/PullPaymentHostedService.cs @@ -592,7 +592,7 @@ namespace BTCPayServer.HostedServices await ctx.Payouts.AddAsync(payout); try { - await payoutHandler.TrackClaim(req.ClaimRequest.PaymentMethodId, req.ClaimRequest.Destination); + await payoutHandler.TrackClaim(req.ClaimRequest, payout); await ctx.SaveChangesAsync(); if (req.ClaimRequest.PreApprove.GetValueOrDefault(ppBlob?.AutoApproveClaims is true)) { diff --git a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs index 97afc27f1..b7429be50 100644 --- a/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs +++ b/BTCPayServer/HostedServices/TransactionLabelMarkerHostedService.cs @@ -80,12 +80,18 @@ namespace BTCPayServer.HostedServices //if the object is an address, we also link the labels to the tx if(walletObjectData.Value.Type == WalletObjectData.Types.Address) { - var labels = walletObjectData.Value.GetNeighbours() + var neighbours = walletObjectData.Value.GetNeighbours().ToArray(); + var labels = neighbours .Where(data => data.Type == WalletObjectData.Types.Label).Select(data => new WalletObjectId(walletObjectDatas.Key, data.Type, data.Id)); foreach (var label in labels) { await _walletRepository.EnsureWalletObjectLink(label, txWalletObject); + var attachments = neighbours.Where(data => data.Type == label.Id); + foreach (var attachment in attachments) + { + await _walletRepository.EnsureWalletObjectLink(new WalletObjectId(walletObjectDatas.Key, attachment.Type, attachment.Id), txWalletObject); + } } } } @@ -103,14 +109,8 @@ namespace BTCPayServer.HostedServices { Attachment.Invoice(invoiceEvent.Invoice.Id) }; - foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice)) - { - labels.Add(Attachment.PaymentRequest(paymentId)); - } - foreach (var appId in AppService.GetAppInternalTags(invoiceEvent.Invoice)) - { - labels.Add(Attachment.App(appId)); - } + labels.AddRange(PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice).Select(Attachment.PaymentRequest)); + labels.AddRange(AppService.GetAppInternalTags(invoiceEvent.Invoice).Select(Attachment.App)); await _walletRepository.AddWalletTransactionAttachment(walletId, transactionId, labels); break; diff --git a/BTCPayServer/Models/WalletViewModels/WalletPSBTReadyViewModel.cs b/BTCPayServer/Models/WalletViewModels/WalletPSBTReadyViewModel.cs index 4807e07b2..9014d4f5f 100644 --- a/BTCPayServer/Models/WalletViewModels/WalletPSBTReadyViewModel.cs +++ b/BTCPayServer/Models/WalletViewModels/WalletPSBTReadyViewModel.cs @@ -16,7 +16,7 @@ namespace BTCPayServer.Models.WalletViewModels public bool Positive { get; set; } public string Destination { get; set; } public string Balance { get; set; } - public Dictionary Labels { get; set; } = new(); + public IEnumerable Labels { get; set; } = new List(); } public class InputViewModel @@ -25,7 +25,7 @@ namespace BTCPayServer.Models.WalletViewModels public string Error { get; set; } public bool Positive { get; set; } public string BalanceChange { get; set; } - public Dictionary Labels { get; set; } = new(); + public IEnumerable Labels { get; set; } = new List(); } public bool HasErrors => Inputs.Count == 0 || Inputs.Any(i => !string.IsNullOrEmpty(i.Error)); public string BalanceChange { get; set; } diff --git a/BTCPayServer/Services/WalletRepository.cs b/BTCPayServer/Services/WalletRepository.cs index 17994b6fc..c9ba11468 100644 --- a/BTCPayServer/Services/WalletRepository.cs +++ b/BTCPayServer/Services/WalletRepository.cs @@ -318,10 +318,10 @@ namespace BTCPayServer.Services public async Task<(string Label, string Color)[]> GetWalletLabels(WalletObjectId objectId) { - return await GetWalletLabels(w => - w.WalletId == objectId.WalletId.ToString() && - w.Type == objectId.Type && - w.Id == objectId.Id); + + await using var ctx = _ContextFactory.CreateContext(); + var obj = await GetWalletObject(objectId, true); + return obj is null ? Array.Empty<(string Label, string Color)>() : obj.GetNeighbours().Where(data => data.Type == WalletObjectData.Types.Label).Select(FormatToLabel).ToArray(); } private async Task<(string Label, string Color)[]> GetWalletLabels(Expression> predicate) diff --git a/BTCPayServer/Views/UIWallets/_PSBTInfo.cshtml b/BTCPayServer/Views/UIWallets/_PSBTInfo.cshtml index 4caade81e..d061e1d11 100644 --- a/BTCPayServer/Views/UIWallets/_PSBTInfo.cshtml +++ b/BTCPayServer/Views/UIWallets/_PSBTInfo.cshtml @@ -35,10 +35,25 @@ { @input.Index } - @foreach (var label in input.Labels) - { -
@label.Key
- } + @if (input.Labels.Any()) + { +
+ @foreach (var label in input.Labels) + { +
+ @label.Text + @if (!string.IsNullOrEmpty(label.Link)) + { + + + + } +
+ } +
+ } @input.BalanceChange @@ -64,11 +79,25 @@ @destination.Destination - - @foreach (var label in destination.Labels) - { -
@label.Key
- } + @if (destination.Labels.Any()) + { +
+ @foreach (var label in destination.Labels) + { +
+ @label.Text + @if (!string.IsNullOrEmpty(label.Link)) + { + + + + } +
+ } +
+ } @destination.Balance