diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj
index d1dab16..c454981 100644
--- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj
@@ -41,10 +41,20 @@
-
+
-
+ <_ContentIncludedByDefault Remove="Views\Shared\Wabisabi\StoreIntegrationWabisabiOption.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\Shared\Wabisabi\WabisabiDashboard.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\Shared\Wabisabi\WabisabiNav.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\Shared\Wabisabi\WabisabiServerNavvExtension.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\WabisabiCoordinatorConfig\UpdateWabisabiSettings.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\WabisabiStore\Spend.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\WabisabiStore\UpdateWabisabiStoreSettings.cshtml" />
+ <_ContentIncludedByDefault Remove="Views\_ViewImports.cshtml" />
+
+
+
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs
index 75a546d..bd4e999 100644
--- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs
@@ -1,15 +1,18 @@
using System;
using System.Linq;
+using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
-using BTCPayServer.Abstractions.Constants;
+using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using NBitcoin;
using NBitcoin.Secp256k1;
using NNostr.Client;
using WalletWasabi.Backend.Controllers;
+using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes;
namespace BTCPayServer.Plugins.Wabisabi
{
@@ -18,6 +21,17 @@ namespace BTCPayServer.Plugins.Wabisabi
[Route("plugins/wabisabi-coordinator/edit")]
public class WabisabiCoordinatorConfigController : Controller
{
+ public static string OurDisclaimer = @"By using this plugin, the user agrees that they are solely responsible for determining the legality of its operation in their jurisdiction and for the handling of any revenue generated through its use. The user also agrees that any coordinator fees generated through the use of the plugin may be configured to be split up and forwarded to multiple destinations, which by default are non-profit organizations. However, there is also a hardcoded destination intended to fund the further development and maintenance of the plugin. The user understands that the forwarding of these fees to the hardcoded destination should be considered a donation and not an obligation of the developer or the plugin. The user also acknowledges that the plugin is open-source and licensed under the MIT license and therefore has the right to adapt the code and remove this feature if they do not accept the hardcoded donation. The user agrees to use the plugin at their own risk and acknowledges that being a coinjoin coordinator carries certain risks, including but not limited to legal risks, technical risks, privacy risks and reputational risks. It is their responsibility to carefully consider these risks before using the plugin.
+
+This disclaimer serves as a binding agreement between the user and the developers of this plugin and supersedes any previous agreements or understandings, whether written or oral. The user agrees to fully waive and release the developers of this plugin and BTCPay Server contributors, from any and all liabilities, claims, demands, damages, or causes of action arising out of or related to the use of this plugin. In the event of any legal issues arising from the use of this plugin, the user also agrees to indemnify and hold harmless the developers of this plugin and BTCPay Server contributors from any claims, costs, losses, damages, liabilities, judgments and expenses (including reasonable fees of attorneys and other professionals) arising from or in any way related to the user's use of the plugin or violation of these terms. Any failure or delay by the developer to exercise or enforce any right or remedy provided under this disclaimer will not constitute a waiver of that or any other right or remedy, and no single or partial exercise of any right or remedy will preclude or restrict the further exercise of that or any other right or remedy.
+
+Legal risks: as the coordinator, the user may be considered to be operating a money transmitting business and may be subject to regulatory requirements and oversight.
+
+Technical risks: the plugin uses complex cryptography and code, and there may be bugs or vulnerabilities that could result in the loss of funds.
+
+Privacy risks: as the coordinator, the user may have access to sensitive transaction data, and it is their responsibility to protect this data and comply with any applicable privacy laws.
+
+Reputation risks: as the coordinator, the user may be associated with illegal activities and may face reputational damage.";
private readonly WabisabiCoordinatorService _wabisabiCoordinatorService;
public WabisabiCoordinatorConfigController(WabisabiCoordinatorService wabisabiCoordinatorService)
{
@@ -41,13 +55,47 @@ namespace BTCPayServer.Plugins.Wabisabi
return View(Wabisabi);
}
+ private static bool IsLocalNetwork(string server)
+ {
+ ArgumentNullException.ThrowIfNull(server);
+ if (Uri.CheckHostName(server) == UriHostNameType.Dns)
+ {
+ return server.EndsWith(".internal", StringComparison.OrdinalIgnoreCase) ||
+ server.EndsWith(".local", StringComparison.OrdinalIgnoreCase) ||
+ server.EndsWith(".lan", StringComparison.OrdinalIgnoreCase) ||
+ server.IndexOf('.', StringComparison.OrdinalIgnoreCase) == -1;
+ }
+ if (IPAddress.TryParse(server, out var ip))
+ {
+ return ip.IsLocal() || ip.IsRFC1918();
+ }
+ if (Uri.TryCreate(server, UriKind.Absolute, out var res) && res.IsLoopback || res.Host == "localhost")
+ {
+ return true;
+ }
+ return false;
+ }
[HttpPost("")]
public async Task UpdateWabisabiSettings(WabisabiCoordinatorSettings vm,
string command, string config)
{
switch (command)
{
+ case "nostr-current-url":
+ if (IsLocalNetwork(Request.GetAbsoluteRoot()))
+ {
+
+ TempData["ErrorMessage"] = "the current url is only reachable from your local network. You need a public domain or use Tor.";
+ return View(vm);
+ }
+ else
+ {
+ vm.UriToAdvertise = Request.GetAbsoluteRootUri();
+ TempData["SuccessMessage"] = $"Will create nostr events that point to ${ vm.UriToAdvertise }";
+ await _wabisabiCoordinatorService.UpdateSettings( vm);
+ return RedirectToAction(nameof(UpdateWabisabiSettings));
+ }
case "generate-nostr-key":
if (ECPrivKey.TryCreate(new ReadOnlySpan(RandomNumberGenerator.GetBytes(32)), out var key))
{
@@ -67,7 +115,7 @@ namespace BTCPayServer.Plugins.Wabisabi
}
catch (Exception e)
{
- ModelState.AddModelError("config", "config json was invalid");
+ ModelState.AddModelError("config", $"config json was invalid ({e.Message})");
return View(vm);
}
await _wabisabiCoordinatorService.UpdateSettings( vm);
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs
index c07af9b..2551d7b 100644
--- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs
@@ -23,6 +23,7 @@ using Microsoft.Extensions.Options;
using NBitcoin;
using NBitcoin.RPC;
using NBXplorer;
+using NBXplorer.Models;
using Newtonsoft.Json.Linq;
using NNostr.Client;
using WalletWasabi.Bases;
@@ -43,8 +44,6 @@ public class WabisabiCoordinatorService : PeriodicRunner
private readonly IMemoryCache _memoryCache;
private readonly WabisabiCoordinatorClientInstanceManager _instanceManager;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IConfiguration _configuration;
- private readonly IServiceProvider _serviceProvider;
public readonly IdempotencyRequestCache IdempotencyRequestCache;
@@ -54,8 +53,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
public WabisabiCoordinatorService(ISettingsRepository settingsRepository,
IOptions dataDirectories, IExplorerClientProvider clientProvider, IMemoryCache memoryCache,
WabisabiCoordinatorClientInstanceManager instanceManager,
- IHttpClientFactory httpClientFactory,
- IConfiguration configuration, IServiceProvider serviceProvider) : base(TimeSpan.FromMinutes(15))
+ IHttpClientFactory httpClientFactory) : base(TimeSpan.FromMinutes(15))
{
_settingsRepository = settingsRepository;
_dataDirectories = dataDirectories;
@@ -63,13 +61,11 @@ public class WabisabiCoordinatorService : PeriodicRunner
_memoryCache = memoryCache;
_instanceManager = instanceManager;
_httpClientFactory = httpClientFactory;
- _configuration = configuration;
- _serviceProvider = serviceProvider;
IdempotencyRequestCache = new(memoryCache);
}
- private WabisabiCoordinatorSettings cachedSettings;
-
+ private WabisabiCoordinatorSettings cachedSettings;
+
public async Task GetSettings()
{
return cachedSettings ??= (await _settingsRepository.GetSettingAsync(
@@ -93,10 +89,14 @@ public class WabisabiCoordinatorService : PeriodicRunner
break;
}
}
+ else if (existing.Enabled &&
+ _instanceManager.HostedServices.TryGetValue("local", out var instance))
+ {
+ instance.TermsConditions = wabisabiCoordinatorSettings.TermsConditions;
+ }
await this.ActionAsync(CancellationToken.None);
await _settingsRepository.UpdateSetting(wabisabiCoordinatorSettings, nameof(WabisabiCoordinatorSettings));
-
}
public class BtcPayRpcClient : CachedRpcClient
@@ -119,6 +119,37 @@ public class WabisabiCoordinatorService : PeriodicRunner
return result;
}
+
+ public override async Task SendRawTransactionAsync(Transaction transaction,
+ CancellationToken cancellationToken = default)
+ {
+ var result = await _explorerClient.BroadcastAsync(transaction, cancellationToken);
+ if (!result.Success)
+ {
+ throw new RPCException((RPCErrorCode)result.RPCCode, result.RPCMessage, null);
+ }
+
+ return transaction.GetHash();
+ }
+
+ public override async Task EstimateSmartFeeAsync(int confirmationTarget,
+ EstimateSmartFeeMode estimateMode = EstimateSmartFeeMode.Conservative,
+ CancellationToken cancellationToken = default)
+ {
+
+ string cacheKey = $"{nameof(EstimateSmartFeeAsync)}:{confirmationTarget}:{estimateMode}";
+
+ return await IdempotencyRequestCache.GetCachedResponseAsync(
+ cacheKey,
+ action: async (_, cancellationToken) =>
+ {
+ var result = await _explorerClient.GetFeeRateAsync(confirmationTarget, cancellationToken);
+ return new EstimateSmartFeeResponse() {FeeRate = result.FeeRate, Blocks = result.BlockCount};
+ },
+ options: CacheOptionsWithExpirationToken(size: 1, expireInSeconds: 60),
+ cancellationToken).ConfigureAwait(false);
+
+ }
}
public override async Task StartAsync(CancellationToken cancellationToken)
@@ -148,60 +179,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
public async Task StartCoordinator(CancellationToken cancellationToken)
{
await HostedServices.StartAllAsync(cancellationToken);
-
- var host = await _serviceProvider.GetService>();
- Console.Error.WriteLine("ADDRESSES:" +
- host.ServerFeatures.Get().Addresses.FirstOrDefault());
- string rootPath = _configuration.GetValue("rootpath", "/");
- var serverAddress = host.ServerFeatures.Get().Addresses.FirstOrDefault();
- _instanceManager.AddCoordinator("Local Coordinator", "local", provider =>
- {
- if (!string.IsNullOrEmpty(serverAddress))
- {
- var serverAddressUri = new Uri(serverAddress);
- if (new[] {UriHostNameType.IPv4, UriHostNameType.IPv6}.Contains(serverAddressUri.HostNameType))
- {
- var ipEndpoint = IPEndPoint.Parse(serverAddressUri.Host);
- if (Equals(ipEndpoint.Address, IPAddress.Any))
- {
- ipEndpoint.Address = IPAddress.Loopback;
- }
-
- if (Equals(ipEndpoint.Address, IPAddress.IPv6Any))
- {
- ipEndpoint.Address = IPAddress.Loopback;
- }
-
- UriBuilder builder = new(serverAddressUri);
- builder.Host = ipEndpoint.Address.ToString();
- builder.Path = $"{rootPath}plugins/wabisabi-coordinator/";
-
- Console.Error.WriteLine($"COORD URL-1: {builder.Uri}");
- return builder.Uri;
- }
- }
-
-
- Uri result;
- var rawBind = _configuration.GetValue("bind", IPAddress.Loopback.ToString())
- .Split(":", StringSplitOptions.RemoveEmptyEntries);
-
- var bindAddress = IPAddress.Parse(rawBind.First());
- if (Equals(bindAddress, IPAddress.Any))
- {
- bindAddress = IPAddress.Loopback;
- }
-
- if (Equals(bindAddress, IPAddress.IPv6Any))
- {
- bindAddress = IPAddress.IPv6Loopback;
- }
-
- int bindPort = rawBind.Length > 2 ? int.Parse(rawBind[1]) : _configuration.GetValue("port", 443);
- result = new Uri($"https://{bindAddress}:{bindPort}{rootPath}plugins/wabisabi-coordinator/");
- Console.Error.WriteLine($"COORD URL: {result}");
- return result;
- });
+ _instanceManager.AddCoordinator("Local Coordinator", "local", _ => null, cachedSettings.TermsConditions);
}
public async Task StopAsync(CancellationToken cancellationToken)
@@ -211,52 +189,11 @@ public class WabisabiCoordinatorService : PeriodicRunner
protected override async Task ActionAsync(CancellationToken cancel)
{
- var network = _clientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork.Name.ToLower();
+ var network = _clientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork;
var s = await GetSettings();
if (s.Enabled && !string.IsNullOrEmpty(s.NostrIdentity) && s.NostrRelay is not null)
{
- try
- {
-
- var key = NostrExtensions.ParseKey(s.NostrIdentity);
- var client = new NostrClient(s.NostrRelay);
- await client.ConnectAndWaitUntilConnected(cancel);
- _= client.ListenForMessages();
- var evt = new NostrEvent()
- {
- Kind = WabisabiStoreController.coordinatorEventKind,
- PublicKey = key.CreatePubKey().ToXOnlyPubKey().ToHex(),
- CreatedAt = DateTimeOffset.UtcNow,
- Tags = new List()
- {
- new()
- {
- TagIdentifier = "uri",
- Data = new List()
- {
- "https://somewhere.com"
- }
- },
- new()
- {
- TagIdentifier = "network",
- Data = new List()
- {
- network
- }
- }
- }
- };
- await evt.ComputeIdAndSign(key);
- await client.PublishEvent(evt, cancel);
- client.Dispose();
-
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- throw;
- }
+ await Nostr.Publish(s.NostrRelay, network, s.NostrIdentity, s.UriToAdvertise, s.CoordinatorDescription,cancel);
}
}
}
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorSettings.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorSettings.cs
index f66e82b..a3fa30d 100644
--- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorSettings.cs
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorSettings.cs
@@ -17,10 +17,33 @@ public class WabisabiCoordinatorSettings
[JsonIgnore] public ECPrivKey? Key => string.IsNullOrEmpty(NostrIdentity)? null: NostrExtensions.ParseKey(NostrIdentity);
[JsonIgnore] public ECXOnlyPubKey PubKey => Key?.CreatePubKey().ToXOnlyPubKey();
+ public Uri UriToAdvertise { get; set; }
+ public string TermsConditions { get; set; } = @"
+These terms and conditions govern your use of the Coinjoin Coordinator service. By using the service, you agree to be bound by these terms and conditions. If you do not agree to these terms and conditions, you should not use the service.
+
+Coinjoin Coordinator Service: The Coinjoin Coordinator service is a tool that allows users to anonymize their cryptocurrency transactions by pooling them with other users' funds and sending them within a common transaction. The service does not store, transmit, or otherwise handle users' cryptocurrency funds.
+
+Legal Compliance: You are responsible for complying with all applicable laws and regulations in your jurisdiction in relation to your use of the Coinjoin Coordinator service. The service is intended to be used for lawful purposes only.
+
+No Warranty: The Coinjoin Coordinator service is provided on an ""as is"" and ""as available"" basis, without any warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose.
+
+Limitation of Liability: In no event shall the Coinjoin Coordinator be liable for any direct, indirect, incidental, special, or consequential damages, or loss of profits, arising out of or in connection with your use of the service.
+
+Indemnification: You agree to indemnify and hold the Coinjoin Coordinator, its affiliates, officers, agents, and employees harmless from any claim or demand, including reasonable attorneys' fees, made by any third party due to or arising out of your use of the service, your violation of these terms and conditions, or your violation of any rights of another.
+
+Severability: If any provision of these terms and conditions is found to be invalid or unenforceable, the remaining provisions shall remain in full force and effect.
+
+Governing Law: These terms and conditions shall be governed by and construed in accordance with the laws of the jurisdiction in which the Coinjoin Coordinator is based.
+
+Changes to Terms and Conditions: Coinjoin Coordinator reserves the right, at its sole discretion, to modify or replace these terms and conditions at any time.
+";
+
+ public string CoordinatorDescription { get; set; }
}
public class DiscoveredCoordinator
{
public Uri Uri { get; set; }
public string Name { get; set; }
+ public string Description { get; set; }
}
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WasabiLeechController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WasabiLeechController.cs
index 721e6db..b772391 100644
--- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WasabiLeechController.cs
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WasabiLeechController.cs
@@ -9,7 +9,7 @@ namespace WalletWasabi.Backend.Controllers;
[Route("plugins/wabisabi-coordinator")]
[AllowAnonymous]
-public class WasabiLeechController:Controller
+public class WasabiLeechController : Controller
{
private readonly WabisabiCoordinatorClientInstanceManager _coordinatorClientInstanceManager;
@@ -17,20 +17,29 @@ public class WasabiLeechController:Controller
{
_coordinatorClientInstanceManager = coordinatorClientInstanceManager;
}
+
+ [HttpGet("api/v4/Wasabi/legaldocuments")]
+ public async Task GetLegalDocuments()
+ {
+ if (_coordinatorClientInstanceManager.HostedServices.TryGetValue("local", out var instance))
+ {
+ return Ok(instance.TermsConditions);
+ }
+
+ return NotFound();
+ }
+
[Route("{*key}")]
public async Task Forward(string key, CancellationToken cancellationToken)
{
-
-
- if(!_coordinatorClientInstanceManager.HostedServices.TryGetValue("zksnacks", out var coordinator))
+ if (!_coordinatorClientInstanceManager.HostedServices.TryGetValue("zksnacks", out var coordinator))
return BadRequest();
var b = new UriBuilder(coordinator.Coordinator);
b.Path = key;
b.Query = Request.QueryString.ToString();
-
+
return RedirectPreserveMethod(b.ToString());
}
-
}
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs
new file mode 100644
index 0000000..a2f8df3
--- /dev/null
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Nostr.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NBitcoin;
+using Newtonsoft.Json.Linq;
+using NNostr.Client;
+using WalletWasabi.Backend.Controllers;
+
+namespace BTCPayServer.Plugins.Wabisabi;
+
+public class Nostr
+{
+ public static int Kind = 15750;
+
+ public static async Task Publish(
+ Uri relayUri,
+ Network currentNetwork,
+ string key,
+ Uri coordinatorUri,
+ string description,
+ CancellationToken cancellationToken)
+ {
+ var privateKey = NostrExtensions.ParseKey(key);
+ var client = new NostrClient(relayUri);
+ await client.ConnectAndWaitUntilConnected(cancellationToken);
+ _ = client.ListenForMessages();
+ var evt = new NostrEvent()
+ {
+ Kind = Kind,
+ Content = description,
+ PublicKey = privateKey.CreatePubKey().ToXOnlyPubKey().ToHex(),
+ CreatedAt = DateTimeOffset.UtcNow,
+ Tags = new List()
+ {
+ new() {TagIdentifier = "uri", Data = new List() {new Uri(coordinatorUri, "plugins/wabisabi-coordinator").ToString()}},
+ new() {TagIdentifier = "network", Data = new List() {currentNetwork.Name}}
+ }
+ };
+ await evt.ComputeIdAndSign(privateKey);
+ await client.PublishEvent(evt, cancellationToken);
+ client.Dispose();
+ }
+
+ public static async Task> Discover(
+ Uri relayUri,
+ Network currentNetwork,
+ string ourPubKey,
+ CancellationToken cancellationToken)
+ {
+ using var nostrClient = new NostrClient(relayUri);
+ await nostrClient.CreateSubscription("nostr-wabisabi-coordinators",
+ new[]
+ {
+ new NostrSubscriptionFilter()
+ {
+ Kinds = new[] {Kind}, Since = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(1)),
+ }
+ }, cancellationToken);
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
+ await nostrClient.ConnectAndWaitUntilConnected(cts.Token);
+ _ = nostrClient.ListenForMessages();
+ var result = new List();
+ var tcs = new TaskCompletionSource();
+ Stopwatch stopwatch = new();
+ stopwatch.Start();
+ nostrClient.MessageReceived += (sender, s) =>
+ {
+ if (JArray.Parse(s).FirstOrDefault()?.Value() == "EOSE")
+ {
+ tcs.SetResult();
+ }
+ };
+ nostrClient.EventsReceived += (sender, tuple) =>
+ {
+ stopwatch.Restart();
+ result.AddRange(tuple.events);
+ };
+ while (!tcs.Task.IsCompleted && !cts.IsCancellationRequested &&
+ stopwatch.ElapsedMilliseconds < 10000)
+ {
+ await Task.Delay(1000, cts.Token);
+ }
+
+ nostrClient.Dispose();
+
+ var network = currentNetwork.Name
+ .ToLower();
+ return result.Where(@event =>
+ @event.PublicKey != ourPubKey &&
+ @event.CreatedAt < DateTimeOffset.UtcNow.AddMinutes(15) &&
+ @event.Verify() &&
+ @event.Tags.Any(tag =>
+ tag.TagIdentifier == "uri" &&
+ tag.Data.Any(s => Uri.IsWellFormedUriString(s, UriKind.Absolute))) &&
+ @event.Tags.Any(tag =>
+ tag.TagIdentifier == "network" && tag.Data.FirstOrDefault() == network)
+ ).Select(@event => new DiscoveredCoordinator()
+ {
+ Description = @event.Content,
+ Name = @event.PublicKey,
+ Uri = new Uri(@event.GetTaggedData("uri")
+ .First(s => Uri.IsWellFormedUriString(s, UriKind.Absolute)))
+ }).ToList();
+ }
+}
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml
index 601671f..5ab9533 100644
--- a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml
+++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiCoordinatorConfig/UpdateWabisabiSettings.cshtml
@@ -1,6 +1,7 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using WalletWasabi.Backend.Controllers
@using Microsoft.AspNetCore.Mvc.ModelBinding
+@using BTCPayServer.Plugins.Wabisabi
@model WalletWasabi.Backend.Controllers.WabisabiCoordinatorSettings
@inject WabisabiCoordinatorService WabisabiCoordinatorService
@@ -13,24 +14,12 @@