Skip to content

Commit f238692

Browse files
committed
Remove cash cow with cheat code and replace upload wallet with private view key
1 parent 4d61788 commit f238692

22 files changed

Lines changed: 112 additions & 349 deletions

.github/workflows/codacy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ permissions:
1313

1414
jobs:
1515
report-coverage:
16-
if: github.repository == 'btcpay-monero/btcpayserver-monero-plugin' && github.event.workflow_run.conclusion == 'success'
16+
if: github.repository == 'btcpay-monero/btcpayserver-monero-plugin'
1717
runs-on: ubuntu-latest
1818
steps:
1919
- name: Download coverage report

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
4242
- name: Run unit tests
4343
run: |
44-
dotnet tool install --global JetBrains.dotCover.CommandLineTools
44+
dotnet tool install --global JetBrains.dotCover.CommandLineTools --version 2025.1.6
4545
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.*"
4646
4747
- name: Run integration tests

BTCPayServer.Plugins.IntegrationTests/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ RUN mkdir -p ${MONERO_PLUGIN_FOLDER}
3232
RUN cd Plugins/Monero && dotnet build BTCPayServer.Plugins.Monero.sln --configuration ${CONFIGURATION_NAME} /p:RazorCompileOnBuild=true --output ${MONERO_PLUGIN_FOLDER}
3333
RUN cd BTCPayServer.Plugins.IntegrationTests && dotnet build --configuration ${CONFIGURATION_NAME} /p:CI_TESTS=true /p:RazorCompileOnBuild=true
3434
RUN dotnet tool install --global Microsoft.Playwright.CLI
35-
RUN dotnet tool install --global JetBrains.DotCover.CommandLineTools
35+
RUN dotnet tool install --global JetBrains.DotCover.CommandLineTools --version 2025.1.6
3636
ENV PATH="$PATH:/root/.dotnet/tools"
3737
RUN playwright install chromium --with-deps
3838
WORKDIR /source/BTCPayServer.Plugins.IntegrationTests

BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public async Task EnableMoneroPluginSuccessfully()
3636
await s.RegisterNewUser(true);
3737
await s.CreateNewStore(preferredExchange: "Kraken");
3838
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
39+
await s.Page.Locator("input#PrimaryAddress").FillAsync("43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L");
40+
await s.Page.Locator("input#PrivateViewKey").FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
41+
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
42+
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
43+
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
3944
await s.Page.CheckAsync("#Enabled");
4045
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "2");
4146
await s.Page.ClickAsync("#SaveButton");

BTCPayServer.Plugins.IntegrationTests/docker-compose.yml

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ services:
3232
- nbxplorer
3333
- postgres
3434
- xmr_wallet
35-
- xmr_cashcow_wallet
3635

3736
nbxplorer:
3837
image: nicolasdorier/nbxplorer:2.5.25
@@ -90,36 +89,24 @@ services:
9089
- "bitcoin_datadir:/data"
9190

9291
monerod:
93-
image: btcpayserver/monero:0.18.4.0
92+
image: btcpayserver/monero:0.18.4.2
9493
restart: unless-stopped
9594
container_name: monerod
96-
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
95+
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
9796
volumes:
98-
- "xmr_data:/home/monero/.bitmonero"
97+
- xmr_data:/data
9998
ports:
10099
- "18081:18081"
101100

102101
xmr_wallet:
103-
image: btcpayserver/monero:0.18.4.0
102+
image: btcpayserver/monero:0.18.4.2
104103
restart: unless-stopped
105104
container_name: xmr_wallet
106-
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"
105+
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"
107106
ports:
108107
- "18082:18082"
109108
volumes:
110-
- "xmr_wallet:/wallet"
111-
depends_on:
112-
- monerod
113-
114-
xmr_cashcow_wallet:
115-
image: btcpayserver/monero:0.18.4.0
116-
restart: unless-stopped
117-
container_name: xmr_cashcow_wallet
118-
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
119-
ports:
120-
- "18092:18092"
121-
volumes:
122-
- "xmr_cashcow_wallet:/wallet"
109+
- xmr_wallet:/wallet
123110
depends_on:
124111
- monerod
125112

@@ -137,7 +124,6 @@ volumes:
137124
bitcoin_datadir:
138125
xmr_data:
139126
xmr_wallet:
140-
xmr_cashcow_wallet:
141127

142128
networks:
143129
default:

BTCPayServer.Plugins.UnitTests/Monero/Configuration/MoneroLikeConfigurationTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,14 @@ public void MoneroLikeConfigurationItem_ShouldSetAndGetProperties()
2626
InternalWalletRpcUri = new Uri("http://localhost:18082"),
2727
WalletDirectory = "/wallets",
2828
Username = "user",
29-
Password = "password",
30-
CashCowWalletRpcUri = new Uri("http://localhost:18083")
29+
Password = "password"
3130
};
3231

3332
Assert.Equal("http://localhost:18081/", configItem.DaemonRpcUri.ToString());
3433
Assert.Equal("http://localhost:18082/", configItem.InternalWalletRpcUri.ToString());
3534
Assert.Equal("/wallets", configItem.WalletDirectory);
3635
Assert.Equal("user", configItem.Username);
3736
Assert.Equal("password", configItem.Password);
38-
Assert.Equal("http://localhost:18083/", configItem.CashCowWalletRpcUri.ToString());
3937
}
4038

4139
[Trait("Category", "Unit")]

Plugins/Monero/Configuration/MoneroLikeConfiguration.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,5 @@ public class MoneroLikeConfigurationItem
1515
public string WalletDirectory { get; set; }
1616
public string Username { get; set; }
1717
public string Password { get; set; }
18-
public Uri CashCowWalletRpcUri { get; set; }
1918
}
2019
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using System;
2+
3+
namespace BTCPayServer.Plugins.Monero.Controllers;
4+
5+
public class GenerateFromKeysException(string message) : Exception(message);

Plugins/Monero/Controllers/MoneroLikeStoreController.cs

Lines changed: 20 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.ComponentModel.DataAnnotations;
4-
using System.Diagnostics;
54
using System.Globalization;
6-
using System.IO;
75
using System.Linq;
86
using System.Threading.Tasks;
97

@@ -21,7 +19,6 @@
2119
using BTCPayServer.Services.Stores;
2220

2321
using Microsoft.AspNetCore.Authorization;
24-
using Microsoft.AspNetCore.Http;
2522
using Microsoft.AspNetCore.Mvc;
2623
using Microsoft.AspNetCore.Mvc.Rendering;
2724
using Microsoft.Extensions.Localization;
@@ -183,22 +180,22 @@ public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(MoneroLikePayme
183180
}
184181

185182
}
186-
else if (command == "upload-wallet")
183+
else if (command == "set-wallet-details")
187184
{
188185
var valid = true;
189-
if (viewModel.WalletFile == null)
186+
if (viewModel.PrimaryAddress == null)
190187
{
191-
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["Please select the view-only wallet file"]);
188+
ModelState.AddModelError(nameof(viewModel.PrimaryAddress), StringLocalizer["Please set your primary public address"]);
192189
valid = false;
193190
}
194-
if (viewModel.WalletKeysFile == null)
191+
if (viewModel.PrivateViewKey == null)
195192
{
196-
ModelState.AddModelError(nameof(viewModel.WalletKeysFile), StringLocalizer["Please select the view-only wallet keys file"]);
193+
ModelState.AddModelError(nameof(viewModel.PrivateViewKey), StringLocalizer["Please set your private view key"]);
197194
valid = false;
198195
}
199196
if (configurationItem.WalletDirectory == null)
200197
{
201-
ModelState.AddModelError(nameof(viewModel.WalletFile), StringLocalizer["This installation doesn't support wallet import (BTCPAY_XMR_WALLET_DAEMON_WALLETDIR is not set)"]);
198+
ModelState.AddModelError(nameof(viewModel.PrimaryAddress), StringLocalizer["This installation doesn't support wallet creation (BTCPAY_XMR_WALLET_DAEMON_WALLETDIR is not set)"]);
202199
valid = false;
203200
}
204201
if (valid)
@@ -216,71 +213,31 @@ public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(MoneroLikePayme
216213
new { cryptoCode });
217214
}
218215
}
219-
220-
var fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet");
221-
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
222-
{
223-
await viewModel.WalletFile.CopyToAsync(fileStream);
224-
try
225-
{
226-
Exec($"chmod 666 {fileAddress}");
227-
}
228-
catch
229-
{
230-
// ignored
231-
}
232-
}
233-
234-
fileAddress = Path.Combine(configurationItem.WalletDirectory, "wallet.keys");
235-
using (var fileStream = new FileStream(fileAddress, FileMode.Create))
236-
{
237-
await viewModel.WalletKeysFile.CopyToAsync(fileStream);
238-
try
239-
{
240-
Exec($"chmod 666 {fileAddress}");
241-
}
242-
catch
243-
{
244-
// ignored
245-
}
246-
}
247-
248-
fileAddress = Path.Combine(configurationItem.WalletDirectory, "password");
249-
using (var fileStream = new StreamWriter(fileAddress, false))
250-
{
251-
await fileStream.WriteAsync(viewModel.WalletPassword);
252-
try
253-
{
254-
Exec($"chmod 666 {fileAddress}");
255-
}
256-
catch
257-
{
258-
// ignored
259-
}
260-
}
261-
262216
try
263217
{
264-
var response = await _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<OpenWalletRequest, OpenWalletResponse>("open_wallet", new OpenWalletRequest
218+
var response = await _MoneroRpcProvider.WalletRpcClients[cryptoCode].SendCommandAsync<GenerateFromKeysRequest, GenerateFromKeysResponse>("generate_from_keys", new GenerateFromKeysRequest
265219
{
266-
Filename = "wallet",
220+
PrimaryAddress = viewModel.PrimaryAddress,
221+
PrivateViewKey = viewModel.PrivateViewKey,
222+
WalletFileName = "view_wallet",
223+
RestoreHeight = viewModel.RestoreHeight,
267224
Password = viewModel.WalletPassword
268225
});
269226
if (response?.Error != null)
270227
{
271-
throw new WalletOpenException(response.Error.Message);
228+
throw new GenerateFromKeysException(response.Error.Message);
272229
}
273230
}
274231
catch (Exception ex)
275232
{
276-
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not open the wallet: {0}", ex.Message]);
233+
ModelState.AddModelError(nameof(viewModel.AccountIndex), StringLocalizer["Could not generate view wallet from keys: {0}", ex.Message]);
277234
return View("/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml", viewModel);
278235
}
279236

280237
TempData.SetStatusMessageModel(new StatusMessageModel
281238
{
282239
Severity = StatusMessageModel.StatusSeverity.Info,
283-
Message = StringLocalizer["View-only wallet files uploaded. The wallet will soon become available."].Value
240+
Message = StringLocalizer["View-only wallet created. The wallet will soon become available."].Value
284241
});
285242
return RedirectToAction(nameof(GetStoreMoneroLikePaymentMethod), new { cryptoCode });
286243
}
@@ -297,7 +254,6 @@ public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(MoneroLikePayme
297254
vm.AccountIndex = viewModel.AccountIndex;
298255
vm.SettlementConfirmationThresholdChoice = viewModel.SettlementConfirmationThresholdChoice;
299256
vm.CustomSettlementConfirmationThreshold = viewModel.CustomSettlementConfirmationThreshold;
300-
vm.SupportWalletExport = configurationItem.WalletDirectory is not null;
301257
return View("/Views/Monero/GetStoreMoneroLikePaymentMethod.cshtml", vm);
302258
}
303259

@@ -323,30 +279,6 @@ public async Task<IActionResult> GetStoreMoneroLikePaymentMethod(MoneroLikePayme
323279
new { StatusMessage = $"{cryptoCode} settings updated successfully", storeId = StoreData.Id });
324280
}
325281

326-
private void Exec(string cmd)
327-
{
328-
329-
var escapedArgs = cmd.Replace("\"", "\\\"", StringComparison.InvariantCulture);
330-
331-
var process = new Process
332-
{
333-
StartInfo = new ProcessStartInfo
334-
{
335-
RedirectStandardOutput = true,
336-
UseShellExecute = false,
337-
CreateNoWindow = true,
338-
WindowStyle = ProcessWindowStyle.Hidden,
339-
FileName = "/bin/sh",
340-
Arguments = $"-c \"{escapedArgs}\""
341-
}
342-
};
343-
344-
#pragma warning disable CA1416 // Validate platform compatibility
345-
process.Start();
346-
#pragma warning restore CA1416 // Validate platform compatibility
347-
process.WaitForExit();
348-
}
349-
350282
public class MoneroLikePaymentMethodListViewModel
351283
{
352284
public IEnumerable<MoneroLikePaymentMethodViewModel> Items { get; set; }
@@ -355,18 +287,19 @@ public class MoneroLikePaymentMethodListViewModel
355287
public class MoneroLikePaymentMethodViewModel : IValidatableObject
356288
{
357289
public MoneroRPCProvider.MoneroLikeSummary Summary { get; set; }
358-
public bool SupportWalletExport { get; set; }
359290
public string CryptoCode { get; set; }
360291
public string NewAccountLabel { get; set; }
361292
public long AccountIndex { get; set; }
362293
public bool Enabled { get; set; }
363294

364295
public IEnumerable<SelectListItem> Accounts { get; set; }
365296
public bool WalletFileFound { get; set; }
366-
[Display(Name = "View-Only Wallet File")]
367-
public IFormFile WalletFile { get; set; }
368-
[Display(Name = "Wallet Keys File")]
369-
public IFormFile WalletKeysFile { get; set; }
297+
[Display(Name = "Primary Public Address")]
298+
public string PrimaryAddress { get; set; }
299+
[Display(Name = "Private View Key")]
300+
public string PrivateViewKey { get; set; }
301+
[Display(Name = "Restore Height")]
302+
public int RestoreHeight { get; set; }
370303
[Display(Name = "Wallet Password")]
371304
public string WalletPassword { get; set; }
372305
[Display(Name = "Consider the invoice settled when the payment transaction …")]

Plugins/Monero/Controllers/WalletOpenException.cs

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)