mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Wallet Transactions Export: Add BIP-329 support (#4799)
* Wallet Transactions Export: Add BIP-329 support * Adjust wording * Export one line per label * Join labels, fix type * Rewrite the ProcessBip329 function to be more performant * Add nullable on all TransactionsExport --------- Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
@@ -1570,9 +1570,20 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Contains("\"Amount\": \"3.00000000\"", s.Driver.PageSource);
|
Assert.Contains("\"Amount\": \"3.00000000\"", s.Driver.PageSource);
|
||||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
|
// BIP-329 export
|
||||||
|
s.Driver.FindElement(By.Id("ExportDropdownToggle")).Click();
|
||||||
|
s.Driver.FindElement(By.Id("ExportBIP329")).Click();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
Assert.Contains(s.WalletId.ToString(), s.Driver.Url);
|
||||||
|
Assert.EndsWith("export?format=bip329", s.Driver.Url);
|
||||||
|
Assert.Contains("{\"type\":\"tx\",\"ref\":\"", s.Driver.PageSource);
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
// CSV export
|
// CSV export
|
||||||
s.Driver.FindElement(By.Id("ExportDropdownToggle")).Click();
|
s.Driver.FindElement(By.Id("ExportDropdownToggle")).Click();
|
||||||
s.Driver.FindElement(By.Id("ExportCSV")).Click();
|
s.Driver.FindElement(By.Id("ExportCSV")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Timeout = TestTimeout)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using BTCPayServer.Client.Models;
|
|||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using ExchangeSharp;
|
using ExchangeSharp;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Chrome;
|
using OpenQA.Selenium.Chrome;
|
||||||
@@ -250,7 +251,6 @@ retry:
|
|||||||
{
|
{
|
||||||
// 1. Generate an API Token on https://www.transifex.com/user/settings/api/
|
// 1. Generate an API Token on https://www.transifex.com/user/settings/api/
|
||||||
// 2. Run "dotnet user-secrets set TransifexAPIToken <youapitoken>"
|
// 2. Run "dotnet user-secrets set TransifexAPIToken <youapitoken>"
|
||||||
|
|
||||||
await PullTransifexTranslationsCore(TranslationFolder.CheckoutV1);
|
await PullTransifexTranslationsCore(TranslationFolder.CheckoutV1);
|
||||||
await PullTransifexTranslationsCore(TranslationFolder.CheckoutV2);
|
await PullTransifexTranslationsCore(TranslationFolder.CheckoutV2);
|
||||||
|
|
||||||
|
|||||||
@@ -1317,22 +1317,34 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
|
||||||
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null);
|
var walletTransactionsInfoAsync = WalletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null);
|
||||||
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation, null, null);
|
var input = await wallet.FetchTransactionHistory(paymentMethod.AccountDerivation);
|
||||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||||
var export = new TransactionsExport(wallet, walletTransactionsInfo);
|
var export = new TransactionsExport(wallet, walletTransactionsInfo);
|
||||||
var res = export.Process(input, format);
|
var res = export.Process(input, format);
|
||||||
|
var fileType = format switch
|
||||||
|
{
|
||||||
|
"csv" => "csv",
|
||||||
|
"json" => "json",
|
||||||
|
"bip329" => "jsonl",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
|
||||||
|
};
|
||||||
|
var mimeType = format switch
|
||||||
|
{
|
||||||
|
"csv" => "text/csv",
|
||||||
|
"json" => "application/json",
|
||||||
|
"bip329" => "text/jsonl", // https://stackoverflow.com/questions/59938644/what-is-the-mime-type-of-jsonl-files
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
|
||||||
|
};
|
||||||
var cd = new ContentDisposition
|
var cd = new ContentDisposition
|
||||||
{
|
{
|
||||||
FileName = $"btcpay-{walletId}-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{format}",
|
FileName = $"btcpay-{walletId}-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture)}.{fileType}",
|
||||||
Inline = true
|
Inline = true
|
||||||
};
|
};
|
||||||
Response.Headers.Add("Content-Disposition", cd.ToString());
|
Response.Headers.Add("Content-Disposition", cd.ToString());
|
||||||
Response.Headers.Add("X-Content-Type-Options", "nosniff");
|
Response.Headers.Add("X-Content-Type-Options", "nosniff");
|
||||||
return Content(res, "application/" + format);
|
return Content(res, mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class UpdateLabelsRequest
|
public class UpdateLabelsRequest
|
||||||
{
|
{
|
||||||
public string? Id { get; set; }
|
public string? Id { get; set; }
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Client.Models;
|
using System.Text;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Models.WalletViewModels;
|
|
||||||
using BTCPayServer.Services.Invoices;
|
|
||||||
using BTCPayServer.Services.Rates;
|
|
||||||
using CsvHelper.Configuration;
|
using CsvHelper.Configuration;
|
||||||
using CsvHelper.Configuration.Attributes;
|
using CsvHelper.Configuration.Attributes;
|
||||||
using NBXplorer.Models;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Wallets.Export
|
namespace BTCPayServer.Services.Wallets.Export
|
||||||
@@ -41,18 +38,46 @@ namespace BTCPayServer.Services.Wallets.Export
|
|||||||
|
|
||||||
if (_walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
if (_walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||||
{
|
{
|
||||||
model.Labels = transactionInfo.LabelColors?.Select(l => l.Key).ToList();
|
model.Labels = transactionInfo.LabelColors.Select(l => l.Key).ToList();
|
||||||
model.Comment = transactionInfo.Comment;
|
model.Comment = transactionInfo.Comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
return fileFormat switch
|
||||||
|
{
|
||||||
|
"bip329" => ProcessBip329(list),
|
||||||
|
"json" => ProcessJson(list),
|
||||||
|
"csv" => ProcessCsv(list),
|
||||||
|
_ => throw new Exception("Export format not supported")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(fileFormat, "json", StringComparison.OrdinalIgnoreCase))
|
// https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki
|
||||||
return ProcessJson(list);
|
private static string ProcessBip329(List<ExportTransaction> txs)
|
||||||
if (string.Equals(fileFormat, "csv", StringComparison.OrdinalIgnoreCase))
|
{
|
||||||
return ProcessCsv(list);
|
var sw = new StringWriter();
|
||||||
throw new Exception("Export format not supported");
|
var jsonw = new JsonTextWriter(sw);
|
||||||
|
foreach (var tx in txs)
|
||||||
|
{
|
||||||
|
if (tx.Labels is null)
|
||||||
|
continue;
|
||||||
|
foreach (var label in tx.Labels)
|
||||||
|
{
|
||||||
|
jsonw.WriteStartObject();
|
||||||
|
jsonw.WritePropertyName("type");
|
||||||
|
jsonw.WriteValue("tx");
|
||||||
|
jsonw.WritePropertyName("ref");
|
||||||
|
jsonw.WriteValue(tx.TransactionId);
|
||||||
|
jsonw.WritePropertyName("label");
|
||||||
|
jsonw.WriteValue(label);
|
||||||
|
jsonw.WriteEndObject();
|
||||||
|
jsonw.WriteWhitespace("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonw.Flush();
|
||||||
|
return sw.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ProcessJson(List<ExportTransaction> invoices)
|
private static string ProcessJson(List<ExportTransaction> invoices)
|
||||||
@@ -87,14 +112,14 @@ namespace BTCPayServer.Services.Wallets.Export
|
|||||||
public class ExportTransaction
|
public class ExportTransaction
|
||||||
{
|
{
|
||||||
[Name("Transaction Id")]
|
[Name("Transaction Id")]
|
||||||
public string TransactionId { get; set; }
|
public string TransactionId { get; set; } = string.Empty;
|
||||||
public DateTimeOffset Timestamp { get; set; }
|
public DateTimeOffset Timestamp { get; set; }
|
||||||
public string Amount { get; set; }
|
public string Amount { get; set; } = string.Empty;
|
||||||
public string Currency { get; set; }
|
public string Currency { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Name("Is Confirmed")]
|
[Name("Is Confirmed")]
|
||||||
public bool IsConfirmed { get; set; }
|
public bool IsConfirmed { get; set; }
|
||||||
public string Comment { get; set; }
|
public string? Comment { get; set; }
|
||||||
public List<string> Labels { get; set; }
|
public List<string>? Labels { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@
|
|||||||
<div class="dropdown-menu" aria-labelledby="ExportDropdownToggle">
|
<div class="dropdown-menu" aria-labelledby="ExportDropdownToggle">
|
||||||
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="csv" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportCSV">CSV</a>
|
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="csv" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportCSV">CSV</a>
|
||||||
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="json" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportJSON">JSON</a>
|
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="json" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportJSON">JSON</a>
|
||||||
|
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="bip329" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportBIP329">Wallet Labels (BIP-329)</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user