Remove cash cow with cheat code and replace upload wallet with private view key
This commit is contained in:
parent
73e8905166
commit
36a274fcfa
22 changed files with 112 additions and 349 deletions
2
.github/workflows/codacy.yml
vendored
2
.github/workflows/codacy.yml
vendored
|
|
@ -13,7 +13,7 @@ permissions:
|
|||
|
||||
jobs:
|
||||
report-coverage:
|
||||
if: github.repository == 'btcpay-monero/btcpayserver-monero-plugin' && github.event.workflow_run.conclusion == 'success'
|
||||
if: github.repository == 'btcpay-monero/btcpayserver-monero-plugin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download coverage report
|
||||
|
|
|
|||
2
.github/workflows/dotnet.yml
vendored
2
.github/workflows/dotnet.yml
vendored
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
dotnet tool install --global JetBrains.dotCover.CommandLineTools
|
||||
dotnet tool install --global JetBrains.dotCover.CommandLineTools --version 2025.1.6
|
||||
dotCover cover-dotnet --TargetArguments="test BTCPayServer.Plugins.UnitTests -c Release --no-build" --output=coverage/dotCover.UnitTests.output.dcvr --filters="-:Assembly=BTCPayServer.Plugins.UnitTests;-:Assembly=testhost;-:Assembly=BTCPayServer;-:Class=AspNetCoreGeneratedDocument.*"
|
||||
|
||||
- name: Run integration tests
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ RUN mkdir -p ${MONERO_PLUGIN_FOLDER}
|
|||
RUN cd Plugins/Monero && dotnet build BTCPayServer.Plugins.Monero.sln --configuration ${CONFIGURATION_NAME} /p:RazorCompileOnBuild=true --output ${MONERO_PLUGIN_FOLDER}
|
||||
RUN cd BTCPayServer.Plugins.IntegrationTests && dotnet build --configuration ${CONFIGURATION_NAME} /p:CI_TESTS=true /p:RazorCompileOnBuild=true
|
||||
RUN dotnet tool install --global Microsoft.Playwright.CLI
|
||||
RUN dotnet tool install --global JetBrains.DotCover.CommandLineTools
|
||||
RUN dotnet tool install --global JetBrains.DotCover.CommandLineTools --version 2025.1.6
|
||||
ENV PATH="$PATH:/root/.dotnet/tools"
|
||||
RUN playwright install chromium --with-deps
|
||||
WORKDIR /source/BTCPayServer.Plugins.IntegrationTests
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBi
|
|||
await s.RegisterNewUser(true);
|
||||
await s.CreateNewStore(preferredExchange: "Kraken");
|
||||
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
|
||||
await s.Page.Locator("input#PrimaryAddress").FillAsync("43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L");
|
||||
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");
|
||||
await s.Page.ClickAsync("#SaveButton");
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ services:
|
|||
- nbxplorer
|
||||
- postgres
|
||||
- xmr_wallet
|
||||
- xmr_cashcow_wallet
|
||||
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.5.25
|
||||
|
|
@ -90,36 +89,24 @@ services:
|
|||
- "bitcoin_datadir:/data"
|
||||
|
||||
monerod:
|
||||
image: btcpayserver/monero:0.18.4.0
|
||||
image: btcpayserver/monero:0.18.4.2
|
||||
restart: unless-stopped
|
||||
container_name: monerod
|
||||
entrypoint: monerod --fixed-difficulty 1 --log-level=2 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --regtest --no-igd --hide-my-port --offline --non-interactive
|
||||
command: monerod --fixed-difficulty 1 --log-level=2 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --regtest --no-igd --hide-my-port --offline --non-interactive
|
||||
volumes:
|
||||
- "xmr_data:/home/monero/.bitmonero"
|
||||
- xmr_data:/data
|
||||
ports:
|
||||
- "18081:18081"
|
||||
|
||||
xmr_wallet:
|
||||
image: btcpayserver/monero:0.18.4.0
|
||||
image: btcpayserver/monero:0.18.4.2
|
||||
restart: unless-stopped
|
||||
container_name: xmr_wallet
|
||||
entrypoint: monero-wallet-rpc --log-level 2 --allow-mismatched-daemon-version --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-dir=/wallet --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
|
||||
command: monero-wallet-rpc --log-level 2 --allow-mismatched-daemon-version --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-dir=/wallet --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
|
||||
ports:
|
||||
- "18082:18082"
|
||||
volumes:
|
||||
- "xmr_wallet:/wallet"
|
||||
depends_on:
|
||||
- monerod
|
||||
|
||||
xmr_cashcow_wallet:
|
||||
image: btcpayserver/monero:0.18.4.0
|
||||
restart: unless-stopped
|
||||
container_name: xmr_cashcow_wallet
|
||||
entrypoint: monero-wallet-rpc --log-level 2 --allow-mismatched-daemon-version --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18092 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-dir=/wallet
|
||||
ports:
|
||||
- "18092:18092"
|
||||
volumes:
|
||||
- "xmr_cashcow_wallet:/wallet"
|
||||
- xmr_wallet:/wallet
|
||||
depends_on:
|
||||
- monerod
|
||||
|
||||
|
|
@ -137,7 +124,6 @@ volumes:
|
|||
bitcoin_datadir:
|
||||
xmr_data:
|
||||
xmr_wallet:
|
||||
xmr_cashcow_wallet:
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ namespace BTCPayServer.Plugins.UnitTests.Monero.Configuration
|
|||
InternalWalletRpcUri = new Uri("http://localhost:18082"),
|
||||
WalletDirectory = "/wallets",
|
||||
Username = "user",
|
||||
Password = "password",
|
||||
CashCowWalletRpcUri = new Uri("http://localhost:18083")
|
||||
Password = "password"
|
||||
};
|
||||
|
||||
Assert.Equal("http://localhost:18081/", configItem.DaemonRpcUri.ToString());
|
||||
|
|
@ -35,7 +34,6 @@ namespace BTCPayServer.Plugins.UnitTests.Monero.Configuration
|
|||
Assert.Equal("/wallets", configItem.WalletDirectory);
|
||||
Assert.Equal("user", configItem.Username);
|
||||
Assert.Equal("password", configItem.Password);
|
||||
Assert.Equal("http://localhost:18083/", configItem.CashCowWalletRpcUri.ToString());
|
||||
}
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,5 @@ namespace BTCPayServer.Plugins.Monero.Configuration
|
|||
public string WalletDirectory { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public Uri CashCowWalletRpcUri { get; set; }
|
||||
}
|
||||
}
|
||||
5
Plugins/Monero/Controllers/GenerateFromKeysException.cs
Normal file
5
Plugins/Monero/Controllers/GenerateFromKeysException.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using System;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.Controllers;
|
||||
|
||||
public class GenerateFromKeysException(string message) : Exception(message);
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -21,7 +19,6 @@ using BTCPayServer.Services.Invoices;
|
|||
using BTCPayServer.Services.Stores;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
|
@ -183,22 +180,22 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
}
|
||||
|
||||
}
|
||||
else if (command == "upload-wallet")
|
||||
else if (command == "set-wallet-details")
|
||||
{
|
||||
var valid = true;
|
||||
if (viewModel.WalletFile == null)
|
||||
if (viewModel.PrimaryAddress == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["Please select the view-only wallet file"]);
|
||||
ModelState.AddModelError(nameof(viewModel.PrimaryAddress), StringLocalizer["Please set your primary public address"]);
|
||||
valid = false;
|
||||
}
|
||||
if (viewModel.WalletKeysFile == null)
|
||||
if (viewModel.PrivateViewKey == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the view-only wallet keys file"]);
|
||||
ModelState.AddModelError(nameof(viewModel.PrivateViewKey), StringLocalizer["Please set your private view key"]);
|
||||
valid = false;
|
||||
}
|
||||
if (configurationItem.WalletDirectory == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["This installation doesn't support wallet import (BTCPAY_XMR_WALLET_DAEMON_WALLETDIR is not set)"]);
|
||||
ModelState.AddModelError(nameof(viewModel.PrimaryAddress), StringLocalizer["This installation doesn't support wallet creation (BTCPAY_XMR_WALLET_DAEMON_WALLETDIR is not set)"]);
|
||||
valid = false;
|
||||
}
|
||||
if (valid)
|
||||
|
|
@ -216,71 +213,31 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
new { cryptoCode });
|
||||
}
|
||||
}
|
||||
|
||||
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
|
||||
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
|
||||
{
|
||||
await viewModel.WalletFile.CopyToAsync(fileStream);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet.keys");
|
||||
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
|
||||
{
|
||||
await viewModel.WalletKeysFile.CopyToAsync(fileStream);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
fileAddress = Path.Combine(configurationItem.WalletDirectory, "password");
|
||||
using (var fileStream = new StreamWriter(fileAddress, false))
|
||||
{
|
||||
await fileStream.WriteAsync(viewModel.WalletPassword);
|
||||
try
|
||||
{
|
||||
Exec($"chmod 666 {fileAddress}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<OpenWalletRequest, OpenWalletResponse>("open_wallet", new OpenWalletRequest
|
||||
var response = await _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<GenerateFromKeysRequest, GenerateFromKeysResponse>("generate_from_keys", new GenerateFromKeysRequest
|
||||
{
|
||||
Filename = "wallet",
|
||||
PrimaryAddress = viewModel.PrimaryAddress,
|
||||
PrivateViewKey = viewModel.PrivateViewKey,
|
||||
WalletFileName = "view_wallet",
|
||||
RestoreHeight = viewModel.RestoreHeight,
|
||||
Password = viewModel.WalletPassword
|
||||
});
|
||||
if (response?.Error != null)
|
||||
{
|
||||
throw new WalletOpenException(response.Error.Message);
|
||||
throw new GenerateFromKeysException(response.Error.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not open the wallet: {0}", ex.Message]);
|
||||
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not generate view wallet from keys: {0}", ex.Message]);
|
||||
return View("/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml", viewModel);
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Info,
|
||||
Message = StringLocalizer["View-only wallet files uploaded. The wallet will soon become available."].Value
|
||||
Message = StringLocalizer["View-only wallet created. The wallet will soon become available."].Value
|
||||
});
|
||||
return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod), new { cryptoCode });
|
||||
}
|
||||
|
|
@ -297,7 +254,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
vm.AccountIndex = viewModel.AccountIndex;
|
||||
vm.SettlementConfirmationThresholdChoice = viewModel.SettlementConfirmationThresholdChoice;
|
||||
vm.CustomSettlementConfirmationThreshold = viewModel.CustomSettlementConfirmationThreshold;
|
||||
vm.SupportWalletExport = configurationItem.WalletDirectory is not null;
|
||||
return View("/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml", vm);
|
||||
}
|
||||
|
||||
|
|
@ -323,30 +279,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
new { StatusMessage = $"{cryptoCode} settings updated successfully", storeId = StoreData.Id });
|
||||
}
|
||||
|
||||
private void Exec(string cmd)
|
||||
{
|
||||
|
||||
var escapedArgs = cmd.Replace("\"", "\\\"", StringComparison.InvariantCulture);
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
FileName = "/bin/sh",
|
||||
Arguments = $"-c \"{escapedArgs}\""
|
||||
}
|
||||
};
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
process.Start();
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
process.WaitForExit();
|
||||
}
|
||||
|
||||
public class MoneroLikePaymentMethodListViewModel
|
||||
{
|
||||
public IEnumerable<MoneroLikePaymentMethodViewModel> Items { get; set; }
|
||||
|
|
@ -355,7 +287,6 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
public class MoneroLikePaymentMethodViewModel : IValidatableObject
|
||||
{
|
||||
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
|
||||
public bool SupportWalletExport { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string NewAccountLabel { get; set; }
|
||||
public long AccountIndex { get; set; }
|
||||
|
|
@ -363,10 +294,12 @@ namespace BTCPayServer.Plugins.Monero.Controllers
|
|||
|
||||
public IEnumerable<SelectListItem> Accounts { get; set; }
|
||||
public bool WalletFileFound { get; set; }
|
||||
[Display(Name = "View-Only Wallet File")]
|
||||
public IFormFile WalletFile { get; set; }
|
||||
[Display(Name = "Wallet Keys File")]
|
||||
public IFormFile WalletKeysFile { get; set; }
|
||||
[Display(Name = "Primary Public Address")]
|
||||
public string PrimaryAddress { get; set; }
|
||||
[Display(Name = "Private View Key")]
|
||||
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 …")]
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.Controllers;
|
||||
|
||||
public class WalletOpenException(string message) : Exception(message);
|
||||
|
|
@ -78,15 +78,12 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
|
|||
services.AddSingleton<MoneroRPCProvider>();
|
||||
services.AddHostedService<MoneroLikeSummaryUpdaterHostedService>();
|
||||
services.AddHostedService<MoneroListener>();
|
||||
services.AddSingleton<IPaymentMethodHandler>(provider =>
|
||||
(IPaymentMethodHandler)ActivatorUtilities.CreateInstance(provider, typeof(MoneroLikePaymentMethodHandler), new object[] { network }));
|
||||
services.AddSingleton<IPaymentLinkExtension>(provider =>
|
||||
(IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroPaymentLinkExtension), new object[] { network, pmi }));
|
||||
services.AddSingleton<ICheckoutModelExtension>(provider =>
|
||||
(ICheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroCheckoutModelExtension), new object[] { network, pmi }));
|
||||
|
||||
services.AddSingleton<ICheckoutCheatModeExtension>(provider =>
|
||||
(ICheckoutCheatModeExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroCheckoutCheatModeExtension), new object[] { network, pmi }));
|
||||
services.AddSingleton(provider =>
|
||||
(IPaymentMethodHandler)ActivatorUtilities.CreateInstance(provider, typeof(MoneroLikePaymentMethodHandler), network));
|
||||
services.AddSingleton(provider =>
|
||||
(IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroPaymentLinkExtension), network, pmi));
|
||||
services.AddSingleton(provider =>
|
||||
(ICheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(MoneroCheckoutModelExtension), network, pmi));
|
||||
|
||||
services.AddUIExtension("store-nav", "/Views/Monero/StoreNavMoneroExtension.cshtml");
|
||||
services.AddUIExtension("store-wallets-nav", "/Views/Monero/StoreWalletsNavMoneroExtension.cshtml");
|
||||
|
|
@ -126,9 +123,6 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
|
|||
var walletDaemonUri =
|
||||
configuration.GetOrDefault<Uri>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_uri", null);
|
||||
var cashCowWalletDaemonUri =
|
||||
configuration.GetOrDefault<Uri>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_cashcow_wallet_daemon_uri", null);
|
||||
var walletDaemonWalletDirectory =
|
||||
configuration.GetOrDefault<string>(
|
||||
$"{moneroLikeSpecificBtcPayNetwork.CryptoCode}_wallet_daemon_walletdir", null);
|
||||
|
|
@ -154,14 +148,13 @@ public class MoneroPlugin : BaseBTCPayServerPlugin
|
|||
}
|
||||
else
|
||||
{
|
||||
result.MoneroLikeConfigurationItems.Add(moneroLikeSpecificBtcPayNetwork.CryptoCode, new MoneroLikeConfigurationItem()
|
||||
result.MoneroLikeConfigurationItems.Add(moneroLikeSpecificBtcPayNetwork.CryptoCode, new MoneroLikeConfigurationItem
|
||||
{
|
||||
DaemonRpcUri = daemonUri,
|
||||
Username = daemonUsername,
|
||||
Password = daemonPassword,
|
||||
InternalWalletRpcUri = walletDaemonUri,
|
||||
WalletDirectory = walletDaemonWalletDirectory,
|
||||
CashCowWalletRpcUri = cashCowWalletDaemonUri,
|
||||
WalletDirectory = walletDaemonWalletDirectory
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ using Newtonsoft.Json;
|
|||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models
|
||||
{
|
||||
public partial class OpenWalletErrorResponse
|
||||
public class ErrorResponse
|
||||
{
|
||||
[JsonProperty("code")] public int Code { get; set; }
|
||||
[JsonProperty("message")] public string Message { get; set; }
|
||||
12
Plugins/Monero/RPC/Models/GenerateFromKeysRequest.cs
Normal file
12
Plugins/Monero/RPC/Models/GenerateFromKeysRequest.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
|
||||
public class GenerateFromKeysRequest
|
||||
{
|
||||
[JsonProperty("address")] public string PrimaryAddress { get; set; }
|
||||
[JsonProperty("viewkey")] public string PrivateViewKey { get; set; }
|
||||
[JsonProperty("filename")] public string WalletFileName { get; set; }
|
||||
[JsonProperty("restore_height")] public int RestoreHeight { get; set; }
|
||||
[JsonProperty("password")] public string Password { get; set; }
|
||||
}
|
||||
11
Plugins/Monero/RPC/Models/GenerateFromKeysResponse.cs
Normal file
11
Plugins/Monero/RPC/Models/GenerateFromKeysResponse.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
|
||||
public class GenerateFromKeysResponse
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("jsonrpc")] public string Jsonrpc { get; set; }
|
||||
[JsonProperty("result")] public GenerateFromKeysResult Result { get; set; }
|
||||
[JsonProperty("error")] public ErrorResponse Error { get; set; }
|
||||
}
|
||||
9
Plugins/Monero/RPC/Models/GenerateFromKeysResult.cs
Normal file
9
Plugins/Monero/RPC/Models/GenerateFromKeysResult.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
|
||||
public class GenerateFromKeysResult
|
||||
{
|
||||
[JsonProperty("address")] public string ViewWalletAddress { get; set; }
|
||||
[JsonProperty("info")] public string CreationInfo { get; set; }
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models
|
||||
{
|
||||
public partial class OpenWalletRequest
|
||||
{
|
||||
[JsonProperty("filename")] public string Filename { get; set; }
|
||||
[JsonProperty("password")] public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.RPC.Models
|
||||
{
|
||||
public partial 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 OpenWalletErrorResponse Error { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.Monero.RPC;
|
||||
using BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
|
||||
namespace BTCPayServer.Plugins.Monero.Services;
|
||||
|
||||
public class MoneroCheckoutCheatModeExtension : ICheckoutCheatModeExtension
|
||||
{
|
||||
private readonly MoneroRPCProvider _rpcProvider;
|
||||
private readonly MoneroLikeSpecificBtcPayNetwork _network;
|
||||
private readonly PaymentMethodId _paymentMethodId;
|
||||
|
||||
public MoneroCheckoutCheatModeExtension(
|
||||
MoneroRPCProvider rpcProvider,
|
||||
MoneroLikeSpecificBtcPayNetwork network,
|
||||
PaymentMethodId paymentMethodId)
|
||||
{
|
||||
_rpcProvider = rpcProvider;
|
||||
_network = network;
|
||||
_paymentMethodId = paymentMethodId;
|
||||
}
|
||||
|
||||
public bool Handle(PaymentMethodId paymentMethodId) => _paymentMethodId == paymentMethodId;
|
||||
|
||||
public async Task<ICheckoutCheatModeExtension.PayInvoiceResult> PayInvoice(ICheckoutCheatModeExtension.PayInvoiceContext payInvoiceContext)
|
||||
{
|
||||
var amount = payInvoiceContext.Amount;
|
||||
for (int i = 0; i < _network.Divisibility; i++)
|
||||
{
|
||||
amount *= 10;
|
||||
}
|
||||
|
||||
var cashcow = _rpcProvider.CashCowWalletRpcClients[_network.CryptoCode];
|
||||
var result = await cashcow.SendCommandAsync<TransferRequest, TransferResponse>("transfer",
|
||||
new TransferRequest()
|
||||
{
|
||||
Destinations = new[] { new TransferDestination()
|
||||
{
|
||||
Amount = (long)amount,
|
||||
Address = payInvoiceContext.PaymentPrompt.Destination
|
||||
}
|
||||
}
|
||||
});
|
||||
return new ICheckoutCheatModeExtension.PayInvoiceResult(result.TransactionHash);
|
||||
}
|
||||
|
||||
public async Task<ICheckoutCheatModeExtension.MineBlockResult> MineBlock(ICheckoutCheatModeExtension.MineBlockContext mineBlockContext)
|
||||
{
|
||||
var cashcow = _rpcProvider.CashCowWalletRpcClients[_network.CryptoCode];
|
||||
var deamon = _rpcProvider.DaemonRpcClients[_network.CryptoCode];
|
||||
var address = (await cashcow.SendCommandAsync<GetAddressRequest, GetAddressResponse>("get_address", new()
|
||||
{
|
||||
AccountIndex = 0
|
||||
})).Address;
|
||||
await deamon.SendCommandAsync<GenerateBlocks, JsonRpcClient.NoRequestModel>("generateblocks", new GenerateBlocks()
|
||||
{
|
||||
WalletAddress = address,
|
||||
AmountOfBlocks = mineBlockContext.BlockCount
|
||||
});
|
||||
return new ICheckoutCheatModeExtension.MineBlockResult();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using BTCPayServer.Plugins.Monero.Configuration;
|
||||
using BTCPayServer.Plugins.Monero.RPC;
|
||||
using BTCPayServer.Plugins.Monero.RPC.Models;
|
||||
using BTCPayServer.Services;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using NBitcoin;
|
||||
|
||||
|
|
@ -19,9 +15,7 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
public class MoneroRPCProvider
|
||||
{
|
||||
private readonly MoneroLikeConfiguration _moneroLikeConfiguration;
|
||||
private readonly ILogger<MoneroRPCProvider> _logger;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayServerEnvironment environment;
|
||||
public ImmutableDictionary<string, JsonRpcClient> DaemonRpcClients;
|
||||
public ImmutableDictionary<string, JsonRpcClient> WalletRpcClients;
|
||||
|
||||
|
|
@ -30,14 +24,11 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
public ConcurrentDictionary<string, MoneroLikeSummary> Summaries => _summaries;
|
||||
|
||||
public MoneroRPCProvider(MoneroLikeConfiguration moneroLikeConfiguration,
|
||||
ILogger<MoneroRPCProvider> logger,
|
||||
EventAggregator eventAggregator,
|
||||
IHttpClientFactory httpClientFactory, BTCPayServerEnvironment environment)
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_moneroLikeConfiguration = moneroLikeConfiguration;
|
||||
_logger = logger;
|
||||
_eventAggregator = eventAggregator;
|
||||
this.environment = environment;
|
||||
DaemonRpcClients =
|
||||
_moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.DaemonRpcUri, pair.Value.Username, pair.Value.Password,
|
||||
|
|
@ -46,18 +37,8 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
_moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.InternalWalletRpcUri, "", "",
|
||||
httpClientFactory.CreateClient($"{pair.Key}client")));
|
||||
if (environment.CheatMode)
|
||||
{
|
||||
CashCowWalletRpcClients =
|
||||
_moneroLikeConfiguration.MoneroLikeConfigurationItems
|
||||
.Where(i => i.Value.CashCowWalletRpcUri is not null).ToImmutableDictionary(pair => pair.Key,
|
||||
pair => new JsonRpcClient(pair.Value.CashCowWalletRpcUri, "", "",
|
||||
httpClientFactory.CreateClient($"{pair.Key}cashcow-client")));
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableDictionary<string, JsonRpcClient> CashCowWalletRpcClients { get; set; }
|
||||
|
||||
public bool IsConfigured(string cryptoCode) => WalletRpcClients.ContainsKey(cryptoCode) && DaemonRpcClients.ContainsKey(cryptoCode);
|
||||
public bool IsAvailable(string cryptoCode)
|
||||
{
|
||||
|
|
@ -96,9 +77,6 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
{
|
||||
summary.DaemonAvailable = false;
|
||||
}
|
||||
|
||||
bool walletCreated = false;
|
||||
retry:
|
||||
try
|
||||
{
|
||||
var walletResult =
|
||||
|
|
@ -107,23 +85,11 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
summary.WalletHeight = walletResult.Height;
|
||||
summary.WalletAvailable = true;
|
||||
}
|
||||
catch when (environment.CheatMode && !walletCreated)
|
||||
{
|
||||
await CreateTestWallet(walletRpcClient);
|
||||
walletCreated = true;
|
||||
goto retry;
|
||||
}
|
||||
catch
|
||||
{
|
||||
summary.WalletAvailable = false;
|
||||
}
|
||||
|
||||
if (environment.CheatMode &&
|
||||
CashCowWalletRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var cashCow))
|
||||
{
|
||||
await MakeCashCowFat(cashCow, daemonRpcClient);
|
||||
}
|
||||
|
||||
var changed = !_summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary);
|
||||
|
||||
_summaries.AddOrReplace(cryptoCode, summary);
|
||||
|
|
@ -135,68 +101,6 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
return summary;
|
||||
}
|
||||
|
||||
private async Task MakeCashCowFat(JsonRpcClient cashcow, JsonRpcClient deamon)
|
||||
{
|
||||
try
|
||||
{
|
||||
var walletResult =
|
||||
await cashcow.SendCommandAsync<JsonRpcClient.NoRequestModel, GetHeightResponse>(
|
||||
"get_height", JsonRpcClient.NoRequestModel.Instance);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.LogInformation("Creating XMR cashcow wallet...");
|
||||
await CreateTestWallet(cashcow);
|
||||
}
|
||||
|
||||
var balance =
|
||||
(await cashcow.SendCommandAsync<JsonRpcClient.NoRequestModel, GetBalanceResponse>("get_balance",
|
||||
JsonRpcClient.NoRequestModel.Instance));
|
||||
if (balance.UnlockedBalance != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_logger.LogInformation("Mining blocks for the cashcow...");
|
||||
var address = (await cashcow.SendCommandAsync<GetAddressRequest, GetAddressResponse>("get_address", new()
|
||||
{
|
||||
AccountIndex = 0
|
||||
})).Address;
|
||||
await deamon.SendCommandAsync<GenerateBlocks, JsonRpcClient.NoRequestModel>("generateblocks", new GenerateBlocks()
|
||||
{
|
||||
WalletAddress = address,
|
||||
AmountOfBlocks = 100
|
||||
});
|
||||
_logger.LogInformation("Mining succeed!");
|
||||
}
|
||||
|
||||
private static async Task CreateTestWallet(JsonRpcClient walletRpcClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
await walletRpcClient.SendCommandAsync<OpenWalletRequest, JsonRpcClient.NoRequestModel>(
|
||||
"open_wallet",
|
||||
new OpenWalletRequest()
|
||||
{
|
||||
Filename = "wallet",
|
||||
Password = "password"
|
||||
});
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
await walletRpcClient.SendCommandAsync<CreateWalletRequest, JsonRpcClient.NoRequestModel>("create_wallet",
|
||||
new()
|
||||
{
|
||||
Filename = "wallet",
|
||||
Password = "password",
|
||||
Language = "English"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public class MoneroDaemonStateChange
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace BTCPayServer.Plugins.Monero.Services
|
|||
|
||||
public bool AllAvailable()
|
||||
{
|
||||
return _moneroRpcProvider.Summaries.All(pair => pair.Value.WalletAvailable);
|
||||
return _moneroRpcProvider.Summaries.All(pair => pair.Value.DaemonAvailable);
|
||||
}
|
||||
|
||||
public string Partial { get; } = "/Views/Monero/MoneroSyncSummary.cshtml";
|
||||
|
|
|
|||
|
|
@ -20,38 +20,45 @@
|
|||
<div class="card">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Node available: @Model.Summary.DaemonAvailable</li>
|
||||
<li class="list-group-item">Wallet RPC available: @Model.Summary.WalletAvailable</li>
|
||||
<li class="list-group-item">Last updated: @Model.Summary.UpdatedAt</li>
|
||||
<li class="list-group-item">Synced: @Model.Summary.Synced (@Model.Summary.CurrentHeight / @Model.Summary.TargetHeight)</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.SupportWalletExport && Model.Summary?.WalletHeight is null or 0)
|
||||
@if (Model.Summary?.WalletHeight is null or 0)
|
||||
{
|
||||
<form method="post" asp-action="GetStoreMoneroLikePaymentMethod"
|
||||
<form method="post" asp-action="GetStoreMoneroLikePaymentMethod"
|
||||
asp-route-storeId="@Context.GetRouteValue("storeId")"
|
||||
asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")"
|
||||
class="mt-4" enctype="multipart/form-data">
|
||||
class="mt-4">
|
||||
|
||||
<div class="card my-2">
|
||||
<h3 class="card-title p-2">Upload Wallet</h3>
|
||||
<h3 class="card-title p-2">Set View-Only Wallet Details</h3>
|
||||
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletFile" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletFile" required>
|
||||
<span asp-validation-for="WalletFile" class="text-danger"></span>
|
||||
<label asp-for="PrimaryAddress" class="form-label">Wallet Primary Address</label>
|
||||
<input type="text" class="form-control" asp-for="PrimaryAddress" required>
|
||||
<span asp-validation-for="PrimaryAddress" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletKeysFile" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletKeysFile" required>
|
||||
<span asp-validation-for="WalletKeysFile" class="text-danger"></span>
|
||||
<label asp-for="PrivateViewKey" class="form-label">Wallet Private View Key</label>
|
||||
<input type="text" class="form-control" asp-for="PrivateViewKey" required>
|
||||
<span asp-validation-for="PrivateViewKey" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletPassword" class="form-label"></label>
|
||||
<input class="form-control" asp-for="WalletPassword">
|
||||
<label asp-for="RestoreHeight" class="form-label">Restore Height (Block Height)</label>
|
||||
<input type="number" class="form-control" asp-for="RestoreHeight" required>
|
||||
<span asp-validation-for="RestoreHeight" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group p-2">
|
||||
<label asp-for="WalletPassword" class="form-label">Wallet Password</label>
|
||||
<input type="password" class="form-control" asp-for="WalletPassword" required>
|
||||
<span asp-validation-for="WalletPassword" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="card-footer text-right">
|
||||
<button name="command" value="upload-wallet" class="btn btn-secondary" type="submit">Upload</button>
|
||||
<button name="command" value="set-wallet-details" class="btn btn-secondary" type="submit">Set Wallet Details</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -21,13 +21,13 @@ This plugin extends BTCPay Server to enable users to receive payments via Monero
|
|||
|
||||
Configure this plugin using the following environment variables:
|
||||
|
||||
| Environment variable | Description | Example |
|
||||
| --- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
|
||||
**BTCPAY_XMR_DAEMON_URI** | **Required**. The URI of the [monerod](https://github.com/monero-project/monero) RPC interface. | http://127.0.0.1:18081 |
|
||||
**BTCPAY_XMR_DAEMON_USERNAME** | **Optional**. The username for authenticating with the daemon. | john |
|
||||
**BTCPAY_XMR_DAEMON_PASSWORD** | **Optional**. The password for authenticating with the daemon. | secret |
|
||||
**BTCPAY_XMR_WALLET_DAEMON_URI** | **Required**. The URI of the [monero-wallet-rpc](https://getmonero.dev/interacting/monero-wallet-rpc.html) RPC interface. | http://127.0.0.1:18082 |
|
||||
**BTCPAY_XMR_WALLET_DAEMON_WALLETDIR** | **Optional**. The directory where BTCPay Server saves wallet files uploaded via the UI ([See this blog post for more details](https://sethforprivacy.com/guides/accepting-monero-via-btcpay-server/#configure-the-bitcoin-wallet-of-choice)). | /home/cypherpunk/Monero/wallets/ |
|
||||
| Environment variable | Description | Example |
|
||||
| --- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
|
||||
**BTCPAY_XMR_DAEMON_URI** | **Required**. The URI of the [monerod](https://github.com/monero-project/monero) RPC interface. | http://127.0.0.1:18081 |
|
||||
**BTCPAY_XMR_DAEMON_USERNAME** | **Optional**. The username for authenticating with the daemon. | john |
|
||||
**BTCPAY_XMR_DAEMON_PASSWORD** | **Optional**. The password for authenticating with the daemon. | secret |
|
||||
**BTCPAY_XMR_WALLET_DAEMON_URI** | **Required**. The URI of the [monero-wallet-rpc](https://getmonero.dev/interacting/monero-wallet-rpc.html) RPC interface. | http://127.0.0.1:18082 |
|
||||
**BTCPAY_XMR_WALLET_DAEMON_WALLETDIR** | **Optional**. The directory where BTCPay Server saves wallet files created via the UI ([See this blog post for more details](https://sethforprivacy.com/guides/accepting-monero-via-btcpay-server/#configure-the-bitcoin-wallet-of-choice)). | /home/cypherpunk/Monero/wallets/ |
|
||||
|
||||
BTCPay Server's Docker deployment simplifies the setup by automatically configuring these variables. For further details, refer to this [blog post](https://sethforprivacy.com/guides/accepting-monero-via-btcpay-server).
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ dotnet test BTCPayServer.Plugins.UnitTests --verbosity normal
|
|||
To run unit tests with coverage, install JetBrains dotCover CLI:
|
||||
|
||||
```bash
|
||||
dotnet tool install --global JetBrains.dotCover.CommandLineTools
|
||||
dotnet tool install --global JetBrains.dotCover.CommandLineTools --version 2025.1.6
|
||||
```
|
||||
Then run the following command:
|
||||
|
||||
|
|
@ -83,10 +83,6 @@ dotnet build btcpay-monero-plugin.sln
|
|||
docker compose -f BTCPayServer.Plugins.IntegrationTests/docker-compose.yml run tests
|
||||
```
|
||||
|
||||
| Environment variable | Description | Example |
|
||||
| --- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- |
|
||||
**BTCPAY_XMR_CASHCOW_WALLET_DAEMON_URI** | **Optional**. | The URI of the [monero-wallet-rpc](https://getmonero.dev/interacting/monero-wallet-rpc.html) interface for the cashcow wallet. This is used to create a second wallet for testing purposes in regtest mode.
|
||||
|
||||
## Code formatting
|
||||
|
||||
We use the **unmodified** standardized `.editorconfig` from .NET SDK. Run `dotnet new editorconfig --force` to apply the latest version.
|
||||
|
|
@ -105,8 +101,7 @@ Then create the `appsettings.dev.json` file in `btcpayserver/BTCPayServer`, with
|
|||
{
|
||||
"DEBUG_PLUGINS": "..\\..\\Plugins\\Monero\\bin\\Debug\\net8.0\\BTCPayServer.Plugins.Monero.dll",
|
||||
"XMR_DAEMON_URI": "http://127.0.0.1:18081",
|
||||
"XMR_WALLET_DAEMON_URI": "http://127.0.0.1:18082",
|
||||
"XMR_CASHCOW_WALLET_DAEMON_URI": "http://127.0.0.1:18092"
|
||||
"XMR_WALLET_DAEMON_URI": "http://127.0.0.1:18082"
|
||||
}
|
||||
```
|
||||
This will ensure that BTCPay Server loads the plugin when it starts.
|
||||
|
|
@ -133,9 +128,6 @@ We recommend using [Rider](https://www.jetbrains.com/rider/) for plugin developm
|
|||
|
||||
Visual Studio does not support this feature.
|
||||
|
||||
When debugging in regtest, BTCPay Server will automatically create an configure two wallets. (cashcow and merchant)
|
||||
You can trigger payments or mine blocks on the invoice's checkout page.
|
||||
|
||||
## About docker-compose deployment
|
||||
|
||||
BTCPay Server maintains its own [deployment stack project](https://github.com/btcpayserver/btcpayserver-docker) to enable users to easily update or deploy additional infrastructure (such as nodes).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue