mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Auto label utxos based on invoice and payjoin (#1499)
* Auto label utxos based on invoice and payjoin This PR introduces automatic labelling to transactions. * If it is an incoming tx to an invoice, it will tag it as such. * If it was a payjoin tx , it will tag it as such. * If a transaction's inputs were exposed to a payjoin sender, we tag it as such. * wip * wip * support in coinselection * remove ugly hack * support wallet transactions page * remove messy loop * better label template helpers * add tests and susbcribe to event * simplify data * fix test * fix label + color * fix remove label * renove useless call * add toString * fix potential crash by txid * trim json keyword in manual label * format file
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@@ -24,12 +22,43 @@ namespace BTCPayServer.Data
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
if (color == null)
|
||||
throw new ArgumentNullException(nameof(color));
|
||||
if (value.StartsWith("{"))
|
||||
{
|
||||
var jObj = JObject.Parse(value);
|
||||
if (jObj.ContainsKey("value"))
|
||||
{
|
||||
switch (jObj["value"].Value<string>())
|
||||
{
|
||||
case "invoice":
|
||||
Value = "invoice";
|
||||
Tooltip = $"Received through an invoice ({jObj["id"].Value<string>()})";
|
||||
Link = jObj.ContainsKey("id") ? $"/invoices/{jObj["id"].Value<string>()}" : "";
|
||||
break;
|
||||
case "pj-exposed":
|
||||
Value = "payjoin-exposed";
|
||||
Tooltip = $"This utxo was exposed through a payjoin proposal for an invoice ({jObj["id"].Value<string>()})";
|
||||
Link = jObj.ContainsKey("id") ? $"/invoices/{jObj["id"].Value<string>()}" : "";
|
||||
break;
|
||||
default:
|
||||
Value = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
RawValue = value;
|
||||
|
||||
Color = color;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
public string RawValue { get; }
|
||||
public string Color { get; }
|
||||
public string Link { get; }
|
||||
public string Tooltip { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -258,7 +259,7 @@ namespace BTCPayServer.Tests
|
||||
.GetAttribute("href");
|
||||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
|
||||
|
||||
s.GoToWalletSend(senderWalletId);
|
||||
s.GoToWallet(senderWalletId, WalletsNavPages.Send);
|
||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
@@ -293,7 +294,7 @@ namespace BTCPayServer.Tests
|
||||
.GetAttribute("href");
|
||||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
|
||||
|
||||
s.GoToWalletSend(senderWalletId);
|
||||
s.GoToWallet(senderWalletId, WalletsNavPages.Send);
|
||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
@@ -304,7 +305,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
|
||||
var txId = await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
|
||||
{
|
||||
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
|
||||
return Task.CompletedTask;
|
||||
@@ -344,6 +345,15 @@ namespace BTCPayServer.Tests
|
||||
.FindElement(By.ClassName("payment-value"));
|
||||
Assert.False(paymentValueRowColumn.Text.Contains("payjoin",
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.GoToWallet(receiverWalletId, WalletsNavPages.Transactions);
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
Assert.Contains("payjoin", s.Driver.PageSource);
|
||||
//this label does not always show since input gets used
|
||||
// Assert.Contains("payjoin-exposed", s.Driver.PageSource);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using BTCPayServer.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
@@ -318,7 +319,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public async Task FundStoreWallet(WalletId walletId, int coins = 1, decimal denomination = 1m)
|
||||
{
|
||||
GoToWalletReceive(walletId);
|
||||
GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = Driver.FindElement(By.Id("vue-address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
|
||||
@@ -335,7 +336,7 @@ namespace BTCPayServer.Tests
|
||||
.GetAttribute("href");
|
||||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
|
||||
|
||||
GoToWalletSend(walletId);
|
||||
GoToWallet(walletId, WalletsNavPages.Send);
|
||||
Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
Driver.SwitchTo().Alert().Accept();
|
||||
@@ -371,14 +372,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
|
||||
public void GoToWalletSend(WalletId walletId)
|
||||
public void GoToWallet(WalletId walletId, WalletsNavPages navPages = WalletsNavPages.Send)
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}/send"));
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}"));
|
||||
if (navPages != WalletsNavPages.Transactions)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Wallet{navPages}")).Click();
|
||||
}
|
||||
|
||||
internal void GoToWalletReceive(WalletId walletId)
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, $"wallets/{walletId}/receive"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using NBitcoin.Payment;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@@ -432,7 +433,7 @@ namespace BTCPayServer.Tests
|
||||
var storeId = s.CreateNewStore().storeId;
|
||||
s.GenerateWallet("BTC", "", false, true);
|
||||
var walletId = new WalletId(storeId, "BTC");
|
||||
s.GoToWalletReceive(walletId);
|
||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
s.Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = s.Driver.FindElement(By.Id("vue-address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
||||
@@ -456,7 +457,7 @@ namespace BTCPayServer.Tests
|
||||
coin => coin.OutPoint == spentOutpoint);
|
||||
});
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
s.GoToWalletSend(walletId);
|
||||
s.GoToWallet(walletId, WalletsNavPages.Send);
|
||||
s.Driver.FindElement(By.Id("advancedSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
|
||||
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace BTCPayServer.Controllers
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
if (addlabel != null)
|
||||
{
|
||||
addlabel = addlabel.Trim().ToLowerInvariant().Replace(',',' ').Truncate(MaxLabelSize);
|
||||
addlabel = addlabel.Trim().TrimStart('{').ToLowerInvariant().Replace(',',' ').Truncate(MaxLabelSize);
|
||||
var labels = walletBlobInfo.GetLabels();
|
||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
@@ -170,7 +170,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else if (removelabel != null)
|
||||
{
|
||||
removelabel = removelabel.Trim().ToLowerInvariant().Truncate(MaxLabelSize);
|
||||
removelabel = removelabel.Trim();
|
||||
if (walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
if (walletTransactionInfo.Labels.Remove(removelabel))
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@@ -17,7 +19,15 @@ namespace BTCPayServer.Data
|
||||
var blobInfo = JsonConvert.DeserializeObject<WalletTransactionInfo>(ZipUtils.Unzip(walletTransactionData.Blob));
|
||||
if (!string.IsNullOrEmpty(walletTransactionData.Labels))
|
||||
{
|
||||
blobInfo.Labels.AddRange(walletTransactionData.Labels.Split(',', StringSplitOptions.RemoveEmptyEntries));
|
||||
if (walletTransactionData.Labels.StartsWith('['))
|
||||
{
|
||||
blobInfo.Labels.AddRange(JArray.Parse(walletTransactionData.Labels).Values<string>());
|
||||
}
|
||||
else
|
||||
{
|
||||
blobInfo.Labels.AddRange(walletTransactionData.Labels.Split(',',
|
||||
StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
}
|
||||
return blobInfo;
|
||||
}
|
||||
@@ -29,9 +39,8 @@ namespace BTCPayServer.Data
|
||||
walletTransactionData.Blob = Array.Empty<byte>();
|
||||
return;
|
||||
}
|
||||
if (blobInfo.Labels.Any(l => l.Contains(',', StringComparison.OrdinalIgnoreCase)))
|
||||
throw new ArgumentException(paramName: nameof(blobInfo), message: "Labels must not contains ','");
|
||||
walletTransactionData.Labels = String.Join(',', blobInfo.Labels);
|
||||
|
||||
walletTransactionData.Labels = JArray.FromObject(blobInfo.Labels).ToString();
|
||||
walletTransactionData.Blob = ZipUtils.Zip(JsonConvert.SerializeObject(blobInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace BTCPayServer.HostedServices
|
||||
{
|
||||
private readonly AppService _AppService;
|
||||
|
||||
protected override void SubscibeToEvents()
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<InvoiceEvent>();
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
|
||||
|
||||
protected virtual void SubscibeToEvents()
|
||||
protected virtual void SubscribeToEvents()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace BTCPayServer.HostedServices
|
||||
public virtual Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Subscriptions = new List<IEventAggregatorSubscription>();
|
||||
SubscibeToEvents();
|
||||
SubscribeToEvents();
|
||||
_Cts = new CancellationTokenSource();
|
||||
_ProcessingEvents = ProcessEvents(_Cts.Token);
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class TransactionLabelMarkerHostedService : EventHostedServiceBase
|
||||
{
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
|
||||
public TransactionLabelMarkerHostedService(EventAggregator eventAggregator, WalletRepository walletRepository) :
|
||||
base(eventAggregator)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
_walletRepository = walletRepository;
|
||||
}
|
||||
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<InvoiceEvent>();
|
||||
Subscribe<UpdateTransactionLabel>();
|
||||
}
|
||||
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
|
||||
{
|
||||
if (evt is InvoiceEvent invoiceEvent && invoiceEvent.Name == InvoiceEvent.ReceivedPayment &&
|
||||
invoiceEvent.Payment.GetPaymentMethodId().PaymentType == BitcoinPaymentType.Instance &&
|
||||
invoiceEvent.Payment.GetCryptoPaymentData() is BitcoinLikePaymentData bitcoinLikePaymentData)
|
||||
{
|
||||
var walletId = new WalletId(invoiceEvent.Invoice.StoreId, invoiceEvent.Payment.GetCryptoCode());
|
||||
var transactionId = bitcoinLikePaymentData.Outpoint.Hash;
|
||||
var labels = new List<(string color, string label)>
|
||||
{
|
||||
UpdateTransactionLabel.InvoiceLabelTemplate(invoiceEvent.Invoice.Id)
|
||||
};
|
||||
|
||||
if (invoiceEvent.Invoice.GetPayments(invoiceEvent.Payment.GetCryptoCode()).Any(entity =>
|
||||
entity.GetCryptoPaymentData() is BitcoinLikePaymentData pData &&
|
||||
pData.PayjoinInformation?.CoinjoinTransactionHash == transactionId))
|
||||
{
|
||||
labels.Add(UpdateTransactionLabel.PayjoinLabelTemplate());
|
||||
}
|
||||
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel()
|
||||
{
|
||||
WalletId = walletId,
|
||||
TransactionLabels =
|
||||
new Dictionary<uint256, List<(string color, string label)>>() {{transactionId, labels}}
|
||||
});
|
||||
}
|
||||
else if (evt is UpdateTransactionLabel updateTransactionLabel)
|
||||
{
|
||||
var walletTransactionsInfo =
|
||||
await _walletRepository.GetWalletTransactionsInfo(updateTransactionLabel.WalletId);
|
||||
var walletBlobInfo = await _walletRepository.GetWalletInfo(updateTransactionLabel.WalletId);
|
||||
await Task.WhenAll(updateTransactionLabel.TransactionLabels.Select(async pair =>
|
||||
{
|
||||
if (!walletTransactionsInfo.TryGetValue(pair.Key.ToString(), out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
}
|
||||
|
||||
foreach (var label in pair.Value)
|
||||
{
|
||||
walletBlobInfo.LabelColors.TryAdd(label.label, label.color);
|
||||
}
|
||||
|
||||
await _walletRepository.SetWalletInfo(updateTransactionLabel.WalletId, walletBlobInfo);
|
||||
var update = false;
|
||||
foreach (var label in pair.Value)
|
||||
{
|
||||
if (walletTransactionInfo.Labels.Add(label.label))
|
||||
{
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (update)
|
||||
{
|
||||
await _walletRepository.SetWalletTransactionInfo(updateTransactionLabel.WalletId,
|
||||
pair.Key.ToString(), walletTransactionInfo);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateTransactionLabel
|
||||
{
|
||||
public static (string color, string label) PayjoinLabelTemplate()
|
||||
{
|
||||
return ("#51b13e", "payjoin");
|
||||
}
|
||||
|
||||
public static (string color, string label) InvoiceLabelTemplate(string invoice)
|
||||
{
|
||||
return ("#cedc21", JObject.FromObject(new {value = "invoice", id = invoice}).ToString());
|
||||
}
|
||||
|
||||
public static (string color, string label) PayjoinExposedLabelTemplate(string invoice)
|
||||
{
|
||||
return ("#51b13e", JObject.FromObject(new {value = "pj-exposed", id = invoice}).ToString());
|
||||
}
|
||||
|
||||
public WalletId WalletId { get; set; }
|
||||
public Dictionary<uint256, List<(string color, string label)>> TransactionLabels { get; set; }
|
||||
public override string ToString()
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (var transactionLabel in TransactionLabels)
|
||||
{
|
||||
result.AppendLine(
|
||||
$"Adding {transactionLabel.Value.Count} labels to {transactionLabel.Key} in wallet {WalletId}");
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace BTCPayServer.HostedServices
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
protected override void SubscibeToEvents()
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<UserRegisteredEvent>();
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<IHostedService, BackgroundJobSchedulerHostedService>();
|
||||
services.AddSingleton<IHostedService, AppHubStreamer>();
|
||||
services.AddSingleton<IHostedService, AppInventoryUpdaterHostedService>();
|
||||
services.AddSingleton<IHostedService, TransactionLabelMarkerHostedService>();
|
||||
services.AddSingleton<IHostedService, UserEventHostedService>();
|
||||
services.AddSingleton<IHostedService, DynamicDnsHostedService>();
|
||||
services.AddSingleton<IHostedService, TorServicesHostedService>();
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
await (_CheckingPendingPayments ?? Task.CompletedTask);
|
||||
}
|
||||
|
||||
protected override void SubscibeToEvents()
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<InvoiceEvent>();
|
||||
Subscribe<PaymentRequestUpdated>();
|
||||
|
||||
@@ -21,6 +21,7 @@ using Newtonsoft.Json.Linq;
|
||||
using NicolasDorier.RateLimits;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using BTCPayServer.Data;
|
||||
using NBitcoin.DataEncoders;
|
||||
|
||||
namespace BTCPayServer.Payments.PayJoin
|
||||
@@ -86,6 +87,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly NBXplorerDashboard _dashboard;
|
||||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
|
||||
public PayJoinEndpointController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
InvoiceRepository invoiceRepository, ExplorerClientProvider explorerClientProvider,
|
||||
@@ -93,7 +95,8 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
PayJoinRepository payJoinRepository,
|
||||
EventAggregator eventAggregator,
|
||||
NBXplorerDashboard dashboard,
|
||||
DelayedTransactionBroadcaster broadcaster)
|
||||
DelayedTransactionBroadcaster broadcaster,
|
||||
WalletRepository walletRepository)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
@@ -104,6 +107,7 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
_eventAggregator = eventAggregator;
|
||||
_dashboard = dashboard;
|
||||
_broadcaster = broadcaster;
|
||||
_walletRepository = walletRepository;
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
@@ -475,6 +479,17 @@ namespace BTCPayServer.Payments.PayJoin
|
||||
}
|
||||
await _btcPayWalletProvider.GetWallet(network).SaveOffchainTransactionAsync(originalTx);
|
||||
_eventAggregator.Publish(new InvoiceEvent(invoice, 1002, InvoiceEvent.ReceivedPayment) {Payment = payment});
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel()
|
||||
{
|
||||
WalletId = new WalletId(invoice.StoreId, network.CryptoCode),
|
||||
TransactionLabels = selectedUTXOs.GroupBy(pair => pair.Key.Hash ).Select(utxo =>
|
||||
new KeyValuePair<uint256, List<(string color, string label)>>(utxo.Key,
|
||||
new List<(string color, string label)>()
|
||||
{
|
||||
UpdateTransactionLabel.PayjoinExposedLabelTemplate(invoice.Id)
|
||||
}))
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value)
|
||||
});
|
||||
|
||||
if (psbtFormat && HexEncoder.IsWellFormed(rawBody))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -37,7 +37,7 @@ namespace BTCPayServer.Services.Apps
|
||||
_HubContext = hubContext;
|
||||
}
|
||||
|
||||
protected override void SubscibeToEvents()
|
||||
protected override void SubscribeToEvents()
|
||||
{
|
||||
Subscribe<InvoiceEvent>();
|
||||
Subscribe<AppsController.AppUpdated>();
|
||||
|
||||
@@ -49,8 +49,17 @@
|
||||
v-for="label of item.labels"
|
||||
v-bind:style="{ 'background-color': label.color}"
|
||||
key="label.value">
|
||||
|
||||
<span v-if="!label.link" data-toggle="tooltip" v-tooltip="label.tooltip">
|
||||
{{label.value}}
|
||||
</span>
|
||||
|
||||
<a :href="label.link" target="_blank"v-if="label.link" data-toggle="tooltip" v-tooltip="label.tooltip">
|
||||
{{label.value}}
|
||||
<i class="fa fa-info-circle"></i>
|
||||
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-muted ml-2">{{item.amount}}</span>
|
||||
</div>
|
||||
|
||||
@@ -90,8 +90,8 @@
|
||||
<td style="text-align:left">
|
||||
@foreach (var label in transaction.Labels)
|
||||
{
|
||||
<a
|
||||
asp-route-labelFilter="@label.Value"
|
||||
<div
|
||||
|
||||
class="badge transactionLabel"
|
||||
style="
|
||||
background-color: @label.Color;
|
||||
@@ -100,24 +100,27 @@
|
||||
padding-right: 16px;
|
||||
position: relative;
|
||||
"
|
||||
>
|
||||
@label.Value
|
||||
title="@label.Tooltip">
|
||||
<a asp-route-labelFilter="@label.RawValue" class="text-white">@label.Value</a>
|
||||
|
||||
@if (!string.IsNullOrEmpty(label.Link))
|
||||
{
|
||||
<a href="@label.Link" target="_blank" class="fa fa-info-circle"></a>
|
||||
}
|
||||
<form
|
||||
asp-route-walletId="@this.Context.GetRouteValue("walletId")"
|
||||
asp-action="ModifyTransaction"
|
||||
method="post"
|
||||
class="removeTransactionLabelForm"
|
||||
>
|
||||
<input type="hidden" name="transactionId" value="@transaction.Id" />
|
||||
class="removeTransactionLabelForm">
|
||||
<input type="hidden" name="transactionId" value="@transaction.Id"/>
|
||||
<button
|
||||
name="removelabel"
|
||||
type="submit"
|
||||
value="@label.Value"
|
||||
>
|
||||
value="@label.RawValue">
|
||||
<span class="fa fa-close"></span>
|
||||
</button>
|
||||
</form>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="smMaxWidth text-truncate @(transaction.IsConfirmed ? "" : "unconf")">
|
||||
|
||||
Reference in New Issue
Block a user