feat: withdrawal

This commit is contained in:
2024-03-22 10:07:30 +00:00
parent 5d2a4c4360
commit 805a92537c
4 changed files with 128 additions and 7 deletions

View File

@ -1,7 +1,9 @@
using System.Security.Cryptography;
using System.Text;
using BTCPayServer.Lightning;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using Nostr.Client.Utils;
using NostrStreamer.ApiModel;
using NostrStreamer.Database;
@ -79,6 +81,90 @@ public class UserService
return invoice.PaymentRequest;
}
public async Task<(long Fee, string Preimage)> WithdrawFunds(string pubkey, string invoice)
{
var user = await GetUser(pubkey);
if (user == default) throw new Exception("No user found");
var maxOut = await MaxWithdrawalAmount(pubkey);
var pr = BOLT11PaymentRequest.Parse(invoice, invoice.StartsWith("lnbc1") ? Network.Main : Network.RegTest);
if (pr.MinimumAmount == 0)
{
throw new Exception("0 amount invoice not supported");
}
if (maxOut <= pr.MinimumAmount.MilliSatoshi)
{
throw new Exception("Not enough balance to pay invoice");
}
// start by taking balance
var rHash = pr.PaymentHash!.ToString();
await using (var tx = await _db.Database.BeginTransactionAsync())
{
await _db.Users
.Where(a => a.PubKey == pubkey)
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance - pr.MinimumAmount.MilliSatoshi));
_db.Payments.Add(new()
{
PubKey = pubkey,
Invoice = invoice,
Type = PaymentType.Withdrawal,
PaymentHash = rHash,
Amount = (ulong)pr.MinimumAmount.MilliSatoshi / 1000,
});
await tx.CommitAsync();
}
try
{
const double feeMax = 0.005; // 0.5% max fee
var result = await _lnd.SendPayment(invoice, (long)(pr.MinimumAmount.MilliSatoshi * feeMax));
if (result?.Status is Lnrpc.Payment.Types.PaymentStatus.Succeeded)
{
await _db.Payments
.Where(a => a.PaymentHash == rHash)
.ExecuteUpdateAsync(o => o.SetProperty(v => v.IsPaid, true)
.SetProperty(v => v.Amount, b => b.Amount + (ulong)result.FeeSat));
return (result.FeeMsat, result.PaymentPreimage);
}
throw new Exception("Payment failed");
}
catch
{
// return balance on error
await _db.Users
.Where(a => a.PubKey == pubkey)
.ExecuteUpdateAsync(p => p.SetProperty(o => o.Balance, b => b.Balance + pr.MinimumAmount.MilliSatoshi));
throw;
}
}
public async Task<List<Payment>> ListPayments(string pubkey)
{
return await _db.Payments
.Where(a => a.PubKey == pubkey && a.IsPaid)
.ToListAsync();
}
public async Task<long> MaxWithdrawalAmount(string pubkey)
{
var credit = 1000 * await _db.Payments
.Where(a => a.PubKey == pubkey &&
a.IsPaid &&
a.Type == PaymentType.Credit)
.SumAsync(a => (long)a.Amount);
var balance = await _db.Users
.Where(a => a.PubKey == pubkey)
.Select(a => a.Balance)
.FirstAsync();
return Math.Max(0, balance - credit);
}
public async Task<User?> GetUser(string pubkey)
{
return await _db.Users.AsNoTracking()
@ -124,4 +210,4 @@ public class UserService
.SetProperty(v => v.ContentWarning, req.ContentWarning)
.SetProperty(v => v.Goal, req.Goal));
}
}
}