mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Add transaction info PATCH endpoint (#3561)
* Add transaction info patch endpoint * Add "#nullable enable" to LabelFactory * Add Swagger docs * Update OnChain to onchain * update feeRate to feerate * Add test * replace "Onchain" with "onchain"
This commit is contained in:
@@ -20,7 +20,7 @@ namespace BTCPayServer.Client
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}"), token);
|
||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/preview",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview",
|
||||
bodyPayload: paymentMethod,
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Post), token);
|
||||
@@ -73,7 +73,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/preview",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview",
|
||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||
method: HttpMethod.Get), token);
|
||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||
@@ -84,7 +84,7 @@ namespace BTCPayServer.Client
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/generate",
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/generate",
|
||||
bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet"), token);
|
||||
return await HandleResponse<OnChainWalletOverviewData>(response);
|
||||
}
|
||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||
@@ -29,7 +29,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/feeRate", queryParams), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feeRate", queryParams), token);
|
||||
return await HandleResponse<OnChainWalletFeeRateData>(response);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>()
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>()
|
||||
{
|
||||
{"forceGenerate", forceGenerate}
|
||||
}), token);
|
||||
@@ -50,7 +50,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address", method: HttpMethod.Delete), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", query), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query), token);
|
||||
return await HandleResponse<IEnumerable<OnChainWalletTransactionData>>(response);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,18 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions/{transactionId}"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}"), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> PatchOnChainWalletTransaction(
|
||||
string storeId, string cryptoCode, string transactionId,
|
||||
PatchOnChainTransactionRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}", queryPayload: null, bodyPayload: request, HttpMethod.Patch), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
@@ -85,7 +96,7 @@ namespace BTCPayServer.Client
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/utxos"), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos"), token);
|
||||
return await HandleResponse<IEnumerable<OnChainWalletUTXOData>>(response);
|
||||
}
|
||||
|
||||
@@ -100,7 +111,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
@@ -115,7 +126,7 @@ namespace BTCPayServer.Client
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
return Transaction.Parse(await HandleResponse<string>(response), network);
|
||||
}
|
||||
}
|
||||
|
||||
12
BTCPayServer.Client/Models/PatchOnChainTransactionRequest.cs
Normal file
12
BTCPayServer.Client/Models/PatchOnChainTransactionRequest.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class PatchOnChainTransactionRequest
|
||||
{
|
||||
|
||||
public string? Comment { get; set; } = null;
|
||||
public List<string>? Labels { get; set; } = null;
|
||||
}
|
||||
}
|
||||
@@ -1703,7 +1703,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
|
||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
|
||||
|
||||
await AssertValidationError(new[] { "accountKeyPath" }, () => viewOnlyClient.SendHttpRequest<GreenfieldValidationError[]>(path: $"api/v1/stores/{store.Id}/payment-methods/Onchain/BTC/preview", method: HttpMethod.Post,
|
||||
await AssertValidationError(new[] { "accountKeyPath" }, () => viewOnlyClient.SendHttpRequest<GreenfieldValidationError[]>(path: $"api/v1/stores/{store.Id}/payment-methods/onchain/BTC/preview", method: HttpMethod.Post,
|
||||
bodyPayload: JObject.Parse("{\"accountKeyPath\": \"0/1\"}")));
|
||||
|
||||
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
||||
@@ -2126,7 +2126,30 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
});
|
||||
await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
var transaction = await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
|
||||
Assert.Equal(transaction.TransactionHash, txdata.TransactionHash);
|
||||
Assert.Equal(String.Empty, transaction.Comment);
|
||||
Assert.Equal(new Dictionary<string, LabelData>(), transaction.Labels);
|
||||
|
||||
// transaction patch tests
|
||||
var patchedTransaction = await client.PatchOnChainWalletTransaction(
|
||||
walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString(),
|
||||
new PatchOnChainTransactionRequest() {
|
||||
Comment = "test comment",
|
||||
Labels = new List<string>
|
||||
{
|
||||
"test label"
|
||||
}
|
||||
});
|
||||
Assert.Equal("test comment", patchedTransaction.Comment);
|
||||
Assert.Equal(
|
||||
new Dictionary<string, LabelData>()
|
||||
{
|
||||
{ "test label", new LabelData(){ Type = "raw", Text = "test label" } }
|
||||
}.ToJson(),
|
||||
patchedTransaction.Labels.ToJson()
|
||||
);
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -50,6 +51,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly WalletReceiveService _walletReceiveService;
|
||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||
private readonly LabelFactory _labelFactory;
|
||||
|
||||
public GreenfieldStoreOnChainWalletsController(
|
||||
IAuthorizationService authorizationService,
|
||||
@@ -64,7 +66,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
DelayedTransactionBroadcaster delayedTransactionBroadcaster,
|
||||
EventAggregator eventAggregator,
|
||||
WalletReceiveService walletReceiveService,
|
||||
IFeeProviderFactory feeProviderFactory)
|
||||
IFeeProviderFactory feeProviderFactory,
|
||||
LabelFactory labelFactory
|
||||
)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
@@ -79,6 +83,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_eventAggregator = eventAggregator;
|
||||
_walletReceiveService = walletReceiveService;
|
||||
_feeProviderFactory = feeProviderFactory;
|
||||
_labelFactory = labelFactory;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
@@ -229,6 +234,65 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok(ToModel(walletTransactionsInfoAsync, tx, wallet));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPatch("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}")]
|
||||
public async Task<IActionResult> PatchOnChainWalletTransaction(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
string transactionId,
|
||||
[FromBody] PatchOnChainTransactionRequest request
|
||||
)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out var network,
|
||||
out var derivationScheme, out var actionResult))
|
||||
return actionResult;
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
var tx = await wallet.FetchTransaction(derivationScheme.AccountDerivation, uint256.Parse(transactionId));
|
||||
if (tx is null)
|
||||
{
|
||||
return this.CreateAPIError(404, "transaction-not-found", "The transaction was not found.");
|
||||
}
|
||||
|
||||
var walletId = new WalletId(storeId, cryptoCode);
|
||||
var walletTransactionsInfoAsync = _walletRepository.GetWalletTransactionsInfo(walletId);
|
||||
if (!(await walletTransactionsInfoAsync).TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
}
|
||||
|
||||
if (request.Comment != null)
|
||||
{
|
||||
walletTransactionInfo.Comment = request.Comment.Trim().Truncate(WalletTransactionDataExtensions.MaxCommentSize);
|
||||
}
|
||||
|
||||
if (request.Labels != null)
|
||||
{
|
||||
var walletBlobInfo = await _walletRepository.GetWalletInfo(walletId);
|
||||
|
||||
foreach (string label in request.Labels)
|
||||
{
|
||||
var rawLabel = await _labelFactory.BuildLabel(
|
||||
walletBlobInfo,
|
||||
Request,
|
||||
walletTransactionInfo,
|
||||
walletId,
|
||||
transactionId,
|
||||
label
|
||||
);
|
||||
walletTransactionInfo.Labels.TryAdd(rawLabel.Text, rawLabel);
|
||||
}
|
||||
}
|
||||
|
||||
await _walletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||
var walletTransactionsInfo =
|
||||
(await _walletRepository.GetWalletTransactionsInfo(walletId, new[] { transactionId }))
|
||||
.Values
|
||||
.FirstOrDefault();
|
||||
|
||||
return Ok(ToModel(walletTransactionsInfo, tx, wallet));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos")]
|
||||
public async Task<IActionResult> GetOnChainWalletUTXOs(string storeId, string cryptoCode)
|
||||
|
||||
@@ -122,20 +122,6 @@ namespace BTCPayServer.Controllers
|
||||
_walletHistogramService = walletHistogramService;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
readonly string[] LabelColorScheme =
|
||||
{
|
||||
"#fbca04",
|
||||
"#0e8a16",
|
||||
"#ff7619",
|
||||
"#84b6eb",
|
||||
"#5319e7",
|
||||
"#cdcdcd",
|
||||
"#cc317c",
|
||||
};
|
||||
|
||||
const int MaxLabelSize = 20;
|
||||
const int MaxCommentSize = 200;
|
||||
[HttpPost]
|
||||
[Route("{walletId}")]
|
||||
public async Task<IActionResult> ModifyTransaction(
|
||||
@@ -173,33 +159,19 @@ namespace BTCPayServer.Controllers
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
if (addlabel != null)
|
||||
{
|
||||
addlabel = addlabel.Trim().TrimStart('{').ToLowerInvariant().Replace(',', ' ').Truncate(MaxLabelSize);
|
||||
var labels = _labelFactory.GetWalletColoredLabels(walletBlobInfo, Request);
|
||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
}
|
||||
if (!labels.Any(l => l.Text.Equals(addlabel, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
List<string> allColors = new List<string>();
|
||||
allColors.AddRange(LabelColorScheme);
|
||||
allColors.AddRange(labels.Select(l => l.Color));
|
||||
var chosenColor =
|
||||
allColors
|
||||
.GroupBy(k => k)
|
||||
.OrderBy(k => k.Count())
|
||||
.ThenBy(k =>
|
||||
{
|
||||
var indexInColorScheme = Array.IndexOf(LabelColorScheme, k.Key);
|
||||
|
||||
// Ensures that any label color which may not be in our label color scheme is given the least priority
|
||||
return indexInColorScheme == -1 ? double.PositiveInfinity : indexInColorScheme;
|
||||
})
|
||||
.First().Key;
|
||||
walletBlobInfo.LabelColors.Add(addlabel, chosenColor);
|
||||
await WalletRepository.SetWalletInfo(walletId, walletBlobInfo);
|
||||
}
|
||||
var rawLabel = new RawLabel(addlabel);
|
||||
var rawLabel = await _labelFactory.BuildLabel(
|
||||
walletBlobInfo,
|
||||
Request,
|
||||
walletTransactionInfo,
|
||||
walletId,
|
||||
transactionId,
|
||||
addlabel
|
||||
);
|
||||
if (walletTransactionInfo.Labels.TryAdd(rawLabel.Text, rawLabel))
|
||||
{
|
||||
await WalletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||
@@ -224,7 +196,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else if (addcomment != null)
|
||||
{
|
||||
addcomment = addcomment.Trim().Truncate(MaxCommentSize);
|
||||
addcomment = addcomment.Trim().Truncate(WalletTransactionDataExtensions.MaxCommentSize);
|
||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
walletTransactionInfo = new WalletTransactionInfo();
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
public static class WalletTransactionDataExtensions
|
||||
{
|
||||
public static int MaxCommentSize = 200;
|
||||
|
||||
public static WalletTransactionInfo GetBlobInfo(this WalletTransactionData walletTransactionData)
|
||||
{
|
||||
WalletTransactionInfo blobInfo;
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using Amazon.Util.Internal.PlatformServices;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
|
||||
namespace BTCPayServer.Services.Labels
|
||||
{
|
||||
public class LabelFactory
|
||||
{
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
|
||||
public LabelFactory(LinkGenerator linkGenerator)
|
||||
public LabelFactory(
|
||||
LinkGenerator linkGenerator,
|
||||
WalletRepository walletRepository
|
||||
)
|
||||
{
|
||||
_linkGenerator = linkGenerator;
|
||||
_walletRepository = walletRepository;
|
||||
}
|
||||
|
||||
public IEnumerable<ColoredLabel> ColorizeTransactionLabels(WalletBlobInfo walletBlobInfo, WalletTransactionInfo transactionInfo,
|
||||
@@ -39,7 +46,7 @@ namespace BTCPayServer.Services.Labels
|
||||
}
|
||||
|
||||
const string DefaultColor = "#000";
|
||||
private ColoredLabel CreateLabel(LabelData uncoloredLabel, string color, HttpRequest request)
|
||||
private ColoredLabel CreateLabel(LabelData uncoloredLabel, string? color, HttpRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(uncoloredLabel);
|
||||
color ??= DefaultColor;
|
||||
@@ -93,6 +100,68 @@ namespace BTCPayServer.Services.Labels
|
||||
return coloredLabel;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
readonly string[] LabelColorScheme =
|
||||
{
|
||||
"#fbca04",
|
||||
"#0e8a16",
|
||||
"#ff7619",
|
||||
"#84b6eb",
|
||||
"#5319e7",
|
||||
"#cdcdcd",
|
||||
"#cc317c",
|
||||
};
|
||||
|
||||
readonly int MaxLabelSize = 20;
|
||||
|
||||
async public Task<RawLabel> BuildLabel(
|
||||
WalletBlobInfo walletBlobInfo,
|
||||
HttpRequest request,
|
||||
WalletTransactionInfo walletTransactionInfo,
|
||||
WalletId walletId,
|
||||
string transactionId,
|
||||
string label
|
||||
)
|
||||
{
|
||||
label = label.Trim().TrimStart('{').ToLowerInvariant().Replace(',', ' ').Truncate(MaxLabelSize);
|
||||
var labels = GetWalletColoredLabels(walletBlobInfo, request);
|
||||
|
||||
if (!labels.Any(l => l.Text.Equals(label, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var chosenColor = ChooseBackgroundColor(walletBlobInfo, request);
|
||||
walletBlobInfo.LabelColors.Add(label, chosenColor);
|
||||
await _walletRepository.SetWalletInfo(walletId, walletBlobInfo);
|
||||
}
|
||||
|
||||
return new RawLabel(label);
|
||||
}
|
||||
|
||||
private string ChooseBackgroundColor(
|
||||
WalletBlobInfo walletBlobInfo,
|
||||
HttpRequest request
|
||||
)
|
||||
{
|
||||
var labels = GetWalletColoredLabels(walletBlobInfo, request);
|
||||
|
||||
List<string> allColors = new List<string>();
|
||||
allColors.AddRange(LabelColorScheme);
|
||||
allColors.AddRange(labels.Select(l => l.Color));
|
||||
var chosenColor =
|
||||
allColors
|
||||
.GroupBy(k => k)
|
||||
.OrderBy(k => k.Count())
|
||||
.ThenBy(k =>
|
||||
{
|
||||
var indexInColorScheme = Array.IndexOf(LabelColorScheme, k.Key);
|
||||
|
||||
// Ensures that any label color which may not be in our label color scheme is given the least priority
|
||||
return indexInColorScheme == -1 ? double.PositiveInfinity : indexInColorScheme;
|
||||
})
|
||||
.First().Key;
|
||||
|
||||
return chosenColor;
|
||||
}
|
||||
|
||||
private string TextColor(string bgColor)
|
||||
{
|
||||
int nThreshold = 105;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"paths": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
@@ -57,7 +57,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet/feeRate": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feerate": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
@@ -124,7 +124,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet/address": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
@@ -239,7 +239,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet/transactions": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
@@ -405,7 +405,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet/transactions/{transactionId}": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
@@ -469,9 +469,83 @@
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
],
|
||||
"summary": "Patch store on-chain wallet transaction info",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet/utxos": {
|
||||
{
|
||||
"name": "cryptoCode",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The crypto code of the wallet to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": "BTC"
|
||||
},
|
||||
{
|
||||
"name": "transactionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The transaction id to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PatchOnChainTransactionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Patch store on-chain wallet transaction info",
|
||||
"operationId": "StoreOnChainWallets_PatchOnChainWalletTransaction",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "transaction",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/OnChainWalletTransactionData"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to view the specified store"
|
||||
},
|
||||
"404": {
|
||||
"description": "The key is not found for this store/wallet"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API_Key": [
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Wallet (On Chain)"
|
||||
@@ -559,7 +633,7 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"feeRate": {
|
||||
"feerate": {
|
||||
"type": "number",
|
||||
"format": "decimal",
|
||||
"description": "The fee rate (sats per byte) based on the wallet's configured recommended block confirmation target"
|
||||
@@ -758,7 +832,7 @@
|
||||
"$ref": "#/components/schemas/CreateOnChainTransactionRequestDestination"
|
||||
}
|
||||
},
|
||||
"feeRate": {
|
||||
"feerate": {
|
||||
"type": "number",
|
||||
"format": "decimal or long (sats/byte)",
|
||||
"description": "Transaction fee."
|
||||
@@ -795,6 +869,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PatchOnChainTransactionRequest": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"comment": {
|
||||
"nullable": true,
|
||||
"type": "string",
|
||||
"description": "Transaction comment"
|
||||
},
|
||||
"labels": {
|
||||
"nullable": true,
|
||||
"description": "Transaction labels",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user