diff --git a/NostrStreamer/Extensions.cs b/NostrStreamer/Extensions.cs index 5e06d6b..c61e77f 100644 --- a/NostrStreamer/Extensions.cs +++ b/NostrStreamer/Extensions.cs @@ -17,6 +17,11 @@ public static class Extensions { return NostrPrivateKey.FromBech32(cfg.PrivateKey).DerivePublicKey().Hex; } + + public static NostrPrivateKey GetPrivateKey(this Config cfg) + { + return NostrPrivateKey.FromBech32(cfg.PrivateKey); + } public static List GetVariants(this IngestEndpoint ep) { diff --git a/NostrStreamer/Program.cs b/NostrStreamer/Program.cs index 8066bb6..b229a73 100644 --- a/NostrStreamer/Program.cs +++ b/NostrStreamer/Program.cs @@ -46,6 +46,7 @@ internal static class Program services.AddSingleton(s => s.GetRequiredService()); services.AddSingleton(); services.AddHostedService(); + services.AddTransient(); // streaming services services.AddTransient(); diff --git a/NostrStreamer/Services/LndInvoiceStream.cs b/NostrStreamer/Services/LndInvoiceStream.cs index dfb80ca..e4d4e04 100644 --- a/NostrStreamer/Services/LndInvoiceStream.cs +++ b/NostrStreamer/Services/LndInvoiceStream.cs @@ -43,6 +43,7 @@ public class LndInvoicesStream : BackgroundService { using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); + var zapService = scope.ServiceProvider.GetRequiredService(); try { @@ -56,6 +57,10 @@ public class LndInvoicesStream : BackgroundService payment.IsPaid = true; payment.User.Balance += (long)(payment.Amount * 1000L); await db.SaveChangesAsync(stoppingToken); + if (!string.IsNullOrEmpty(payment.Nostr)) + { + zapService.HandlePaid(payment.Invoice, payment.Nostr); + } } } catch (Exception ex) diff --git a/NostrStreamer/Services/ZapService.cs b/NostrStreamer/Services/ZapService.cs new file mode 100644 index 0000000..f8a165e --- /dev/null +++ b/NostrStreamer/Services/ZapService.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using Nostr.Client.Client; +using Nostr.Client.Messages; +using Nostr.Client.Requests; + +namespace NostrStreamer.Services; + +public class ZapService +{ + private readonly ILogger _logger; + private readonly Config _config; + private readonly INostrClient _nostrClient; + + public ZapService(ILogger logger, Config config, INostrClient nostrClient) + { + _logger = logger; + _config = config; + _nostrClient = nostrClient; + } + + public void HandlePaid(string pr, string? zapRequest) + { + try + { + if (string.IsNullOrEmpty(zapRequest)) return; + + var zapNote = JsonConvert.DeserializeObject(zapRequest); + if (zapNote == default) + { + _logger.LogWarning("Could not parse zap note {note}", zapRequest); + return; + } + + var key = _config.GetPrivateKey(); + var pubKey = key.DerivePublicKey().Hex; + var tags = zapNote.Tags!.Where(a => a.TagIdentifier?.Length == 1).ToList(); + tags.Add(new("bolt11", pr)); + tags.Add(new("description", zapRequest)); + + var zapReceipt = new NostrEvent() + { + Kind = NostrKind.Zap, + CreatedAt = DateTime.UtcNow, + Pubkey = pubKey, + Content = zapNote.Content, + Tags = new(tags.ToArray()) + }; + + var zapReceiptSigned = zapReceipt.Sign(key); + + var jsonZap = JsonConvert.SerializeObject(zapReceiptSigned); + _logger.LogInformation("Created tip receipt {json}", jsonZap); + _nostrClient.Send(new NostrEventRequest(zapReceiptSigned)); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to handle zap"); + } + } +}