mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
wip
This commit is contained in:
@@ -54,6 +54,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Dynami
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Bringin", "Plugins\BTCPayServer.Plugins.Bringin\BTCPayServer.Plugins.Bringin.csproj", "{D4AFEC95-64D4-4FC4-9AE4-B82F4C6D6E29}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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-Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}
|
||||
|
||||
@@ -5,10 +5,13 @@ using System.Threading.Tasks;
|
||||
using Breez.Sdk;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
|
||||
namespace BTCPayServer.Plugins.Breez;
|
||||
@@ -108,8 +111,8 @@ public class BreezController : Controller
|
||||
return View((object) storeId);
|
||||
}
|
||||
|
||||
[HttpGet("swapin/create")]
|
||||
public async Task<IActionResult> SwapInCreate(string storeId)
|
||||
[HttpGet("send")]
|
||||
public async Task<IActionResult> Send(string storeId)
|
||||
{
|
||||
var client = _breezService.GetClient(storeId);
|
||||
if (client is null)
|
||||
@@ -117,9 +120,85 @@ public class BreezController : Controller
|
||||
return RedirectToAction(nameof(Configure), new {storeId});
|
||||
}
|
||||
|
||||
client.Sdk.ReceiveOnchain(new ReceiveOnchainRequest());
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Swapin created successfully";
|
||||
return RedirectToAction(nameof(SwapIn), new {storeId});
|
||||
return View((object) 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));
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[HttpPost("configure")]
|
||||
public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings)
|
||||
{
|
||||
if (command == "clear")
|
||||
@@ -244,9 +323,10 @@ public class BreezController : Controller
|
||||
{
|
||||
return RedirectToAction(nameof(Configure), new {storeId});
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
return View(viewModel);
|
||||
|
||||
@@ -10,34 +10,43 @@ using Network = Breez.Sdk.Network;
|
||||
|
||||
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;
|
||||
public readonly string PaymentKey;
|
||||
|
||||
public BreezLightningClient(string inviteCode, string apiKey, string workingDir, NBitcoin.Network network,
|
||||
string mnemonic)
|
||||
string mnemonic, string paymentKey)
|
||||
{
|
||||
_network = network;
|
||||
PaymentKey = paymentKey;
|
||||
var nodeConfig = new NodeConfig.Greenlight(
|
||||
new GreenlightNodeConfig(null, inviteCode)
|
||||
);
|
||||
var config = BreezSdkMethods.DefaultConfig(
|
||||
network ==NBitcoin.Network.Main ? EnvironmentType.PRODUCTION: EnvironmentType.STAGING,
|
||||
network == NBitcoin.Network.Main ? EnvironmentType.PRODUCTION : EnvironmentType.STAGING,
|
||||
apiKey,
|
||||
nodeConfig
|
||||
) with {
|
||||
workingDir= workingDir,
|
||||
network = network == NBitcoin.Network.Main ? Network.BITCOIN : network == NBitcoin.Network.TestNet ? Network.TESTNET: network == NBitcoin.Network.RegTest? Network.REGTEST: Network.SIGNET
|
||||
) with
|
||||
{
|
||||
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);
|
||||
Sdk = BreezSdkMethods.Connect(config, seed, this);
|
||||
|
||||
}
|
||||
|
||||
public BlockingBreezServices Sdk { get; }
|
||||
|
||||
public event EventHandler<BreezEvent> EventReceived;
|
||||
|
||||
public void OnEvent(BreezEvent e)
|
||||
{
|
||||
EventReceived?.Invoke(this, e);
|
||||
@@ -73,15 +82,16 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
Fee = LightMoney.MilliSatoshis(payment.feeMsat),
|
||||
AmountSent = LightMoney.MilliSatoshis(payment.amountMsat)
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
private LightningInvoice FromPayment(Payment p)
|
||||
{
|
||||
|
||||
if (p?.details is not PaymentDetails.Ln lnPaymentDetails)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bolt11 = BOLT11PaymentRequest.Parse(lnPaymentDetails.data.bolt11, _network);
|
||||
|
||||
return new LightningInvoice()
|
||||
@@ -97,7 +107,7 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
PaymentStatus.COMPLETE => LightningInvoiceStatus.Paid,
|
||||
_ => LightningInvoiceStatus.Unpaid
|
||||
},
|
||||
PaidAt = DateTimeOffset.FromUnixTimeMilliseconds(p.paymentTime),
|
||||
PaidAt = DateTimeOffset.FromUnixTimeSeconds(p.paymentTime),
|
||||
ExpiresAt = bolt11.ExpiryDate
|
||||
};
|
||||
}
|
||||
@@ -105,18 +115,28 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, CancellationToken cancellation = default)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoice[]> ListInvoices(CancellationToken cancellation = default)
|
||||
{
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -130,26 +150,48 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
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 p = Sdk.ReceivePayment(new ReceivePaymentRequest((ulong)amount.MilliSatoshi, description, null, null, false,expiryS ));
|
||||
return await GetInvoice(p.lnInvoice.paymentHash, cancellation);
|
||||
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,
|
||||
false, expiryS));
|
||||
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);
|
||||
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);
|
||||
return new LightningInvoice()
|
||||
{
|
||||
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)
|
||||
@@ -159,14 +201,16 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
|
||||
public async Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = default)
|
||||
{
|
||||
|
||||
var ni = Sdk.NodeInfo();
|
||||
return new LightningNodeInformation()
|
||||
{
|
||||
PeersCount = ni.connectedPeers.Count,
|
||||
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
|
||||
BlockHeight = (int)ni.blockHeight
|
||||
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
|
||||
BlockHeight = (int) ni.blockHeight
|
||||
};
|
||||
}
|
||||
|
||||
@@ -194,9 +238,53 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
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)
|
||||
@@ -204,7 +292,8 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
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();
|
||||
}
|
||||
@@ -221,7 +310,6 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
|
||||
public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = default)
|
||||
{
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -236,7 +324,7 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
Sdk.Dispose();
|
||||
}
|
||||
|
||||
public class BreezInvoiceListener: ILightningInvoiceListener
|
||||
public class BreezInvoiceListener : ILightningInvoiceListener
|
||||
{
|
||||
private readonly BreezLightningClient _breezLightningClient;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
@@ -266,17 +354,18 @@ public class BreezLightningClient: ILightningClient, IDisposable, EventListener
|
||||
|
||||
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))
|
||||
{
|
||||
return await task.WithCancellation(cancellation);
|
||||
}
|
||||
|
||||
await Task.Delay(100, cancellation);
|
||||
}
|
||||
|
||||
cancellation.ThrowIfCancellationRequested();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,18 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Plugins.Breez;
|
||||
|
||||
public class BreezService:IHostedService
|
||||
public class BreezService:EventHostedServiceBase
|
||||
{
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly IOptions<DataDirectories> _dataDirectories;
|
||||
@@ -26,10 +27,12 @@ public class BreezService:IHostedService
|
||||
private Dictionary<string, BreezSettings> _settings;
|
||||
private Dictionary<string, BreezLightningClient> _clients = new();
|
||||
|
||||
public BreezService(StoreRepository storeRepository,
|
||||
public BreezService(
|
||||
EventAggregator eventAggregator,
|
||||
StoreRepository storeRepository,
|
||||
IOptions<DataDirectories> dataDirectories,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
ILogger<BreezService> logger)
|
||||
ILogger<BreezService> logger) : base(eventAggregator, logger)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_dataDirectories = dataDirectories;
|
||||
@@ -37,6 +40,22 @@ public class BreezService:IHostedService
|
||||
_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()
|
||||
{
|
||||
var dir = _dataDirectories.Value.DataDir;
|
||||
@@ -44,7 +63,7 @@ public class BreezService:IHostedService
|
||||
}
|
||||
|
||||
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!);
|
||||
foreach (var keyValuePair in _settings)
|
||||
@@ -59,6 +78,7 @@ public class BreezService:IHostedService
|
||||
}
|
||||
}
|
||||
tcs.TrySetResult();
|
||||
await base.StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<BreezSettings?> Get(string storeId)
|
||||
@@ -85,7 +105,7 @@ public class BreezService:IHostedService
|
||||
var network = Network.Main; // _btcPayNetworkProvider.BTC.NBitcoinNetwork;
|
||||
Directory.CreateDirectory(GetWorkDir());
|
||||
var client = new BreezLightningClient(settings.InviteCode, settings.ApiKey, GetWorkDir(),
|
||||
network, settings.Mnemonic);
|
||||
network, settings.Mnemonic, settings.PaymentKey);
|
||||
if (storeId is not null)
|
||||
{
|
||||
_clients.AddOrReplace(storeId, client);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
var storeId = Context.GetCurrentStoreId();
|
||||
|
||||
ViewData.SetActivePage("Breez", "Payments", "Payments");
|
||||
TempData.TryGetValue("bolt11", out var bolt11);
|
||||
}
|
||||
|
||||
<div class="row mb-4">
|
||||
@@ -16,11 +17,28 @@
|
||||
</h3>
|
||||
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||
|
||||
<button type="submit" class="btn btn-primary">Send</button>
|
||||
<button type="submit" class="btn btn-primary">Receive</button>
|
||||
<a asp-action="Send" asp-controller="Breez" asp-route-storeId="@storeId" type="submit" class="btn btn-primary">Send</a>
|
||||
<a asp-action="Receive" asp-controller="Breez" asp-route-storeId="@storeId" type="submit" class="btn btn-primary">Receive</a>
|
||||
</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"/>
|
||||
<vc:pager view-model="Model"></vc:pager>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
48
Plugins/BTCPayServer.Plugins.Breez/Views/Breez/Send.cshtml
Normal file
48
Plugins/BTCPayServer.Plugins.Breez/Views/Breez/Send.cshtml
Normal 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>
|
||||
@@ -23,7 +23,15 @@
|
||||
if (sdk is null)
|
||||
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 deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
|
||||
var ni = sdk.NodeInfo();
|
||||
@@ -46,12 +54,6 @@
|
||||
<h3 class="mb-0">
|
||||
<span>@ViewData["Title"]</span>
|
||||
</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>
|
||||
|
||||
@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"/>
|
||||
<label for="Address">Address</label>
|
||||
</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)
|
||||
{
|
||||
<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;
|
||||
if (inProgressSwap.minAllowedDeposit <= (long) onChainSats)
|
||||
{
|
||||
<div>
|
||||
<div class="w-100">
|
||||
<form asp-action="Sweep" asp-route-storeId="@storeId">
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
var sdk = BreezService.GetClient(storeId)?.Sdk;
|
||||
if (sdk is null)
|
||||
return;
|
||||
|
||||
var inProgressSwaps = sdk.InProgressReverseSwaps();
|
||||
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
|
||||
var f = sdk.RecommendedFees();
|
||||
@@ -63,11 +62,11 @@
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<label for="satPerByte" class="form-label" data-required>Refund address</label>
|
||||
<input type="number" min="@f.minimumFee" list="satPerByte" id="satPerByte" name="satPerByte" class="form-control" required/>
|
||||
<label for="satPerByte" class="form-label" data-required>Feerate</label>
|
||||
<input type="number" min="@f.minimumFee" list="fees" id="satPerByte" name="satPerByte" class="form-control" required/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="amount" class="form-label" data-required>Amount (sats)</label>
|
||||
|
||||
@@ -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="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="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Outs</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 Out</a>
|
||||
}
|
||||
<a asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
|
||||
</div>
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
@inject BreezService BreezService;
|
||||
@inject BreezService BreezService;
|
||||
@using BTCPayServer.Plugins.Breez
|
||||
@model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel
|
||||
@{
|
||||
var storeId = Model.StoreId;
|
||||
if(Model.CryptoCode != "BTC")
|
||||
if (Model.CryptoCode != "BTC")
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -14,16 +13,16 @@
|
||||
<div id="BreezSetup" class="pt-3 tab-pane fade" role="tabpanel" aria-labelledby="LightningNodeType-Breez">
|
||||
@if (client is not null)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-action="Configure" asp-controller="Breez" asp-route-storeId="@storeId">Breez needs to be configured beforehand.</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const typePrefix = 'type=breez;store=@Model.StoreId';
|
||||
@if (client is not null)
|
||||
{
|
||||
<script>
|
||||
const typePrefix = 'type=breez;key=@client.PaymentKey';
|
||||
const triggerEl = document.getElementById('LightningNodeType-Breez')
|
||||
const connStringEl = document.getElementById('ConnectionString')
|
||||
const connString = connStringEl.value;
|
||||
@@ -62,4 +61,4 @@
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
}
|
||||
@@ -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 *, BTCPayServer
|
||||
@@ -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>
|
||||
|
||||
232
Plugins/BTCPayServer.Plugins.LDK/Program.cs
Normal file
232
Plugins/BTCPayServer.Plugins.LDK/Program.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user