mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +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 =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
await _httpClient.SendAsync(
|
||||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain",
|
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain",
|
||||||
query), token);
|
query), token);
|
||||||
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
|
return await HandleResponse<IEnumerable<OnChainPaymentMethodData>>(response);
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
method: HttpMethod.Delete), token);
|
||||||
await HandleResponse(response);
|
await HandleResponse(response);
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ namespace BTCPayServer.Client
|
|||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.SendAsync(
|
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);
|
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||||
return await HandleResponse<OnChainPaymentMethodData>(response);
|
return await HandleResponse<OnChainPaymentMethodData>(response);
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ namespace BTCPayServer.Client
|
|||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.SendAsync(
|
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,
|
bodyPayload: paymentMethod,
|
||||||
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||||
method: HttpMethod.Post), token);
|
method: HttpMethod.Post), token);
|
||||||
@@ -73,7 +73,7 @@ namespace BTCPayServer.Client
|
|||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.SendAsync(
|
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 } },
|
queryPayload: new Dictionary<string, object>() { { "offset", offset }, { "amount", amount } },
|
||||||
method: HttpMethod.Get), token);
|
method: HttpMethod.Get), token);
|
||||||
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
return await HandleResponse<OnChainPaymentMethodPreviewResultData>(response);
|
||||||
@@ -84,7 +84,7 @@ namespace BTCPayServer.Client
|
|||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.SendAsync(
|
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,
|
bodyPayload: request,
|
||||||
method: HttpMethod.Post), token);
|
method: HttpMethod.Post), token);
|
||||||
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
|
return await HandleResponse<OnChainPaymentMethodDataWithSensitiveData>(response);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<OnChainWalletOverviewData>(response);
|
||||||
}
|
}
|
||||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||||
@@ -29,7 +29,7 @@ namespace BTCPayServer.Client
|
|||||||
}
|
}
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<OnChainWalletFeeRateData>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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}
|
{"forceGenerate", forceGenerate}
|
||||||
}), token);
|
}), token);
|
||||||
@@ -50,7 +50,7 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
await HandleResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ namespace BTCPayServer.Client
|
|||||||
}
|
}
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<IEnumerable<OnChainWalletTransactionData>>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,18 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +96,7 @@ namespace BTCPayServer.Client
|
|||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<IEnumerable<OnChainWalletUTXOData>>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +111,7 @@ namespace BTCPayServer.Client
|
|||||||
}
|
}
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +126,7 @@ namespace BTCPayServer.Client
|
|||||||
}
|
}
|
||||||
var response =
|
var response =
|
||||||
await _httpClient.SendAsync(
|
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);
|
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",
|
Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC",
|
||||||
new UpdateOnChainPaymentMethodRequest() { Enabled = true, DerivationScheme = xpub })).Addresses.First().Address);
|
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\"}")));
|
bodyPayload: JObject.Parse("{\"accountKeyPath\": \"0/1\"}")));
|
||||||
|
|
||||||
var method = await client.UpdateStoreOnChainPaymentMethod(store.Id, "BTC",
|
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 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 () =>
|
await AssertHttpError(403, async () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using BTCPayServer.Payments.PayJoin;
|
|||||||
using BTCPayServer.Payments.PayJoin.Sender;
|
using BTCPayServer.Payments.PayJoin.Sender;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
|
using BTCPayServer.Services.Labels;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -50,6 +51,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly WalletReceiveService _walletReceiveService;
|
private readonly WalletReceiveService _walletReceiveService;
|
||||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||||
|
private readonly LabelFactory _labelFactory;
|
||||||
|
|
||||||
public GreenfieldStoreOnChainWalletsController(
|
public GreenfieldStoreOnChainWalletsController(
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
@@ -64,7 +66,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
DelayedTransactionBroadcaster delayedTransactionBroadcaster,
|
DelayedTransactionBroadcaster delayedTransactionBroadcaster,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
WalletReceiveService walletReceiveService,
|
WalletReceiveService walletReceiveService,
|
||||||
IFeeProviderFactory feeProviderFactory)
|
IFeeProviderFactory feeProviderFactory,
|
||||||
|
LabelFactory labelFactory
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_btcPayWalletProvider = btcPayWalletProvider;
|
_btcPayWalletProvider = btcPayWalletProvider;
|
||||||
@@ -79,6 +83,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_walletReceiveService = walletReceiveService;
|
_walletReceiveService = walletReceiveService;
|
||||||
_feeProviderFactory = feeProviderFactory;
|
_feeProviderFactory = feeProviderFactory;
|
||||||
|
_labelFactory = labelFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -229,6 +234,65 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return Ok(ToModel(walletTransactionsInfoAsync, tx, wallet));
|
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)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos")]
|
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos")]
|
||||||
public async Task<IActionResult> GetOnChainWalletUTXOs(string storeId, string cryptoCode)
|
public async Task<IActionResult> GetOnChainWalletUTXOs(string storeId, string cryptoCode)
|
||||||
|
|||||||
@@ -121,21 +121,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_connectionFactory = connectionFactory;
|
_connectionFactory = connectionFactory;
|
||||||
_walletHistogramService = walletHistogramService;
|
_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]
|
[HttpPost]
|
||||||
[Route("{walletId}")]
|
[Route("{walletId}")]
|
||||||
public async Task<IActionResult> ModifyTransaction(
|
public async Task<IActionResult> ModifyTransaction(
|
||||||
@@ -173,33 +159,19 @@ namespace BTCPayServer.Controllers
|
|||||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||||
if (addlabel != null)
|
if (addlabel != null)
|
||||||
{
|
{
|
||||||
addlabel = addlabel.Trim().TrimStart('{').ToLowerInvariant().Replace(',', ' ').Truncate(MaxLabelSize);
|
|
||||||
var labels = _labelFactory.GetWalletColoredLabels(walletBlobInfo, Request);
|
|
||||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||||
{
|
{
|
||||||
walletTransactionInfo = new WalletTransactionInfo();
|
walletTransactionInfo = new WalletTransactionInfo();
|
||||||
}
|
}
|
||||||
if (!labels.Any(l => l.Text.Equals(addlabel, StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
var rawLabel = await _labelFactory.BuildLabel(
|
||||||
List<string> allColors = new List<string>();
|
walletBlobInfo,
|
||||||
allColors.AddRange(LabelColorScheme);
|
Request,
|
||||||
allColors.AddRange(labels.Select(l => l.Color));
|
walletTransactionInfo,
|
||||||
var chosenColor =
|
walletId,
|
||||||
allColors
|
transactionId,
|
||||||
.GroupBy(k => k)
|
addlabel
|
||||||
.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);
|
|
||||||
if (walletTransactionInfo.Labels.TryAdd(rawLabel.Text, rawLabel))
|
if (walletTransactionInfo.Labels.TryAdd(rawLabel.Text, rawLabel))
|
||||||
{
|
{
|
||||||
await WalletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
await WalletRepository.SetWalletTransactionInfo(walletId, transactionId, walletTransactionInfo);
|
||||||
@@ -224,7 +196,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
else if (addcomment != null)
|
else if (addcomment != null)
|
||||||
{
|
{
|
||||||
addcomment = addcomment.Trim().Truncate(MaxCommentSize);
|
addcomment = addcomment.Trim().Truncate(WalletTransactionDataExtensions.MaxCommentSize);
|
||||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||||
{
|
{
|
||||||
walletTransactionInfo = new WalletTransactionInfo();
|
walletTransactionInfo = new WalletTransactionInfo();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ namespace BTCPayServer.Data
|
|||||||
}
|
}
|
||||||
public static class WalletTransactionDataExtensions
|
public static class WalletTransactionDataExtensions
|
||||||
{
|
{
|
||||||
|
public static int MaxCommentSize = 200;
|
||||||
|
|
||||||
public static WalletTransactionInfo GetBlobInfo(this WalletTransactionData walletTransactionData)
|
public static WalletTransactionInfo GetBlobInfo(this WalletTransactionData walletTransactionData)
|
||||||
{
|
{
|
||||||
WalletTransactionInfo blobInfo;
|
WalletTransactionInfo blobInfo;
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using Amazon.Util.Internal.PlatformServices;
|
using System.Collections.Generic;
|
||||||
using BTCPayServer.Client.Models;
|
using System.Linq;
|
||||||
using BTCPayServer.Data;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Newtonsoft.Json.Linq;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Labels
|
namespace BTCPayServer.Services.Labels
|
||||||
{
|
{
|
||||||
public class LabelFactory
|
public class LabelFactory
|
||||||
{
|
{
|
||||||
private readonly LinkGenerator _linkGenerator;
|
private readonly LinkGenerator _linkGenerator;
|
||||||
|
private readonly WalletRepository _walletRepository;
|
||||||
|
|
||||||
public LabelFactory(LinkGenerator linkGenerator)
|
public LabelFactory(
|
||||||
|
LinkGenerator linkGenerator,
|
||||||
|
WalletRepository walletRepository
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
|
_walletRepository = walletRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ColoredLabel> ColorizeTransactionLabels(WalletBlobInfo walletBlobInfo, WalletTransactionInfo transactionInfo,
|
public IEnumerable<ColoredLabel> ColorizeTransactionLabels(WalletBlobInfo walletBlobInfo, WalletTransactionInfo transactionInfo,
|
||||||
@@ -39,7 +46,7 @@ namespace BTCPayServer.Services.Labels
|
|||||||
}
|
}
|
||||||
|
|
||||||
const string DefaultColor = "#000";
|
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);
|
ArgumentNullException.ThrowIfNull(uncoloredLabel);
|
||||||
color ??= DefaultColor;
|
color ??= DefaultColor;
|
||||||
@@ -92,7 +99,69 @@ namespace BTCPayServer.Services.Labels
|
|||||||
}
|
}
|
||||||
return coloredLabel;
|
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)
|
private string TextColor(string bgColor)
|
||||||
{
|
{
|
||||||
int nThreshold = 105;
|
int nThreshold = 105;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/v1/stores/{storeId}/payment-methods/OnChain/{cryptoCode}/wallet": {
|
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Store Wallet (On Chain)"
|
"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": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Store Wallet (On Chain)"
|
"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": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Store Wallet (On Chain)"
|
"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": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Store Wallet (On Chain)"
|
"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": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Store Wallet (On Chain)"
|
"Store Wallet (On Chain)"
|
||||||
@@ -469,9 +469,83 @@
|
|||||||
"Basic": []
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": {
|
"/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Store Wallet (On Chain)"
|
"Store Wallet (On Chain)"
|
||||||
@@ -559,7 +633,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"feeRate": {
|
"feerate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "decimal",
|
"format": "decimal",
|
||||||
"description": "The fee rate (sats per byte) based on the wallet's configured recommended block confirmation target"
|
"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"
|
"$ref": "#/components/schemas/CreateOnChainTransactionRequestDestination"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"feeRate": {
|
"feerate": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "decimal or long (sats/byte)",
|
"format": "decimal or long (sats/byte)",
|
||||||
"description": "Transaction fee."
|
"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