This commit is contained in:
Kukks
2023-11-03 15:07:44 +01:00
parent ba68159a3a
commit 96b2d273de
15 changed files with 687 additions and 92 deletions

View File

@@ -54,6 +54,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Dynami
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Bringin", "Plugins\BTCPayServer.Plugins.Bringin\BTCPayServer.Plugins.Bringin.csproj", "{D4AFEC95-64D4-4FC4-9AE4-B82F4C6D6E29}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Bringin", "Plugins\BTCPayServer.Plugins.Bringin\BTCPayServer.Plugins.Bringin.csproj", "{D4AFEC95-64D4-4FC4-9AE4-B82F4C6D6E29}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.LDK", "Plugins\BTCPayServer.Plugins.LDK\BTCPayServer.Plugins.LDK.csproj", "{661DBF95-0F60-49C0-829A-C5997B44AF60}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -262,6 +264,14 @@ Global
{5934F898-00B1-4781-BD18-04DF8685BC76}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU {5934F898-00B1-4781-BD18-04DF8685BC76}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
{5934F898-00B1-4781-BD18-04DF8685BC76}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU {5934F898-00B1-4781-BD18-04DF8685BC76}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
{5934F898-00B1-4781-BD18-04DF8685BC76}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU {5934F898-00B1-4781-BD18-04DF8685BC76}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Debug|Any CPU.Build.0 = Debug|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Release|Any CPU.ActiveCfg = Release|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Release|Any CPU.Build.0 = Release|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
{661DBF95-0F60-49C0-829A-C5997B44AF60}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962} {B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}

View File

@@ -5,10 +5,13 @@ using System.Threading.Tasks;
using Breez.Sdk; using Breez.Sdk;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Lightning;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Plugins.Breez; namespace BTCPayServer.Plugins.Breez;
@@ -108,8 +111,8 @@ public class BreezController : Controller
return View((object) storeId); return View((object) storeId);
} }
[HttpGet("swapin/create")] [HttpGet("send")]
public async Task<IActionResult> SwapInCreate(string storeId) public async Task<IActionResult> Send(string storeId)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
if (client is null) if (client is null)
@@ -117,9 +120,85 @@ public class BreezController : Controller
return RedirectToAction(nameof(Configure), new {storeId}); return RedirectToAction(nameof(Configure), new {storeId});
} }
client.Sdk.ReceiveOnchain(new ReceiveOnchainRequest()); return View((object) storeId);
TempData[WellKnownTempData.SuccessMessage] = "Swapin created successfully"; }
return RedirectToAction(nameof(SwapIn), new {storeId}); [Route("receive")]
public async Task<IActionResult> Receive(string storeId, ulong? amount)
{
var client = _breezService.GetClient(storeId);
if (client is null)
{
return RedirectToAction(nameof(Configure), new {storeId});
}
if (amount is not null)
{
var invoice = await client.CreateInvoice(LightMoney.FromUnit(amount.Value, LightMoneyUnit.Satoshi).MilliSatoshi, null, TimeSpan.Zero);
TempData["bolt11"] = invoice.BOLT11;
return RedirectToAction("Payments", "Breez", new {storeId });
}
return View((object) storeId);
}
[HttpPost("send")]
public async Task<IActionResult> Send(string storeId, string address, ulong? amount)
{
var client = _breezService.GetClient(storeId);
if (client is null)
{
return RedirectToAction(nameof(Configure), new {storeId});
}
var payParams = new PayInvoiceParams();
string bolt11 = null;
if (HexEncoder.IsWellFormed(address))
{
if (PubKey.TryCreatePubKey(ConvertHelper.FromHexString(address), out var pubKey))
{
if (amount is null)
{
TempData[WellKnownTempData.ErrorMessage] =
$"Cannot do keysend payment without specifying an amount";
return RedirectToAction(nameof(Send), new {storeId});
}
payParams.Amount = amount.Value * 1000;
payParams.Destination = pubKey;
}
else
{
TempData[WellKnownTempData.ErrorMessage] = $"invalid nodeid";
return RedirectToAction(nameof(Send), new {storeId});
}
}
else
{
bolt11 = address;
if (amount is not null)
{
payParams.Amount = amount.Value * 1000;
}
}
var result = await client.Pay(bolt11, payParams);
switch (result.Result)
{
case PayResult.Ok:
TempData[WellKnownTempData.SuccessMessage] = $"Sending successful";
break;
case PayResult.Unknown:
case PayResult.CouldNotFindRoute:
case PayResult.Error:
default:
TempData[WellKnownTempData.ErrorMessage] = $"Sending did not indicate success";
break;
}
return RedirectToAction(nameof(Payments), new {storeId});
} }
@@ -207,7 +286,7 @@ public class BreezController : Controller
return View(await _breezService.Get(storeId)); return View(await _breezService.Get(storeId));
} }
[HttpPost("")] [HttpPost("configure")]
public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings) public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings)
{ {
if (command == "clear") if (command == "clear")
@@ -244,9 +323,10 @@ public class BreezController : Controller
{ {
return RedirectToAction(nameof(Configure), new {storeId}); return RedirectToAction(nameof(Configure), new {storeId});
} }
viewModel ??= new PaymentsViewModel(); viewModel ??= new PaymentsViewModel();
viewModel.Payments = client.Sdk.ListPayments(new ListPaymentsRequest(PaymentTypeFilter.ALL, null, null, null, viewModel.Payments = client.Sdk.ListPayments(new ListPaymentsRequest(PaymentTypeFilter.ALL, null, null, true,
(uint?) viewModel.Skip, (uint?) viewModel.Count)); (uint?) viewModel.Skip, (uint?) viewModel.Count));
return View(viewModel); return View(viewModel);

View File

@@ -10,34 +10,43 @@ using Network = Breez.Sdk.Network;
namespace BTCPayServer.Plugins.Breez; namespace BTCPayServer.Plugins.Breez;
public class BreezLightningClient : ILightningClient, IDisposable, EventListener
public class BreezLightningClient: ILightningClient, IDisposable, EventListener
{ {
public override string ToString()
{
return $"type=breez;key={PaymentKey}";
}
private readonly NBitcoin.Network _network; private readonly NBitcoin.Network _network;
public readonly string PaymentKey;
public BreezLightningClient(string inviteCode, string apiKey, string workingDir, NBitcoin.Network network, public BreezLightningClient(string inviteCode, string apiKey, string workingDir, NBitcoin.Network network,
string mnemonic) string mnemonic, string paymentKey)
{ {
_network = network; _network = network;
PaymentKey = paymentKey;
var nodeConfig = new NodeConfig.Greenlight( var nodeConfig = new NodeConfig.Greenlight(
new GreenlightNodeConfig(null, inviteCode) new GreenlightNodeConfig(null, inviteCode)
); );
var config = BreezSdkMethods.DefaultConfig( var config = BreezSdkMethods.DefaultConfig(
network ==NBitcoin.Network.Main ? EnvironmentType.PRODUCTION: EnvironmentType.STAGING, network == NBitcoin.Network.Main ? EnvironmentType.PRODUCTION : EnvironmentType.STAGING,
apiKey, apiKey,
nodeConfig nodeConfig
) with { ) with
workingDir= workingDir, {
network = network == NBitcoin.Network.Main ? Network.BITCOIN : network == NBitcoin.Network.TestNet ? Network.TESTNET: network == NBitcoin.Network.RegTest? Network.REGTEST: Network.SIGNET workingDir = workingDir,
network = network == NBitcoin.Network.Main ? Network.BITCOIN :
network == NBitcoin.Network.TestNet ? Network.TESTNET :
network == NBitcoin.Network.RegTest ? Network.REGTEST : Network.SIGNET
}; };
var seed = BreezSdkMethods.MnemonicToSeed(mnemonic); var seed = BreezSdkMethods.MnemonicToSeed(mnemonic);
Sdk = BreezSdkMethods.Connect(config, seed, this); Sdk = BreezSdkMethods.Connect(config, seed, this);
} }
public BlockingBreezServices Sdk { get; } public BlockingBreezServices Sdk { get; }
public event EventHandler<BreezEvent> EventReceived; public event EventHandler<BreezEvent> EventReceived;
public void OnEvent(BreezEvent e) public void OnEvent(BreezEvent e)
{ {
EventReceived?.Invoke(this, e); EventReceived?.Invoke(this, e);
@@ -73,15 +82,16 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
Fee = LightMoney.MilliSatoshis(payment.feeMsat), Fee = LightMoney.MilliSatoshis(payment.feeMsat),
AmountSent = LightMoney.MilliSatoshis(payment.amountMsat) AmountSent = LightMoney.MilliSatoshis(payment.amountMsat)
}; };
} }
private LightningInvoice FromPayment(Payment p) private LightningInvoice FromPayment(Payment p)
{ {
if (p?.details is not PaymentDetails.Ln lnPaymentDetails) if (p?.details is not PaymentDetails.Ln lnPaymentDetails)
{ {
return null; return null;
} }
var bolt11 = BOLT11PaymentRequest.Parse(lnPaymentDetails.data.bolt11, _network); var bolt11 = BOLT11PaymentRequest.Parse(lnPaymentDetails.data.bolt11, _network);
return new LightningInvoice() return new LightningInvoice()
@@ -97,7 +107,7 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
PaymentStatus.COMPLETE => LightningInvoiceStatus.Paid, PaymentStatus.COMPLETE => LightningInvoiceStatus.Paid,
_ => LightningInvoiceStatus.Unpaid _ => LightningInvoiceStatus.Unpaid
}, },
PaidAt = DateTimeOffset.FromUnixTimeMilliseconds(p.paymentTime), PaidAt = DateTimeOffset.FromUnixTimeSeconds(p.paymentTime),
ExpiresAt = bolt11.ExpiryDate ExpiresAt = bolt11.ExpiryDate
}; };
} }
@@ -105,18 +115,28 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, CancellationToken cancellation = default) public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, CancellationToken cancellation = default)
{ {
var p = Sdk.PaymentByHash(paymentHash.ToString()!); var p = Sdk.PaymentByHash(paymentHash.ToString()!);
if(p is null)
return new LightningInvoice()
{
Id = paymentHash.ToString(),
PaymentHash = paymentHash.ToString(),
Status = LightningInvoiceStatus.Expired,
};
return FromPayment(p); return FromPayment(p);
} }
public async Task<LightningInvoice[]> ListInvoices(CancellationToken cancellation = default) public async Task<LightningInvoice[]> ListInvoices(CancellationToken cancellation = default)
{ {
return await ListInvoices(null, cancellation); return await ListInvoices(null, cancellation);
} }
public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request, CancellationToken cancellation = default) public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request,
CancellationToken cancellation = default)
{ {
return Sdk.ListPayments(new ListPaymentsRequest(PaymentTypeFilter.RECEIVED, null, null, request?.PendingOnly is not true, (uint?) request?.OffsetIndex, null)) return Sdk.ListPayments(new ListPaymentsRequest(PaymentTypeFilter.RECEIVED, null, null,
request?.PendingOnly is not true, (uint?) request?.OffsetIndex, null))
.Select(FromPayment).ToArray(); .Select(FromPayment).ToArray();
} }
@@ -130,26 +150,48 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
return await ListPayments(null, cancellation); return await ListPayments(null, cancellation);
} }
public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request, CancellationToken cancellation = default) public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request,
CancellationToken cancellation = default)
{ {
return Sdk.ListPayments(new ListPaymentsRequest(PaymentTypeFilter.RECEIVED, null, null, null, (uint?) request?.OffsetIndex, null)) return Sdk.ListPayments(new ListPaymentsRequest(PaymentTypeFilter.RECEIVED, null, null, null,
(uint?) request?.OffsetIndex, null))
.Select(ToLightningPayment).ToArray(); .Select(ToLightningPayment).ToArray();
} }
public async Task<LightningInvoice> CreateInvoice(LightMoney amount, string description, TimeSpan expiry, CancellationToken cancellation = default) public async Task<LightningInvoice> CreateInvoice(LightMoney amount, string description, TimeSpan expiry,
CancellationToken cancellation = default)
{ {
var expiryS = expiry == TimeSpan.Zero ? (uint?) null : Math.Max(0, (uint) expiry.TotalSeconds);
var expiryS =expiry == TimeSpan.Zero? (uint?) null: Math.Max(0, (uint)expiry.TotalSeconds); var p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong) amount.MilliSatoshi, description, null, null,
var p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong)amount.MilliSatoshi, description, null, null, false,expiryS )); false, expiryS));
return await GetInvoice(p.lnInvoice.paymentHash, cancellation); return FromPR(p);
} }
public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest, CancellationToken cancellation = default) public LightningInvoice FromPR(ReceivePaymentResponse response)
{ {
var expiryS =createInvoiceRequest.Expiry == TimeSpan.Zero? (uint?) null: Math.Max(0, (uint)createInvoiceRequest.Expiry.TotalSeconds); return new LightningInvoice()
var p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong)createInvoiceRequest.Amount.MilliSatoshi, (createInvoiceRequest.Description??createInvoiceRequest.DescriptionHash.ToString())!, null, null, createInvoiceRequest.DescriptionHashOnly,expiryS )); {
return await GetInvoice(p.lnInvoice.paymentHash, cancellation); Amount = LightMoney.MilliSatoshis(response.lnInvoice.amountMsat ?? 0),
Id = response.lnInvoice.paymentHash,
Preimage = ConvertHelper.ToHexString(response.lnInvoice.paymentSecret.ToArray()),
PaymentHash = response.lnInvoice.paymentHash,
BOLT11 = response.lnInvoice.bolt11,
Status = LightningInvoiceStatus.Unpaid,
ExpiresAt = DateTimeOffset.FromUnixTimeSeconds((long) response.lnInvoice.expiry)
};
}
public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest,
CancellationToken cancellation = default)
{
var expiryS = createInvoiceRequest.Expiry == TimeSpan.Zero
? (uint?) null
: Math.Max(0, (uint) createInvoiceRequest.Expiry.TotalSeconds);
var p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong) createInvoiceRequest.Amount.MilliSatoshi,
(createInvoiceRequest.Description ?? createInvoiceRequest.DescriptionHash.ToString())!, null, null,
createInvoiceRequest.DescriptionHashOnly, expiryS));
return FromPR(p);
} }
public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = default) public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = default)
@@ -159,14 +201,16 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
public async Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = default) public async Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = default)
{ {
var ni = Sdk.NodeInfo(); var ni = Sdk.NodeInfo();
return new LightningNodeInformation() return new LightningNodeInformation()
{ {
PeersCount = ni.connectedPeers.Count, PeersCount = ni.connectedPeers.Count,
Alias = $"greenlight {ni.id}", Alias = $"greenlight {ni.id}",
NodeInfoList = {new NodeInfo(new PubKey(ni.id), "blockstrean.com", 69)},//we have to fake this as btcpay currently requires this to enable the payment method NodeInfoList =
BlockHeight = (int)ni.blockHeight {
new NodeInfo(new PubKey(ni.id), "blockstrean.com", 69)
}, //we have to fake this as btcpay currently requires this to enable the payment method
BlockHeight = (int) ni.blockHeight
}; };
} }
@@ -194,9 +238,53 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
return await Pay(null, payParams, cancellation); return await Pay(null, payParams, cancellation);
} }
public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams, CancellationToken cancellation = default) public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams,
CancellationToken cancellation = default)
{ {
throw new NotImplementedException(); SendPaymentResponse result;
try
{
if (bolt11 is null)
{
result = Sdk.SendSpontaneousPayment(new SendSpontaneousPaymentRequest(payParams.Destination.ToString(),
(ulong) payParams.Amount.MilliSatoshi));
}
else
{
result = Sdk.SendPayment(new SendPaymentRequest(bolt11, (ulong?) payParams.Amount?.MilliSatoshi));
}
var details = result.payment.details as PaymentDetails.Ln;
return new PayResponse()
{
Result = result.payment.status switch
{
PaymentStatus.FAILED => PayResult.Error,
PaymentStatus.COMPLETE => PayResult.Ok,
PaymentStatus.PENDING => PayResult.Unknown,
_ => throw new ArgumentOutOfRangeException()
},
Details = new PayDetails()
{
Status = result.payment.status switch
{
PaymentStatus.FAILED => LightningPaymentStatus.Failed,
PaymentStatus.COMPLETE => LightningPaymentStatus.Complete,
PaymentStatus.PENDING => LightningPaymentStatus.Pending,
_ => LightningPaymentStatus.Unknown
},
Preimage =
details.data.paymentPreimage is null ? null : uint256.Parse(details.data.paymentPreimage),
PaymentHash = details.data.paymentHash is null ? null : uint256.Parse(details.data.paymentHash),
FeeAmount = result.payment.feeMsat,
TotalAmount = LightMoney.MilliSatoshis(result.payment.amountMsat + result.payment.feeMsat),
}
};
}
catch (Exception e)
{
return new PayResponse(PayResult.Error, e.Message);
}
} }
public async Task<PayResponse> Pay(string bolt11, CancellationToken cancellation = default) public async Task<PayResponse> Pay(string bolt11, CancellationToken cancellation = default)
@@ -204,7 +292,8 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
return await Pay(bolt11, null, cancellation); return await Pay(bolt11, null, cancellation);
} }
public async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest, CancellationToken cancellation = default) public async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest,
CancellationToken cancellation = default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -221,7 +310,6 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = default) public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = default)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -236,7 +324,7 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
Sdk.Dispose(); Sdk.Dispose();
} }
public class BreezInvoiceListener: ILightningInvoiceListener public class BreezInvoiceListener : ILightningInvoiceListener
{ {
private readonly BreezLightningClient _breezLightningClient; private readonly BreezLightningClient _breezLightningClient;
private readonly CancellationToken _cancellationToken; private readonly CancellationToken _cancellationToken;
@@ -266,17 +354,18 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation) public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
{ {
while(cancellation.IsCancellationRequested is not true) while (cancellation.IsCancellationRequested is not true)
{ {
if (_invoices.TryDequeue(out var task)) if (_invoices.TryDequeue(out var task))
{ {
return await task.WithCancellation(cancellation); return await task.WithCancellation(cancellation);
} }
await Task.Delay(100, cancellation); await Task.Delay(100, cancellation);
} }
cancellation.ThrowIfCancellationRequested(); cancellation.ThrowIfCancellationRequested();
return null; return null;
} }
} }
} }

View File

@@ -7,17 +7,18 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NBitcoin; using NBitcoin;
namespace BTCPayServer.Plugins.Breez; namespace BTCPayServer.Plugins.Breez;
public class BreezService:IHostedService public class BreezService:EventHostedServiceBase
{ {
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly IOptions<DataDirectories> _dataDirectories; private readonly IOptions<DataDirectories> _dataDirectories;
@@ -26,10 +27,12 @@ public class BreezService:IHostedService
private Dictionary<string, BreezSettings> _settings; private Dictionary<string, BreezSettings> _settings;
private Dictionary<string, BreezLightningClient> _clients = new(); private Dictionary<string, BreezLightningClient> _clients = new();
public BreezService(StoreRepository storeRepository, public BreezService(
EventAggregator eventAggregator,
StoreRepository storeRepository,
IOptions<DataDirectories> dataDirectories, IOptions<DataDirectories> dataDirectories,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
ILogger<BreezService> logger) ILogger<BreezService> logger) : base(eventAggregator, logger)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_dataDirectories = dataDirectories; _dataDirectories = dataDirectories;
@@ -37,6 +40,22 @@ public class BreezService:IHostedService
_logger = logger; _logger = logger;
} }
protected override void SubscribeToEvents()
{
Subscribe<StoreRemovedEvent>();
base.SubscribeToEvents();
}
protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
{
if (evt is StoreRemovedEvent storeRemovedEvent)
{
await Handle(storeRemovedEvent.StoreId, null);
_settings.Remove(storeRemovedEvent.StoreId);
}
await base.ProcessEvent(evt, cancellationToken);
}
private string GetWorkDir() private string GetWorkDir()
{ {
var dir = _dataDirectories.Value.DataDir; var dir = _dataDirectories.Value.DataDir;
@@ -44,7 +63,7 @@ public class BreezService:IHostedService
} }
TaskCompletionSource tcs = new(); TaskCompletionSource tcs = new();
public async Task StartAsync(CancellationToken cancellationToken) public override async Task StartAsync(CancellationToken cancellationToken)
{ {
_settings = (await _storeRepository.GetSettingsAsync<BreezSettings>("Breez")).Where(pair => pair.Value is not null).ToDictionary(pair => pair.Key, pair => pair.Value!); _settings = (await _storeRepository.GetSettingsAsync<BreezSettings>("Breez")).Where(pair => pair.Value is not null).ToDictionary(pair => pair.Key, pair => pair.Value!);
foreach (var keyValuePair in _settings) foreach (var keyValuePair in _settings)
@@ -59,6 +78,7 @@ public class BreezService:IHostedService
} }
} }
tcs.TrySetResult(); tcs.TrySetResult();
await base.StartAsync(cancellationToken);
} }
public async Task<BreezSettings?> Get(string storeId) public async Task<BreezSettings?> Get(string storeId)
@@ -85,7 +105,7 @@ public class BreezService:IHostedService
var network = Network.Main; // _btcPayNetworkProvider.BTC.NBitcoinNetwork; var network = Network.Main; // _btcPayNetworkProvider.BTC.NBitcoinNetwork;
Directory.CreateDirectory(GetWorkDir()); Directory.CreateDirectory(GetWorkDir());
var client = new BreezLightningClient(settings.InviteCode, settings.ApiKey, GetWorkDir(), var client = new BreezLightningClient(settings.InviteCode, settings.ApiKey, GetWorkDir(),
network, settings.Mnemonic); network, settings.Mnemonic, settings.PaymentKey);
if (storeId is not null) if (storeId is not null)
{ {
_clients.AddOrReplace(storeId, client); _clients.AddOrReplace(storeId, client);

View File

@@ -6,6 +6,7 @@
var storeId = Context.GetCurrentStoreId(); var storeId = Context.GetCurrentStoreId();
ViewData.SetActivePage("Breez", "Payments", "Payments"); ViewData.SetActivePage("Breez", "Payments", "Payments");
TempData.TryGetValue("bolt11", out var bolt11);
} }
<div class="row mb-4"> <div class="row mb-4">
@@ -16,11 +17,28 @@
</h3> </h3>
<div class="d-flex gap-3 mt-3 mt-sm-0"> <div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary">Send</button> <a asp-action="Send" asp-controller="Breez" asp-route-storeId="@storeId" type="submit" class="btn btn-primary">Send</a>
<button type="submit" class="btn btn-primary">Receive</button> <a asp-action="Receive" asp-controller="Breez" asp-route-storeId="@storeId" type="submit" class="btn btn-primary">Receive</a>
</div> </div>
</div> </div>
@if (bolt11 is string bolt11s)
{
<div class="payment-box">
<div class="qr-container" data-clipboard="@bolt11s">
<vc:qr-code data="@bolt11s"/>
</div>
<div class="input-group mt-3">
<div class="form-floating">
<vc:truncate-center text="@bolt11s" padding="15" elastic="true" classes="form-control-plaintext" id="Address"/>
<label for="Address">BOLT11 Invoice</label>
</div>
</div>
</div>
}
<partial name="Breez/BreezPaymentsTable" model="Model.Payments"/> <partial name="Breez/BreezPaymentsTable" model="Model.Payments"/>
<vc:pager view-model="Model"></vc:pager> <vc:pager view-model="Model"></vc:pager>
</div> </div>

View File

@@ -0,0 +1,44 @@
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@inject BreezService BreezService
@{
ViewData.SetActivePage("Breez", "Receive", "Receive");
var storeId = Model switch
{
string s => s,
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId()
};
var sdk = BreezService.GetClient(storeId)?.Sdk;
if (sdk is null)
return;
var nodeState = sdk.NodeInfo();
var max = nodeState.maxReceivableMsat / 1000;
}
<form method="post" asp-action="Receive" asp-route-storeId="@storeId">
<div class="row mb-4">
<div class="col-12">
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">
<span>@ViewData["Title"]</span>
</h3>
<div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary">Receive</button>
</div>
</div>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label for="amount" class="form-label">Amount (sats)</label>
<input type="number"id="amount" min="0" max="@max" name="amount" class="form-control"/>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,48 @@
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@inject BreezService BreezService
@{
ViewData.SetActivePage("Breez", "Send", "Send");
var storeId = Model switch
{
string s => s,
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId()
};
var sdk = BreezService.GetClient(storeId)?.Sdk;
if (sdk is null)
return;
var nodeState = sdk.NodeInfo();
var max = nodeState.maxPayableMsat / 1000;
}
<form method="post" asp-action="Send" asp-route-storeId="@storeId">
<div class="row mb-4">
<div class="col-12">
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">
<span>@ViewData["Title"]</span>
</h3>
<div class="d-flex gap-3 mt-3 mt-sm-0">
<button type="submit" class="btn btn-primary">Send</button>
</div>
</div>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label for="address" class="form-label" data-required>Bolt11 or node Id</label>
<input type="text" id="address" name="address" class="form-control" required/>
</div>
<div class="form-group">
<label for="amount" class="form-label">Amount (sats) (required for 0 amount bolt11 or nodeid payments)</label>
<input type="number"id="amount" max="@max" name="amount" class="form-control"/>
</div>
</div>
</div>
</form>

View File

@@ -23,7 +23,15 @@
if (sdk is null) if (sdk is null)
return; return;
var inProgressSwap = sdk.InProgressSwap(); SwapInfo inProgressSwap = null;
try
{
inProgressSwap = sdk.InProgressSwap();
inProgressSwap ??= sdk.ReceiveOnchain(new ReceiveOnchainRequest());
}
catch (Exception e)
{
}
var refundables = sdk.ListRefundables(); var refundables = sdk.ListRefundables();
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC"); var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
var ni = sdk.NodeInfo(); var ni = sdk.NodeInfo();
@@ -46,12 +54,6 @@
<h3 class="mb-0"> <h3 class="mb-0">
<span>@ViewData["Title"]</span> <span>@ViewData["Title"]</span>
</h3> </h3>
<div class="d-flex gap-3 mt-3 mt-sm-0">
@if (inProgressSwap is null)
{
<a class="btn btn-primary" asp-action="SwapInCreate" asp-controller="Breez" asp-route-storeId="@storeId">Create</a>
}
</div>
</div> </div>
@if (inProgressSwap is not null) @if (inProgressSwap is not null)
@@ -65,19 +67,20 @@
<vc:truncate-center text="@inProgressSwap.bitcoinAddress" padding="15" elastic="true" classes="form-control-plaintext" id="Address"/> <vc:truncate-center text="@inProgressSwap.bitcoinAddress" padding="15" elastic="true" classes="form-control-plaintext" id="Address"/>
<label for="Address">Address</label> <label for="Address">Address</label>
</div> </div>
<span class="">Please send an amount between @Money.Satoshis(inProgressSwap.minAllowedDeposit).ToDecimal(MoneyUnit.BTC) BTC and @Money.Satoshis(inProgressSwap.maxAllowedDeposit).ToDecimal(MoneyUnit.BTC) </span> <span class="text-muted">Please send an amount between <br/> @Money.Satoshis(inProgressSwap.minAllowedDeposit).ToDecimal(MoneyUnit.BTC) and @Money.Satoshis(inProgressSwap.maxAllowedDeposit).ToDecimal(MoneyUnit.BTC)BTC </span>
@if (deriv is not null) @if (deriv is not null)
{ {
<a class="btn btn-primary" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@Model.WalletId" asp-route-defaultDestination="@inProgressSwap.bitcoinAddress">Send using BTCPay Wallet</a> var wallet = new WalletId(storeId, "BTC");
<a class="btn btn-link w-100" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@wallet" asp-route-defaultDestination="@inProgressSwap.bitcoinAddress">Send using BTCPay Wallet</a>
} }
@{ @{
var onChainSats = ni.onchainBalanceMsat / 1000; var onChainSats = ni.onchainBalanceMsat / 1000;
if (inProgressSwap.minAllowedDeposit <= (long) onChainSats) if (inProgressSwap.minAllowedDeposit <= (long) onChainSats)
{ {
<div> <div class="w-100">
<form asp-action="Sweep" asp-route-storeId="@storeId"> <form asp-action="Sweep" asp-route-storeId="@storeId">
<input type="hidden" value="@inProgressSwap.bitcoinAddress" name="address"/> <input type="hidden" value="@inProgressSwap.bitcoinAddress" name="address"/>
<button type="submit"> Sweep onchain funds to swap in</button> <button class="btn btn-link w-100" type="submit"> Sweep onchain funds to swap in</button>
</form> </form>
</div> </div>
} }

View File

@@ -27,7 +27,6 @@
var sdk = BreezService.GetClient(storeId)?.Sdk; var sdk = BreezService.GetClient(storeId)?.Sdk;
if (sdk is null) if (sdk is null)
return; return;
var inProgressSwaps = sdk.InProgressReverseSwaps(); var inProgressSwaps = sdk.InProgressReverseSwaps();
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC"); var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
var f = sdk.RecommendedFees(); var f = sdk.RecommendedFees();
@@ -63,11 +62,11 @@
<div asp-validation-summary="ModelOnly" class="text-danger"></div> <div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group"> <div class="form-group">
<label for="address" class="form-label" data-required>Address</label> <label for="address" class="form-label" data-required>Address</label>
<input type="text" id="address" name="address" class="form-control" required/> <input type="text" id="address" list="addresses" name="address" class="form-control" required/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="satPerByte" class="form-label" data-required>Refund address</label> <label for="satPerByte" class="form-label" data-required>Feerate</label>
<input type="number" min="@f.minimumFee" list="satPerByte" id="satPerByte" name="satPerByte" class="form-control" required/> <input type="number" min="@f.minimumFee" list="fees" id="satPerByte" name="satPerByte" class="form-control" required/>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="amount" class="form-label" data-required>Amount (sats)</label> <label for="amount" class="form-label" data-required>Amount (sats)</label>

View File

@@ -24,8 +24,8 @@
{ {
<a asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a> <a asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a>
<a asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Payments")">Payments</a> <a asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Payments")">Payments</a>
<a asp-action="SwapIn" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapIn")">Swap Ins</a> <a asp-action="SwapIn" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapIn")">Swap In</a>
<a asp-action="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Outs</a> <a asp-action="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Out</a>
} }
<a asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a> <a asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
</div> </div>

View File

@@ -91,6 +91,10 @@
<h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.maxReceivableMsat)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.maxReceivableMsat)</h3> <h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.maxReceivableMsat)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.maxReceivableMsat)</h3>
<span class="text-secondary fw-semibold currency">BTC receivable</span> <span class="text-secondary fw-semibold currency">BTC receivable</span>
</div> </div>
<div class="balance d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.inboundLiquidityMsats)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.inboundLiquidityMsats)</h3>
<span class="text-secondary fw-semibold currency">BTC inbound liquidity</span>
</div>
<div class="balance d-flex align-items-baseline gap-1"> <div class="balance d-flex align-items-baseline gap-1">
<h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.maxPayableMsat)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.maxPayableMsat)</h3> <h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.maxPayableMsat)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.maxPayableMsat)</h3>
<span class="text-secondary fw-semibold currency">BTC spendable</span> <span class="text-secondary fw-semibold currency">BTC spendable</span>

View File

@@ -1,10 +1,9 @@
 @inject BreezService BreezService;
@inject BreezService BreezService;
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel @model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel
@{ @{
var storeId = Model.StoreId; var storeId = Model.StoreId;
if(Model.CryptoCode != "BTC") if (Model.CryptoCode != "BTC")
{ {
return; return;
} }
@@ -14,16 +13,16 @@
<div id="BreezSetup" class="pt-3 tab-pane fade" role="tabpanel" aria-labelledby="LightningNodeType-Breez"> <div id="BreezSetup" class="pt-3 tab-pane fade" role="tabpanel" aria-labelledby="LightningNodeType-Breez">
@if (client is not null) @if (client is not null)
{ {
} }
else else
{ {
<a asp-action="Configure" asp-controller="Breez" asp-route-storeId="@storeId">Breez needs to be configured beforehand.</a> <a asp-action="Configure" asp-controller="Breez" asp-route-storeId="@storeId">Breez needs to be configured beforehand.</a>
} }
</div> </div>
@if (client is not null)
<script> {
const typePrefix = 'type=breez;store=@Model.StoreId'; <script>
const typePrefix = 'type=breez;key=@client.PaymentKey';
const triggerEl = document.getElementById('LightningNodeType-Breez') const triggerEl = document.getElementById('LightningNodeType-Breez')
const connStringEl = document.getElementById('ConnectionString') const connStringEl = document.getElementById('ConnectionString')
const connString = connStringEl.value; const connString = connStringEl.value;
@@ -62,4 +61,4 @@
}) })
}) })
</script> </script>
}

View File

@@ -1,2 +1,9 @@
@addTagHelper *, BTCPayServer.Abstractions @using BTCPayServer.Abstractions.Extensions
@inject BTCPayServer.Abstractions.Services.Safe Safe
@addTagHelper *, BTCPayServer.Abstractions
@addTagHelper *, BTCPayServer.TagHelpers
@addTagHelper *, BTCPayServer.Views.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BTCPayServer

View File

@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
</PropertyGroup>
<!-- Plugin specific properties -->
<PropertyGroup>
<Product>LDK</Product>
<Description>The way lightning's meant to be</Description>
<Version>1.0.0</Version>
</PropertyGroup>
<!-- Plugin development properties -->
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<PreserveCompilationContext>false</PreserveCompilationContext>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<!-- This will make sure that referencing BTCPayServer doesn't put any artifact in the published directory -->
<ItemDefinitionGroup>
<ProjectReference>
<Properties>StaticWebAssetsEnabled=false</Properties>
<Private>false</Private>
<ExcludeAssets>runtime;native;build;buildTransitive;contentFiles</ExcludeAssets>
</ProjectReference>
</ItemDefinitionGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**" />
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="org.ldk" Version="0.0.118-alpha1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,232 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Runtime.Internal.Util;
using BTCPayServer;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Services;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NBitcoin;
using NBXplorer;
using org.ldk.enums;
using org.ldk.structs;
using enums_Network = org.ldk.enums.Network;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Logger = org.ldk.structs.Logger;
using Network = NBitcoin.Network;
using OutPoint = org.ldk.structs.OutPoint;
using Path = System.IO.Path;
public class LDKService : IHostedService, PersistInterface, BroadcasterInterfaceInterface, FeeEstimatorInterface, EventHandlerInterface, LoggerInterface, FilterInterface
{
private readonly ILogger<LDKService> _logger;
private readonly IFeeProviderFactory _feeProviderFactory;
private readonly IOptions<DataDirectories> _dataDirectories;
private readonly BTCPayNetwork _network;
private readonly ExplorerClient _explorerClient;
private readonly string _workDir;
private readonly enums_Network _ldkNetwork;
private readonly Logger _ldklogger;
private readonly FeeEstimator _ldkfeeEstimator;
private readonly BroadcasterInterface _ldkbroadcaster;
private readonly Persist _ldkpersist;
private readonly Filter _ldkfilter;
private readonly NetworkGraph _ldkNetworkGraph;
private readonly ChainMonitor _ldkChainMonitor;
public LDKService(BTCPayNetworkProvider btcPayNetworkProvider,
ExplorerClientProvider explorerClientProvider,
ILogger<LDKService> logger,
IFeeProviderFactory feeProviderFactory,
IOptions<DataDirectories> dataDirectories)
{
_logger = logger;
_feeProviderFactory = feeProviderFactory;
_dataDirectories = dataDirectories;
_network = btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
_explorerClient = explorerClientProvider.GetExplorerClient(_network);
_workDir = GetWorkDir();
Directory.CreateDirectory(_workDir);
_ldkNetwork = GetLdkNetwork(_network.NBitcoinNetwork);
_ldklogger = Logger.new_impl(this);
_ldkfeeEstimator = FeeEstimator.new_impl(this);
_ldkbroadcaster = BroadcasterInterface.new_impl(this);
_ldkpersist = Persist.new_impl(this);
_ldkfilter = Filter.new_impl(this);
_ldkNetworkGraph = NetworkGraph.of(_ldkNetwork, _ldklogger);
_ldkChainMonitor = ChainMonitor.of( Option_FilterZ.Option_FilterZ_Some.some(_ldkfilter), _ldkbroadcaster, _ldklogger, _ldkfeeEstimator, _ldkpersist);
}
private static enums_Network GetLdkNetwork(Network network)
{
enums_Network? ldkNetwork = null;
if (network.ChainName == ChainName.Mainnet)
ldkNetwork = org.ldk.enums.Network.LDKNetwork_Bitcoin;
else if (network.ChainName == ChainName.Testnet)
ldkNetwork = org.ldk.enums.Network.LDKNetwork_Testnet;
else if (network.ChainName == ChainName.Regtest)
ldkNetwork = org.ldk.enums.Network.LDKNetwork_Regtest;
return ldkNetwork ?? throw new NotSupportedException();
}
private string GetWorkDir()
{
var dir = _dataDirectories.Value.DataDir;
return Path.Combine(dir, "Plugins", "LDK");
}
public async Task StartAsync(CancellationToken cancellationToken)
{
}
public async Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public int get_est_sat_per_1000_weight(ConfirmationTarget confirmation_target)
{
var feeProvider = _feeProviderFactory.CreateFeeProvider(_network);
var targetBlocks = confirmation_target switch
{
ConfirmationTarget.LDKConfirmationTarget_OnChainSweep => 30, // High priority (10-50 blocks)
ConfirmationTarget
.LDKConfirmationTarget_MaxAllowedNonAnchorChannelRemoteFee =>
20, // Moderate to high priority (small multiple of high-priority estimate)
ConfirmationTarget
.LDKConfirmationTarget_MinAllowedAnchorChannelRemoteFee =>
12, // Moderate priority (long-term mempool minimum or medium-priority)
ConfirmationTarget
.LDKConfirmationTarget_MinAllowedNonAnchorChannelRemoteFee =>
12, // Moderate priority (medium-priority feerate)
ConfirmationTarget.LDKConfirmationTarget_AnchorChannelFee => 6, // Lower priority (can be bumped later)
ConfirmationTarget
.LDKConfirmationTarget_NonAnchorChannelFee => 20, // Moderate to high priority (high-priority feerate)
ConfirmationTarget.LDKConfirmationTarget_ChannelCloseMinimum => 144, // Within a day or so (144-250 blocks)
_ => throw new ArgumentOutOfRangeException(nameof(confirmation_target), confirmation_target, null)
};
return (int) Math.Max(253, feeProvider.GetFeeRateAsync(targetBlocks).GetAwaiter().GetResult().FeePerK.Satoshi);
}
public void log(Record record)
{
var level = record.get_level() switch
{
Level.LDKLevel_Trace => LogLevel.Trace,
Level.LDKLevel_Debug => LogLevel.Debug,
Level.LDKLevel_Info => LogLevel.Information,
Level.LDKLevel_Warn => LogLevel.Warning,
Level.LDKLevel_Error => LogLevel.Error,
Level.LDKLevel_Gossip => LogLevel.Trace,
};
_logger.Log(level, $"[{record.get_module_path()}] {record.get_args()}");
}
public void broadcast_transactions(byte[][] txs)
{
foreach (var tx in txs)
{
var loadedTx = Transaction.Load(tx, _explorerClient.Network.NBitcoinNetwork);
_explorerClient.Broadcast(loadedTx);
}
}
public ChannelMonitorUpdateStatus persist_new_channel(OutPoint channel_id, ChannelMonitor data,
MonitorUpdateId update_id)
{
var name = Convert.ToHexString(channel_id.write());
File.WriteAllBytes(Path.Combine(_workDir, name), data.write());
return ChannelMonitorUpdateStatus.LDKChannelMonitorUpdateStatus_Completed;
}
public ChannelMonitorUpdateStatus update_persisted_channel(OutPoint channel_id, ChannelMonitorUpdate update,
ChannelMonitor data, MonitorUpdateId update_id)
{
var name = Convert.ToHexString(channel_id.write());
File.WriteAllBytes(Path.Combine(_workDir, name), data.write());
return ChannelMonitorUpdateStatus.LDKChannelMonitorUpdateStatus_Completed;
}
public void handle_event(Event _event)
{
switch (_event)
{
case Event.Event_BumpTransaction eventBumpTransaction:
switch (eventBumpTransaction.bump_transaction)
{
case BumpTransactionEvent.BumpTransactionEvent_ChannelClose bumpTransactionEventChannelClose:
break;
case BumpTransactionEvent.BumpTransactionEvent_HTLCResolution bumpTransactionEventHtlcResolution:
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
case Event.Event_ChannelClosed eventChannelClosed:
break;
case Event.Event_ChannelPending eventChannelPending:
break;
case Event.Event_ChannelReady eventChannelReady:
break;
case Event.Event_DiscardFunding eventDiscardFunding:
break;
case Event.Event_FundingGenerationReady eventFundingGenerationReady:
break;
case Event.Event_HTLCHandlingFailed eventHtlcHandlingFailed:
break;
case Event.Event_HTLCIntercepted eventHtlcIntercepted:
break;
case Event.Event_InvoiceRequestFailed eventInvoiceRequestFailed:
break;
case Event.Event_OpenChannelRequest eventOpenChannelRequest:
break;
case Event.Event_PaymentClaimable eventPaymentClaimable:
break;
case Event.Event_PaymentClaimed eventPaymentClaimed:
break;
case Event.Event_PaymentFailed eventPaymentFailed:
break;
case Event.Event_PaymentForwarded eventPaymentForwarded:
break;
case Event.Event_PaymentPathFailed eventPaymentPathFailed:
break;
case Event.Event_PaymentPathSuccessful eventPaymentPathSuccessful:
break;
case Event.Event_PaymentSent eventPaymentSent:
break;
case Event.Event_PendingHTLCsForwardable eventPendingHtlCsForwardable:
break;
case Event.Event_ProbeFailed eventProbeFailed:
break;
case Event.Event_ProbeSuccessful eventProbeSuccessful:
break;
case Event.Event_SpendableOutputs eventSpendableOutputs:
break;
default:
throw new ArgumentOutOfRangeException(nameof(_event));
}
}
public void register_tx(byte[] txid, byte[] script_pubkey)
{
throw new NotImplementedException();
}
public void register_output(WatchedOutput output)
{
throw new NotImplementedException();
}
}