Skip to content

Commit 47c5858

Browse files
Add wallet management and bump submodule version
1 parent 1ae5455 commit 47c5858

16 files changed

Lines changed: 1802 additions & 262 deletions

BTCPayServer.Plugins.IntegrationTests/Monero/MoneroPluginIntegrationTest.cs

Lines changed: 135 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Diagnostics;
2-
31
using BTCPayServer.Plugins.Monero.Services;
42
using BTCPayServer.Rating;
53
using BTCPayServer.Services.Rates;
@@ -13,6 +11,87 @@ namespace BTCPayServer.Plugins.IntegrationTests.Monero;
1311

1412
public class MoneroPluginIntegrationTest(ITestOutputHelper helper) : MoneroAndBitcoinIntegrationTestBase(helper)
1513
{
14+
#region Methods
15+
16+
private async Task CreateWalletViaForm(
17+
PlaywrightTester s,
18+
string walletName,
19+
string address,
20+
string viewKey,
21+
string password,
22+
string restoreHeight)
23+
{
24+
await s.Page.FillAsync("input#WalletName", walletName);
25+
await s.Page.FillAsync("input#PrimaryAddress", address);
26+
await s.Page.FillAsync("input#PrivateViewKey", viewKey);
27+
await s.Page.FillAsync("input#WalletPassword", password);
28+
await s.Page.FillAsync("input#RestoreHeight", restoreHeight);
29+
await s.Page.ClickAsync("button[name='command'][value='connect-wallet']");
30+
await s.Page.Locator($".wallet-card[data-wallet='{walletName}']").WaitForAsync();
31+
}
32+
33+
private async Task CreateWalletViaModal(
34+
PlaywrightTester s,
35+
string walletName,
36+
string address,
37+
string viewKey,
38+
string password,
39+
string restoreHeight)
40+
{
41+
await s.Page.FillAsync("#createWalletModal input#WalletName", walletName);
42+
await s.Page.FillAsync("#createWalletModal input#PrimaryAddress", address);
43+
await s.Page.FillAsync("#createWalletModal input#PrivateViewKey", viewKey);
44+
await s.Page.FillAsync("#createWalletModal input#WalletPassword", password);
45+
await s.Page.FillAsync("#createWalletModal input#RestoreHeight", restoreHeight);
46+
await s.Page.ClickAsync("#createWalletModal button[name='command'][value='replace-wallet']");
47+
await s.Page.Locator($".wallet-card[data-wallet='{walletName}']").WaitForAsync();
48+
49+
}
50+
51+
private async Task<bool> WalletExists(PlaywrightTester s, string walletName)
52+
{
53+
var walletCard = s.Page.Locator($".wallet-card[data-wallet='{walletName}']");
54+
try
55+
{
56+
await walletCard.WaitForAsync();
57+
return true;
58+
}
59+
catch
60+
{
61+
return false;
62+
}
63+
}
64+
65+
private async Task SwitchActiveWallet(PlaywrightTester s, string walletName, string password)
66+
{
67+
var walletCard = s.Page.Locator($".wallet-card[data-wallet='{walletName}']");
68+
await walletCard.Locator(".wallet-card-content").ClickAsync();
69+
await s.Page.Locator("input#ActiveWalletPassword").WaitForAsync();
70+
await s.Page.FillAsync("input#ActiveWalletPassword", password);
71+
await s.Page.ClickAsync("button[name='command'][value='set-active-wallet']");
72+
await s.Page.Locator($".wallet-card[data-wallet='{walletName}'].wallet-card-active").WaitForAsync();
73+
}
74+
75+
private async Task DeleteWallet(PlaywrightTester s, string walletName)
76+
{
77+
var walletCard = s.Page.Locator($".wallet-card[data-wallet='{walletName}']");
78+
await walletCard.Locator(".wallet-actions .dropdown-toggle").ClickAsync();
79+
await walletCard.Locator(".wallet-action-delete").WaitForAsync();
80+
await walletCard.Locator(".wallet-action-delete").ClickAsync();
81+
await s.Page.Locator("#deleteWalletModal").WaitForAsync();
82+
await s.Page.ClickAsync("#deleteWalletModal button[name='command'][value='delete-wallets']");
83+
await walletCard.WaitForAsync(new() { State = Microsoft.Playwright.WaitForSelectorState.Detached });
84+
}
85+
86+
private async Task<bool> WalletIsActive(PlaywrightTester s, string walletName)
87+
{
88+
var walletCard = s.Page.Locator($".wallet-card[data-wallet='{walletName}']");
89+
var hasActiveClass = await walletCard.GetAttributeAsync("class");
90+
return hasActiveClass?.Contains("wallet-card-active") ?? false;
91+
}
92+
93+
#endregion
94+
1695
[Fact]
1796
public async Task EnableMoneroPluginSuccessfully()
1897
{
@@ -40,73 +119,74 @@ public async Task EnableMoneroPluginSuccessfully()
40119
await s.RegisterNewUser(true);
41120
await s.CreateNewStore(preferredExchange: "Kraken");
42121
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
43-
await s.Page.Locator("input#PrimaryAddress")
44-
.FillAsync(
45-
"43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L");
46-
await s.Page.Locator("input#PrivateViewKey")
47-
.FillAsync("1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e");
48-
await s.Page.Locator("input#RestoreHeight").FillAsync("0");
49-
await s.Page.Locator("input#WalletPassword").FillAsync("pass123");
50-
await s.Page.ClickAsync("button[name='command'][value='set-wallet-details']");
122+
await s.Page.Locator("text=No Wallet Configured").WaitForAsync();
123+
await s.Page.ClickAsync("a[href*='/connect/XMR']");
124+
await s.Page.Locator("input#WalletName").WaitForAsync();
125+
await CreateWalletViaForm(
126+
s,
127+
walletName: "primary-wallet",
128+
address: "43Pnj6ZKGFTJhaLhiecSFfLfr64KPJZw7MyGH73T6PTDekBBvsTAaWEUSM4bmJqDuYLizhA13jQkMRPpz9VXBCBqQQb6y5L",
129+
viewKey: "1bfa03b0c78aa6bc8292cf160ec9875657d61e889c41d0ebe5c54fd3a2c4b40e",
130+
password: "pass123",
131+
restoreHeight: "0"
132+
);
133+
Assert.True(await WalletExists(s, "primary-wallet"));
134+
Assert.True(await WalletIsActive(s, "primary-wallet"));
135+
51136
await s.Page.CheckAsync("#Enabled");
52137
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "2");
53138
await s.Page.ClickAsync("#SaveButton");
54-
var classList = await s.Page
55-
.Locator("svg.icon-checkmark")
56-
.GetAttributeAsync("class");
57-
Assert.Contains("text-success", classList);
58-
59-
// Set rate provider
60-
await s.Page.Locator("#menu-item-General").ClickAsync();
61-
await s.Page.Locator("#menu-item-Rates").ClickAsync();
62-
await s.Page.FillAsync("#DefaultCurrencyPairs", "BTC_USD,XMR_USD,XMR_BTC");
63-
await s.Page.SelectOptionAsync("#PrimarySource_PreferredExchange", "kraken");
64-
await s.Page.Locator("#page-primary").ClickAsync();
65-
66-
// Generate a new invoice
67-
await s.Page.Locator("a.nav-link[href*='invoices']").ClickAsync();
68-
await s.Page.Locator("#page-primary").ClickAsync();
69-
await s.Page.FillAsync("#Amount", "4.20");
70-
await s.Page.FillAsync("#BuyerEmail", "monero@monero.com");
71-
await Task.Delay(TimeSpan.FromSeconds(25)); // wallet-rpc needs some time to sync. refactor this later
72-
await s.Page.Locator("#page-primary").ClickAsync();
73-
74-
// View the invoice
75-
var href = await s.Page.Locator("a[href^='/i/']").GetAttributeAsync("href");
76-
var invoiceId = href?.Split("/i/").Last();
77-
await s.Page.Locator($"a[href='/i/{invoiceId}']").ClickAsync();
78-
await s.Page.ClickAsync("#DetailsToggle");
79-
80-
// Verify the total fiat amount is $4.20
81-
var totalFiat = await s.Page
82-
.Locator("#PaymentDetails-TotalFiat dd.clipboard-button")
83-
.InnerTextAsync();
84-
Assert.Equal("$4.20", totalFiat);
139+
await s.Page.Locator("svg.icon-checkmark.text-success").WaitForAsync();
85140

86-
await s.Page.GoBackAsync();
87-
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
141+
var invoiceId = await s.CreateInvoice(s.StoreId, 10, "USD");
142+
await s.GoToInvoiceCheckout(invoiceId);
88143

89-
// Create a new account label
90-
await s.Page.FillAsync("#NewAccountLabel", "tst-account");
91-
await s.Page.ClickAsync("button[name='command'][value='add-account']");
144+
var copyButton = s.Page.Locator("button[data-clipboard].clipboard-button");
145+
var moneroAddress = await copyButton.GetAttributeAsync("data-clipboard");
146+
Assert.NotNull(moneroAddress);
147+
Assert.True(moneroAddress.StartsWith("8"), $"Expected Monero address to start with 8, but got: {moneroAddress}");
92148

93-
// Select primary Account Index
149+
await s.GoToStore(s.StoreId);
94150
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
95-
await s.Page.SelectOptionAsync("#AccountIndex", "1");
151+
Assert.True(await WalletIsActive(s, "primary-wallet"));
152+
153+
await s.Page.CheckAsync("#Enabled");
154+
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "3");
96155
await s.Page.ClickAsync("#SaveButton");
97156

98-
// Verify selected account index
157+
await s.GoToStore(s.StoreId);
99158
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
100-
var selectedValue = await s.Page.Locator("#AccountIndex").InputValueAsync();
101-
Assert.Equal("1", selectedValue);
159+
await s.Page.Locator("#walletManagementMenu").WaitForAsync();
102160

103-
// Select confirmation time to 0
104-
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "3");
105-
await s.Page.ClickAsync("#SaveButton");
161+
Assert.True(await WalletExists(s, "primary-wallet"));
162+
Assert.True(await WalletIsActive(s, "primary-wallet"));
163+
await s.Page.ClickAsync("#walletManagementMenu");
164+
await s.Page.ClickAsync("button[data-bs-target='#createWalletModal']");
165+
await s.Page.Locator("#createWalletModal input#WalletName").WaitForAsync();
106166

107-
await CleanUp(s);
167+
await CreateWalletViaModal(
168+
s,
169+
walletName: "secondary-wallet",
170+
address: "41u3R13W1UDhvHGNS1cqtoh4CKFkuZ5aWbkmkEskhm3SNsuaLQAeFtrXyd6Q2XAgwzf41CMC65u8fVWjB38RLAUb8AKJMw9",
171+
viewKey: "134af10334bd65ce91e015db3ea9f0b1abd1a9ed2fa378bc537498f4f52f6f0f",
172+
password: "pass456",
173+
restoreHeight: "100"
174+
);
175+
Assert.True(await WalletExists(s, "primary-wallet"));
176+
Assert.True(await WalletExists(s, "secondary-wallet"));
177+
Assert.True(await WalletIsActive(s, "secondary-wallet"));
178+
179+
await SwitchActiveWallet(s, "primary-wallet", "pass123");
180+
Assert.True(await WalletIsActive(s, "primary-wallet"));
181+
Assert.False(await WalletIsActive(s, "secondary-wallet"));
182+
183+
await DeleteWallet(s, "secondary-wallet");
184+
Assert.True(await WalletExists(s, "primary-wallet"));
185+
Assert.False(await WalletExists(s, "secondary-wallet"));
186+
Assert.True(await WalletIsActive(s, "primary-wallet"));
108187
}
109188

189+
110190
[Fact]
111191
public async Task ShouldFailWhenWrongPrimaryAddress()
112192
{
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace BTCPayServer.Plugins.Monero.Configuration
4+
{
5+
public class MoneroWalletState
6+
{
7+
public string ActiveWalletName { get; set; }
8+
9+
public string ActiveWalletPassword { get; set; }
10+
11+
public DateTimeOffset? LastActivatedAt { get; set; }
12+
13+
public string LastActivatedByStoreId { get; set; }
14+
15+
public bool IsInitialized => !string.IsNullOrEmpty(ActiveWalletName);
16+
17+
public bool IsConnected { get; set; }
18+
}
19+
}

0 commit comments

Comments
 (0)