Validation of Nostr connection shouldn't be in CreateClient

This commit is contained in:
nicolas.dorier
2025-01-21 16:42:24 +09:00
parent 4220553564
commit 0c44da8dd1
3 changed files with 57 additions and 63 deletions

View File

@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Payments.Lightning;
using NBitcoin; using NBitcoin;
using NBitcoin.Secp256k1; using NBitcoin.Secp256k1;
using NNostr.Client; using NNostr.Client;
@@ -16,24 +17,28 @@ using SHA256 = System.Security.Cryptography.SHA256;
namespace BTCPayServer.Plugins.NIP05; namespace BTCPayServer.Plugins.NIP05;
public class NostrWalletConnectLightningClient : ILightningClient public class NostrWalletConnectLightningClient : IExtendedLightningClient
{ {
[Display] public string DisplayLabel => $"Nostr Wallet Connect {_connectParams.lud16} {_connectParams.relays.First()} "; [Display] public string DisplayLabel => $"Nostr Wallet Connect {_connectParams.lud16} {_connectParams.relays.First()} ";
public string? DisplayName => "Nostr Wallet Connect (NwC)";
public Uri? ServerUri => _serverUri;
private readonly NostrClientPool _nostrClientPool; private readonly NostrClientPool _nostrClientPool;
private readonly Uri _uri; private readonly Uri _uri;
private readonly Network _network; private readonly Network _network;
private readonly (string[] Commands, string[] Notifications) _commands;
private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectParams; private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectParams;
private readonly Uri? _serverUri;
public NostrWalletConnectLightningClient(NostrClientPool nostrClientPool, Uri uri, Network network, public NostrWalletConnectLightningClient(NostrClientPool nostrClientPool, Uri uri, Network network)
(string[] Commands, string[] Notifications) commands)
{ {
_nostrClientPool = nostrClientPool; _nostrClientPool = nostrClientPool;
_uri = uri; _uri = uri;
_network = network; _network = network;
_commands = commands;
_connectParams = NIP47.ParseUri(uri); _connectParams = NIP47.ParseUri(uri);
_serverUri = _connectParams.relays.FirstOrDefault();
} }
public override string ToString() public override string ToString()
@@ -245,15 +250,19 @@ public class NostrWalletConnectLightningClient : ILightningClient
} }
} }
public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = new()) public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = default)
{ {
var x = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation); var x = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cancellation);
if (_commands.Notifications?.Contains("payment_received") is true) var commands = await x.Item1.FetchNIP47AvailableCommands(_connectParams.Item1, cancellationToken: cancellation);
bool? hasNotification = commands?.Notifications?.Contains("payment_received");
if (hasNotification is false)
{ {
return new NotificationListener(_network, x, _connectParams); var response = await x.Item1.SendNIP47Request<NIP47.GetInfoResponse>(_connectParams.pubkey, _connectParams.secret, new NIP47.GetInfoRequest(), cancellationToken: cancellation);
hasNotification = response?.Notifications?.Contains("payment_received");
} }
return hasNotification is true
return new PollListener(_network, x, _connectParams); ? new NotificationListener(_network, x, _connectParams)
: new PollListener(_network, x, _connectParams);
} }
@@ -513,4 +522,36 @@ public class NostrWalletConnectLightningClient : ILightningClient
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public async Task<ValidationResult?> Validate()
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10));
var (client, disposable) = await _nostrClientPool.GetClientAndConnect(_connectParams.relays, cts.Token).ConfigureAwait(false);
using (disposable)
{
var commands = await client.FetchNIP47AvailableCommands(_connectParams.Item1, cancellationToken: cts.Token);
var requiredCommands = new[] { "get_info", "make_invoice", "lookup_invoice", "list_transactions" };
if (commands?.Commands is null || requiredCommands.Any(c => !commands.Value.Commands.Contains(c)))
{
return new ValidationResult("No commands available or not all required commands are available (get_info, make_invoice, lookup_invoice, list_transactions)");
}
var response = await client
.SendNIP47Request<NIP47.GetInfoResponse>(_connectParams.pubkey, _connectParams.secret,
new NIP47.GetInfoRequest(), cancellationToken: cts.Token);
var walletNetwork = response.Network;
if (!_network.ChainName.ToString().Equals(walletNetwork,
StringComparison.InvariantCultureIgnoreCase))
{
return new ValidationResult($"The network of the wallet ({walletNetwork}) does not match the network of the server ({_network.ChainName})");
}
if (response?.Methods is null || requiredCommands.Any(c => !response.Methods.Contains(c)))
{
return new ValidationResult("No commands available or not all required commands are available (get_info, make_invoice, lookup_invoice, list_transactions)");
}
}
return ValidationResult.Success;
}
} }

View File

@@ -39,55 +39,8 @@ public class NostrWalletConnectLightningConnectionStringHandler : ILightningConn
error = "Invalid nostr wallet connect uri"; error = "Invalid nostr wallet connect uri";
return null; return null;
} }
try
{
var connectParams = NIP47.ParseUri(uri);
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10));
var (client, disposable) = _nostrClientPool.GetClientAndConnect(connectParams.relays, cts.Token).ConfigureAwait(false).GetAwaiter().GetResult();
using (disposable)
{
var commands = client.FetchNIP47AvailableCommands(connectParams.Item1, cancellationToken: cts.Token)
.ConfigureAwait(false).GetAwaiter().GetResult();
var requiredCommands = new[] {"get_info", "make_invoice", "lookup_invoice", "list_transactions"};
if (commands?.Commands is null || requiredCommands.Any(c => !commands.Value.Commands.Contains(c)))
{
error =
"No commands available or not all required commands are available (get_info, make_invoice, lookup_invoice, list_transactions)";
return null;
}
var response = client
.SendNIP47Request<NIP47.GetInfoResponse>(connectParams.pubkey, connectParams.secret,
new NIP47.GetInfoRequest(), cancellationToken: cts.Token).ConfigureAwait(false).GetAwaiter()
.GetResult();
var walletNetwork = response.Network;
if (!network.ChainName.ToString().Equals(walletNetwork,
StringComparison.InvariantCultureIgnoreCase))
{
error =
$"The network of the wallet ({walletNetwork}) does not match the network of the server ({network.ChainName})";
return null;
}
if (response?.Methods is null || requiredCommands.Any(c => !response.Methods.Contains(c)))
{
error =
"No commands available or not all required commands are available (get_info, make_invoice, lookup_invoice, list_transactions)";
return null;
}
(string[] Commands, string[] Notifications) values = (response.Methods ?? commands.Value.Commands,
response.Notifications ?? commands.Value.Notifications);
error = null; error = null;
return new NostrWalletConnectLightningClient(_nostrClientPool, uri, network, values); return new NostrWalletConnectLightningClient(_nostrClientPool, uri, network);
}
}
catch (Exception e)
{
error = "Invalid nostr wallet connect uri: " + e.Message;
return null;
}
} }
} }