diff --git a/.gitattributes b/.gitattributes
index 0b8e159..8793de5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,4 +6,6 @@
# Denote all files that are truly binary and should not be modified.
*.png binary
-*.jpg binary
\ No newline at end of file
+*.jpg binary
+*.keys binary
+wallet binary
\ No newline at end of file
diff --git a/BTCPayServer.Plugins.IntegrationTests/BTCPayServer.Plugins.IntegrationTests.csproj b/BTCPayServer.Plugins.IntegrationTests/BTCPayServer.Plugins.IntegrationTests.csproj
index b0e1029..2d3a71c 100644
--- a/BTCPayServer.Plugins.IntegrationTests/BTCPayServer.Plugins.IntegrationTests.csproj
+++ b/BTCPayServer.Plugins.IntegrationTests/BTCPayServer.Plugins.IntegrationTests.csproj
@@ -9,6 +9,7 @@
+
@@ -32,6 +33,12 @@
+
+
+ PreserveNewest
+
+
+
StaticWebAssetsEnabled=false
diff --git a/BTCPayServer.Plugins.IntegrationTests/Monero/IntegrationTestUtils.cs b/BTCPayServer.Plugins.IntegrationTests/Monero/IntegrationTestUtils.cs
index d51aab2..27e67d2 100644
--- a/BTCPayServer.Plugins.IntegrationTests/Monero/IntegrationTestUtils.cs
+++ b/BTCPayServer.Plugins.IntegrationTests/Monero/IntegrationTestUtils.cs
@@ -5,20 +5,26 @@ using BTCPayServer.Tests;
using Microsoft.Extensions.Logging;
+using Mono.Unix.Native;
+
using Npgsql;
+using static Mono.Unix.Native.Syscall;
+
namespace BTCPayServer.Plugins.IntegrationTests.Monero;
public static class IntegrationTestUtils
{
-
private static readonly ILogger Logger = LoggerFactory
.Create(builder => builder.AddConsole())
.CreateLogger("IntegrationTestUtils");
+ private static readonly string ContainerWalletDir =
+ Environment.GetEnvironmentVariable("BTCPAY_XMR_WALLET_DAEMON_WALLETDIR") ?? "/wallet";
+
public static async Task CleanUpAsync(PlaywrightTester playwrightTester)
{
- MoneroRPCProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService();
+ MoneroRpcProvider moneroRpcProvider = playwrightTester.Server.PayTester.GetService();
if (moneroRpcProvider.IsAvailable("XMR"))
{
await moneroRpcProvider.CloseWallet("XMR");
@@ -26,7 +32,7 @@ public static class IntegrationTestUtils
if (playwrightTester.Server.PayTester.InContainer)
{
- moneroRpcProvider.DeleteWallet();
+ DeleteWalletInContainer();
await DropDatabaseAsync(
"btcpayserver",
"Host=postgres;Port=5432;Username=postgres;Database=postgres");
@@ -62,6 +68,101 @@ public static class IntegrationTestUtils
}
}
+ public static async Task CopyWalletFilesToMoneroRpcDirAsync(PlaywrightTester playwrightTester, String walletDir)
+ {
+ Logger.LogInformation("Starting to copy wallet files");
+ if (playwrightTester.Server.PayTester.InContainer)
+ {
+ CopyWalletFilesInContainer(walletDir);
+ }
+ else
+ {
+ await CopyWalletFilesToLocalDocker(walletDir);
+ }
+ }
+
+ private static void CopyWalletFilesInContainer(String walletDir)
+ {
+ try
+ {
+ CopyWalletFile("wallet", walletDir);
+ CopyWalletFile("wallet.keys", walletDir);
+ CopyWalletFile("password", walletDir);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed to copy wallet files to the Monero directory.");
+ }
+ }
+
+ private static void CopyWalletFile(string name, string walletDir)
+ {
+ var resourceWalletDir = Path.Combine(AppContext.BaseDirectory, "Resources", walletDir);
+
+ var src = Path.Combine(resourceWalletDir, name);
+ var dst = Path.Combine(ContainerWalletDir, name);
+
+ if (!File.Exists(src))
+ {
+ return;
+ }
+
+ File.Copy(src, dst, overwrite: true);
+
+ // monero ownership
+ if (chown(dst, 980, 980) == 0)
+ {
+ return;
+ }
+
+ Logger.LogError("chown failed for {File}. errno={Errno}", dst, Stdlib.GetLastError());
+ }
+
+
+ private static async Task CopyWalletFilesToLocalDocker(String walletDir)
+ {
+ try
+ {
+ var fullWalletDir = Path.Combine(AppContext.BaseDirectory, "Resources", walletDir);
+
+ await RunProcessAsync("docker",
+ $"cp \"{Path.Combine(fullWalletDir, "wallet")}\" xmr_wallet:/wallet/wallet");
+
+ await RunProcessAsync("docker",
+ $"cp \"{Path.Combine(fullWalletDir, "wallet.keys")}\" xmr_wallet:/wallet/wallet.keys");
+
+ await RunProcessAsync("docker",
+ $"cp \"{Path.Combine(fullWalletDir, "password")}\" xmr_wallet:/wallet/password");
+
+ await RunProcessAsync("docker",
+ "exec xmr_wallet chown monero:monero /wallet/wallet /wallet/wallet.keys /wallet/password");
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed to copy wallet files to the Monero directory.");
+ }
+ }
+
+ static async Task RunProcessAsync(string fileName, string args)
+ {
+ var psi = new ProcessStartInfo
+ {
+ FileName = fileName,
+ Arguments = args,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false
+ };
+
+ using var process = Process.Start(psi)!;
+ await process.WaitForExitAsync();
+
+ if (process.ExitCode != 0)
+ {
+ throw new Exception(await process.StandardError.ReadToEndAsync());
+ }
+ }
+
private static async Task RemoveWalletFromLocalDocker()
{
try
@@ -101,4 +202,33 @@ public static class IntegrationTestUtils
Logger.LogError(ex, "Wallet cleanup via Docker failed.");
}
}
+
+ private static void DeleteWalletInContainer()
+ {
+ try
+ {
+ var walletFile = Path.Combine(ContainerWalletDir, "wallet");
+ var keysFile = walletFile + ".keys";
+ var passwordFile = Path.Combine(ContainerWalletDir, "password");
+
+ if (File.Exists(walletFile))
+ {
+ File.Delete(walletFile);
+ }
+
+ if (File.Exists(keysFile))
+ {
+ File.Delete(keysFile);
+ }
+
+ if (File.Exists(passwordFile))
+ {
+ File.Delete(passwordFile);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed to delete wallet files in directory {Dir}", ContainerWalletDir);
+ }
+ }
}
\ No newline at end of file
diff --git a/BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs b/BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs
index c42b022..b0c4476 100644
--- a/BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs
+++ b/BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs
@@ -44,7 +44,6 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
await s.Page.Locator("input#PrivateViewKey")
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
- await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
await s.Page.CheckAsync("#Enabled");
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "2");
@@ -119,7 +118,6 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
await s.Page.Locator("input#PrivateViewKey")
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
- await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
var errorText = await s.Page
.Locator("div.validation-summary-errors li")
@@ -136,12 +134,12 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
await using var s = CreatePlaywrightTester();
await s.StartAsync();
- MoneroRPCProvider moneroRpcProvider = s.Server.PayTester.GetService();
+ MoneroRpcProvider moneroRpcProvider = s.Server.PayTester.GetService();
await moneroRpcProvider.WalletRpcClients["XMR"].SendCommandAsync("generate_from_keys", new GenerateFromKeysRequest
{
PrimaryAddress = "43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L",
PrivateViewKey = "1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e",
- WalletFileName = "view_wallet",
+ WalletFileName = "wallet",
Password = ""
});
await moneroRpcProvider.CloseWallet("XMR");
@@ -154,13 +152,49 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
await s.Page.Locator("input#PrivateViewKey")
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
- await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
var errorText = await s.Page
.Locator("div.validation-summary-errors li")
.InnerTextAsync();
Assert.Equal("Could not generate view wallet from keys: Wallet already exists.", errorText);
+ await IntegrationTestUtils.CleanUpAsync(s);
+ }
+
+ [Fact]
+ public async Task ShouldLoadViewWalletOnStartUpIfExists()
+ {
+ await using var s = CreatePlaywrightTester();
+ await IntegrationTestUtils.CopyWalletFilesToMoneroRpcDirAsync(s, "wallet");
+ await s.StartAsync();
+ await s.RegisterNewUser(true);
+ await s.CreateNewStore();
+ await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
+
+ var walletRpcIsAvailable = await s.Page
+ .Locator("li.list-group-item:text('Wallet RPC available: True')")
+ .InnerTextAsync();
+
+ Assert.Contains("Wallet RPC available: True", walletRpcIsAvailable);
+
+ await IntegrationTestUtils.CleanUpAsync(s);
+ }
+
+ [Fact]
+ public async Task ShouldLoadViewWalletWithPasswordOnStartUpIfExists()
+ {
+ await using var s = CreatePlaywrightTester();
+ await IntegrationTestUtils.CopyWalletFilesToMoneroRpcDirAsync(s, "wallet_password");
+ await s.StartAsync();
+ await s.RegisterNewUser(true);
+ await s.CreateNewStore();
+ await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
+
+ var walletRpcIsAvailable = await s.Page
+ .Locator("li.list-group-item:text('Wallet RPC available: True')")
+ .InnerTextAsync();
+
+ Assert.Contains("Wallet RPC available: True", walletRpcIsAvailable);
await IntegrationTestUtils.CleanUpAsync(s);
}
diff --git a/BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet
new file mode 100644
index 0000000..95acb9a
Binary files /dev/null and b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet differ
diff --git a/BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet.keys b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet.keys
new file mode 100644
index 0000000..968b124
Binary files /dev/null and b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet/wallet.keys differ
diff --git a/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/password b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/password
new file mode 100644
index 0000000..f8f0db2
--- /dev/null
+++ b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/password
@@ -0,0 +1 @@
+pass123
\ No newline at end of file
diff --git a/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/wallet b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/wallet
new file mode 100644
index 0000000..acac115
Binary files /dev/null and b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/wallet differ
diff --git a/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/wallet.keys b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/wallet.keys
new file mode 100644
index 0000000..c02fb97
Binary files /dev/null and b/BTCPayServer.Plugins.IntegrationTests/Resources/wallet_password/wallet.keys differ
diff --git a/LICENSE.md b/LICENSE.md
index 9fecbd2..5966ff3 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017-2025 btcpayserver
+Copyright (c) 2017-2026 btcpayserver
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Plugins/Monero/BTCPayServer.Plugins.Monero.json b/Plugins/Monero/BTCPayServer.Plugins.Monero.json
index 7f9e2a5..324f633 100644
--- a/Plugins/Monero/BTCPayServer.Plugins.Monero.json
+++ b/Plugins/Monero/BTCPayServer.Plugins.Monero.json
@@ -1,7 +1,7 @@
{
"Identifier": "BTCPayServer.Plugins.Monero",
"Name": "BTCPay Server: Monero support plugin",
- "Version": "1.0.1.0",
+ "Version": "1.1.0",
"Description": "This plugin extends BTCPay Server to enable users to receive payments via Monero.",
"SystemPlugin": false,
"Dependencies": [
diff --git a/Plugins/Monero/Controllers/MoneroLikeStoreController.cs b/Plugins/Monero/Controllers/MoneroLikeStoreController.cs
index ffdd1ae..ab99996 100644
--- a/Plugins/Monero/Controllers/MoneroLikeStoreController.cs
+++ b/Plugins/Monero/Controllers/MoneroLikeStoreController.cs
@@ -33,12 +33,12 @@ namespace BTCPayServer.Plugins.Monero.Controllers
{
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
private readonly StoreRepository _StoreRepository;
- private readonly MoneroRPCProvider _MoneroRpcProvider;
+ private readonly MoneroRpcProvider _MoneroRpcProvider;
private readonly PaymentMethodHandlerDictionary _handlers;
private IStringLocalizer StringLocalizer { get; }
public UIMoneroLikeStoreController(MoneroLikeConfiguration moneroLikeConfiguration,
- StoreRepository storeRepository, MoneroRPCProvider moneroRpcProvider,
+ StoreRepository storeRepository, MoneroRpcProvider moneroRpcProvider,
PaymentMethodHandlerDictionary handlers,
IStringLocalizer stringLocalizer)
{
@@ -219,9 +219,8 @@ namespace BTCPayServer.Plugins.Monero.Controllers
{
PrimaryAddress = viewModel.PrimaryAddress,
PrivateViewKey = viewModel.PrivateViewKey,
- WalletFileName = "view_wallet",
- RestoreHeight = viewModel.RestoreHeight,
- Password = viewModel.WalletPassword
+ WalletFileName = "wallet",
+ RestoreHeight = viewModel.RestoreHeight
});
if (response?.Error != null)
{
@@ -286,7 +285,7 @@ namespace BTCPayServer.Plugins.Monero.Controllers
public class MoneroLikePaymentMethodViewModel : IValidatableObject
{
- public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
+ public MoneroRpcProvider.MoneroLikeSummary Summary { get; set; }
public string CryptoCode { get; set; }
public string NewAccountLabel { get; set; }
public long AccountIndex { get; set; }
@@ -300,8 +299,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
public string PrivateViewKey { get; set; }
[Display(Name = "Restore Height")]
public int RestoreHeight { get; set; }
- [Display(Name = "Wallet Password")]
- public string WalletPassword { get; set; }
[Display(Name = "Consider the invoice settled when the payment transaction …")]
public MoneroLikeSettlementThresholdChoice SettlementConfirmationThresholdChoice { get; set; }
[Display(Name = "Required Confirmations"), Range(0, 100)]
diff --git a/Plugins/Monero/MoneroPlugin.cs b/Plugins/Monero/MoneroPlugin.cs
index cabf09d..73b38ec 100644
--- a/Plugins/Monero/MoneroPlugin.cs
+++ b/Plugins/Monero/MoneroPlugin.cs
@@ -75,9 +75,10 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
PreAuthenticate = true
};
});
- services.AddSingleton();
+ services.AddSingleton();
services.AddHostedService();
services.AddHostedService();
+ services.AddHostedService();
services.AddSingleton(provider =>
(IPaymentMethodHandler)ActivatorUtilities.CreateInstance(provider, typeof(MoneroLikePaymentMethodHandler), network));
services.AddSingleton(provider =>
diff --git a/Plugins/Monero/Payments/MoneroLikePaymentMethodHandler.cs b/Plugins/Monero/Payments/MoneroLikePaymentMethodHandler.cs
index 19d7af6..774894d 100644
--- a/Plugins/Monero/Payments/MoneroLikePaymentMethodHandler.cs
+++ b/Plugins/Monero/Payments/MoneroLikePaymentMethodHandler.cs
@@ -17,11 +17,11 @@ namespace BTCPayServer.Plugins.Monero.Payments
private readonly MoneroLikeSpecificBtcPayNetwork _network;
public MoneroLikeSpecificBtcPayNetwork Network => _network;
public JsonSerializer Serializer { get; }
- private readonly MoneroRPCProvider _moneroRpcProvider;
+ private readonly MoneroRpcProvider _moneroRpcProvider;
public PaymentMethodId PaymentMethodId { get; }
- public MoneroLikePaymentMethodHandler(MoneroLikeSpecificBtcPayNetwork network, MoneroRPCProvider moneroRpcProvider)
+ public MoneroLikePaymentMethodHandler(MoneroLikeSpecificBtcPayNetwork network, MoneroRpcProvider moneroRpcProvider)
{
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
_network = network;
diff --git a/Plugins/Monero/RPC/Models/OpenWalletRequest.cs b/Plugins/Monero/RPC/Models/OpenWalletRequest.cs
new file mode 100644
index 0000000..15f598b
--- /dev/null
+++ b/Plugins/Monero/RPC/Models/OpenWalletRequest.cs
@@ -0,0 +1,10 @@
+using Newtonsoft.Json;
+
+namespace BTCPayServer.Plugins.Monero.RPC.Models
+{
+ public class OpenWalletRequest
+ {
+ [JsonProperty("filename")] public string Filename { get; set; }
+ [JsonProperty("password")] public string Password { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Monero/RPC/Models/OpenWalletResponse.cs b/Plugins/Monero/RPC/Models/OpenWalletResponse.cs
new file mode 100644
index 0000000..e04578b
--- /dev/null
+++ b/Plugins/Monero/RPC/Models/OpenWalletResponse.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+
+namespace BTCPayServer.Plugins.Monero.RPC.Models
+{
+ public class OpenWalletResponse
+ {
+ [JsonProperty("id")] public string Id { get; set; }
+ [JsonProperty("jsonrpc")] public string Jsonrpc { get; set; }
+ [JsonProperty("result")] public object Result { get; set; }
+ [JsonProperty("error")] public ErrorResponse Error { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs b/Plugins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs
index 899c029..19ddb25 100644
--- a/Plugins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs
+++ b/Plugins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs
@@ -12,13 +12,13 @@ namespace BTCPayServer.Plugins.Monero.Services
{
public class MoneroLikeSummaryUpdaterHostedService : IHostedService
{
- private readonly MoneroRPCProvider _MoneroRpcProvider;
+ private readonly MoneroRpcProvider _MoneroRpcProvider;
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
public Logs Logs { get; }
private CancellationTokenSource _Cts;
- public MoneroLikeSummaryUpdaterHostedService(MoneroRPCProvider moneroRpcProvider, MoneroLikeConfiguration moneroLikeConfiguration, Logs logs)
+ public MoneroLikeSummaryUpdaterHostedService(MoneroRpcProvider moneroRpcProvider, MoneroLikeConfiguration moneroLikeConfiguration, Logs logs)
{
_MoneroRpcProvider = moneroRpcProvider;
_moneroLikeConfiguration = moneroLikeConfiguration;
diff --git a/Plugins/Monero/Services/MoneroListener.cs b/Plugins/Monero/Services/MoneroListener.cs
index 3380f7b..545ea25 100644
--- a/Plugins/Monero/Services/MoneroListener.cs
+++ b/Plugins/Monero/Services/MoneroListener.cs
@@ -29,7 +29,7 @@ namespace BTCPayServer.Plugins.Monero.Services
{
private readonly InvoiceRepository _invoiceRepository;
private readonly EventAggregator _eventAggregator;
- private readonly MoneroRPCProvider _moneroRpcProvider;
+ private readonly MoneroRpcProvider _moneroRpcProvider;
private readonly MoneroLikeConfiguration _MoneroLikeConfiguration;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly ILogger _logger;
@@ -39,7 +39,7 @@ namespace BTCPayServer.Plugins.Monero.Services
public MoneroListener(InvoiceRepository invoiceRepository,
EventAggregator eventAggregator,
- MoneroRPCProvider moneroRpcProvider,
+ MoneroRpcProvider moneroRpcProvider,
MoneroLikeConfiguration moneroLikeConfiguration,
BTCPayNetworkProvider networkProvider,
ILogger logger,
@@ -62,12 +62,12 @@ namespace BTCPayServer.Plugins.Monero.Services
{
base.SubscribeToEvents();
Subscribe();
- Subscribe();
+ Subscribe();
}
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{
- if (evt is MoneroRPCProvider.MoneroDaemonStateChange stateChange)
+ if (evt is MoneroRpcProvider.MoneroDaemonStateChange stateChange)
{
if (_moneroRpcProvider.IsAvailable(stateChange.CryptoCode))
{
diff --git a/Plugins/Monero/Services/MoneroLoadUpService.cs b/Plugins/Monero/Services/MoneroLoadUpService.cs
new file mode 100644
index 0000000..1327cf9
--- /dev/null
+++ b/Plugins/Monero/Services/MoneroLoadUpService.cs
@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using BTCPayServer.Plugins.Monero.RPC.Models;
+
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace BTCPayServer.Plugins.Monero.Services;
+
+public class MoneroLoadUpService : IHostedService
+{
+ private const string CryptoCode = "XMR";
+ private readonly ILogger _logger;
+ private readonly MoneroRpcProvider _moneroRpcProvider;
+
+ public MoneroLoadUpService(ILogger logger, MoneroRpcProvider moneroRpcProvider)
+ {
+ _moneroRpcProvider = moneroRpcProvider;
+ _logger = logger;
+ }
+
+ [Obsolete("Remove optional password parameter")]
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("Attempt to load existing wallet");
+
+ string walletDir = _moneroRpcProvider.GetWalletDirectory(CryptoCode);
+ if (!string.IsNullOrEmpty(walletDir))
+ {
+ string password = await TryToGetPassword(walletDir, cancellationToken);
+
+ await _moneroRpcProvider.WalletRpcClients[CryptoCode]
+ .SendCommandAsync("open_wallet",
+ new OpenWalletRequest { Filename = "wallet", Password = password }, cancellationToken);
+
+ await _moneroRpcProvider.UpdateSummary(CryptoCode);
+ _logger.LogInformation("Existing wallet successfully loaded");
+ }
+ else
+ {
+ _logger.LogInformation("No wallet directory configured, skipping wallet migration");
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError("Failed to load {CryptoCode} wallet. Error Message: {ErrorMessage}", CryptoCode,
+ ex.Message);
+ }
+ }
+
+ [Obsolete("Password is obsolete due to the inability to fully separate the password file from the wallet file.")]
+ private async Task TryToGetPassword(string walletDir, CancellationToken cancellationToken)
+ {
+ string password = "";
+ string passwordFile = Path.Combine(walletDir, "password");
+ if (File.Exists(passwordFile))
+ {
+ password = await File.ReadAllTextAsync(passwordFile, cancellationToken);
+ password = password.Trim();
+ }
+ else
+ {
+ _logger.LogInformation("No password file found - ignoring");
+ }
+
+ return password;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Monero/Services/MoneroRPCProvider.cs b/Plugins/Monero/Services/MoneroRpcProvider.cs
similarity index 70%
rename from Plugins/Monero/Services/MoneroRPCProvider.cs
rename to Plugins/Monero/Services/MoneroRpcProvider.cs
index 3d19381..4ccdad8 100644
--- a/Plugins/Monero/Services/MoneroRPCProvider.cs
+++ b/Plugins/Monero/Services/MoneroRpcProvider.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
-using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
@@ -9,32 +8,25 @@ using BTCPayServer.Plugins.Monero.Configuration;
using BTCPayServer.Plugins.Monero.RPC;
using BTCPayServer.Plugins.Monero.RPC.Models;
-using Microsoft.Extensions.Logging;
-
using NBitcoin;
namespace BTCPayServer.Plugins.Monero.Services
{
- public class MoneroRPCProvider
+ public class MoneroRpcProvider
{
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
private readonly EventAggregator _eventAggregator;
public ImmutableDictionary DaemonRpcClients;
public ImmutableDictionary WalletRpcClients;
- private readonly ILogger _logger;
- private readonly ConcurrentDictionary _summaries = new();
+ public ConcurrentDictionary Summaries { get; } = new();
- public ConcurrentDictionary Summaries => _summaries;
-
- public MoneroRPCProvider(MoneroLikeConfiguration moneroLikeConfiguration,
+ public MoneroRpcProvider(MoneroLikeConfiguration moneroLikeConfiguration,
EventAggregator eventAggregator,
- ILogger logger,
IHttpClientFactory httpClientFactory)
{
_moneroLikeConfiguration = moneroLikeConfiguration;
_eventAggregator = eventAggregator;
- _logger = logger;
DaemonRpcClients =
_moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
pair => new JsonRpcClient(pair.Value.DaemonRpcUri, pair.Value.Username, pair.Value.Password,
@@ -49,7 +41,7 @@ namespace BTCPayServer.Plugins.Monero.Services
public bool IsAvailable(string cryptoCode)
{
cryptoCode = cryptoCode.ToUpperInvariant();
- return _summaries.ContainsKey(cryptoCode) && IsAvailable(_summaries[cryptoCode]);
+ return Summaries.ContainsKey(cryptoCode) && IsAvailable(Summaries[cryptoCode]);
}
private bool IsAvailable(MoneroLikeSummary summary)
@@ -69,43 +61,12 @@ namespace BTCPayServer.Plugins.Monero.Services
"close_wallet", JsonRpcClient.NoRequestModel.Instance);
}
- public void DeleteWallet()
+ public string GetWalletDirectory(string cryptoCode)
{
- if (!_moneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue("XMR", out var configItem))
- {
- _logger.LogWarning("DeleteWallet: No XMR configuration found.");
- return;
- }
-
- if (string.IsNullOrEmpty(configItem.WalletDirectory))
- {
- _logger.LogWarning("DeleteWallet: WalletDirectory is null or empty for XMR configuration.");
- return;
- }
- try
- {
- var walletFile = Path.Combine(configItem.WalletDirectory, "view_wallet");
- var keysFile = walletFile + ".keys";
- var passwordFile = Path.Combine(configItem.WalletDirectory, "password");
-
- if (File.Exists(walletFile))
- {
- File.Delete(walletFile);
- }
- if (File.Exists(keysFile))
- {
- File.Delete(keysFile);
- }
- if (File.Exists(passwordFile))
- {
- File.Delete(passwordFile);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Failed to delete wallet files in directory {Dir}",
- configItem.WalletDirectory);
- }
+ cryptoCode = cryptoCode.ToUpperInvariant();
+ return !_moneroLikeConfiguration.MoneroLikeConfigurationItems.TryGetValue(cryptoCode, out var configItem)
+ ? null
+ : configItem.WalletDirectory;
}
public async Task UpdateSummary(string cryptoCode)
@@ -146,9 +107,9 @@ namespace BTCPayServer.Plugins.Monero.Services
summary.WalletAvailable = false;
}
- var changed = !_summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
+ var changed = !Summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
- _summaries.AddOrReplace(cryptoCode, summary);
+ Summaries.AddOrReplace(cryptoCode, summary);
if (changed)
{
_eventAggregator.Publish(new MoneroDaemonStateChange() { Summary = summary, CryptoCode = cryptoCode });
diff --git a/Plugins/Monero/Services/MoneroSyncSummaryProvider.cs b/Plugins/Monero/Services/MoneroSyncSummaryProvider.cs
index 83cccc3..7748706 100644
--- a/Plugins/Monero/Services/MoneroSyncSummaryProvider.cs
+++ b/Plugins/Monero/Services/MoneroSyncSummaryProvider.cs
@@ -9,9 +9,9 @@ namespace BTCPayServer.Plugins.Monero.Services
{
public class MoneroSyncSummaryProvider : ISyncSummaryProvider
{
- private readonly MoneroRPCProvider _moneroRpcProvider;
+ private readonly MoneroRpcProvider _moneroRpcProvider;
- public MoneroSyncSummaryProvider(MoneroRPCProvider moneroRpcProvider)
+ public MoneroSyncSummaryProvider(MoneroRpcProvider moneroRpcProvider)
{
_moneroRpcProvider = moneroRpcProvider;
}
@@ -42,6 +42,6 @@ namespace BTCPayServer.Plugins.Monero.Services
}
}
- public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
+ public MoneroRpcProvider.MoneroLikeSummary Summary { get; set; }
}
}
\ No newline at end of file
diff --git a/Plugins/Monero/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml b/Plugins/Monero/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml
index 81444ef..b3ee472 100644
--- a/Plugins/Monero/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml
+++ b/Plugins/Monero/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml
@@ -52,11 +52,6 @@
-
-
-
-
-
diff --git a/Plugins/Monero/Views/Monero/MoneroSyncSummary.cshtml b/Plugins/Monero/Views/Monero/MoneroSyncSummary.cshtml
index 37052ef..1a8769c 100644
--- a/Plugins/Monero/Views/Monero/MoneroSyncSummary.cshtml
+++ b/Plugins/Monero/Views/Monero/MoneroSyncSummary.cshtml
@@ -1,8 +1,5 @@
-@using BTCPayServer
-@using BTCPayServer.Data
@using BTCPayServer.Plugins.Monero.Services
-@using Microsoft.AspNetCore.Identity
-@inject MoneroRPCProvider MoneroRpcProvider
+@inject MoneroRpcProvider MoneroRpcProvider
@inject SignInManager SignInManager;
@if (SignInManager.IsSignedIn(User) && User.IsInRole(Roles.ServerAdmin) && MoneroRpcProvider.Summaries.Any())
diff --git a/SECURITY.md b/SECURITY.md
index b06b5c7..377c596 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,10 +4,10 @@
We currently support the following versions of the Monero plugin for BTCPayServer:
-| Version | Supported |
-| ------- | ------------------ |
-| 2.x | ✅ Yes |
-| 1.x | ❌ No |
+| Version | Supported |
+|---------|-------------|
+| 1.1.x | ✅ Yes |
+| 1.0.x | ❌ No |
## Reporting a Vulnerability
diff --git a/submodules/btcpayserver b/submodules/btcpayserver
index 335c906..3f52aa7 160000
--- a/submodules/btcpayserver
+++ b/submodules/btcpayserver
@@ -1 +1 @@
-Subproject commit 335c906b811b8f131a5973ef3e9cdcac99017654
+Subproject commit 3f52aa7fd9faf8bb99343572b3dae305037db2ba