add nwc and bump nostr

This commit is contained in:
Kukks
2024-05-08 08:35:27 +02:00
parent 6e95ddb8b1
commit 3db5b5537d
10 changed files with 717 additions and 8 deletions

View File

@@ -11,7 +11,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Nostr </Product> <Product>Nostr </Product>
<Description>Allows you to verify your nostr account with NIP5 and zap like the rest of the crazies</Description> <Description>Allows you to verify your nostr account with NIP5 and zap like the rest of the crazies</Description>
<Version>1.1.3</Version> <Version>1.1.4</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->
@@ -36,7 +36,7 @@
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" /> <ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.38" /> <PackageReference Include="NNostr.Client" Version="0.0.43" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Resources" /> <Folder Include="Resources" />

View File

@@ -2,6 +2,7 @@ using System.Text;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services; using BTCPayServer.Abstractions.Services;
using BTCPayServer.Lightning;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Plugins.NIP05 namespace BTCPayServer.Plugins.NIP05
@@ -17,10 +18,15 @@ namespace BTCPayServer.Plugins.NIP05
{ {
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Nip05Nav", applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Nip05Nav",
"store-integrations-nav")); "store-integrations-nav"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("NWC/LNPaymentMethodSetupTab", "ln-payment-method-setup-tab"));
applicationBuilder.AddSingleton<IPluginHookFilter, LnurlDescriptionFilter>(); applicationBuilder.AddSingleton<IPluginHookFilter, LnurlDescriptionFilter>();
applicationBuilder.AddSingleton<IPluginHookFilter, LnurlFilter>(); applicationBuilder.AddSingleton<IPluginHookFilter, LnurlFilter>();
applicationBuilder.AddSingleton<Zapper>(); applicationBuilder.AddSingleton<Zapper>();
applicationBuilder.AddHostedService(sp => sp.GetRequiredService<Zapper>()); applicationBuilder.AddHostedService(sp => sp.GetRequiredService<Zapper>());
applicationBuilder.AddSingleton<NostrWalletConnectLightningConnectionStringHandler>();
applicationBuilder.AddSingleton<ILightningConnectionStringHandler>(provider => provider.GetRequiredService<NostrWalletConnectLightningConnectionStringHandler>());
base.Execute(applicationBuilder); base.Execute(applicationBuilder);
} }

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using NNostr.Client;
using NNostr.Client.Protocols;
namespace BTCPayServer.Plugins.NIP05;
public class NostrClientPool
{
private static readonly ConcurrentDictionary<string, NostrClientWrapper> _clientPool = new();
private static readonly Timer _cleanupTimer;
static NostrClientPool()
{
_cleanupTimer = new Timer(CleanupExpiredClients, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}
public static (INostrClient, IDisposable) GetClient(string connstring)
{
var connParams = NIP47.ParseUri(new Uri(connstring));
var clientWrapper = _clientPool.GetOrAdd(connstring.ToString(),
k => new NostrClientWrapper(new CompositeNostrClient(connParams.relays)));
clientWrapper.IncrementUsage();
return (clientWrapper.Client, new UsageDisposable(clientWrapper));
}
public static async Task<(INostrClient, IDisposable)> GetClientAndConnect(string connstring, CancellationToken token)
{
var result = GetClient(connstring);
await result.Item1.ConnectAndWaitUntilConnected(token, CancellationToken.None);
return result;
}
public static void KillClient(string connstring)
{
if (_clientPool.TryRemove(connstring, out var clientWrapper))
{
clientWrapper.Dispose();
}
}
private static void CleanupExpiredClients(object state)
{
foreach (var key in _clientPool.Keys)
{
if (_clientPool[key].IsExpired())
{
if (_clientPool.TryRemove(key, out var clientWrapper))
{
clientWrapper.Dispose();
}
}
}
}
private class UsageDisposable : IDisposable
{
private readonly NostrClientWrapper _clientWrapper;
public UsageDisposable(NostrClientWrapper clientWrapper)
{
_clientWrapper = clientWrapper;
}
public void Dispose()
{
_clientWrapper.DecrementUsage();
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Threading;
using NNostr.Client;
namespace BTCPayServer.Plugins.NIP05;
public class NostrClientWrapper : IDisposable
{
public INostrClient Client { get; private set; }
private int _usageCount = 0;
private bool _isDisposed = false;
private DateTimeOffset _lastUsed;
public NostrClientWrapper(INostrClient client)
{
Client = client;
_lastUsed = DateTimeOffset.UtcNow;
}
public void IncrementUsage()
{
_lastUsed = DateTimeOffset.UtcNow;
Interlocked.Increment(ref _usageCount);
}
public void DecrementUsage()
{
_lastUsed = DateTimeOffset.UtcNow;
if (Interlocked.Decrement(ref _usageCount) == 0 && IsExpired())
{
Dispose();
}
}
public bool IsExpired()
{
return DateTimeOffset.UtcNow - _lastUsed > TimeSpan.FromMinutes(5);
}
public void Dispose()
{
if (!_isDisposed)
{
Client.Dispose();
_isDisposed = true;
}
}
}

View File

@@ -0,0 +1,469 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using NBitcoin;
using NBitcoin.Secp256k1;
using NNostr.Client;
using NNostr.Client.Protocols;
namespace BTCPayServer.Plugins.NIP05;
public class NostrWalletConnectLightningClient : ILightningClient
{
private readonly Uri _uri;
private readonly Network _network;
private readonly (string[] Commands, string[] Notifications) _commands;
private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectParams;
public NostrWalletConnectLightningClient(Uri uri, Network network,
(string[] Commands, string[] Notifications) commands)
{
_uri = uri;
_network = network;
_commands = commands;
_connectParams = NIP47.ParseUri(uri);
}
public override string ToString()
{
return $"type=nwc;key={_uri}";
}
public async Task<LightningInvoice> GetInvoice(string invoiceId,
CancellationToken cancellation = new CancellationToken())
{
return await GetInvoice(uint256.Parse(invoiceId), cancellation);
}
private static LightningInvoice? ToLightningInvoice(NIP47.Nip47Transaction tx, Network network)
{
if (tx.Type != "incoming")
{
return null;
}
var isPaid = tx.SettledAt.HasValue;
var invoice = BOLT11PaymentRequest.Parse(tx.Invoice, network);
var expiresAt = tx.ExpiresAt is not null
? DateTimeOffset.FromUnixTimeSeconds(tx.ExpiresAt.Value)
: invoice.ExpiryDate;
var expired = !isPaid && expiresAt < DateTimeOffset.UtcNow;
var s = tx.SettledAt is not null
? DateTimeOffset.FromUnixTimeSeconds(tx.SettledAt.Value)
: (DateTimeOffset?) null;
return new LightningInvoice()
{
PaymentHash = tx.PaymentHash,
Amount = LightMoney.MilliSatoshis(tx.AmountMsats),
Preimage = tx.Preimage,
Id = tx.PaymentHash,
Status = isPaid ? LightningInvoiceStatus.Paid :
expired ? LightningInvoiceStatus.Expired : LightningInvoiceStatus.Unpaid,
PaidAt = s,
ExpiresAt = expiresAt,
AmountReceived = isPaid ? LightMoney.MilliSatoshis(tx.AmountMsats) : LightMoney.Zero,
BOLT11 = tx.Invoice
};
}
public async Task<LightningInvoice> GetInvoice(uint256 paymentHash,
CancellationToken cancellation = new CancellationToken())
{
var (nostrClient, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var tx = await nostrClient.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey,
_connectParams.secret,
new NIP47.LookupInvoiceRequest()
{
PaymentHash = paymentHash.ToString()
}, cancellation);
return ToLightningInvoice(tx, _network)!;
}
}
public async Task<LightningInvoice[]> ListInvoices(CancellationToken cancellation = new CancellationToken())
{
return await ListInvoices(new ListInvoicesParams(), cancellation);
}
public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request,
CancellationToken cancellation = new CancellationToken())
{
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var response = await client.SendNIP47Request<NIP47.ListTransactionsResponse>(_connectParams.pubkey,
_connectParams.secret,
new NIP47.ListTransactionsRequest()
{
Type = "incoming",
Offset = (int) (request.OffsetIndex ?? 0),
Unpaid = request.PendingOnly ?? false,
}, cancellation);
return response.Transactions.Select(transaction => ToLightningInvoice(transaction, _network))
.Where(i => i is not null).ToArray()!;
}
}
private LightningPayment? ToLightningPayment(NIP47.Nip47Transaction tx)
{
if (tx.Type != "outgoing")
{
return null;
}
var isPaid = tx.SettledAt.HasValue || !string.IsNullOrEmpty(tx.Preimage);
var invoice = BOLT11PaymentRequest.Parse(tx.Invoice, _network);
var expiresAt = tx.ExpiresAt is not null
? DateTimeOffset.FromUnixTimeSeconds(tx.ExpiresAt.Value)
: invoice.ExpiryDate;
var created = DateTimeOffset.FromUnixTimeSeconds(tx.CreatedAt);
var expired = !isPaid && expiresAt < DateTimeOffset.UtcNow;
var s = tx.SettledAt is not null
? DateTimeOffset.FromUnixTimeSeconds(tx.SettledAt.Value)
: (DateTimeOffset?) null;
return new LightningPayment()
{
PaymentHash = tx.PaymentHash,
Amount = LightMoney.MilliSatoshis(tx.AmountMsats),
Preimage = tx.Preimage,
Id = tx.PaymentHash,
Status = isPaid ? LightningPaymentStatus.Complete :
expired ? LightningPaymentStatus.Failed : LightningPaymentStatus.Unknown,
BOLT11 = tx.Invoice,
Fee = LightMoney.MilliSatoshis(tx.FeesPaidMsats),
AmountSent = LightMoney.MilliSatoshis(tx.AmountMsats + tx.FeesPaidMsats),
CreatedAt = created
};
}
public async Task<LightningPayment> GetPayment(string paymentHash,
CancellationToken cancellation = new CancellationToken())
{
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var tx = await client.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey, _connectParams.secret,
new NIP47.LookupInvoiceRequest()
{
PaymentHash = paymentHash
}, cancellation);
return ToLightningPayment(tx)!;
}
}
public async Task<LightningPayment[]> ListPayments(CancellationToken cancellation = new CancellationToken())
{
return await ListPayments(new ListPaymentsParams(), cancellation);
}
public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request,
CancellationToken cancellation = new CancellationToken())
{
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var response = await client.SendNIP47Request<NIP47.ListTransactionsResponse>(_connectParams.pubkey,
_connectParams.secret,
new NIP47.ListTransactionsRequest()
{
Type = "outgoing",
Offset = (int) (request.OffsetIndex ?? 0),
Unpaid = request.IncludePending ?? false,
}, cancellation);
return response.Transactions.Select(ToLightningPayment).Where(i => i is not null).ToArray()!;
}
}
public async Task<LightningInvoice> CreateInvoice(LightMoney amount, string description, TimeSpan expiry,
CancellationToken cancellation = new CancellationToken())
{
return await CreateInvoice(new CreateInvoiceParams(amount, description, expiry), cancellation);
}
public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest,
CancellationToken cancellation = new CancellationToken())
{
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var response = await client.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey,
_connectParams.secret,
new NIP47.MakeInvoiceRequest()
{
AmountMsats = createInvoiceRequest.Amount.MilliSatoshi,
Description = createInvoiceRequest.Description is null || createInvoiceRequest.DescriptionHashOnly
? null
: createInvoiceRequest.Description,
DescriptionHash = createInvoiceRequest.DescriptionHash?.ToString(),
ExpirySeconds = (int) createInvoiceRequest.Expiry.TotalSeconds,
}, cancellation);
return ToLightningInvoice(response, _network)!;
}
}
public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = new CancellationToken())
{
var x = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
if (_commands.Notifications?.Contains("payment_received") is true)
{
return new NotificationListener(_network, x, _connectParams);
}
return new PollListener(_network, x, _connectParams);
}
public class NotificationListener : ILightningInvoiceListener
{
private readonly Network _network;
private readonly INostrClient _client;
private readonly CancellationTokenSource _cts;
private readonly IAsyncEnumerable<NIP47.Nip47Notification> _notifications;
private readonly IDisposable _disposable;
public NotificationListener(Network network, (INostrClient, IDisposable) client,
(ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) x)
{
_network = network;
_client = client.Item1;
_disposable = client.Item2;
_cts = new CancellationTokenSource();
_notifications = _client.SubscribeNip47Notifications(x.pubkey, x.secret, _cts.Token);
}
public void Dispose()
{
_cts.Cancel();
_disposable.Dispose();
}
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
{
var enumerator = _notifications.GetAsyncEnumerator(cancellation);
while (await enumerator.MoveNextAsync(cancellation))
{
if (enumerator.Current.NotificationType == "payment_received")
{
var tx = enumerator.Current.Deserialize<NIP47.Nip47Transaction>();
return ToLightningInvoice(tx, _network)!;
}
}
throw new Exception("No notification received");
}
}
public class PollListener : ILightningInvoiceListener
{
private readonly Network _network;
private readonly (ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) _connectparams;
private readonly INostrClient _client;
private readonly CancellationTokenSource _cts;
private readonly IAsyncEnumerable<NIP47.Nip47Notification> _notifications;
private readonly IDisposable _disposable;
private NIP47.ListTransactionsResponse? _lastPaid;
private Channel<LightningInvoice> queue = Channel.CreateUnbounded<LightningInvoice>();
public PollListener(Network network, (INostrClient, IDisposable) client,
(ECXOnlyPubKey pubkey, ECPrivKey secret, Uri[] relays, string lud16) connectparams)
{
_network = network;
_connectparams = connectparams;
_client = client.Item1;
_disposable = client.Item2;
_cts = new CancellationTokenSource();
_ = Poll();
}
private async Task Poll()
{
try
{
while (!_cts.IsCancellationRequested)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
cts.CancelAfter(TimeSpan.FromSeconds(10));
var paid = await _client.SendNIP47Request<NIP47.ListTransactionsResponse>(_connectparams.pubkey,
_connectparams.secret, new NIP47.ListTransactionsRequest()
{
Type = "incoming",
// Unpaid = true, //seems like this is ignored... so we only get paid ones
Limit = 300
}, cancellationToken: cts.Token);
paid.Transactions = paid.Transactions.Where(i => i is {Type: "incoming", SettledAt: not null}).ToArray();
if (_lastPaid is not null)
{
var paidInvoicesSinceLastPoll = paid.Transactions.Where(i =>
_lastPaid.Transactions.All(j => j.PaymentHash != i.PaymentHash)).Select(i =>
ToLightningInvoice(i, _network)!);
//all invoices which are no longer in the unpaid list are paid
// var paidInvoicesSinceLastPoll = _lastPaid.Transactions
// .Where(i => paid.Transactions.All(j => j.PaymentHash != i.PaymentHash))
// .Select(i => ToLightningInvoice(i, _network)!);
foreach (var invoice in paidInvoicesSinceLastPoll)
{
await queue.Writer.WriteAsync(invoice,_cts.Token);
}
}
_lastPaid = paid;
await Task.Delay(1000, _cts.Token);
}
}
catch (Exception e)
{
Dispose();
}
}
public void Dispose()
{
_cts.Cancel();
_disposable.Dispose();
queue.Writer.Complete();
}
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
{
return await queue.Reader.ReadAsync(CancellationTokenSource
.CreateLinkedTokenSource(_cts.Token, cancellation).Token);
}
}
public async Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
public async Task<LightningNodeBalance> GetBalance(CancellationToken cancellation = new CancellationToken())
{
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var response = await client.SendNIP47Request<NIP47.GetBalanceResponse>(_connectParams.pubkey,
_connectParams.secret,
new NIP47.NIP47Request("get_balance"), cancellation);
return new LightningNodeBalance()
{
OffchainBalance = new OffchainBalance()
{
Local = LightMoney.MilliSatoshis(response.BalanceMsats),
}
};
}
}
public async Task<PayResponse> Pay(PayInvoiceParams payParams,
CancellationToken cancellation = new CancellationToken())
{
return await Pay(null, new PayInvoiceParams(), cancellation);
}
public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams,
CancellationToken cancellation = new CancellationToken())
{
try
{
var (client, usage) = await NostrClientPool.GetClientAndConnect(_uri.ToString(), cancellation);
using (usage)
{
var response = await client.SendNIP47Request<NIP47.Nip47Transaction>(_connectParams.pubkey,
_connectParams.secret,
bolt11 is null
? new NIP47.PayKeysendRequest()
{
Amount = Convert.ToDecimal(payParams.Amount.MilliSatoshi),
Pubkey = payParams.Destination.ToHex(),
TlvRecords = payParams.CustomRecords?.Select(kv => new NIP47.TlvRecord()
{
Type = kv.Key.ToString(),
Value = kv.Value
}).ToArray()
}
: new NIP47.PayInvoiceRequest()
{
Invoice = bolt11,
Amount = payParams.Amount?.MilliSatoshi is not null
? Convert.ToDecimal(payParams.Amount.MilliSatoshi)
: null,
}, cancellation);
var lp = ToLightningPayment(response);
return new PayResponse(lp.Status == LightningPaymentStatus.Complete ? PayResult.Ok : PayResult.Error,
new PayDetails()
{
Preimage = lp.Preimage is null ? null : new uint256(lp.Preimage),
Status = lp.Status,
TotalAmount = lp.AmountSent,
PaymentHash = new uint256(lp.PaymentHash),
FeeAmount = lp.Fee
});
}
}
catch (Exception e)
{
return new PayResponse()
{
Result = PayResult.Error,
ErrorDetail = e.Message
};
}
}
public async Task<PayResponse> Pay(string bolt11, CancellationToken cancellation = new CancellationToken())
{
return await Pay(bolt11, new PayInvoiceParams(), cancellation);
}
public async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest,
CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
public async Task<BitcoinAddress> GetDepositAddress(CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
public async Task<ConnectionResult> ConnectTo(NodeInfo nodeInfo,
CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
public async Task<LightningChannel[]> ListChannels(CancellationToken cancellation = new CancellationToken())
{
throw new NotSupportedException();
}
}

View File

@@ -0,0 +1,67 @@
#nullable enable
using System;
using System.Linq;
using System.Threading;
using BTCPayServer.Lightning;
using NBitcoin;
using NNostr.Client.Protocols;
namespace BTCPayServer.Plugins.NIP05;
public class NostrWalletConnectLightningConnectionStringHandler : ILightningConnectionStringHandler
{
public ILightningClient? Create(string connectionString, Network network, out string? error)
{
if (!connectionString.StartsWith(NIP47.UriScheme, StringComparison.OrdinalIgnoreCase) && !connectionString.StartsWith("type=nwc;key="))
{
error = null;
return null;
}
connectionString = connectionString.Replace("type=nwc;key=", "");
try
{
Uri.TryCreate(connectionString, UriKind.Absolute, out var uri);
var connectParams = NIP47.ParseUri(uri); var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10));
var (client, disposable) = NostrClientPool.GetClientAndConnect(connectionString, 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;
}
error = null;
return new NostrWalletConnectLightningClient(uri, network, commands.Value);
}
}
catch (Exception e)
{
error = "Invalid nostr wallet connect uri";
return null;
}
}
}

View File

@@ -0,0 +1,41 @@
@model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel
@{
var storeId = Model.StoreId;
if (Model.CryptoCode != "BTC")
{
return;
}
}
<script>
document.addEventListener("DOMContentLoaded", function () {
const customNodeAccordian = document.getElementById("CustomNodeSupport");
const template = document.getElementById("nwc");
customNodeAccordian.appendChild(template.content.cloneNode(true));
});
</script>
<template id="nwc">
<div class="accordion-item">
<h2 class="accordion-header" id="CustomNWCHeader">
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#CustomNWCContent" aria-controls="CustomNWCContent" aria-expanded="false">
<span><strong>Nostr Wallet Connect</strong> via NIP47</span>
<vc:icon symbol="caret-down"/>
</button>
</h2>
<div id="CustomNWCContent" class="accordion-collapse collapse" aria-labelledby="CustomNWCHeader" data-bs-parent="#CustomNodeSupport">
<div class="accordion-body">
<ul class="pb-2">
<li>
<code><b>type=</b>nwc;<b>key=</b>b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c</code>
</li>
<li>
<code><b>nostr+walletconnect:</b>b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c</code>
</li>
</ul>
</div>
</div>
</div>
</template>

View File

@@ -44,7 +44,7 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.40"></PackageReference> <PackageReference Include="NNostr.Client" Version="0.0.43"></PackageReference>
<PackageReference Include="WabiSabi" Version="1.0.1.2"/> <PackageReference Include="WabiSabi" Version="1.0.1.2"/>
</ItemGroup> </ItemGroup>
<Target Name="DeleteExampleFile" AfterTargets="Publish"> <Target Name="DeleteExampleFile" AfterTargets="Publish">

View File

@@ -59,7 +59,7 @@ public class Nostr
{ {
new() {TagIdentifier = EndpointTagIdentifier, Data = new List<string>() {new Uri(coordinatorUri, "plugins/wabisabi-coordinator/").ToString()}}, new() {TagIdentifier = EndpointTagIdentifier, Data = new List<string>() {new Uri(coordinatorUri, "plugins/wabisabi-coordinator/").ToString()}},
new() {TagIdentifier = TypeTagIdentifier, Data = new List<string>() { TypeTagValue}}, new() {TagIdentifier = TypeTagIdentifier, Data = new List<string>() { TypeTagValue}},
new() {TagIdentifier = NetworkTagIdentifier, Data = new List<string>() {currentNetwork.Name.ToLower()}} new() {TagIdentifier = NetworkTagIdentifier, Data = new List<string>() {currentNetwork.ChainName.ToString().ToLower()}}
} }
}; };
@@ -83,12 +83,13 @@ public class Nostr
} }
}); });
var result = new List<NostrEvent>(); var result = new List<NostrEvent>();
var network = currentNetwork.Name.ToLower(); var network = currentNetwork.ChainName.ToString().ToLower();
var cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token, var cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token,
cancellationToken); cancellationToken);
await nostrClient.Connect(cts.Token); await nostrClient.Connect(cts.Token);
result = await nostrClient.SubscribeForEvents( result = await nostrClient.SubscribeForEvents(
new[] new[]
{ {
@@ -98,7 +99,7 @@ public class Nostr
ExtensionData = new Dictionary<string, JsonElement>() ExtensionData = new Dictionary<string, JsonElement>()
{ {
["#type"] = JsonSerializer.SerializeToElement(new[] {TypeTagValue}), ["#type"] = JsonSerializer.SerializeToElement(new[] {TypeTagValue}),
["#network"] = JsonSerializer.SerializeToElement(new[] {network}) ["#network"] = JsonSerializer.SerializeToElement(new[] {network, currentNetwork.Name.ToLower()})
}, },
Limit = 1000 Limit = 1000
} }