Skip to content

Commit 068195e

Browse files
Add wallet management and bump submodule version
1 parent cdfdb2c commit 068195e

18 files changed

Lines changed: 1833 additions & 254 deletions
Lines changed: 138 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BTCPayServer.Rating;
22
using BTCPayServer.Services.Rates;
3+
using BTCPayServer.Tests;
34
using BTCPayServer.Tests.Mocks;
45

56
using Xunit;
@@ -9,6 +10,87 @@ namespace BTCPayServer.Plugins.IntegrationTests.Monero;
910

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

78-
// Create a new account label
79-
await s.Page.FillAsync("#NewAccountLabel", "tst-account");
80-
await s.Page.ClickAsync("button[name='command'][value='add-account']");
140+
var invoiceId = await s.CreateInvoice(s.StoreId, 10, "USD");
141+
await s.GoToInvoiceCheckout(invoiceId);
81142

82-
// Select primary Account Index
83-
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
84-
await s.Page.SelectOptionAsync("#AccountIndex", "1");
85-
await s.Page.ClickAsync("#SaveButton");
143+
var copyButton = s.Page.Locator("button[data-clipboard].clipboard-button");
144+
var moneroAddress = await copyButton.GetAttributeAsync("data-clipboard");
145+
Assert.NotNull(moneroAddress);
146+
Assert.True(moneroAddress.StartsWith("8"), $"Expected Monero address to start with 8, but got: {moneroAddress}");
86147

87-
// Verify selected account index
148+
await s.GoToStore(s.StoreId);
88149
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
89-
var selectedValue = await s.Page.Locator("#AccountIndex").InputValueAsync();
90-
Assert.Equal("1", selectedValue);
150+
Assert.True(await WalletIsActive(s, "primary-wallet"));
91151

92-
// Select confirmation time to 0
152+
await s.Page.CheckAsync("#Enabled");
93153
await s.Page.SelectOptionAsync("#SettlementConfirmationThresholdChoice", "3");
94154
await s.Page.ClickAsync("#SaveButton");
155+
156+
await s.GoToStore(s.StoreId);
157+
await s.Page.Locator("a.nav-link[href*='monerolike/XMR']").ClickAsync();
158+
await s.Page.Locator("#walletManagementMenu").WaitForAsync();
159+
160+
Assert.True(await WalletExists(s, "primary-wallet"));
161+
Assert.True(await WalletIsActive(s, "primary-wallet"));
162+
await s.Page.ClickAsync("#walletManagementMenu");
163+
await s.Page.ClickAsync("button[data-bs-target='#createWalletModal']");
164+
await s.Page.Locator("#createWalletModal input#WalletName").WaitForAsync();
165+
166+
await CreateWalletViaModal(
167+
s,
168+
walletName: "secondary-wallet",
169+
address: "41u3R13W1UDhvHGNS1cqtoh4CKFkuZ5aWbkmkEskhm3SNsuaLQAeFtrXyd6Q2XAgwzf41CMC65u8fVWjB38RLAUb8AKJMw9",
170+
viewKey: "134af10334bd65ce91e015db3ea9f0b1abd1a9ed2fa378bc537498f4f52f6f0f",
171+
password: "pass456",
172+
restoreHeight: "100"
173+
);
174+
Assert.True(await WalletExists(s, "primary-wallet"));
175+
Assert.True(await WalletExists(s, "secondary-wallet"));
176+
Assert.True(await WalletIsActive(s, "secondary-wallet"));
177+
178+
await SwitchActiveWallet(s, "primary-wallet", "pass123");
179+
Assert.True(await WalletIsActive(s, "primary-wallet"));
180+
Assert.False(await WalletIsActive(s, "secondary-wallet"));
181+
182+
await DeleteWallet(s, "secondary-wallet");
183+
Assert.True(await WalletExists(s, "primary-wallet"));
184+
Assert.False(await WalletExists(s, "secondary-wallet"));
185+
Assert.True(await WalletIsActive(s, "primary-wallet"));
186+
95187
}
96188
}

BTCPayServer.Plugins.IntegrationTests/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ services:
2222
- "tests:127.0.0.1"
2323
volumes:
2424
- ../coverage:/coverage
25+
- xmr_wallet:/wallet
2526

2627
# The dev container is not used, it is just handy to run `docker-compose up dev` to start all services
2728
dev:
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)