This commit is contained in:
Kukks
2023-04-23 11:31:56 +02:00
parent 2cf0e7b7fd
commit c9a5dbc7d1
7 changed files with 89 additions and 38 deletions

View File

@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=b4e2ed08_002D4ad3_002D4648_002D8bdb_002D3107200460b9_0023BTCPayServer_002EPlugins_002ELiquidPlus/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=b4e2ed08_002D4ad3_002D4648_002D8bdb_002D3107200460b9_0023BTCPayServer_002EPlugins_002ELiquidPlus/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nostr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using NBitcoin.DataEncoders;
using NBitcoin.Secp256k1;
using NNostr.Client;
using NNostr.Client.Protocols;
@@ -38,21 +37,35 @@ public class Nip5Controller : Controller
[HttpGet]
public async Task<IActionResult> Edit(string storeId)
{
var settings = await _storeRepository.GetSettingAsync<Nip5StoreSettings>(storeId, "NIP05");
var settings = await GetForStore(storeId);
return View(settings ?? new());
}
[NonAction]
public async Task<Nip5StoreSettings?> GetForStore(string storeId)
{
return await _memoryCache.GetOrCreateAsync("NIP05_" + storeId, async entry => await _storeRepository.GetSettingAsync<Nip5StoreSettings>(storeId, "NIP05"));
}
[NonAction]
public async Task UpdateStore(string storeId, Nip5StoreSettings? settings)
{
_memoryCache.Remove("NIP05_" + storeId);
await _storeRepository.UpdateSetting(storeId, "NIP05", settings);
_memoryCache.CreateEntry("NIP05_" + storeId).SetValue(settings);
}
[HttpPost]
public async Task<IActionResult> Edit(string storeId, Nip5StoreSettings settings, string command)
{
var existingSettings = await GetForStore(storeId);
if (command == "remove")
{
var settingss = await _storeRepository.GetSettingAsync<Nip5StoreSettings>(storeId, "NIP05");
if (settingss is not null)
if (existingSettings is not null)
{
await _storeRepository.UpdateSetting<Nip5StoreSettings>(storeId, "NIP05", null);
_memoryCache.Remove($"NIP05_{settingss.Name.ToLowerInvariant()}");
await UpdateStore(storeId, null);
_memoryCache.Remove($"NIP05_{existingSettings.Name.ToLowerInvariant()}");
}
return RedirectToAction("Edit", new {storeId});
@@ -139,13 +152,12 @@ k = settings.PrivateKey.FromNIP19Nsec();
return View(settings);
}
var settingssx = await _storeRepository.GetSettingAsync<Nip5StoreSettings>(storeId, "NIP05");
if (settingssx is not null)
if (existingSettings?.Name is not null)
{
_memoryCache.Remove($"NIP05_{settingssx.Name.ToLowerInvariant()}");
_memoryCache.Remove($"NIP05_{existingSettings.Name.ToLowerInvariant()}");
}
await _storeRepository.UpdateSetting(storeId, "NIP05", settings);
await UpdateStore(storeId, settings);
return RedirectToAction("Edit", new {storeId});
}
[NonAction]

View File

@@ -7,14 +7,25 @@ using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Events;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using NBitcoin;
using NBitcoin.Secp256k1;
using NNostr.Client;
namespace BTCPayServer.Plugins.NIP05;
public record ZapperSettings(string ZapperPrivateKey)
{
public ECPrivKey ZappingKey => NostrExtensions.ParseKey(ZapperPrivateKey);
public ECXOnlyPubKey ZappingPublicKey => ZappingKey.CreateXOnlyPubKey();
public string ZappingPublicKeyHex => ZappingPublicKey.ToHex();
}
public class Zapper : IHostedService
{
record PendingZapEvent(string[] relays, NostrEvent nostrEvent);
@@ -23,15 +34,28 @@ public class Zapper : IHostedService
private readonly Nip5Controller _nip5Controller;
private readonly IMemoryCache _memoryCache;
private readonly ILogger<Zapper> _logger;
private readonly SettingsRepository _settingsRepository;
private IEventAggregatorSubscription _subscription;
private ConcurrentBag<PendingZapEvent> _pendingZapEvents = new();
public Zapper(EventAggregator eventAggregator, Nip5Controller nip5Controller, IMemoryCache memoryCache, ILogger<Zapper> logger)
private async Task<ZapperSettings> GetSettings()
{ var result = await _settingsRepository.GetSettingAsync<ZapperSettings>( "Zapper");
if (result is not null) return result;
result = new ZapperSettings(Convert.ToHexString(RandomUtils.GetBytes(32)));
await _settingsRepository.UpdateSetting(result, "Zapper");
return result;
}
public Zapper(EventAggregator eventAggregator,
Nip5Controller nip5Controller, IMemoryCache memoryCache, ILogger<Zapper> logger, SettingsRepository settingsRepository, StoreRepository storeRepository)
{
_eventAggregator = eventAggregator;
_nip5Controller = nip5Controller;
_memoryCache = memoryCache;
_logger = logger;
_settingsRepository = settingsRepository;
}
public Task StartAsync(CancellationToken cancellationToken)
@@ -70,7 +94,27 @@ public class Zapper : IHostedService
var tcs = new TaskCompletionSource();
using var c = new NostrClient(new Uri(relay.Key));
await c.ConnectAndWaitUntilConnected(cts.Token);
var pendingOksOnIds = relay.Value.Select(a => a.Id).ToHashSet();
var subscription = new NostrSubscriptionFilter()
{
Ids = pendingOksOnIds.ToArray()
};
c.EventsReceived+= (sender, args) =>
{
foreach (var nostrEvent in args.events)
{
if (nostrEvent.Id == "zap-confirmation")
{
pendingOksOnIds.Remove(nostrEvent.Id);
if (!pendingOksOnIds.Any())
{
tcs.SetResult();
}
}
}
};
await c.CreateSubscription("zap-confirmations",new []{subscription}, cts.Token);
c.OkReceived += (sender, okargs) =>
{
pendingOksOnIds.Remove(okargs.eventId);
@@ -87,7 +131,6 @@ public class Zapper : IHostedService
}
catch (Exception e)
{
_logger.LogError(e, $"Error zapping to {relay.Key}");
}
}));
@@ -131,23 +174,11 @@ public class Zapper : IHostedService
}
var pmd = (LNURLPayPaymentMethodDetails) pm.GetPaymentMethodDetails();
var name = pmd.ConsumedLightningAddress.Split("@")[0];
var settings = await _nip5Controller.Get(name);
if (settings.storeId != arg.Invoice.StoreId)
{
return;
}
if (string.IsNullOrEmpty(settings.settings.PrivateKey))
{
return;
}
var key = NostrExtensions.ParseKey(settings.settings.PrivateKey);
var settings = await GetSettings();
var zapRequestEvent = JsonSerializer.Deserialize<NostrEvent>(zapRequest);
var relays = zapRequestEvent.Tags.Where(tag => tag.TagIdentifier == "relays").SelectMany(tag => tag.Data).ToArray();
var tags = zapRequestEvent.Tags.Where(a => a.TagIdentifier.Length == 1).ToList();
tags.Add(new()
{
@@ -165,15 +196,15 @@ public class Zapper : IHostedService
{
Kind = 9735,
CreatedAt = DateTimeOffset.UtcNow,
PublicKey = settings.settings.PubKey,
PublicKey = settings.ZappingPublicKeyHex,
Content = zapRequestEvent.Content,
Tags = tags
};
await zapReceipt.ComputeIdAndSignAsync(key);
_pendingZapEvents.Add(new PendingZapEvent(relays.Concat(settings.settings.Relays?? Array.Empty<string>()).Distinct().ToArray(), zapReceipt));
await zapReceipt.ComputeIdAndSignAsync(settings.ZappingKey);
var userNostrSettings = await _nip5Controller.GetForStore(arg.Invoice.StoreId);
_pendingZapEvents.Add(new PendingZapEvent(relays.Concat(userNostrSettings?.Relays?? Array.Empty<string>()).Distinct().ToArray(), zapReceipt));
}
public Task StopAsync(CancellationToken cancellationToken)

View File

@@ -137,7 +137,7 @@ namespace BTCPayServer.Plugins.Prism
lnClients.Add(payout.StoreDataId, lnClient);
}
if (lnClient is not null)
if (lnClient is not null && proof?.PaymentHash is not null)
{
var p = await lnClient.GetPayment(proof.PaymentHash, CancellationToken);
feePaid = (long) p.Fee.ToUnit(LightMoneyUnit.Satoshi);

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Crypto.Randomness;
using WalletWasabi.Extensions;
@@ -161,6 +162,14 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
}
solution.Coins.Add(coin);
// we make sure to spend all coins of the same script as it reduces the chance of the user stupidly consolidating later on
var scriptPubKey = coin.ScriptPubKey;
var reusedAddressCoins = remainingCoins.Where(smartCoin => smartCoin.ScriptPubKey == scriptPubKey).ToArray();
foreach (var reusedAddressCoin in reusedAddressCoins)
{
remainingCoins.Remove(reusedAddressCoin);
solution.Coins.Add(reusedAddressCoin);
}
// Loop through the pending payments and handle each payment by subtracting the payment amount from the total value of the selected coins
var potentialPayments = remainingPendingPayments
@@ -317,8 +326,6 @@ public class SubsetSolution
sc.TryGetValue(AnonsetType.Red, out var rcoins);
sb.AppendLine(
$"Solution total coins:{Coins.Count} R:{rcoins?.Length ?? 0} O:{ocoins?.Length ?? 0} G:{gcoins?.Length ?? 0} AL:{GetAnonLoss(Coins)} total value: {TotalValue} total payments:{TotalPaymentCost}/{TotalPaymentsGross} leftover: {LeftoverValue}");
sb.AppendLine(
$"Used coins: {string.Join(", ", Coins.Select(coin => coin.Outpoint + " " + coin.Amount.ToString() + " A" + coin.AnonymitySet))}");
if (HandledPayments.Any())
sb.AppendLine($"handled payments: {string.Join(", ", HandledPayments.Select(p => p.Value))} ");
return sb.ToString();

View File

@@ -60,7 +60,7 @@
{
<div class="alert alert-danger d-flex align-items-center" role="alert">
<vc:icon symbol="warning"/>
<span class="ms-3">This wallet is either not a hot wallet, or enabled in yout store settings and will not be able to participate in coinjoins.</span>
<span class="ms-3">This wallet is either not a hot wallet, or enabled in your store settings and will not be able to participate in coinjoins.</span>
</div>
}
}